Files
sodalive-android/docs/20260627_콘텐츠_전체보기/plan-task.md

40 KiB

콘텐츠 전체보기 구현 계획/TASK

For agentic workers: REQUIRED SUB-SKILL: 구현 시 superpowers:subagent-driven-development 또는 superpowers:executing-plans를 사용해 task 단위로 진행한다. 각 단계는 체크박스(- [ ])로 추적하고, 완료 즉시 - [x]로 갱신한다. 구현 범위 변경이 생기면 이 문서를 먼저 수정한 뒤 코드에 반영한다.

Goal: 홈/콘텐츠 추천 섹션의 전체보기 chevron을 연결하고, 기존 콘텐츠 전체 탭 이동을 먼저 적용한 뒤 New&Hot처음부터 함께 성장! 전용 신규 전체보기 화면을 제공한다.

Architecture: 구현 순서는 chevron 표시 -> 기존 콘텐츠 전체 탭 이동 -> 신규 전체보기 페이지/API로 고정한다. 기존 화면 이동은 MainV2Activity, ContentMainFragment, ContentAllTabViewModel에 작은 public navigation contract를 추가해 처리한다. 신규 전체보기 화면은 마지막 단계에서 kr.co.vividnext.sodalive.v2.main.content.overview 하위 Activity/ViewModel/API/Repository/DTO/UI model/adapter로 격리한다.

Tech Stack: Kotlin, Android XML Views, ViewBinding, RecyclerView, Retrofit, Gson, RxJava3, Koin, JUnit4/Robolectric local unit test.


전제와 성공 기준

  • PRD: docs/20260627_콘텐츠_전체보기/prd.md
  • 구현 순서:
    • 1순위: 홈/콘텐츠 추천 섹션 chevron 표시.
    • 2순위: 신규 페이지가 필요 없는 섹션을 기존 콘텐츠 전체 탭으로 이동.
    • 3순위: 신규 콘텐츠 전체보기 페이지/API 생성 및 신규 페이지 대상 섹션 이동.
  • 전체보기 chevron 표시 섹션:
    • 홈 추천 탭: 처음부터 함께 성장!
    • 콘텐츠 추천 탭: 오직 보이스온에서만!, 새로 올라온 오디오, New&Hot, 무료 오디오, 포인트 오디오
  • 기존 콘텐츠 전체 탭 이동 섹션:
    • 오직 보이스온에서만! -> 콘텐츠 탭 전체 -> 오리지널 카테고리
    • 새로 올라온 오디오 -> 콘텐츠 탭 전체 -> 오디오 카테고리
    • 무료 오디오 -> 콘텐츠 탭 전체 -> 무료 카테고리 -> 인기순
    • 포인트 오디오 -> 콘텐츠 탭 전체 -> 포인트 카테고리 -> 인기순
  • 신규 전체보기 페이지 이동 섹션:
    • 홈 추천 탭 처음부터 함께 성장! -> 신규 콘텐츠 전체보기 -> FIRST_AUDIO_CONTENT
    • 콘텐츠 추천 탭 New&Hot -> 신규 콘텐츠 전체보기 -> NEW_AND_HOT_AUDIO
  • 댓글 많은 오디오, 추천 오디오에는 chevron을 표시하지 않는다.
  • 신규 API endpoint는 GET /api/v2/contents이다.
  • 신규 API query parameter key는 소문자 page, size, type만 사용한다.
  • 신규 API 기본값은 page=0, size=20, type=NEW_AND_HOT_AUDIO다.
  • 앱 구현 DTO annotation은 Gson @SerializedName을 사용한다.
  • 신규 전체보기 화면 title은 NEW_AND_HOT_AUDIO일 때 New&Hot, FIRST_AUDIO_CONTENT일 때 처음부터 함께 성장!이다.
  • 신규 전체보기 화면은 Figma node 482:15105 기준 2열 오디오 카드 grid를 사용한다.
  • 구현 완료 후 최소 다음 명령을 실행한다.
    • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.*"
    • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*"
    • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.overview.*"
    • ./gradlew :app:mergeDebugResources
    • ./gradlew :app:compileDebugKotlin
    • ./gradlew :app:ktlintCheck
    • git diff --check

Phase 순서

  • Phase 1: 기존 구조 확인과 작업 경계 고정
  • Phase 2: 홈/콘텐츠 추천 섹션 chevron 표시
  • Phase 3: 기존 콘텐츠 전체 탭 이동 contract 추가
  • Phase 4: 기존 콘텐츠 전체 탭 이동 섹션 연결
  • Phase 5: 신규 전체보기 API, DTO, Repository, UI model 추가
  • Phase 6: 신규 전체보기 ViewModel, Activity, Adapter 구현
  • Phase 7: 신규 전체보기 대상 섹션 이동 연결
  • Phase 8: 통합 검증과 수동 확인

