545 lines
47 KiB
Markdown
545 lines
47 KiB
Markdown
# 크리에이터 채널 라이브 탭 구현 계획/TASK
|
|
|
|
> **For agentic workers:** 각 단계는 체크박스(`- [ ]`)로 추적하고, 완료 즉시 `- [x]`로 갱신한다. 구현 범위 변경이 생기면 이 문서를 먼저 수정한 뒤 코드에 반영한다.
|
|
|
|
**Goal:** `GET /api/v2/creator-channels/{creatorId}/live` 응답을 기반으로 크리에이터 채널의 `라이브` 탭에 현재 라이브, 라이브 다시듣기 목록, 정렬, pagination, 본인 채널 전용 하단 `라이브 시작하기` CTA를 표시한다.
|
|
|
|
**Architecture:** 기존 `CreatorChannelActivity`의 `ViewPager2`/`CreatorChannelPagerAdapter` 구조를 유지하고, `CreatorChannelTab.Live`의 placeholder를 신규 `CreatorChannelLiveFragment`로 교체한다. 라이브 탭 전용 Fragment/ViewModel/mapper/UI model/adapter/popup은 `kr.co.vividnext.sodalive.v2.creator.channel.live` 하위에 둔다. API/Repository는 홈 탭과 라이브 탭이 함께 쓰는 채널 공통 계층으로 보고 기존 `CreatorChannelHomeApi`/`CreatorChannelHomeRepository`를 `CreatorChannelApi`/`CreatorChannelRepository`로 rename한다. 홈 탭에서 이미 쓰는 `CreatorChannelLiveResponse`, `CreatorChannelAudioContentResponse`, 본인 판정, 라이브 시작 진입 흐름은 가능한 한 재사용한다.
|
|
|
|
**Tech Stack:** Kotlin, Android XML Views, ViewBinding, RecyclerView, Retrofit, Gson, RxJava3, Koin, JUnit4/Robolectric local unit test.
|
|
|
|
---
|
|
|
|
## 전제와 성공 기준
|
|
- PRD: `docs/20260617_크리에이터_채널_라이브_탭/prd.md`
|
|
- 기존 채널 컨테이너: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt`
|
|
- 기존 탭 adapter: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapter.kt`
|
|
- 기존 채널 API/DTO/Repository:
|
|
- rename 대상: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelHomeApi.kt` -> `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelApi.kt`
|
|
- rename 대상: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelHomeRepository.kt` -> `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelRepository.kt`
|
|
- 유지/확장: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelHomeModels.kt`
|
|
- 별도 `CreatorChannelLiveApi`, `CreatorChannelLiveRepository`는 생성하지 않는다.
|
|
- Figma:
|
|
- 전체: `290:8945`
|
|
- 전체 empty: `290:8959`
|
|
- Sort-bar: `290:8949`
|
|
- 현재 진행 중인 라이브: `290:8950`
|
|
- 라이브 다시듣기 item: `290:8954`, `290:8956`
|
|
- 정렬 컨텍스트 메뉴: `290:9041`
|
|
- 본인 채널 하단 CTA: `665:19359`, `665:19371`
|
|
- 첫 페이지 `page`는 `0`이다.
|
|
- 다음 페이지는 마지막 성공 응답의 `page + 1`로 요청한다.
|
|
- `ContentSort` 기본값은 `LATEST`이다.
|
|
- `isOwned == true`와 `isRented == true`가 동시에 내려오면 `소장중`을 우선 표시한다.
|
|
- `seriesName`은 라이브 다시듣기 item에 표시하지 않는다.
|
|
- `isFirstContent`, `isOriginalSeries`는 기존 오디오 item 정책과 동일하게 매핑한다.
|
|
- `currentLive == null`이고 `liveReplayContents.isEmpty()`인 전체 empty 상태에서는 Sort-bar, 현재 라이브 카드, 라이브 다시듣기 리스트를 제거하고 중앙에 `크리에이터가 라이브를 준비 중입니다.\n기대해 주세요!` 문구를 표시한다.
|
|
- 전체 empty 상태가 본인 채널에 표시되는 경우에도 empty 문구는 동일하게 표시하고, 하단 `라이브 시작하기` CTA는 본인 채널 정책에 따라 계속 표시한다.
|
|
- 같은 `creatorId`로 `loadLive()`가 재호출되면 Fragment/View 재생성 또는 탭 재바인딩으로 보고 기존 ViewModel 상태를 유지한다. 명시적 새로고침은 후속 필요 시 별도 refresh API로 분리한다.
|
|
- 최초 조회 실패로 `Error` 상태인 경우 `retryLive()`를 통해 현재 `creatorId`의 첫 페이지를 다시 요청한다.
|
|
- 전체 error 상태에서는 안내 문구 아래 retry 버튼을 표시하고, retry 버튼은 `CreatorChannelLiveViewModel.retryLive()`를 호출한다.
|
|
- drawable 리소스는 기존 파일을 재사용한다.
|
|
- `ic_new_sort`
|
|
- `ic_new_shield_small`
|
|
- `ic_new_player_play`
|
|
- `ic_new_create_live`
|
|
- 구현 완료 후 최소 다음 명령을 실행한다.
|
|
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Live*"`
|
|
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.*"`
|
|
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*"`
|
|
- `./gradlew :app:mergeDebugResources`
|
|
- `./gradlew :app:compileDebugKotlin`
|
|
- `./gradlew :app:ktlintCheck`
|
|
|
|
---
|
|
|
|
## Figma 참조 필요 Phase
|
|
- Phase 1: 부분 참조
|
|
- 기존 코드 경계와 리소스 존재 여부 확인이 중심이며, Figma는 PRD 기준만 확인한다.
|
|
- Phase 2: Figma 참조 불필요
|
|
- API/DTO/Repository/ViewModel 상태 모델은 서버 계약과 기존 코드 패턴을 따른다.
|
|
- Phase 3: 부분 참조
|
|
- mapper와 presenter 정책은 PRD와 Figma item variant의 상태 표시를 함께 확인한다.
|
|
- Phase 4: 필수 참조
|
|
- `fragment_creator_channel_live.xml`, sort-bar, current live card, replay item layout, 전체 empty 상태는 Figma `290:8949`, `290:8950`, `290:8954`, `290:8956`, `290:8959`를 기준으로 구현한다.
|
|
- Phase 5: 필수 참조
|
|
- 정렬 컨텍스트 메뉴는 Figma `290:9041` 기준으로 구현한다.
|
|
- Phase 6: 필수 참조
|
|
- 본인 채널 하단 CTA는 Figma `665:19359`, `665:19371` 기준으로 구현한다.
|
|
- Phase 7: 부분 참조
|
|
- 탭 연결, 화면 이동, pagination 동작은 기존 코드 패턴 중심으로 검증한다.
|
|
- Phase 8: 필수 참조
|
|
- 최종 수동 화면 검증은 PRD의 모든 Figma 노드와 실제 화면을 대조한다.
|
|
|
|
---
|
|
|
|
## 파일 구조
|
|
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapter.kt`
|
|
- `CreatorChannelTab.Live`를 신규 `CreatorChannelLiveFragment`로 연결한다.
|
|
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveFragment.kt`
|
|
- 라이브 탭 UI, adapter, sort menu, pagination, CTA click 연결을 담당한다.
|
|
- 생성 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveViewModel.kt`
|
|
- 라이브 탭 API 호출, 정렬 변경, pagination 상태, loading/error/content 상태를 관리한다.
|
|
- rename: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelHomeApi.kt` -> `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelApi.kt`
|
|
- 홈 API와 라이브 API endpoint를 함께 가진 채널 공통 API로 변경한다.
|
|
- rename: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelHomeRepository.kt` -> `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelRepository.kt`
|
|
- 홈 API, 라이브 API, 팔로우, 대화, 후원, 신고 등 채널 공통 동작을 제공한다.
|
|
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelHomeModels.kt`
|
|
- `CreatorChannelAudioContentResponse`에 라이브 탭 전용 필드 `isAdult`, `isOwned`, `isRented`를 서버 계약에 맞춰 추가한다.
|
|
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/common/data/ContentSort.kt`
|
|
- v2 API 공용 정렬 enum으로 `ContentSort`를 둔다.
|
|
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/data/CreatorChannelLiveTabResponse.kt`
|
|
- `CreatorChannelLiveTabResponse`를 크리에이터 채널 라이브 탭 응답 전용 모델로 둔다.
|
|
- 생성 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/model/CreatorChannelLiveUiModels.kt`
|
|
- sort option, replay item status, pagination 상태, CTA 노출 여부를 순수 UI model로 정의한다.
|
|
- 생성 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/model/CreatorChannelLiveMappers.kt`
|
|
- DTO를 UI model로 변환하고 소장/대여/무료/가격/포인트/19금 상태를 결정한다.
|
|
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/ui/CreatorChannelLiveReplayAdapter.kt`
|
|
- 라이브 다시듣기 목록 RecyclerView adapter를 담당한다.
|
|
- 생성 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/ui/CreatorChannelLiveSortPopup.kt`
|
|
- Sort-bar anchor 기준 컨텍스트 메뉴 표시를 담당한다.
|
|
- 생성: `app/src/main/res/layout/fragment_creator_channel_live.xml`
|
|
- sort-bar, current live card container, replay RecyclerView, owner CTA를 포함한다.
|
|
- 생성: `app/src/main/res/layout/item_creator_channel_live_replay.xml`
|
|
- 라이브 다시듣기 item layout이다.
|
|
- 생성 후보: `app/src/main/res/layout/view_creator_channel_live_sort_menu.xml`
|
|
- 정렬 컨텍스트 메뉴를 XML로 분리할 때만 추가한다.
|
|
- 수정: `app/src/main/res/values/strings.xml`
|
|
- 정렬 label, `소장중`, `대여중`, `라이브 시작하기`, empty/error 문구를 추가한다.
|
|
- 수정: `app/src/main/res/values-en/strings.xml`, `app/src/main/res/values-ja/strings.xml`
|
|
- 신규 문자열의 다국어 값을 추가한다.
|
|
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt`
|
|
- 신규 ViewModel/API/Repository binding이 필요하면 추가한다.
|
|
- 테스트 생성:
|
|
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveMapperTest.kt`
|
|
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLivePaginationTest.kt`
|
|
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveViewModelTest.kt`
|
|
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveFragmentLayoutTest.kt`
|
|
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapterTest.kt`
|
|
|
|
---
|
|
|
|
### Phase 1: 기존 구조 확인과 작업 경계 고정
|
|
|
|
- [x] **Task 1.1: 크리에이터 채널 탭 구조와 본인 판정 경로 확인**
|
|
- 확인:
|
|
- `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/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeFragment.kt`
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/model/CreatorChannelHomeMappers.kt`
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/model/CreatorChannelHomeUiModels.kt`
|
|
- 작업:
|
|
- `CreatorChannelTab.Live`가 현재 placeholder로 연결되는 지점을 확인한다.
|
|
- 본인 여부(`isOwner`)가 Activity와 탭 Fragment로 전달되는 기존 경로를 확인한다.
|
|
- 라이브 시작 진입이 기존 `CreatorChannelActivity`의 `onOwnerFabLiveClicked()` 또는 `LiveRoomCreateActivity` 경로를 재사용할 수 있는지 확인한다.
|
|
- 검증:
|
|
- 라이브 탭 구현이 홈 탭 section adapter를 변경하지 않고 독립 Fragment로 추가 가능한지 기록한다.
|
|
- 검증 기록:
|
|
- 2026-06-17: `CreatorChannelPagerAdapter`는 현재 `CreatorChannelTab.Home`만 `CreatorChannelHomeFragment.newInstance(creatorId)`로 연결하고, `CreatorChannelTab.Live`를 포함한 나머지 탭은 `CreatorChannelPlaceholderFragment.newInstance(tab)`로 처리함을 확인했다. 따라서 Phase 7에서 Live 탭만 신규 Fragment로 교체해도 홈 탭 section adapter 변경 없이 독립 추가 가능하다.
|
|
- 2026-06-17: 본인 여부는 `CreatorChannelHomeMappers.toUiContent()`에서 `creator.creatorId == currentMemberId`로 계산되어 `CreatorChannelHeaderUiModel.isOwner`와 donation section에 전달되고, `CreatorChannelActivity`는 이 값을 owner FAB/상단 액션/DM/후원 분기 조건으로 사용함을 확인했다.
|
|
- 2026-06-17: 라이브 시작 진입은 `CreatorChannelActivity.onOwnerFabLiveClicked()`가 `liveRoomCreateLauncher.launch(Intent(this, LiveRoomCreateActivity::class.java))`를 호출하는 기존 경로를 사용하며, 생성 결과는 `homeActionDelegate?.refreshHome()` 후 `Constants.EXTRA_ROOM_ID`, `Constants.EXTRA_ROOM_CHANNEL_NAME`에 따라 `CreatorChannelLiveCoordinator.enterLiveRoom()` 또는 생성 완료 toast로 처리됨을 확인했다.
|
|
|
|
- [x] **Task 1.2: 기존 오디오 item 정책과 drawable/string 리소스 확인**
|
|
- 확인:
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelHomeAudioContentCardView.kt`
|
|
- `app/src/main/res/layout/item_creator_channel_home_audio_content.xml`
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentTag.kt`
|
|
- `app/src/main/res/drawable-mdpi/ic_new_sort.png`
|
|
- `app/src/main/res/drawable-mdpi/ic_new_shield_small.png`
|
|
- `app/src/main/res/drawable-mdpi/ic_new_player_play.png`
|
|
- `app/src/main/res/drawable-mdpi/ic_new_create_live.png`
|
|
- 작업:
|
|
- `isFirstContent`, `isOriginalSeries`, point/free/original tag의 기존 표시 정책을 정리한다.
|
|
- 라이브 다시듣기 item에서 `seriesName`은 표시하지 않는다는 PRD 결정을 재확인한다.
|
|
- 검증:
|
|
- 위 4개 drawable 리소스가 존재함을 확인한다.
|
|
- 검증 기록:
|
|
- 2026-06-17: 기존 홈 오디오 item은 `CreatorChannelHomeAudioContentCardView.bind()`에서 `isOriginalSeries == true`이면 original tag, `isPointAvailable`이면 point tag, `isFirstContent`이면 first tag, `price <= 0`이면 free tag를 표시한다. secondary text는 기존 홈 카드에서 `duration`과 `seriesName`을 조합하지만, 라이브 다시듣기 item에서는 PRD/계획 기준에 따라 `seriesName`을 표시하지 않는 것으로 재확인했다.
|
|
- 2026-06-17: `AudioContentTag`의 top tag 순서는 `Original`, `First`, bottom tag 순서는 `Point`, `Free`로 정의되어 있음을 확인했다.
|
|
- 2026-06-17: `test -f`로 `app/src/main/res/drawable-mdpi/ic_new_sort.png`, `ic_new_shield_small.png`, `ic_new_player_play.png`, `ic_new_create_live.png`가 모두 존재함을 확인했다.
|
|
|
|
- [x] **Task 1.3: 채널 공통 API/Repository rename**
|
|
- rename:
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelHomeApi.kt` -> `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/CreatorChannelHomeRepository.kt` -> `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelRepository.kt`
|
|
- 수정:
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeViewModel.kt`
|
|
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeViewModelTest.kt`
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt`
|
|
- 기타 `CreatorChannelHomeApi`, `CreatorChannelHomeRepository` import 참조 파일
|
|
- 작업:
|
|
- class/interface 이름을 `CreatorChannelApi`, `CreatorChannelRepository`로 변경한다.
|
|
- 기존 홈 탭 동작은 변경하지 않는다.
|
|
- 별도 `CreatorChannelLiveApi`, `CreatorChannelLiveRepository`는 만들지 않는다.
|
|
- 검증 명령:
|
|
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelHomeViewModelTest"`
|
|
- `./gradlew :app:compileDebugKotlin`
|
|
- 기대 결과:
|
|
- rename 후 기존 홈 탭 ViewModel 테스트와 컴파일이 PASS한다.
|
|
- 검증 기록:
|
|
- 2026-06-17: rename-only 변경으로 `CreatorChannelHomeApi.kt`/`CreatorChannelHomeRepository.kt`를 각각 `CreatorChannelApi.kt`/`CreatorChannelRepository.kt`로 변경하고 interface/class/import/DI/test 참조를 갱신했다. 홈 endpoint `GET /api/v2/creator-channels/{creatorId}/home`와 기존 repository method 동작은 변경하지 않았다.
|
|
- 2026-06-17: 신규 동작 추가가 없는 rename-only 작업이므로 RED 테스트 신규 작성 대상에서 제외했다. 기존 회귀 검증은 아래 명령 실행 결과로 누적한다.
|
|
- 2026-06-17: 최초 병렬 검증 실행 중 `:app:kspDebugKotlin`이 KSP incremental cache(`app/build/kspCaches/debug`) 경합/손상으로 실패했다. 생성물 캐시만 삭제한 뒤 순차 재실행했다.
|
|
- 2026-06-17: `./gradlew :app:compileDebugKotlin` 순차 재실행 결과 `BUILD SUCCESSFUL`로 통과했다. 기존 deprecation/annotation 경고는 출력되었으나 rename 변경과 무관하다.
|
|
- 2026-06-17: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelHomeViewModelTest"` 순차 재실행 결과 `BUILD SUCCESSFUL`로 통과했다.
|
|
|
|
---
|
|
|
|
### Phase 2: API/DTO/Repository/ViewModel 계약 추가
|
|
|
|
- [x] **Task 2.1: 라이브 탭 DTO와 `ContentSort` 추가**
|
|
- 수정:
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelHomeModels.kt`
|
|
- 생성:
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/v2/common/data/ContentSort.kt`
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/data/CreatorChannelLiveTabResponse.kt`
|
|
- 작업:
|
|
- `CreatorChannelLiveTabResponse`를 라이브 탭 `data/CreatorChannelLiveTabResponse.kt`에 `@Keep`, `@SerializedName` 기반 data class로 추가한다.
|
|
- `ContentSort` enum에 `LATEST`, `POPULAR`, `OWNED`, `PRICE_HIGH`, `PRICE_LOW`를 추가하고 `v2.common.data` 패키지에 둔다.
|
|
- 기존 `CreatorChannelAudioContentResponse`에 `isAdult`, `isOwned`, `isRented` 필드를 추가할 때 홈 탭 API 하위 호환성이 깨지지 않는지 확인한다.
|
|
- 하위 호환이 필요하면 라이브 탭 전용 DTO 분리 또는 nullable/default 정책을 테스트로 고정한다.
|
|
- 검증 명령:
|
|
- `./gradlew :app:compileDebugKotlin`
|
|
- 기대 결과:
|
|
- DTO 추가 후 컴파일된다.
|
|
- 검증 기록:
|
|
- 2026-06-17: `CreatorChannelHomeModels.kt`에 `CreatorChannelLiveTabResponse`와 `ContentSort(LATEST, POPULAR, OWNED, PRICE_HIGH, PRICE_LOW)`를 추가했다. 기존 홈 탭 테스트 fixture의 positional constructor 호환을 유지하기 위해 `CreatorChannelAudioContentResponse`의 라이브 탭 전용 `isAdult`, `isOwned`, `isRented` 필드는 기본값이 있는 trailing field로 추가했다.
|
|
- 2026-06-17: `./gradlew :app:compileDebugKotlin` 실행 결과 `BUILD SUCCESSFUL`로 통과했다.
|
|
|
|
- [x] **Task 2.1.1: 라이브 탭 응답 모델 파일 경계 분리**
|
|
- 생성:
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/v2/common/data/ContentSort.kt`
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/data/CreatorChannelLiveTabResponse.kt`
|
|
- 수정:
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelHomeModels.kt`
|
|
- `docs/20260617_크리에이터_채널_라이브_탭/prd.md`
|
|
- 작업:
|
|
- `ContentSort`는 v2 API 공용 정렬 enum이므로 `v2.common.data` 패키지로 분리한다.
|
|
- `CreatorChannelLiveTabResponse`는 크리에이터 채널 라이브 탭 응답 계약이므로 `v2.creator.channel.live.data` 패키지로 분리한다.
|
|
- 홈 탭과 라이브 탭이 함께 쓰는 `CreatorChannelLiveResponse`, `CreatorChannelAudioContentResponse`는 기존 공용 DTO로 유지한다.
|
|
- 별도 `CreatorChannelLiveApi`/`CreatorChannelLiveRepository`는 만들지 않고 기존 공통 `CreatorChannelApi`/`CreatorChannelRepository`를 유지한다.
|
|
- 검증 명령:
|
|
- `rg -n "CreatorChannelLiveTabResponse|enum class ContentSort" app/src/main/java/kr/co/vividnext/sodalive/v2`
|
|
- `./gradlew :app:compileDebugKotlin`
|
|
- 기대 결과:
|
|
- `ContentSort`는 `v2/common/data/ContentSort.kt`에 정의된다.
|
|
- `CreatorChannelLiveTabResponse`는 `v2/creator/channel/live/data/CreatorChannelLiveTabResponse.kt`에 정의된다.
|
|
- 모델 파일 분리 후 Kotlin 컴파일이 PASS한다.
|
|
- 검증 기록:
|
|
- 2026-06-17: 사용자 리뷰에 따라 `CreatorChannelLiveTabResponse`와 `ContentSort`를 `CreatorChannelHomeModels.kt`에서 라이브 탭 응답 전용 파일로 이동했다. 같은 `data` 패키지의 타입 이동이라 API/Repository/ViewModel의 패키지 import 계약은 유지된다.
|
|
- 2026-06-17: `rg -n "CreatorChannelLiveTabResponse|enum class ContentSort" app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data` 실행 결과, 타입 정의가 라이브 탭 응답 전용 파일에만 있고 `CreatorChannelApi.kt`의 응답 타입 참조만 남아 있음을 확인했다.
|
|
- 2026-06-17: `./gradlew :app:compileDebugKotlin` 최초 실행은 sandbox가 `~/.gradle` lock 파일 생성을 막아 실패했다. 동일 명령을 권한 승인 후 재실행해 `BUILD SUCCESSFUL in 24s`로 통과했다.
|
|
- 2026-06-17: 사용자 추가 지시에 따라 `ContentSort`는 `v2.common.data.ContentSort`, `CreatorChannelLiveTabResponse`는 `v2.creator.channel.live.data.CreatorChannelLiveTabResponse`로 이동하도록 계획과 PRD의 파일 경계를 갱신했다.
|
|
- 2026-06-17: `rg -n "creator\\.channel\\.data\\.ContentSort|creator\\.channel\\.data\\.CreatorChannelLiveTabResponse|ContentSort|CreatorChannelLiveTabResponse" app/src/main/java app/src/test/java`로 이전 패키지 import가 남지 않았고, `ContentSort`는 `v2.common.data`, `CreatorChannelLiveTabResponse`는 `v2.creator.channel.live.data` 참조로 갱신됐음을 확인했다.
|
|
- 2026-06-17: `./gradlew :app:compileDebugKotlin` 최초 실행은 sandbox가 `~/.gradle` lock 파일 생성을 막아 실패했다. 동일 명령을 권한 승인 후 재실행해 `BUILD SUCCESSFUL in 12s`로 통과했다.
|
|
- 2026-06-17: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.*"` 실행 결과 `BUILD SUCCESSFUL in 32s`로 통과했다.
|
|
|
|
- [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}/live")` endpoint를 추가한다.
|
|
- query parameter `page`, `size`, `sort`를 전달한다.
|
|
- `sort`는 `ContentSort.name` 또는 Retrofit enum 변환의 기존 정책을 확인해 서버 값이 `LATEST` 등 대문자로 나가도록 한다.
|
|
- 검증 명령:
|
|
- `./gradlew :app:compileDebugKotlin`
|
|
- 기대 결과:
|
|
- API/Repository가 기존 Koin graph와 충돌 없이 컴파일된다.
|
|
- 검증 기록:
|
|
- 2026-06-17: `CreatorChannelApi`에 `GET /api/v2/creator-channels/{creatorId}/live` endpoint를 추가하고 `page`, `size`, `sort` query와 `Authorization` header를 전달하도록 했다. `CreatorChannelRepository.getLive()`는 별도 live repository를 만들지 않고 기존 공통 repository에서 API를 얇게 위임한다.
|
|
- 2026-06-17: `sort`는 `ContentSort` enum query로 전달해 `LATEST` 등 enum name이 Retrofit query 값으로 사용되도록 했다.
|
|
- 2026-06-17: `./gradlew :app:compileDebugKotlin` 실행 결과 `BUILD SUCCESSFUL`로 통과했다.
|
|
|
|
- [x] **Task 2.3: ViewModel RED 테스트 작성**
|
|
- 생성:
|
|
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveViewModelTest.kt`
|
|
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLivePaginationTest.kt`
|
|
- 작업:
|
|
- 최초 로딩이 `page=0`, `sort=LATEST`로 호출되는지 검증한다.
|
|
- `hasNext == true`일 때 `page + 1`로 다음 페이지를 요청하는지 검증한다.
|
|
- loading 중 중복 load-more 요청이 막히는지 검증한다.
|
|
- 정렬 변경 시 목록과 page가 초기화되는지 검증한다.
|
|
- 선택 중인 정렬을 다시 선택하면 API를 재호출하지 않는지 검증한다.
|
|
- 검증 명령:
|
|
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveViewModelTest"`
|
|
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLivePaginationTest"`
|
|
- 기대 결과:
|
|
- ViewModel 미구현 상태에서 RED 실패한다.
|
|
- 검증 기록:
|
|
- 2026-06-17: `CreatorChannelLiveViewModelTest`와 `CreatorChannelLivePaginationTest`를 추가했다. 최초 로딩 `page=0`/`sort=LATEST`, `hasNext` 기반 `page + 1` pagination, loading 중 중복 load-more 차단, 정렬 변경 시 page/list 초기화, 같은 정렬 재선택 no-op, 실패/empty 상태를 검증한다.
|
|
- 2026-06-17: production 구현 전 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveViewModelTest"` 실행 결과 `ContentSort`, `CreatorChannelLiveTabResponse`, `CreatorChannelLiveViewModel`, `CreatorChannelRepository.getLive` 미구현으로 `:app:compileDebugUnitTestKotlin FAILED`가 발생해 RED를 확인했다.
|
|
- 2026-06-17: `최초 로드 전 정렬 변경은 기본 최신순 최초 로드 계약을 바꾸지 않는다` 테스트를 추가하고 production 보정 전 실행한 결과, `changeSort(POPULAR)`가 최초 로드 기본값을 오염시켜 테스트가 실패함을 확인한 뒤 guard를 구현했다.
|
|
- 2026-06-17: 리뷰 코멘트에 따라 같은 `creatorId`로 `loadLive()`가 재호출되면 기존 목록/page/load-more 결과를 유지해야 한다는 테스트를 추가했다. production 보정 전 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveViewModelTest"` 실행 결과 해당 테스트가 실패함을 확인했다.
|
|
|
|
- [x] **Task 2.4: `CreatorChannelLiveViewModel` 구현**
|
|
- 생성:
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveViewModel.kt`
|
|
- 수정:
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt`
|
|
- 작업:
|
|
- initial loading, content, empty, error, pagination loading, pagination error 상태를 정의한다.
|
|
- `page=0`, `size` 기본값, `sort=LATEST` 초기값을 적용한다.
|
|
- 정렬 변경과 pagination 요청 중복 방지 로직을 구현한다.
|
|
- 검증 명령:
|
|
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveViewModelTest"`
|
|
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLivePaginationTest"`
|
|
- 기대 결과:
|
|
- ViewModel/pagination 테스트가 PASS한다.
|
|
- 검증 기록:
|
|
- 2026-06-17: `CreatorChannelLiveViewModel`을 추가하고 `Loading`, `Empty`, `Error`, `Content` 상태를 정의했다. 초기 로드는 `page=0`, `size=10`, `sort=LATEST`로 요청하고, pagination은 마지막 성공 응답의 `page + 1`로 append하며 `isLoadingMore`로 중복 요청을 차단한다.
|
|
- 2026-06-17: 정렬 변경은 기존 목록/page를 초기화해 첫 페이지를 다시 요청하고, 같은 정렬 재선택은 API를 재호출하지 않는다. 최초 로드 전 정렬 변경은 기본 `LATEST` 최초 로드 계약을 바꾸지 않도록 no-op 처리했다.
|
|
- 2026-06-17: 리뷰 게이트에서 정렬 변경 중 이전 첫 페이지/load-more 응답이 최신 상태를 덮어쓸 수 있다는 지적이 있어 RED 테스트를 추가했다. production 보정 전 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.*"` 실행 결과 `이전 첫 페이지 응답은 이후 정렬 변경 결과를 덮어쓰지 않는다`, `이전 load-more 응답은 이후 정렬 변경 목록에 append되지 않는다` 2개 테스트가 실패함을 확인했다.
|
|
- 2026-06-17: `requestGeneration` guard를 추가해 오래된 첫 페이지/load-more 성공 또는 실패 응답을 무시하도록 보정했다. 보정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.*"` 실행 결과 `BUILD SUCCESSFUL`로 통과했다.
|
|
- 2026-06-17: `AppDI`에 `CreatorChannelLiveViewModel(get())` binding을 추가했다. 기존 `CreatorChannelApi`/`CreatorChannelRepository` binding은 재사용했다.
|
|
- 2026-06-17: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.*"` 실행 결과 `BUILD SUCCESSFUL`로 통과했다.
|
|
- 2026-06-17: 같은 `creatorId`와 기존 상태가 있는 `loadLive()` 재호출은 no-op 하도록 보정해 Fragment/View 재생성 또는 탭 재바인딩 시 기존 정렬/page/list 상태를 유지한다. 보정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveViewModelTest"` 실행 결과 `BUILD SUCCESSFUL`로 통과했다.
|
|
- 2026-06-17: 리뷰 코멘트의 empty 정책을 반영해 PRD/계획 문서에 Figma `290:8959` 기준 전체 empty 상태를 기록했다. `CreatorChannelLiveUiState.Empty`는 Sort-bar/list를 제거하고 중앙 문구를 표시하기 위한 상태로 유지하며, 본인 채널 하단 CTA는 Phase 6의 본인 채널 정책대로 유지한다.
|
|
- 2026-06-17: 리뷰 코멘트에 따라 `Error` 상태에서 retry 버튼이 현재 `creatorId` 첫 페이지를 재요청해야 한다는 테스트를 추가했다. production 보정 전 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveViewModelTest"` 실행 결과 `retryLive` 미구현으로 `:app:compileDebugUnitTestKotlin FAILED`가 발생해 RED를 확인했다.
|
|
- 2026-06-17: Android 샘플과 기존 retry 의미를 따라 `loadLive()`의 같은 `creatorId` 상태 보존 guard는 유지하고, 명시적 `retryLive()` API를 추가했다. Phase 4 retry 버튼은 `retryLive()`를 호출하면 된다. 보정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveViewModelTest"` 및 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.*"` 실행 결과 `BUILD SUCCESSFUL`로 통과했다.
|
|
|
|
---
|
|
|
|
### Phase 3: UI model과 상태 표시 mapper 구현
|
|
|
|
- [ ] **Task 3.1: 라이브 다시듣기 표시 정책 RED 테스트 작성**
|
|
- 생성:
|
|
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveMapperTest.kt`
|
|
- 작업:
|
|
- `isOwned && isRented` 동시 true면 `소장중` 상태가 선택되는지 검증한다.
|
|
- `price == 0`이면 무료 tag와 play CTA 상태가 선택되는지 검증한다.
|
|
- `isAdult == true`이면 `ic_new_shield_small` 표시 상태가 선택되는지 검증한다.
|
|
- `isPointAvailable == true`이면 point tag 표시 상태가 선택되는지 검증한다.
|
|
- `seriesName`은 secondary text에 포함되지 않는지 검증한다.
|
|
- `isFirstContent`, `isOriginalSeries`는 기존 오디오 item 정책과 동일한 tag 상태로 매핑되는지 검증한다.
|
|
- 검증 명령:
|
|
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveMapperTest"`
|
|
- 기대 결과:
|
|
- mapper 미구현 상태에서 RED 실패한다.
|
|
|
|
- [ ] **Task 3.2: UI model/mapper 구현**
|
|
- 생성 후보:
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/model/CreatorChannelLiveUiModels.kt`
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/model/CreatorChannelLiveMappers.kt`
|
|
- 작업:
|
|
- sort label resource, replay item CTA/status, tag 상태, current live 상태를 UI model로 분리한다.
|
|
- 가격/포인트/소장중/대여중/play button 상태 우선순위를 순수 함수로 고정한다.
|
|
- `ContentSort`와 문자열 리소스 매핑을 구현한다.
|
|
- 검증 명령:
|
|
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveMapperTest"`
|
|
- 기대 결과:
|
|
- mapper 테스트가 PASS한다.
|
|
|
|
---
|
|
|
|
### Phase 4: 라이브 탭 Fragment와 목록 UI 구현
|
|
|
|
- [ ] **Task 4.1: Fragment layout과 replay item layout 추가**
|
|
- 생성:
|
|
- `app/src/main/res/layout/fragment_creator_channel_live.xml`
|
|
- `app/src/main/res/layout/item_creator_channel_live_replay.xml`
|
|
- 수정:
|
|
- `app/src/main/res/values/strings.xml`
|
|
- `app/src/main/res/values-en/strings.xml`
|
|
- `app/src/main/res/values-ja/strings.xml`
|
|
- 작업:
|
|
- Figma 기준 sort-bar, current live card, replay list, owner CTA 영역을 배치한다.
|
|
- replay item은 88dp 썸네일, title 최대 2줄, duration 1줄, 우측 price/play/status 영역을 포함한다.
|
|
- `ic_new_shield_small`, `ic_new_player_play`, `ic_new_sort`, `ic_new_create_live`를 layout 또는 binding에서 사용 가능하게 준비한다.
|
|
- 검증 명령:
|
|
- `./gradlew :app:mergeDebugResources`
|
|
- 기대 결과:
|
|
- 신규 layout/string/drawable 참조가 resource merge에 성공한다.
|
|
|
|
- [ ] **Task 4.2: Adapter와 Fragment 구현**
|
|
- 생성:
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveFragment.kt`
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/ui/CreatorChannelLiveReplayAdapter.kt`
|
|
- 작업:
|
|
- ViewModel 상태를 관찰해 sort-bar, current live card, replay list, loading/empty/error를 갱신한다.
|
|
- error 상태에서는 Sort-bar, current live card, replay list를 숨기고 중앙 안내 문구와 그 아래 retry 버튼을 표시한다.
|
|
- retry 버튼 클릭은 `CreatorChannelLiveViewModel.retryLive()`를 호출해 현재 `creatorId` 첫 페이지를 재요청한다.
|
|
- replay item click은 기존 오디오 컨텐츠 상세/재생 진입 정책을 확인해 연결한다.
|
|
- current live card click은 기존 홈 탭 라이브 진입 정책을 재사용한다.
|
|
- RecyclerView scroll listener로 하단 접근 시 ViewModel load-more를 호출한다.
|
|
- 검증 명령:
|
|
- `./gradlew :app:compileDebugKotlin`
|
|
- 기대 결과:
|
|
- Fragment/adapter가 컴파일된다.
|
|
|
|
- [ ] **Task 4.3: Fragment layout 테스트 추가**
|
|
- 생성:
|
|
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveFragmentLayoutTest.kt`
|
|
- 작업:
|
|
- `fragment_creator_channel_live.xml`에 sort-bar, RecyclerView, owner CTA가 존재하는지 검증한다.
|
|
- `item_creator_channel_live_replay.xml`에 19금 badge, point/free tag container, price/play/status 영역이 존재하는지 검증한다.
|
|
- `ic_new_*` 리소스 참조가 layout 또는 코드 상수로 연결되는지 검증한다.
|
|
- 검증 명령:
|
|
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveFragmentLayoutTest"`
|
|
- 기대 결과:
|
|
- layout 테스트가 PASS한다.
|
|
|
|
---
|
|
|
|
### Phase 5: 정렬 컨텍스트 메뉴 구현
|
|
|
|
- [ ] **Task 5.1: Sort popup 구현**
|
|
- 생성 후보:
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/ui/CreatorChannelLiveSortPopup.kt`
|
|
- `app/src/main/res/layout/view_creator_channel_live_sort_menu.xml`
|
|
- 수정:
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveFragment.kt`
|
|
- 작업:
|
|
- Sort-bar 정렬 영역 아래에 Figma `290:9041` 형태의 컨텍스트 메뉴를 표시한다.
|
|
- 현재 선택 항목은 focused 배경으로 표시한다.
|
|
- 메뉴 외부 터치, 같은 정렬 재선택, 새 정렬 선택 시 메뉴를 닫는다.
|
|
- 새 정렬 선택 시 ViewModel에 정렬 변경을 전달한다.
|
|
- 검증:
|
|
- 작은 화면에서 메뉴가 화면 밖으로 벗어나지 않도록 위치 보정 기준을 코드에 남긴다.
|
|
|
|
- [ ] **Task 5.2: Sort 동작 테스트 보강**
|
|
- 수정:
|
|
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveViewModelTest.kt`
|
|
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveMapperTest.kt`
|
|
- 작업:
|
|
- `ContentSort`별 label resource 매핑을 검증한다.
|
|
- 정렬 변경 시 첫 페이지가 다시 로딩되는지 검증한다.
|
|
- 같은 정렬 재선택은 API 재호출을 하지 않는지 검증한다.
|
|
- 검증 명령:
|
|
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.*"`
|
|
- 기대 결과:
|
|
- sort 관련 테스트가 PASS한다.
|
|
|
|
---
|
|
|
|
### Phase 6: 본인 채널 하단 CTA와 라이브 시작 진입 연결
|
|
|
|
- [ ] **Task 6.1: 본인 CTA 노출과 inset 처리 구현**
|
|
- 수정:
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveFragment.kt`
|
|
- `app/src/main/res/layout/fragment_creator_channel_live.xml`
|
|
- 작업:
|
|
- 본인 채널일 때만 하단 CTA를 표시한다.
|
|
- 타인 채널 또는 본인 여부 로딩 전에는 CTA를 숨긴다.
|
|
- CTA 표시 시 RecyclerView 하단 padding을 추가해 마지막 item이 CTA에 가려지지 않도록 한다.
|
|
- navigation bar bottom inset을 CTA 영역에 반영한다.
|
|
- 버튼 icon은 `ic_new_create_live`, label은 `라이브 시작하기` 문자열 리소스를 사용한다.
|
|
- 검증 명령:
|
|
- `./gradlew :app:mergeDebugResources`
|
|
- `./gradlew :app:compileDebugKotlin`
|
|
- 기대 결과:
|
|
- CTA resource와 inset 코드가 컴파일된다.
|
|
|
|
- [ ] **Task 6.2: 기존 라이브 시작 플로우 연결**
|
|
- 확인:
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt`
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/live/room/create/LiveRoomCreateActivity.kt`
|
|
- 수정:
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveFragment.kt`
|
|
- 필요 시 `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt`
|
|
- 작업:
|
|
- `CreatorChannelActivity`의 기존 owner FAB live action과 같은 진입 경로를 재사용한다.
|
|
- 중복 터치를 방지한다.
|
|
- 라이브 생성 완료 후 기존 홈 refresh/라이브룸 진입 정책과 충돌하지 않도록 처리한다.
|
|
- 검증:
|
|
- 라이브 시작 진입 대상 Activity/Fragment와 전달 extra를 계획 문서 해당 Task의 `검증 기록`에 남긴다.
|
|
|
|
---
|
|
|
|
### Phase 7: 탭 연결과 통합 동작
|
|
|
|
- [ ] **Task 7.1: `CreatorChannelTab.Live`를 실제 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`
|
|
- 작업:
|
|
- Live 탭 position에서 `CreatorChannelLiveFragment.newInstance(creatorId)`를 반환한다.
|
|
- 나머지 탭은 기존 placeholder 정책을 유지한다.
|
|
- 검증 명령:
|
|
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelPagerAdapterTest"`
|
|
- 기대 결과:
|
|
- Live 탭만 실제 Fragment로 연결되고 나머지 placeholder 정책은 유지된다.
|
|
|
|
- [ ] **Task 7.2: DI와 lifecycle 통합**
|
|
- 수정:
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt`
|
|
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveFragment.kt`
|
|
- 작업:
|
|
- 신규 ViewModel/API/Repository injection이 기존 홈 탭과 충돌하지 않는지 확인한다.
|
|
- Fragment 재생성 후 정렬, page, 목록 상태가 ViewModel에 유지되는지 확인한다.
|
|
- 탭 전환 시 중복 최초 API 호출이 발생하지 않도록 초기 로딩 조건을 정리한다.
|
|
- 검증 명령:
|
|
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.*"`
|
|
- `./gradlew :app:compileDebugKotlin`
|
|
- 기대 결과:
|
|
- Live 탭 관련 테스트와 컴파일이 PASS한다.
|
|
|
|
---
|
|
|
|
### Phase 8: 최종 검증과 문서 기록
|
|
|
|
- [ ] **Task 8.1: 자동 검증 실행**
|
|
- 실행 명령:
|
|
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.*"`
|
|
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Live*"`
|
|
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*"`
|
|
- `./gradlew :app:mergeDebugResources`
|
|
- `./gradlew :app:compileDebugKotlin`
|
|
- `./gradlew :app:ktlintCheck`
|
|
- 기대 결과:
|
|
- 모든 명령이 `BUILD SUCCESSFUL`로 통과한다.
|
|
- 검증 기록:
|
|
- 실행 일시, 명령, 결과, 기존 경고 여부를 이 Task 아래에 누적 기록한다.
|
|
|
|
- [ ] **Task 8.2: 수동 확인**
|
|
- 확인 항목:
|
|
- `라이브` 탭 진입 시 `page=0`, `sort=LATEST`로 최초 조회된다.
|
|
- Sort-bar에 `전체 {count}`와 현재 정렬 label, `ic_new_sort`가 표시된다.
|
|
- 정렬 컨텍스트 메뉴가 Sort-bar 아래에 표시되고 선택/닫힘 동작이 정상이다.
|
|
- `currentLive != null`일 때 현재 라이브 카드가 표시된다.
|
|
- 라이브 다시듣기 item의 19금/포인트/무료/소장중/대여중/가격 상태가 DTO와 일치한다.
|
|
- `isOwned && isRented` 동시 true item은 `소장중`으로 표시된다.
|
|
- `seriesName`은 표시되지 않는다.
|
|
- `hasNext == true`일 때 하단 스크롤로 다음 페이지가 append된다.
|
|
- 본인 채널에서만 하단 `라이브 시작하기` CTA가 표시된다.
|
|
- 타인 채널에서는 하단 CTA가 표시되지 않는다.
|
|
- CTA가 마지막 item을 가리지 않는다.
|
|
- 검증 기록:
|
|
- 실제 확인한 시나리오와 결과를 이 Task 아래에 한국어로 누적 기록한다.
|
|
|
|
---
|
|
|
|
## Verification Log
|
|
- 2026-06-17: Phase 1 진행. `CreatorChannelHomeApi`/`CreatorChannelHomeRepository`를 `CreatorChannelApi`/`CreatorChannelRepository`로 rename하고 기존 홈 endpoint/repository method 동작은 유지했다.
|
|
- 2026-06-17: `./gradlew :app:compileDebugKotlin` PASS. 최초 병렬 실행은 KSP incremental cache 손상으로 실패했으나 `app/build/kspCaches/debug` 생성물 캐시 삭제 후 순차 재실행에서 통과했다.
|
|
- 2026-06-17: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelHomeViewModelTest"` PASS.
|
|
- 2026-06-17: Phase 2 진행. 라이브 탭 DTO/API/Repository/ViewModel 계약과 RED/GREEN 테스트를 추가했다. UI/layout/mapper/Fragment/tab 연결은 Phase 3 이후 범위라 변경하지 않았다.
|
|
- 2026-06-17: RED 확인. production 구현 전 live ViewModel 테스트 실행 시 `ContentSort`, `CreatorChannelLiveTabResponse`, `CreatorChannelLiveViewModel`, `CreatorChannelRepository.getLive` 미구현으로 `:app:compileDebugUnitTestKotlin FAILED`가 발생했다.
|
|
- 2026-06-17: 추가 RED 확인. 최초 로드 전 `changeSort(POPULAR)` 호출이 기본 `LATEST` 최초 로드 계약을 오염시키는 실패를 확인한 뒤 guard를 적용했다.
|
|
- 2026-06-17: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.*"` PASS.
|
|
- 2026-06-17: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelHomeViewModelTest"` PASS.
|
|
- 2026-06-17: `./gradlew :app:compileDebugKotlin` PASS.
|
|
- 2026-06-17: 리뷰 게이트 지적으로 stale async response 보강. production 보정 전 stale first-page/load-more 테스트 2개가 실패했고, `requestGeneration` guard 적용 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.*"` PASS.
|
|
- 2026-06-17: stale async response 보강 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelHomeViewModelTest"` PASS.
|
|
- 2026-06-17: stale async response 보강 후 `./gradlew :app:compileDebugKotlin` PASS.
|
|
- 2026-06-17: 리뷰 코멘트 반영. Figma `290:8959` 기준 전체 empty 상태는 Sort-bar/list 없이 중앙 문구를 표시하고, 본인 채널 하단 CTA는 유지하는 것으로 PRD/계획 문서에 기록했다.
|
|
- 2026-06-17: 같은 `creatorId`의 `loadLive()` 재호출 상태 보존 RED 테스트를 추가했다. 보정 전 테스트 실패 확인 후 기존 상태가 있으면 no-op 하도록 구현했고, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveViewModelTest"` PASS.
|
|
- 2026-06-17: 리뷰 코멘트 반영 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.*"` PASS.
|
|
- 2026-06-17: 리뷰 코멘트 반영 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelHomeViewModelTest"` PASS. 병렬 실행 중 Robolectric 임시 DataStore 정리 경고가 한 차례 출력되었으나, 동일 명령 순차 재실행 결과 `BUILD SUCCESSFUL`로 통과했다.
|
|
- 2026-06-17: 리뷰 코멘트 반영 후 `./gradlew :app:compileDebugKotlin` PASS.
|
|
- 2026-06-17: 리뷰 코멘트 반영. `loadLive()`의 같은 `creatorId` 상태 보존 guard는 유지하고, Error 상태 재시도는 명시적 `retryLive()`로 분리했다. production 보정 전 `retryLive` 미구현 컴파일 실패를 확인했고, 보정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveViewModelTest"` PASS.
|
|
- 2026-06-17: Phase 4 UI 작업에 error 안내 문구 아래 retry 버튼을 표시하고, retry 버튼이 `CreatorChannelLiveViewModel.retryLive()`를 호출한다는 요구사항을 추가했다.
|
|
- 2026-06-17: `retryLive()` 추가 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.*"` PASS.
|
|
- 2026-06-17: `retryLive()` 추가 후 `./gradlew :app:compileDebugKotlin` PASS.
|
|
- 2026-06-17: `retryLive()` 추가 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelHomeViewModelTest"` 최초 병렬 실행에서 `채널 후원 성공은 기존 후원 API를 호출하고 홈을 다시 로드한다`가 실패했으나, 변경 범위와 무관한 SharedPreferenceManager 상태 간섭 가능성을 분리하기 위해 동일 명령을 순차 재실행했고 `BUILD SUCCESSFUL`로 통과했다.
|
|
- 2026-06-17: 전체 회귀 명령 실행. 최초 sandbox 실행은 `~/.gradle` wrapper lock 접근 제한으로 실패해 권한 승인 후 Gradle 명령을 재실행했다.
|
|
- 2026-06-17: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Live*"` PASS.
|
|
- 2026-06-17: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.*"` PASS.
|
|
- 2026-06-17: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*"` FAIL. `CreatorChannelActivitySourceTest`의 기존 XML/source 문자열 검증 3개가 실패했다: `activity_creator_channel.xml`의 `ic_new_talk`/`ic_new_dm` 기대, `item_creator_channel_home_donation.xml`의 `196dp` 기대, `item_creator_channel_home_series_content.xml`의 `android:text="Only"` 기대.
|
|
- 2026-06-17: `./gradlew :app:mergeDebugResources` PASS.
|
|
- 2026-06-17: `./gradlew :app:compileDebugKotlin` PASS.
|
|
- 2026-06-17: `./gradlew :app:ktlintCheck` 최초 실행에서 신규 live test 줄바꿈과 `CreatorChannelLiveModels.kt` 파일명 규칙 위반으로 FAIL. 테스트 helper 줄바꿈을 정리하고 응답 모델 파일명을 `CreatorChannelLiveTabResponse.kt`로 변경한 뒤 재실행해 PASS.
|
|
- 2026-06-17: ktlint 보정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.*"` PASS, `./gradlew :app:compileDebugKotlin` PASS, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Live*"` PASS.
|
|
- 2026-06-17: ktlint 보정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*"` 재실행 결과 동일한 `CreatorChannelActivitySourceTest` 3개 실패가 재현됐다.
|