# 콘텐츠 전체보기 구현 계획/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` - 작업: - `HomeMainFragment`의 `ViewSectionTitleBinding.setTitle(titleResId, showMore)`가 chevron 표시를 이미 지원하는지 확인한다. - `ContentMainFragment`의 `ViewSectionTitleBinding.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 아래에 명령, 결과, 확인 내용을 한국어로 누적한다. - [ ] **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 재사용 위치를 확인한다. - `ContentMainFragment`의 `showAllContent()`와 `ContentAllTabViewModel` type/sort 변경 위치를 확인한다. - `MainContentAllType.ORIGINAL`, `AUDIO`, `FREE`, `POINT`와 `ContentSort.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 아래에 명령, 결과, 확인 내용을 한국어로 누적한다. --- ### 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 아래에 명령, 결과, 확인 내용을 한국어로 누적한다. - [ ] **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 아래에 명령, 결과, 확인 내용을 한국어로 누적한다. --- ### 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`, `POINT`는 `dayOfWeek = null`로 요청한다. - 기존 `changeType`, `changeSort`, `loadInitial` 테스트를 깨지 않는다. - 검증: - Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.ContentAllTabViewModelTest"` - Expected: 기존 테스트와 신규 외부 선택 테스트 PASS. - 검증 기록: - 구현 시 이 Task 아래에 명령, 결과, 확인 내용을 한국어로 누적한다. - [ ] **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 아래에 명령, 결과, 확인 내용을 한국어로 누적한다. - [ ] **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` 이후 기존 또는 신규 `ContentMainFragment`에 `selectAllTab(type, sort)`를 전달한다. - 이미 콘텐츠 탭에 있는 경우 fragment를 중복 생성하지 않고 현재 fragment method를 호출한다. - 검증: - Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.MainV2ActivitySourceTest"` - Expected: MainV2 콘텐츠 전체 탭 routing source 테스트 PASS. - 검증 기록: - 구현 시 이 Task 아래에 명령, 결과, 확인 내용을 한국어로 누적한다. --- ### 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.LATEST`로 `MainV2Activity.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 아래에 명령, 결과, 확인 내용을 한국어로 누적한다. - [ ] **Task 4.2: 기존 전체 탭 이동 수동 검증** - 확인: - 콘텐츠 추천 탭 `오직 보이스온에서만!` chevron 클릭 시 콘텐츠 `전체` -> `오리지널` 진입. - 콘텐츠 추천 탭 `새로 올라온 오디오` chevron 클릭 시 콘텐츠 `전체` -> `오디오` 진입. - 콘텐츠 추천 탭 `무료 오디오` chevron 클릭 시 콘텐츠 `전체` -> `무료` + `인기순` 진입. - 콘텐츠 추천 탭 `포인트 오디오` chevron 클릭 시 콘텐츠 `전체` -> `포인트` + `인기순` 진입. - 콘텐츠 추천 탭 `New&Hot`과 홈 `처음부터 함께 성장!`은 chevron만 표시되고 신규 페이지 이동은 아직 연결되지 않음. - 검증 기록: - 구현 시 이 Task 아래에 테스트 기기/빌드 variant/API 응답 조건과 확인 결과를 한국어로 누적한다. --- ### 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`을 사용한다. - `ContentOverviewType`은 `NEW_AND_HOT_AUDIO`, `FIRST_AUDIO_CONTENT`만 정의한다. - Repository는 `Single>`를 반환한다. - 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`를 정의한다. - `ContentOverviewUiModel`은 `contentId`, `title`, `coverImage`, `creatorNickname`, `tags`, `showAdultBadge`를 포함한다. - `ContentOverviewType.toTitleResId()`를 정의한다. - `NEW_AND_HOT_AUDIO`는 `R.string.content_recommendation_section_new_and_hot`, `FIRST_AUDIO_CONTENT`는 `R.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.xml`의 `AudioContentCardView`를 재사용한다. - 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_ID`로 `AudioContentDetailActivity`를 연다. - 검증: - 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_ID`에 `contentId`를 전달한다. - 성인 콘텐츠 접근 제한은 기존 `AudioContentDetailActivity` 정책을 따른다. - Koin `viewModelModule`에 `ContentOverviewViewModel`을 등록한다. - 검증: - 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에 걸친 통합 검증, 회귀 검증, 최종 수동 확인 기록을 이 섹션에 누적한다.