Files
sodalive-android/docs/20260620_크리에이터_채널_시리즈_탭/plan-task.md

580 lines
46 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 크리에이터 채널 시리즈 탭 구현 계획/TASK
> **For agentic workers:** 각 단계는 체크박스(`- [ ]`)로 추적하고, 완료 즉시 `- [x]`로 갱신한다. 구현 범위 변경이 생기면 이 문서를 먼저 수정한 뒤 코드에 반영한다.
**Goal:** `GET /api/v2/creator-channels/{creatorId}/series` 응답을 기반으로 크리에이터 채널의 `시리즈` 탭에 정렬, 시리즈 목록, 조건부 소장 진행 정보, empty 상태와 pagination을 표시한다.
**Architecture:** 기존 `CreatorChannelActivity``ViewPager2`/`CreatorChannelPagerAdapter` 구조를 유지하고, `CreatorChannelTab.Series`의 placeholder를 신규 `CreatorChannelSeriesFragment`로 교체한다. 시리즈 탭 전용 Fragment/ViewModel/DTO/mapper/adapter는 `kr.co.vividnext.sodalive.v2.creator.channel.series` 하위에 두되, API/Repository는 기존 채널 공통 `CreatorChannelApi`/`CreatorChannelRepository`에 endpoint만 추가한다. 정렬 UI는 오디오 탭에서 사용하는 `CreatorChannelSortPopup``ContentSort.toLabelResId()`를 재사용하고, 목록 하단 감지와 ViewPager 높이 갱신은 기존 Live/Audio 탭 패턴에 Series를 추가한다.
**Tech Stack:** Kotlin, Android XML Views, ViewBinding, RecyclerView, Retrofit, Gson, RxJava3, Koin, JUnit4/Robolectric local unit test.
---
## 전제와 성공 기준
- PRD: `docs/20260620_크리에이터_채널_시리즈_탭/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/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/creator/channel/CreatorChannelActivity.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/detail/SeriesDetailActivity.kt`
- `Constants.EXTRA_SERIES_ID`
- 기존 오디오 탭 참조:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/CreatorChannelAudioFragment.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/CreatorChannelAudioViewModel.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/model/CreatorChannelAudioMappers.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelSortPopup.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/model/CreatorChannelSortModels.kt`
- 기존 홈 시리즈 item 참조:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelHomeSeriesCardView.kt`
- `app/src/main/res/layout/item_creator_channel_home_series.xml`
- `app/src/main/res/layout/item_creator_channel_home_series_content.xml`
- Figma:
- 전체: `290:9031`
- 시리즈 item: `290:9036`
- 시리즈 콘텐츠 소장률: `290:9038`
- API endpoint는 `GET /api/v2/creator-channels/{creatorId}/series`이다.
- 첫 페이지 `page``0`, 기본 `size``20`, 기본 `sort``ContentSort.LATEST`이다.
- 정렬 옵션은 `ContentSort.LATEST`, `ContentSort.POPULAR`, `ContentSort.OWNED`, `ContentSort.PRICE_HIGH`, `ContentSort.PRICE_LOW` 5개만 표시한다.
- Figma에 보이는 `추천순`은 표시하지 않는다.
- `coverImageUrl`은 시리즈 썸네일 이미지로 사용한다.
- 시리즈 item 우측의 `전체소장`/play button 영역은 표시하지 않는다.
- 썸네일처럼 크기 제한이 필요한 영역 외에는 불필요한 고정 width/height를 만들지 않는다.
- 내 채널이 아닌 경우에만 `purchasedContentCount`, `paidContentCount`, `purchasedPaidContentRate` 기반 소장 진행 정보를 표시한다.
- 내 채널인 경우 item에는 제목, 발행 요일, 총 콘텐츠 수, 연재/완결 상태만 표시한다.
- 시리즈 empty 문구는 다음 다국어 문자열 리소스로 제공한다.
- 한국어: `크리에이터가 시리즈를 준비 중입니다.\n기대해 주세요!`
- 영어: `The creator is preparing a series.\nPlease look forward to it!`
- 일본어: `クリエイターがシリーズを準備中です。\n楽しみにお待ちください`
- 구현 완료 후 최소 다음 명령을 실행한다.
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.series.*"`
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Series*"`
- `./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: 제한 참조
- 기존 코드 경계, 시리즈 상세 진입, 공통 sort/pagination 패턴 확인이 중심이며 Figma는 PRD 기준만 확인한다.
- Phase 2: Figma 참조 불필요
- API/DTO/Repository/ViewModel 상태 모델은 서버 계약과 기존 오디오 탭 패턴을 따른다.
- Phase 3: 제한 참조
- mapper는 PRD와 Figma item variant의 정보 표시/소장률 조건을 함께 확인한다.
- Phase 4: 필수 참조
- Sort-bar, empty, 시리즈 item layout, 우측 버튼 제거 후 info 영역 확장은 Figma `290:9031`, `290:9036`, `290:9038` 기준으로 구현한다.
- Phase 5: 제한 참조
- 탭 연결, pagination, navigation, ViewPager 높이 갱신은 기존 코드 패턴 중심으로 검증한다.
- Phase 6: 필수 참조
- 최종 수동 화면 검증은 PRD의 모든 Figma 노드와 실제 화면을 대조한다.
---
## 파일 구조
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapter.kt`
- `CreatorChannelTab.Series`를 신규 `CreatorChannelSeriesFragment`로 연결한다.
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt`
- `CreatorChannelSeriesFragment.Host` 구현, 시리즈 탭 선택 시 최초 로드, pagination trigger, ViewPager 높이 갱신, 시리즈 상세 이동을 연결한다.
- 수정: `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/series/data/CreatorChannelSeriesTabResponse.kt`
- `CreatorChannelSeriesTabResponse`, `CreatorChannelSeriesResponse`를 정의한다.
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/series/CreatorChannelSeriesViewModel.kt`
- 최초 조회, 정렬 변경, retry, pagination, loading/error/empty/content 상태를 관리한다.
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/series/model/CreatorChannelSeriesUiModels.kt`
- series item, possession progress, 화면 상태 UI model을 정의한다.
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/series/model/CreatorChannelSeriesMappers.kt`
- DTO를 UI model로 변환하고 subtitle, original/adult tag, owner별 progress 표시 여부를 결정한다.
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/series/CreatorChannelSeriesFragment.kt`
- 시리즈 탭 UI, adapter, 공통 sort popup, retry, pagination error toast, host callback 연결을 담당한다.
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/series/ui/CreatorChannelSeriesAdapter.kt`
- 시리즈 목록 RecyclerView adapter를 담당한다.
- 생성: `app/src/main/res/layout/fragment_creator_channel_series.xml`
- Sort-bar, RecyclerView, empty/error/retry 영역을 포함한다.
- 생성: `app/src/main/res/layout/item_creator_channel_series.xml`
- Figma 시리즈 item을 구현한다. 우측 버튼 영역은 포함하지 않는다.
- 수정: `app/src/main/res/values/strings.xml`
- 시리즈 탭 empty/error/retry/상태/소장률 문구를 추가한다.
- 수정: `app/src/main/res/values-en/strings.xml`, `app/src/main/res/values-ja/strings.xml`
- 신규 empty/error/retry/상태 문구의 다국어 값을 추가한다.
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt`
- `CreatorChannelSeriesViewModel` binding을 추가한다.
- 테스트 생성:
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/series/CreatorChannelSeriesMapperTest.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/series/CreatorChannelSeriesViewModelTest.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/series/CreatorChannelSeriesPaginationTest.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/series/CreatorChannelSeriesFragmentLayoutTest.kt`
- 테스트 수정:
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapterTest.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt`
---
### Phase 1: 기존 구조 확인과 작업 경계 고정
- [x] **Task 1.1: 오디오 탭 재사용 경계 확인**
- 확인:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/CreatorChannelAudioFragment.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/CreatorChannelAudioViewModel.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/model/CreatorChannelSortModels.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelSortPopup.kt`
- 작업:
- Series 탭도 `CreatorChannelSortPopup`을 그대로 사용한다.
- sort option은 `ContentSort.entries`를 그대로 쓰되 enum 5개만 노출되는지 확인한다.
- 오디오 탭의 `loadMore`, `requestGeneration`, `paginationErrorMessage`, `consumePaginationErrorMessage` 패턴을 시리즈 ViewModel에 동일하게 적용한다.
- 검증:
- `rg -n "CreatorChannelSortPopup|toLabelResId|loadMore|paginationErrorMessage" app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel`
- 기대 결과: 공통 sort popup과 오디오 pagination 패턴이 확인된다.
- 검증 기록:
- 2026-06-20 확인: `CreatorChannelSortPopup``ContentSort.entries`를 기반으로 option을 구성하고, `ContentSort.toLabelResId()``LATEST`, `POPULAR`, `OWNED`, `PRICE_HIGH`, `PRICE_LOW` 5개 enum만 매핑한다.
- 2026-06-20 확인: `CreatorChannelAudioViewModel``loadMore`, `requestGeneration`, `paginationErrorMessage`, `consumePaginationErrorMessage` 패턴을 Series ViewModel에 재사용할 경계로 확정했다.
- [x] **Task 1.2: 시리즈 상세 진입 경로 확인**
- 확인:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/detail/SeriesDetailActivity.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/common/Constants.kt`
- 작업:
- 기존 `onSeriesClicked(series)``SeriesDetailActivity``Constants.EXTRA_SERIES_ID`를 전달하는지 확인한다.
- 시리즈 탭 item 클릭은 신규 경로를 만들지 않고 같은 Activity method를 재사용한다.
- 검증:
- `rg -n "SeriesDetailActivity|EXTRA_SERIES_ID|onSeriesClicked" app/src/main/java`
- 기대 결과: 기존 시리즈 상세 진입점이 확인된다.
- 검증 기록:
- 2026-06-20 확인: `CreatorChannelActivity.onSeriesClicked(series)``SeriesDetailActivity`를 시작하고 `Constants.EXTRA_SERIES_ID``series.seriesId`를 전달한다.
- 2026-06-20 확인: 시리즈 탭 item 클릭은 신규 상세 이동 경로를 만들지 않고 동일한 `SeriesDetailActivity` + `Constants.EXTRA_SERIES_ID` 경로를 재사용한다.
- [x] **Task 1.3: Series 탭 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.Series`가 현재 `CreatorChannelPlaceholderFragment`로 연결되는지 확인한다.
- 신규 `CreatorChannelSeriesFragment.newInstance(creatorId)`로 교체할 수 있는지 확인한다.
- 검증:
- `CreatorChannelPagerAdapterTest`에 Series 탭 연결 테스트를 추가할 준비가 되었는지 기록한다.
- 검증 기록:
- 2026-06-20 확인: `CreatorChannelPagerAdapter.createFragment()``Home`, `Live`, `Audio`만 전용 Fragment로 연결하고, `CreatorChannelTab.Series`는 현재 `else` 분기를 통해 `CreatorChannelPlaceholderFragment.newInstance(tab)`로 연결된다.
- 2026-06-20 확인: `CreatorChannelTab.Series` enum이 존재하므로 Phase 5에서 `CreatorChannelSeriesFragment.newInstance(creatorId)` 분기를 추가하는 테스트를 준비할 수 있다.
- [x] **Task 1.4: 기존 시리즈 썸네일/tag 리소스 확인**
- 확인:
- `app/src/main/res/layout/item_creator_channel_home_series.xml`
- `app/src/main/res/layout/item_creator_channel_home_series_content.xml`
- `app/src/main/res/layout/view_series_original_tag.xml`
- `app/src/main/res/drawable`
- 작업:
- original tag와 adult tag를 기존 리소스로 표현할 수 있는지 확인한다.
- image placeholder는 기존 시리즈/오디오 콘텐츠 item의 placeholder 정책을 확인해 따른다.
- 검증:
- `rg -n "view_series_original_tag|isAdult|adult|original" app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel app/src/main/res/layout app/src/main/res/drawable`
- 기대 결과: 기존 tag/placeholder 재사용 경계가 확인된다.
- 검증 기록:
- 2026-06-20 확인: 홈 시리즈 item은 `item_creator_channel_home_series_content.xml`에서 `layout_series_original_tag`, `bg_series_original_tag`, `ic_series_original`, `img_new_only`를 사용하고, 공통 `view_series_original_tag.xml`도 존재한다.
- 2026-06-20 확인: adult badge는 오디오/라이브 채널 item의 `bg_creator_channel_live_adult_badge` 및 adult 표시 패턴을 재사용 후보로 확인했다.
---
### Phase 2: API/DTO/Repository/ViewModel 계약 추가
- [x] **Task 2.1: 시리즈 탭 DTO 추가**
- 생성:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/series/data/CreatorChannelSeriesTabResponse.kt`
- 작업:
- `@Keep`, `@SerializedName` 기반으로 `CreatorChannelSeriesTabResponse`, `CreatorChannelSeriesResponse`를 추가한다.
- `ContentSort`는 기존 `kr.co.vividnext.sodalive.v2.common.data.ContentSort`를 import해 사용한다.
- `coverImageUrl` 필드를 포함한다.
- 홈 API의 `CreatorChannelSeriesResponse`와 이름이 충돌하지 않도록 package import를 명확히 관리한다.
- 검증 명령:
- `./gradlew :app:compileDebugKotlin`
- 기대 결과:
- 신규 DTO 추가 후 컴파일이 PASS한다.
- 검증 기록:
- 2026-06-20 구현: `CreatorChannelSeriesTabResponse`, `CreatorChannelSeriesResponse``series/data` package에 추가하고 `ContentSort`, `coverImageUrl`, pagination field, progress nullable field를 포함했다.
- 2026-06-20 검증: `./gradlew --no-daemon :app:compileDebugKotlin` PASS.
- [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}/series")` endpoint를 추가한다.
- query parameter `sort`, `page`, `size`를 전달한다.
- Repository method는 `getSeries(creatorId, page, size, sort, token)` 형태로 둔다.
- 검증 명령:
- `./gradlew :app:compileDebugKotlin`
- 기대 결과:
- API/Repository 추가 후 기존 Koin graph와 충돌 없이 컴파일된다.
- 검증 기록:
- 2026-06-20 구현: `CreatorChannelApi.getSeries()``GET /api/v2/creator-channels/{creatorId}/series` endpoint와 `page`, `size`, `sort`, `Authorization` 전달을 추가하고, `CreatorChannelRepository.getSeries(creatorId, page, size, sort, token)` 위임 method를 추가했다.
- 2026-06-20 검증: `./gradlew --no-daemon :app:compileDebugKotlin` PASS.
- [x] **Task 2.3: ViewModel RED 테스트 작성**
- 생성:
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/series/CreatorChannelSeriesViewModelTest.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/series/CreatorChannelSeriesPaginationTest.kt`
- 테스트 케이스:
- 최초 로딩이 `page=0`, `size=20`, `sort=LATEST`로 호출된다.
- 정렬 변경 시 `page=0`, 선택된 sort로 재조회된다.
- 같은 정렬을 다시 선택하면 API를 재호출하지 않는다.
- `hasNext == true`일 때 다음 페이지는 마지막 응답의 `page + 1`로 요청한다.
- load-more 요청에는 현재 sort와 `size=20`을 유지한다.
- loading 중 중복 load-more 요청은 무시된다.
- 다음 페이지 성공 시 기존 series 뒤에 append한다.
- 다음 페이지 실패 시 기존 목록은 유지하고 pagination error message만 설정한다.
- `seriesCount == 0`이면 `Empty` 상태가 된다.
- 표시 가능한 series가 0개이면 `Empty` 상태가 된다.
- 내 채널이면 item progress UI model이 `null`이다.
- 내 채널이 아니고 progress 관련 nullable field가 모두 있으면 progress UI model이 생성된다.
- 내 채널이 아니어도 progress 관련 nullable field 중 하나라도 `null`이면 progress UI model이 `null`이다.
- 검증 명령:
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesViewModelTest"`
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesPaginationTest"`
- 기대 결과:
- production 구현 전 `CreatorChannelSeriesViewModel` 미구현으로 RED 실패한다.
- 검증 기록:
- 2026-06-20 기록: ULTRAWORK TDD 순서상 RED 테스트가 먼저 필요했으나, 실제 작업에서는 production skeleton이 먼저 추가되어 production 미구현 RED는 별도로 확보하지 못했다. 이후 동일 범위 내에서 `CreatorChannelSeriesViewModelTest`, `CreatorChannelSeriesPaginationTest`를 추가해 최초 로드/정렬/empty/progress/pagination/stale response 계약을 고정했다.
- 2026-06-20 검증: `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.series.*"` PASS.
- [x] **Task 2.4: `CreatorChannelSeriesViewModel` 구현**
- 생성:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/series/CreatorChannelSeriesViewModel.kt`
- 수정:
- `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt`
- 작업:
- `DEFAULT_PAGE_SIZE = 20`, `FIRST_PAGE = 0`, 기본 `selectedSort = ContentSort.LATEST`로 둔다.
- `loadSeries(creatorId, isOwner)`는 같은 creatorId/isOwner로 이미 상태가 있으면 중복 최초 조회를 막는다.
- `changeSort(sort)`는 같은 sort이면 API를 재호출하지 않는다.
- `retrySeries()`는 현재 sort로 첫 페이지를 다시 조회한다.
- `loadMore()`는 content 상태, `hasNext`, `isLoadingMore`, creatorId를 확인해 중복 요청을 막는다.
- `requestGeneration`으로 오래된 응답이 최신 상태를 덮어쓰지 않게 한다.
- 성공 응답의 `seriesCount == 0` 또는 표시 가능한 item이 0개이면 `Empty` 상태로 전환한다.
- pagination 실패는 기존 content를 유지하고 `paginationErrorMessage`에만 반영한다.
- 검증 명령:
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesViewModelTest"`
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesPaginationTest"`
- 기대 결과:
- ViewModel 테스트가 GREEN이다.
- 검증 기록:
- 2026-06-20 구현: `CreatorChannelSeriesViewModel`을 추가하고 `loadSeries`, `changeSort`, `retrySeries`, `loadMore`, `consumePaginationErrorMessage`, `requestGeneration`, `DEFAULT_PAGE_SIZE=20` 상태 관리를 오디오 탭 패턴에 맞춰 구현했다. `AppDI``CreatorChannelSeriesViewModel` Koin binding을 추가했다.
- 2026-06-20 검증: `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.series.*"` PASS.
---
### Phase 3: Mapper/UI model 계약 추가
- [x] **Task 3.1: Mapper RED 테스트 작성**
- 생성:
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/series/CreatorChannelSeriesMapperTest.kt`
- 테스트 케이스:
- `isProceeding == true`이면 subtitle에 `연재`가 포함된다.
- `isProceeding == false`이면 subtitle에 `완결`이 포함된다.
- `publishedDaysOfWeek`, `contentCount`, 진행 상태를 `매주 월 • 총 45화 • 연재` 형식으로 조합한다.
- `publishedDaysOfWeek`가 blank이면 빈 bullet 없이 `총 45화 • 연재` 형식으로 조합한다.
- `isOriginal == true`이면 original tag 표시 flag가 true이다.
- `isAdult == true`이면 adult tag 표시 flag가 true이다.
- 내 채널이면 progress가 생성되지 않는다.
- 내 채널이 아니고 `purchasedContentCount=12`, `paidContentCount=45`, `purchasedPaidContentRate=40`이면 progress가 생성된다.
- rate가 0 미만이면 progress bar percent는 0으로 clamp된다.
- rate가 100 초과이면 progress bar percent는 100으로 clamp된다.
- 검증 명령:
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesMapperTest"`
- 기대 결과:
- production mapper 미구현으로 RED 실패한다.
- 검증 기록:
- 2026-06-20 기록: ULTRAWORK TDD 순서상 RED 테스트가 먼저 필요했으나, 실제 작업에서는 mapper production 파일이 먼저 추가되어 production 미구현 RED는 별도로 확보하지 못했다. 이후 `CreatorChannelSeriesMapperTest`를 추가해 subtitle, original/adult flag, owner별 progress, partial-null progress, progressScale clamp, blank title 제외 계약을 고정했다.
- 2026-06-20 검증: `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.series.*"` PASS.
- [x] **Task 3.2: UI model과 mapper 구현**
- 생성:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/series/model/CreatorChannelSeriesUiModels.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/series/model/CreatorChannelSeriesMappers.kt`
- 작업:
- `CreatorChannelSeriesItemUiModel`에는 `seriesId`, `title`, `subtitle`, `coverImageUrl`, `showOriginalTag`, `showAdultBadge`, `progress`를 둔다.
- `CreatorChannelSeriesProgressUiModel`에는 `purchasedCount`, `paidCount`, `ratePercent`, `progressScale`를 둔다.
- progress는 내 채널이면 생성하지 않는다.
- progress는 nullable field가 모두 있을 때만 생성한다.
- progress bar 표시값은 `purchasedPaidContentRate / 100f`를 0f..1f로 clamp한다.
- subtitle은 blank segment를 제외하고 ` • `로 join한다.
- 검증 명령:
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesMapperTest"`
- 기대 결과:
- Mapper 테스트가 GREEN이다.
- 검증 기록:
- 2026-06-20 구현: `CreatorChannelSeriesItemUiModel`, `CreatorChannelSeriesProgressUiModel`, `toSeriesItemUiModels(isOwner)` mapper를 추가했다. subtitle은 blank segment를 제외하고 ` • `로 join하며, progress는 owner이거나 nullable field가 누락되면 생성하지 않고 `purchasedPaidContentRate / 100f`를 0f..1f로 clamp한다.
- 2026-06-20 검증: `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.series.*"` PASS.
- 2026-06-20 후속 수정: mapper가 subtitle을 한국어 문자열(`총 n화`, `연재`, `완결`)로 직접 조합하던 문제를 수정했다. `CreatorChannelSeriesSubtitleUiModel``publishedDaysOfWeek`, `contentCount`, `isProceeding` 조각 데이터를 유지하고, Adapter bind 시점에 다국어 string resource로 subtitle을 조합하도록 변경했다.
- 2026-06-20 후속 RED 확인: mapper/layout 테스트 기대값을 subtitle 조각 모델과 resource 기반 bind 계약으로 변경한 뒤 production 수정 전 `item.subtitle``String`이라 `isProceeding`, `contentCount`, `publishedDaysOfWeek` 접근 컴파일 오류가 발생함을 확인했다.
- 2026-06-20 후속 검증: `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesMapperTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesFragmentLayoutTest"` PASS.
---
### Phase 4: Fragment, Adapter, XML UI 구현
- [x] **Task 4.1: Fragment/Layout RED 테스트 작성**
- 생성:
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/series/CreatorChannelSeriesFragmentLayoutTest.kt`
- 테스트 케이스:
- `fragment_creator_channel_series.xml`이 존재한다.
- `item_creator_channel_series.xml`이 존재한다.
- fragment layout에 Sort-bar, total count, sort label, RecyclerView, empty, error, retry view id가 존재한다.
- item layout에 thumbnail, title, subtitle, original tag, adult badge, progress container, progress count, progress percent, progress fill id가 존재한다.
- item layout source에 `전체소장` 또는 play button id/text가 포함되지 않는다.
- empty 문자열 resource key가 한국어/영어/일본어 파일에 모두 존재한다.
- 검증 명령:
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesFragmentLayoutTest"`
- 기대 결과:
- layout/string 미구현으로 RED 실패한다.
- 검증 기록:
- 2026-06-20 RED 확인: `CreatorChannelSeriesFragmentLayoutTest`를 추가하고 실행했을 때 `CreatorChannelSeriesFragment`, `fragment_creator_channel_series`, `item_creator_channel_series` 및 관련 view id 미구현으로 `compileDebugUnitTestKotlin`이 실패했다.
- 2026-06-20 GREEN 확인: layout/string/Fragment/Adapter 구현 후 `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesFragmentLayoutTest"` PASS.
- [x] **Task 4.2: 문자열 리소스 추가**
- 수정:
- `app/src/main/res/values/strings.xml`
- `app/src/main/res/values-en/strings.xml`
- `app/src/main/res/values-ja/strings.xml`
- 작업:
- `creator_channel_series_empty_message`를 3개 언어에 추가한다.
- `creator_channel_series_error_message`, `creator_channel_series_retry_button`, `creator_channel_series_status_proceeding`, `creator_channel_series_status_completed`, `creator_channel_series_total_content_count`, `creator_channel_series_progress_count`, `creator_channel_series_progress_percent`를 추가한다.
- 기존 공통 문자열이 있으면 중복 생성하지 않고 재사용한다.
- 검증 명령:
- `./gradlew :app:mergeDebugResources`
- 기대 결과:
- 신규 문자열 리소스 병합이 PASS한다.
- 검증 기록:
- 2026-06-20 구현: `creator_channel_series_empty_message`, error/retry/status/total/progress 문자열을 한국어/영어/일본어 리소스에 추가했다.
- 2026-06-20 검증: `./gradlew --no-daemon :app:mergeDebugResources` PASS.
- [x] **Task 4.3: 시리즈 탭 fragment layout 작성**
- 생성:
- `app/src/main/res/layout/fragment_creator_channel_series.xml`
- 작업:
- 오디오 탭의 sort bar 구조를 참고하되 theme tab과 audio rate card는 만들지 않는다.
- 좌측 `전체` label과 `seriesCount` TextView를 둔다.
- 우측 sort label과 `ic_new_sort` ImageView를 둔다.
- RecyclerView는 vertical list로 사용한다.
- empty 영역에는 `creator_channel_series_empty_message`를 표시한다.
- error TextView와 retry Button은 기존 오디오 탭 error/retry 패턴을 따른다.
- 검증 명령:
- `./gradlew :app:mergeDebugResources`
- 기대 결과:
- ViewBinding `FragmentCreatorChannelSeriesBinding`이 생성된다.
- 검증 기록:
- 2026-06-20 구현: `fragment_creator_channel_series.xml`에 sort bar, total count, sort label/icon, RecyclerView, empty/error/retry 영역을 추가했다.
- 2026-06-20 검증: `./gradlew --no-daemon :app:mergeDebugResources` PASS, `FragmentCreatorChannelSeriesBinding` 생성 후 `./gradlew --no-daemon :app:compileDebugKotlin` PASS.
- [x] **Task 4.4: 시리즈 item layout 작성**
- 생성:
- `app/src/main/res/layout/item_creator_channel_series.xml`
- 작업:
- 썸네일은 Figma 기준 `122dp x 172dp`, radius 14dp 형태로 제한한다.
- item root는 `match_parent` width와 `wrap_content` height를 사용한다.
- 좌측 썸네일 외 info/progress 영역은 `0dp` width + weight 또는 ConstraintLayout constraint로 남은 공간을 채운다.
- 우측 `전체소장`/play button 영역은 만들지 않는다.
- original tag는 기존 `view_series_original_tag` 또는 기존 equivalent resource를 재사용한다.
- adult badge는 기존 adult icon/background 정책을 재사용한다.
- progress container는 내 채널이거나 progress가 null이면 adapter에서 `GONE` 처리할 수 있게 분리한다.
- progress fill은 scaleX 방식으로 0f..1f를 적용할 수 있게 pivot start 기준 구조로 만든다.
- 검증 명령:
- `./gradlew :app:mergeDebugResources`
- 기대 결과:
- ViewBinding `ItemCreatorChannelSeriesBinding`이 생성된다.
- 검증 기록:
- 2026-06-20 구현: `item_creator_channel_series.xml`에 122dp x 172dp thumbnail, wrap-content original tag, adult badge, title/subtitle, progress count/percent/fill을 추가하고 우측 `전체소장`/play button 영역은 제외했다.
- 2026-06-20 검증: 최초 `./gradlew --no-daemon :app:compileDebugKotlin`은 존재하지 않는 `@dimen/spacing_2` 참조로 실패했고, `2dp`로 수정 후 PASS.
- [x] **Task 4.5: Adapter 구현**
- 생성:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/series/ui/CreatorChannelSeriesAdapter.kt`
- 작업:
- `RecyclerView.Adapter` 또는 기존 프로젝트 adapter 패턴을 따른다.
- `submitItems(items)`로 내부 목록을 갱신한다.
- item 클릭 시 `onSeriesClicked(seriesId)`를 호출한다.
- `title`, `subtitle`, `coverImageUrl`, original/adult tag, progress count/rate/progress fill을 bind한다.
- `coverImageUrl`은 기존 `loadUrl` extension과 placeholder/error 정책을 사용한다.
- progress가 null이면 progress container를 `GONE` 처리한다.
- 검증 명령:
- `./gradlew :app:compileDebugKotlin`
- 기대 결과:
- Adapter 컴파일이 PASS한다.
- 검증 기록:
- 2026-06-20 구현: `CreatorChannelSeriesAdapter`를 추가하고 `submitItems`, item click, title/subtitle/image/original/adult/progress binding, thumbnail radius outline 처리를 구현했다.
- 2026-06-20 검증: `./gradlew --no-daemon :app:compileDebugKotlin` PASS.
- [x] **Task 4.6: Fragment 구현**
- 생성:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/series/CreatorChannelSeriesFragment.kt`
- 작업:
- `newInstance(creatorId)`로 creatorId argument를 받는다.
- `Host` interface에는 `isCreatorChannelOwner()`, `onCreatorChannelSeriesClicked(seriesId: Long)`, `onCreatorChannelSeriesContentChanged()`를 둔다.
- `onCreatorChannelSeriesTabSelected()`에서 `viewModel.loadSeries(creatorId, isOwner = host.isCreatorChannelOwner())`를 호출한다.
- `onCreatorChannelSeriesScrolledToBottom()`에서 `viewModel.loadMore()`를 호출한다.
- `onCreatorChannelSeriesViewportHeightChanged(minHeight)`는 empty/error 영역 최소 높이 조정이 필요하면 기존 Live/Audio 패턴에 맞춰 구현한다.
- sort button 클릭 시 `CreatorChannelSortPopup`을 띄우고 선택 결과를 `viewModel.changeSort(sort)`로 전달한다.
- Loading/Empty/Error/Content 상태별 view visibility를 명확히 bind한다.
- content bind 시 total count, sort label, adapter items를 갱신한다.
- pagination error message는 Toast로 표시하고 consume한다.
- content layout key가 바뀔 때만 host에 content changed를 알린다.
- 검증 명령:
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesFragmentLayoutTest"`
- `./gradlew :app:compileDebugKotlin`
- 기대 결과:
- Fragment layout 테스트와 Kotlin 컴파일이 PASS한다.
- 검증 기록:
- 2026-06-20 구현: `CreatorChannelSeriesFragment`를 추가하고 `newInstance(creatorId)`, `Host`, sort popup, `loadSeries`, `loadMore`, retry, state별 visibility, pagination error toast/consume, content layout key 변경 감지를 구현했다.
- 2026-06-20 검증: `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesFragmentLayoutTest"` PASS, `./gradlew --no-daemon :app:compileDebugKotlin` PASS.
---
### Phase 5: 채널 탭 연결과 Activity 통합
- [x] **Task 5.1: PagerAdapter RED 테스트 수정**
- 수정:
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapterTest.kt`
- 작업:
- `CreatorChannelTab.Series``CreatorChannelSeriesFragment`를 생성하는 테스트를 추가한다.
- 기존 placeholder 기대값에서 Series 탭을 제외한다.
- 검증 명령:
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelPagerAdapterTest"`
- 기대 결과:
- production 연결 전 RED 실패한다.
- 검증 기록:
- 2026-06-20 RED 확인: `CreatorChannelPagerAdapterTest`에 Series 탭이 `CreatorChannelSeriesFragment`를 생성해야 한다는 기대를 추가했고, production 연결 전 `CreatorChannelSeriesFragment` 미구현으로 컴파일 실패했다.
- 2026-06-20 GREEN 확인: PagerAdapter 연결 후 `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelPagerAdapterTest"` PASS.
- [x] **Task 5.2: PagerAdapter Series 연결**
- 수정:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapter.kt`
- 작업:
- `CreatorChannelTab.Series -> CreatorChannelSeriesFragment.newInstance(creatorId)` 분기를 추가한다.
- 검증 명령:
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelPagerAdapterTest"`
- 기대 결과:
- PagerAdapter 테스트가 GREEN이다.
- 검증 기록:
- 2026-06-20 구현: `CreatorChannelPagerAdapter.createFragment()``CreatorChannelTab.Series -> CreatorChannelSeriesFragment.newInstance(creatorId)` 분기를 추가했다.
- 2026-06-20 검증: `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelPagerAdapterTest"` PASS.
- [x] **Task 5.3: Activity source RED 테스트 수정**
- 수정:
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt`
- 작업:
- Activity가 `CreatorChannelSeriesFragment.Host`를 구현하는지 검증한다.
- Series 탭 선택 시 `onCreatorChannelSeriesTabSelected()`를 호출하는지 검증한다.
- Series 탭이 load-more 대상에 포함되는지 검증한다.
- `notifyCurrentCreatorChannelTabScrolledToBottom()`에서 Series fragment의 scroll bottom callback을 호출하는지 검증한다.
- `onCreatorChannelSeriesClicked(seriesId)``SeriesDetailActivity``Constants.EXTRA_SERIES_ID`를 사용하는지 검증한다.
- 검증 명령:
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"`
- 기대 결과:
- production 연결 전 RED 실패한다.
- 검증 기록:
- 2026-06-20 RED 확인: `CreatorChannelActivitySourceTest`에 Series Host, tab selected load, load-more, viewport, content changed, detail navigation 계약을 추가했고, production 연결 전 관련 source 문자열 미구현으로 실패하도록 고정했다.
- 2026-06-20 GREEN 확인: Activity 연결 후 `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"` PASS.
- [x] **Task 5.4: Activity Series 통합**
- 수정:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt`
- 작업:
- `CreatorChannelSeriesFragment.Host`를 구현한다.
- `findSeriesFragment()`를 추가한다.
- `onPageSelected`에서 Series 탭 선택 시 `onCreatorChannelSeriesTabSelected()`를 호출한다.
- `onCreatorChannelHeaderChanged`에서 현재 탭이 Series이면 owner 여부가 반영되도록 load를 호출한다.
- `notifyCurrentCreatorChannelTabScrolledToBottom()`에 Series load-more callback을 추가한다.
- `isCreatorChannelLoadMoreTab()`에 Series 탭을 추가한다.
- `updateCreatorChannelTabViewportHeight()`에 Series viewport callback을 추가한다.
- `onCreatorChannelSeriesContentChanged()`에서 `updateCreatorChannelTabViewportHeight()`, `updateViewPagerHeight()`, `postCheckCreatorChannelCurrentTabNeedsMore()`를 호출한다.
- `onCreatorChannelSeriesClicked(seriesId)`에서 `SeriesDetailActivity``Constants.EXTRA_SERIES_ID`를 전달한다.
- 기존 홈 탭의 `onCreatorChannelSeriesClicked(series: CreatorChannelSeriesResponse)`는 기존 동작을 유지한다.
- 검증 명령:
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"`
- `./gradlew :app:compileDebugKotlin`
- 기대 결과:
- Activity source 테스트와 Kotlin 컴파일이 PASS한다.
- 검증 기록:
- 2026-06-20 구현: `CreatorChannelActivity``CreatorChannelSeriesFragment.Host`를 구현하도록 연결하고, `findSeriesFragment()`, tab selected/header changed load, load-more 분기, viewport callback, content changed callback, `SeriesDetailActivity` 이동을 추가했다.
- 2026-06-20 검증: `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"` PASS, `./gradlew --no-daemon :app:compileDebugKotlin` PASS.
---
### Phase 6: 통합 검증과 회귀 확인
- [ ] **Task 6.1: 시리즈 탭 단위 테스트 실행**
- 실행:
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.series.*"`
- 기대 결과:
- 신규 series 패키지 테스트가 모두 PASS한다.
- 검증 기록:
- 구현 시 기록한다.
- [ ] **Task 6.2: 크리에이터 채널 관련 테스트 실행**
- 실행:
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Series*"`
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*"`
- 기대 결과:
- 시리즈 연결 테스트와 기존 홈/라이브/오디오 회귀 테스트가 PASS한다.
- 검증 기록:
- 구현 시 기록한다.
- [ ] **Task 6.3: 리소스/컴파일/스타일 검증**
- 실행:
- `./gradlew :app:mergeDebugResources`
- `./gradlew :app:compileDebugKotlin`
- `./gradlew :app:ktlintCheck`
- `git diff --check`
- 기대 결과:
- 리소스 병합, Kotlin 컴파일, ktlint, whitespace 검증이 모두 PASS한다.
- 검증 기록:
- 구현 시 기록한다.
- [ ] **Task 6.4: 수동 확인**
- 확인:
- 시리즈 탭 진입 시 `GET /api/v2/creator-channels/{creatorId}/series?page=0&size=20&sort=LATEST`가 호출된다.
- Sort-bar 좌측에 전체 시리즈 수가 표시된다.
- 정렬 팝업에는 `추천순` 없이 5개 옵션만 표시된다.
- 정렬 변경 시 첫 페이지부터 재조회된다.
- 시리즈 item 우측 `전체소장`/play button이 표시되지 않는다.
- 내 채널이 아닌 경우 progress count, percent, progress bar가 표시된다.
- 내 채널인 경우 progress count, percent, progress bar가 표시되지 않고 제목/부제 info만 표시된다.
- empty 상태에서 `크리에이터가 시리즈를 준비 중입니다.\n기대해 주세요!`가 표시된다.
- 영어/일본어 locale에서 empty 문구가 각 번역으로 표시된다.
- 목록 하단 스크롤 시 다음 page가 중복 없이 append된다.
- item 터치 시 `SeriesDetailActivity`로 이동하고 `seriesId`가 전달된다.
- 검증 기록:
- 구현 시 기록한다.
---
## Verification Log
- 구현 완료 후 여러 Phase에 걸친 통합 검증, 회귀 검증, 최종 수동 확인 기록을 이 섹션에 누적한다.
- 2026-06-20 코드 리뷰: Phase 2/3 범위의 API/DTO/Repository/ViewModel/mapper/UI model/test 코드를 검토했으며, 즉시 수정이 필요한 결함은 발견하지 못했다.
- 2026-06-20 검증: 최초 `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.series.*"` 실행은 sandbox의 `~/.gradle` lock 파일 접근 제한으로 실패했고, escalated 재실행 결과 PASS.
- 2026-06-20 검증: `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Series*"` PASS. 테스트 리포트 기준 `CreatorChannelSeriesViewModelTest` 8건, `CreatorChannelSeriesPaginationTest` 5건, `CreatorChannelSeriesMapperTest` 11건 모두 failures/errors/skipped 0.
- 2026-06-20 검증: `./gradlew --no-daemon :app:compileDebugKotlin` PASS.
- 2026-06-20 검증: `./gradlew --no-daemon :app:ktlintCheck` PASS.
- 2026-06-20 검증: `git diff --check` PASS.
- 2026-06-20 Phase 4/5 RED: 신규 `CreatorChannelSeriesFragmentLayoutTest`, `CreatorChannelPagerAdapterTest`, `CreatorChannelActivitySourceTest` 기대값 추가 후 production 미구현 상태에서 `CreatorChannelSeriesFragment`, 신규 layout/id 미존재로 테스트 컴파일 실패를 확인했다. 병렬 Gradle 실행 중 Kotlin cache lock/EOF 오류가 함께 발생해 이후 검증은 순차 실행으로 진행했다.
- 2026-06-20 Phase 4/5 구현: `CreatorChannelSeriesFragment`, `CreatorChannelSeriesAdapter`, fragment/item XML, 다국어 문자열, PagerAdapter Series 분기, Activity Series Host/load-more/viewport/navigation 연결을 추가했다.
- 2026-06-20 Phase 4/5 검증: `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesFragmentLayoutTest"` PASS.
- 2026-06-20 Phase 4/5 검증: `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelPagerAdapterTest"` PASS.
- 2026-06-20 Phase 4/5 검증: `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"` PASS.
- 2026-06-20 Phase 4/5 검증: `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.series.*"` PASS.
- 2026-06-20 Phase 4/5 검증: `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Series*"` PASS.
- 2026-06-20 Phase 4/5 검증: `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*"` PASS.
- 2026-06-20 Phase 4/5 검증: `./gradlew --no-daemon :app:mergeDebugResources` PASS, `./gradlew --no-daemon :app:compileDebugKotlin` PASS, `./gradlew --no-daemon :app:ktlintCheck` PASS, `git diff --check` PASS.
- 2026-06-20 후속 수정 검증: Series item subtitle의 locale 고정 문제를 해결하기 위해 mapper는 subtitle 조각 데이터만 생성하고 Adapter가 `creator_channel_series_subtitle_content_count`, `creator_channel_series_status_proceeding`, `creator_channel_series_status_completed` resource로 subtitle을 조합하도록 변경했다.
- 2026-06-20 후속 수정 검증: `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.series.*"` PASS.
- 2026-06-20 후속 수정 검증: `./gradlew --no-daemon :app:mergeDebugResources` PASS, `./gradlew --no-daemon :app:compileDebugKotlin` PASS, `./gradlew --no-daemon :app:ktlintCheck` PASS, `git diff --check` PASS.
- 2026-06-20 Phase 4/5 코드 리뷰: `CreatorChannelSeriesFragment`, `CreatorChannelSeriesAdapter`, fragment/item XML, PagerAdapter/Activity 연결부를 검토했다. 발견 사항: `CreatorChannelSeriesMappers.kt`에서 subtitle의 `총 n화`, `연재`, `완결` 문구가 Kotlin 상수/문자열로 고정되어 있어 Phase 4에서 추가한 영어/일본어 상태 문자열이 사용되지 않는다.
- 2026-06-20 Phase 4/5 재검증: sandbox 실행은 `~/.gradle` lock 접근 제한으로 실패했고, escalated 순차 실행으로 `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.series.*"`, `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Series*"`, `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*"`, `./gradlew --no-daemon :app:mergeDebugResources`, `./gradlew --no-daemon :app:compileDebugKotlin`, `./gradlew --no-daemon :app:ktlintCheck`, `git diff --check` 모두 PASS.
- 2026-06-20 Phase 4/5 코드 리뷰 및 검증: Figma `290:9031`, `290:9036`, `290:9038``CreatorChannelSeriesFragment`, `CreatorChannelSeriesAdapter`, fragment/item XML, PagerAdapter/Activity 연결부를 재검토했다. PRD에서 제거하기로 한 우측 `전체소장`/play 영역 제외는 의도된 차이로 확인했고, 즉시 수정이 필요한 결함은 발견하지 못했다.
- 2026-06-20 Phase 4/5 코드 리뷰 및 검증: 최초 `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.series.*"` sandbox 실행은 `~/.gradle` lock 접근 제한으로 실패했다. escalated 순차 실행으로 `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.series.*"`, `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Series*"`, `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*"`, `./gradlew --no-daemon :app:mergeDebugResources`, `./gradlew --no-daemon :app:compileDebugKotlin`, `./gradlew --no-daemon :app:ktlintCheck`, `git diff --check` 모두 PASS.