From d53167c48e60fdd2c887396e7768ebb7c358bbc1 Mon Sep 17 00:00:00 2001 From: klaus Date: Sun, 12 Jul 2026 01:24:02 +0900 Subject: [PATCH] =?UTF-8?q?docs(content):=20=EC=A0=84=EC=B2=B4=20=ED=83=AD?= =?UTF-8?q?=20pagination=20=EC=95=88=EC=A0=95=ED=99=94=20=EC=9A=94?= =?UTF-8?q?=EA=B5=AC=EB=A5=BC=20=EA=B8=B0=EB=A1=9D=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plan-task.md | 55 +++++++++++++++++++ .../prd.md | 6 ++ 2 files changed, 61 insertions(+) diff --git a/docs/20260625_메인_콘텐츠_탭_내부_전체_탭/plan-task.md b/docs/20260625_메인_콘텐츠_탭_내부_전체_탭/plan-task.md index c44e6ccf..ff72cd5f 100644 --- a/docs/20260625_메인_콘텐츠_탭_내부_전체_탭/plan-task.md +++ b/docs/20260625_메인_콘텐츠_탭_내부_전체_탭/plan-task.md @@ -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에 머물러 콘텐츠 전체 탭까지 이동한 실제 스크롤 육안 확인은 완료하지 못했다. diff --git a/docs/20260625_메인_콘텐츠_탭_내부_전체_탭/prd.md b/docs/20260625_메인_콘텐츠_탭_내부_전체_탭/prd.md index 987ecfdc..7630c559 100644 --- a/docs/20260625_메인_콘텐츠_탭_내부_전체_탭/prd.md +++ b/docs/20260625_메인_콘텐츠_탭_내부_전체_탭/prd.md @@ -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`와 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` 기반으로 변경분만 반영해야 한다.