# 20260319_라이브룸채팅삭제기능구현계획.md ## 개요 - 라이브 룸에서 방장(크리에이터) 전용 채팅 삭제 기능을 추가하기 위한 구현 계획 문서다. - 기능 범위는 단건 삭제(길게 누름 + 확인 다이얼로그)와 강퇴 연계 일괄 삭제(다이얼로그 없이 즉시 삭제)다. - 본 문서는 계획을 먼저 확정하고, 구현/검증 결과는 하단 `검증 기록`에 누적한다. ## 요구사항 해석(확정) - 채팅 삭제 권한은 방장(크리에이터)만 가진다. - 삭제 대상 채팅을 길게 누르면 삭제 확인 다이얼로그를 띄운다. - 다이얼로그 본문은 `[닉네임]: [채팅 내용]` 형식으로 노출한다. - 다이얼로그 버튼은 `취소/삭제` 두 가지다. - 삭제 확정 시 모든 사용자 화면에서 동일 채팅이 제거되어야 한다. - 강퇴 시에는 다이얼로그 없이 해당 유저의 채팅을 즉시 일괄 삭제하고, 이 결과가 모든 사용자에게 동기화되어야 한다. ## 현재 구조 조사 요약 - 일반 채팅은 `LiveRoomActivity.inputChat()`에서 `agora.inputChat(message)`로 RTM STRING 발송하고, 수신 측은 `onMessageEvent` STRING 분기에서 `LiveRoomNormalChat`을 리스트에 append 한다. - 실시간 제어 이벤트(방정보 수정, 채팅 얼림, 룰렛 등)는 `LiveRoomChatRawMessageType` + `agora.sendRawMessageToGroup()` + `onMessageEvent` BINARY 분기 패턴을 이미 사용 중이다. - 강퇴는 `LiveRoomActivity.kickOut()` -> `LiveRoomViewModel.kickOut()`(API) + `LiveRoomRequestType.KICK_OUT` peer 메시지 전송으로 처리되며, 강퇴 대상 단말은 수신 후 `finish()`로 종료한다. - 현재 라이브룸 채팅 모델/어댑터에는 단건 삭제를 위한 명시적 식별자/long-press 콜백/삭제 브로드캐스트 타입이 없다. ## 설계 결정 - 삭제 동기화는 기존 제어 이벤트와 동일하게 RTM BINARY raw message로 처리한다. - 단건 삭제 정합성을 위해 라이브룸 일반 채팅에 식별자(`chatId`)를 추가한다. - 강퇴 연계 일괄 삭제는 `targetUserId` 기반 raw message 이벤트로 처리한다. - 롱프레스 진입은 `LiveRoomChatAdapter`에서 `LiveRoomNormalChat` 항목에만 연결하고, 실제 권한 검증은 `LiveRoomActivity`에서 `isHost`로 최종 보장한다. - 기존 STRING 채팅 수신 분기는 호환성 fallback으로 유지하고, 삭제 정합성이 필요한 경로는 raw payload 기반으로 처리한다. ## 완료 기준 (Acceptance Criteria) - [x] AC1: 방장(크리에이터)만 채팅 길게 누름 시 삭제 확인 다이얼로그를 볼 수 있다. - [x] AC2: 삭제 다이얼로그에 `[닉네임]: [채팅 내용]` 형식과 `취소/삭제` 버튼이 노출된다. - [x] AC3: 단건 삭제 확정 시 모든 사용자 화면에서 동일 채팅이 제거된다. - [x] AC4: 유저 강퇴 시 다이얼로그 없이 해당 유저의 채팅이 일괄 삭제된다. - [x] AC5: 강퇴 기반 일괄 삭제도 모든 사용자 화면에 동일하게 반영된다. - [x] AC6: 기존 채팅/후원/강퇴 흐름(삭제 기능 외)은 회귀 없이 유지된다. ## 구현 체크리스트 ### 1) 채팅 모델/식별자 확장 - [x] `LiveRoomNormalChat`에 단건 삭제용 `chatId` 필드를 추가한다. - [x] 강퇴 기반 일괄 삭제 범위를 위해 작성자 식별이 가능한 채팅 타입(`LiveRoomNormalChat`, `LiveRoomDonationChat`, 필요 시 `LiveRoomRouletteDonationChat`)의 사용자 식별 정보를 정리한다. ### 2) Raw message 스키마 확장 - [x] `LiveRoomChatRawMessageType`에 삭제 관련 타입(`NORMAL_CHAT`, `DELETE_CHAT`, `DELETE_CHAT_BY_USER`)을 추가한다. - [x] `LiveRoomChatRawMessage`에 삭제/일반채팅 동기화에 필요한 필드(`chatId`, `targetUserId`)를 nullable로 추가한다. ### 3) `LiveRoomActivity` 송신/수신 경로 반영 - [x] `inputChat()`에서 일반 채팅 raw payload 송신 + 로컬 리스트 반영 로직을 정리한다. - [x] `rtmEventListener.onMessageEvent` BINARY 분기에 `NORMAL_CHAT`, `DELETE_CHAT`, `DELETE_CHAT_BY_USER` 처리 로직을 추가한다. - [x] STRING 수신 분기는 fallback 경로로 유지하고, 삭제 식별자가 없는 legacy 메시지 처리 원칙을 명시한다. ### 4) 방장 전용 long-press 삭제 UX - [x] `LiveRoomChatAdapter`에 `LiveRoomNormalChat` long-press 콜백을 추가한다. - [x] `LiveRoomActivity`에서 방장 권한(`isHost`) 검증 후 삭제 확인 다이얼로그를 노출한다. - [x] 다이얼로그 본문은 문자열 포맷으로 `[닉네임]: [채팅 내용]`을 구성하고 `취소/삭제` 액션을 연결한다. ### 5) 강퇴 연계 일괄 삭제 - [x] `kickOut(userId)` 경로에서 강퇴 대상 사용자의 채팅 일괄 삭제 raw 이벤트를 다이얼로그 없이 즉시 브로드캐스트한다. - [x] 수신 측에서 `targetUserId`에 해당하는 채팅을 일괄 제거하고 리스트를 갱신한다. ### 6) 문자열/국제화 - [x] `values/strings.xml`에 라이브룸 채팅 삭제 다이얼로그 제목/본문 포맷 문자열을 추가한다. - [x] `values-en/strings.xml`, `values-ja/strings.xml`에도 동일 키를 추가한다. ### 7) 검증 - [x] `lsp_diagnostics`로 수정 파일의 신규 오류 유무를 확인한다. - [x] `./gradlew :app:testDebugUnitTest`를 실행한다. - [x] `./gradlew :app:assembleDebug`를 실행한다. - [ ] 수동 QA: 방장/일반유저 2계정으로 단건 삭제 및 강퇴 일괄 삭제의 전 사용자 반영을 확인한다. ## 영향 파일(예상) ### 필수 - `app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt` - 방장 전용 long-press 삭제 진입, 삭제 확인 다이얼로그, 단건/일괄 삭제 raw 이벤트 송신, 수신 분기 삭제 처리, 강퇴 연계 삭제 처리 추가. - `app/src/main/java/kr/co/vividnext/sodalive/live/room/chat/LiveRoomChatRawMessage.kt` - 삭제/일반채팅 동기화용 raw message type 및 payload 필드 추가. - `app/src/main/java/kr/co/vividnext/sodalive/live/room/chat/LiveRoomChat.kt` - 단건 삭제용 `chatId` 및 작성자 기반 일괄 삭제 대응 필드(필요 타입) 추가. - `app/src/main/java/kr/co/vividnext/sodalive/live/room/chat/LiveRoomChatAdapter.kt` - 일반 채팅 아이템 long-press 콜백 전달 경로 추가. - `app/src/main/res/values/strings.xml` - 라이브룸 채팅 삭제 다이얼로그 문자열(제목/본문 포맷) 추가. - `app/src/main/res/values-en/strings.xml` - 동일 문자열 키 영문 번역 추가. - `app/src/main/res/values-ja/strings.xml` - 동일 문자열 키 일본어 번역 추가. ### 참고(변경 가능성 낮음) - `app/src/main/java/kr/co/vividnext/sodalive/agora/Agora.kt` - 기존 `sendRawMessageToGroup` API로 처리 가능해 직접 수정 가능성은 낮다. ## 리스크 및 확인사항 - 일반 채팅 송신 형식을 raw 중심으로 전환할 경우, 구버전/타플랫폼과의 프로토콜 호환성 리스크가 있다. - fallback STRING 메시지는 삭제 식별자 정합성이 약할 수 있으므로, 삭제 대상 식별 우선순위를 명확히 정의해야 한다. - `kickOut` API 호출은 현재 성공/실패 콜백을 사용하지 않으므로, “API 성공 후 삭제 이벤트 전파” 순서 보장이 필요하면 ViewModel 시그니처 확장을 검토해야 한다. - 동일 사용자의 동일 본문 반복 메시지 삭제 시 단건 선택 정합성(정확히 1건 삭제) 검증이 필요하다. ## 외부 레퍼런스(요약) - Agora Signaling 메시지 페이로드 구조화 권장(문자열/바이너리 + 앱 스키마): - https://docs.agora.io/en/signaling/core-functionality/message-payload-structuring - Stream Chat Android 삭제 권한/삭제 확인 다이얼로그/삭제 이벤트 처리 패턴: - https://github.com/GetStream/stream-chat-android/blob/d7ce8ede69b0098a06fca17cacecaa9dc0bafdbd/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/utils/CapabilitiesHelper.kt#L137-L155 - https://github.com/GetStream/stream-chat-android/blob/d7ce8ede69b0098a06fca17cacecaa9dc0bafdbd/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/MessageListView.kt#L2269-L2277 ## 검증 기록 - 기록 템플릿(후속 누적): - YYYY-MM-DD - 무엇/왜/어떻게: - 실행 명령/도구: - `명령 또는 사용 도구` - 결과: - 2026-03-19 - 무엇/왜/어떻게: 라이브룸 채팅 삭제 구현 전에 기존 채팅 송수신/강퇴/어댑터 구조를 병렬 탐색하고, 요구사항을 충족하는 상세 구현 계획(예상 수정 파일/추가 항목/검증 항목)을 문서화했다. - 실행 명령/도구: - `task(subagent_type="explore")` x3 - `task(subagent_type="librarian")` x2 - `grep("LiveRoomChatRawMessageType|kickOut\(|onMessageEvent|setOnLongClickListener|confirm_delete_title")` - `ast_grep_search("agora.sendRawMessageToGroup($$$)")` - `read(LiveRoomActivity.kt, LiveRoomChat.kt, LiveRoomChatAdapter.kt, LiveRoomChatRawMessage.kt, LiveRoomViewModel.kt, Agora.kt, LiveApi.kt, strings*.xml, dialog_live.xml)` - `bash("rg -n ...")` 시도 - `apply_patch` (본 문서 생성/상세화) - 결과: - `LiveRoomActivity` 중심의 채팅 입력(STRING)/제어이벤트(BINARY)/강퇴(peer) 경로를 확인했다. - 단건 삭제와 강퇴 일괄 삭제를 위해 필요한 확장 지점(모델, raw 타입, 어댑터 콜백, Activity 분기, 문자열 리소스)을 파일 단위로 확정했다. - 현재 실행 환경에서 `rg` 명령은 미설치(`command not found`)로 확인되어 동일 탐색은 `grep`/`ast_grep_search`/`read`로 보완했다. - 2026-03-19 - 무엇/왜/어떻게: 계획 문서 기준으로 라이브룸 채팅 삭제 기능(방장 long-press 단건 삭제, 강퇴 연계 일괄 삭제, 전 사용자 동기화)을 실제 코드에 반영하고 빌드/테스트/수동 실행 검증을 수행했다. - 실행 명령/도구: - 코드 반영: `apply_patch` (`LiveRoomActivity.kt`, `LiveRoomChat.kt`, `LiveRoomChatAdapter.kt`, `LiveRoomChatRawMessage.kt`, `strings.xml`, `values-en/strings.xml`, `values-ja/strings.xml`) - 정적 진단: `lsp_diagnostics(LiveRoomActivity.kt, LiveRoomChat.kt, LiveRoomChatAdapter.kt, LiveRoomChatRawMessage.kt, strings*.xml)` - 테스트/빌드: `./gradlew :app:testDebugUnitTest :app:assembleDebug` - 수동 실행: `adb devices`, `./gradlew :app:installDebug`, `adb shell monkey -p kr.co.vividnext.sodalive.debug -c android.intent.category.LAUNCHER 1`, `adb shell run-as kr.co.vividnext.sodalive.debug am start --user 0 -n kr.co.vividnext.sodalive.debug/kr.co.vividnext.sodalive.live.room.LiveRoomActivity --el roomId 1` - 결과: - 방장 long-press 삭제 진입/확인 다이얼로그/단건 삭제 브로드캐스트, 강퇴 시 즉시 일괄 삭제 브로드캐스트가 코드 경로에 반영되었다. - `:app:testDebugUnitTest`, `:app:assembleDebug`, `:app:installDebug`가 성공했다. - `lsp_diagnostics`는 Kotlin/XML LSP 미설정 환경으로 실행 불가 응답을 반환했다. - 단말 앱 실행 및 `LiveRoomActivity` 인텐트 실행까지는 확인했으나, 이 환경에서는 방장/일반유저 2계정 동시 접속 시나리오를 재현할 계정/세션 준비가 없어 최종 E2E(두 사용자 화면 동시 확인)는 후속 수동 검증이 필요하다. - 2026-03-19 - 무엇/왜/어떻게: Oracle 리뷰에서 삭제 이벤트 수신부의 권한 검증 누락(host-only 보장 약화)과 STRING fallback 채팅 개별 삭제 취약점이 확인되어, 삭제 수신 분기에 방장 검증을 추가하고 fallback 삭제 로직을 보완했다. - 실행 명령/도구: - 리뷰: `task(subagent_type="oracle")` - 코드 반영: `apply_patch` (`LiveRoomActivity.kt`) - 정적 진단: `lsp_diagnostics(LiveRoomActivity.kt)` - 테스트/빌드: `./gradlew :app:testDebugUnitTest :app:assembleDebug` - 수동 실행: `adb devices`, `./gradlew :app:installDebug`, `adb shell monkey -p kr.co.vividnext.sodalive.debug -c android.intent.category.LAUNCHER 1`, `adb shell run-as kr.co.vividnext.sodalive.debug am start --user 0 -n kr.co.vividnext.sodalive.debug/kr.co.vividnext.sodalive.live.room.LiveRoomActivity --el roomId 1` - 결과: - `DELETE_CHAT`, `DELETE_CHAT_BY_USER` 수신 처리에서 발신자가 방장인지 검증하도록 반영했다. - `chatId`가 비어있는 legacy STRING 채팅도 `targetUserId + message` 기준으로 단건 삭제를 시도하도록 보완했다. - `:app:testDebugUnitTest`, `:app:assembleDebug`, `:app:installDebug`가 모두 성공했다. - `lsp_diagnostics`는 Kotlin LSP 미설정 환경으로 실행 불가 응답을 반환했다. - 2026-03-19 - 무엇/왜/어떻게: 사용자 요청에 따라 채팅 삭제 다이얼로그 본문 포맷에서 대괄호를 제거했다. - 실행 명령/도구: - 코드 반영: `apply_patch` (`app/src/main/res/values/strings.xml`, `app/src/main/res/values-en/strings.xml`, `app/src/main/res/values-ja/strings.xml`) - 검증: `grep("screen_live_room_chat_delete_message_format")`, `lsp_diagnostics(strings*.xml)`, `./gradlew :app:testDebugUnitTest :app:assembleDebug`, `adb devices`, `./gradlew :app:installDebug`, `adb shell monkey -p kr.co.vividnext.sodalive.debug -c android.intent.category.LAUNCHER 1`, `adb shell run-as kr.co.vividnext.sodalive.debug am start --user 0 -n kr.co.vividnext.sodalive.debug/kr.co.vividnext.sodalive.live.room.LiveRoomActivity --el roomId 1` - 결과: - `screen_live_room_chat_delete_message_format` 값이 `[%1$s]: [%2$s]`에서 `%1$s: %2$s`로 변경되어 대괄호 없이 노출된다. - 테스트/빌드/설치 및 앱/액티비티 실행이 성공했다. - `lsp_diagnostics`는 XML LSP 미설정 환경으로 실행 불가 응답을 반환했다. - 2026-03-19 - 무엇/왜/어떻게: 삭제 이벤트 payload에서 `targetChatId`를 제거하고 `chatId` 단일 필드로 통일해, 삭제 송신/수신이 동일 키를 사용하도록 정리했다. - 실행 명령/도구: - 코드 반영: `apply_patch` (`app/src/main/java/kr/co/vividnext/sodalive/live/room/chat/LiveRoomChatRawMessage.kt`, `app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt`, `docs/20260319_라이브룸채팅삭제기능구현계획.md`) - 참조 확인: `grep("targetChatId")`, `grep("targetChatId", include="*.md")` - 정적 진단: `lsp_diagnostics(LiveRoomActivity.kt, LiveRoomChatRawMessage.kt)` - 테스트/빌드: `./gradlew :app:testDebugUnitTest :app:assembleDebug` - 수동 실행: `adb devices`, `./gradlew :app:installDebug`, `adb shell monkey -p kr.co.vividnext.sodalive.debug -c android.intent.category.LAUNCHER 1`, `adb shell run-as kr.co.vividnext.sodalive.debug am start --user 0 -n kr.co.vividnext.sodalive.debug/kr.co.vividnext.sodalive.live.room.LiveRoomActivity --el roomId 1` - 결과: - `LiveRoomChatRawMessage`에서 `targetChatId` 필드를 제거했고, `DELETE_CHAT` 송신은 `chatId`에 삭제 대상 채팅 ID를 담아 전송하도록 변경했다. - `DELETE_CHAT` 수신도 `message.chatId`를 읽어 삭제하도록 변경했으며, 저장소 내 `targetChatId` 문자열 참조는 0건이다. - `:app:testDebugUnitTest`, `:app:assembleDebug`, `:app:installDebug`가 성공했고 앱/액티비티 실행까지 확인했다. - `lsp_diagnostics`는 Kotlin LSP 미설정 환경으로 실행 불가 응답을 반환했다.