Figma 참조 필요 Phase

  • Phase 1: 제한 참조
    • 기존 홈/콘텐츠 추천 섹션 title, 콘텐츠 전체 탭, MainV2 navigation 구조 확인 중심으로 진행한다.
  • Phase 2: 제한 참조
    • chevron 표시는 기존 view_section_title.xml과 홈/콘텐츠 추천 섹션 구조를 따른다.
  • Phase 3~4: Figma 참조 불필요
    • 기존 콘텐츠 전체 탭 이동은 기존 ContentAllTabViewModel의 type/sort 계약과 MainV2 tab 전환 구조를 따른다.
  • Phase 5: Figma 참조 불필요
    • API/DTO/Repository/UI model/mapper/ViewModel은 PRD 서버 계약과 기존 V2 data layer/paging 패턴을 따른다.
  • Phase 6: 필수 참조
    • 신규 전체보기 Activity layout은 Figma 482:15105의 title bar, black background, 2열 grid, card spacing을 기준으로 확인한다.
  • Phase 7~8: 필수 참조
    • 신규 페이지 대상 섹션 이동과 최종 수동 화면 검증은 PRD의 Figma reference와 실제 화면을 대조한다.

파일 구조

기존 화면 이동 선행 범위

  • Modify: app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt
    • 처음부터 함께 성장! title chevron을 먼저 표시한다. 신규 페이지 이동은 Phase 7에서 연결한다.
  • Modify: app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt
    • 콘텐츠 추천 섹션 chevron 표시, 기존 콘텐츠 전체 탭 이동, 신규 페이지 이동을 단계별로 추가한다.
  • Modify: app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentAllTabViewModel.kt
    • 외부 진입용 type/sort 선택 API를 추가한다.
  • Modify: app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt
    • 콘텐츠 탭 내부 전체를 특정 type/sort로 여는 public method와 기존 fragment 재사용 처리를 추가한다.
  • Modify: app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragmentSourceTest.kt
    • 콘텐츠 추천 섹션 chevron/routing/source를 검증한다.
  • Modify: app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/ContentAllTabViewModelTest.kt
    • 외부 type/sort 선택 API를 검증한다.
  • Create: app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentSourceTest.kt
    • 처음부터 함께 성장! chevron/source를 검증한다.
  • Modify: app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2ActivitySourceTest.kt
    • 콘텐츠 탭 내부 전체 type/sort routing contract를 검증한다.

신규 전체보기 후행 범위

  • Create: app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/overview/data/ContentOverviewApi.kt
    • GET /api/v2/contents Retrofit endpoint를 정의한다.
  • Create: app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/overview/data/ContentOverviewModels.kt
    • ContentOverviewPageResponse, ContentOverviewType, ContentOverviewItemResponse DTO를 정의한다.
  • Create: app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/overview/data/ContentOverviewRepository.kt
    • API 호출을 repository method로 감싼다.
  • Create: app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/overview/model/ContentOverviewUiState.kt
    • Loading, Content, Empty, Error 상태와 paging/loading-more 상태를 정의한다.
  • Create: app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/overview/model/ContentOverviewUiModels.kt
    • 신규 전체보기 화면 item/title/type UI model을 정의한다.
  • Create: app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/overview/model/ContentOverviewMappers.kt
    • DTO를 UI model로 변환하고 AudioContentTag를 매핑한다.
  • Create: app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/overview/ContentOverviewViewModel.kt
    • type/page/hasNext/loading-more/API 상태를 관리한다.
  • Create: app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/overview/ContentOverviewActivity.kt
    • title bar, 2열 RecyclerView, paging, empty/error/loading, 상세 routing을 연결한다.
  • Create: app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/overview/ui/ContentOverviewAdapter.kt
    • AudioContentCardView 기반 2열 grid item을 바인딩한다.
  • Create: app/src/main/res/layout/activity_content_overview.xml
    • 신규 전체보기 화면 layout이다.
  • Modify: app/src/main/AndroidManifest.xml
    • ContentOverviewActivity를 등록한다.
  • Modify: app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt
    • 신규 API, Repository, ViewModel을 Koin에 등록한다.
  • Create: app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/overview/ContentOverviewMapperTest.kt
    • DTO -> UI model/tag/title mapping을 검증한다.
  • Create: app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/overview/ContentOverviewViewModelTest.kt
    • 첫 페이지, load-more, 실패, stale response 방지를 검증한다.
  • Create: app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/overview/ContentOverviewActivitySourceTest.kt
    • layout id, 2열 grid, intent extra, 상세 이동 source를 검증한다.

