Files
sodalive-ios/docs/20260319_라이브룸채팅창얼리기기능구현계획.md
Yu Sung 70003af82b feat(live-room): 채팅창 얼리기 기능을 추가한다
채팅 입력 제어와 룸 상태 동기화를 통합해 지연 입장자도 동일 상태를 적용한다.
2026-03-19 18:20:13 +09:00

19 KiB

20260319_라이브룸채팅창얼리기기능구현계획.md

개요

  • LiveRoomViewV2 기반 iOS 라이브룸에 채팅창 얼리기(Freeze) 토글을 추가한다.
  • 채팅창 얼리기 상태에서는 방장을 제외한 모든 사용자가 채팅 입력/전송을 할 수 없어야 한다.
  • 상태는 지연 입장 사용자까지 일관되게 적용되도록 ROOM_INFO 기반으로 동기화한다.
  • 본 문서는 구현 계획 및 실행 추적 문서이며, 체크리스트/검증 기록을 통해 실제 반영 상태를 함께 관리한다.

요구사항 요약

  • 토글 버튼 위치: LiveRoomInfoHostView 상단 토글 영역의 시그 ON/OFF 버튼 왼쪽.
  • 얼림(ON): 방장을 제외한 전체 유저 채팅 입력 불가(포커스/입력/전송 모두 차단).
  • 녹임(OFF): 채팅금지 해제와 동일하게 즉시 채팅 가능 상태로 복귀.
  • 상태 메시지: 얼림/녹임 시 모든 유저 채팅 리스트에 시스템 상태 메시지 1회 노출.
  • 상태 메시지 UI: 사용자 입장 알림(LiveRoomJoinChatItemView)과 동일한 스타일 사용.
  • 지연 입장: 채팅창이 얼려진 상태로 입장한 사용자도 즉시 상태를 받아 입력 불가여야 함.
  • 지연 입장 + 얼림 상태: ROOM_INFO.isChatFrozen == true인 경우 채팅 리스트에 얼림 상태 메시지를 즉시 1회 노출해야 함.
  • 얼림 상태 입력 피드백: 사용자가 채팅 입력 영역(입력창/전송 버튼)을 터치하면 차단 안내 토스트를 노출해야 함.
  • 얼림 상태 포커스 UX: 키보드가 올라오지 않는 상황에서 레이아웃이 키보드 높이만큼 밀리지 않아야 함.
  • 상단 토글 라벨은 얼림 ON/OFF 문구를 사용한다.
  • 상태 변경 패턴: 룰렛과 동일하게 서버 API 선반영 후, 성공 시 RTM 브로드캐스트 전파.

상태 저장 전략 판단 (ROOM_INFO vs 별도 상태)

결론

  • ROOM_INFOisChatFrozen 상태를 저장하고, RTM 이벤트는 즉시 반영용으로 병행한다.

판단 근거

  • LiveRoomViewModel.getRoomInfoliveRoomInfo를 갱신하며 룸 전역 상태(isActiveRoulette)를 적용한다.
  • rtmKit(_:didReceivePresenceEvent:)remoteJoin 시점에 getRoomInfo(userId:onSuccess:)를 재호출해 지연 입장 상태를 복원한다.
  • 룸 전역 상태 선례가 이미 존재한다.
    • GetRoomInfoResponse.isActiveRoulette
    • LiveRoomChatRawMessageType.TOGGLE_ROULETTE
    • LiveRoomViewModel RTM 수신 분기(decoded.type == .TOGGLE_ROULETTE)
  • 기존 NO_CHATTINGUserDefaults.noChatRoomList 기반 로컬 제어라 방 전체 상태의 단일 진실원천(SSOT)으로는 부적합하다.

외부 레퍼런스(요약)

