docs(content): 콘텐츠 전체보기 문서를 정리한다
This commit is contained in:
511
docs/20260627_콘텐츠_전체보기/plan-task.md
Normal file
511
docs/20260627_콘텐츠_전체보기/plan-task.md
Normal file
@@ -0,0 +1,511 @@
|
||||
# 콘텐츠 전체보기 구현 계획/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<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`를 정의한다.
|
||||
- `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에 걸친 통합 검증, 회귀 검증, 최종 수동 확인 기록을 이 섹션에 누적한다.
|
||||
Reference in New Issue
Block a user