docs(content): 추천 Phase 4-6 검증을 기록한다

This commit is contained in:
2026-06-23 17:18:51 +09:00
parent e4d650c3e7
commit 8d33b90e67

View File

@@ -302,7 +302,7 @@
### Phase 4: 배너, 오리지널 시리즈, 공통 오디오 카드 섹션 구현 ### Phase 4: 배너, 오리지널 시리즈, 공통 오디오 카드 섹션 구현
- [ ] **Task 4.1: 배너 binder 구현** - [x] **Task 4.1: 배너 binder 구현**
- 생성: - 생성:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ui/ContentBannerBinder.kt` - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ui/ContentBannerBinder.kt`
- 작업: - 작업:
@@ -312,8 +312,10 @@
- 배너 클릭은 route model을 통해 event/creator/series/link 우선순위를 기존 홈 배너 정책과 맞춘다. - 배너 클릭은 route model을 통해 event/creator/series/link 우선순위를 기존 홈 배너 정책과 맞춘다.
- 검증: - 검증:
- 샘플 또는 API 응답으로 배너가 있을 때 carousel/counter가 보이고, 없을 때 section이 숨겨진다. - 샘플 또는 API 응답으로 배너가 있을 때 carousel/counter가 보이고, 없을 때 section이 숨겨진다.
- 검증 기록:
- 2026-06-23: `ContentBannerBinder`를 추가하고 `ContentMainFragment`에서 배너 section visibility와 click route를 연결했다. 빈 배너는 `ll_content_banner_section``GONE` 처리하고, `ContentBannerRoute`는 event/creator/series/link 우선순위와 link validation을 source test로 확인했다.
- [ ] **Task 4.2: 오리지널 시리즈 adapter 구현** - [x] **Task 4.2: 오리지널 시리즈 adapter 구현**
- 생성: - 생성:
- `app/src/main/res/layout/item_content_original_series.xml` - `app/src/main/res/layout/item_content_original_series.xml`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ui/ContentOriginalSeriesAdapter.kt` - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ui/ContentOriginalSeriesAdapter.kt`
@@ -325,8 +327,10 @@
- 클릭 시 `SeriesDetailActivity`로 이동하고 `Constants.EXTRA_SERIES_ID`를 전달한다. - 클릭 시 `SeriesDetailActivity`로 이동하고 `Constants.EXTRA_SERIES_ID`를 전달한다.
- 검증: - 검증:
- `originalSeries`가 비면 `오직 보이스온에서만!` 섹션 전체가 숨겨진다. - `originalSeries`가 비면 `오직 보이스온에서만!` 섹션 전체가 숨겨진다.
- 검증 기록:
- 2026-06-23: `item_content_original_series.xml``ContentOriginalSeriesAdapter`를 추가하고 가로 RecyclerView에 연결했다. `seriesId <= 0` 클릭 무시와 `SeriesDetailActivity`/`Constants.EXTRA_SERIES_ID` routing source를 확인했다.
- [ ] **Task 4.3: 공통 오디오 카드 adapter 구현** - [x] **Task 4.3: 공통 오디오 카드 adapter 구현**
- 생성: - 생성:
- `app/src/main/res/layout/item_content_audio_card.xml` - `app/src/main/res/layout/item_content_audio_card.xml`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ui/ContentAudioCardAdapter.kt` - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ui/ContentAudioCardAdapter.kt`
@@ -346,8 +350,10 @@
- 클릭 시 `AudioContentDetailActivity`로 이동하고 `Constants.EXTRA_AUDIO_CONTENT_ID`를 전달한다. - 클릭 시 `AudioContentDetailActivity`로 이동하고 `Constants.EXTRA_AUDIO_CONTENT_ID`를 전달한다.
- 검증: - 검증:
- 무료/포인트/첫 콘텐츠/오리지널 태그 조합이 mapper 테스트와 화면에서 일치한다. - 무료/포인트/첫 콘텐츠/오리지널 태그 조합이 mapper 테스트와 화면에서 일치한다.
- 검증 기록:
- 2026-06-23: `item_content_audio_card.xml`, `ContentAudioCardAdapter`, `AudioContentCardView.setAdultVisible()` 및 기본 `gone` 성인 badge를 추가했다. 최신/무료/포인트는 `AudioContentCardSize.Medium`, 추천은 `AudioContentCardSize.Large`로 연결했고 `duration`은 표시하지 않았다.
- [ ] **Task 4.4: 최신/무료/포인트/추천 섹션 Fragment 바인딩** - [x] **Task 4.4: 최신/무료/포인트/추천 섹션 Fragment 바인딩**
- 수정: - 수정:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt` - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt`
- `app/src/main/res/layout/fragment_v2_main_content.xml` - `app/src/main/res/layout/fragment_v2_main_content.xml`
@@ -358,12 +364,14 @@
- 추천 오디오 홀수 item은 마지막 item이 좌측 정렬되도록 기본 GridLayoutManager 정책을 사용한다. - 추천 오디오 홀수 item은 마지막 item이 좌측 정렬되도록 기본 GridLayoutManager 정책을 사용한다.
- 검증: - 검증:
- `latestAudios`, `freeAudios`, `pointAudios`, `recommendedAudios` 각각의 empty/non-empty 상태를 수동 확인한다. - `latestAudios`, `freeAudios`, `pointAudios`, `recommendedAudios` 각각의 empty/non-empty 상태를 수동 확인한다.
- 검증 기록:
- 2026-06-23: 최신/무료/포인트 horizontal RecyclerView와 추천 2열 `GridLayoutManager`를 바인딩했다. 각 section은 item list가 비면 container 전체를 `GONE` 처리하도록 구현했다.
--- ---
### Phase 5: New&Hot와 최근 댓글 섹션 구현 ### Phase 5: New&Hot와 최근 댓글 섹션 구현
- [ ] **Task 5.1: New&Hot group adapter 구현** - [x] **Task 5.1: New&Hot group adapter 구현**
- 생성: - 생성:
- `app/src/main/res/layout/item_content_new_and_hot_group.xml` - `app/src/main/res/layout/item_content_new_and_hot_group.xml`
- `app/src/main/res/layout/item_content_audio_list.xml` - `app/src/main/res/layout/item_content_audio_list.xml`
@@ -377,8 +385,10 @@
- 클릭 시 오디오 상세로 이동한다. - 클릭 시 오디오 상세로 이동한다.
- 검증: - 검증:
- item 수 1, 2, 3, 4, 6개 샘플에서 group 수와 row 수가 의도대로 보인다. - item 수 1, 2, 3, 4, 6개 샘플에서 group 수와 row 수가 의도대로 보인다.
- 검증 기록:
- 2026-06-23: `ContentNewAndHotAdapter`에서 `items.chunked(NEW_AND_HOT_GROUP_SIZE)``private const val NEW_AND_HOT_GROUP_SIZE = 3` 정책을 구현했다. 마지막 group이 3개 미만이면 실제 item row만 inflate하도록 처리했다.
- [ ] **Task 5.2: 최근 댓글이 많은 오디오 adapter 구현** - [x] **Task 5.2: 최근 댓글이 많은 오디오 adapter 구현**
- 생성: - 생성:
- `app/src/main/res/layout/item_content_commented_audio.xml` - `app/src/main/res/layout/item_content_commented_audio.xml`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ui/ContentCommentedAudioAdapter.kt` - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ui/ContentCommentedAudioAdapter.kt`
@@ -391,8 +401,10 @@
- 클릭 시 오디오 상세로 이동한다. - 클릭 시 오디오 상세로 이동한다.
- 검증: - 검증:
- 댓글 본문이 있는 item은 댓글 영역이 보이고, blank 댓글 item은 댓글 영역이 숨겨진다. - 댓글 본문이 있는 item은 댓글 영역이 보이고, blank 댓글 item은 댓글 영역이 숨겨진다.
- 검증 기록:
- 2026-06-23: `item_content_commented_audio.xml``ContentCommentedAudioAdapter`를 추가했다. `layoutContentCommentArea.isVisible = item.showLatestComment`로 blank 댓글 영역을 숨기고, `ivContentCommentProfile.loadUrl`로 댓글 작성자 프로필 이미지를 로딩하도록 구현했다.
- [ ] **Task 5.3: New&Hot와 댓글 섹션 Fragment 바인딩** - [x] **Task 5.3: New&Hot와 댓글 섹션 Fragment 바인딩**
- 수정: - 수정:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt` - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt`
- `app/src/main/res/layout/fragment_v2_main_content.xml` - `app/src/main/res/layout/fragment_v2_main_content.xml`
@@ -403,12 +415,14 @@
- 검증: - 검증:
- Figma 순서상 `New&Hot`가 최신 오디오 뒤, 무료 오디오 앞에 배치되는지 확인한다. - Figma 순서상 `New&Hot`가 최신 오디오 뒤, 무료 오디오 앞에 배치되는지 확인한다.
- `최근 댓글이 많은 오디오`가 포인트 오디오 뒤, 추천 오디오 앞에 배치되는지 확인한다. - `최근 댓글이 많은 오디오`가 포인트 오디오 뒤, 추천 오디오 앞에 배치되는지 확인한다.
- 검증 기록:
- 2026-06-23: `fragment_v2_main_content.xml`의 Phase 3 section 순서를 유지하며 New&Hot는 최신 뒤/무료 앞, 최근 댓글은 포인트 뒤/추천 앞에 adapter를 연결했다. 제외 섹션 id/string은 추가하지 않았다.
--- ---
### Phase 6: 실제 API 상태 바인딩, routing, 제외 섹션 검증 ### Phase 6: 실제 API 상태 바인딩, routing, 제외 섹션 검증
- [ ] **Task 6.1: ViewModel observe와 실제 API 로딩 연결** - [x] **Task 6.1: ViewModel observe와 실제 API 로딩 연결**
- 수정: - 수정:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt` - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt`
- 작업: - 작업:
@@ -420,8 +434,10 @@
- `Error` 또는 전체 empty는 기존 정책에 맞춰 빈 콘텐츠 또는 toast를 표시한다. - `Error` 또는 전체 empty는 기존 정책에 맞춰 빈 콘텐츠 또는 toast를 표시한다.
- 검증: - 검증:
- API 성공/실패를 개발 환경에서 확인하거나 ViewModel 테스트로 상태 전환을 확인한다. - API 성공/실패를 개발 환경에서 확인하거나 ViewModel 테스트로 상태 전환을 확인한다.
- 검증 기록:
- 2026-06-23: `ContentMainViewModel`을 Koin `by viewModel()`로 주입하고 `recommendationsStateLiveData`, `isLoading`, `toastLiveData` observer를 연결했다. `LoadingDialog(requireActivity(), layoutInflater)`, `loadingDialog.show(screenWidth)`, `toastMessage?.let(::showToast)`, 최초 `loadRecommendations()` 호출을 source test와 컴파일로 확인했다.
- [ ] **Task 6.2: routing source test 작성** - [x] **Task 6.2: routing source test 작성**
- 생성: - 생성:
- `app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragmentSourceTest.kt` - `app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragmentSourceTest.kt`
- 테스트 케이스: - 테스트 케이스:
@@ -435,8 +451,10 @@
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.ContentMainFragmentSourceTest"` - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.ContentMainFragmentSourceTest"`
- 기대 결과: - 기대 결과:
- 구현 전 실패하고, Phase 6 완료 후 PASS한다. - 구현 전 실패하고, Phase 6 완료 후 PASS한다.
- 검증 기록:
- 2026-06-23: 기존 RED `ContentMainFragmentSourceTest`에 Android `Uri`/`RuntimeEnvironment` 검증이 포함되어 Robolectric runner와 기본 `Application` 설정을 추가했다. 이후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.ContentMainFragmentSourceTest"` 결과 `BUILD SUCCESSFUL`.
- [ ] **Task 6.3: 배너 routing 구현** - [x] **Task 6.3: 배너 routing 구현**
- 수정: - 수정:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/model/AudioRecommendationsUiModels.kt` - `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/model/AudioRecommendationsUiModels.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/ContentMainFragment.kt`
@@ -448,6 +466,8 @@
- 목적지가 없으면 클릭을 무시한다. - 목적지가 없으면 클릭을 무시한다.
- 검증: - 검증:
- 목적지별 배너 샘플로 intent extras가 의도대로 들어가는지 확인한다. - 목적지별 배너 샘플로 intent extras가 의도대로 들어가는지 확인한다.
- 검증 기록:
- 2026-06-23: `ContentBannerRoute`, `toContentBannerRoute()`, `toContentBannerIntent(context)`를 추가했다. event는 `Constants.EXTRA_EVENT`, creator는 `CreatorChannelActivity.newIntent`, series는 `Constants.EXTRA_SERIES_ID`, link/deep link는 `Intent.ACTION_VIEW`로 생성됨을 source test로 확인했다.
--- ---
@@ -489,3 +509,6 @@
- 2026-06-23: 후속 요청에 따라 content title-bar 좌측을 이미지 로고에서 하단 대화 탭과 같은 텍스트 title(`screen_content_main_title`, `콘텐츠`)로 변경했다. `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `git diff --check` 결과 모두 성공했다. XML `lsp_diagnostics`는 XML LSP 서버 미설정으로 실행하지 못했다. - 2026-06-23: 후속 요청에 따라 content title-bar 좌측을 이미지 로고에서 하단 대화 탭과 같은 텍스트 title(`screen_content_main_title`, `콘텐츠`)로 변경했다. `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `git diff --check` 결과 모두 성공했다. XML `lsp_diagnostics`는 XML LSP 서버 미설정으로 실행하지 못했다.
- 2026-06-23: 리뷰 지적 중 무료/포인트 섹션의 tag 강제 표시 여부를 재검토했다. `AudioRecommendationsMappers`, 기존 홈/크리에이터 채널 mapper, mapper 테스트, `ast-grep` 검색 결과 모두 tag는 섹션 소속이 아니라 item 속성(`price == 0`, `isPointAvailable == true`, `isFirstContent`, `isOriginalSeries`) 기반으로 산출하는 정책이었다. 이에 맞춰 PRD의 `무료 태그는 반드시 표시한다`, `포인트 태그는 반드시 표시한다` 문구를 섹션 membership으로 강제하지 않는 속성 기반 표시 정책으로 정정했다. - 2026-06-23: 리뷰 지적 중 무료/포인트 섹션의 tag 강제 표시 여부를 재검토했다. `AudioRecommendationsMappers`, 기존 홈/크리에이터 채널 mapper, mapper 테스트, `ast-grep` 검색 결과 모두 tag는 섹션 소속이 아니라 item 속성(`price == 0`, `isPointAvailable == true`, `isFirstContent`, `isOriginalSeries`) 기반으로 산출하는 정책이었다. 이에 맞춰 PRD의 `무료 태그는 반드시 표시한다`, `포인트 태그는 반드시 표시한다` 문구를 섹션 membership으로 강제하지 않는 속성 기반 표시 정책으로 정정했다.
- 2026-06-23: Phase 1~3 코드 리뷰 및 검증을 재수행했다. `ContentMainFragment` 패키지 이동과 `MainV2Activity` 참조, `AudioRecommendations` API/DTO/Repository/ViewModel/mapper, 고정 title-bar/tab-bar 레이아웃, 제외 섹션 미추가 상태를 확인했고 blocking issue는 발견하지 못했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.*"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check` 결과 모두 성공했다. - 2026-06-23: Phase 1~3 코드 리뷰 및 검증을 재수행했다. `ContentMainFragment` 패키지 이동과 `MainV2Activity` 참조, `AudioRecommendations` API/DTO/Repository/ViewModel/mapper, 고정 title-bar/tab-bar 레이아웃, 제외 섹션 미추가 상태를 확인했고 blocking issue는 발견하지 못했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.*"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check` 결과 모두 성공했다.
- 2026-06-23: Phase 4~6 구현 후 RED `ContentMainFragmentSourceTest``ContentBannerRoute` 미구현으로 실패하는 것을 먼저 확인했다. 구현 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.*"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check`를 순차 실행했고 모두 성공했다. 병렬 Gradle 검증 중 Kotlin incremental cache 충돌이 한 번 발생했으나 `./gradlew --stop` 후 순차 재실행으로 성공을 확인했다. Kotlin/XML `lsp_diagnostics``kotlin-lsp` 및 XML LSP 서버 미설치로 실행하지 못했다.
- 2026-06-23: Phase 4~6 코드 리뷰 및 검증을 재수행했다. 배너/오리지널 시리즈/공통 오디오 카드/New&Hot/최근 댓글 adapter, 실제 API 상태 observer, section visibility, 오디오/시리즈/배너 routing, 제외 섹션 미추가 상태를 확인했고 blocking issue는 발견하지 못했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.*"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check` 결과 모두 성공했다. `:app:mergeDebugResources`는 최초 sandbox 실행에서 `~/.gradle` lock 파일 권한으로 실패해 승인 후 재실행했고 성공했다.