# 크리에이터 채널 커뮤니티 탭 구현 계획/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: 기존 구조 확인과 재사용 경계 고정 - [ ] **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와 관련 테스트 갱신 지점이 확인된다. - [ ] **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 필요성이 기록된다. - [ ] **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 사용 방식이 확인된다. - [ ] **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 리소스가 확인된다. --- ### Phase 2: API/DTO/Repository/ViewModel 계약 추가 - [ ] **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한다. - [ ] **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와 충돌 없이 컴파일된다. - [ ] **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 실패한다. - [ ] **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이다. --- ### Phase 3: Mapper/UI model 정책 추가 - [ ] **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 실패한다. - [ ] **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이다. --- ### Phase 4: Fragment, Adapter, XML UI 구현 - [ ] **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한다. - [ ] **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한다. - [ ] **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한다. - [ ] **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이다. - [ ] **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 참조가 모두 해소된다. --- ### Phase 5: Activity/Pager/Owner CTA/Pagination 연결 - [ ] **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로 연결됨이 검증된다. - [ ] **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한다. - [ ] **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 검증으로 확인된다. - [ ] **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와 호출이 컴파일된다. --- ### Phase 6: 통합 검증과 수동 확인 - [ ] **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한다. - 검증 기록: - 구현 후 기록한다. - [ ] **Task 6.2: 리소스/컴파일/린트 검증** - 실행: - `./gradlew :app:mergeDebugResources` - `./gradlew :app:compileDebugKotlin` - `./gradlew :app:ktlintCheck` - `git diff --check` - 기대 결과: - resource merge, Kotlin compile, ktlint, whitespace 검증이 모두 PASS한다. - 검증 기록: - 구현 후 기록한다. - [ ] **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 문구만 표시된다. - 검증 기록: - 구현 후 기록한다. --- ## Verification Log - 계획 문서 생성 단계에서는 코드 변경을 수행하지 않았다. 구현 후 통합 검증, 회귀 검증, 최종 수동 확인 기록을 이 섹션에 누적한다.