완료 기준 (Acceptance Criteria)

  • AC1: 방장이 얼림 ON 시 방장을 제외한 사용자는 LiveRoomInputChatView에서 포커스/입력/전송이 모두 불가능하다.
  • AC2: 방장이 얼림 OFF 시 방장을 제외한 사용자의 채팅 입력/전송이 즉시 복구된다.
  • AC3: 얼림/녹임 이벤트마다 모든 사용자 채팅 리스트에 시스템 상태 메시지가 1회씩 노출된다.
  • AC4: 얼림 상태에서 새로 입장한 사용자는 입장 직후 입력 불가 상태를 즉시 적용받는다.
  • AC5: 얼리기 토글 버튼은 LiveRoomInfoHostView에서 시그 ON/OFF 버튼의 왼쪽에 배치된다.
  • AC6: 방장 얼림 ON/OFF 시 서버 API가 선행 호출되고, 성공한 경우에만 RTM 상태 브로드캐스트가 전송된다.
  • AC7: 지연 입장 시 ROOM_INFO.isChatFrozen == true이면 채팅 리스트에 얼림 상태 메시지가 1회 표시된다.
  • AC8: 채팅 얼림 상태에서 입력 영역 터치 시 chatFreezeBlockedMessage 토스트가 표시된다.
  • AC9: 채팅 얼림 상태 입력 포커스 시 키보드 미노출 상태에서 화면 오프셋 밀림이 발생하지 않는다.
  • AC10: 지연 입장으로 얼림 상태를 받은 사용자는 방장 해제(RTM TOGGLE_CHAT_FREEZE=false) 직후 입력이 즉시 가능해야 한다.

구현 체크리스트

1) UI/입력 제어

  • SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInfoHostView.swift에 얼리기 토글 UI 및 콜백 추가(onClickToggleSignature 왼쪽 배치).
  • SodaLive/Sources/Live/Room/V2/LiveRoomViewV2.swift에서 호스트 토글 액션 바인딩(LiveRoomInfoHostView 인자 확장).
  • SodaLive/Sources/Live/Room/LiveRoomViewModel.swift에 전역 상태(isChatFrozen) 및 권한 판별(방장 제외 차단) 로직 추가.
  • SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInputChatView.swiftSodaLive/Sources/CustomView/ChatTextFieldView.swift에 비활성 상태 반영(입력/전송 버튼 차단).
  • LiveRoomViewModel.sendMessage 경로에 Freeze 가드 추가(서버/RTM 지연 시에도 전송 방지).
  • SodaLive/Sources/Live/Room/V2/LiveRoomViewV2.swift 키보드 오프셋 계산에 isChatFrozenForCurrentUser 가드를 추가해 얼림 상태에서 레이아웃 밀림을 차단.
  • SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInputChatView.swift에 입력창/전송 버튼 터치 차단 콜백을 추가해 얼림 상태 터치 시 토스트를 노출.
  • SodaLive/Sources/CustomView/ChatTextFieldView.swift에서 Coordinator가 최신 isEnabled를 참조하도록 상태 동기화 보완(지연 입장 후 해제 불가 버그 수정).

2) 상태 전파/수신

  • 서버 API 경로 추가: 얼림 상태 변경 endpoint 및 request DTO 추가(LiveApi/LiveRepository/request 모델).
  • 방장 토글 액션은 API 성공 콜백에서만 RTM 브로드캐스트를 전송하도록 순서 보장(룰렛과 동일).
  • API 실패 시 RTM 미전송 + 오류 메시지 표시 시나리오 반영.
  • LiveRoomChatRawMessageType에 Freeze 이벤트 타입 추가(예: TOGGLE_CHAT_FREEZE).
  • LiveRoomChatRawMessage에 Freeze 상태 전달 필드 추가(예: isChatFrozen: Bool?).
  • LiveRoomViewModel.rtmKit(_:didReceiveMessageEvent:)에 Freeze 수신 분기 추가.

3) 지연 입장 동기화

  • SodaLive/Sources/Live/Room/GetRoomInfoResponse.swiftisChatFrozen 필드 추가.
  • LiveRoomViewModel.getRoomInfo에서 isChatFrozen을 UI 입력 제어 상태에 반영.
  • onAppear, didJoinedOfUid, didReceivePresenceEvent(remoteJoin)의 기존 getRoomInfo 재조회 흐름으로 상태 재적용 경로 유지.
  • LiveRoomViewModel.getRoomInfo 초기 동기화에서 isChatFrozen == true일 때 채팅 얼림 상태 메시지를 1회 주입.

4) 시스템 메시지(UI 동일성)

  • 입장 알림과 동일한 스타일을 재사용할 수 있도록 시스템 메시지 모델 경로 확장(LiveRoomChat/LiveRoomJoinChatItemView).
  • 얼림/녹임 메시지를 messages.append(...) + invalidateChat() 경로로 주입.
  • 자기 메시지 self-echo 중복 방지를 위해 발신자 로컬 주입 + 수신 분기에서 self 제외 처리.

