docs(content): 전체 탭 pagination 안정화 요구를 기록한다

This commit is contained in:
2026-07-12 01:24:02 +09:00
parent c2b212c1ce
commit d53167c48e
2 changed files with 61 additions and 0 deletions

View File

@@ -765,3 +765,58 @@
- PASS: device `2cec640c34017ece`에서 debug 앱 실행 후 콘텐츠 `전체 > 오디오`, `전체 > 시리즈`를 탭하고 UI hierarchy 기준 카드 x bounds가 3열 grid 열 안에 들어오는 것을 확인했다.
- 참고: 최초 `ktlintCheck` 실행은 `ContentMainFragmentSourceTest`의 긴 assertion 한 줄로 실패했고, 줄바꿈 보정 후 재실행해 통과했다.
- 참고: `ktlintCheck``.editorconfig disabled_rules` deprecation warning과 Gradle deprecation warning은 기존 경고로 이번 변경과 무관하다.
---
### Phase 8: 전체 탭 pagination 스크롤 안정화
- [x] **Task 8.1: adapter 보존 source 회귀 테스트 추가**
- 수정:
- `app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragmentSourceTest.kt`
- 작업:
- `bindAllTabContent()`가 같은 콘텐츠 계열에서 `rvContentAllItems.adapter`를 반복 대입하지 않도록 guard helper를 요구한다.
- `clearAllTabItems()`가 render-time 상태 정리 중 `rvContentAllItems.adapter = null`을 호출하지 않도록 고정한다.
- `onDestroyView()` lifecycle cleanup의 adapter null 처리만 유지되는지 확인한다.
- `ContentAllAudioCardAdapter`, `ContentAllSeriesCardAdapter``DiffUtil.calculateDiff(...)`를 사용하고 `submitItems()`에서 `notifyDataSetChanged()`를 호출하지 않도록 고정한다.
- 검증:
- Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.ContentMainFragmentSourceTest"`
- Expected: 구현 전 RED 실패, 구현 후 PASS.
- 2026-07-12: RED 확인. `ContentMainFragmentSourceTest`에 pagination 렌더링 adapter 보존과 전체 탭 adapter DiffUtil 사용 source 검증을 추가한 뒤 실행했고, 기존 구현에서 신규 테스트 2건이 실패했다. 첫 실행은 120초 timeout으로 결과를 받지 못해 300초 제한으로 재실행했다.
- [x] **Task 8.2: ContentMainFragment adapter 반복 재설정 방지**
- 수정:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt`
- 작업:
- 현재 adapter와 목표 adapter가 다를 때만 `rvContentAllItems.adapter`를 변경한다.
- 추가 페이지 로딩 상태 재렌더링 중 같은 adapter를 유지한다.
- Empty/Error/Loading 렌더링의 item clear는 adapter를 분리하지 않고 목록만 비운다.
- 검증:
- Source test와 Kotlin compile로 확인한다.
- 2026-07-12: `setAllTabAdapterIfNeeded(adapter: RecyclerView.Adapter<*>)` helper를 추가해 현재 adapter와 다를 때만 `rvContentAllItems.adapter`를 교체하도록 변경했다. `clearAllTabItems()`에서는 adapter null 처리를 제거하고 `onDestroyView()` lifecycle cleanup에는 adapter null 처리를 유지했다.
- [x] **Task 8.3: 전체 탭 오디오/시리즈 adapter DiffUtil 전환**
- 수정:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ui/ContentAllAudioCardAdapter.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ui/ContentAllSeriesCardAdapter.kt`
- 작업:
- public `submitItems(...)` API는 유지한다.
- 오디오 identity는 `audioContentId`, 시리즈 identity는 `seriesId`로 비교한다.
- content 비교는 UI model data class equality를 사용한다.
- grid item width 변경은 기존 guard를 유지하되 전체 dataset reset 대신 range changed로 처리한다.
- 검증:
- Source test와 Kotlin compile로 확인한다.
- 2026-07-12: `ContentAllAudioCardAdapter``ContentAllSeriesCardAdapter``submitItems()``DiffUtil.calculateDiff(...)` + `dispatchUpdatesTo(this)` 방식으로 변경했다. identity는 각각 `audioContentId`, `seriesId`를 사용하고, width 변경은 기존 guard 후 `notifyItemRangeChanged(0, items.size)`만 호출하도록 변경했다.
- [x] **Task 8.4: 통합 검증과 수동 확인 가능 여부 기록**
- 실행:
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.ContentMainFragmentSourceTest"`
- `./gradlew :app:compileDebugKotlin`
- `./gradlew :app:ktlintCheck`
- `git diff --check`
- 수동 확인:
- 연결 기기/에뮬레이터가 있으면 콘텐츠 `전체` 탭에서 하단 pagination 시 스크롤이 상단으로 튀지 않는지 확인한다.
- 기기가 없으면 환경 차단으로 기록하고 source/compile/style 검증으로 대체한다.
- 검증 기록:
- 2026-07-12: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.ContentMainFragmentSourceTest"` 재실행 결과 BUILD SUCCESSFUL을 확인했다.
- 2026-07-12: `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check`가 모두 통과했다. `ktlintCheck``.editorconfig disabled_rules` deprecation warning과 Gradle deprecation warning은 기존 경고로 이번 변경과 무관하다.
- 2026-07-12: `adb devices`에서 device `2cec640c34017ece` 연결을 확인했고 `./gradlew :app:installDebug`로 debug 앱 설치가 BUILD SUCCESSFUL이었다. `adb shell monkey -p kr.co.vividnext.sodalive.debug -c android.intent.category.LAUNCHER 1` 실행 후 foreground가 `kr.co.vividnext.sodalive.debug/kr.co.vividnext.sodalive.splash.SplashActivity`임을 확인했다. SplashActivity에 머물러 콘텐츠 전체 탭까지 이동한 실제 스크롤 육안 확인은 완료하지 못했다.

