Files
sodalive-android/docs/20260617_크리에이터_채널_라이브_탭/plan-task.md

74 KiB

크리에이터 채널 라이브 탭 구현 계획/TASK

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

Goal: GET /api/v2/creator-channels/{creatorId}/live 응답을 기반으로 크리에이터 채널의 라이브 탭에 현재 라이브, 라이브 다시듣기 목록, 정렬, pagination, 본인 채널 전용 하단 라이브 시작하기 CTA를 표시한다.

Architecture: 기존 CreatorChannelActivityViewPager2/CreatorChannelPagerAdapter 구조를 유지하고, CreatorChannelTab.Live의 placeholder를 신규 CreatorChannelLiveFragment로 교체한다. 라이브 탭 전용 Fragment/ViewModel/mapper/UI model/adapter/popup은 kr.co.vividnext.sodalive.v2.creator.channel.live 하위에 둔다. API/Repository는 홈 탭과 라이브 탭이 함께 쓰는 채널 공통 계층으로 보고 기존 CreatorChannelHomeApi/CreatorChannelHomeRepositoryCreatorChannelApi/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
  • 첫 페이지 page0이다.
  • 다음 페이지는 마지막 성공 응답의 page + 1로 요청한다.
  • ContentSort 기본값은 LATEST이다.
  • isOwned == trueisRented == true가 동시에 내려오면 소장중을 우선 표시한다.
  • seriesName은 라이브 다시듣기 item에 표시하지 않는다.
  • isFirstContent, isOriginalSeries는 기존 오디오 item 정책과 동일하게 매핑한다.
  • currentLive == null이고 liveReplayContents.isEmpty()인 전체 empty 상태에서는 Sort-bar, 현재 라이브 카드, 라이브 다시듣기 리스트를 제거하고 중앙에 크리에이터가 라이브를 준비 중입니다.\n기대해 주세요! 문구를 표시한다.
  • 전체 empty 상태가 본인 채널에 표시되는 경우에도 empty 문구는 동일하게 표시하고, 하단 라이브 시작하기 CTA는 본인 채널 정책에 따라 계속 표시한다.
  • 같은 creatorIdloadLive()가 재호출되면 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: 기존 구조 확인과 작업 경계 고정

  • 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로 전달되는 기존 경로를 확인한다.
      • 라이브 시작 진입이 기존 CreatorChannelActivityonOwnerFabLiveClicked() 또는 LiveRoomCreateActivity 경로를 재사용할 수 있는지 확인한다.
    • 검증:
      • 라이브 탭 구현이 홈 탭 section adapter를 변경하지 않고 독립 Fragment로 추가 가능한지 기록한다.
    • 검증 기록:
      • 2026-06-17: CreatorChannelPagerAdapter는 현재 CreatorChannelTab.HomeCreatorChannelHomeFragment.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로 처리됨을 확인했다.
  • 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는 기존 홈 카드에서 durationseriesName을 조합하지만, 라이브 다시듣기 item에서는 PRD/계획 기준에 따라 seriesName을 표시하지 않는 것으로 재확인했다.
      • 2026-06-17: AudioContentTag의 top tag 순서는 Original, First, bottom tag 순서는 Point, Free로 정의되어 있음을 확인했다.
      • 2026-06-17: test -fapp/src/main/res/drawable-mdpi/ic_new_sort.png, ic_new_shield_small.png, ic_new_player_play.png, ic_new_create_live.png가 모두 존재함을 확인했다.
  • 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 계약 추가

  • 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 패키지에 둔다.
      • 기존 CreatorChannelAudioContentResponseisAdult, isOwned, isRented 필드를 추가할 때 홈 탭 API 하위 호환성이 깨지지 않는지 확인한다.
      • 하위 호환이 필요하면 라이브 탭 전용 DTO 분리 또는 nullable/default 정책을 테스트로 고정한다.
    • 검증 명령:
      • ./gradlew :app:compileDebugKotlin
    • 기대 결과:
      • DTO 추가 후 컴파일된다.
    • 검증 기록:
      • 2026-06-17: CreatorChannelHomeModels.ktCreatorChannelLiveTabResponseContentSort(LATEST, POPULAR, OWNED, PRICE_HIGH, PRICE_LOW)를 추가했다. 기존 홈 탭 테스트 fixture의 positional constructor 호환을 유지하기 위해 CreatorChannelAudioContentResponse의 라이브 탭 전용 isAdult, isOwned, isRented 필드는 기본값이 있는 trailing field로 추가했다.
      • 2026-06-17: ./gradlew :app:compileDebugKotlin 실행 결과 BUILD SUCCESSFUL로 통과했다.
  • 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
    • 기대 결과:
      • ContentSortv2/common/data/ContentSort.kt에 정의된다.
      • CreatorChannelLiveTabResponsev2/creator/channel/live/data/CreatorChannelLiveTabResponse.kt에 정의된다.
      • 모델 파일 분리 후 Kotlin 컴파일이 PASS한다.
    • 검증 기록:
      • 2026-06-17: 사용자 리뷰에 따라 CreatorChannelLiveTabResponseContentSortCreatorChannelHomeModels.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: 사용자 추가 지시에 따라 ContentSortv2.common.data.ContentSort, CreatorChannelLiveTabResponsev2.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가 남지 않았고, ContentSortv2.common.data, CreatorChannelLiveTabResponsev2.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로 통과했다.
  • 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를 전달한다.
      • sortContentSort.name 또는 Retrofit enum 변환의 기존 정책을 확인해 서버 값이 LATEST 등 대문자로 나가도록 한다.
    • 검증 명령:
      • ./gradlew :app:compileDebugKotlin
    • 기대 결과:
      • API/Repository가 기존 Koin graph와 충돌 없이 컴파일된다.
    • 검증 기록:
      • 2026-06-17: CreatorChannelApiGET /api/v2/creator-channels/{creatorId}/live endpoint를 추가하고 page, size, sort query와 Authorization header를 전달하도록 했다. CreatorChannelRepository.getLive()는 별도 live repository를 만들지 않고 기존 공통 repository에서 API를 얇게 위임한다.
      • 2026-06-17: sortContentSort enum query로 전달해 LATEST 등 enum name이 Retrofit query 값으로 사용되도록 했다.
      • 2026-06-17: ./gradlew :app:compileDebugKotlin 실행 결과 BUILD SUCCESSFUL로 통과했다.
  • 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: CreatorChannelLiveViewModelTestCreatorChannelLivePaginationTest를 추가했다. 최초 로딩 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: 리뷰 코멘트에 따라 같은 creatorIdloadLive()가 재호출되면 기존 목록/page/load-more 결과를 유지해야 한다는 테스트를 추가했다. production 보정 전 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveViewModelTest" 실행 결과 해당 테스트가 실패함을 확인했다.
  • 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: AppDICreatorChannelLiveViewModel(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 실패한다.
    • 검증 기록:
      • 2026-06-17: CreatorChannelLiveMapperTest를 추가해 isOwned && isRented 동시 true 시 소장중 우선, 무료 콘텐츠의 free tag/play CTA, 19금 shield, point tag, seriesName 미표시, isFirstContent/isOriginalSeries tag 정책, ContentSort label resource 매핑을 검증하도록 했다.
      • 2026-06-17: production mapper 구현 전 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveMapperTest" 실행 결과 CreatorChannelLiveReplayStatus, toReplayUiModel, toLabelResId 미구현으로 :app:compileDebugUnitTestKotlin FAILED가 발생해 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한다.
    • 검증 기록:
      • 2026-06-17: CreatorChannelLiveReplayUiModel, CreatorChannelLiveReplayStatus, CreatorChannelLiveSortOptionUiModel을 추가하고 CreatorChannelAudioContentResponse.toReplayUiModel(), ContentSort.toLabelResId(), ContentSort.toSortOptionUiModel()을 구현했다.
      • 2026-06-17: 가격/상태 우선순위는 소장중 > 대여중 > 무료 play > 가격 표시로 고정했다. tag는 기존 오디오 정책과 동일하게 isOriginalSeries == true이면 Original, isFirstContent이면 First, isPointAvailable이면 Point, price == 0이면 Free를 매핑한다. secondaryTextduration만 사용해 seriesName을 표시하지 않는다.
      • 2026-06-17: ContentSort label은 LATEST/POPULAR/PRICE_HIGH/PRICE_LOW에 기존 리소스 screen_audio_content_sort_newest, screen_audio_content_sort_popularity, screen_audio_content_sort_price_high, screen_audio_content_sort_price_low를 재사용하고, 기존 리소스가 없는 OWNED소장순 label은 creator_channel_live_sort_owned 문자열로 추가했다.
      • 2026-06-17: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveMapperTest" 실행 결과 BUILD SUCCESSFUL로 통과했다.
      • 2026-06-17: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.*" 실행 결과 BUILD SUCCESSFUL로 통과했다.
      • 2026-06-17: ./gradlew :app:compileDebugKotlin 실행 결과 BUILD SUCCESSFUL로 통과했다.
      • 2026-06-17: ./gradlew :app:ktlintCheck 실행 결과 BUILD SUCCESSFUL로 통과했다. 기존 .editorconfigdisabled_rules deprecation 경고는 출력되었으나 Phase 3 변경과 무관하다.
      • 2026-06-17: 리뷰 게이트에서 LATEST최신 콘텐츠, OWNED소장중 리소스에 매핑되는 문제가 지적되어 LATESTscreen_audio_content_sort_newest, OWNED는 신규 creator_channel_live_sort_owned로 보정했다.
      • 2026-06-17: label 보정 후 ./gradlew :app:mergeDebugResources PASS, ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveMapperTest" PASS, ./gradlew :app:compileDebugKotlin PASS, ./gradlew :app:ktlintCheck PASS. 병렬 실행 중 :app:dataBindingGenBaseClassesDebug가 Gradle 생성물 경합으로 R-def.txt 누락 실패했으나 동일 compile 명령 순차 재실행에서 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에 성공한다.
    • 검증 기록:
      • 2026-06-17: Figma 290:8949, 290:8950, 290:8954, 290:8959 기준으로 fragment_creator_channel_live.xmlitem_creator_channel_live_replay.xml을 추가했다. sort-bar는 52dp, current live card는 78dp pill, replay item은 88dp 썸네일/최대 2줄 title/1줄 duration/우측 action 영역으로 구성했다.
      • 2026-06-17: ic_new_sort, ic_new_shield_small, ic_new_player_play, ic_new_create_live는 기존 drawable을 재사용하고, live current/price/adult/retry 배경 drawable만 Phase 4 UI 표현에 필요한 최소 리소스로 추가했다.
      • 2026-06-17: creator_channel_live_total_label, empty/error/retry/owner CTA 문자열을 values, values-en, values-ja에 추가했다.
      • 2026-06-17: ./gradlew :app:mergeDebugResources 실행 결과 BUILD SUCCESSFUL로 통과했다.
  • 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가 컴파일된다.
    • 검증 기록:
      • 2026-06-17: CreatorChannelLiveFragment를 추가해 CreatorChannelLiveViewModel.liveStateLiveDataLoading, Empty, Error, Content 상태에 따라 sort-bar/current live/replay list/empty/error/retry visibility를 갱신하도록 했다.
      • 2026-06-17: error 상태 retry 버튼은 CreatorChannelLiveViewModel.retryLive()를 호출한다. RecyclerView 하단 접근 시 loadMore()를 호출하며 중복/hasNext guard는 ViewModel 정책을 따른다.
      • 2026-06-17: CreatorChannelLiveReplayAdapter를 추가해 mapper의 CreatorChannelLiveReplayUiModel을 19금 shield, original/first/point/free tag, play/소장중/대여중/가격 상태로 바인딩한다.
      • 2026-06-17: replay item click은 CreatorChannelLiveFragment.Host.onCreatorChannelLiveReplayClicked(audioContentId)로 위임하고, CreatorChannelActivity에서 기존 AudioContentDetailActivity 진입 경로를 재사용하도록 연결했다. current live card click은 기존 홈 탭과 동일하게 onCreatorChannelCurrentLiveClicked(live)를 재사용한다.
      • 2026-06-17: Phase 5 범위인 sort popup/정렬 변경 동작은 구현하지 않고, Phase 4에서는 sort-bar 표시와 icon 준비까지만 유지했다.
      • 2026-06-17: ./gradlew :app:compileDebugKotlin 실행 결과 BUILD SUCCESSFUL로 통과했다. 병렬 Gradle 실행 중 Kotlin incremental cache/daemon 경합 로그가 출력되었으나, fallback/최종 실행은 성공했다.
  • 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한다.
    • 검증 기록:
      • 2026-06-17: production layout/code 추가 전 CreatorChannelLiveFragmentLayoutTest를 먼저 작성했다. RED 실행 결과 fragment_creator_channel_live, item_creator_channel_live_replay 및 필수 id 미존재로 :app:compileDebugUnitTestKotlin FAILED가 발생해 실패를 확인했다.
      • 2026-06-17: layout/source 구현 후 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveFragmentLayoutTest" 실행 결과 BUILD SUCCESSFUL로 통과했다.
      • 2026-06-17: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.*" 실행 결과 BUILD SUCCESSFUL로 통과해 Phase 4 layout 테스트와 기존 live ViewModel/mapper/pagination 테스트 회귀를 함께 확인했다.

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에 정렬 변경을 전달한다.
    • 검증:
      • 작은 화면에서 메뉴가 화면 밖으로 벗어나지 않도록 위치 보정 기준을 코드에 남긴다.
    • 검증 기록:
      • 2026-06-17: Figma 290:9041 기준으로 view_creator_channel_live_sort_menu.xml, bg_creator_channel_live_sort_popup.xml, bg_creator_channel_live_sort_selected.xml을 추가했다. Popup 배경은 gray_900 fill, gray_700 1dp stroke, radius_14로 구성하고, 선택 row는 gray_800 배경으로 표시한다.
      • 2026-06-17: CreatorChannelLiveSortPopup을 추가해 ContentSort.entries와 기존 toSortOptionUiModel(selectedSort)/toLabelResId() 매핑을 재사용하도록 했다. PopupWindowisOutsideTouchable = true, isFocusable = true로 외부 터치 dismiss를 지원하고, 같은 정렬 선택은 dismiss만 수행하며 새 정렬 선택은 viewModel.changeSort(sort)로 전달한다.
      • 2026-06-17: 작은 화면에서 popup 오른쪽이 visible display frame 밖으로 나가는 경우 calculateHorizontalOffset()으로 음수 x offset을 계산해 showAsDropDown()에 전달하도록 위치 보정 기준을 코드에 남겼다.
      • 2026-06-17: CreatorChannelLiveFragmentlayoutCreatorChannelLiveSortButton 클릭 시 현재 Content 상태의 selectedSort로 popup을 표시하고, onDestroyView()에서 sortPopup?.dismiss()로 window leak을 방지한다.
  • 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한다.
    • 검증 기록:
      • 2026-06-17: production 구현 전 CreatorChannelLiveFragmentLayoutTest에 popup layout/resource/source wiring 테스트를 먼저 추가했다. RED 실행 결과 view_creator_channel_live_sort_menu, layout_creator_channel_live_sort_options 미존재로 :app:compileDebugUnitTestKotlin FAILED가 발생해 실패를 확인했다.
      • 2026-06-17: 기존 CreatorChannelLiveMapperTestContentSort별 label resource 매핑을 이미 검증하고, 기존 CreatorChannelLiveViewModelTest는 정렬 변경 첫 페이지 재로딩 및 같은 정렬 재선택 no-op을 이미 검증하고 있어 중복 테스트를 추가하지 않았다.
      • 2026-06-17: 구현 후 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveFragmentLayoutTest" 실행 결과 BUILD SUCCESSFUL로 통과했다.
      • 2026-06-17: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.*", ./gradlew :app:mergeDebugResources, ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck 실행 결과 모두 BUILD SUCCESSFUL로 통과했다. 기존 .editorconfigdisabled_rules deprecation 경고와 기존 Kotlin deprecation/annotation 경고는 Phase 5 변경과 무관하다.

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 정책은 유지된다.
    • 검증 기록:
      • 2026-06-17: 리뷰 게이트 차단 이슈 보정으로 CreatorChannelPagerAdapter에서 CreatorChannelTab.Live -> CreatorChannelLiveFragment.newInstance(creatorId) 분기를 추가했다. Home/Live 탭은 실제 Fragment로 연결하고, 후속 탭은 기존 placeholder 정책을 유지한다.
      • 2026-06-17: 보정 후 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" PASS, ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Live*" PASS, ./gradlew :app:compileDebugKotlin PASS, ./gradlew :app:ktlintCheck PASS.
  • 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 아래에 한국어로 누적 기록한다.

Phase 4 Review Fix: Lazy Load와 Empty/Error 중앙 정렬 보정

  • Task RF4.1: Live 탭 선택 시점 lazy load 보정

    • 수정:
      • 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
    • 작업:
      • Live Fragment 생성 시점인 onViewCreated()에서는 loadLive()를 호출하지 않는다.
      • CreatorChannelTab.Live가 선택된 시점에만 Live Fragment에 명시적으로 로딩을 요청한다.
      • 같은 creatorId/state에 대한 중복 호출 방지는 기존 CreatorChannelLiveViewModel.loadLive() guard를 유지한다.
    • 검증 명령:
      • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"
      • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveFragmentLayoutTest"
    • 기대 결과:
      • Live 탭을 보지 않은 Activity 최초 진입에서는 Live API 요청이 시작되지 않고, Live 탭 선택 시점에 첫 조회가 시작된다.
    • 검증 기록:
      • 2026-06-17: production 보정 전 source 테스트에서 offscreenPageLimit = CreatorChannelTab.entries.size - 1, onViewCreated() 즉시 viewModel.loadLive(creatorId) 호출, Live 선택 hook 미존재로 RED 실패를 확인했다.
      • 2026-06-17: CreatorChannelLiveFragment.onViewCreated()의 즉시 loadLive() 호출을 제거하고 onCreatorChannelLiveTabSelected()로 이동했다. CreatorChannelActivity.onPageSelected()는 Live 탭 선택 시 viewPager.post { findLiveFragment()?.onCreatorChannelLiveTabSelected() }를 호출하며, 전체 탭 선생성을 유발하던 offscreenPageLimit = CreatorChannelTab.entries.size - 1 설정을 제거했다.
      • 2026-06-17: 보정 후 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" PASS, ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveFragmentLayoutTest" PASS.
  • Task RF4.2: Empty/Error 상태 viewport 중앙 정렬 보정

    • 수정:
      • app/src/main/res/layout/fragment_creator_channel_live.xml
      • 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
    • 작업:
      • Live Fragment root가 ViewPager2 page 높이를 채우도록 보장한다.
      • Empty/Error 상태 전환 시에도 ViewPager2 높이 재측정을 요청한다.
      • Activity의 updateViewPagerHeight()는 현재 page의 최소 높이를 탭 viewport 기준으로 보정한다.
    • 검증 명령:
      • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"
      • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveFragmentLayoutTest"
      • ./gradlew :app:mergeDebugResources
    • 기대 결과:
      • Live Empty 문구와 Error/Retry 그룹이 작은 wrap-content 영역이 아니라 탭 viewport 중앙에 표시된다.
    • 검증 기록:
      • 2026-06-17: production 보정 전 layout/source 테스트에서 Live root wrap_content, Empty/Error 상태 height callback 미보장, Activity page minimumHeight 보정 미존재로 RED 실패를 확인했다.
      • 2026-06-17: fragment_creator_channel_live.xml root 높이를 match_parent로 변경하고, bindEmpty()/bindError()에서도 host.onCreatorChannelLiveContentChanged()를 호출하도록 했다. CreatorChannelActivity.updateViewPagerHeight()는 현재 page 측정 전 currentPage.minimumHeight = calculateCreatorChannelTabViewportHeight()를 적용해 탭 viewport 기준 최소 높이를 보장한다.
      • 2026-06-17: 보정 후 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" PASS, ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveFragmentLayoutTest" PASS, ./gradlew :app:mergeDebugResources PASS.
  • Task RF4.3: Home delegate 보존과 load-more 메타데이터 보존 보정

    • 수정:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveViewModel.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLivePaginationTest.kt
    • 작업:
      • Live lazy load는 유지하되 Home 탭의 HomeActionDelegate가 후속 탭 이동 중 제거되지 않도록 ViewPager2.offscreenPageLimit을 기존처럼 전체 탭 수 기준으로 복구한다.
      • 다음 페이지 성공 시 기존 첫 페이지의 currentLive, liveReplayContentCount, selectedSort를 보존하고, 목록 append와 page/size/hasNext만 다음 응답 기준으로 갱신한다.
    • 검증 명령:
      • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"
      • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLivePaginationTest"
      • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Live*"
      • ./gradlew :app:compileDebugKotlin
    • 기대 결과:
      • Home 탭 delegate 기반 상단 액션/refresh 경로가 Live 및 후속 탭 이동 후에도 유지된다.
      • 다음 페이지 응답이 currentLive == null이거나 총 개수가 부분 값이어도 현재 라이브 카드와 총 개수/정렬 label이 첫 페이지 상태를 유지한다.
    • 검증 기록:
      • 2026-06-17: CreatorChannelActivity.setupTabsAndPager()binding.viewPager.offscreenPageLimit = CreatorChannelTab.entries.size - 1를 복구해 Home 탭 Fragment와 HomeActionDelegate가 후속 탭 이동 중 제거되지 않도록 했다. CreatorChannelActivitySourceTest의 기존 offscreenPageLimit 제거 기대는 복구 정책에 맞춰 갱신했다.
      • 2026-06-17: CreatorChannelLiveViewModel.loadMore() 성공 처리에서 data.toContentState()로 전체 content metadata를 덮어쓰지 않고, 기존 Contentcopy()liveReplayContents append와 page/size/hasNext만 다음 응답 기준으로 갱신하도록 변경했다.
      • 2026-06-17: CreatorChannelLivePaginationTest에 다음 페이지 응답이 currentLive == null, 다른 liveReplayContentCount, 다른 sort를 내려줘도 첫 페이지의 현재 라이브/count/sort를 보존하는 회귀 테스트를 추가했다.
      • 2026-06-17: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" 최초 실행은 상충하던 기존 offscreenPageLimit 제거 assertion으로 실패했고, 테스트 계약 갱신 후 재실행 결과 BUILD SUCCESSFUL로 통과했다.
      • 2026-06-17: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLivePaginationTest" 최초 실행은 신규 테스트 fixture의 CreatorChannelLiveResponse 필드명 오기로 컴파일 실패했고, coverImageUrl로 수정 후 재실행 결과 BUILD SUCCESSFUL로 통과했다.
      • 2026-06-17: 추가 회귀 검증으로 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Live*", ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck를 실행했고 모두 BUILD SUCCESSFUL로 통과했다. ktlintCheck에서는 기존 .editorconfigdisabled_rules deprecation 경고가 계속 출력되었다.

Verification Log

  • 2026-06-17: Phase 1 진행. CreatorChannelHomeApi/CreatorChannelHomeRepositoryCreatorChannelApi/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: 같은 creatorIdloadLive() 재호출 상태 보존 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.xmlic_new_talk/ic_new_dm 기대, item_creator_channel_home_donation.xml196dp 기대, item_creator_channel_home_series_content.xmlandroid: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개 실패가 재현됐다.
  • 2026-06-17: Phase 3 진행. 라이브 다시듣기 UI model/mapper와 mapper RED/GREEN 테스트를 추가했다. Fragment/layout/sort popup/tab 연결은 Phase 4 이후 범위라 변경하지 않았다.
  • 2026-06-17: Phase 3 RED 확인. production mapper 구현 전 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveMapperTest" 실행 시 CreatorChannelLiveReplayStatus, toReplayUiModel, toLabelResId 미구현으로 :app:compileDebugUnitTestKotlin FAILED가 발생했다.
  • 2026-06-17: Phase 3 GREEN 확인. ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveMapperTest" PASS.
  • 2026-06-17: Phase 3 회귀 검증. ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.*" PASS, ./gradlew :app:compileDebugKotlin PASS, ./gradlew :app:ktlintCheck PASS. ktlint 실행 중 기존 .editorconfigdisabled_rules deprecation 경고가 출력되었다.
  • 2026-06-17: Phase 3 리뷰 게이트 보정. LATEST label은 최신순 리소스 screen_audio_content_sort_newest로, OWNED label은 신규 다국어 리소스 creator_channel_live_sort_owned로 변경했다. 보정 후 ./gradlew :app:mergeDebugResources PASS, ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveMapperTest" PASS, ./gradlew :app:compileDebugKotlin PASS, ./gradlew :app:ktlintCheck PASS.
  • 2026-06-17: Phase 4 진행. CreatorChannelLiveFragment, CreatorChannelLiveReplayAdapter, fragment_creator_channel_live.xml, item_creator_channel_live_replay.xml, 최소 배경 drawable/string, layout RED/GREEN 테스트를 추가했다. Sort popup/정렬 변경 동작과 owner CTA 실제 노출/inset/라이브 시작 연결은 Phase 5/6 범위로 남겼다.
  • 2026-06-17: Phase 4 RED 확인. production layout/code 추가 전 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveFragmentLayoutTest" 실행 시 신규 layout/id/source 미존재로 :app:compileDebugUnitTestKotlin FAILED가 발생했다.
  • 2026-06-17: Phase 4 GREEN 확인. ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveFragmentLayoutTest" PASS.
  • 2026-06-17: Phase 4 회귀 검증. ./gradlew :app:mergeDebugResources PASS, ./gradlew :app:compileDebugKotlin PASS, ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.*" PASS, ./gradlew :app:ktlintCheck PASS. Gradle 병렬 실행 중 Kotlin daemon/incremental cache 경합 로그가 일부 출력되었으나 fallback/최종 결과는 BUILD SUCCESSFUL이었다. 기존 .editorconfigdisabled_rules deprecation 경고와 Kotlin/Android deprecation 경고는 변경 범위와 무관하게 계속 출력된다.
  • 2026-06-17: Phase 4 리뷰 보정 진행. NestedScrollView가 스크롤을 소유하는 구조에서 Live pagination을 Fragment 내부 RecyclerView.OnScrollListener가 아니라 CreatorChannelActivity의 parent scroll bottom detection으로 전달하도록 보정했다. Live content bind 후 onCreatorChannelLiveContentChanged()ViewPager2 높이 재측정을 요청하고, 현재 라이브 시간은 홈 탭의 formatCreatorChannelLiveDateTime()을 재사용하도록 변경했다. Owned/Rented 다시듣기 item은 PRD 요구대로 play icon과 상태 텍스트를 함께 표시한다.
  • 2026-06-17: Phase 4 리뷰 보정 RED 확인. production 보정 전 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveFragmentLayoutTest"는 신규 Live fragment source 계약 미충족으로 FAIL, ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"는 신규 NestedScrollView pagination/height callback 계약 미충족으로 FAIL했다. 같은 실행에서 기존 오디오 상세 이동 source assertion도 startAudioContentDetail() helper 추출 이후의 현재 코드와 맞지 않아 테스트 계약만 갱신했다.
  • 2026-06-17: Phase 4 리뷰 보정 GREEN 확인. 병렬 Gradle 실행 중 Kotlin daemon/incremental cache 경합으로 Constants.class 누락 및 fallback timeout이 발생했으나, daemon 정리 후 순차 재실행한 ./gradlew :app:compileDebugKotlinBUILD SUCCESSFUL로 통과했다. 이어서 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveFragmentLayoutTest" PASS, ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" PASS.
  • 2026-06-17: Phase 4 리뷰 보정 최종 검증. ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.*" PASS, ./gradlew :app:mergeDebugResources PASS, ./gradlew :app:ktlintCheck PASS, ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Live*" PASS. ktlintCheck에서는 기존 .editorconfigdisabled_rules deprecation 경고가 계속 출력되었으나 실패 원인은 아니었다.
  • 2026-06-17: Phase 4 리뷰 게이트에서 CreatorChannelPagerAdapter가 아직 Live 탭을 CreatorChannelPlaceholderFragment로 생성해 Live 보정 코드가 실제 사용자 경로에서 실행되지 않는다는 차단 이슈가 발견됐다. 최소 보정으로 CreatorChannelTab.Live -> CreatorChannelLiveFragment.newInstance(creatorId) 분기를 추가하고, 기존 placeholder-only source test를 홈/라이브 실제 Fragment + 후속 탭 placeholder 계약으로 갱신했다.
  • 2026-06-17: Live pager 연결 보정 후 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" PASS, ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Live*" PASS, ./gradlew :app:compileDebugKotlin PASS, ./gradlew :app:ktlintCheck PASS. ktlintCheck에서는 기존 .editorconfigdisabled_rules deprecation 경고가 계속 출력되었다.
  • 2026-06-17: 리뷰 지적 2건 보정. Live 탭 API는 Live 탭 선택 시점 lazy load로 변경했고, Empty/Error는 Live page root와 Activity page minimumHeight 보정으로 탭 viewport 중앙 정렬을 보장했다. 최종 검증으로 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" PASS, ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveFragmentLayoutTest" PASS, ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Live*" PASS, ./gradlew :app:mergeDebugResources PASS, ./gradlew :app:compileDebugKotlin PASS, ./gradlew :app:ktlintCheck PASS. 초기 병렬 RED 확인 중 Kotlin incremental cache 경합 로그가 출력됐으나 daemon 정리 후 순차 검증은 모두 통과했다.
  • 2026-06-17: 추가 리뷰 지적 2건 보정. ViewPager2.offscreenPageLimit을 복구해 Home 탭의 HomeActionDelegate 기반 상단 액션/refresh 경로를 유지하고, Live load-more 성공 시 첫 페이지의 current live/count/sort metadata를 보존하도록 변경했다. 검증으로 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" PASS, ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLivePaginationTest" PASS, ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Live*" PASS, ./gradlew :app:compileDebugKotlin PASS, ./gradlew :app:ktlintCheck PASS. ktlintCheck에서는 기존 .editorconfigdisabled_rules deprecation 경고가 계속 출력되었다.
  • 2026-06-17: Phase 5 코드 리뷰 및 검증. Figma 290:9041 컨텍스트 메뉴와 스크린샷을 재확인해 gray_900 배경, gray_700 stroke, radius_14, 선택 row gray_800, 16sp medium, 12/8 padding 기준이 구현에 반영됐음을 확인했다. PRD/계획의 서버 정렬 계약은 ContentSort 5개 옵션이므로 Figma의 추천순 row는 이번 범위에서 제외했다.
  • 2026-06-17: Phase 5 코드 리뷰에서 수정 필요 결함은 발견하지 못했다. CreatorChannelLiveSortPopup은 외부 터치 dismiss, 같은 정렬 재선택 dismiss-only, 새 정렬 선택 시 viewModel.changeSort(sort) 전달, 우측 화면 밖 보정, onDestroyView() dismiss 경로를 갖는다.
  • 2026-06-17: Phase 5 검증으로 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveFragmentLayoutTest" PASS, ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.*" PASS, ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Live*" PASS, ./gradlew :app:mergeDebugResources PASS, ./gradlew :app:compileDebugKotlin PASS, ./gradlew :app:ktlintCheck PASS를 확인했다.
  • 2026-06-17: 추가 상위 회귀 검증 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*" 최초 실행은 drawable 3개 추가 후 resource id 캐시가 맞지 않아 CreatorChannelHomeMapperTest, CreatorChannelTitleBarStateTest의 drawable id assertion이 3씩 밀려 실패했다. 해당 클래스들을 --rerun-tasks로 강제 재컴파일하자 통과했고, 이후 동일 채널 전체 회귀 명령을 순차 재실행해 BUILD SUCCESSFUL로 통과했다. 병렬 --rerun-tasks 중 Kotlin incremental cache 경합 로그가 출력됐으나 최종 순차 검증은 통과했다.