Files
sodalive-android/docs/20260621_크리에이터_채널_커뮤니티_탭/plan-task.md

702 lines
84 KiB
Markdown

# 크리에이터 채널 커뮤니티 탭 구현 계획/TASK
> **For agentic workers:** 각 단계는 체크박스(`- [ ]`)로 추적하고, 완료 즉시 `- [x]`로 갱신한다. 구현 범위 변경이 생기면 이 문서를 먼저 수정한 뒤 코드에 반영한다.
**Goal:** `GET /api/v2/creator-channels/{creatorId}/community` 응답을 기반으로 크리에이터 채널의 `커뮤니티` 탭에 리스트/썸네일 보기 전환, 커뮤니티 게시글 목록, 유료/구매/본인 채널 표시 분기, 오디오 재생, empty 상태, 본인 채널 하단 `커뮤니티 글 올리기` CTA와 pagination을 표시한다.
**Architecture:** 기존 `CreatorChannelActivity``ViewPager2`/`CreatorChannelPagerAdapter` 구조를 유지하고, `CreatorChannelTab.Community`의 placeholder를 신규 `CreatorChannelCommunityFragment`로 교체한다. 커뮤니티 탭 전용 Fragment/ViewModel/DTO/mapper/adapter는 `kr.co.vividnext.sodalive.v2.creator.channel.community` 하위에 두되, API/Repository는 기존 채널 공통 `CreatorChannelApi`/`CreatorChannelRepository`에 endpoint만 추가한다. 리스트형은 v2 `FeedCommunityView` 재사용 가능성을 우선 검토하되 본인 채널 우측 액션, 댓글 불가 숨김, 중앙 재생 버튼이 기존 홈 피드에 영향을 주면 커뮤니티 탭 전용 item layout으로 제한한다.
**Tech Stack:** Kotlin, Android XML Views, ViewBinding, RecyclerView, Retrofit, Gson, RxJava3, Koin, JUnit4/Robolectric local unit test.
---
## 전제와 성공 기준
- PRD: `docs/20260621_크리에이터_채널_커뮤니티_탭/prd.md`
- 기존 채널 컨테이너:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapter.kt`
- `app/src/main/res/layout/activity_creator_channel.xml`
- 기존 채널 API/Repository:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelApi.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelRepository.kt`
- 기존 커뮤니티/피드 참조:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedCommunityView.kt`
- `app/src/main/res/layout/view_feed_community.xml`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedItem.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityAllGridAdapter.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityPostMenuBottomSheetDialog.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/player/CreatorCommunityMediaPlayerManager.kt`
- Figma:
- 전체 리스트형: `290:9061`
- 전체 썸네일형: `290:9073`
- 유료이고 구매하지 않은 게시글: `290:9066`
- 전체 리스트형 + 본인 채널: `665:19021`
- API endpoint는 `GET /api/v2/creator-channels/{creatorId}/community`이다.
- 첫 페이지 `page``0`, 기본 `size``20`이다.
- 보기 방식은 클라이언트 UI 상태이며 API query parameter로 보내지 않는다.
- 기본 보기 방식은 리스트형이며 label/icon은 `리스트형`/`ic_new_list`이다.
- 토글 후 썸네일형 label/icon은 `썸네일형`/`ic_new_grid`이다.
- 게시글 item 자체를 터치해도 아무 동작을 수행하지 않는다.
- 본인 또는 구매한 사용자의 오디오 게시글 재생은 기존 `CreatorCommunityMediaPlayerManager`를 재사용한다.
- 본인 채널에 본인이 쓴 리스트형 게시글에서만 우측 상단 더보기와 유료 가격을 표시한다.
- 댓글 불가 게시글은 댓글 icon과 댓글 수를 모두 숨긴다.
- 본인 채널이면 하단 고정 `커뮤니티 글 올리기` CTA를 표시하고 기존 `CreatorCommunityWriteActivity` 진입을 우선 재사용한다.
- 구현 완료 후 최소 다음 명령을 실행한다.
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Community*"`
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*"`
- `./gradlew :app:mergeDebugResources`
- `./gradlew :app:compileDebugKotlin`
- `./gradlew :app:ktlintCheck`
- `git diff --check`
---
## Figma 참조 필요 Phase
- Phase 1: 제한 참조
- 기존 코드 경계, 위젯 재사용 가능성, 기존 커뮤니티 정책 확인이 중심이며 Figma는 PRD 기준만 확인한다.
- Phase 2: Figma 참조 불필요
- API/DTO/Repository/ViewModel 상태 모델은 서버 계약과 기존 오디오/시리즈 탭 패턴을 따른다.
- Phase 3: 제한 참조
- mapper와 item state는 PRD, 기존 `FeedCommunityView`, 기존 `CreatorCommunityAllGridAdapter` 정책을 함께 확인한다.
- Phase 4: 필수 참조
- 리스트형 item, 유료 미구매 잠금 영역, 썸네일형 grid item, Sort-bar는 Figma `290:9061`, `290:9073`, `290:9066`, `665:19021` 기준으로 구현한다.
- Phase 5: 제한 참조
- 탭 연결, pagination, owner CTA, media player 연결은 기존 코드 패턴 중심으로 검증한다.
- Phase 6: 필수 참조
- 최종 수동 화면 검증은 PRD의 모든 Figma 노드와 실제 화면을 대조한다.
---
## 파일 구조
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapter.kt`
- `CreatorChannelTab.Community`를 신규 `CreatorChannelCommunityFragment`로 연결한다.
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt`
- `CreatorChannelCommunityFragment.Host` 구현, 커뮤니티 탭 선택 시 최초 로드, pagination trigger, ViewPager 높이 갱신, owner CTA 표시/클릭, media player 생명주기 연결을 추가한다.
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelApi.kt`
- 커뮤니티 탭 endpoint를 추가한다.
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelRepository.kt`
- 커뮤니티 탭 repository method를 추가한다.
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/data/CreatorChannelCommunityTabResponse.kt`
- `CreatorChannelCommunityTabResponse`, `CreatorChannelCommunityPostResponse`를 정의한다.
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityViewModel.kt`
- 최초 조회, retry, pagination, 보기 방식 상태, loading/error/empty/content 상태를 관리한다.
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/model/CreatorChannelCommunityUiModels.kt`
- 보기 방식, 게시글 item, 잠금/오디오/owner action 상태, 화면 상태 UI model을 정의한다.
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/model/CreatorChannelCommunityMappers.kt`
- DTO를 UI model로 변환하고 유료/구매/본인/댓글 가능/썸네일 preview 정책을 결정한다.
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityFragment.kt`
- 커뮤니티 탭 UI, 보기 방식 토글, adapter 전환, pagination error toast, media player callback, host callback 연결을 담당한다.
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityListAdapter.kt`
- 리스트형 RecyclerView adapter를 담당한다.
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityGridAdapter.kt`
- 썸네일형 3열 RecyclerView adapter를 담당한다.
- 생성: `app/src/main/res/layout/fragment_creator_channel_community.xml`
- Sort-bar, RecyclerView, empty/error/retry 영역을 포함한다.
- 생성: `app/src/main/res/layout/item_creator_channel_community_list.xml`
- 리스트형 커뮤니티 item을 구현한다. 필요 시 `FeedCommunityView` 대신 전용 layout으로 만든다.
- 생성: `app/src/main/res/layout/item_creator_channel_community_grid.xml`
- 썸네일형 정사각형 grid item을 구현한다.
- 수정 가능: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedCommunityView.kt`, `app/src/main/res/layout/view_feed_community.xml`
- 공통 위젯을 재사용하는 편이 더 작다고 판단되는 경우에만 optional bind flag를 추가한다. 기존 홈/추천 피드 동작이 변하면 안 된다.
- 수정: `app/src/main/res/values/strings.xml`, `app/src/main/res/values-en/strings.xml`, `app/src/main/res/values-ja/strings.xml`
- 보기 방식 label, empty/error/retry/notice/CTA 문구를 추가 또는 기존 문자열 재사용으로 정리한다.
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt`
- `CreatorChannelCommunityViewModel` binding을 추가한다.
- 테스트 생성:
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityMapperTest.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityViewModelTest.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityPaginationTest.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityFragmentLayoutTest.kt`
- 테스트 수정:
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapterTest.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt`
---
### Phase 1: 기존 구조 확인과 재사용 경계 고정
- [x] **Task 1.1: Community 탭 placeholder 연결 지점 확인**
- 확인:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapter.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/model/CreatorChannelHomeUiModels.kt`
- 작업:
- `CreatorChannelTab.Community`가 현재 `CreatorChannelPlaceholderFragment`로 연결되는지 확인한다.
- 신규 `CreatorChannelCommunityFragment.newInstance(creatorId)`로 교체할 위치를 고정한다.
- 검증:
- `rg -n "CreatorChannelTab\\.Community|CreatorChannelPlaceholderFragment|createFragment" app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel`
- 기대 결과: Community 탭 placeholder와 관련 테스트 갱신 지점이 확인된다.
- 검증 기록:
- 2026-06-21: `CreatorChannelPagerAdapter.createFragment()`는 Home/Live/Audio/Series만 실제 Fragment를 반환하고 나머지는 `CreatorChannelPlaceholderFragment.newInstance(tab)`로 처리한다. 따라서 `CreatorChannelTab.Community`는 현재 placeholder이며, Phase 5에서 `CreatorChannelCommunityFragment.newInstance(creatorId)` 분기를 추가할 위치는 `CreatorChannelPagerAdapter.kt``when (tab)`이다.
- 2026-06-21: `CreatorChannelPagerAdapterTest`는 Home/Live/Audio/Series 외 탭이 placeholder임을 검증하고, `CreatorChannelActivitySourceTest`는 현재 Community 분기가 없음을 확인한다. Phase 5에서 두 테스트의 기대값 갱신이 필요하다.
- [x] **Task 1.2: v2 `FeedCommunityView` 재사용 가능성 확인**
- 확인:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedCommunityView.kt`
- `app/src/main/res/layout/view_feed_community.xml`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedItem.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelHomeSectionAdapter.kt`
- 작업:
- `FeedItem.Community``creatorProfileUrl`, `imageUrl`, `audioUrl`, `price`, `existOrdered` 매핑에 충분한지 확인한다.
- `FeedCommunityView`의 유료 미구매 잠금 overlay는 재사용 후보로 둔다.
- 댓글 불가 숨김, 본인 채널 우측 상단 더보기/가격, 이미지 중앙 재생 버튼이 기존 홈/추천 피드에 영향을 줄 수 있으면 전용 item layout으로 구현한다.
- 검증:
- `rg -n "FeedCommunityView|FeedItem\\.Community|existOrdered|ll_feed_community_paid_overlay|tv_feed_community_comment_count" app/src/main/java app/src/main/res/layout/view_feed_community.xml`
- 기대 결과: 공통 위젯 수정 여부와 전용 layout 필요성이 기록된다.
- 검증 기록:
- 2026-06-21: `FeedItem.Community`에는 `creatorImageUrl`, `imageUrl`, `audioUrl`, `price`, `existOrdered`, `commentCount`, `likeCount`가 있어 기본 표시 데이터와 유료 미구매 판단(`price > 0 && !existOrdered`)은 담을 수 있다. `FeedCommunityView``view_feed_community.xml`에는 `ll_feed_community_paid_overlay`, `tv_feed_community_comment_count`가 존재해 잠금 overlay는 재사용 후보로 확인했다.
- 2026-06-21: 기존 `FeedCommunityView`는 댓글 수를 항상 표시하고, owner 우측 더보기/상단 가격/중앙 play-pause 버튼 상태를 제공하지 않으며, 홈 섹션과 홈 추천에서도 재사용된다. 기존 홈/추천 피드 영향 없이 댓글 불가 숨김, owner action, 중앙 재생 버튼을 넣으려면 커뮤니티 탭 전용 item layout/adapter로 구현하는 것으로 경계를 고정한다.
- [x] **Task 1.3: 기존 커뮤니티 그리드/더보기/재생 정책 확인**
- 확인:
- `app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityAllGridAdapter.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityPostMenuBottomSheetDialog.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/player/CreatorCommunityMediaPlayerManager.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/player/CreatorCommunityContentItem.kt`
- 작업:
- 썸네일형 text preview는 기존 `CreatorCommunityAllGridAdapter.CONTENT_PREVIEW_MAX_LENGTH = 24` 정책을 우선 따른다.
- 우측 더보기는 기존 `CreatorCommunityPostMenuBottomSheetDialog`의 수정/삭제/고정/고정 해제 액션 정책을 따른다.
- 재생은 `CreatorCommunityMediaPlayerManager.toggleContent(CreatorCommunityContentItem(postId, audioUrl))`를 사용한다.
- 검증:
- `rg -n "CONTENT_PREVIEW_MAX_LENGTH|CreatorCommunityPostMenuBottomSheetDialog|CreatorCommunityMediaPlayerManager|CreatorCommunityContentItem|toggleContent" app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community`
- 기대 결과: 기존 그리드 preview, 더보기 메뉴, 재생 manager 사용 방식이 확인된다.
- 검증 기록:
- 2026-06-21: `CreatorCommunityAllGridAdapter.CONTENT_PREVIEW_MAX_LENGTH = 24`이며 text-only grid preview는 줄바꿈을 공백으로 치환하고 trim 후 24자까지만 사용한다. 신규 grid preview 정책은 이 구현을 따른다.
- 2026-06-21: `CreatorCommunityPostMenuBottomSheetDialog`는 creator 여부에 따라 신고 또는 고정/수정/삭제 메뉴를 표시하고, 고정 상태에 따라 pin/unpin 문구와 icon을 전환한다. 본인 채널 우측 더보기는 이 dialog 정책을 재사용한다.
- 2026-06-21: `CreatorCommunityMediaPlayerManager``CreatorCommunityContentItem(contentId, url)`을 받아 `toggleContent()`로 재생/일시정지를 전환하고 `isPlayingContent(contentId)``stopContent()`를 제공한다. 커뮤니티 탭 오디오 재생도 이 manager를 재사용한다.
- [x] **Task 1.4: Owner CTA 진입점과 리소스 확인**
- 확인:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt`
- `app/src/main/res/layout/activity_creator_channel.xml`
- `app/src/main/res/values/strings.xml`
- `app/src/main/res/values-en/strings.xml`
- `app/src/main/res/values-ja/strings.xml`
- 작업:
- 기존 `onOwnerFabCommunityClicked()``CreatorCommunityWriteActivity`를 여는지 확인한다.
- 하단 고정 owner CTA도 Community 탭에서 같은 method를 호출하도록 연결 계획을 고정한다.
- `ic_new_upload_community_post`, `creator_channel_owner_fab_community` 리소스 존재를 확인한다.
- 검증:
- `rg -n "onOwnerFabCommunityClicked|CreatorCommunityWriteActivity|ic_new_upload_community_post|creator_channel_owner_fab_community" app/src/main/java app/src/main/res`
- 기대 결과: 기존 커뮤니티 작성 진입점과 icon/string 리소스가 확인된다.
- 검증 기록:
- 2026-06-21: `CreatorChannelActivity.onOwnerFabCommunityClicked()`는 owner FAB를 닫고 `communityWriteLauncher.launch(Intent(this, CreatorCommunityWriteActivity::class.java))`로 기존 작성 화면을 연다. Community 탭 하단 owner CTA는 같은 method를 호출하도록 연결한다.
- 2026-06-21: `activity_creator_channel.xml`에는 `owner_fab_community_button``@drawable/ic_new_upload_community_post``@string/creator_channel_owner_fab_community`를 사용하고, `strings.xml`/`values-en`/`values-ja`에 해당 문구가 이미 존재한다. `currentOwnerCtaTab()`은 현재 Live/Audio만 반환하므로 Phase 5에서 Community 포함이 필요하다.
---
### Phase 2: API/DTO/Repository/ViewModel 계약 추가
- [x] **Task 2.1: 커뮤니티 탭 DTO 추가**
- 생성:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/data/CreatorChannelCommunityTabResponse.kt`
- 작업:
- `@Keep`, `@SerializedName` 기반으로 `CreatorChannelCommunityTabResponse`, `CreatorChannelCommunityPostResponse`를 추가한다.
- `CreatorChannelCommunityPostResponse`에는 `postId`, `creatorId`, `creatorNickname`, `creatorProfileUrl`, `createdAtUtc`, `content`, `imageUrl`, `audioUrl`, `price`, `existOrdered`, `isCommentAvailable`, `likeCount`, `commentCount`, `isPinned`를 포함한다.
- `@JsonProperty`가 아닌 프로젝트 기존 Gson 패턴인 `@SerializedName`을 사용한다.
- 검증 명령:
- `./gradlew :app:compileDebugKotlin`
- 기대 결과:
- 신규 DTO 추가 후 컴파일이 PASS한다.
- 검증 기록:
- 2026-06-21: `CreatorChannelCommunityTabResponse`, `CreatorChannelCommunityPostResponse`를 전용 `community/data` 패키지에 추가했고, `@Keep`/`@SerializedName` 기반 Gson DTO 계약을 구성했다. 최종 `./gradlew :app:compileDebugKotlin` 검증 대상으로 포함한다.
- [x] **Task 2.2: 커뮤니티 탭 endpoint와 Repository method 추가**
- 수정:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelApi.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelRepository.kt`
- 작업:
- `@GET("/api/v2/creator-channels/{creatorId}/community")` endpoint를 추가한다.
- query parameter `page`, `size`만 전달한다.
- Repository method는 `getCommunity(creatorId, page, size, token)` 형태로 둔다.
- 검증 명령:
- `./gradlew :app:compileDebugKotlin`
- 기대 결과:
- API/Repository 추가 후 기존 Koin graph와 충돌 없이 컴파일된다.
- 검증 기록:
- 2026-06-21: `CreatorChannelApi.getCommunity()``@GET("/api/v2/creator-channels/{creatorId}/community")`를 추가하고 query parameter는 `page`, `size`만 전달하도록 구성했다. `CreatorChannelRepository.getCommunity(creatorId, page, size, token)`도 동일 계약으로 추가했다. 최종 `./gradlew :app:compileDebugKotlin` 검증 대상으로 포함한다.
- [x] **Task 2.3: ViewModel RED 테스트 작성**
- 생성:
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityViewModelTest.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityPaginationTest.kt`
- 테스트 케이스:
- 최초 로딩이 `page=0`, `size=20`으로 호출된다.
- 기본 보기 방식은 `List`이다.
- 보기 방식 toggle은 `List -> Grid -> List` 순서이며 API를 재호출하지 않는다.
- `communityPostCount == 0`이면 `Empty` 상태가 된다.
- 표시 가능한 `communityPosts`가 없으면 `Empty` 상태가 된다.
- `hasNext == true`일 때 다음 페이지는 마지막 응답의 `page + 1`로 요청한다.
- load-more 요청에는 `size=20`을 유지한다.
- loading 중 중복 load-more 요청은 무시된다.
- 다음 페이지 성공 시 기존 게시글 뒤에 append한다.
- 다음 페이지 실패 시 기존 목록은 유지하고 pagination error message만 설정한다.
- `consumePaginationErrorMessage()` 호출 후 pagination error message가 null이 된다.
- 검증 명령:
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityViewModelTest"`
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityPaginationTest"`
- 기대 결과:
- production 구현 전 `CreatorChannelCommunityViewModel` 미구현으로 RED 실패한다.
- 검증 기록:
- 2026-06-21: production ViewModel 구현 전 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityViewModelTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityPaginationTest"` 실행 결과 `:app:compileDebugUnitTestKotlin`에서 `Unresolved reference 'CreatorChannelCommunityViewModel'`, `CreatorChannelCommunityUiState`, `CreatorChannelCommunityViewMode`로 RED 실패함을 확인했다.
- [x] **Task 2.4: `CreatorChannelCommunityViewModel` 구현**
- 생성:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityViewModel.kt`
- 수정:
- `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt`
- 작업:
- `DEFAULT_PAGE_SIZE = 20`, `FIRST_PAGE = 0`, 기본 `viewMode = CreatorChannelCommunityViewMode.List`로 둔다.
- `loadCommunity(creatorId: Long, isOwner: Boolean)`는 같은 `creatorId`와 기존 state가 있으면 중복 최초 조회를 막는다.
- `toggleViewMode()`는 현재 로드된 데이터를 유지하고 API를 재호출하지 않는다.
- `retryCommunity()`는 첫 페이지를 다시 조회한다.
- `loadMore()`는 content 상태, `hasNext`, `isLoadingMore`, `creatorId`를 확인해 중복 요청을 막는다.
- `requestGeneration`으로 오래된 응답이 최신 상태를 덮어쓰지 않게 한다.
- 첫 페이지 성공 후 `communityPostCount == 0` 또는 표시 가능한 item이 0개이면 `Empty` 상태로 전환한다.
- pagination 실패는 기존 content를 유지하고 `paginationErrorMessage`에만 반영한다.
- 검증 명령:
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`
- 기대 결과:
- ViewModel 테스트가 GREEN이다.
- 검증 기록:
- 2026-06-21: `CreatorChannelCommunityViewModel`을 기존 Audio/Series 탭 패턴에 맞춰 `LiveData`, `BaseViewModel`, `requestGeneration`, pagination error consume 구조로 구현하고 `AppDI` Koin binding을 추가했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` 실행 결과 BUILD SUCCESSFUL로 GREEN 확인했다.
- 2026-06-21: Phase 2 코드 리뷰에서 DTO/API/Repository/ViewModel/test 변경을 `CreatorChannelAudioViewModel`, `CreatorChannelSeriesViewModel` 패턴과 대조했고, 수정이 필요한 결함은 발견하지 않았다. 추가 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Community*"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check`를 실행했으며 모두 PASS했다.
---
### Phase 3: Mapper/UI model 정책 추가
- [x] **Task 3.1: Mapper RED 테스트 작성**
- 생성:
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityMapperTest.kt`
- 테스트 케이스:
- `createdAtUtc`는 기존 상대 시간 포맷 helper를 사용해 UI text로 변환된다.
- `creatorProfileUrl`은 profile image URL로 매핑된다.
- `isPinned == true`이면 notice/pin 표시 상태가 true다.
- `isCommentAvailable == false`이면 댓글 icon/count 표시 상태가 false다.
- `price > 0 && !existOrdered && !isOwner`이면 유료 미구매 잠금 상태다.
- 유료 미구매 상태에서는 image placeholder mode가 `LockedGray`이고 play button 표시 상태가 false다.
- `isOwner == true` 또는 `existOrdered == true`이면 `audioUrl != null && imageUrl != null`에서 play button 표시 상태가 true다.
- 본인 채널에 본인이 쓴 게시글에서만 owner more button 표시 상태가 true다.
- 본인 채널에 본인이 쓴 유료 게시글에서만 top price 표시 상태가 true다.
- 타인 채널에서는 top more/price 표시 상태가 false다.
- grid text-only preview는 줄바꿈을 공백으로 바꾸고 24자까지만 사용한다.
- 검증 명령:
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityMapperTest"`
- 기대 결과:
- mapper 미구현 상태에서 RED 실패한다.
- 검증 기록:
- 2026-06-21: `CreatorChannelCommunityMapperTest`를 추가해 보기 방식 label/icon, 상대 시간, profile URL, notice/comment 표시, 유료 미구매 잠금, play button, owner more/top price, grid preview 정책을 RED 테스트로 고정했다. Production mapper/model 구현 전 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityMapperTest"` 실행 결과 `:app:compileDebugUnitTestKotlin`에서 `community.model` 패키지, `toCommunityPostUiModels`, `CreatorChannelCommunityViewMode.labelResId/iconResId`, UI model fields 미구현으로 실패해 RED를 확인했다.
- [x] **Task 3.2: UI model과 mapper 구현**
- 생성:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/model/CreatorChannelCommunityUiModels.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/model/CreatorChannelCommunityMappers.kt`
- 작업:
- `CreatorChannelCommunityViewMode``List`, `Grid`로 정의하고 label/icon resource를 가진다.
- `CreatorChannelCommunityPostUiModel`에는 `postId`, `creatorId`, `creatorNickname`, `creatorProfileUrl`, `createdAtText`, `content`, `imageUrl`, `audioUrl`, `price`, `existOrdered`, `likeCount`, `commentCount`, `showComment`, `showNotice`, `isLocked`, `showOwnerMore`, `showOwnerTopPrice`, `showPlayButton`, `gridPreviewText`를 둔다.
- `showComment = isCommentAvailable`로 매핑한다.
- `isLocked = price > 0 && !existOrdered && !isOwner`로 매핑한다.
- `showPlayButton = !isLocked && !audioUrl.isNullOrBlank() && !imageUrl.isNullOrBlank()`로 매핑한다.
- `showOwnerMore``showOwnerTopPrice``isOwner == true && creatorId == currentUserId` 조건으로 제한한다.
- `gridPreviewText`는 기존 `CreatorCommunityAllGridAdapter`처럼 줄바꿈 제거 후 trim하고 24자까지 사용한다.
- 검증 명령:
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityMapperTest"`
- 기대 결과:
- Mapper 테스트가 GREEN이다.
- 검증 기록:
- 2026-06-21: `CreatorChannelCommunityViewMode`, `CreatorChannelCommunityPostUiModel`, `CreatorChannelCommunityImageMode`, `toCommunityPostUiModels()`를 추가하고 `formatUtcRelativeTimeText()`와 기존 grid preview 24자 정책을 재사용했다. `CreatorChannelCommunityViewMode``List`/`Grid` label과 `drawable-mdpi``ic_new_list`/`ic_new_grid` icon resource를 가진다.
- 2026-06-21: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityMapperTest"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check` 모두 PASS했다. 최초 `ktlintCheck`는 typealias 파일명/line length 위반으로 실패했고, 파일명을 `CreatorChannelCommunityViewMode.kt`로 맞춘 뒤 재실행 PASS했다.
- 2026-06-21: Reviewer gate의 컨텍스트 마이닝에서 `CreatorChannelCommunityViewModel.Content.communityPosts`가 DTO 목록을 유지해 mapper가 production 상태 경로에 적용되지 않았다는 FAIL을 확인했다. `CreatorChannelCommunityViewModel` 생성자에 `Context`를 주입하고 first page/load-more 성공 경로에서 `data.communityPosts.toCommunityPostUiModels(context, isOwner, SharedPreferenceManager.userId)`를 적용해 `Content.communityPosts``CreatorChannelCommunityPostUiModel` 목록을 들도록 보정했다. 보정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:ktlintCheck`, `git diff --check` PASS했다.
- 2026-06-21: 후속 리뷰에서 ViewModel이 `Context`를 직접 주입받아 생명주기상 memory leak 가능성이 있다는 지적을 반영했다. `UtcRelativeTimeTextFormatter``AndroidUtcRelativeTimeTextFormatter`를 추가하고, `CreatorChannelCommunityViewModel``Context` 대신 formatter만 주입받도록 변경했다. mapper는 formatter로 `createdAtText`를 생성하며, Android 구현체는 application context만 보관한다. 보정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:ktlintCheck`, `git diff --check` PASS했다.
- 2026-06-21: Phase 3 코드 리뷰에서 mapper 정책과 ViewModel 상태 경로를 `CreatorCommunityAllGridAdapter`의 24자 preview 정책, PRD의 유료/구매/본인/댓글 표시 분기와 대조했다. 수정이 필요한 결함은 발견하지 않았다. 재검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Community*"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check`를 실행했으며 모두 PASS했다.
---
### Phase 4: Fragment, Adapter, XML UI 구현
- [x] **Task 4.1: Fragment layout 추가**
- 생성:
- `app/src/main/res/layout/fragment_creator_channel_community.xml`
- 작업:
- Sort-bar에는 좌측 `전체` + `communityPostCount`, 우측 보기 방식 label + icon을 배치한다.
- RecyclerView는 리스트형과 썸네일형을 같은 `RecyclerView`에서 `LayoutManager`/adapter 교체로 처리한다.
- empty, error message, retry button 영역은 오디오/시리즈 탭 패턴을 따른다.
- 검증 명령:
- `./gradlew :app:mergeDebugResources`
- 기대 결과:
- 신규 layout binding 생성이 PASS한다.
- 검증 기록:
- 2026-06-21: RED 단계에서 `CreatorChannelCommunityFragmentLayoutTest`를 먼저 추가했고, production 파일 추가 전 `compileDebugUnitTestKotlin`이 fragment/list/grid layout ID, resource, source file 미구현으로 실패함을 확인했다.
- 2026-06-21: `fragment_creator_channel_community.xml`을 추가해 Sort-bar, 단일 `RecyclerView`, empty/error/retry 영역을 구성했다. `./gradlew :app:mergeDebugResources` PASS로 신규 layout binding 생성을 확인했다.
- [x] **Task 4.2: 리스트형 item layout/adapter 추가**
- 생성:
- `app/src/main/res/layout/item_creator_channel_community_list.xml`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityListAdapter.kt`
- 작업:
- Figma `290:9061`, `290:9066`, `665:19021` 기준의 feed card를 구현한다.
- `creatorProfileUrl`, nickname, 상대 시간, notice, 본문, 이미지/잠금 영역, reaction 영역을 표시한다.
- `showComment == false`이면 댓글 icon/count view를 `GONE` 처리한다.
- `isLocked == true`이면 이미지 대신 회색 RoundedRectangle과 lock/가격 캡슐을 표시하고 play button을 숨긴다.
- `showPlayButton == true`이면 이미지 가운데 play/pause button을 표시한다.
- `showOwnerMore == true`이면 우측 상단 더보기 버튼을 표시하고 기존 `CreatorCommunityPostMenuBottomSheetDialog` 호출 callback을 연결한다.
- root item click listener는 설정하지 않거나 no-op으로 둔다.
- 검증 명령:
- `./gradlew :app:mergeDebugResources`
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityFragmentLayoutTest"`
- 기대 결과:
- layout resource와 adapter bind source 검증이 PASS한다.
- 검증 기록:
- 2026-06-21: `item_creator_channel_community_list.xml``CreatorChannelCommunityListAdapter.kt`를 추가해 리스트형 card, 댓글 숨김, 유료 미구매 잠금, play button, owner more/price 표시 정책을 binding 경로에 반영했다.
- 2026-06-21: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityFragmentLayoutTest"` PASS로 리스트 layout/resource와 adapter bind source 검증을 확인했다.
- 2026-06-21: Phase 4 reviewer gate에서 유료 미구매 locked item의 `imageUrl` 유지와 이미지 로드 가능성, locked price pill 표시 검증 부족, play/pause icon이 실제 재생 상태와 분리된 점, owner more callback에 item 정보와 고정 상태가 부족한 점으로 초기 FAIL을 확인했다. 보정으로 locked item은 `imageUrl`을 비우고 이미지 로드를 막았으며, 가격 pill 표시를 테스트로 고정하고, `isPlayingContent(postId)` 기반 play/pause icon bind와 owner more item callback에 `isPinned` 보존 정보를 반영했다. 보정 후 Phase 4 관련 검증은 PASS했다.
- 2026-06-21: 최종 Phase 4 review fix로 리스트형 locked price capsule을 `tv_creator_channel_community_list_locked_price`가 이미지 잠금 영역 안에 표시되도록 이동했다. `ListAdapter`의 locked price와 top price 표시 조건도 분리해, 상단 가격은 본인 채널 owner-only 조건에서만 다시 보이도록 복구했다.
- 2026-06-22: Phase 4 코드 리뷰에서 Figma `665:19021` 본인 채널 리스트형 유료 게시글의 가격 태그와 더보기 버튼이 feed 카드 우측 상단 `etc` 영역에 함께 배치되는 것을 확인했다. 기존 XML은 `tv_creator_channel_community_list_top_price`가 reaction row 우측에 있어 요구사항과 어긋났으므로 RED 테스트를 추가한 뒤, `layout_creator_channel_community_list_top_actions` 컨테이너 안으로 가격 태그와 더보기 버튼을 이동했다. `CreatorChannelCommunityListAdapter`는 상단 액션 컨테이너 visibility를 `showOwnerMore || showOwnerTopPrice`로 bind하도록 보정했다.
- [x] **Task 4.3: 썸네일형 grid item layout/adapter 추가**
- 생성:
- `app/src/main/res/layout/item_creator_channel_community_grid.xml`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityGridAdapter.kt`
- 작업:
- 3열 정사각형 item을 유지하도록 layout params를 adapter에서 계산하거나 `GridLayoutManager` span 폭에 맞춘다.
- `imageUrl != null && !isLocked`이면 이미지 전체 표시.
- `imageUrl == null && !isLocked`이면 `gridPreviewText`를 중앙 정렬로 표시.
- `isLocked == true`이면 잠금/가격 표시를 노출한다.
- `showNotice == true`이면 pin/notice icon을 상단에 표시한다.
- root item click listener는 설정하지 않거나 no-op으로 둔다.
- 검증 명령:
- `./gradlew :app:mergeDebugResources`
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityFragmentLayoutTest"`
- 기대 결과:
- grid layout/resource 검증이 PASS한다.
- 검증 기록:
- 2026-06-21: `item_creator_channel_community_grid.xml``CreatorChannelCommunityGridAdapter.kt`를 추가해 3열 정사각형 grid, 이미지/텍스트 preview, 잠금/가격, notice 표시 정책을 구현했다.
- 2026-06-21: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityFragmentLayoutTest"` PASS와 `./gradlew :app:mergeDebugResources` PASS로 grid layout/resource 검증을 확인했다.
- 2026-06-21: Phase 4 reviewer gate에서 grid item 크기가 좌우 margin을 반영하지 않아 3열 정사각형 sizing이 과대 계산될 수 있다는 FAIL을 확인했다. 보정으로 grid adapter의 item 크기 계산에 RecyclerView padding과 item margin을 반영했고, margin-aware sizing 테스트를 추가했다. 보정 후 grid layout 검증은 PASS했다.
- [x] **Task 4.4: `CreatorChannelCommunityFragment` 구현**
- 생성:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityFragment.kt`
- 작업:
- `CreatorChannelCommunityViewModel`을 observe하고 Loading/Empty/Error/Content를 bind한다.
- Sort-bar 우측 toggle click에서 `viewModel.toggleViewMode()`를 호출한다.
- view mode가 List이면 `LinearLayoutManager`, Grid이면 `GridLayoutManager(spanCount = 3)`를 적용한다.
- pagination error message는 Toast로 표시하고 consume한다.
- `onCreatorChannelCommunityTabSelected()`, `onCreatorChannelCommunityScrolledToBottom()`, `onCreatorChannelCommunityOwnerCtaVisibilityChanged()` entry를 제공한다.
- media player update callback에서 adapter의 play/pause 상태를 갱신한다.
- Fragment `onDestroyView()` 또는 `onDestroy()`에서 `CreatorCommunityMediaPlayerManager.stopContent()`를 호출해 재생 리소스를 정리한다.
- 검증 명령:
- `./gradlew :app:compileDebugKotlin`
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`
- 기대 결과:
- Fragment와 adapter 테스트가 GREEN이다.
- 검증 기록:
- 2026-06-21: `CreatorChannelCommunityFragment.kt`를 추가해 ViewModel 상태 observe, Loading/Empty/Error/Content bind, 보기 방식 toggle, List/Grid `LayoutManager` 전환, pagination error consume, tab/scroll/owner CTA entry, media player 정리 경로를 구현했다.
- 2026-06-21: 병렬 Gradle 실행 1건에서 Kotlin incremental cache/daemon 충돌과 timeout이 있었고, 영향받은 community test를 단독 재실행해 PASS를 확인했다. 이후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` PASS와 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Community*"` PASS를 확인했다.
- 2026-06-21: Phase 4 reviewer gate에서 Fragment가 화면 이탈 `onPause()` 시 오디오를 멈추지 않아 백그라운드 재생이 남을 수 있다는 FAIL을 확인했다. 보정으로 `onPause()`에서 `pauseContent()`를 호출하고, 기존 정리 경로의 `stopContent()`는 유지했다. 보정 후 Fragment 생명주기 검증은 PASS했다.
- 2026-06-21: 최종 Phase 4 review fix로 `CreatorCommunityMediaPlayerManager``prepareAsync()` 완료 전 `pauseContent()``stopContent()`가 호출될 수 있는 경로를 막는 prepared-state guard를 추가했다. prepare 전 pause/stop 호출은 MediaPlayer invalid state를 만들지 않도록 보호하고, Fragment 생명주기 정리 경로는 유지했다.
- [x] **Task 4.5: 문자열/리소스 정리**
- 수정:
- `app/src/main/res/values/strings.xml`
- `app/src/main/res/values-en/strings.xml`
- `app/src/main/res/values-ja/strings.xml`
- 작업:
- `리스트형`, `썸네일형`, empty/error/retry/notice 문구를 추가한다.
- 기존 `creator_channel_owner_fab_community`, `screen_creator_community_purchase_with_can`는 재사용 가능하면 중복 추가하지 않는다.
- `ic_new_list`, `ic_new_grid`, `ic_new_upload_community_post`가 없으면 기존 drawable 정책에 맞게 추가 여부를 별도 확인 후 진행한다.
- 검증 명령:
- `./gradlew :app:mergeDebugResources`
- 기대 결과:
- 한국어/영어/일본어 string 참조가 모두 해소된다.
- 검증 기록:
- 2026-06-21: community 관련 문자열을 `strings.xml`, `values-en/strings.xml`, `values-ja/strings.xml`에 추가하고 기존 재사용 가능한 문구는 중복하지 않았다.
- 2026-06-21: `./gradlew :app:mergeDebugResources` PASS, `./gradlew :app:compileDebugKotlin` PASS, `./gradlew :app:ktlintCheck` PASS, `git diff --check` PASS를 확인했다. `ktlintCheck`는 신규 layout test와 list adapter의 formatting-only 수정 후 PASS했다.
---
### Phase 5: Activity/Pager/Owner CTA/Pagination 연결
- [x] **Task 5.1: PagerAdapter에서 Community Fragment 연결**
- 수정:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapter.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapterTest.kt`
- 작업:
- `CreatorChannelTab.Community -> CreatorChannelCommunityFragment.newInstance(creatorId)` 분기를 추가한다.
- 기존 placeholder 기대 테스트를 Community 실제 Fragment 기대값으로 갱신한다.
- 검증 명령:
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelPagerAdapterTest"`
- 기대 결과:
- Community 탭이 신규 Fragment로 연결됨이 검증된다.
- 검증 기록:
- 2026-06-22: RED로 `CreatorChannelPagerAdapterTest`에 Community 실제 Fragment 기대값을 먼저 추가했고, production 변경 전 `CreatorChannelPagerAdapterTest > createFragment는 Home Live Audio Series Community를 실제 Fragment로 생성하고 나머지는 placeholder를 유지한다``CreatorChannelPagerAdapterTest.kt:31` assertion으로 실패함을 확인했다.
- 2026-06-22: `CreatorChannelPagerAdapter.createFragment()``CreatorChannelTab.Community -> CreatorChannelCommunityFragment.newInstance(creatorId)` 분기를 추가하고 placeholder 기대에서 Community를 제외했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelPagerAdapterTest"` PASS로 Community 탭 Fragment 연결을 확인했다.
- [x] **Task 5.2: `CreatorChannelActivity` Host 연결**
- 수정:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt`
- 작업:
- `CreatorChannelCommunityFragment.Host`를 구현한다.
- `findCommunityFragment()`를 추가한다.
- `onPageSelected`와 header 변경 시 현재 탭이 Community이면 `onCreatorChannelCommunityTabSelected()`를 호출한다.
- `notifyCurrentCreatorChannelTabScrolledToBottom()`에 Community 분기를 추가한다.
- `isCreatorChannelLoadMoreTab()`에 Community를 포함한다.
- `onCreatorChannelCommunityContentChanged()`에서 ViewPager 높이 갱신과 추가 load-more 필요 여부 확인을 호출한다.
- 검증 명령:
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"`
- 기대 결과:
- Community 탭 선택/스크롤/높이 갱신 source 검증이 PASS한다.
- 검증 기록:
- 2026-06-22: RED로 `CreatorChannelActivitySourceTest`에 Community Host/pagination/owner CTA source 계약을 먼저 추가했고, production 변경 전 `CreatorChannelActivitySourceTest > Community tab source는 Fragment Host pagination owner CTA를 Activity에 연결한다``CreatorChannelActivitySourceTest.kt:455` assertion으로 실패함을 확인했다.
- 2026-06-22: `CreatorChannelActivity``CreatorChannelCommunityFragment.Host`를 구현하도록 연결하고, `findCommunityFragment()`, 탭 선택/header 변경 시 `onCreatorChannelCommunityTabSelected()`, 하단 스크롤 시 `onCreatorChannelCommunityScrolledToBottom()`, load-more 대상 포함, `onCreatorChannelCommunityContentChanged()`의 높이 갱신/추가 load-more 재평가를 추가했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest.Community tab source는 Fragment Host pagination owner CTA를 Activity에 연결한다"` PASS로 source 계약을 확인했다.
- [x] **Task 5.3: Owner CTA Community 연결**
- 수정:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt`
- 작업:
- `currentOwnerCtaTab()`에 Community 탭을 포함한다.
- `updateOwnerCtaVisibility()`에서 Community일 때 `ic_new_upload_community_post`, `creator_channel_owner_fab_community`를 bind한다.
- `onOwnerCtaClicked()`에서 Community일 때 기존 `onOwnerFabCommunityClicked()`를 호출한다.
- `findCommunityFragment()?.onCreatorChannelCommunityOwnerCtaVisibilityChanged(ownerCtaTab == CreatorChannelTab.Community)`를 호출해 하단 padding/inset을 반영하게 한다.
- 검증 명령:
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"`
- `./gradlew :app:mergeDebugResources`
- 기대 결과:
- 본인 채널 Community 탭 하단 CTA 연결이 source/resource 검증으로 확인된다.
- 검증 기록:
- 2026-06-22: `currentOwnerCtaTab()`에 Community를 포함하고, Community 탭 CTA는 `ic_new_upload_community_post`/`creator_channel_owner_fab_community`를 bind하며 클릭 시 기존 `onOwnerFabCommunityClicked()`를 호출하도록 연결했다. `findCommunityFragment()?.onCreatorChannelCommunityOwnerCtaVisibilityChanged(ownerCtaTab == CreatorChannelTab.Community)` 경로도 추가해 Fragment padding 반영을 호출한다.
- 2026-06-22: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest.Community tab source는 Fragment Host pagination owner CTA를 Activity에 연결한다"` PASS, `./gradlew :app:mergeDebugResources` PASS로 source/resource 검증을 확인했다.
- [x] **Task 5.4: media player 생명주기와 adapter 갱신 연결**
- 수정:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityFragment.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityListAdapter.kt`
- 작업:
- `CreatorCommunityMediaPlayerManager(requireContext()) { listAdapter.notifyDataSetChanged() }` 형태로 생성한다.
- play/pause button click에서 `CreatorCommunityContentItem(postId, audioUrl)`를 전달해 `toggleContent()`를 호출한다.
- bind 시 `mediaPlayerManager.isPlayingContent(postId)` 값으로 play/pause icon을 결정한다.
- Fragment 정리 시 `stopContent()`를 호출한다.
- 검증 명령:
- `./gradlew :app:compileDebugKotlin`
- 기대 결과:
- 기존 media player manager import와 호출이 컴파일된다.
- 검증 기록:
- 2026-06-22: Phase 4에서 이미 구현된 `CreatorChannelCommunityFragment``CreatorCommunityMediaPlayerManager(requireContext()) { listAdapter.notifyDataSetChanged() }`, `CreatorCommunityContentItem(item.postId, audioUrl)` 기반 `toggleContent()`, adapter `isPlayingContent(postId)` icon bind, `onPause()`/`onDestroyView()` 정리 경로를 Phase 5 source test로 재확인했다. 신규 production 변경은 Activity/Pager 연결에 한정했다.
- 2026-06-22: `./gradlew :app:compileDebugKotlin` PASS로 기존 media player manager import와 호출 컴파일을 확인했다.
- [x] **Task 5.5: reviewer gate 후속 no-op Host API 제거**
- 수정:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityFragment.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt`
- 작업:
- `CreatorChannelCommunityFragment.Host`에서 실제 호출되지 않는 `postId` 기반 owner more fallback API를 제거한다.
- `CreatorChannelActivity``onCreatorChannelCommunityOwnerMoreClicked(postId: Long) = Unit` no-op 구현을 제거한다.
- 검증 명령:
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest.Community tab source는 Fragment Host pagination owner CTA를 Activity에 연결한다"`
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Community*"`
- `./gradlew :app:compileDebugKotlin`
- 기대 결과:
- owner more Host API가 item 기반 콜백으로 단일화되고 no-op 구현 없이 컴파일된다.
- 검증 기록:
- 2026-06-22: RED로 `CreatorChannelActivitySourceTest``onCreatorChannelCommunityOwnerMoreClicked(postId: Long)``onCreatorChannelCommunityOwnerMoreClicked(item.postId)` fallback 부재 검증을 추가했고, production 보정 전 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest.Community tab source는 Fragment Host pagination owner CTA를 Activity에 연결한다"``CreatorChannelActivitySourceTest.kt:481` assertion으로 실패함을 확인했다.
- 2026-06-22: `CreatorChannelCommunityFragment.Host`를 item 기반 `onCreatorChannelCommunityOwnerMoreClicked(item)` 단일 API로 정리하고, `CreatorChannelActivity``onCreatorChannelCommunityOwnerMoreClicked(postId: Long) = Unit` no-op override를 제거했다.
- 2026-06-22: 단일 GREEN으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest.Community tab source는 Fragment Host pagination owner CTA를 Activity에 연결한다"` PASS를 확인했다. 회귀로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Community*"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` PASS를 확인했다.
- 2026-06-22: `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check` PASS를 확인했다. 최초 Gradle 실행은 sandbox의 `~/.gradle` lock 파일 접근 제한으로 실패했으며, 사용자 승인 후 동일 명령을 재실행했다.
---
### Phase 6: 통합 검증과 수동 확인
- [x] **Task 6.1: 단위 테스트 실행**
- 실행:
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Community*"`
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*"`
- 기대 결과:
- 커뮤니티 탭 mapper/ViewModel/pagination/layout/source 테스트가 모두 PASS한다.
- 검증 기록:
- 2026-06-22: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` PASS.
- 2026-06-22: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Community*"` PASS.
- 2026-06-22: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*"` PASS.
- [x] **Task 6.2: 리소스/컴파일/린트 검증**
- 실행:
- `./gradlew :app:mergeDebugResources`
- `./gradlew :app:compileDebugKotlin`
- `./gradlew :app:ktlintCheck`
- `git diff --check`
- 기대 결과:
- resource merge, Kotlin compile, ktlint, whitespace 검증이 모두 PASS한다.
- 검증 기록:
- 2026-06-22: `./gradlew :app:mergeDebugResources` PASS.
- 2026-06-22: `./gradlew :app:compileDebugKotlin` PASS.
- 2026-06-22: `./gradlew :app:ktlintCheck` PASS.
- 2026-06-22: `git diff --check` PASS.
- [x] **Task 6.3: 수동 화면 확인**
- 확인 항목:
- 타인 채널 리스트형 기본값에서 Sort-bar 우측이 `리스트형`/`ic_new_list`로 표시된다.
- 토글 시 썸네일형 3열 grid로 바뀌고 API 재호출 없이 현재 데이터를 표시한다.
- 다시 토글 시 리스트형으로 복귀한다.
- 유료 미구매 게시글은 회색 RoundedRectangle, lock, 가격 캡슐을 표시하고 재생 버튼을 숨긴다.
- 본인 또는 구매한 사용자의 오디오 게시글은 이미지 중앙에 재생/일시정지 버튼을 표시하고 기존 media player manager로 재생된다.
- 댓글 불가 게시글은 댓글 icon과 댓글 수가 보이지 않는다.
- 본인 채널 리스트형에서 본인이 쓴 게시글만 우측 상단 더보기와 유료 가격이 보인다.
- 게시글 item 터치 시 아무 동작도 하지 않는다.
- 본인 채널 Community 탭 하단 `커뮤니티 글 올리기` CTA가 고정 표시되고 기존 작성 화면으로 진입한다.
- CTA가 목록 마지막 item 또는 empty 문구를 가리지 않는다.
- `hasNext == true`일 때 스크롤 하단에서 다음 page가 append된다.
- empty 상태에서 Sort-bar와 목록/grid가 숨겨지고 empty 문구만 표시된다.
- 검증 기록:
- 2026-06-22: Figma `290:9061`, `290:9073`, `290:9066`, `665:19021`의 design context와 screenshot을 재확인했다. 리스트형 기본 Sort-bar는 `리스트형`/list icon, 썸네일형은 `썸네일형`/grid icon과 3열 gap 0 grid, 유료 미구매 feed/grid는 lock + 가격 캡슐, 본인 채널 리스트형은 우측 상단 가격 + 더보기와 하단 `커뮤니티 글 올리기` CTA 기준임을 대조했다.
- 2026-06-22: 연결 기기 `SM-G960N - 10``./gradlew :app:installDebug`로 최신 debug APK 설치 PASS를 확인했다.
- 2026-06-22: `adb shell am start -n kr.co.vividnext.sodalive.debug/kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivity --el extra_creator_id 100``CreatorChannelActivity`가 exported되지 않아 `Permission Denial`로 차단됨을 확인했다.
- 2026-06-22: debug scheme `voiceon-test://channel/100` 딥링크는 `DeepLinkActivity`로 resolve됐지만, 화면 포커스와 UI dump는 `MainV2Activity` 홈 화면(`추천`, `랭킹`, `팔로잉`, `방금 활동한 크리에이터`)에 머물렀고 `커뮤니티`/`리스트형`/`썸네일형` 문구가 없어 커뮤니티 탭 실제 조작 검증에는 도달하지 못했다.
- 2026-06-22: 따라서 실제 기기 수동 조작 항목 중 리스트/썸네일 토글, 오디오 재생, owner CTA 클릭, pagination append, empty 상태 표시는 이번 세션에서 직접 확인하지 못했다. 대신 Figma screenshot 대조, source/layout 단위 테스트, debug APK 설치, 딥링크 resolve 확인, 리소스/컴파일/린트/공백 검증으로 대체했다.
---
## Verification Log
- 계획 문서 생성 단계에서는 코드 변경을 수행하지 않았다. 구현 후 통합 검증, 회귀 검증, 최종 수동 확인 기록을 이 섹션에 누적한다.
- 2026-06-22 Phase 6 통합 검증과 수동 확인:
- 단위 테스트: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Community*"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*"` 모두 PASS.
- 리소스/컴파일/린트/공백: `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check` 모두 PASS.
- Figma 대조: `290:9061`, `290:9073`, `290:9066`, `665:19021` screenshot/context를 기준으로 리스트형, 썸네일형, 유료 미구매, 본인 채널 CTA/owner action 요구사항을 재확인했다.
- 실제 기기 확인: `adb devices`에서 `SM-G960N - 10` 연결 확인, `./gradlew :app:installDebug` PASS. `CreatorChannelActivity` 직접 실행은 exported=false로 shell start가 차단됐고, `voiceon-test://channel/100` 딥링크는 resolve됐지만 `MainV2Activity` 홈에 머물러 커뮤니티 탭 실조작 QA는 blocked로 기록한다.
- 대체 검증: UI dump에서 홈 화면 표시(`추천`, `랭킹`, `팔로잉`, `방금 활동한 크리에이터`)만 확인되어 커뮤니티 탭 문구는 확인되지 않았다. 실제 리스트/썸네일 토글, 오디오 재생, owner CTA, pagination, empty 상태 조작은 QA 가능한 계정/creator id 또는 내부 진입 경로 확보 후 재확인이 필요하다.
- 2026-06-22 수동 확인 후속 UI 불일치 수정 계획:
- 발견 항목: 썸네일형 전환 시 3열 Grid가 시각적으로 유지되지 않는 문제, 리스트형 공지 표시가 Figma와 다른 문제, 중앙/우측 상단 유료 가격 capsule이 Figma와 다른 문제, 게시물 이미지 14dp radius 미반영 및 GIF 미재생 문제를 확인했다.
- 수정 범위: `item_creator_channel_community_grid.xml`, `item_creator_channel_community_list.xml`, `CreatorChannelCommunityGridAdapter.kt`, `CreatorChannelCommunityListAdapter.kt`에 한정한다. 레거시 커뮤니티 파일과 공용 `ImageExtensions.loadUrl()`은 수정하지 않는다.
- TDD 예외 사유: 이번 후속 수정은 RecyclerView item 측정, XML 배치, bitmap/GIF 로딩 라이브러리 전환처럼 실제 Android 렌더링 표면에서 확인되는 시각 보정이다. 기존 layout/source 테스트가 구조 존재와 연결을 이미 검증하고 있어, 새 production 변경 전 실패하는 순수 단위 테스트를 안정적으로 만들기 어렵다. 대신 resource merge, Kotlin compile, ktlint, diff check와 소스 기반 표면 검증으로 확인한다.
- 기대 결과: 썸네일형은 `GridLayoutManager(3)`에서 정사각형 item 높이를 명시적으로 받아 3열 Grid로 표시되고, 리스트형 공지는 pin icon + `Notice` 행으로 표시되며, 유료 가격 capsule은 중앙/우측 상단 Figma 형태를 따른다. 게시물 이미지는 Glide `CenterCrop + RoundedCorners(14dp)`로 로드되어 GIF 재생과 14dp radius를 지원한다.
- 2026-06-22 수동 확인 후속 UI 불일치 수정 검증:
- 수정 기록: Grid item root 높이를 adapter가 계산한 정사각형 크기로 직접 지정할 수 있게 XML root를 `match_parent`로 변경하고, Grid/List 게시물 이미지는 Glide `CenterCrop + RoundedCorners(14dp)`로 로딩하도록 전환했다. 리스트형 공지는 item 최상단 pin icon + `Notice` 행으로 이동했고, owner 우측 상단 가격 capsule과 중앙 locked 가격 capsule의 크기/typography/icon gap을 Figma 기준에 맞게 조정했다.
- 보정 기록: 최초 검증에서 `@color/point_400` 미존재로 resource linking이 실패해 기존 design token `@color/green_400(#73FF01)`로 교체했다. 기존 layout/source 테스트 3건은 새 XML 구조와 adapter 정사각형 계약에 맞게 기대값을 갱신했다.
- 검증: `./gradlew :app:mergeDebugResources` PASS, `./gradlew :app:compileDebugKotlin` PASS, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` PASS, `./gradlew :app:ktlintCheck` PASS, `git diff --check` PASS.
- 수동 확인: `adb devices` 결과 연결된 기기가 없어 실제 앱에서 리스트/썸네일 토글, GIF 재생, radius, 공지/가격 표시를 직접 조작 검증하지 못했다. 이번 세션에서는 Figma screenshot 대조, source/layout 검증, resource/compile/test/lint로 대체했다.
- 리뷰어 게이트: visual-engineering reviewer가 변경 파일, Figma 이슈 4건, 검증 증거, 실제 기기 QA 제한을 검토했고 blocking issue 없음으로 PASS했다.
- 2026-06-22 썸네일형 후속 동작 보정 계획:
- 요구사항: 썸네일형에서는 GIF 재생을 요구하지 않고 정적 썸네일 표시를 허용한다. 썸네일형 item은 모두 정사각형 비율을 유지해야 하며, 썸네일형 상태에서도 스크롤 하단 pagination이 동작해야 한다.
- 수정 계획: `CreatorChannelCommunityGridAdapter`의 Glide 요청은 grid 전용으로 `asBitmap()`을 사용해 정적 이미지를 로드하고, 리스트형 adapter의 GIF 재생 가능 경로는 유지한다. `CreatorChannelActivity`는 Community content/view mode 변경으로 ViewPager 높이를 갱신한 뒤 현재 하단 조건을 재평가해 NestedScrollView의 추가 scroll 이벤트가 없어도 load-more 경로가 호출되도록 보정한다.
- 검증 계획: `CreatorChannelCommunityFragmentLayoutTest`, `CreatorChannelActivitySourceTest`, community 관련 unit test, `mergeDebugResources`, `compileDebugKotlin`, `ktlintCheck`, `git diff --check`를 실행한다.
- 수정 기록: Grid adapter 이미지 요청에 `asBitmap()`을 적용해 썸네일형 GIF를 정적 이미지로 로드하도록 했다. `updateViewPagerHeight()`에 height 갱신 이후 callback을 추가하고 Community/Live/Audio/Series content 변경 시 높이 갱신 후 bottom 조건을 재평가하도록 순서를 보장했다.
- 검증: focused source/layout test 최초 실행은 기존 source test가 `updateViewPagerHeight()` 시그니처와 height 비교 문자열을 고정해 둔 탓에 2건 실패했고, 새 계약에 맞춰 테스트 기대값을 갱신한 뒤 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"` PASS를 확인했다.
- 회귀/빌드: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` PASS, `./gradlew :app:mergeDebugResources :app:compileDebugKotlin :app:ktlintCheck` PASS, `git diff --check` PASS. `ktlintCheck`는 기존 `.editorconfig disabled_rules` deprecation 경고만 출력됐다.
- 수동 확인: 현재 세션에서는 연결된 기기/에뮬레이터가 없어 실제 화면에서 썸네일형 스크롤 pagination과 GIF 정적 표시를 직접 조작 검증하지 못했다.
- 리뷰 보정 계획: context mining reviewer가 content/view mode 변경 후 bottom 재평가 경로가 기존 scroll listener의 200dp threshold와 달리 `remainingScroll <= 0`만 사용한다고 지적했다. `checkCreatorChannelCurrentTabNeedsMore()`도 동일 threshold를 사용하도록 보정하고, PRD의 GIF 재생 문구를 리스트형/썸네일형 기준으로 분리한다.
- 리뷰 보정 검증: `checkCreatorChannelCurrentTabNeedsMore()``CREATOR_CHANNEL_LOAD_MORE_THRESHOLD_DP`를 사용하도록 수정했고, `CreatorChannelActivitySourceTest`에 threshold 재사용 검증을 추가했다. PRD의 GIF 문구는 리스트형은 재생, 썸네일형은 정적 썸네일 허용으로 분리했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"` PASS, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` PASS, `./gradlew :app:mergeDebugResources :app:compileDebugKotlin :app:ktlintCheck` PASS, `git diff --check` PASS.
- 추가 보정 계획: 수동 확인 요청에 따라 썸네일형 Grid는 3등분 구조와 기존 item margin을 유지하되, 이미지가 있는 item content가 세로로 길어지지 않고 1:1 비율을 가져야 한다. margin을 포함한 cell 폭에서 좌우 margin을 제외한 content width를 계산하고 같은 값을 height로 지정한다.
- 추가 보정 기록: `item_creator_channel_community_grid.xml` root margin은 유지했고, `CreatorChannelCommunityGridAdapter`의 item size 계산은 `(availableWidth - itemHorizontalMargins * 3) / 3`을 사용하는 helper로 분리했다. `CreatorChannelCommunityFragmentLayoutTest`는 360px 가용 폭과 좌우 margin 8px 기준 content 112px, 365px 기준 113px, 좁은 폭 0px 계산과 `height = squareSize` 계약을 검증한다.
- 추가 보정 검증: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityFragmentLayoutTest"` PASS.
- 2026-06-22 썸네일형 Grid Figma/레거시 재보정:
- 기준 확인: Figma `290:9078`은 134px 정사각 item이 gap 0으로 배치되고, Figma `290:9079` 공지 item은 우측 상단 pin icon만 표시한다. 레거시 `CreatorCommunityAllGridAdapter`는 bind 시 root `layoutParams.width``height`를 같은 `itemSize`로 지정하고, `item_creator_community_all_grid.xml`은 pin icon `ImageView``top|end`에 둔다.
- RED: 새 source/layout 계약을 먼저 추가했고, production 보정 전 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityFragmentLayoutTest"``iv_creator_channel_community_grid_notice` 미존재와 helper signature 불일치로 `compileDebugUnitTestKotlin` 실패를 확인했다.
- 수정 기록: v2 Grid adapter도 레거시처럼 `setItemSizePx()`로 item size를 받아 root width/height를 같은 값으로 고정하도록 변경했다. Grid mode에서는 RecyclerView 좌우 padding을 0으로 조정해 Figma처럼 item 사이 gap이 생기지 않게 했고, list mode 복귀 시 좌우 14dp padding을 복구한다. Grid notice는 text label 대신 우측 상단 `ic_pin` ImageView로 변경했다.
- GREEN: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityFragmentLayoutTest"` PASS.
- 리뷰 게이트 FAIL: 5-agent review에서 code quality reviewer는 Grid item size가 RecyclerView width 측정 전 0으로 계산되면 `itemSizePx`가 0으로 남을 수 있다고 지적했고, context mining reviewer는 locked grid tile에서 text preview가 lock overlay 뒤에 표시될 수 있다고 지적했다. QA reviewer는 debug build 설치와 deep link 진입까지 확인했지만 테스트 creator id의 API error 상태로 실제 grid content visual QA는 blocked로 판정했다.
- 리뷰 보정 RED: `CreatorChannelCommunityFragmentLayoutTest`에 layout 이후 `doOnLayout` 기반 size 재계산 계약과 locked grid item에서 text preview를 숨기는 계약을 추가했다. Production 보정 전 focused test는 `updateGridItemSize()`/`doOnLayout` 미존재와 locked text 조건 미충족으로 2건 실패했다.
- 리뷰 보정 기록: Grid mode bind 시 `updateGridItemSize()`를 즉시 호출하고 `RecyclerView.doOnLayout { updateGridItemSize() }`로 width 측정 이후 item size를 재계산하도록 했다. Grid adapter는 `!item.isLocked && item.imageMode != CreatorChannelCommunityImageMode.Image`일 때만 text preview를 표시해 locked tile은 lock/price overlay만 보이도록 했다.
- 리뷰 보정 GREEN: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityFragmentLayoutTest"` PASS.
- 2026-06-22 썸네일형 Grid rounded corner 제거 계획:
- 요구사항: 썸네일형 Grid item은 rounded corner를 제거하고 정사각 모서리로 표시한다. 리스트형 게시물 이미지의 14dp radius와 GIF 재생 정책은 유지한다.
- 수정 계획: `CreatorChannelCommunityGridAdapter`에서 Grid 전용 `RoundedCorners(14dp)` transform과 root `clipToOutline`을 제거한다. `item_creator_channel_community_grid.xml`의 Grid root/image rounded drawable background는 non-rounded 배경으로 교체한다. 공유 drawable과 레거시 파일은 수정하지 않는다.
- 검증 계획: `CreatorChannelCommunityFragmentLayoutTest`에 Grid rounded corner 제거 계약을 RED로 추가한 뒤 production 보정 후 focused/community regression, resource merge, Kotlin compile, ktlint, `git diff --check`를 실행한다.
- RED: `CreatorChannelCommunityFragmentLayoutTest`에 Grid XML이 `bg_feed_card`/`bg_feed_community_image`를 사용하지 않고, Grid adapter source가 `RoundedCorners`, `14f.dpToPx()`, `root.clipToOutline = true`를 포함하지 않는다는 계약을 추가했다. Production 보정 전 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityFragmentLayoutTest"`는 rounded drawable background와 `RoundedCorners` 사용으로 2건 실패했다.
- 수정 기록: `CreatorChannelCommunityGridAdapter`에서 `RoundedCorners` transform, `dpToPx` import, root `clipToOutline` 설정을 제거하고 `CenterCrop()`만 유지했다. `item_creator_channel_community_grid.xml`의 root/image background는 공유 rounded drawable 대신 `@color/gray_900`으로 변경했다.
- GREEN: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityFragmentLayoutTest"` PASS.
- 회귀/빌드: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"` PASS, `./gradlew :app:mergeDebugResources :app:compileDebugKotlin :app:ktlintCheck` PASS, `git diff --check` PASS. `ktlintCheck`는 기존 `.editorconfig disabled_rules` deprecation 경고만 출력됐다.
- 2026-06-21 Phase 2 검증:
- RED: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityViewModelTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityPaginationTest"`는 production ViewModel 구현 전 `CreatorChannelCommunityViewModel`/`CreatorChannelCommunityUiState`/`CreatorChannelCommunityViewMode` 미구현으로 `:app:compileDebugUnitTestKotlin` 실패를 확인했다.
- GREEN: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` PASS.
- 회귀: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Community*"` PASS.
- 확장 회귀: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*"`는 229개 중 `CreatorChannelHomeViewModelTest > 채널 후원 성공은 기존 후원 API를 호출하고 홈을 다시 로드한다` 1건 실패. 동일 테스트 단독 재실행 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelHomeViewModelTest.채널 후원 성공은 기존 후원 API를 호출하고 홈을 다시 로드한다"`는 PASS하여 Phase 2 변경과 직접 관련 없는 비결정적 실패로 기록한다.
- 빌드/리소스/린트: `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check` PASS.
- 2026-06-21 Phase 3 코드 리뷰 및 검증:
- 코드 리뷰: `CreatorChannelCommunityMappers`, `CreatorChannelCommunityUiModels`, `CreatorChannelCommunityViewModel`의 first page/load-more UI model 적용 경로, Koin binding, string resource를 Phase 3 정책과 대조했고 수정이 필요한 결함은 발견하지 않았다.
- GREEN: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` PASS.
- 회귀: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Community*"` PASS.
- 확장 회귀: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*"` PASS.
- 빌드/리소스/린트: `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check` PASS.
- 2026-06-21 Phase 4 검증:
- RED: `CreatorChannelCommunityFragmentLayoutTest`를 먼저 추가했고 production 구현 전 `compileDebugUnitTestKotlin`이 fragment/list/grid layout ID, resource, source file 미구현으로 실패함을 확인했다.
- Production: `fragment_creator_channel_community.xml`, `item_creator_channel_community_list.xml`, `item_creator_channel_community_grid.xml`, `CreatorChannelCommunityFragment.kt`, `CreatorChannelCommunityListAdapter.kt`, `CreatorChannelCommunityGridAdapter.kt`, ko/en/ja community strings를 추가했다.
- GREEN: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityFragmentLayoutTest"` PASS.
- 회귀: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` PASS, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Community*"` PASS.
- 빌드/리소스/린트: `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check` PASS. `ktlintCheck`는 신규 layout test와 list adapter의 formatting-only 수정 후 PASS했다.
- 참고: 병렬 Gradle 실행 1건에서 Kotlin incremental cache/daemon 충돌과 timeout이 발생했고, 영향받은 community test를 단독 재실행해 PASS를 확인했다.
- 2026-06-21 Phase 4 reviewer gate 수정 및 재검증:
- 초기 결과: reviewer gate가 locked image와 price pill, play/pause 상태, owner more item 정보, `onPause()` media pause, grid margin sizing 문제로 FAIL했다.
- 수정 기록: `isPinned` 보존, locked item `imageUrl` clearing과 이미지 load 차단, locked price pill 검증, `isPlayingContent(postId)` 기반 play/pause icon, owner more item callback, Fragment `onPause()``pauseContent()`, grid margin-aware sizing, 관련 테스트 갱신을 반영했다.
- GREEN: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` PASS.
- 회귀: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Community*"` PASS.
- 빌드/리소스/린트: `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check` PASS.
- 최종 결과: post-fix reviewer gate PASS로 Phase 4 review-gate fixes 검증을 완료했다.
- 2026-06-21 Phase 4 최종 리뷰 수정 및 검증:
- 최종 수정 기록: 리스트형 locked list price capsule은 `tv_creator_channel_community_list_locked_price`를 이미지 잠금 영역 안으로 이동해 locked card 내부에서 표시되게 했다. `ListAdapter`는 locked price와 top price 조건을 분리했고, top price는 본인 채널 owner-only 게시글에서만 보이도록 복구했다. `CreatorCommunityMediaPlayerManager`에는 `prepareAsync()` 완료 전 `pauseContent()`/`stopContent()` 호출을 막는 prepared-state guard를 추가했다.
- GREEN: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` PASS.
- 회귀: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Community*"` PASS.
- 리소스: `./gradlew :app:mergeDebugResources` PASS.
- 컴파일: `./gradlew :app:compileDebugKotlin` PASS.
- 린트: `./gradlew :app:ktlintCheck` PASS.
- 공백 검증: `git diff --check` PASS.
- 2026-06-22 Phase 4 보안 로그 제거 후 최종 검증:
- 보안 수정 기록: `CreatorChannelCommunityViewModel.kt``Logger.e(message)``Logger` import를 제거했고, `CreatorCommunityMediaPlayerManager.kt``e.printStackTrace()`를 제거했다. `authToken()`/`SharedPreferenceManager.token`은 repository 호출용 bearer 생성 경로로만 남아 있으며 로그로 노출하지 않는다.
- GREEN: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` PASS.
- 컴파일: `./gradlew :app:compileDebugKotlin` PASS.
- 린트: `./gradlew :app:ktlintCheck` PASS.
- 공백 검증: `git diff --check` PASS.
- 최종 보안 재리뷰: Oracle verdict PASS, severity none, blocking_issues 없음.
- 2026-06-22 Phase 4 코드 리뷰 및 검증:
- 코드 리뷰: Figma `665:19021`과 Phase 4 요구사항을 기준으로 리스트형 owner 유료 가격 태그 위치, 잠금 이미지 처리, play/pause 상태, owner more callback, grid sizing, media player 생명주기를 대조했다. 가격 태그가 reaction row에 배치된 결함 1건을 발견했고, 우측 상단 액션 컨테이너로 이동해 수정했다.
- RED: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityFragmentLayoutTest"``layout_creator_channel_community_list_top_actions` 미구현으로 실패함을 확인했다.
- GREEN: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityFragmentLayoutTest"` PASS.
- 회귀: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` PASS, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Community*"` PASS.
- 리소스/컴파일/린트: `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck` PASS.
- 공백 검증: `git diff --check` PASS.
- 참고: Gradle 실행 중 기존 `WeekCalendarAdapter.kt` Kotlin annotation target 경고, 기존 테스트 deprecation 경고, 기존 `.editorconfig``disabled_rules` deprecation 경고가 출력됐으나 이번 Phase 4 변경 파일의 실패는 없었다.
- 2026-06-22 Phase 5 검증:
- RED: `CreatorChannelPagerAdapterTest`는 production 연결 전 Community가 placeholder라 `CreatorChannelPagerAdapterTest.kt:31` assertion으로 실패했고, `CreatorChannelActivitySourceTest > Community tab source는 Fragment Host pagination owner CTA를 Activity에 연결한다`는 production 연결 전 `CreatorChannelActivitySourceTest.kt:455` assertion으로 실패함을 확인했다.
- Production: `CreatorChannelPagerAdapter`에서 `CreatorChannelTab.Community``CreatorChannelCommunityFragment.newInstance(creatorId)`로 연결했다. `CreatorChannelActivity`에는 `CreatorChannelCommunityFragment.Host`, `findCommunityFragment()`, 탭 선택/header 변경 최초 로드, nested scroll pagination, content changed 높이 갱신/추가 load-more 재평가, Community owner CTA icon/text/click/padding callback 연결을 추가했다.
- GREEN: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelPagerAdapterTest"` PASS, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest.Community tab source는 Fragment Host pagination owner CTA를 Activity에 연결한다"` PASS.
- 회귀: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Community*"` PASS, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*"` PASS. 기존 `tab source는 기존 custom tab no op 정책을 제거한다`의 Community 부재 assertion은 Phase 5 목표와 충돌해 제거했다.
- 리소스/컴파일/린트: `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check` PASS. `ktlintCheck`는 신규 source test의 긴 assertion formatting-only 수정 후 PASS했다.
- 참고: 초반 병렬 Gradle 실행으로 `app/build/kspCaches/debugUnitTest` KSP cache corruption과 Kotlin daemon 충돌이 발생했다. 생성 캐시 `app/build/kspCaches/debugUnitTest`를 제거하고 `./gradlew --stop` 후 순차 재실행해 동일 테스트가 PASS함을 확인했다.
- 2026-06-22 Phase 5 reviewer gate 수정 및 재검증:
- 초기 결과: reviewer gate가 `CreatorChannelActivity.onCreatorChannelCommunityOwnerMoreClicked(item)``onClickPin`/`onClickModify`/`onClickDelete` no-op 콜백을 차단 이슈로 FAIL했다. 이는 기존 `CreatorCommunityPostMenuBottomSheetDialog` 수정/삭제/고정/고정 해제 정책을 따른다는 Phase 1.3/Phase 5 요구사항과 충돌하는 것으로 확인했다.
- RED: `CreatorChannelActivitySourceTest`에 owner more callback no-op 방지 검증을 추가했고, production 보정 전 `CreatorChannelActivitySourceTest.kt:473` assertion으로 실패함을 확인했다.
- 수정 기록: `CreatorChannelActivity`에 기존 `CreatorCommunityAllViewModel`을 주입해 `updateCommunityPostFixed(item.postId, !item.isPinned)`, `CreatorCommunityModifyActivity` 결과 launcher, delete 확인 `SodaDialog``deleteCommunityPostList(postId = item.postId)`를 연결했다. write/modify/pin/delete 성공 후 현재 Community Fragment가 `onCreatorChannelCommunityRefreshRequested()`로 첫 페이지를 재조회하도록 `CreatorChannelCommunityFragment`/`CreatorChannelCommunityViewModel` refresh entry를 추가했다.
- 후속 reviewer 결과: pin/delete 성공 후 refresh가 기존 `CreatorCommunityAllViewModel.communityPostListLiveData` emit에 의존하면 빈 목록 성공 시 최신 화면 갱신이 누락될 수 있다는 차단 이슈를 확인했다. 또한 레거시 코드는 수정하지 않는다는 작업 원칙에 따라 `CreatorCommunityAllViewModel` 변경을 제거했다.
- 최종 수정 기록: `CreatorChannelActivity`에서 기존 레거시 ViewModel을 수정/주입하지 않고 `CreatorCommunityRepository`를 직접 호출해 고정/고정 해제와 삭제 요청을 수행한다. 성공 응답에서는 즉시 `findCommunityFragment()?.onCreatorChannelCommunityRefreshRequested()`를 호출해 마지막 게시글 삭제처럼 빈 목록이 되는 경우에도 Community 탭이 자체 first page refresh를 수행하도록 보정했다. 수정 진입은 기존 `CreatorCommunityModifyActivity`를 호출만 하며, 결과 성공 시 동일 refresh entry를 호출한다.
- GREEN: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest.Community tab source는 Fragment Host pagination owner CTA를 Activity에 연결한다"` PASS.
- 회귀: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Community*"` PASS, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*"` PASS.
- 리소스/컴파일/whitespace: `./gradlew :app:mergeDebugResources` PASS, `./gradlew :app:compileDebugKotlin` PASS, `git diff --check` PASS.
- 린트 참고: `./gradlew :app:ktlintCheck`는 현재 diff가 없는 레거시 `CreatorCommunityAllViewModel.kt`의 기존 package underscore/indentation 위반으로 FAIL했다. 사용자 지침에 따라 레거시 파일은 수정하지 않았고, `GIT_MASTER=1 git diff -- app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/CreatorCommunityAllViewModel.kt` 결과 변경 없음으로 확인했다.
- 최종 reviewer gate: Phase 5 변경, owner more callback, pin/delete 성공 refresh, 레거시 `CreatorCommunityAllViewModel.kt` 미수정 상태, 레거시 ktlint 실패 범위 문서화를 재검토했고 PASS를 확인했다.
- 2026-06-22 Phase 5 후속 no-op Host API 제거 및 최종 검증:
- 코드 리뷰: `CreatorChannelCommunityFragment.Host`에 실제 호출 경로가 item 기반으로 전환된 뒤에도 `postId` fallback API가 남아 있고, `CreatorChannelActivity`가 이를 no-op으로 구현하는 불필요한 확장 지점을 확인했다. owner more는 `CreatorChannelCommunityPostUiModel``isPinned` 등 item 상태가 필요하므로 item 기반 콜백 하나로 단일화하는 것이 Phase 5 요구사항에 더 맞다고 판단했다.
- RED: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest.Community tab source는 Fragment Host pagination owner CTA를 Activity에 연결한다"`는 production 보정 전 `CreatorChannelActivitySourceTest.kt:481` assertion으로 실패함을 확인했다.
- 수정 기록: `CreatorChannelCommunityFragment.Host``onCreatorChannelCommunityOwnerMoreClicked(postId: Long)` fallback API와 `CreatorChannelActivity`의 no-op override를 제거했고, source test에 no-op/fallback 부재 검증을 추가했다.
- GREEN: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest.Community tab source는 Fragment Host pagination owner CTA를 Activity에 연결한다"` PASS.
- 회귀: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Community*"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` PASS.
- 리소스/컴파일/린트/공백: `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check` PASS.
- 참고: 첫 Gradle 실행은 sandbox의 `~/.gradle` lock 파일 접근 제한으로 실패했고, 사용자 승인 후 같은 테스트를 재실행해 RED/GREEN을 확인했다. 이번 최종 `ktlintCheck`는 PASS했으며 `.editorconfig disabled_rules` deprecation 경고만 출력됐다.