View File

@@ -26,6 +26,8 @@
- 2026-07-10 후속 변경: 전체 탭 sort-bar에는 좌측 `전체` 라벨도 표시하지 않는다.
- 2026-07-10 후속 변경: 전체 탭 정렬 조건에서 `소장순`을 제거한다.
- 2026-07-10 후속 변경: `무료` 타입 선택 시 정렬 조건에서 `높은 가격순`, `낮은 가격순`을 표시하지 않는다.
- 2026-07-12 후속 변경: 추가 페이지 로딩 시 `RecyclerView` adapter를 반복 재설정하지 않아 스크롤 위치가 위로 튀지 않게 한다.
- 2026-07-12 후속 변경: 전체 탭 오디오/시리즈 adapter는 append 갱신 시 `DiffUtil` 기반으로 변경분만 반영한다.
---
@@ -253,6 +255,8 @@ data class MainContentSeriesResponse(
- 일본어 요일 UI의 `RANDOM` 표기는 `その他`로 표시한다.
- 영어 요일 UI의 `RANDOM` 표기는 `OTHER`로 표시한다.
- 시리즈 카드 성인 배지는 Figma Large/Small 태그 크기와 기존 오디오 성인 배지 표현을 따른다.
- 추가 페이지 로딩 중에는 현재 `RecyclerView` adapter를 유지하고, 같은 콘텐츠 계열의 상태 재렌더링만으로 adapter를 다시 대입하지 않는다.
- 첫 페이지 loading/empty/error 렌더링처럼 목록을 비우는 상태에서도 lifecycle cleanup이 아닌 이상 adapter를 `null`로 분리하지 않는다.
### 재사용 가능한 V2 위젯 후보
- `kr.co.vividnext.sodalive.v2.widget.CapsuleTabBarView`
@@ -284,6 +288,7 @@ data class MainContentSeriesResponse(
- 응답 처리는 기존 `ApiResponse<T>`와 Rx 타입(`Single`, `Flowable`) 패턴을 우선 검토한다.
- 공개 API 스키마는 사용자 요청에 명시된 계약을 그대로 따른다.
- `BuildConfig` 값이나 민감 정보는 로그/Toast/크래시 메시지에 노출하지 않는다.
- 전체 탭 pagination append는 `notifyDataSetChanged()` 전체 갱신이 아니라 `DiffUtil.calculateDiff(...)` 결과를 dispatch하는 방식으로 처리한다.
---
@@ -306,3 +311,4 @@ data class MainContentSeriesResponse(
## 12. Verification Log
- 2026-06-25: 사용자 후속 요구사항에 따라 전체 탭 오디오/시리즈 카드가 fixed Small `122dp` card width에 의존하지 않고 RecyclerView 3열 가용 item width를 사용해야 함을 문서화했다. 오디오 썸네일은 item width 기준 1:1, 시리즈 썸네일은 item width 기준 122:172 비율을 유지한다.
- 2026-06-25: 후속 UI 보정 검증으로 focused source/widget tests, `mergeDebugResources`, `compileDebugKotlin`, `ktlintCheck`, `git diff --check`가 모두 PASS했다. 전체 탭 adapter의 fixed Small 호출 제거와 동적 grid width API 사용, 오디오 1:1 및 시리즈 122:172 썸네일 계약을 테스트로 고정했다.
- 2026-07-12: 사용자 후속 요구사항에 따라 전체 탭 다음 페이지 로딩 시 adapter 반복 재설정으로 스크롤이 상단으로 튀는 문제를 수정 대상으로 추가했다. 같은 콘텐츠 계열에서는 adapter를 유지하고, 오디오/시리즈 전체 탭 adapter는 `DiffUtil` 기반으로 변경분만 반영해야 한다.