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

32 KiB
Raw Blame History

크리에이터 채널 시리즈 탭 구현 계획/TASK

For agentic workers: 각 단계는 체크박스(- [ ])로 추적하고, 완료 즉시 - [x]로 갱신한다. 구현 범위 변경이 생기면 이 문서를 먼저 수정한 뒤 코드에 반영한다.

Goal: GET /api/v2/creator-channels/{creatorId}/series 응답을 기반으로 크리에이터 채널의 시리즈 탭에 정렬, 시리즈 목록, 조건부 소장 진행 정보, empty 상태와 pagination을 표시한다.

Architecture: 기존 CreatorChannelActivityViewPager2/CreatorChannelPagerAdapter 구조를 유지하고, CreatorChannelTab.Series의 placeholder를 신규 CreatorChannelSeriesFragment로 교체한다. 시리즈 탭 전용 Fragment/ViewModel/DTO/mapper/adapter는 kr.co.vividnext.sodalive.v2.creator.channel.series 하위에 두되, API/Repository는 기존 채널 공통 CreatorChannelApi/CreatorChannelRepository에 endpoint만 추가한다. 정렬 UI는 오디오 탭에서 사용하는 CreatorChannelSortPopupContentSort.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이다.
  • 첫 페이지 page0, 기본 size20, 기본 sortContentSort.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: 기존 구조 확인과 작업 경계 고정

  • 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 패턴이 확인된다.
    • 검증 기록:
      • 구현 시 기록한다.
  • 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)SeriesDetailActivityConstants.EXTRA_SERIES_ID를 전달하는지 확인한다.
      • 시리즈 탭 item 클릭은 신규 경로를 만들지 않고 같은 Activity method를 재사용한다.
    • 검증:
      • rg -n "SeriesDetailActivity|EXTRA_SERIES_ID|onSeriesClicked" app/src/main/java
      • 기대 결과: 기존 시리즈 상세 진입점이 확인된다.
    • 검증 기록:
      • 구현 시 기록한다.
  • 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 탭 연결 테스트를 추가할 준비가 되었는지 기록한다.
    • 검증 기록:
      • 구현 시 기록한다.
  • 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 재사용 경계가 확인된다.
    • 검증 기록:
      • 구현 시 기록한다.

Phase 2: API/DTO/Repository/ViewModel 계약 추가

  • 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한다.
    • 검증 기록:
      • 구현 시 기록한다.
  • 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와 충돌 없이 컴파일된다.
    • 검증 기록:
      • 구현 시 기록한다.
  • 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 실패한다.
    • 검증 기록:
      • 구현 시 기록한다.
  • 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이다.
    • 검증 기록:
      • 구현 시 기록한다.

Phase 3: Mapper/UI model 계약 추가

  • 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 실패한다.
    • 검증 기록:
      • 구현 시 기록한다.
  • 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이다.
    • 검증 기록:
      • 구현 시 기록한다.

Phase 4: Fragment, Adapter, XML UI 구현

  • 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 실패한다.
    • 검증 기록:
      • 구현 시 기록한다.
  • 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한다.
    • 검증 기록:
      • 구현 시 기록한다.
  • 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이 생성된다.
    • 검증 기록:
      • 구현 시 기록한다.
  • 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이 생성된다.
    • 검증 기록:
      • 구현 시 기록한다.
  • 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한다.
    • 검증 기록:
      • 구현 시 기록한다.
  • 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한다.
    • 검증 기록:
      • 구현 시 기록한다.

Phase 5: 채널 탭 연결과 Activity 통합

  • Task 5.1: PagerAdapter RED 테스트 수정

    • 수정:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapterTest.kt
    • 작업:
      • CreatorChannelTab.SeriesCreatorChannelSeriesFragment를 생성하는 테스트를 추가한다.
      • 기존 placeholder 기대값에서 Series 탭을 제외한다.
    • 검증 명령:
      • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelPagerAdapterTest"
    • 기대 결과:
      • production 연결 전 RED 실패한다.
    • 검증 기록:
      • 구현 시 기록한다.
  • 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이다.
    • 검증 기록:
      • 구현 시 기록한다.
  • 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)SeriesDetailActivityConstants.EXTRA_SERIES_ID를 사용하는지 검증한다.
    • 검증 명령:
      • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"
    • 기대 결과:
      • production 연결 전 RED 실패한다.
    • 검증 기록:
      • 구현 시 기록한다.
  • 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)에서 SeriesDetailActivityConstants.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한다.
    • 검증 기록:
      • 구현 시 기록한다.

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에 걸친 통합 검증, 회귀 검증, 최종 수동 확인 기록을 이 섹션에 누적한다.