5) 문자열/국제화

  • SodaLive/Sources/I18n/I18n.swiftI18n.LiveRoom에 Freeze 토글 라벨 및 상태 메시지 문구 추가.
  • ko/en/ja 3개 언어 키 셋을 동일 범위로 정의.
  • Freeze 토글 라벨을 얼림 ON/OFF 문구로 조정.

6) 검증

  • 정적 진단: 수정 파일 대상 lsp_diagnostics 확인.
  • 빌드: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build.
  • 빌드(개발 스킴): xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build.
  • 테스트 시도: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" testSodaLive-dev test 실행(두 스킴 모두 test action 미구성 확인).
  • 수동 QA: 방장/일반유저 2계정으로 ON/OFF, 지연 입장, 재연결, 메시지 노출 시나리오 검증.

영향 파일(예상)

  • SodaLive/Sources/Live/Room/V2/LiveRoomViewV2.swift
  • SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInfoHostView.swift
  • SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInputChatView.swift
  • SodaLive/Sources/CustomView/ChatTextFieldView.swift
  • SodaLive/Sources/Live/Room/LiveRoomViewModel.swift
  • SodaLive/Sources/Live/Room/Chat/LiveRoomChatRawMessage.swift
  • SodaLive/Sources/Live/Room/Chat/LiveRoomChat.swift
  • SodaLive/Sources/Live/Room/Chat/LiveRoomJoinChatItemView.swift
  • SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomChatView.swift
  • SodaLive/Sources/Live/Room/GetRoomInfoResponse.swift
  • SodaLive/Sources/Live/LiveApi.swift
  • SodaLive/Sources/Live/LiveRepository.swift
  • SodaLive/Sources/Live/Room/SetManagerOrSpeakerOrAudienceRequest.swift (SetChatFreezeRequest 추가)
  • SodaLive/Sources/I18n/I18n.swift

리스크 및 의존성

  • 서버 ROOM_INFO 응답에 isChatFrozen 필드가 제공되지 않으면 지연 입장 정합성 보장이 어렵다.
  • RTM 메시지 단독 구현 시 pub/sub 특성상 지연 입장 사용자에게 과거 상태 스냅샷 누락 위험이 있다.
  • API 성공 이전 RTM 전송 시 서버 상태와 클라이언트 UI 불일치가 발생할 수 있으므로, 전송 순서를 API 성공 이후로 강제해야 한다.
  • 입력 차단을 전송 가드만으로 처리하면 키보드 입력은 가능해 요구사항(입력 자체 불가)을 만족하지 못하므로 TextField 비활성 처리가 필요하다.

