diff --git a/docs/20260611_크리에이터_채널_홈_탭/plan-task.md b/docs/20260611_크리에이터_채널_홈_탭/plan-task.md new file mode 100644 index 00000000..b33ae73f --- /dev/null +++ b/docs/20260611_크리에이터_채널_홈_탭/plan-task.md @@ -0,0 +1,503 @@ +# 크리에이터 채널 홈 탭 구현 계획/TASK + +> **For agentic workers:** 구현 시 `superpowers:subagent-driven-development` 또는 `superpowers:executing-plans`를 사용해 task 단위로 진행한다. 각 단계는 체크박스(`- [ ]`)로 추적하고, 완료 즉시 `- [x]`로 갱신한다. + +**Goal:** `GET /api/v2/creator-channels/{creatorId}/home` 응답을 기반으로 크리에이터 채널 신규 페이지의 상단 구성과 `홈` 탭 컨텐츠를 표시한다. + +**Architecture:** 신규 화면은 `kr.co.vividnext.sodalive.v2.creatorchannel` 하위에 Activity, ViewModel, data/model/ui 패키지를 두고 기존 Retrofit + RxJava3 + `ApiResponse` + Koin 패턴을 따른다. `RecommendedActivityType`은 홈 추천 전용 이름을 제거해 공용 `CreatorActivityType`으로 이동하고, 홈 추천과 크리에이터 채널 홈이 같은 타입을 참조하게 한다. UI는 Android XML Views/ViewBinding 기반으로 구성하고, 홈 탭 외 다른 탭 상세 화면은 이번 범위에서 연결하지 않는다. + +**Tech Stack:** Kotlin, Android XML Views, ViewBinding, RecyclerView/NestedScroll, Retrofit, Gson, RxJava3, Koin, JUnit4/Robolectric local unit test. + +--- + +## 전제와 성공 기준 +- PRD: `docs/20260611_크리에이터_채널_홈_탭/prd.md` +- Figma 전체 페이지: `296:14890` +- 크리에이터 이미지 영역: `296:14892` +- title-bar 상태별 Figma: + - 팔로우 상태가 아님: `296:14287` + - 팔로우 중 + 알림 받기 중: `296:14288` + - 팔로우 중 + 알림 받기 중이 아님: `296:14289` +- 이번 범위는 크리에이터 채널 전체 구성과 `홈` 탭에 한정한다. +- tab-bar 목록은 `홈`, `라이브`, `오디오`, `시리즈`, `커뮤니티`, `팬Talk`, `후원` 순서를 따른다. +- `라이브`, `오디오`, `시리즈`, `커뮤니티`, `팬Talk`, `후원` 탭의 상세 컨텐츠와 pagination은 구현하지 않는다. +- 홈 API는 `GET /api/v2/creator-channels/{creatorId}/home`을 사용한다. +- 팔로우/알림 변경은 기존 `UserRepository.creatorFollow()` 및 `CreatorFollowNotifyFragment` 사용 패턴을 재사용한다. +- `대화하기`는 `ChatRoomActivity`, `DM 보내기`는 `DmChatRoomActivity.newIntentByCreatorId()`로 이동한다. +- title-bar black 전환 기준은 title-bar와 tab-bar의 실제 거리 및 프로필 이미지가 절반 이상 사라졌는지를 함께 사용한다. +- 구현 완료 후 최소 다음 명령을 실행한다. + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.common.*"` + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creatorchannel.*"` + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*"` + - `./gradlew :app:mergeDebugResources` + - `./gradlew :app:compileDebugKotlin` + - `./gradlew :app:ktlintCheck` + +--- + +## Figma 참조 필요 Phase +- Phase 1: 부분 참조 + - 구현 범위 확인 시 PRD의 Figma 노드 기준만 확인한다. + - 실제 치수/레이어 구현 판단은 하지 않는다. +- Phase 2: Figma 참조 불필요 + - `CreatorActivityType` 공용화는 서버 타입/기존 홈 추천 타입 정리 작업이다. +- Phase 3: Figma 참조 불필요 + - API/DTO/Repository는 서버 계약과 기존 네트워크 패턴만 따른다. +- Phase 4: 부분 참조 + - `CreatorChannelHomeSection` 순서와 section 표시 여부를 Figma `296:14890` 홈 구성 기준으로 맞출 때 참조한다. + - title-bar 아이콘 상태 자체는 PRD와 리소스명으로 검증 가능하지만, 우측 액션 배치 우선순위는 Figma 상태별 노드 `296:14287`, `296:14288`, `296:14289`를 확인한다. +- Phase 5: 필수 참조 + - `activity_creator_channel_home.xml`, title-bar overlay, 크리에이터 이미지 영역, 버튼 영역 보존/중앙 정렬, tab-bar 배치, 홈 section item layout은 Figma `296:14890`, `296:14892`, `296:14287`, `296:14288`, `296:14289`를 기준으로 구현한다. +- Phase 6: 필수 참조 + - sticky tab-bar 위치, title-bar와 tab-bar가 가까워지는 시점, 프로필 이미지가 절반 이상 사라지는 기준을 실제 화면 배치와 비교해야 하므로 Figma `296:14890`과 `296:14892`를 참조한다. +- Phase 7: 부분 참조 + - 팔로우/알림 API 연결은 Figma가 필요 없지만, tab-bar 7개 항목의 시각 표시와 title-bar 아이콘 click target 배치는 Figma 기준을 확인한다. +- Phase 8: 필수 참조 + - 수동 화면 검증은 Figma와 실제 구현 화면을 비교해 PRD Metrics를 확인한다. + +--- + +## 파일 구조 +- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/common/CreatorActivityType.kt` + - 기존 `RecommendedActivityType`을 대체하는 공용 활동 타입을 정의한다. +- 수정/삭제 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/RecommendedActivityType.kt` + - 공용 타입 이동 후 기존 파일은 제거하거나 호환 alias가 반드시 필요한 경우 최소 범위로 남긴다. +- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationMappers.kt` + - `RecommendedActivityType` 참조를 `CreatorActivityType`으로 변경한다. +- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationUiModels.kt` + - 최근 활동 크리에이터 UI 모델의 타입을 공용 `CreatorActivityType`으로 변경한다. +- 수정: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/RecommendedActivityTypeTest.kt` + - 테스트명을 유지하거나 `CreatorActivityTypeTest`로 이동해 공용 타입 검증으로 변경한다. +- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/CreatorChannelHomeActivity.kt` + - 크리에이터 채널 홈 화면, intent 진입, edge-to-edge/inset, title/tab scroll, click 연결을 담당한다. +- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/CreatorChannelHomeViewModel.kt` + - 홈 API 호출, 팔로우/알림 변경, loading/error/content 상태를 관리한다. +- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/data/CreatorChannelHomeApi.kt` + - `GET /api/v2/creator-channels/{creatorId}/home` endpoint를 정의한다. +- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/data/CreatorChannelHomeModels.kt` + - PRD의 서버 DTO를 Android DTO로 정의한다. +- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/data/CreatorChannelHomeRepository.kt` + - 홈 API와 기존 크리에이터 팔로우 API 호출을 ViewModel에 제공한다. +- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/model/CreatorChannelHomeUiModels.kt` + - 화면 상태, tab 항목, header/action/home section UI model을 정의한다. +- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/model/CreatorChannelHomeMappers.kt` + - API 응답을 UI model로 변환한다. +- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/model/CreatorChannelTitleBarState.kt` + - 팔로우/알림 조합별 title-bar 아이콘 상태와 버튼 표시 상태를 순수 모델로 정의한다. +- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/model/CreatorChannelScrollState.kt` + - title-bar black 전환과 tab-bar sticky 기준을 순수 함수로 계산한다. +- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/ui/CreatorChannelHomeSectionAdapter.kt` + - 홈 탭 section 목록을 RecyclerView로 표시한다. +- 생성 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/ui/CreatorChannelTabAdapter.kt` + - 기존 `TextTabBarView`로 7개 탭 표시가 어렵거나 Figma tab-bar와 맞지 않을 때만 추가한다. +- 생성: `app/src/main/res/layout/activity_creator_channel_home.xml` + - 크리에이터 이미지 영역, title-bar, tab-bar, 홈 컨텐츠 RecyclerView를 포함한 화면 레이아웃이다. +- 생성: `app/src/main/res/layout/item_creator_channel_home_*.xml` + - 홈 탭 섹션별 item layout을 필요한 만큼 추가한다. +- 수정: `app/src/main/AndroidManifest.xml` + - `CreatorChannelHomeActivity`를 등록한다. +- 수정: `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt` + - 신규 API, Repository, ViewModel을 Koin에 등록한다. +- 테스트 생성/수정: + - 생성: `app/src/test/java/kr/co/vividnext/sodalive/v2/common/CreatorActivityTypeTest.kt` + - 생성: `app/src/test/java/kr/co/vividnext/sodalive/v2/creatorchannel/CreatorChannelHomeMapperTest.kt` + - 생성: `app/src/test/java/kr/co/vividnext/sodalive/v2/creatorchannel/CreatorChannelTitleBarStateTest.kt` + - 생성: `app/src/test/java/kr/co/vividnext/sodalive/v2/creatorchannel/CreatorChannelScrollStateTest.kt` + - 생성: `app/src/test/java/kr/co/vividnext/sodalive/v2/creatorchannel/CreatorChannelHomeViewModelTest.kt` + - 생성: `app/src/test/java/kr/co/vividnext/sodalive/v2/creatorchannel/CreatorChannelHomeActivitySourceTest.kt` + +--- + +### Phase 1: 기존 구조 확인과 작업 경계 고정 + +- [ ] **Task 1.1: 기존 v2 홈/채팅/팔로우 구조 확인** + - 확인: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeRecommendationApi.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeRecommendationViewModel.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/user/UserRepository.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/CreatorFollowNotifyFragment.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRoomActivity.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatRoomActivity.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt` + - 검증: + - REST는 Retrofit + RxJava3 + `ApiResponse` 패턴을 따른다. + - DM 이동은 `DmChatRoomActivity.newIntentByCreatorId(context, creatorId)`를 사용할 수 있음을 확인한다. + - 팔로우/알림 변경은 `UserRepository.creatorFollow(creatorId, follow, notify)`를 재사용할 수 있음을 확인한다. + +- [ ] **Task 1.2: 구현 제외 범위 재확인** + - 확인: + - `docs/20260611_크리에이터_채널_홈_탭/prd.md` + - 제외: + - `라이브`, `오디오`, `시리즈`, `커뮤니티`, `팬Talk`, `후원` 탭 상세 화면 + - 홈 탭 pagination + - 신규 팔로우/알림 API + - `ChatRoomActivity`, `DmChatRoomActivity` 내부 동작 변경 + - 별도 analytics/logging + - 검증: + - 이후 Phase의 모든 Task가 위 제외 범위를 침범하지 않는지 확인한다. + +--- + +### Phase 2: `CreatorActivityType` 공용화 + +- [ ] **Task 2.1: 공용 활동 타입 RED 테스트 작성** + - 생성: + - `app/src/test/java/kr/co/vividnext/sodalive/v2/common/CreatorActivityTypeTest.kt` + - 수정: + - `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/RecommendedActivityTypeTest.kt` + - 작업: + - `LIVE`, `LIVE_REPLAY`, `AUDIO`, `COMMUNITY`가 대소문자 무시로 파싱되는지 검증한다. + - 각 타입의 label resource가 기존 `RecommendedActivityType`과 동일한지 검증한다. + - 알 수 없는 code는 `null`을 반환하는지 검증한다. + - 검증 명령: + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.common.CreatorActivityTypeTest"` + - 기대 결과: + - 공용 타입 미구현으로 RED 실패한다. + +- [ ] **Task 2.2: `CreatorActivityType` 생성 및 홈 추천 참조 변경** + - 생성: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/common/CreatorActivityType.kt` + - 수정: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationMappers.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationUiModels.kt` + - `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` + - 삭제 후보: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/RecommendedActivityType.kt` + - 작업: + - 기존 enum 값과 `code`, `labelResId`를 그대로 유지해 `CreatorActivityType`을 생성한다. + - `HomeRecommendationRecentlyActiveCreatorUiModel.activityType` 타입을 `CreatorActivityType?`으로 변경한다. + - `HomeRecommendationMappers.kt`의 parser helper 이름은 `toCreatorActivityTypeLabelRes()`로 변경하거나, 기존 테스트 호환을 위해 같은 파일 안에서 최소 alias만 유지한다. + - 홈 추천 테스트의 import와 enum 참조를 `CreatorActivityType`으로 변경한다. + - 검증 명령: + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.common.CreatorActivityTypeTest"` + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*"` + - 기대 결과: + - 공용 타입 테스트와 기존 홈 추천 테스트가 PASS한다. + +--- + +### Phase 3: 크리에이터 채널 홈 API/DTO/Repository 추가 + +- [ ] **Task 3.1: API DTO와 endpoint 정의** + - 생성: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/data/CreatorChannelHomeApi.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/data/CreatorChannelHomeModels.kt` + - 작업: + - PRD의 `CreatorChannelHomeResponse`와 하위 DTO를 모두 `@Keep`, `@SerializedName` 기반 data class로 추가한다. + - `CreatorChannelScheduleResponse.type`은 공용 `CreatorActivityType`을 사용한다. + - Retrofit endpoint는 `@GET("/api/v2/creator-channels/{creatorId}/home")`로 정의한다. + - 반환 타입은 `Single>`를 사용한다. + - 검증 명령: + - `./gradlew :app:compileDebugKotlin` + - 기대 결과: + - 신규 DTO와 API가 컴파일된다. + +- [ ] **Task 3.2: Repository 추가** + - 생성: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/data/CreatorChannelHomeRepository.kt` + - 작업: + - `getHome(creatorId: Long, token: String)`에서 홈 API를 호출한다. + - 기존 `UserRepository`를 생성자 인자로 받아 `followCreator(creatorId, follow, notify)`를 얇게 위임한다. + - token 문자열은 기존 ViewModel 패턴과 맞춰 ViewModel에서 `"Bearer ${SharedPreferenceManager.token}"`로 만든다. + - 검증 명령: + - `./gradlew :app:compileDebugKotlin` + - 기대 결과: + - Repository가 기존 DI 패턴과 충돌 없이 컴파일된다. + +- [ ] **Task 3.3: Koin DI 등록** + - 수정: + - `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt` + - 작업: + - `CreatorChannelHomeApi`, `CreatorChannelHomeRepository`, `CreatorChannelHomeViewModel` import를 추가한다. + - `networkModule`에 `single { ApiBuilder().build(get(), CreatorChannelHomeApi::class.java) }`를 추가한다. + - `repositoryModule`에 `factory { CreatorChannelHomeRepository(api = get(), userRepository = get()) }`를 추가한다. + - `viewModelModule`에 `viewModel { CreatorChannelHomeViewModel(get()) }`를 추가한다. + - 검증 명령: + - `./gradlew :app:compileDebugKotlin` + - 기대 결과: + - Koin 등록과 import가 컴파일된다. + +--- + +### Phase 4: UI 모델/매퍼/ViewModel 구현 + +- [ ] **Task 4.1: 홈 응답 매퍼 RED 테스트 작성** + - 생성: + - `app/src/test/java/kr/co/vividnext/sodalive/v2/creatorchannel/CreatorChannelHomeMapperTest.kt` + - 작업: + - `creator.isFollow`, `creator.isNotify`, `creator.isAiChatAvailable`, `creator.isDmAvailable`가 UI model에 그대로 반영되는지 검증한다. + - tab 목록이 `홈`, `라이브`, `오디오`, `시리즈`, `커뮤니티`, `팬Talk`, `후원` 순서인지 검증한다. + - `currentLive`, `latestAudioContent`가 null이면 해당 홈 section이 생성되지 않는지 검증한다. + - 빈 list는 crash 없이 빈 section 또는 hidden section 정책으로 매핑되는지 검증한다. + - SNS URL이 빈 문자열이면 해당 SNS item이 숨김 또는 disabled 상태로 매핑되는지 검증한다. + - 검증 명령: + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creatorchannel.CreatorChannelHomeMapperTest"` + - 기대 결과: + - UI model/mapper 미구현으로 RED 실패한다. + +- [ ] **Task 4.2: UI model과 mapper 구현** + - 생성: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/model/CreatorChannelHomeUiModels.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/model/CreatorChannelHomeMappers.kt` + - 작업: + - `CreatorChannelHomeUiState.Loading`, `Content`, `Empty`, `Error`를 정의한다. + - `CreatorChannelTab.Home`, `Live`, `Audio`, `Series`, `Community`, `FanTalk`, `Donation`을 정의한다. + - `CreatorChannelHeaderUiModel`에 `creatorId`, `nickname`, `profileImageUrl`, `followerCount`, `isFollow`, `isNotify`, `isAiChatAvailable`, `isDmAvailable`을 둔다. + - 홈 section은 Figma 순서에 맞춰 `sealed interface CreatorChannelHomeSection`으로 정의한다. + - `CreatorChannelHomeResponse.toUiContent()` 매퍼를 구현한다. + - 검증 명령: + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creatorchannel.CreatorChannelHomeMapperTest"` + - 기대 결과: + - 매퍼 테스트가 PASS한다. + +- [ ] **Task 4.3: title-bar 상태 순수 모델 테스트와 구현** + - 생성: + - `app/src/test/java/kr/co/vividnext/sodalive/v2/creatorchannel/CreatorChannelTitleBarStateTest.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/model/CreatorChannelTitleBarState.kt` + - 작업: + - `isFollow=false`이면 follow icon은 `ic_new_follow`, bell icon은 없음으로 계산한다. + - `isFollow=true && isNotify=true`이면 follow icon은 `ic_new_following`, bell icon은 `ic_bar_bell_colored`로 계산한다. + - `isFollow=true && isNotify=false`이면 follow icon은 `ic_new_following`, bell icon은 `ic_bar_bell`로 계산한다. + - 상태 변경 중에는 중복 클릭 방지를 위한 `isActionEnabled=false` 상태를 표현한다. + - 검증 명령: + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creatorchannel.CreatorChannelTitleBarStateTest"` + - 기대 결과: + - title-bar 상태 테스트가 PASS한다. + +- [ ] **Task 4.4: ViewModel RED 테스트와 구현** + - 생성: + - `app/src/test/java/kr/co/vividnext/sodalive/v2/creatorchannel/CreatorChannelHomeViewModelTest.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/CreatorChannelHomeViewModel.kt` + - 작업: + - `loadHome(creatorId)`는 `creatorId > 0`일 때만 홈 API를 호출한다. + - API 성공 시 `CreatorChannelHomeUiState.Content`를 발행한다. + - API 실패 또는 exception 시 `Error`와 unknown error toast를 발행한다. + - `follow(follow, notify)`는 기존 팔로우 API를 호출하고 성공 시 로컬 header의 `isFollow`, `isNotify`를 갱신한다. + - 팔로우/알림 변경 중에는 중복 요청을 막는다. + - 검증 명령: + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creatorchannel.CreatorChannelHomeViewModelTest"` + - 기대 결과: + - ViewModel 테스트가 PASS한다. + +--- + +### Phase 5: 레이아웃과 홈 탭 UI 연결 + +- [ ] **Task 5.1: 화면 layout 생성** + - 생성: + - `app/src/main/res/layout/activity_creator_channel_home.xml` + - 작업: + - root는 `CoordinatorLayout`, `ConstraintLayout`, `NestedScrollView`/`RecyclerView` 중 기존 sticky 구현에 가장 적합한 구조를 선택한다. + - 크리에이터 이미지 영역은 status bar 영역까지 확장 가능하게 상단에 배치한다. + - title-bar는 이미지 위 overlay로 배치하고 `ic_new_bar_back`, `ic_new_more`를 사용한다. + - follow, following, bell icon view를 title-bar 우측에 배치한다. + - `대화하기`, `DM 보내기` 버튼 영역은 버튼이 없어도 높이를 보존할 수 있는 container로 만든다. + - tab-bar는 7개 탭을 표시할 container로 만든다. + - 홈 컨텐츠는 `RecyclerView` 또는 기존 섹션 패턴에 맞는 container로 둔다. + - 검증 명령: + - `./gradlew :app:mergeDebugResources` + - 기대 결과: + - 신규 layout과 drawable 참조가 resource merge를 통과한다. + +- [ ] **Task 5.2: 홈 섹션 Adapter와 item layout 추가** + - 생성: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/ui/CreatorChannelHomeSectionAdapter.kt` + - `app/src/main/res/layout/item_creator_channel_home_live.xml` + - `app/src/main/res/layout/item_creator_channel_home_latest_audio.xml` + - `app/src/main/res/layout/item_creator_channel_home_donation.xml` + - `app/src/main/res/layout/item_creator_channel_home_notice.xml` + - `app/src/main/res/layout/item_creator_channel_home_schedule.xml` + - `app/src/main/res/layout/item_creator_channel_home_audio.xml` + - `app/src/main/res/layout/item_creator_channel_home_series.xml` + - `app/src/main/res/layout/item_creator_channel_home_community.xml` + - `app/src/main/res/layout/item_creator_channel_home_fantalk.xml` + - `app/src/main/res/layout/item_creator_channel_home_introduce.xml` + - `app/src/main/res/layout/item_creator_channel_home_activity.xml` + - `app/src/main/res/layout/item_creator_channel_home_sns.xml` + - 작업: + - 모든 section을 한 번에 과하게 완성하지 말고, API 필드를 화면에 표시할 최소 텍스트/이미지 영역부터 연결한다. + - 기존 `AudioContentCardView`, `SeriesContentCardView`, `FeedAdapter`, `LiveThumbnail*` 위젯을 재사용할 수 있는 section은 우선 재사용한다. + - 빈 list 또는 null section은 adapter item으로 추가하지 않는다. + - 검증 명령: + - `./gradlew :app:mergeDebugResources` + - `./gradlew :app:compileDebugKotlin` + - 기대 결과: + - section adapter와 item layout이 컴파일된다. + +- [ ] **Task 5.3: Activity 생성 및 ViewModel observe 연결** + - 생성: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/CreatorChannelHomeActivity.kt` + - `app/src/test/java/kr/co/vividnext/sodalive/v2/creatorchannel/CreatorChannelHomeActivitySourceTest.kt` + - 수정: + - `app/src/main/AndroidManifest.xml` + - 작업: + - `EXTRA_CREATOR_ID`와 `newIntent(context, creatorId)`를 추가한다. + - `creatorId <= 0`이면 API 호출 없이 finish한다. + - `onCreate`에서 ViewModel observe, adapter, tab-bar, title-bar click listener를 연결한다. + - `onCreate` 또는 `onStart`에서 `viewModel.loadHome(creatorId)`를 호출한다. + - Manifest에 Activity를 등록한다. + - 검증 명령: + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creatorchannel.CreatorChannelHomeActivitySourceTest"` + - `./gradlew :app:compileDebugKotlin` + - 기대 결과: + - intent extra, invalid creatorId 처리, Manifest 등록 검증이 PASS한다. + +- [ ] **Task 5.4: title-bar와 이미지 영역 bind 구현** + - 수정: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/CreatorChannelHomeActivity.kt` + - 작업: + - `creator.profileImageUrl`, `nickname`, `followerCount`를 이미지 영역에 표시한다. + - `CreatorChannelTitleBarState`에 따라 `ic_new_follow`, `ic_new_following`, `ic_bar_bell_colored`, `ic_bar_bell`을 표시한다. + - title-bar 초기 배경은 투명 overlay로 시작한다. + - 뒤로가기는 `finish()`로 처리한다. + - 더보기는 기존 프로필/크리에이터 더보기 메뉴 패턴을 확인해 최소 연결하거나, 기존 패턴이 명확하지 않으면 click handler만 분리하고 후속 구현 대상으로 남긴다. + - 검증 명령: + - `./gradlew :app:compileDebugKotlin` + - 기대 결과: + - header/title bind가 컴파일된다. + +- [ ] **Task 5.5: 대화하기/DM 버튼 표시와 이동 연결** + - 수정: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/CreatorChannelHomeActivity.kt` + - 작업: + - `isAiChatAvailable=false`이면 `대화하기` 버튼을 숨긴다. + - `isDmAvailable=false`이면 `DM 보내기` 버튼을 숨긴다. + - 두 버튼 중 하나만 보일 때는 버튼 container 안에서 가운데 정렬한다. + - 두 버튼이 모두 숨김이어도 버튼 영역 container 높이는 유지한다. + - `대화하기`는 `ChatRoomActivity.newIntent(context, roomId)` 계약상 `roomId`가 필요하므로, 홈 API에 room id가 없으면 기존 AI 채팅 진입에 필요한 source를 구현 계획 단계에서 추가 확인하고, 확인 전에는 click을 비활성 처리하지 말고 별도 route helper에 `creatorId` 기반 진입 불가를 명시한다. + - `DM 보내기`는 `DmChatRoomActivity.newIntentByCreatorId(context, creatorId)`로 이동한다. + - 검증 명령: + - `./gradlew :app:compileDebugKotlin` + - 기대 결과: + - 버튼 표시 조건과 DM 이동이 컴파일된다. + +--- + +### Phase 6: sticky tab-bar와 scroll 상태 구현 + +- [ ] **Task 6.1: scroll 상태 계산 RED 테스트 작성** + - 생성: + - `app/src/test/java/kr/co/vividnext/sodalive/v2/creatorchannel/CreatorChannelScrollStateTest.kt` + - 작업: + - title-bar와 tab-bar의 실제 거리가 기준 이하이고 프로필 이미지가 절반 이상 사라졌을 때 black 전환이 `true`인지 검증한다. + - 둘 중 하나라도 만족하지 않으면 black 전환이 `false`인지 검증한다. + - tab-bar sticky top은 status bar 높이와 title-bar 높이를 합산해 계산되는지 검증한다. + - 검증 명령: + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creatorchannel.CreatorChannelScrollStateTest"` + - 기대 결과: + - scroll 상태 모델 미구현으로 RED 실패한다. + +- [ ] **Task 6.2: scroll 상태 계산 모델 구현** + - 생성: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/model/CreatorChannelScrollState.kt` + - 작업: + - `shouldUseBlackTitleBar(titleBarBottom: Int, tabBarTop: Int, profileImageVisibleHeight: Int, profileImageTotalHeight: Int): Boolean`을 추가한다. + - 실제 거리 조건은 `tabBarTop <= titleBarBottom` 또는 기존 layout에서 측정 가능한 최소 gap 기준으로 구현한다. + - 프로필 이미지 절반 이상 사라짐은 `profileImageVisibleHeight <= profileImageTotalHeight / 2`로 계산한다. + - sticky top 계산 helper는 `statusBarHeight + titleBarHeight`를 반환한다. + - 검증 명령: + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creatorchannel.CreatorChannelScrollStateTest"` + - 기대 결과: + - scroll 상태 테스트가 PASS한다. + +- [ ] **Task 6.3: Activity scroll listener 연결** + - 수정: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/CreatorChannelHomeActivity.kt` + - 작업: + - system bar inset을 읽어 이미지 영역이 status bar 뒤까지 확장되도록 적용한다. + - title-bar content에는 status bar inset만큼 top padding 또는 margin을 적용해 터치 영역을 보존한다. + - scroll listener에서 tab-bar sticky 위치와 title-bar black 배경을 갱신한다. + - tab-bar가 sticky 된 이후에는 홈 컨텐츠만 계속 스크롤되도록 header/tab translate 또는 parent scroll range를 조정한다. + - 검증 명령: + - `./gradlew :app:compileDebugKotlin` + - 수동 확인: + - 상단에서 이미지가 status bar 영역까지 표시된다. + - 스크롤 시 tab-bar가 title-bar 아래에 고정된다. + - 프로필 이미지가 절반 이상 사라지고 tab-bar가 title-bar에 가까워지면 title-bar가 black으로 전환된다. + +--- + +### Phase 7: 팔로우/알림 변경과 탭 동작 연결 + +- [ ] **Task 7.1: 팔로우/알림 click 흐름 연결** + - 수정: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/CreatorChannelHomeActivity.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/CreatorChannelHomeViewModel.kt` + - 작업: + - 미팔로우 상태에서 follow icon 클릭 시 `viewModel.follow(follow = true, notify = true)`를 호출한다. + - 팔로우 중 상태에서 follow/following icon 클릭 시 `CreatorFollowNotifyFragment`를 띄운다. + - `알림 모두 받기`, `알림 받지 않기`, `언팔로우` 선택은 각각 기존 `follow=true/notify=true`, `follow=true/notify=false`, `follow=false/notify=false` 조합으로 호출한다. + - 요청 중에는 title-bar follow/bell click을 막는다. + - 검증 명령: + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creatorchannel.CreatorChannelHomeViewModelTest"` + - `./gradlew :app:compileDebugKotlin` + - 기대 결과: + - 팔로우/알림 상태 변경 테스트와 컴파일이 PASS한다. + +- [ ] **Task 7.2: tab-bar 7개 항목 표시와 홈 외 탭 클릭 정책 적용** + - 수정: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/CreatorChannelHomeActivity.kt` + - `app/src/main/res/layout/activity_creator_channel_home.xml` + - 생성 후보: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creatorchannel/ui/CreatorChannelTabAdapter.kt` + - 작업: + - tab-bar에 `홈`, `라이브`, `오디오`, `시리즈`, `커뮤니티`, `팬Talk`, `후원`을 표시한다. + - 기본 선택은 `홈`이다. + - 홈 외 탭 클릭 시 상세 화면을 로드하지 않는다. + - 홈 외 탭 클릭 정책은 구현 시점에 최소 동작으로 둔다. + - 선택 상태만 바꾸지 않고 유지하거나, + - 선택은 가능하되 컨텐츠 전환은 하지 않는 방식 중 기존 tab 컴포넌트 동작과 가장 충돌이 적은 방식을 선택한다. + - 검증 명령: + - `./gradlew :app:compileDebugKotlin` + - 수동 확인: + - 7개 탭이 가로 폭을 초과해도 표시/스크롤이 가능하다. + - 홈 외 탭을 눌러도 crash가 발생하지 않는다. + +--- + +### Phase 8: 최종 검증과 문서 기록 + +- [ ] **Task 8.1: 단위 테스트 실행** + - 실행: + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.common.*"` + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creatorchannel.*"` + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*"` + - 기대 결과: + - 신규 공용 타입, 크리에이터 채널 홈, 기존 홈 추천 관련 테스트가 모두 `BUILD SUCCESSFUL`로 통과한다. + +- [ ] **Task 8.2: 리소스/컴파일/린트 검증** + - 실행: + - `./gradlew :app:mergeDebugResources` + - `./gradlew :app:compileDebugKotlin` + - `./gradlew :app:ktlintCheck` + - 기대 결과: + - 모든 명령이 `BUILD SUCCESSFUL`로 통과한다. + +- [ ] **Task 8.3: 수동 화면 검증** + - 확인: + - 크리에이터 이미지가 OS status bar 영역까지 표시된다. + - title-bar 좌측 `ic_new_bar_back`, 우측 `ic_new_more`가 표시된다. + - 팔로우/알림 상태별 아이콘 조합이 PRD와 일치한다. + - `대화하기`, `DM 보내기` 버튼 표시 조합별 레이아웃이 유지된다. + - tab-bar가 title-bar 아래에 sticky 되고 title-bar black 전환 기준이 동작한다. + - 홈 API 응답의 각 section이 홈 탭에 표시된다. + - 기대 결과: + - PRD Metrics 항목을 모두 만족한다. + +- [ ] **Task 8.4: 검증 기록 누적** + - 수정: + - `docs/20260611_크리에이터_채널_홈_탭/plan-task.md` + - `docs/20260611_크리에이터_채널_홈_탭/prd.md` + - 작업: + - 실행한 테스트/빌드/린트 명령과 결과를 검증 기록에 누적한다. + - 실패 후 수정한 명령이 있다면 실패 기록을 삭제하지 않고 후속 성공 기록을 추가한다. + - 기대 결과: + - 문서 하단에 구현 후 검증 이력이 남는다. + +--- + +## Verification Log +- 2026-06-12: `docs/20260611_크리에이터_채널_홈_탭/prd.md`, `docs/agent-guides/work-plan-docs.md`, `docs/agent-guides/build-test-style.md`, `docs/agent-guides/code-style.md`를 확인해 계획 문서 작성 규칙과 검증 명령 규칙을 확인했다. +- 2026-06-12: 기존 `HomeRecommendationApi`, `HomeRecommendationModels`, `HomeRecommendationRepository`, `HomeRecommendationViewModel`, `RecommendedActivityType`, `HomeRecommendationMappers`, `HomeRecommendationUiModels`, `AppDI`, `ChatRoomActivity`, `DmChatRoomActivity`를 확인해 신규 크리에이터 채널 홈 구현 계획의 파일 경계와 재사용 지점을 정리했다. +- 2026-06-12: 이번 단계는 구현 계획/TASK 문서 작성만 수행했으며 구현/빌드/테스트는 실행하지 않았다. +- 2026-06-12: Figma Design 참조가 필요한 Phase를 점검해 Phase 5, Phase 6, Phase 8은 필수 참조, Phase 1, Phase 4, Phase 7은 부분 참조, Phase 2, Phase 3은 참조 불필요로 계획 문서에 명시했다. diff --git a/docs/20260611_크리에이터_채널_홈_탭/prd.md b/docs/20260611_크리에이터_채널_홈_탭/prd.md new file mode 100644 index 00000000..03ad63cd --- /dev/null +++ b/docs/20260611_크리에이터_채널_홈_탭/prd.md @@ -0,0 +1,417 @@ +# PRD: 크리에이터 채널 신규 페이지 + +## 1. Overview +크리에이터의 대표 이미지, 팔로우/알림 상태, 대화/DM 진입 버튼, 탭 바, 홈 탭 컨텐츠를 포함한 크리에이터 채널 신규 페이지의 1차 범위를 Figma `296:14890` 기준으로 구현한다. + +--- + +## 2. Problem +- 크리에이터 상세 채널을 표시할 신규 페이지가 필요하다. +- 크리에이터 채널 상단은 OS status bar 영역까지 이미지가 확장되어야 하며, 기존 일반 화면의 title bar/inset 처리와 다를 수 있다. +- 팔로우 상태와 알림 수신 상태에 따라 title bar 우측 액션 구성이 달라져야 한다. +- `대화하기`, `DM 보내기` 버튼은 권한 또는 서버 상태에 따라 없을 수 있어, 버튼 노출 조합별 배치 규칙이 필요하다. +- 스크롤 시 tab-bar가 title-bar 아래에 고정되고, title-bar 배경색이 black으로 전환되는 상단 collapsing 동작이 필요하다. +- 이번 범위는 크리에이터 채널의 전체 상단 구성과 `홈` 탭에 해당하는 컨텐츠만 포함하며, 나머지 탭의 상세 화면은 후속 범위로 분리해야 한다. +- 홈 탭 진입 즉시 사용할 API와 서버 DTO가 확정되어 Android 구현 요구사항에 반영해야 한다. + +--- + +## 3. Goals +- Figma `296:14890`의 크리에이터 채널 신규 페이지 상단 구조를 정의한다. +- 크리에이터 이미지 영역은 화면 최상단부터 OS status bar 영역까지 표시한다. +- title bar에는 좌측 뒤로가기와 우측 더보기/팔로우/알림 상태 액션을 표시한다. +- 팔로우하지 않은 상태, 팔로우 중이면서 알림 받기 중인 상태, 팔로우 중이지만 알림 받기 중이 아닌 상태를 구분해 아이콘을 표시한다. +- 크리에이터 이미지 영역 내 `대화하기`, `DM 보내기` 버튼 노출 조합과 정렬 규칙을 정의한다. +- tab-bar는 스크롤 중 title-bar 영역까지만 이동한 뒤 고정되며, 이후 아래 컨텐츠만 스크롤되도록 한다. +- tab-bar와 title-bar가 가까워지는 스크롤 구간에서 title-bar 배경색을 black으로 변경한다. +- tab-bar에는 `홈`, `라이브`, `오디오`, `시리즈`, `커뮤니티`, `팬Talk`, `후원`을 표시한다. +- 크리에이터 채널 진입 시 `GET /api/v2/creator-channels/{creatorId}/home`을 호출해 상단 구성과 홈 탭 컨텐츠에 필요한 데이터를 표시한다. +- 서버 DTO의 `CreatorActivityType`과 기존 홈 추천 API의 `RecommendedActivityType` 내용이 동일하므로, 홈 추천 전용 타입명을 공용 `CreatorActivityType`으로 변경하고 공용 패키지로 이동하는 요구사항을 정의한다. + +--- + +## 4. Non-Goals +- `라이브`, `오디오`, `시리즈`, `커뮤니티`, `팬Talk`, `후원` 탭을 선택했을 때의 탭별 상세 페이지와 pagination은 이번 범위에서 제외한다. +- `Frame 1707482896`과 그 하위 레이어 중 `홈` 탭 첫 화면 구성에 필요한 데이터 표시 외 세부 상호작용은 이번 범위에서 제외한다. +- 팔로우/언팔로우, 알림 켜기/끄기 API는 신규 API를 만들지 않고 기존 크리에이터 팔로우 API를 사용한다. +- `대화하기`는 기존 AI 채팅 `ChatRoomActivity`로 이동하고, `DM 보내기`는 신규 `DmChatRoomActivity`로 이동한다. 각 화면 내부 동작 변경은 이번 범위에서 제외한다. +- 기존 크리에이터 랭킹 페이지, 채팅 탭, DM 채팅방 상세 화면의 동작을 변경하지 않는다. +- Figma에 표시된 iOS status bar 컴포넌트 자체를 Android에 그대로 구현하지 않는다. Android system bar/inset 정책에 맞춰 이미지 확장 요구만 반영한다. + +--- + +## 5. Target Users +- 크리에이터 채널에서 크리에이터 정보와 컨텐츠 탭을 확인하려는 앱 사용자. +- 크리에이터를 팔로우하거나 알림을 설정하려는 앱 사용자. +- 크리에이터와 대화 또는 DM을 시작하려는 앱 사용자. +- `kr.co.vividnext.sodalive.v2` 하위 신규 페이지를 구현/유지보수하는 Android 개발자. + +--- + +## 6. User Stories +- 사용자는 크리에이터 채널에 진입했을 때 대표 이미지와 크리에이터 정보를 먼저 보고 싶다. +- 사용자는 상단에서 이전 화면으로 돌아가거나 더보기 메뉴를 열 수 있어야 한다. +- 사용자는 팔로우하지 않은 크리에이터를 바로 팔로우할 수 있어야 한다. +- 사용자는 팔로우 중인 크리에이터의 알림 수신 상태를 상단에서 확인하고 변경할 수 있어야 한다. +- 사용자는 가능한 경우 크리에이터와 대화하거나 DM을 보낼 수 있어야 한다. +- 사용자는 페이지를 스크롤해도 현재 선택 가능한 tab-bar가 상단에 고정되어 계속 접근 가능하길 기대한다. + +--- + +## 7. Core Features + +### Creator Channel Page Structure +크리에이터 채널 신규 페이지는 상단 이미지 영역, title bar, tab-bar, tab 하단 컨텐츠 영역으로 구성한다. + +#### Requirements +- 전체 페이지 기준 Figma 노드는 `296:14890`이다. +- 크리에이터 이미지 영역은 Figma `296:14892`의 `banner-creater`를 기준으로 한다. +- title bar는 Figma `296:14893`의 `title-bar`를 기준으로 한다. +- tab-bar는 Figma `296:14894`의 `tab-bar`를 기준으로 한다. +- 크리에이터 이미지 영역은 OS status bar 영역까지 확장되어 표시되어야 한다. +- title bar는 이미지 위에 overlay되는 형태로 배치한다. +- tab-bar는 크리에이터 이미지 영역 하단에 배치한다. +- tab-bar 하단 컨텐츠 영역 중 이번 구현 범위는 `홈` 탭 컨텐츠로 한정한다. +- 크리에이터 채널 신규 페이지 진입 시 `creatorId`를 기준으로 홈 API를 즉시 호출한다. +- 홈 API 응답의 `creator` 정보로 상단 이미지 영역, 팔로우/알림 상태, 대화/DM 버튼 노출 여부를 표시한다. +- 홈 API 응답의 홈 탭 데이터로 현재 라이브, 최신 오디오, 후원, 공지, 일정, 오디오 컨텐츠, 시리즈, 커뮤니티, 팬Talk 요약, 소개, 활동, SNS 영역을 표시한다. + +#### Edge Cases +- 크리에이터 이미지 URL이 비어 있거나 로딩 실패하면 기존 이미지 로딩/placeholder 정책을 따른다. +- status bar 높이가 기기별로 달라도 이미지가 화면 최상단까지 자연스럽게 확장되어야 한다. +- display cutout, gesture navigation, status bar inset 환경에서 title bar의 터치 영역이 status bar와 겹쳐 조작 불가능해지면 안 된다. + +### Creator Channel Home API +크리에이터 채널 신규 페이지 진입 시 홈 탭과 상단 구성에 필요한 데이터를 조회한다. + +#### API Endpoint +- `GET /api/v2/creator-channels/{creatorId}/home` + +#### Response Contract +```kotlin +data class CreatorChannelHomeResponse( + val creator: CreatorChannelCreatorResponse, + val currentLive: CreatorChannelLiveResponse?, + val latestAudioContent: CreatorChannelAudioContentResponse?, + val channelDonations: List, + val notices: List, + val schedules: List, + val audioContents: List, + val series: List, + val communities: List, + val fanTalk: CreatorChannelFanTalkSummaryResponse, + val introduce: String, + val activity: CreatorChannelActivityResponse, + val sns: CreatorChannelSnsResponse +) + +data class CreatorChannelCreatorResponse( + val creatorId: Long, + val nickname: String, + val profileImageUrl: String, + val followerCount: Int, + val isAiChatAvailable: Boolean, + val isDmAvailable: Boolean, + val isFollow: Boolean, + val isNotify: Boolean +) + +data class CreatorChannelLiveResponse( + val liveId: Long, + val title: String, + val coverImageUrl: String?, + val beginDateTimeUtc: String, + val price: Int, + val isAdult: Boolean +) + +data class CreatorChannelAudioContentResponse( + val audioContentId: Long, + val title: String, + val duration: String?, + val imageUrl: String?, + val price: Int, + val isPointAvailable: Boolean, + val isFirstContent: Boolean, + val seriesName: String?, + val isOriginalSeries: Boolean? +) + +data class CreatorChannelDonationResponse( + val donationId: Long, + val memberId: Long, + val nickname: String, + val profileImageUrl: String, + val can: Int, + val isSecret: Boolean, + val message: String, + val createdAtUtc: String +) + +data class CreatorChannelScheduleResponse( + val scheduledAtUtc: String, + val title: String, + val type: CreatorActivityType, + val targetId: Long +) + +data class CreatorChannelSeriesResponse( + val seriesId: Long, + val title: String, + val coverImageUrl: String, + val publishedDaysOfWeek: String, + val isComplete: Boolean, + val numberOfContent: Int, + val isNew: Boolean, + val isPopular: Boolean, + val isOriginal: Boolean +) + +data class CreatorChannelCommunityPostResponse( + val postId: Long, + val creatorId: Long, + val creatorNickname: String, + val creatorProfileUrl: String, + val imageUrl: String?, + val audioUrl: String?, + val content: String, + val price: Int, + val dateUtc: String, + val existOrdered: Boolean, + val likeCount: Int, + val commentCount: Int +) + +data class CreatorChannelFanTalkSummaryResponse( + val totalCount: Int, + val latestFanTalk: CreatorChannelFanTalkResponse? +) + +data class CreatorChannelFanTalkResponse( + val fanTalkId: Long, + val memberId: Long, + val nickname: String, + val profileImageUrl: String, + val content: String, + val languageCode: String?, + val createdAtUtc: String +) + +data class CreatorChannelActivityResponse( + val debutDateUtc: String?, + val dDay: String, + val liveCount: Long, + val liveDurationHours: Long, + val liveContributorCount: Long, + val audioContentCount: Long, + val seriesCount: Long +) + +data class CreatorChannelSnsResponse( + val instagramUrl: String, + val fancimmUrl: String, + val xUrl: String, + val youtubeUrl: String, + val kakaoOpenChatUrl: String +) +``` + +#### Requirements +- `creatorId`는 페이지 진입 인자로 전달받고, `creatorId > 0`일 때만 홈 API를 호출한다. +- `creator.nickname`, `creator.profileImageUrl`, `creator.followerCount`를 크리에이터 이미지 영역의 기본 정보로 사용한다. +- `creator.isFollow`, `creator.isNotify`를 title bar 팔로우/알림 상태 표시의 기준으로 사용한다. +- `creator.isAiChatAvailable`이 `true`일 때만 `대화하기` 버튼을 표시한다. +- `creator.isDmAvailable`이 `true`일 때만 `DM 보내기` 버튼을 표시한다. +- 홈 탭 영역은 `CreatorChannelHomeResponse`의 top-level 필드를 사용해 Figma 홈 구성에 맞춰 표시한다. +- nullable 필드인 `currentLive`, `latestAudioContent`, `coverImageUrl`, `imageUrl`, `audioUrl`, `latestFanTalk`, `debutDateUtc`, `languageCode`, `duration`, `seriesName`, `isOriginalSeries`는 null 가능성을 전제로 UI 표시 여부를 결정한다. + +#### Edge Cases +- 홈 API 실패 시 기존 네트워크 오류 처리 정책에 맞춰 crash 없이 처리한다. +- list 필드가 빈 배열이면 해당 홈 섹션은 기존 Figma/앱 empty 정책에 맞춰 숨기거나 비어 있는 상태로 표시한다. +- `creatorId <= 0`이면 API를 호출하지 않고 기존 잘못된 진입 처리 정책을 따른다. + +### Title Bar +상단 title bar는 뒤로가기, 팔로우/알림 상태 액션, 더보기 액션을 제공한다. + +#### Requirements +- 좌측 상단 뒤로가기 아이콘은 `ic_new_bar_back`을 사용한다. +- 우측 상단 더보기 아이콘은 `ic_new_more`를 사용한다. +- 팔로우 상태가 아닐 때는 `ic_new_follow`를 표시한다. +- 팔로우 중이고 알림 받기 중일 때는 `ic_new_following`, `ic_bar_bell_colored`를 표시한다. +- 팔로우 중이고 알림 받기 중이 아닐 때는 `ic_new_following`, `ic_bar_bell`을 표시한다. +- title bar 액션은 Figma 상태별 노드를 기준으로 시각 배치를 맞춘다. +- 뒤로가기 터치 시 이전 화면으로 이동한다. +- 더보기 터치 시 기존 앱의 크리에이터/프로필 더보기 메뉴 패턴을 따른다. +- 팔로우/알림 상태 변경은 기존에 사용하던 크리에이터 팔로우 API를 재사용한다. +- 팔로우/알림 상태 변경 성공 후 title bar의 `isFollow`, `isNotify` 표시 상태를 갱신한다. + +#### State References +| State | Figma Node | Icons | +| --- | --- | --- | +| 팔로우 상태가 아님 | `296:14287` | `ic_new_follow` | +| 팔로우 중 + 알림 받기 중 | `296:14288` | `ic_new_following`, `ic_bar_bell_colored` | +| 팔로우 중 + 알림 받기 중이 아님 | `296:14289` | `ic_new_following`, `ic_bar_bell` | + +#### Edge Cases +- 팔로우 상태 데이터가 아직 로딩 중이면 잘못된 상태 전환이 보이지 않도록 기존 로딩 정책에 맞춰 처리한다. +- 팔로우/알림 상태 변경 요청 중 중복 탭으로 동일 요청이 여러 번 전송되지 않도록 한다. +- title bar 배경이 투명한 상태에서도 아이콘이 이미지 위에서 식별 가능해야 한다. + +### Creator Image Area Actions +크리에이터 이미지 영역에는 `대화하기`, `DM 보내기` 액션 버튼을 표시할 수 있다. + +#### Requirements +- `대화하기` 아이콘은 `ic_new_talk`를 사용한다. +- `DM 보내기` 아이콘은 `ic_new_dm`을 사용한다. +- `대화하기`, `DM 보내기` 버튼은 둘 다 없을 수 있다. +- 두 버튼이 모두 보일 때는 Figma `296:14892`의 버튼 영역 배치를 따른다. +- 두 버튼 중 하나만 보일 때는 해당 버튼을 가운데 표시한다. +- 두 버튼이 모두 보이지 않더라도 버튼 영역의 높이와 여백은 보존한다. +- `대화하기` 버튼은 `creator.isAiChatAvailable=true`일 때 표시한다. +- `DM 보내기` 버튼은 `creator.isDmAvailable=true`일 때 표시한다. +- `대화하기` 버튼 터치 시 기존 AI 채팅 `ChatRoomActivity`로 이동한다. +- `DM 보내기` 버튼 터치 시 신규 `DmChatRoomActivity`로 이동한다. + +#### Edge Cases +- 버튼 문구가 현지화 또는 서버 문구로 길어져도 버튼 내부 텍스트가 잘리거나 겹치면 안 된다. +- 하나의 버튼만 표시될 때 좌우 여백 불균형이 보이지 않도록 중앙 정렬한다. +- 두 버튼이 모두 숨김이어도 크리에이터 이미지 영역의 하단 정보와 tab-bar 위치가 갑자기 위로 당겨지면 안 된다. + +### Tab Bar And Scroll Behavior +tab-bar는 스크롤 중 title-bar 하단에 고정되는 sticky 영역으로 동작한다. + +#### Requirements +- tab-bar 목록은 `홈`, `라이브`, `오디오`, `시리즈`, `커뮤니티`, `팬Talk`, `후원` 순서로 표시한다. +- 이번 범위에서 기본 선택 탭은 `홈`이다. +- 이번 범위에서는 `홈` 탭 컨텐츠만 구현한다. +- `라이브`, `오디오`, `시리즈`, `커뮤니티`, `팬Talk`, `후원` 탭의 상세 컨텐츠 구현은 후속 범위로 둔다. +- tab-bar는 탭 개수가 7개인 상태에서 가로 스크롤 또는 동적 item 폭 등 기존 tab 컴포넌트 정책에 맞춰 표시되어야 한다. +- 사용자가 페이지를 위로 스크롤하면 크리에이터 이미지 영역은 스크롤되어 사라질 수 있다. +- tab-bar는 완전히 화면 밖으로 사라지지 않고 title-bar 영역까지만 이동한 뒤 고정된다. +- tab-bar가 고정된 이후에는 tab-bar 아래 컨텐츠만 세로 스크롤된다. +- 스크롤 시 title-bar와 tab-bar의 실제 거리, 그리고 프로필 이미지가 절반 이상 사라졌는지를 함께 기준으로 title bar의 배경색을 black으로 변경한다. +- 스크롤이 다시 상단으로 돌아오면 title-bar 배경은 Figma 초기 상태에 맞게 이미지 overlay 상태로 복귀한다. + +#### Edge Cases +- 빠른 fling 중에도 tab-bar 고정 위치가 흔들리거나 title-bar와 겹치면 안 된다. +- tab-bar 고정 상태에서 탭을 변경해도 스크롤/고정 상태가 깨지면 안 된다. +- 7개 탭이 가로 폭을 초과해도 탭 선택과 스크롤 동작이 유지되어야 한다. +- status bar 높이와 title bar 높이를 고려해 sticky 기준 위치를 계산해야 한다. + +### Home Tab Content +`홈` 탭은 크리에이터 채널 홈 API 응답을 사용해 Figma 전체 페이지의 홈 컨텐츠를 표시한다. + +#### Requirements +- `currentLive`가 있으면 현재 라이브 영역에 표시한다. +- `latestAudioContent`가 있으면 최신 오디오 컨텐츠 영역에 표시한다. +- `channelDonations`는 후원 요약/목록 영역에 표시한다. +- `notices`는 공지 영역에 표시한다. +- `schedules`는 크리에이터 일정 영역에 표시한다. +- `audioContents`는 오디오 컨텐츠 섹션에 표시한다. +- `series`는 시리즈 섹션에 표시한다. +- `communities`는 커뮤니티 게시글 섹션에 표시한다. +- `fanTalk`는 팬Talk 요약 영역에 표시한다. +- `introduce`는 소개 영역에 표시한다. +- `activity`는 활동 정보 영역에 표시한다. +- `sns`는 SNS 링크 영역에 표시한다. +- 홈 탭의 각 섹션은 Figma `296:14890`의 홈 화면 순서와 기존 위젯/목록 패턴을 우선 따른다. + +#### Edge Cases +- `currentLive` 또는 `latestAudioContent`가 null이면 해당 단일 컨텐츠 영역은 표시하지 않거나 기존 empty 정책을 따른다. +- list 필드가 빈 배열이면 섹션 자체를 숨길지 빈 상태를 표시할지는 기존 홈/크리에이터 화면 정책을 우선 따른다. +- SNS URL 문자열이 빈 값이면 해당 SNS 진입 아이콘 또는 버튼은 비활성/숨김 처리한다. + +### CreatorActivityType Commonization +홈 추천 API에서 사용 중인 `RecommendedActivityType`은 크리에이터 채널 홈 API의 `CreatorActivityType`과 동일한 의미를 가지므로 공용 타입으로 정리한다. + +#### Requirements +- 기존 `RecommendedActivityType`의 enum/value 내용은 서버의 `CreatorActivityType`과 동일하게 유지한다. +- `RecommendedActivityType`이 홈 추천 전용으로만 쓰이는 이름이 아니므로 `CreatorActivityType`으로 변경한다. +- 변경한 `CreatorActivityType`은 홈 추천 패키지가 아닌 공용 패키지로 이동한다. +- 기존 홈 추천 API DTO와 신규 크리에이터 채널 홈 API DTO는 동일한 `CreatorActivityType`을 참조한다. +- 타입명 변경으로 기존 홈 추천 화면의 동작이 달라지면 안 된다. + +--- + +## 8. UX / UI Expectations +- 전체 배경은 Figma `296:14890`과 동일한 black 계열을 유지한다. +- 크리에이터 대표 이미지는 첫 화면에서 가장 큰 시각 요소로 노출한다. +- title bar 초기 상태는 이미지 위 overlay로 보이며, 스크롤 임계점 이후 black 배경으로 전환된다. +- title bar 아이콘은 Figma에 지정된 신규 아이콘 리소스를 사용한다. +- 팔로우/알림 상태별 아이콘 조합은 사용자가 현재 상태를 즉시 구분할 수 있어야 한다. +- `대화하기`, `DM 보내기` 버튼은 노출 여부와 무관하게 이미지 영역 내 레이아웃 안정성을 유지한다. +- tab-bar는 화면 상단에 고정된 뒤에도 title bar와 시각적으로 충돌하지 않아야 한다. +- tab-bar는 `홈`, `라이브`, `오디오`, `시리즈`, `커뮤니티`, `팬Talk`, `후원` 7개 탭을 자연스럽게 표시해야 한다. +- `홈` 탭은 크리에이터 채널 진입 직후 바로 표시되는 기본 화면이다. +- Android 기기별 status bar/inset 차이로 인해 상단 이미지, title bar, tab-bar의 수직 위치가 어긋나면 안 된다. + +--- + +## 9. Technical Constraints +- Android XML Views, ViewBinding, RecyclerView/NestedScroll 계열의 기존 프로젝트 구조를 우선 확인해 따른다. +- 신규 `Activity`, `Fragment`, `ViewModel` 및 연결 하위 코드는 `kr.co.vividnext.sodalive.v2` 패키지 하위에 작성한다. +- 구현 전 `docs/20260611_크리에이터_채널_홈_탭/plan-task.md`를 작성한 뒤 해당 계획에 따라 최소 구현한다. +- 기존에 존재하는 icon drawable 리소스가 있으면 재사용하고, 없는 경우 제공된 리소스명으로 추가한다. +- system bar 영역 확장은 Android edge-to-edge/inset 처리와 기존 앱의 status bar 정책을 확인한 뒤 적용한다. +- tab-bar sticky 동작은 기존 화면의 app bar/collapsing/header scroll 구현 패턴이 있으면 우선 재사용한다. +- 홈 API는 `GET /api/v2/creator-channels/{creatorId}/home` 계약을 따른다. +- 팔로우/알림 변경은 기존 크리에이터 팔로우 API 사용 패턴을 재사용한다. +- `대화하기` 이동은 기존 AI 채팅 `ChatRoomActivity` 사용 패턴을 따른다. +- `DM 보내기` 이동은 신규 `DmChatRoomActivity` 사용 패턴을 따른다. +- `RecommendedActivityType`을 `CreatorActivityType`으로 변경하고 공용 패키지로 이동할 때, 홈 추천 API의 기존 참조도 함께 갱신한다. +- navigation extra 이름, analytics/logging은 근거 파일이나 별도 요구가 확인되기 전까지 임의로 추가하지 않는다. + +--- + +## 10. Metrics +- 크리에이터 이미지 영역이 OS status bar 영역까지 표시된다. +- title bar 좌측에는 `ic_new_bar_back`, 우측에는 `ic_new_more`가 표시된다. +- 팔로우하지 않은 상태에서 `ic_new_follow`가 표시된다. +- 팔로우 중 + 알림 받기 중 상태에서 `ic_new_following`, `ic_bar_bell_colored`가 표시된다. +- 팔로우 중 + 알림 받기 중이 아닌 상태에서 `ic_new_following`, `ic_bar_bell`이 표시된다. +- `대화하기`, `DM 보내기` 버튼이 모두 보일 때 Figma 기준 배치를 따른다. +- `대화하기`, `DM 보내기` 버튼 중 하나만 보일 때 해당 버튼이 가운데 표시된다. +- `대화하기`, `DM 보내기` 버튼이 모두 보이지 않아도 버튼 영역 높이와 여백이 보존된다. +- `creator.isAiChatAvailable=true`일 때만 `대화하기` 버튼이 표시된다. +- `creator.isDmAvailable=true`일 때만 `DM 보내기` 버튼이 표시된다. +- `대화하기` 버튼 터치 시 `ChatRoomActivity`로 이동한다. +- `DM 보내기` 버튼 터치 시 `DmChatRoomActivity`로 이동한다. +- tab-bar에 `홈`, `라이브`, `오디오`, `시리즈`, `커뮤니티`, `팬Talk`, `후원`이 표시된다. +- 크리에이터 채널 진입 시 `GET /api/v2/creator-channels/{creatorId}/home`이 호출된다. +- 홈 API 응답의 `creator.isFollow`, `creator.isNotify`가 title bar 상태 표시 기준으로 사용된다. +- 홈 API 응답의 `currentLive`, `latestAudioContent`, `channelDonations`, `notices`, `schedules`, `audioContents`, `series`, `communities`, `fanTalk`, `introduce`, `activity`, `sns`가 홈 탭 표시 데이터로 사용된다. +- 스크롤 시 tab-bar가 title-bar 영역까지 이동한 뒤 고정된다. +- tab-bar 고정 이후 하단 컨텐츠만 스크롤된다. +- title-bar와 tab-bar의 실제 거리 및 프로필 이미지가 절반 이상 사라진 상태를 기준으로 title-bar 배경색이 black으로 변경된다. +- `RecommendedActivityType`은 공용 `CreatorActivityType`으로 변경되고, 홈 추천 API와 크리에이터 채널 홈 API가 같은 타입을 참조한다. +- `라이브`, `오디오`, `시리즈`, `커뮤니티`, `팬Talk`, `후원` 탭 상세 화면은 이번 구현 범위로 사용되지 않는다. + +--- + +## 11. Open Questions +- 크리에이터 채널 신규 페이지의 진입점과 전달받을 Android extra 또는 navigation argument 이름은 구현 계획에서 기존 이동 패턴을 확인해 확정한다. +- 홈 탭 각 섹션의 empty 상태를 섹션 숨김으로 처리할지, 빈 상태 UI로 처리할지는 기존 크리에이터/홈 화면 패턴 확인이 필요하다. +- 팔로우/알림 변경 성공 후 홈 API를 재조회할지, 로컬 상태만 갱신할지는 기존 크리에이터 팔로우 API 사용 패턴 확인이 필요하다. + +--- + +## 12. References +- 전체 페이지 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=296-14890&m=dev +- 팔로우 상태가 아닐 때 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=296-14287&m=dev +- 팔로우 중 + 알림 받기 중 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=296-14288&m=dev +- 팔로우 중 + 알림 받기 중이 아닐 때 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=296-14289&m=dev +- 크리에이터 이미지 영역 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=296-14892&m=dev +- 사용자 제공 홈 API Endpoint: `GET /api/v2/creator-channels/{creatorId}/home` +- PRD 샘플: `docs/prd/sample-prd.md` +- 문서 작성 규칙: `docs/agent-guides/work-plan-docs.md` + +--- + +## 13. Verification Log +- 2026-06-11: `AGENTS.md`, `docs/agent-guides/work-plan-docs.md`, `docs/prd/sample-prd.md`를 확인해 신규 문서 경로와 PRD 구성 규칙을 확인했다. +- 2026-06-11: Figma `296:14890` metadata와 screenshot을 확인해 `banner-creater`, `title-bar`, `tab-bar`, `StatusBar / iPhone 13`, 제외 대상 `Frame 1707482896` 구조를 확인했다. +- 2026-06-11: 사용자 제공 요구사항에 따라 title bar 아이콘, 팔로우/알림 상태별 아이콘, 크리에이터 이미지 영역 버튼 노출 규칙, status bar 영역 이미지 표시, sticky tab-bar 및 title-bar black 전환 요구사항을 PRD에 반영했다. +- 2026-06-11: 이번 단계는 PRD 작성만 수행했으며 구현/빌드/테스트는 실행하지 않았다. +- 2026-06-12: 사용자 추가 제공 요구사항에 따라 이번 범위를 크리에이터 채널 전체 구성과 `홈` 탭으로 한정하고, tab-bar 7개 항목, 홈 API Endpoint, 서버 DTO, 기존 크리에이터 팔로우 API 재사용, `대화하기`/`DM 보내기` 이동 대상, `CreatorActivityType` 공용화, title bar black 전환 기준을 PRD에 반영했다. +- 2026-06-12: 이번 단계는 PRD 문서 보완만 수행했으며 구현/빌드/테스트는 실행하지 않았다. +- 2026-06-12: 문서 범위가 크리에이터 채널 신규 페이지 전체가 아니라 홈 탭 1차 범위임을 명확히 하기 위해 문서 폴더명을 `docs/20260611_크리에이터_채널_신규_페이지/`에서 `docs/20260611_크리에이터_채널_홈_탭/`으로 변경했다.