diff --git a/docs/20260708_커뮤니티_게시물_상세_댓글_답글/plan-task.md b/docs/20260708_커뮤니티_게시물_상세_댓글_답글/plan-task.md new file mode 100644 index 00000000..02dd2218 --- /dev/null +++ b/docs/20260708_커뮤니티_게시물_상세_댓글_답글/plan-task.md @@ -0,0 +1,229 @@ +# 커뮤니티 게시물 상세 / 댓글 / 답글 구현 계획/TASK + +> **For agentic workers:** 각 단계는 체크박스(`- [ ]`)로 추적하고, 완료 즉시 `- [x]`로 갱신한다. 구현 범위 변경이 생기면 이 문서를 먼저 수정한 뒤 코드에 반영한다. + +**Goal:** 크리에이터 채널 `커뮤니티` 탭에서 게시글 item 터치 시 게시물 상세로 진입해 본문/좋아요/댓글을 조회·작성하고, 댓글의 답글 목록 화면(팬Talk 상세와 동일 UI)에서 답글을 조회·작성하며, 수정/삭제/좋아요는 기존 API를 재사용한다. + +**Architecture:** 조회(상세/댓글/답글)는 신규 v2 endpoint를 v2 API/Repository로 호출한다. 쓰기/수정/삭제/좋아요는 기존 `CreatorCommunityRepository`를 그대로 호출한다. 화면은 커뮤니티 전용으로 신규 구현하되, 답글 목록 화면의 layout XML과 댓글/답글 item view만 재사용 가능하게 설계한다. + +**Tech Stack:** Kotlin, Android XML Views, ViewBinding, RxJava3, Koin, JUnit4/Robolectric. + +--- + +## 전제와 성공 기준 + +- PRD: `docs/20260708_커뮤니티_게시물_상세_댓글_답글/prd.md` +- 기존 커뮤니티 탭 문서: + - `docs/20260621_크리에이터_채널_커뮤니티_탭/prd.md` + - `docs/20260621_크리에이터_채널_커뮤니티_탭/plan-task.md` +- 재사용 vs 복사 결정: 커뮤니티 전용 신규 구현. layout XML + 댓글/답글 item view만 데이터/콜백 주입형으로 공용화(데이터 계층은 커뮤니티 전용). 근거는 PRD `11. Open Questions & Decisions` 참고. +- 조회 API는 신규 v2 endpoint를 사용하고 기본값 `page=0`, `size=20`을 따른다. +- 쓰기/수정/삭제/좋아요/팝업 메뉴는 기존 커뮤니티 코드를 재사용한다. +- 구현 완료 판단 시 Gradle 검증뿐 아니라 Figma 4개 화면의 주요 layout, 하트/전송 버튼 상태, 댓글 불가 처리, 입력 영역 동작이 실제 구현 화면에 반영되었는지 확인한다. +- Figma 대조 결과는 문서 최하단 `Verification Log`에 한국어로 누적 기록한다. +- 구현 완료 후 최소 다음 명령을 실행한다. + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` + - `./gradlew :app:mergeDebugResources` + - `./gradlew :app:compileDebugKotlin` + - `./gradlew :app:ktlintCheck` + - `git diff --check` + +--- + +## Figma 참조 + +- 게시글 상세(댓글 가능 + 댓글 있음): `https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=290-9243&m=dev` +- 게시글 상세(댓글 불가능): `https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=290-9266&m=dev` +- 댓글 답글 목록(답글 없음): `https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=669-40462&m=dev` +- 댓글 답글 목록(답글 있음): `https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=669-40468&m=dev` + +--- + +## API 계약 + +- 조회(신규 v2, `ApiResponse`): + - 상세: `GET /api/v2/creator-channels/community-posts/{postId}` → `CreatorChannelCommunityPostDetailResponse` + - 필드: `postId`, `creatorId`, `creatorNickname`, `creatorProfileUrl`, `createdAtUtc`, `content`, `imageUrl`, `audioUrl`, `price`, `isCommentAvailable`, `existOrdered`, `likeCount`, `commentCount`, `isPinned`, `isLiked`, `comments` + - `comments`는 댓글 첫 페이지(`CreatorChannelCommunityCommentsResponse`)를 포함한다. + - 댓글: `GET /api/v2/creator-channels/community-posts/{postId}/comments?page=0&size=20` → `CreatorChannelCommunityCommentsResponse` + - 필드: `commentCount`, `comments`, `page`, `size`, `hasNext` + - 댓글 필드: `commentId`, `writerId`, `writerProfileImageUrl`, `writerNickname`, `content`, `isSecret`, `createdAtUtc`, `latestReply` + - 최신 답글 필드: `commentId`, `writerId`, `writerProfileImageUrl`, `writerNickname`, `content`, `createdAtUtc` + - 답글: `GET /api/v2/creator-channels/community-comments/{commentId}/replies?page=0&size=20` → `CreatorChannelCommunityRepliesResponse` +- 쓰기/수정/삭제/좋아요(기존 `CreatorCommunityRepository` 재사용): + - 좋아요: `communityPostLike(postId, token)` + - 댓글 쓰기: `registerComment(postId, comment, token = token)` + - 답글 쓰기: `registerComment(postId, comment, parentId = commentId, token = token)` + - 댓글/답글 수정: `modifyComment(ModifyCommentRequest(commentId, comment = ...), token)` + - 댓글/답글 삭제: `modifyComment(ModifyCommentRequest(commentId, isActive = false), token)` +- 팝업 메뉴: `R.menu.content_comment_option_menu`(내 댓글/답글: 수정/삭제), `R.menu.content_comment_option_menu2`(채널 크리에이터가 보는 타인 댓글/답글: 삭제). 일반 타인 댓글/답글은 커뮤니티 댓글 전용 신고 API가 없어 더보기 버튼을 노출하지 않는다. + +--- + +## 파일 구조 (예정) + +- 생성(응답 모델): `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/data/CreatorChannelCommunityDetailResponse.kt` + - PRD의 `CreatorChannelCommunityPostDetailResponse`, `CreatorChannelCommunityCommentsResponse`, `CreatorChannelCommunityCommentResponse`, `CreatorChannelCommunityReplyResponse`, `CreatorChannelCommunityRepliesResponse`. +- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelApi.kt` + - 상세/댓글/답글 조회 endpoint 3개 추가. +- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelRepository.kt` + - 상세/댓글/답글 조회 method 추가. +- 생성(진입점): 커뮤니티 탭 item 터치 → 상세 화면 이동. + - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityFragment.kt`(item click을 Host callback으로 위임). + - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityListAdapter.kt`, `CreatorChannelCommunityGridAdapter.kt`(item click 콜백 추가). + - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt`(상세 화면 실행). +- 생성(상세 화면): `.../community/detail/CreatorChannelCommunityDetailActivity.kt`, `CreatorChannelCommunityDetailViewModel.kt` +- 생성(답글 화면): `.../community/detail/reply/CreatorChannelCommunityReplyActivity.kt`, `CreatorChannelCommunityReplyViewModel.kt` +- 생성(layout): `activity_creator_channel_community_detail.xml`, `activity_creator_channel_community_reply.xml`, 댓글/답글 item layout, 입력 영역 layout, 전송 버튼 배경 drawable(enabled/disabled). +- 생성(어댑터/item view): 댓글 목록 adapter, 답글 목록 adapter(재사용 가능한 댓글/답글 item view). +- 수정: `app/src/main/AndroidManifest.xml`(신규 Activity 등록), `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt`(신규 ViewModel binding). +- 수정: `values`, `values-en`, `values-ja` strings(placeholder 등 신규 문자열 또는 기존 재사용). +- 테스트 생성: 상세/답글 ViewModel 테스트, layout/source 테스트. + +--- + +### Phase 1: 조회 API/모델 계약 + +- [x] **Task 1.1: 응답 데이터 클래스 생성** + - 생성: `.../community/detail/data/CreatorChannelCommunityDetailResponse.kt` + - 작업: PRD 명시 5개 data class를 `@Keep`/`@SerializedName` 포함해 정의한다. + - 검증: `./gradlew :app:compileDebugKotlin` + - 검증 기록(2026-07-08): TDD RED로 `CreatorChannelCommunityDetailContractTest` 추가 후 DTO 패키지/클래스 미존재로 `:app:compileDebugUnitTestKotlin` 실패를 확인했다. 이후 `CreatorChannelCommunityPostDetailResponse`, `CreatorChannelCommunityCommentsResponse`, `CreatorChannelCommunityCommentResponse`, `CreatorChannelCommunityReplyResponse`, `CreatorChannelCommunityRepliesResponse`를 생성하고 `./gradlew :app:compileDebugKotlin` 성공을 확인했다. + +- [x] **Task 1.2: v2 API/Repository 조회 method 추가** + - 수정: `CreatorChannelApi.kt`, `CreatorChannelRepository.kt` + - 작업: 상세/댓글/답글 조회 3개 추가(기본 `page=0`, `size=20`, token 포함). + - 검증: `./gradlew :app:compileDebugKotlin` + - 검증 기록(2026-07-08): 구현 전 `CreatorChannelApi`/`CreatorChannelRepository`의 상세/댓글/답글 method 미존재로 RED 실패를 확인했다. 구현 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` 및 `./gradlew :app:compileDebugKotlin` 성공을 확인했다. + +--- + +### Phase 2: 상세 ViewModel/화면 + +- [x] **Task 2.1: 상세 ViewModel RED 테스트 작성** + - 생성: `CreatorChannelCommunityDetailViewModelTest.kt` + - 케이스: 상세 로드, 좋아요 토글(낙관적 업데이트+실패 롤백, 중복 방지), 댓글 pagination(hasNext), 댓글 쓰기 성공 후 새로고침, 댓글 불가 시 댓글 조회/입력 미노출. + - 검증: 해당 테스트 실행(구현 전 실패). + - 검증 기록(2026-07-08): `CreatorChannelCommunityDetailViewModelTest`를 먼저 추가하고 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityDetailViewModelTest"`를 실행해 `CreatorChannelCommunityDetailViewModel`/`CreatorChannelCommunityDetailUiState` 미존재 컴파일 오류로 RED 실패를 확인했다. + +- [x] **Task 2.2: 상세 ViewModel 구현** + - 생성: `CreatorChannelCommunityDetailViewModel.kt` + - 검증: `CreatorChannelCommunityDetailViewModelTest` PASS. + - 검증 기록(2026-07-08): `CreatorChannelCommunityDetailViewModel`과 상세/댓글/답글 UI state model을 추가하고 동일한 테스트 명령을 재실행해 PASS를 확인했다. 상세 조회, 댓글 첫 페이지/페이지네이션, 좋아요 낙관적 토글/중복 방지/실패 롤백, 댓글 trim 작성/성공 이벤트/댓글 새로고침, 댓글 불가 시 댓글 API/작성 미호출을 검증했다. + +- [x] **Task 2.3: 상세 layout/Activity 구현** + - 생성: `activity_creator_channel_community_detail.xml`, `CreatorChannelCommunityDetailActivity.kt`, 댓글 adapter/item, 입력 영역, 전송 버튼 drawable. + - 작업: Figma `290:9243`(댓글 가능)/`290:9266`(댓글 불가) 기준. 하트 fill/empty, 전송 버튼 gray_900/soda_400 상태, 3줄 자동확장/4줄 스크롤, 댓글 불가 시 댓글/입력 영역 숨김, 최신 답글 1개 중첩 표시, 더보기 팝업 메뉴 재사용. + - 수정: `AndroidManifest.xml`, `AppDI.kt`. + - 검증: `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin` + - 검증 기록(2026-07-08): `CreatorChannelCommunityDetailReplyUiContractTest`를 먼저 추가하고 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`를 실행해 상세/답글 layout·id·source 미존재 컴파일 오류로 RED 실패를 확인했다. 이후 `activity_creator_channel_community_detail.xml`, `CreatorChannelCommunityDetailActivity`, 댓글 adapter/item, 입력 bar 배경/전송 icon drawable, 문자열, manifest/AppDI 등록을 추가했다. 상세 화면은 `newIntent(context, postId)`로 진입하고 `postId <= 0`은 toast 후 종료하며, 하트 토글/댓글 입력 trim 활성화/댓글 불가 시 댓글 목록과 하단 입력 숨김/최신 답글 card 클릭 답글 화면 진입/팝업 메뉴 재사용 계약을 반영했다. + +--- + +### Phase 3: 답글 화면 + +- [x] **Task 3.1: 답글 ViewModel RED 테스트/구현** + - 생성: `CreatorChannelCommunityReplyViewModel(.kt/Test.kt)` + - 케이스: 부모 댓글/답글 로드, 답글 pagination, 답글 쓰기(`parentId = commentId`) 후 새로고침, 수정/삭제 후 새로고침. + - 검증: 테스트 PASS. + - 검증 기록(2026-07-08): `CreatorChannelCommunityReplyViewModelTest`를 먼저 추가하고 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityReplyViewModelTest"`를 실행해 `CreatorChannelCommunityReplyViewModel`/`CreatorChannelCommunityReplyUiState` 미존재 컴파일 오류로 RED 실패를 확인했다. 이후 `CreatorChannelCommunityReplyViewModel`을 추가하고 동일 명령을 재실행해 PASS를 확인했다. 부모 댓글 보존/답글 첫 페이지 조회, `hasNext` 페이지네이션과 중복 `loadMore` 방지, 답글 trim 작성/blank 무시/중복 전송 방지/성공 후 입력·이벤트 정리 및 새로고침, 수정·삭제 후 새로고침, `commentId <= 0`일 때 답글 API 미호출을 검증했다. + +- [x] **Task 3.2: 답글 layout/Activity 구현** + - 생성: `activity_creator_channel_community_reply.xml`, `CreatorChannelCommunityReplyActivity.kt`, 재사용 가능한 댓글/답글 item view/adapter. + - 작업: Figma `669:40462`(답글 없음)/`669:40468`(답글 있음) 기준. 부모 댓글 상단 표시, 답글 들여쓰기, placeholder `답글을 입력하세요`, 더보기 팝업 메뉴 재사용. + - 수정: `AndroidManifest.xml`, `AppDI.kt`. + - 검증: `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin` + - 검증 기록(2026-07-08): 상세 UI 계약 RED 테스트에 답글 layout/source 계약을 함께 추가해 실패를 확인한 뒤 `activity_creator_channel_community_reply.xml`, `CreatorChannelCommunityReplyActivity`, 답글 adapter/item을 생성했다. 답글 화면은 primitive extras로 부모 댓글을 재구성하고 상단 부모 댓글 표시, 들여쓰기 답글 card, `답글을 입력하세요` 입력 bar, trim 기반 전송 버튼 상태, `updateReplyInput`/`submitReply`, 팝업 메뉴의 `modifyReply`/`deleteReply` 연결을 반영했다. + +--- + +### Phase 4: 진입점 연결 + +- [x] **Task 4.1: 커뮤니티 탭 item 터치 → 상세 진입** + - 수정: `CreatorChannelCommunityListAdapter.kt`, `CreatorChannelCommunityGridAdapter.kt`, `CreatorChannelCommunityFragment.kt`(Host callback 추가), `CreatorChannelActivity.kt`. + - 작업: item 터치 시 `postId`로 상세 화면 실행. 댓글에서 답글 화면(`commentId`) 진입 연결. 좋아요/댓글/답글 작성 후 목록 새로고침 정책 연결. + - 검증: `./gradlew :app:compileDebugKotlin` + - 검증 기록(2026-07-08): `CreatorChannelCommunityFragmentLayoutTest`와 `CreatorChannelActivitySourceTest`에 RED source 계약을 먼저 추가해 list/grid adapter root click callback, fragment Host 위임, Activity의 상세 화면 `newIntent` 진입 계약 실패를 확인했다. 이후 list/grid adapter에 `onPostClick`을 추가하고 play/owner more click은 기존 콜백으로 분리 유지했으며, fragment는 `postId`를 Host로 위임하고 Activity는 `postId <= 0L`을 무시한 뒤 `CreatorChannelCommunityDetailActivity.newIntent(this, postId)`로 상세 화면을 실행하도록 연결했다. + +--- + +### Phase 5: 테스트/회귀 검증 + +- [x] **Task 5.1: source/layout 테스트 추가** + - 작업: 조회 API page/size 기본값, 하트 icon 계약(`ic_feed_community_heart_fill`/`ic_feed_community_heart`), 전송 버튼 상태(`ic_new_arrow_up_gray`+gray_900 / `ic_new_arrow_up_white`+soda_400), 댓글 불가 시 댓글/입력 숨김, placeholder 문구를 검증. + - 검증: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` + - 검증 기록(2026-07-08): review-work 차단 이슈 회귀를 막기 위해 상세 댓글 수정/삭제 ViewModel 테스트와 상세/답글 source 계약 테스트를 먼저 추가했다. 구현 전 동일 targeted 테스트를 실행해 `CreatorChannelCommunityDetailViewModel.modifyComment/deleteComment` 미구현 컴파일 오류로 RED 실패를 확인했다. 구현 후 동일 명령을 재실행해 PASS를 확인했고, 상세/답글 Activity의 하단 도달 pagination 호출, 내 댓글/답글에만 destructive popup 노출, 수정 입력 dialog, 삭제 확인 dialog, 상세 오디오 매니저 연결/lifecycle 정리, 상세 로드 Error finish 계약을 source 테스트로 검증했다. + +- [x] **Task 5.2: 전체 검증 실행** + - 실행: 위 성공 기준의 Gradle 명령 전체. + - Figma 수동 확인: `290:9243`, `290:9266`, `669:40462`, `669:40468` 대조. + - 검증 기록: `Verification Log`에 누적. + - 검증 기록(2026-07-08): review-work 차단 이슈 수정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`이 PASS했다. 최초 `./gradlew :app:ktlintCheck`는 신규 테스트 source 문자열 길이 초과로 실패했으며, 테스트 문자열 분리 후 재실행해 PASS했다. `git diff --check`는 출력 없이 PASS했다. + +--- + +### Phase 6: review 재반영 및 서버 계약 수정 + +- [x] **Task 6.1: 서버 댓글 응답 계약 반영** + - 작업: 상세 응답 내부 `comments`와 댓글/최신 답글의 `writer*`/`content` 필드명을 DTO와 UI mapping에 반영한다. + - 검증: `CreatorChannelCommunityDetailContractTest`, 상세/답글 ViewModel 테스트 PASS. + - 검증 기록(2026-07-08): `CreatorChannelCommunityPostDetailResponse`에 `comments`를 추가하고, `CreatorChannelCommunityCommentsResponse.commentCount`, 댓글/답글의 `writerId`, `writerProfileImageUrl`, `writerNickname`, `content` 계약을 반영했다. 상세 최초 댓글 목록은 상세 응답의 embedded `comments`에서 사용하고, 답글 수정/삭제 ID는 응답의 `commentId`를 답글 자체 ID로 매핑했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` PASS로 확인했다. + +- [x] **Task 6.2: 댓글/답글 refresh 중 stale pagination 응답 방지** + - 작업: 상세 댓글과 답글 pagination 요청에 generation guard를 두고, 새로고침 시 이전 요청이 현재 state를 덮지 않게 한다. + - 검증: stale 응답 회귀 테스트 PASS. + - 검증 기록(2026-07-08): 상세 댓글과 답글 ViewModel에 request generation guard를 추가해 새 상세/답글 로드 후 이전 page 응답이 도착해도 현재 목록을 덮지 않도록 수정했다. stale 응답 회귀 테스트를 추가하고 community targeted test PASS를 확인했다. + +- [x] **Task 6.3: 수정 dialog blank 입력 안내 보강** + - 작업: 댓글/답글/부모 댓글 수정에서 공백 입력 시 기존 커뮤니티 정책과 동일한 내용 입력 toast를 표시한다. + - 검증: source 또는 Activity 계약 테스트 PASS. + - 검증 기록(2026-07-08): 댓글/답글/부모 댓글 수정 요청이 공백이면 `screen_creator_community_write_content_hint` toast 이벤트를 노출하고 legacy modify API를 호출하지 않도록 보강했다. ViewModel 회귀 테스트 PASS를 확인했다. + +- [x] **Task 6.4: 레이아웃 속성 직접 검증 테스트 축소** + - 작업: UI layout 크기/padding/source 문자열 검증을 제거하고, 로직/상태/리소스 존재 계약 중심으로 테스트를 줄인다. + - 검증: targeted unit test PASS. + - 검증 기록(2026-07-08): `CreatorChannelCommunityDetailReplyUiContractTest`에서 layout 높이/padding/drawable 문자열 및 Activity source 문자열 직접 검증을 제거하고, inflate 가능한 layout과 필수 view id/문자열/AppDI/manifest 등록 확인으로 축소했다. community targeted test PASS를 확인했다. + +### Phase 7: review 결과 반영 + +- [x] **Task 7.1: embedded 댓글의 채널 크리에이터 권한 매핑 수정** + - 작업: 상세 응답의 embedded `comments`를 최초 UI state로 매핑할 때 `Content` state가 아직 없어 `isCreatorOwner`가 false로 고정되는 문제를 수정한다. + - 검증: 채널 크리에이터가 타인 댓글/최신 답글에 대해 삭제 메뉴 권한을 갖는 ViewModel 회귀 테스트 PASS. + - 검증 기록(2026-07-08): `CreatorChannelCommunityDetailViewModelTest`에 채널 작성자가 상세 embedded 타인 댓글의 삭제 메뉴 권한을 갖는 회귀 테스트를 추가하고, 수정 전 `AssertionError` RED 실패를 확인했다. 이후 embedded 댓글은 상세 응답의 `creatorId`, 추가 페이지 댓글은 현재 post의 `creatorId` 기준으로 `isCreatorOwner`를 명시 전달하도록 수정해 동일 테스트 PASS를 확인했다. + +### Phase 8: 리뷰 결과 추가 반영 + +- [x] **Task 8.1: 댓글 불가 화면/댓글·답글 item UI 정리** + - 작업: 댓글 불가 화면에서는 댓글 목록/입력 영역만 숨기고 별도 안내 문구를 제거한다. 댓글/답글 item에서 Figma/PRD에 없는 reaction row를 제거한다. + - 검증: layout/source 계약 테스트 및 community targeted test PASS. + - 검증 기록(2026-07-08): `CreatorChannelCommunityDetailReplyUiContractTest`에 댓글 불가 안내 view 제거, 댓글/답글 reaction id 제거 계약을 추가했다. `item_creator_channel_community_comment.xml`과 `item_creator_channel_community_reply.xml`에서 Figma에 없는 reaction row를 제거하고 adapter binding 참조를 정리했다. 상세 댓글 불가 화면은 댓글 header/list/input bar만 숨기도록 유지했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` PASS로 확인했다. + +- [x] **Task 8.2: 유료 미구매 paywall 표시 수정** + - 작업: 유료 미구매 게시물은 raw media URL을 노출하지 않되 Figma 중앙 lock + 가격 paywall 영역은 표시한다. + - 검증: 상세 ViewModel/Activity/layout 계약 테스트 PASS. + - 검증 기록(2026-07-08): `CreatorChannelCommunityPostDetailUiModel.showPaywall`을 추가하고 유료 미구매 타인 게시물에서 `imageUrl`/`audioUrl`은 계속 제거하되 `showPaywall`을 true로 노출하도록 수정했다. 상세 layout에는 `layout_creator_channel_community_detail_paywall`과 `tv_creator_channel_community_detail_paywall_price`를 추가해 이미지 컨테이너 중앙에 lock+가격 pill을 표시한다. 상세 ViewModel/UI 계약 테스트 PASS로 확인했다. + +- [x] **Task 8.3: 댓글/답글 상대시간 보정** + - 작업: v2 `createdAtUtc` 파싱을 ISO offset/초 단위 epoch까지 지원하고, 서버·단말 시계 차이로 작성 직후 과거 시간이 표시되지 않도록 최근 시간 보정 규칙을 추가한다. 답글 화면 부모 댓글도 원본 `createdAtUtc`를 전달해 답글 목록과 같은 포맷터 기준으로 표시한다. + - 검증: 상대시간 포맷터 회귀 테스트 및 상세/답글 ViewModel 테스트 PASS. + - 검증 기록(2026-07-08): 기존 공통 함수 `formatUtcRelativeTimeText`를 보강해 epoch millis 기존 동작을 유지하면서 epoch seconds, ISO offset, 6자리 fraction을 파싱하도록 수정했다. ISO 문자열은 서버/단말 시계 오차를 고려해 10분 이내를 `방금전`으로 보정한다. 댓글/답글 UI model에 원본 `createdAtUtc`를 보존하고 답글 화면 `Intent`에 전달해 부모 댓글도 동일한 `UtcRelativeTimeTextFormatter`로 재포맷하도록 수정했다. `RelativeTimeFormatterTest`, 상세/답글 ViewModel 테스트 PASS로 확인했다. + +--- + +## Verification Log + +- 구현 전 문서 작성 단계에서는 Gradle 검증을 실행하지 않았다. +- 2026-07-08: 추가 review findings 반영 범위를 확정했다. 상세/답글 변경 결과를 상위 커뮤니티 목록과 상세 댓글/답글 카운트에 반영하고, 좋아요 API의 `success=false` 응답에서 낙관적 상태를 롤백하며, 댓글/답글 입력창을 3줄까지 자동 확장되도록 고정 높이를 제거하고, 수정 내용이 없을 때 기존 `audio_content_comment_no_change` toast를 표시하도록 보강한다. +- 2026-07-08: Phase 2.3/3.2 UI 구현 전 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`를 실행해 신규 activity/layout/item/source가 없어 `compileDebugUnitTestKotlin` RED 실패를 확인했다. +- 2026-07-08: Phase 2.3/3.2 UI 구현 후 동일 targeted 테스트를 재실행해 48개 테스트 PASS를 확인했다. Robolectric inflate로 상세/답글 layout의 title back/content/input bar 및 item layout 계약을 확인했고, source 검사로 AppDI/manifest/ViewModel binding/popup menu/comment hidden/newIntent 계약을 확인했다. +- 2026-07-08: review-work 차단 이슈 대응으로 Phase 5 테스트를 보강했다. RED는 `CreatorChannelCommunityDetailViewModel.modifyComment/deleteComment` 미구현 컴파일 오류로 확인했고, GREEN은 community targeted 테스트와 CreatorChannelActivity source 테스트, resources merge, Kotlin compile, ktlint, diff whitespace 검증 PASS로 확인했다. +- 2026-07-08: 최종 확인으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 재실행해 모두 `BUILD SUCCESSFUL`을 확인했다. `git diff --check`는 출력 없이 통과했다. +- 2026-07-08: review-work 재검증에서 발견된 추가 차단 이슈를 반영했다. 상세 댓글 추가 로딩은 실제 스크롤 컨테이너인 `NestedScrollView` 기준으로 감지하도록 수정하고, 유료 미구매 잠금 게시글은 작성자가 아닌 경우 이미지/오디오 URL을 UI model에서 제거해 raw media URL 노출을 막았다. 댓글/답글 더보기는 레거시와 동일하게 작성자 또는 채널 크리에이터에게만 노출하며, 답글 화면 부모 댓글에도 수정/삭제 메뉴를 연결했다. PRD의 상세 응답 `comments` 표현은 별도 댓글 endpoint 첫 페이지 조회 계약으로 정리했다. +- 2026-07-08: 추가 차단 이슈 수정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 재실행해 모두 `BUILD SUCCESSFUL`을 확인했다. `git diff --check`는 출력 없이 통과했다. +- 2026-07-08: 부모 댓글 수정/삭제 후 답글 화면의 부모 댓글 영역과 상세 댓글 목록이 stale 상태로 남을 수 있다는 review-work 지적을 반영했다. 부모 댓글 수정 성공 시 답글 ViewModel의 `parentComment` 상태를 즉시 갱신하고, 부모 댓글 변경/삭제 이벤트를 Activity result로 상세 화면에 전달해 상세를 재조회하도록 수정했다. 삭제 성공 시 답글 화면은 `RESULT_OK` 반환 후 종료한다. 회귀 테스트로 부모 댓글 수정 상태 갱신, 삭제 이벤트, ActivityResult source 계약을 추가하고 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` PASS를 확인했다. +- 2026-07-08: 추가 review findings 4건을 반영했다. 상세 진입은 `ActivityResultLauncher`로 변경해 상세의 댓글/좋아요/수정/삭제 및 답글 화면 변경 결과가 상위 커뮤니티 목록 refresh로 전파되도록 했고, 좋아요 API의 `success=false` 응답은 낙관적 상태를 이전 좋아요 상태/개수로 롤백하도록 수정했다. 댓글/답글 작성·수정·삭제 후에는 상세/답글을 서버 재조회해 댓글·답글 count를 동기화하고, 댓글/답글 입력창은 고정 높이를 제거해 3줄까지 자동 확장되도록 `wrap_content`/`minHeight=40dp`/`maxLines=3`/vertical scrollbar 계약을 맞췄다. 수정 내용이 원문과 같으면 `audio_content_comment_no_change` toast 후 API 호출을 막도록 상세/답글 수정 dialog를 보강했다. 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`가 모두 `BUILD SUCCESSFUL`임을 확인했고, `git diff --check`는 출력 없이 통과했다. 최초 병렬 Gradle 실행 중 KSP 증분 캐시 손상/동시 접근 오류가 발생했으나 `app/build/kspCaches/debug` 생성 캐시 제거 후 순차 재실행으로 통과했다. +- 2026-07-08: review-work 재검증에서 답글 화면 변경 결과가 상세 화면 재조회까지만 반영되고 상위 `CreatorChannelActivity` 목록 refresh로 재전파되지 않는 차단 이슈가 확인되어 수정했다. `CreatorChannelCommunityDetailActivity`의 `replyLauncher`에서 답글 화면 `RESULT_OK` 수신 시 `setResult(Activity.RESULT_OK)`를 먼저 호출한 뒤 `loadDetail(postId)`를 수행하도록 변경했고, source contract test가 이 순서를 검증하도록 보강했다. 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`가 모두 `BUILD SUCCESSFUL`임을 확인했고, `git diff --check`는 출력 없이 통과했다. +- 2026-07-08: 추가 review findings 3건을 반영했다. 커뮤니티 게시물 상세 진입 전 `SharedPreferenceManager.token.isBlank()`이면 기존 `showLoginActivity()`로 이동하고 상세 Activity launch를 중단하도록 보강했다. 댓글/답글 작성·수정·삭제 실패 시 v2 `CreatorChannelEvent` 기반 toast 이벤트를 노출해 API 메시지 또는 `common_error_unknown`을 표시하도록 상세/답글 ViewModel과 Activity를 수정했다. 상세 댓글 목록 상단에는 `댓글 {commentCount}` 헤더를 추가하고, 답글 화면 부모 댓글에서는 reply/heart count reaction row와 latest reply block을 숨겨 Figma/PRD 정적 UI와 맞췄다. 회귀 테스트로 로그인 gate source 계약, 실패 toast 이벤트 consume 계약, 댓글 헤더/부모 reaction row source·layout 계약을 추가했으며 targeted unit test는 `BUILD SUCCESSFUL`을 확인했다. +- 2026-07-08: 리뷰 결과 3건과 서버 응답 계약 불일치를 반영했다. 상세 응답 내부 `comments`를 최초 댓글 목록으로 사용하도록 바꾸고, 댓글/최신 답글/답글 응답 DTO를 서버 계약의 `writer*`, `content`, `commentId` 필드명에 맞췄다. 상세 댓글/답글 pagination에는 generation guard를 추가해 새로고침 후 stale page 응답이 현재 목록을 덮지 않게 했고, 댓글/답글/부모 댓글 수정 blank 입력은 `screen_creator_community_write_content_hint` toast 후 API를 호출하지 않도록 했다. UI 계약 테스트는 layout 속성/source 문자열 직접 검증을 제거해 필수 view/resource 등록 중심으로 축소했다. 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`가 모두 `BUILD SUCCESSFUL`임을 확인했다. `git diff --check`, `git diff HEAD --check`는 출력 없이 통과했다. `:app:mergeDebugResources`는 최초 sandbox에서 `~/.gradle` lock 파일 권한 문제로 실패했으나, 동일 명령을 권한 상승 재실행해 통과했다. +- 2026-07-08: 코드 리뷰에서 발견된 채널 크리에이터 권한 매핑 문제를 반영했다. 상세 응답 embedded `comments`를 최초 UI state로 매핑할 때 아직 `Content` state가 없어 `isCreatorOwner`가 false로 고정되던 문제를, 상세 응답의 `creatorId` 또는 현재 post의 `creatorId`를 기준으로 명시 전달하도록 수정했다. 회귀 테스트는 수정 전 RED 실패 후 수정 후 PASS를 확인했다. 최종 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`가 모두 `BUILD SUCCESSFUL`임을 확인했다. `git diff --check`, `git diff HEAD --check`는 출력 없이 통과했다. `:app:mergeDebugResources`는 최초 sandbox에서 `~/.gradle` lock 파일 권한 문제로 실패했으나, 동일 명령을 권한 상승 재실행해 통과했다. +- 2026-07-08: 리뷰 결과 추가 반영을 완료했다. 댓글 불가 화면은 댓글 목록/header/input bar만 숨기고 별도 안내 문구 view를 제거했다. 댓글/답글 item에서 Figma에 없는 reaction row를 제거했다. 유료 미구매 상세는 raw image/audio URL을 UI model에서 계속 제거하면서 `showPaywall`로 중앙 lock+가격 paywall을 표시하도록 수정했다. 상대시간은 기존 공통 `formatUtcRelativeTimeText`를 보강해 epoch seconds, ISO offset, 6자리 fraction, 작성 직후 서버/단말 시계 차이를 처리하고, 답글 화면 부모 댓글에는 원본 `createdAtUtc`를 전달해 동일 포맷터 기준으로 표시되게 했다. 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.common.RelativeTimeFormatterTest"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`가 모두 `BUILD SUCCESSFUL`임을 확인했다. `git diff --check`, `git diff HEAD --check`는 출력 없이 통과했다. `:app:mergeDebugResources`는 최초 sandbox에서 `~/.gradle` lock 파일 권한 문제로 실패했으나, 동일 명령을 권한 상승 재실행해 통과했다. diff --git a/docs/20260708_커뮤니티_게시물_상세_댓글_답글/prd.md b/docs/20260708_커뮤니티_게시물_상세_댓글_답글/prd.md new file mode 100644 index 00000000..6f4d51f6 --- /dev/null +++ b/docs/20260708_커뮤니티_게시물_상세_댓글_답글/prd.md @@ -0,0 +1,224 @@ +# PRD: 크리에이터 채널 커뮤니티 게시물 상세 / 댓글 / 답글 + +## 1. Overview +크리에이터 채널 `커뮤니티` 탭에서 게시글 item을 터치하면 게시물 상세 화면으로 진입하고, 게시글 본문과 좋아요, 댓글 목록, 댓글의 답글 목록을 조회/작성/수정/삭제할 수 있게 한다. + +--- + +## 2. Problem +- 기존 크리에이터 채널 `커뮤니티` 탭은 게시글 목록만 표시하고 item 터치 시 별도 동작이 없어 게시글 상세로 진입할 수 없다. +- 사용자는 게시글 본문, 이미지/오디오, 좋아요 수, 댓글 수, 댓글과 최신 답글을 한 화면에서 확인하고 싶어 한다. +- 사용자는 게시글에 좋아요를 누르고, 댓글과 답글을 작성/수정/삭제하고 싶어 한다. +- 댓글 작성이 불가능한 게시글에서는 댓글 영역과 입력 영역이 노출되면 안 된다. +- 조회용 신규 v2 API 계약과 Figma 기반 UI 요구사항이 별도 문서로 정의되어야 한다. + +--- + +## 3. Goals +- 크리에이터 채널 `커뮤니티` 탭 게시글 item 터치 시 게시물 상세 화면으로 진입하는 신규 진입점을 정의한다. +- Figma 댓글 가능 + 댓글 있음 `290:9243` 기준으로 게시물 상세 UI 요구사항을 정의한다. +- Figma 댓글 불가능 `290:9266` 기준으로 댓글 불가 게시물 상세 UI 요구사항을 정의한다. +- Figma 답글 없음 `669:40462`, 답글 있음 `669:40468` 기준으로 댓글 답글 목록 화면 UI 요구사항을 정의한다. +- 조회는 신규 v2 endpoint를 사용한다. + - 상세: `GET /api/v2/creator-channels/community-posts/{postId}` + - 댓글: `GET /api/v2/creator-channels/community-posts/{postId}/comments` + - 답글: `GET /api/v2/creator-channels/community-comments/{commentId}/replies` +- 상세 응답의 `comments` 필드는 댓글 첫 페이지(`CreatorChannelCommunityCommentsResponse`)를 포함하며, 구현은 서버 계약 필드명을 따른다. + - 상세: `postId`, `creatorId`, `creatorNickname`, `creatorProfileUrl`, `createdAtUtc`, `content`, `imageUrl`, `audioUrl`, `price`, `isCommentAvailable`, `existOrdered`, `likeCount`, `commentCount`, `isPinned`, `isLiked`, `comments` + - 댓글 페이지: `commentCount`, `comments`, `page`, `size`, `hasNext` + - 댓글: `commentId`, `writerId`, `writerProfileImageUrl`, `writerNickname`, `content`, `isSecret`, `createdAtUtc`, `latestReply` + - 최신 답글: `commentId`, `writerId`, `writerProfileImageUrl`, `writerNickname`, `content`, `createdAtUtc` +- 댓글/답글 조회 query parameter 기본값은 `page=0`, `size=20`으로 둔다. +- 댓글 쓰기, 답글 쓰기, 댓글 수정, 댓글 삭제, 좋아요는 기존 `CreatorCommunityRepository`(`/creator-community/*`) API를 그대로 재사용한다. +- 댓글 팝업 메뉴는 기존 커뮤니티 댓글과 동일한 팝업 메뉴(`content_comment_option_menu`, `content_comment_option_menu2`)를 사용한다. +- 하트 터치 시 좋아요 API를 호출하고, `isLiked`에 따라 `ic_feed_community_heart_fill`/`ic_feed_community_heart` icon을 표시한다. +- 댓글 작성이 불가능한 게시글(`isCommentAvailable == false`)이면 댓글 목록과 하단 입력 영역을 숨긴다. +- 하단 댓글/답글 입력 영역은 화면 하단에 고정하고 키보드 표시 여부에 따라 함께 올라오게 한다. + +--- + +## 4. Non-Goals +- 게시글 작성/수정 화면 및 API는 이번 범위에서 구현하지 않는다(기존 화면 유지). +- 유료 게시글 구매(paywall unlock) 흐름은 이번 범위에서 새로 정의하지 않는다. 상세에서는 `price`, `existOrdered` 기반 표시만 다룬다. +- 팬Talk 상세 화면 자체는 이번 범위에서 구현하지 않는다(답글 화면과 UI만 동일). +- 서버 API schema나 endpoint를 새로 정의하지 않는다. +- 레거시 커뮤니티/댓글 관련 파일을 직접 수정하지 않는다. +- 크리에이터 채널의 다른 탭 UI나 공통 스크롤 구조를 재설계하지 않는다. + +--- + +## 5. Target Users +- 크리에이터 채널 커뮤니티 게시글을 상세히 보고 댓글로 소통하려는 로그인 사용자. +- 자신의 채널 게시글/댓글을 관리(수정/삭제)하려는 크리에이터. +- `kr.co.vividnext.sodalive.v2` 하위 크리에이터 채널 기능을 구현/유지보수하는 Android 개발자. + +--- + +## 6. User Stories +- 사용자는 커뮤니티 탭에서 게시글을 터치해 상세 화면으로 이동하고 싶다. +- 사용자는 게시글 상세에서 본문, 이미지/오디오, 좋아요 수, 댓글 수를 확인하고 싶다. +- 사용자는 하트를 눌러 게시글에 좋아요를 남기거나 취소하고 싶다. +- 사용자는 댓글을 작성하고, 각 댓글의 최신 답글을 확인하고 싶다. +- 사용자는 댓글을 터치해 답글 목록 화면으로 이동하고 답글을 작성하고 싶다. +- 사용자는 내가 쓴 댓글/답글을 수정하거나 삭제하고 싶다. +- 채널 크리에이터는 자신의 채널 댓글/답글을 삭제해 관리하고 싶다. +- 사용자는 댓글이 불가능한 게시글에서는 입력창 없이 게시글만 보고 싶다. + +--- + +## 7. Core Features + +### F1. 커뮤니티 게시물 상세 진입 +커뮤니티 탭 게시글 item 터치 시 상세 화면으로 이동한다. + +#### Requirements +- 커뮤니티 탭 게시글 item(리스트형/썸네일형) 터치 시 게시물 상세 화면으로 이동한다. +- 상세 화면에는 `postId`를 전달한다. +- 상세 진입 시 `GET /api/v2/creator-channels/community-posts/{postId}`로 상세를 조회한다. +- 로그인 토큰이 없으면 기존 크리에이터 채널 로그인 이동 정책을 따른다. + +#### Edge Cases +- `postId <= 0`이면 상세 화면으로 이동하지 않는다. +- 상세 조회 실패 시 기존 toast/error 표시 정책을 따르고 화면을 종료한다. + +### F2. 게시물 상세 본문/좋아요 +Figma `290:9243`, `290:9266` 기준으로 게시글 본문과 좋아요를 표시한다. + +#### Requirements +- 상단 title-bar에 뒤로가기 버튼을 표시한다. +- 게시글 header에 작성자 프로필 이미지(`creatorProfileUrl`), 닉네임(`creatorNickname`), 작성 시간(`createdAtUtc`)을 표시한다. +- `createdAtUtc`는 크리에이터 채널 v2 공통 날짜/시간 포맷을 따른다. +- 본문(`content`), 이미지(`imageUrl`), 오디오(`audioUrl`)를 표시한다. +- 오디오 재생은 기존 `CreatorCommunityMediaPlayerManager`를 재사용한다. +- 유료 게시글(`price > 0`)이고 미구매(`existOrdered == false`)이면 Figma paywall(잠금 + 가격) 표시를 따른다. +- 유료 게시글을 구매한 경우(`existOrdered == true`) Figma 기준 `구매완료` badge를 표시한다. +- 하트 터치 시 좋아요 API(`communityPostLike(postId)`)를 호출한다. +- `isLiked == true`이면 `ic_feed_community_heart_fill`, `false`이면 `ic_feed_community_heart`를 표시한다. +- 좋아요 수(`likeCount`)를 하트 옆에 표시한다. +- 댓글 가능(`isCommentAvailable == true`)이면 댓글 수(`commentCount`)와 댓글 icon을 함께 표시한다. + +#### Edge Cases +- `imageUrl`/`audioUrl`이 `null`이면 해당 영역을 표시하지 않는다. +- 좋아요 API 실패 시 하트 상태와 좋아요 수를 원래 값으로 되돌린다. +- 좋아요 중복 호출을 방지한다. + +### F3. 댓글 목록 (댓글 가능 게시글) +Figma `290:9243` 기준으로 댓글 목록을 표시한다. + +#### Requirements +- `GET /api/v2/creator-channels/community-posts/{postId}/comments`로 댓글을 조회한다(기본 `page=0`, `size=20`). +- 상세 조회 성공 후 댓글 endpoint의 첫 페이지(`page=0`, `size=20`)를 최초 댓글 목록으로 사용한다. +- 각 댓글 item에 작성자 프로필 이미지, 닉네임, 본문, 작성 시간을 표시한다. +- 각 댓글에 `latestReply`가 있으면 댓글 아래에 최신 답글 1개를 중첩 표시한다. +- 댓글 또는 최신 답글 영역 터치 시 해당 댓글의 답글 목록 화면으로 이동한다. +- 댓글 item 우측 더보기 버튼 터치 시 기존 커뮤니티 댓글 팝업 메뉴를 표시한다. + - 내 댓글: `content_comment_option_menu`(수정하기/삭제하기) + - 채널 크리에이터가 보는 타인 댓글: `content_comment_option_menu2`(삭제하기 기존 항목) + - 일반 타인 댓글: 커뮤니티 댓글 전용 신고 API가 없어 더보기 버튼을 노출하지 않는다. +- `hasNext == true`이면 현재 `page + 1` 페이지를 추가 로딩한다. +- 상단에 총 댓글 수(`commentCount`)를 표시한다. + +#### Edge Cases +- 댓글이 없으면 댓글 목록 영역을 비운 상태로 표시한다. +- 추가 로딩 중 중복 요청을 방지한다. + +### F4. 댓글 답글 목록 화면 (팬Talk 상세와 동일 UI) +Figma `669:40462`, `669:40468` 기준으로 답글 목록 화면을 표시한다. + +#### Requirements +- 답글 화면에는 `commentId`를 전달한다. +- `GET /api/v2/creator-channels/community-comments/{commentId}/replies`로 답글을 조회한다(기본 `page=0`, `size=20`). +- 상단 title-bar에 뒤로가기 버튼을 표시한다. +- 상단에 부모 댓글(프로필/닉네임/시간/본문/더보기)을 표시한다. +- 부모 댓글 아래에 답글 목록을 들여쓰기하여 표시한다. +- 각 답글 item에 작성자 프로필 이미지, 닉네임, 본문, 작성 시간, 더보기 버튼을 표시한다. +- 답글 item 더보기 버튼은 댓글과 동일한 팝업 메뉴 규칙(내 것: 수정/삭제, 채널 크리에이터가 보는 타인 답글: 삭제, 일반 타인 답글: 미노출)을 따른다. +- `hasNext == true`이면 현재 `page + 1` 페이지를 추가 로딩한다. +- 하단 입력 영역 placeholder는 `답글을 입력하세요`이다. + +#### Edge Cases +- 답글이 없으면(Figma `669:40462`) 부모 댓글만 표시하고 답글 목록 영역은 비운다. +- `commentId <= 0`이면 답글 화면으로 이동하지 않는다. + +### F5. 댓글/답글 입력 영역 +Figma 기준 하단 고정 입력 영역을 제공한다. + +#### Requirements +- 입력값이 없을 때 placeholder를 표시한다. + - 게시물 상세 댓글 입력: `댓글을 입력하세요` + - 답글 화면 입력: `답글을 입력하세요` +- 글자를 입력하면 전송(쓰기) 버튼을 활성화한다. +- 3줄까지는 입력창 높이가 자동 확장된다. +- 4줄 이상이면 3줄 높이를 유지하고 vertical scroll로 처리한다. +- 입력 영역은 화면 하단에 고정되고 키보드가 올라오면 키보드 위로 함께 올라온다. +- 전송 버튼 비활성 상태: `ic_new_arrow_up_gray` icon, 원형 배경, 배경색 `gray_900`. +- 전송 버튼 활성 상태: `ic_new_arrow_up_white` icon, 원형 배경, 배경색 `soda_400`. +- 게시물 상세에서 전송 시 댓글 쓰기 API(`registerComment(postId, comment)`)를 호출한다. +- 답글 화면에서 전송 시 답글 쓰기 API(`registerComment(postId, comment, parentId = commentId)`)를 호출한다. +- 작성 성공 시 입력값을 비우고 목록을 새로고침한다. + +#### Edge Cases +- 공백/줄바꿈만 입력한 경우 전송 버튼은 비활성 상태다. +- 전송 중 중복 전송을 막는다. +- 전송 실패 시 입력값을 유지하고 기존 toast/error 표시 정책을 따른다. + +### F6. 댓글/답글 수정·삭제 +기존 커뮤니티 댓글 수정/삭제 흐름을 재사용한다. + +#### Requirements +- 팝업 메뉴 `수정하기` 선택 시 기존 수정 흐름(`modifyComment(commentId, comment = ...)`)을 따른다. +- 팝업 메뉴 `삭제하기` 선택 시 기존 삭제 확인 dialog 후 삭제 흐름(`modifyComment(commentId, isActive = false)`)을 따른다. +- 수정/삭제 성공 시 현재 목록(댓글 목록 또는 답글 목록)을 새로고침한다. + +#### Edge Cases +- 삭제 확인 dialog에서 취소 시 아무 동작도 하지 않는다. +- 수정 시 변경 내용이 없으면 기존 정책대로 toast를 표시한다. + +### F7. 댓글 불가 게시글 처리 +Figma `290:9266` 기준으로 댓글 불가 상태를 처리한다. + +#### Requirements +- `isCommentAvailable == false`이면 댓글 목록 영역을 숨긴다. +- `isCommentAvailable == false`이면 하단 댓글 입력 영역을 숨긴다. +- 댓글 불가 게시글에서도 하트(좋아요)는 표시한다. +- 댓글 불가 게시글에서는 댓글 수/댓글 icon을 표시하지 않는다. + +#### Edge Cases +- 댓글 불가 게시글에서는 댓글 관련 API(`comments` 조회, 댓글 쓰기)를 호출하지 않는다. + +--- + +## 8. UX / UI Expectations +- 게시글 item 터치부터 상세 진입까지 자연스럽게 이어진다. +- 하트 상태와 좋아요 수는 터치 즉시 반응한다(낙관적 업데이트 후 실패 시 롤백). +- 입력 영역은 키보드 있음/없음 상태를 기준으로 자연스럽게 대응한다. +- 답글 화면은 팬Talk 상세와 시각적으로 동일하게 보인다. +- 전송 버튼 활성/비활성 색상과 icon은 입력값 변화에 즉시 반응한다. +- 댓글 불가 게시글은 입력 영역/댓글 영역 없이 게시글 본문 중심으로 표시된다. + +--- + +## 9. Technical Constraints +- Android XML View/ViewBinding 기반으로 구현한다. +- 신규 Activity/Fragment/ViewModel 및 하위 코드는 `kr.co.vividnext.sodalive.v2.creator.channel.community` 하위에 작성한다. +- 조회용 신규 v2 endpoint는 v2 API/Repository 계약(`ApiResponse`, RxJava3, Koin)을 따른다. +- 댓글 쓰기/답글 쓰기/수정/삭제/좋아요는 기존 `CreatorCommunityRepository`를 호출해 재사용한다(레거시 파일 직접 수정 금지). +- 댓글 팝업 메뉴는 기존 `content_comment_option_menu`, `content_comment_option_menu2` 리소스를 재사용한다. +- 커뮤니티 댓글/답글 신고는 현재 전용 신고 API/레거시 흐름이 없어 이번 구현 범위에서 제외하고, 기존 레거시 메뉴2의 실제 동작인 채널 크리에이터 삭제만 재사용한다. +- 신규 문자열은 `values`, `values-en`, `values-ja`에 추가하거나 기존 문자열을 재사용한다. +- 오디오 재생은 기존 `CreatorCommunityMediaPlayerManager`를 재사용한다. + +--- + +## 10. Metrics +- 별도 분석 이벤트 추가는 이번 범위에서 정의하지 않는다. +- 기능 성공 기준은 게시글 상세 진입, 댓글/답글 조회, 좋아요/댓글/답글 작성·수정·삭제가 정상 동작하고 목록에 반영되는 것이다. +- UI 완료 기준은 Figma `290:9243`, `290:9266`, `669:40462`, `669:40468`의 주요 layout 구조, 하트/전송 버튼 상태, 댓글 불가 처리, 입력 영역 동작이 구현 화면에 반영되었는지 확인되는 것이다. +- 자동 검증 기준은 조회 API 계약(page/size 기본값, 응답 매핑), 하트 icon/전송 버튼 상태 계약, 댓글 불가 게시글의 댓글/입력 영역 숨김 계약을 source/resource/ViewModel 테스트로 확인하는 것이다. + +--- + +## 11. Open Questions & Decisions +- 결정(재사용 vs 복사): 답글 목록 화면 UI는 팬Talk 상세와 동일하지만 조회 API가 다르고, 현재 팬Talk 상세 화면은 v2에 미구현이다. 따라서 이번 범위에서는 **커뮤니티 전용 답글 화면으로 신규 구현하되, layout XML과 댓글/답글 item view(ViewHolder)만 데이터/콜백 주입형으로 설계해 재사용 가능하게 둔다.** ViewModel/Repository/데이터 모델/화면 컨트롤러는 커뮤니티 전용으로 분리한다. + - 근거: 아직 팬Talk 상세라는 두 번째 사용처가 없어 지금 데이터 계층까지 공통 추상화하면 단일 사용처를 위한 추측성 추상화가 된다. UI 리소스/item view만 공용화하면 추후 팬Talk 상세 구현 시 동일 UI를 그대로 재사용할 수 있다. +- 남은 확인: 없음.