검증 기록

  • 2026-03-19 (초안 조사)

    • 무엇/왜/어떻게: 채팅창 얼리기 기능의 저장 전략 판단을 위해 LiveRoom 내부 구현 패턴, RTM 메시지 경로, ROOM_INFO 동기화 지점을 조사하고 계획 초안을 정리했다.
    • 실행 명령/도구:
      • task(subagent_type="explore") x3 (no-chat 흐름, room state sync, system UI 패턴)
      • task(subagent_type="librarian") x2 (Agora 상태 전파 문서/실사례)
      • grep("isNoChatting|NO_CHATTING|..."), grep("LiveRoomChatRawMessageType|ROOM_INFO|...")
      • ast_grep_search("LiveRoomChatRawMessage($$$)")
      • read(LiveRoomViewModel.swift, GetRoomInfoResponse.swift, LiveRoomViewV2.swift 등)
    • 결과:
      • no-chat 기존 구현(로컬 저장 + peer 명령 + 타이머)과 ROOM_INFO 기반 전역 상태 동기화 패턴을 분리 확인.
      • isActiveRoulette 선례를 통해 ROOM_INFO + RTM 병행 전략이 지연 입장 정합성에 유리함을 확인.
      • 시스템 메시지 UI는 LiveRoomJoinChatItemView 스타일 재사용이 요구사항에 부합함을 확인.
  • 2026-03-19 (iOS 계획 전환)

    • 무엇/왜/어떻게: Android 용어/경로 중심 문서를 현재 iOS 프로젝트 구조 기준으로 전면 변환하고, 사용자 요구사항(문서 작업만)을 충족하는 구현 체크리스트로 재작성했다.
    • 실행 명령/도구:
      • task(subagent_type="explore") x2 (V2 상태 토글 흐름, 시스템 메시지 렌더 경로)
      • task(subagent_type="librarian") x2 (Agora/Stream iOS 레퍼런스)
      • grep("getRoomInfo|TOGGLE_ROULETTE|NO_CHATTING|...")
      • read(LiveRoomViewModel.swift, LiveRoomChat.swift, LiveRoomChatRawMessage.swift, LiveApi.swift, LiveRepository.swift, LiveRoomInfoHostView.swift, LiveRoomInputChatView.swift, I18n.swift)
    • 결과:
      • ROOM_INFO + RTM 병행, API 선반영 후 RTM 전파, 지연 입장 재동기화를 iOS 실제 코드 경로에 매핑했다.
      • 토글 위치/입력 차단/시스템 메시지/국제화/검증 명령을 iOS 파일 및 스킴 기준으로 구체화했다.
  • 2026-03-19 (iOS 기능 구현)

    • 무엇/왜/어떻게: 계획 문서 체크리스트를 기준으로 채팅창 얼리기 기능을 iOS 코드에 구현했다. 방장 토글 UI, 서버 API 선반영 후 RTM 브로드캐스트, ROOM_INFO 기반 지연 입장 동기화, 입장 알림 스타일 시스템 메시지, 비방장 입력 차단을 한 흐름으로 연결했다.
    • 실행 명령/도구:
      • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build
      • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
      • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test
      • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test
      • lsp_diagnostics(수정 파일 전수)
    • 결과:
      • SodaLive/SodaLive-dev Debug 빌드 성공.
      • 두 스킴 모두 test action 미구성으로 자동 테스트 실행 불가(Scheme ... is not currently configured for the test action).
      • lsp_diagnostics는 워크스페이스 외부 모듈 해석 제약으로 다수 false positive가 표시되었고, 실제 컴파일 유효성은 xcodebuild 성공으로 확인.
      • 수동 QA(2계정 실기기/시뮬레이터 시나리오)는 로컬 앱 실행 환경에서 후속 수행 필요.
  • 2026-03-19 (Oracle 리뷰 반영)

    • 무엇/왜/어떻게: 구현 후 Oracle 리뷰에서 TOGGLE_CHAT_FREEZE 수신 시 발신자 권한 검증 누락(비방장 RTM 수용 가능) 이슈를 확인해, RTM 수신 분기에 publisher == creatorId 검증을 추가했다.
    • 실행 명령/도구:
      • task(subagent_type="oracle") (구현 검토)
      • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build
      • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
    • 결과:
      • 비방장 발신 TOGGLE_CHAT_FREEZE는 무시되도록 보강.
      • 보강 후 SodaLive/SodaLive-dev Debug 빌드 재검증 성공.
  • 2026-03-19 (입력 포커스 키보드 밀림 보완)

    • 무엇/왜/어떻게: 채팅 얼림 상태에서 키보드가 뜨지 않는데 화면만 밀리는 이슈를 보완하기 위해 입력 포커스 획득을 차단하고, 얼림 상태에서는 키보드 오프셋 적용을 비활성화했다. 동시에 토글 라벨 문구를 얼림 ON/OFF로 조정했다.
    • 실행 명령/도구:
      • task(subagent_type="explore") (키보드 오프셋 경로 분석)
      • read(ChatTextFieldView.swift, LiveRoomInputChatView.swift, LiveRoomViewV2.swift, I18n.swift)
      • lsp_diagnostics(ChatTextFieldView.swift, LiveRoomInputChatView.swift, LiveRoomViewV2.swift, I18n.swift)
      • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build
      • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
      • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test
      • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test
    • 결과:
      • 빌드: 두 스킴 모두 ** BUILD SUCCEEDED ** 확인.
      • 테스트: 두 스킴 모두 Scheme ... is not currently configured for the test action으로 자동 테스트 실행 불가.
      • lsp_diagnostics는 단일 파일 분석 한계로 외부 모듈/확장 미해석 오류가 남았으나, 실제 컴파일 유효성은 xcodebuild 성공으로 확인.
  • 2026-03-19 (지연 입장 메시지/입력 터치 토스트 보완)

    • 무엇/왜/어떻게: 지연 입장 시 얼림 상태 인지성을 높이기 위해 getRoomInfo 초기 동기화에서 isChatFrozen == true면 상태 메시지를 주입했고, 얼림 상태에서 입력 영역 터치 시 차단 토스트를 즉시 노출하도록 입력 콜백을 연결했다.
    • 실행 명령/도구:
      • task(subagent_type="explore") x2 (지연 입장 동기화 경로, 입력 차단 토스트 패턴)
      • grep("isChatFrozenForCurrentUser|appendChatFreezeStatusMessage|getRoomInfo|isShowErrorPopup", include:"*.swift")
      • read(LiveRoomViewModel.swift, LiveRoomInputChatView.swift, LiveRoomViewV2.swift)
      • lsp_diagnostics(LiveRoomViewModel.swift, LiveRoomInputChatView.swift, LiveRoomViewV2.swift)
      • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build
      • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
      • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test
      • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test
    • 결과:
      • 구현 지점(getRoomInfo, 입력 터치 콜백, 에러 토스트 연결)을 반영.
      • 빌드: 두 스킴 모두 ** BUILD SUCCEEDED ** 확인.
      • 테스트: 두 스킴 모두 Scheme ... is not currently configured for the test action으로 자동 테스트 실행 불가.
      • lsp_diagnostics는 단일 파일 분석 한계로 외부 모듈/확장 미해석 오류가 남았으나, 실제 컴파일 유효성은 xcodebuild 성공으로 확인.
      • 동일 기능 기준으로 분리 문서 2개를 본 문서로 통합해 단일 추적 문서 체계로 정리.
      • 수동 QA(지연 입장 시 얼림 메시지 노출, 얼림 상태 입력영역 터치 토스트)는 로컬 앱 실행 환경에서 후속 수행 필요.
  • 2026-03-19 (지연 입장 후 해제 불가 버그 분석)

    • 무엇/왜/어떻게: isChatFrozen 해제 후에도 입력이 막히는 이슈를 재현 경로 기준으로 분석했다. 원인은 ChatTextFieldView.Coordinator가 최초 생성 시점 parent.isEnabled를 계속 참조하고, updateUIView에서 최신 parent로 갱신하지 않아 textFieldShouldBeginEditing이 계속 false를 반환하는 상태 고착이었다.
    • 실행 명령/도구:
      • task(subagent_type="explore") (지연 입장/해제 상태 전이 분석)
      • read(ChatTextFieldView.swift, LiveRoomInputChatView.swift, LiveRoomViewV2.swift, LiveRoomViewModel.swift)
      • grep("textFieldShouldBeginEditing|Coordinator\(|isInputDisabled|TOGGLE_CHAT_FREEZE", include:"*.swift")
    • 결과:
      • 수정 지점을 ChatTextFieldView.updateUIView로 확정.
  • 2026-03-19 (지연 입장 후 해제 불가 버그 수정)

    • 무엇/왜/어떻게: 지연 입장 사용자에게서 얼림 해제 후에도 입력이 막히는 현상을 해결하기 위해 ChatTextFieldView.updateUIView에서 context.coordinator.parent = self를 적용해 coordinator가 최신 isEnabled 상태를 항상 참조하도록 보정했다.
    • 실행 명령/도구:
      • apply_patch(ChatTextFieldView.updateUIView)
      • lsp_diagnostics(ChatTextFieldView.swift, LiveRoomViewModel.swift, LiveRoomInputChatView.swift, LiveRoomViewV2.swift)
      • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build
      • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
      • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test
      • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test
    • 결과:
      • 빌드: 두 스킴 모두 ** BUILD SUCCEEDED ** 확인.
      • 테스트: 두 스킴 모두 Scheme ... is not currently configured for the test action으로 자동 테스트 실행 불가.
      • lsp_diagnostics는 워크스페이스 모듈 해석 한계로 단일 파일 false positive가 남았으나, 컴파일 유효성은 xcodebuild 성공으로 확인.
      • 수동 QA(지연 입장 -> 방장 해제 -> 입력 가능 전환)는 로컬 앱 실행 환경에서 후속 확인 필요.