Phase 1: 기존 구조 확인과 작업 경계 고정

  • Task 1.1: PRD와 기존 구현 상태 대조

    • 확인:
      • docs/20260627_콘텐츠_전체보기/prd.md
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt
      • app/src/main/res/layout/view_section_title.xml
    • 작업:
      • HomeMainFragmentViewSectionTitleBinding.setTitle(titleResId, showMore)가 chevron 표시를 이미 지원하는지 확인한다.
      • ContentMainFragmentViewSectionTitleBinding.setTitle(titleResId)가 현재 chevron을 항상 숨기는지 확인한다.
      • 이번 작업에서 레거시 audio_content/* 파일을 직접 수정하지 않는 것을 확인한다.
    • 검증:
      • Run: rg -n "setTitle\\(|ivSectionTitleChevron|viewHomeFirstAudioTitle|viewContentNewAndHotTitle|viewContentOriginalSeriesTitle" app/src/main/java/kr/co/vividnext/sodalive/v2/main app/src/main/res/layout/view_section_title.xml
      • Expected: 홈 title helper와 콘텐츠 title helper, chevron view id가 확인된다.
    • 검증 기록:
      • 구현 시 이 Task 아래에 명령, 결과, 확인 내용을 한국어로 누적한다.
      • 2026-06-27 실행: rg -n "setTitle\(|ivSectionTitleChevron|viewHomeFirstAudioTitle|viewContentNewAndHotTitle|viewContentOriginalSeriesTitle" app/src/main/java/kr/co/vividnext/sodalive/v2/main app/src/main/res/layout/view_section_title.xml
      • 결과: view_section_title.xmliv_section_title_chevron, HomeMainFragmentsetTitle(titleResId, showMore), ContentMainFragment의 section title 설정 지점을 확인했다.
      • 확인: 홈 title helper는 showMore로 chevron 표시를 이미 지원하며 viewHomeFirstAudioTitleshowMore = true 상태였다. 콘텐츠 title helper는 기존에 chevron을 숨기고 있어 Phase 2에서 helper 확장이 필요했다.
      • 확인: 이번 Phase 1~2 범위에서는 레거시 audio_content/* 파일을 직접 수정하지 않았다.
  • Task 1.2: 기존 콘텐츠 전체 탭 이동 가능 지점 확인

    • 확인:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentAllTabViewModel.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/data/MainContentAllTabModels.kt
    • 작업:
      • MainV2Activity의 bottom tab 전환과 fragment 재사용 위치를 확인한다.
      • ContentMainFragmentshowAllContent()ContentAllTabViewModel type/sort 변경 위치를 확인한다.
      • MainContentAllType.ORIGINAL, AUDIO, FREE, POINTContentSort.POPULAR, LATEST가 기존 목적지 요구를 충족하는지 확인한다.
    • 검증:
      • Run: rg -n "MainContentAllType|ContentSort|showAllContent|changeType|changeSort|clickTab\\(MainV2Tab.CONTENT\\)" app/src/main/java/kr/co/vividnext/sodalive/v2/main
      • Expected: type/sort 선택과 bottom tab 전환 지점이 확인된다.
    • 검증 기록:
      • 구현 시 이 Task 아래에 명령, 결과, 확인 내용을 한국어로 누적한다.
      • 2026-06-27 실행: rg -n "MainContentAllType|ContentSort|showAllContent|changeType|changeSort|clickTab\(MainV2Tab.CONTENT\)" app/src/main/java/kr/co/vividnext/sodalive/v2/main
      • 결과: MainV2Activity.clickTab(MainV2Tab.CONTENT), ContentMainFragment.showAllContent(), ContentAllTabViewModel.changeType/changeSort, MainContentAllType.ORIGINAL/AUDIO/FREE/POINT, ContentSort 사용 지점을 확인했다.
      • 확인: Phase 1에서는 기존 콘텐츠 전체 탭 이동 가능 지점만 확인했고, 실제 contract/API 추가는 Phase 3 범위로 남겼다.

Phase 2: 홈/콘텐츠 추천 섹션 chevron 먼저 표시

  • Task 2.1: 홈 추천 탭 FIRST 섹션 chevron 표시

    • 수정:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt
    • 테스트:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentSourceTest.kt
    • 작업:
      • viewHomeFirstAudioTitle.setTitle(..., showMore = true) 상태를 유지하거나 누락되어 있으면 추가한다.
      • 이 Task에서는 신규 페이지 이동 click listener를 연결하지 않는다.
      • source test는 viewHomeFirstAudioTitle.setTitle 호출이 showMore = true임을 검증한다.
    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentSourceTest"
      • Expected: 홈 FIRST chevron 표시 source 테스트 PASS.
    • 검증 기록:
      • 구현 시 이 Task 아래에 명령, 결과, 확인 내용을 한국어로 누적한다.
      • 2026-06-27 확인: HomeMainFragmentviewHomeFirstAudioTitle.setTitle(R.string.home_recommendation_section_first_audio_contents, showMore = true)가 이미 적용되어 있었다.
      • 2026-06-27 테스트 추가: HomeMainFragmentSourceTest를 생성해 FIRST 섹션 showMore = true와 Phase 2 범위에서 click listener가 아직 연결되지 않았음을 검증했다.
      • 2026-06-27 실행: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentSourceTest"
      • 결과: PASS.
  • Task 2.2: 콘텐츠 추천 탭 section title helper 확장

    • 수정:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt
    • 테스트:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragmentSourceTest.kt
    • 작업:
      • 콘텐츠 탭의 ViewSectionTitleBinding.setTitle(titleResId)setTitle(titleResId, showMore = false)로 확장한다.
      • 오직 보이스온에서만!, 새로 올라온 오디오, New&Hot, 무료 오디오, 포인트 오디오showMore = true로 설정한다.
      • 댓글 많은 오디오, 추천 오디오showMore = false를 유지한다.
      • 이 Task에서는 기존 전체 탭 이동과 신규 페이지 이동 click listener를 연결하지 않는다.
    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.ContentMainFragmentSourceTest"
      • Expected: 콘텐츠 추천 chevron 표시/숨김 source 테스트 PASS.
    • 검증 기록:
      • 구현 시 이 Task 아래에 명령, 결과, 확인 내용을 한국어로 누적한다.
      • 2026-06-27 RED 실행: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.ContentMainFragmentSourceTest"
      • RED 결과: 신규 content recommendation section titles show only Phase 2 chevrons 테스트가 ContentMainFragmentSourceTest.kt:463에서 실패했다. 원인은 콘텐츠 title helper가 showMore를 지원하지 않고 chevron을 항상 숨기는 기존 구현이었다.
      • 2026-06-27 구현: ContentMainFragmentViewSectionTitleBinding.setTitle(titleResId, showMore = false)를 추가하고 오직 보이스온에서만!, 새로 올라온 오디오, New&Hot, 무료 오디오, 포인트 오디오에만 showMore = true를 지정했다. 댓글 많은 오디오, 추천 오디오는 기본값 false를 유지했다.
      • 2026-06-27 GREEN 실행: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.ContentMainFragmentSourceTest"
      • GREEN 결과: PASS.
      • 참고: 홈/콘텐츠 source 테스트를 병렬 실행하던 중 한 번 :app:kspDebugKotlin generated file 접근 오류가 발생했으나, 동일 콘텐츠 테스트 단독 재실행 시 정상 PASS했다.

Phase 3: 기존 콘텐츠 전체 탭 이동 contract 추가

  • Task 3.1: ContentAllTabViewModel 외부 선택 API 추가

    • 수정:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentAllTabViewModel.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/ContentAllTabViewModelTest.kt
    • 작업:
      • fun selectTypeAndSort(type: MainContentAllType, sort: ContentSort)를 추가한다.
      • 함수는 selectedType, selectedSort, selectedDayOfWeek를 갱신하고 page=0부터 재조회한다.
      • ORIGINAL, AUDIO, FREE, POINTdayOfWeek = null로 요청한다.
      • 기존 changeType, changeSort, loadInitial 테스트를 깨지 않는다.
    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.ContentAllTabViewModelTest"
      • Expected: 기존 테스트와 신규 외부 선택 테스트 PASS.
    • 검증 기록:
      • 구현 시 이 Task 아래에 명령, 결과, 확인 내용을 한국어로 누적한다.
      • 2026-06-27 구현: ContentAllTabViewModel.selectTypeAndSort(type, sort)를 추가해 selectedType, selectedSort, selectedDayOfWeek를 갱신하고 첫 페이지를 재조회하도록 했다. SERIES가 아닌 type은 selectedDayOfWeekFor(type) 경로로 dayOfWeek = null을 유지한다.
      • 2026-06-27 테스트 추가: ContentAllTabViewModelTest에 외부 선택 요청 FREE + POPULAR, ORIGINAL + LATEST가 모두 page=0, dayOfWeek=null로 호출되는 검증을 추가했다.
      • 2026-06-27 실행: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.ContentAllTabViewModelTest"
      • 결과: PASS.
  • Task 3.2: ContentMainFragment에 내부 전체 탭 선택 API 추가

    • 수정:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragmentSourceTest.kt
    • 작업:
      • fun selectAllTab(type: MainContentAllType, sort: ContentSort = ContentSort.LATEST)를 추가한다.
      • 함수는 binding.textTabBarContent.root의 선택을 전체로 전환하고 showContentTab(CONTENT_TAB_ALL)을 호출한다.
      • 이후 contentAllTabViewModel.selectTypeAndSort(type, sort)를 호출한다.
      • fragment view가 아직 생성되지 않은 경우를 대비해 pending type/sort를 fragment field에 저장하고 onViewCreated 이후 적용한다.
    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.ContentMainFragmentSourceTest"
      • Expected: 내부 전체 탭 선택 source 테스트 PASS.
    • 검증 기록:
      • 구현 시 이 Task 아래에 명령, 결과, 확인 내용을 한국어로 누적한다.
      • 2026-06-27 구현: ContentMainFragment.selectAllTab(type, sort)를 추가하고 view 생성 전 호출은 pendingAllTabSelection에 저장한 뒤 onViewCreated 이후 적용하도록 했다.
      • 2026-06-27 확인: pending 적용 시 hasSelectedAllTab = true를 먼저 설정한 뒤 textTabBarContent.root.selectTab(CONTENT_TAB_ALL)showContentTab(CONTENT_TAB_ALL)을 호출해 TextTabBarView.selectTab listener가 유발할 수 있는 기본 loadInitial() 중복 호출을 피했다. 실제 데이터 재조회는 contentAllTabViewModel.selectTypeAndSort(...) 단일 경로로 수행한다.
      • 2026-06-27 실행: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.ContentMainFragmentSourceTest"
      • 결과: PASS.
  • Task 3.3: MainV2Activity 콘텐츠 전체 탭 진입 API 추가

    • 수정:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2ActivitySourceTest.kt
    • 작업:
      • fun openContentAllTab(type: MainContentAllType, sort: ContentSort = ContentSort.LATEST)를 추가한다.
      • 현재 탭을 MainV2Tab.CONTENT로 전환한다.
      • changeFragment 이후 기존 또는 신규 ContentMainFragmentselectAllTab(type, sort)를 전달한다.
      • 이미 콘텐츠 탭에 있는 경우 fragment를 중복 생성하지 않고 현재 fragment method를 호출한다.
    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.MainV2ActivitySourceTest"
      • Expected: MainV2 콘텐츠 전체 탭 routing source 테스트 PASS.
    • 검증 기록:
      • 구현 시 이 Task 아래에 명령, 결과, 확인 내용을 한국어로 누적한다.
      • 2026-06-27 구현: MainV2Activity.openContentAllTab(type, sort)를 추가해 CONTENT 탭이 아니면 viewModel.clickTab(MainV2Tab.CONTENT)changeFragment(MainV2Tab.CONTENT)로 콘텐츠 Fragment를 준비한 뒤 기존/new ContentMainFragment.selectAllTab(type, sort)를 호출하도록 했다.
      • 2026-06-27 확인: 이미 CONTENT 탭인 경우 changeFragment를 직접 호출하지 않고 기존 ContentMainFragment를 찾아 selectAllTab만 호출하므로 fragment 중복 생성을 피한다.
      • 2026-06-27 실행: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.MainV2ActivitySourceTest"
      • 결과: PASS.

Phase 4: 기존 콘텐츠 전체 탭 이동 섹션 연결

  • Task 4.1: 콘텐츠 추천 탭 기존 전체 탭 목적지 연결

    • 수정:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt
    • 테스트:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragmentSourceTest.kt
    • 작업:
      • 오직 보이스온에서만! chevron은 MainContentAllType.ORIGINAL, ContentSort.LATESTMainV2Activity.openContentAllTab을 호출한다.
      • 새로 올라온 오디오 chevron은 MainContentAllType.AUDIO, ContentSort.LATEST로 호출한다.
      • 무료 오디오 chevron은 MainContentAllType.FREE, ContentSort.POPULAR로 호출한다.
      • 포인트 오디오 chevron은 MainContentAllType.POINT, ContentSort.POPULAR로 호출한다.
      • New&Hot은 신규 페이지 대상이므로 이 Phase에서는 click listener를 연결하지 않는다.
      • 처음부터 함께 성장!은 신규 페이지 대상이므로 이 Phase에서는 click listener를 연결하지 않는다.
    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.ContentMainFragmentSourceTest"
      • Expected: 기존 전체 탭 이동 섹션별 목적지 source 테스트 PASS.
    • 검증 기록:
      • 구현 시 이 Task 아래에 명령, 결과, 확인 내용을 한국어로 누적한다.
      • 2026-06-27 구현: 오직 보이스온에서만!, 새로 올라온 오디오, 무료 오디오, 포인트 오디오 chevron click listener를 ensureMainV2NavigationAllowed로 감싸고 (activity as? MainV2Activity)?.openContentAllTab(...) 경로에 연결했다.
      • 2026-06-27 확인: routing destination은 각각 ORIGINAL/LATEST, AUDIO/LATEST, FREE/POPULAR, POINT/POPULAR로 지정했다. New&Hot 및 홈 처음부터 함께 성장! click listener는 Phase 4 범위에서 연결하지 않았다.
      • 2026-06-27 실행: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.ContentMainFragmentSourceTest"
      • 결과: PASS.
  • Task 4.2: 기존 전체 탭 이동 수동 검증

    • 확인:
      • 콘텐츠 추천 탭 오직 보이스온에서만! chevron 클릭 시 콘텐츠 전체 -> 오리지널 진입.
      • 콘텐츠 추천 탭 새로 올라온 오디오 chevron 클릭 시 콘텐츠 전체 -> 오디오 진입.
      • 콘텐츠 추천 탭 무료 오디오 chevron 클릭 시 콘텐츠 전체 -> 무료 + 인기순 진입.
      • 콘텐츠 추천 탭 포인트 오디오 chevron 클릭 시 콘텐츠 전체 -> 포인트 + 인기순 진입.
      • 콘텐츠 추천 탭 New&Hot과 홈 처음부터 함께 성장!은 chevron만 표시되고 신규 페이지 이동은 아직 연결되지 않음.
    • 검증 기록:
      • 구현 시 이 Task 아래에 테스트 기기/빌드 variant/API 응답 조건과 확인 결과를 한국어로 누적한다.
      • 2026-06-27 상태: BLOCKED/미완료. 현재 작업 환경에서 Android 기기 또는 에뮬레이터를 통한 실제 화면 조작을 수행할 수 없어 수동 검증을 완료하지 못했다.
      • 2026-06-27 대체 확인: source 테스트로 Phase 4 click listener 연결 대상과 미연결 대상(New&Hot)은 검증했으나, 실제 앱 화면에서 chevron 클릭 후 콘텐츠 전체 탭 UI/type/sort 표시까지는 확인하지 못했다.

Phase 5: 신규 전체보기 API, DTO, Repository, UI model 추가

  • Task 5.1: ContentOverview API/DTO/Repository 추가

    • 생성:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/overview/data/ContentOverviewApi.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/overview/data/ContentOverviewModels.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/overview/data/ContentOverviewRepository.kt
    • 수정:
      • app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt
    • 작업:
      • ContentOverviewApi.getContents(authHeader, page, size, type)@GET("/api/v2/contents")로 정의한다.
      • query annotation 이름은 page, size, type만 사용한다.
      • DTO는 @Keep@SerializedName을 사용한다.
      • ContentOverviewTypeNEW_AND_HOT_AUDIO, FIRST_AUDIO_CONTENT만 정의한다.
      • Repository는 Single<ApiResponse<ContentOverviewPageResponse>>를 반환한다.
      • Koin networkModule, repositoryModule에 신규 API/Repository를 등록한다.
    • 검증:
      • Run: ./gradlew :app:compileDebugKotlin
      • Expected: 신규 data layer와 DI 등록이 컴파일된다.
    • 검증 기록:
      • 구현 시 이 Task 아래에 명령, 결과, 확인 내용을 한국어로 누적한다.
  • Task 5.2: Mapper RED 테스트 작성

    • 생성:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/overview/ContentOverviewMapperTest.kt
    • 테스트 케이스:
      • NEW_AND_HOT_AUDIO title은 New&Hot이다.
      • FIRST_AUDIO_CONTENT title은 처음부터 함께 성장!이다.
      • price == 0이면 AudioContentTag.Free가 포함된다.
      • isPointAvailable == true이면 AudioContentTag.Point가 포함된다.
      • isFirstContent == true이면 AudioContentTag.First가 포함된다.
      • isOriginalSeries == true이면 AudioContentTag.Original이 포함된다.
      • isAdult == true이면 showAdultBadge == true다.
      • page, size, hasNext, type은 UI state에 보존된다.
    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.overview.ContentOverviewMapperTest"
      • Expected: mapper 구현 전 RED 실패.
    • 검증 기록:
      • 구현 시 이 Task 아래에 명령, 결과, 확인 내용을 한국어로 누적한다.
  • Task 5.3: UI model과 mapper 구현

    • 생성:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/overview/model/ContentOverviewUiState.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/overview/model/ContentOverviewUiModels.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/overview/model/ContentOverviewMappers.kt
    • 작업:
      • ContentOverviewUiState.Loading, Content, Empty, Error를 정의한다.
      • ContentOverviewUiModelcontentId, title, coverImage, creatorNickname, tags, showAdultBadge를 포함한다.
      • ContentOverviewType.toTitleResId()를 정의한다.
      • NEW_AND_HOT_AUDIOR.string.content_recommendation_section_new_and_hot, FIRST_AUDIO_CONTENTR.string.home_recommendation_section_first_audio_contents로 매핑한다.
      • ContentOverviewItemResponse.toUiModel()에서 PRD tag mapping을 구현한다.
      • ContentOverviewPageResponse.toContent()에서 paging metadata와 item list를 보존한다.
    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.overview.ContentOverviewMapperTest"
      • Expected: mapper 테스트 PASS.
    • 검증 기록:
      • 구현 시 이 Task 아래에 명령, 결과, 확인 내용을 한국어로 누적한다.

Phase 6: 신규 전체보기 ViewModel, Activity, Adapter 구현

  • Task 6.1: ViewModel RED 테스트 작성

    • 생성:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/overview/ContentOverviewViewModelTest.kt
    • 테스트 케이스:
      • 초기 로드는 전달받은 type으로 page=0, size=20을 요청한다.
      • 첫 페이지 성공 + items 있음은 Content 상태를 emit한다.
      • 첫 페이지 성공 + items empty는 Empty 상태를 emit한다.
      • 첫 페이지 실패는 Error 상태와 toast를 emit한다.
      • hasNext = true 상태에서 loadMore()는 다음 page를 요청하고 append한다.
      • hasNext = false이면 loadMore()가 repository를 추가 호출하지 않는다.
      • 추가 페이지 실패 시 기존 items를 유지하고 pagination error message를 emit한다.
      • type 변경 또는 새 화면 생성 상태 간 page/items를 공유하지 않는다.
    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.overview.ContentOverviewViewModelTest"
      • Expected: ViewModel 구현 전 RED 실패.
    • 검증 기록:
      • 구현 시 이 Task 아래에 명령, 결과, 확인 내용을 한국어로 누적한다.
  • Task 6.2: ViewModel 구현

    • 생성:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/overview/ContentOverviewViewModel.kt
    • 작업:
      • loadFirstPage(type: ContentOverviewType)를 구현한다.
      • loadMore()Content 상태, hasNext, isLoadingMore를 확인한 뒤 다음 page만 요청한다.
      • requestGeneration으로 stale response가 현재 화면 상태를 덮어쓰지 않게 한다.
      • auth header는 기존 ViewModel 패턴대로 Bearer ${SharedPreferenceManager.token}를 사용한다.
      • 첫 페이지 loading은 isLoading LiveData로 처리하고, 추가 페이지 loading은 Content.isLoadingMore로 처리한다.
    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.overview.ContentOverviewViewModelTest"
      • Expected: ViewModel 테스트 PASS.
    • 검증 기록:
      • 구현 시 이 Task 아래에 명령, 결과, 확인 내용을 한국어로 누적한다.
  • Task 6.3: Activity layout과 Adapter 구현

    • 생성:
      • app/src/main/res/layout/activity_content_overview.xml
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/overview/ui/ContentOverviewAdapter.kt
    • 수정:
      • app/src/main/AndroidManifest.xml
    • 작업:
      • layout root는 black background를 사용한다.
      • title bar는 60dp 높이, 좌측 back chevron, 22sp bold title을 배치한다.
      • RecyclerView는 2열 GridLayoutManager로 표시한다.
      • RecyclerView item은 기존 item_content_audio_card.xmlAudioContentCardView를 재사용한다.
      • Adapter는 setGridItemWidthPx(widthPx)를 호출해 2열 item width를 적용한다.
      • 이미지 로딩은 기존 loadUrl 확장을 사용한다.
      • manifest에 kr.co.vividnext.sodalive.v2.main.content.overview.ContentOverviewActivity를 등록한다.
    • 검증:
      • Run: ./gradlew :app:mergeDebugResources
      • Expected: 신규 layout과 resource reference가 merge된다.
    • 검증 기록:
      • 구현 시 이 Task 아래에 명령, 결과, 확인 내용을 한국어로 누적한다.
  • Task 6.4: Activity source RED 테스트 작성

    • 생성:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/overview/ContentOverviewActivitySourceTest.kt
    • 테스트 케이스:
      • ContentOverviewActivity.newIntent(context, type)가 type extra를 포함한다.
      • layout에 back button, title TextView, RecyclerView, empty/error TextView가 있다.
      • Activity가 GridLayoutManager(this, CONTENT_OVERVIEW_GRID_SPAN_COUNT)로 2열 grid를 설정한다.
      • scroll bottom에서 viewModel.loadMore()를 호출한다.
      • item click은 Constants.EXTRA_AUDIO_CONTENT_IDAudioContentDetailActivity를 연다.
    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.overview.ContentOverviewActivitySourceTest"
      • Expected: Activity 구현 전 또는 연결 전 RED 실패.
    • 검증 기록:
      • 구현 시 이 Task 아래에 명령, 결과, 확인 내용을 한국어로 누적한다.
  • Task 6.5: Activity 구현과 ViewModel 연결

    • 생성:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/overview/ContentOverviewActivity.kt
    • 수정:
      • app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt
    • 작업:
      • newIntent(context, type) companion method를 제공한다.
      • intent type extra가 없거나 enum parsing에 실패하면 NEW_AND_HOT_AUDIO를 fallback으로 사용한다.
      • title은 type mapping으로 표시한다.
      • back button은 finish()를 호출한다.
      • Content, Empty, Error, Loading 상태별 UI를 바인딩한다.
      • item click은 AudioContentDetailActivity를 열고 Constants.EXTRA_AUDIO_CONTENT_IDcontentId를 전달한다.
      • 성인 콘텐츠 접근 제한은 기존 AudioContentDetailActivity 정책을 따른다.
      • Koin viewModelModuleContentOverviewViewModel을 등록한다.
    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.overview.*"
      • Expected: overview mapper/ViewModel/source 테스트 PASS.
    • 검증 기록:
      • 구현 시 이 Task 아래에 명령, 결과, 확인 내용을 한국어로 누적한다.

Phase 7: 신규 전체보기 대상 섹션 이동 연결

  • Task 7.1: 홈 FIRST 신규 전체보기 이동 연결

    • 수정:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt
    • 테스트:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentSourceTest.kt
    • 작업:
      • viewHomeFirstAudioTitle.ivSectionTitleChevron.setOnClickListener를 추가한다.
      • 클릭 시 ContentOverviewActivity.newIntent(requireContext(), ContentOverviewType.FIRST_AUDIO_CONTENT)로 이동한다.
      • ensureMainV2NavigationAllowed로 기존 홈 navigation guard 패턴을 따른다.
    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentSourceTest"
      • Expected: 홈 FIRST 신규 전체보기 이동 source 테스트 PASS.
    • 검증 기록:
      • 구현 시 이 Task 아래에 명령, 결과, 확인 내용을 한국어로 누적한다.
  • Task 7.2: 콘텐츠 New&Hot 신규 전체보기 이동 연결

    • 수정:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt
    • 테스트:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragmentSourceTest.kt
    • 작업:
      • viewContentNewAndHotTitle.ivSectionTitleChevron.setOnClickListener를 추가한다.
      • 클릭 시 ContentOverviewActivity.newIntent(requireContext(), ContentOverviewType.NEW_AND_HOT_AUDIO)로 이동한다.
      • ensureMainV2NavigationAllowed로 기존 콘텐츠 navigation guard 패턴을 따른다.
      • 기존 전체 탭 이동 대상 섹션 click listener는 Phase 4 동작을 유지한다.
    • 검증:
      • Run: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.ContentMainFragmentSourceTest"
      • Expected: 콘텐츠 New&Hot 신규 전체보기 이동 source 테스트 PASS.
    • 검증 기록:
      • 구현 시 이 Task 아래에 명령, 결과, 확인 내용을 한국어로 누적한다.

Phase 8: 통합 검증과 수동 확인

  • Task 8.1: 단위/source 테스트 실행

    • 실행:
      • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.*"
      • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*"
      • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.overview.*"
    • 기대 결과:
      • 수정된 홈/콘텐츠/MainV2 source 테스트와 신규 overview 테스트가 모두 PASS한다.
    • 검증 기록:
      • 구현 시 이 Task 아래에 실행 명령과 결과를 한국어로 누적한다.
  • Task 8.2: 빌드/리소스/린트 검증

    • 실행:
      • ./gradlew :app:mergeDebugResources
      • ./gradlew :app:compileDebugKotlin
      • ./gradlew :app:ktlintCheck
      • git diff --check
    • 기대 결과:
      • resource merge, Kotlin compile, ktlint, whitespace 검증이 모두 PASS한다.
    • 검증 기록:
      • 구현 시 이 Task 아래에 실행 명령과 결과를 한국어로 누적한다.
  • Task 8.3: 수동 화면 검증

    • 확인:
      • 홈 추천 탭 처음부터 함께 성장! title 우측 chevron 표시.
      • 콘텐츠 추천 탭 오직 보이스온에서만!, 새로 올라온 오디오, New&Hot, 무료 오디오, 포인트 오디오 title 우측 chevron 표시.
      • 콘텐츠 추천 탭 댓글 많은 오디오, 추천 오디오에는 chevron이 표시되지 않음.
      • 콘텐츠 추천 탭 오직 보이스온에서만! chevron 클릭 시 콘텐츠 전체 -> 오리지널 진입.
      • 콘텐츠 추천 탭 새로 올라온 오디오 chevron 클릭 시 콘텐츠 전체 -> 오디오 진입.
      • 콘텐츠 추천 탭 무료 오디오 chevron 클릭 시 콘텐츠 전체 -> 무료 + 인기순 진입.
      • 콘텐츠 추천 탭 포인트 오디오 chevron 클릭 시 콘텐츠 전체 -> 포인트 + 인기순 진입.
      • 홈 추천 탭 처음부터 함께 성장! chevron 클릭 시 처음부터 함께 성장! 신규 전체보기 진입.
      • 콘텐츠 추천 탭 New&Hot chevron 클릭 시 New&Hot 신규 전체보기 진입.
      • 신규 전체보기 화면 title bar, back button, 2열 grid, tag, 성인 badge, 상세 이동.
      • 신규 전체보기 화면 하단 스크롤 시 page + 1 추가 요청.
    • 검증 기록:
      • 구현 시 이 Task 아래에 테스트 기기/빌드 variant/API 응답 조건과 확인 결과를 한국어로 누적한다.

Verification Log

  • 구현 완료 후 여러 Phase에 걸친 통합 검증, 회귀 검증, 최종 수동 확인 기록을 이 섹션에 누적한다.
  • 2026-06-27 Phase 3~4 범위 실행: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.ContentAllTabViewModelTest" 결과 PASS.
  • 2026-06-27 Phase 3~4 범위 실행: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.ContentMainFragmentSourceTest" 결과 PASS.
  • 2026-06-27 Phase 3~4 범위 실행: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.MainV2ActivitySourceTest" 결과 PASS.
  • 2026-06-27 Phase 3~4 범위 실행: ./gradlew :app:compileDebugKotlin 결과 PASS.
  • 2026-06-27 Phase 3~4 범위 실행: ./gradlew :app:ktlintCheck는 신규 source 테스트의 긴 줄로 1회 실패 후 포맷 수정, 재실행 결과 PASS. 기존 .editorconfigdisabled_rules deprecation 경고는 출력되었으나 실패 원인은 아니었다.
  • 2026-06-27 Phase 3~4 범위 실행: git diff --check 결과 PASS(출력 없음).
  • 2026-06-27 수동 화면 검증: Android 기기/에뮬레이터를 사용할 수 없어 Phase 4 기존 전체 탭 이동 수동 클릭 검증은 완료하지 못했다. Task 4.2에 BLOCKED/미완료로 기록했다.