fix(home): 라이브 전체 버튼을 오른쪽에 고정한다

This commit is contained in:
2026-08-04 11:45:29 +09:00
parent d20d6aa971
commit 3c21a764ae
8 changed files with 236 additions and 98 deletions

View File

@@ -1353,6 +1353,119 @@ git diff --check
- 2026-07-31: 사용자 인터뷰에서 라이브 0개는 기존처럼 섹션을 숨기고 1개 이상이면 최대 20개 뒤에 `전체` 항목을 항상 표시하는 정책을 확정했다. 기존 20개 상한·`전체` 디자인·클릭 목적지·로그인 가드·API 계약을 유지하고, `HomeLiveAdapter` 내부 상태명과 조건 및 `HomeMainFragmentLayoutTest` 경계 테스트만 변경하는 Phase 18을 작성했다. production·test 구현은 시작하지 않았다.
- 2026-07-31: Phase 18을 TDD로 구현했다. RED는 라이브 1개에서 `itemCount`가 기대 2, 실제 1로 실패했고, GREEN은 `showAllItem = items.isNotEmpty()` 최소 변경 후 focused 74개 테스트 성공으로 확인했다. 전체 1,199 tests와 compile·ktlint·diff 검증도 성공했으며 기존 `MoreViewHolder`·클릭 연결·20개 cap은 변경하지 않았다.
## Phase 19: 현재 진행 중인 라이브 `전체` 항목 오른쪽 고정
**Phase 결과:** 메인 홈 추천 탭에서 라이브 목록만 가로 스크롤되고, `전체` 항목은 스크롤 위치와 관계없이 오른쪽 끝의 예약된 영역에 고정된다.
**선행 조건:** Phase 18 완료, `prd.md``2026-08-03 변경: 현재 진행 중인 라이브 전체 항목 오른쪽 고정` 승인.
**Phase 완료 조건:** `P19-T1`, `P19-R1`, `P19-GATE` 완료, RED/GREEN 로직 회귀와 resource merge·compile·lint 결과를 Task 검증 기록과 `Verification Log`에 누적.
### 구현 항목
#### Task 19.1: 스크롤 목록과 고정 `전체` 항목 분리
**Goal 실행 `P19-T1`:** `HomeLiveAdapter`는 최대 20개의 라이브 item만 제공하고, `fragment_v2_main_home.xml`의 고정 `전체` 뷰가 기존 로그인 가드 경로를 호출한다.
- **시작 조건:** Phase 19 선행 조건 충족.
- **완료 증거:** RED/GREEN 체크박스 완료, 아래 S1~S3와 인접 라우팅 회귀 자동 검증, focused test·resource merge·compile·ktlint·diff 결과 누적.
- **범위 밖:** `전체` 디자인·문구·클릭 목적지·로그인 가드 변경, 라이브 최대 20개 정책·item 순서·입장 흐름 변경, 추천/전체 라이브 API 변경, overlay·커스텀 LayoutManager·신규 abstraction·dependency 추가, 레거시 홈 수정.
**Files:**
- Modify: `app/src/main/res/layout/fragment_v2_main_home.xml`
- Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeLiveAdapter.kt`
- Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt`
- Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt`
- Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLoginGuardSourceTest.kt`
- Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md`
**Interfaces:**
- Consumes: `HomeLiveAdapter.submitItems(items)`, `MAX_VISIBLE_LIVE_COUNT = 20`, `HomeMainFragment.openHomeOnAirLive()`의 기존 `ensureV2Access(AccessRequirement.Login)` 계약.
- Produces: adapter의 `0개 -> 0`, `1~20개 -> 원본 개수`, `21개 이상 -> 20` item 계약과, `ll_home_live_section`의 스크롤 `rv_home_lives` + 고정 `tv_home_live_all` 형제 구조.
**시나리오 계약:**
- `S1` 빈 목록: adapter item 수가 0이고 `bindLiveSection()`의 기존 빈 목록 숨김 로직을 유지한다. 자동 증거는 `HomeMainFragmentLayoutTest`의 adapter 경계 로직 테스트다.
- `S2` 라이브 1~20개: adapter item 수가 입력 개수와 같고 adapter가 별도 `전체` item을 만들지 않는다. 자동 증거는 `HomeMainFragmentLayoutTest`의 adapter 경계 로직 테스트다.
- `S3` 라이브 21개 이상 및 인접 회귀: adapter item 수가 정확히 20이고, 고정 `전체` 터치는 기존 `openHomeOnAirLive()` 로그인 가드를 사용하며 라이브 item은 기존 `LiveActionCoordinator` 흐름을 유지한다. 자동 증거는 `HomeMainFragmentLayoutTest``HomeMainFragmentLoginGuardSourceTest`다.
- [x] **RED:** `HomeMainFragmentLayoutTest`의 adapter 계약을 `전체` item 포함 방식에서 0개·1개·20개·21개 이상 라이브 item 수만 검증하는 로직 테스트로 변경한다. View 크기·간격·부모 관계·visibility 같은 레이아웃 속성 테스트는 추가하지 않는다.
- [x] **RED:** `HomeMainFragmentLoginGuardSourceTest``setUpRecommendationAdapters()``binding.tvHomeLiveAll.setOnClickListener { openHomeOnAirLive() }`를 연결하고 `liveAdapter.setOnMoreClick`을 사용하지 않는지 검증하는 한글 테스트를 추가한다. 기존 `openHomeOnAirLive()` 로그인 가드와 라이브 item 공통 Action 검증은 유지한다.
- [x] **RED 확인:** 아래 focused test를 실행해 adapter의 추가 `전체` item과 기존 `setOnMoreClick` 연결 때문에 신규 로직 assertion이 의도대로 실패하는지 확인한다.
```bash
./gradlew :app:testDebugUnitTest \
--tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest" \
--tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLoginGuardSourceTest" \
--no-parallel --console=plain
```
- [x] **GREEN:** `fragment_v2_main_home.xml`에서 `ll_home_live_section`을 수평 컨테이너로 바꾸고, `rv_home_lives``0dp + weight 1` 및 시작 padding을 사용하며, 기존 스타일과 끝 여백을 유지한 `tv_home_live_all`을 오른쪽 형제 뷰로 추가한다.
- [x] **GREEN:** `HomeLiveAdapter`에서 `showAllItem`, `onMoreClick`, `MoreViewHolder`, `createMoreView()`, view type 분기를 제거하고 최대 20개의 라이브 item만 bind한다. `HomeMainFragment`는 고정 뷰 터치를 기존 `openHomeOnAirLive()`에 직접 연결한다.
- [x] **GREEN 확인:** RED 확인과 같은 focused test를 다시 실행해 S1~S3와 기존 로그인 가드·라이브 item 라우팅 검증이 성공하는지 확인한다.
- [x] **REFACTOR:** 이번 변경으로 사용되지 않는 import·adapter 코드만 제거하고 아래 직접 영향 회귀 및 품질 명령을 실행해 결과를 검증 기록에 누적한다. UI/계측 테스트, 기기·에뮬레이터 조작, 스크린샷·시각 QA는 실행하지 않는다.
```bash
./gradlew :app:mergeDebugResources :app:compileDebugKotlin :app:ktlintCheck \
--no-parallel --console=plain
./gradlew :app:testDebugUnitTest --no-parallel --console=plain
git diff --check
```
검증 기록:
- 2026-08-03: 사용자 승인과 코드 조사 결과를 기준으로 Phase 19 계획을 작성했다. `전체`는 overlay가 아닌 예약된 형제 영역에 고정하고, Phase 18의 상시 표시·빈 목록 숨김·20개 cap·기존 디자인·라우팅 계약은 유지한다. production·test 구현은 시작하지 않았다.
- 2026-08-03: `HomeMainFragmentLayoutTest``HomeMainFragmentLoginGuardSourceTest`에 Phase 19 실패 테스트를 먼저 반영했다. focused 실행은 89 tests 중 신규 계약 4개만 실패했다. 실패는 수평 컨테이너 미적용(`HomeMainFragmentLayoutTest.kt:1043`), 라이브 1개 및 22개 입력에서 adapter가 `전체`를 추가해 기대 개수와 불일치(`:1088`, `:1103`), 고정 `tvHomeLiveAll` 클릭 연결 부재(`HomeMainFragmentLoginGuardSourceTest.kt:220`)로 확인되어 요구 동작 미구현에 따른 의도한 RED다.
- 2026-08-03: `fragment_v2_main_home.xml`에 예약 폭을 사용하는 고정 `tv_home_live_all` 형제 뷰를 추가하고, `HomeLiveAdapter`는 최대 20개의 라이브 item만 제공하도록 adapter 소유 `전체` 상태·ViewHolder·callback을 제거했다. `HomeMainFragment`는 고정 뷰 터치를 기존 `openHomeOnAirLive()`에 직접 연결했다. 동일 focused test는 GREEN으로 `BUILD SUCCESSFUL`이었고 `--rerun-tasks` 강제 재실행도 성공했다. `mergeDebugResources`, `compileDebugKotlin`, `ktlintCheck`, `git diff --check`도 성공했으며 ktlint 설정 및 Gradle/Kotlin deprecation warning은 기존 경고다.
- 2026-08-04: 사용자 확인에 따라 Phase 19의 최종 테스트 범위를 로직/local unit test로 정정했다. 임시 `app/src/androidTest` 계측 테스트, 레이아웃 속성 unit test, 스크린샷·시각 QA 산출물과 QA 전용 에뮬레이터를 제거했다. 앞선 레이아웃 assertion RED 기록은 최종 완료 증거에서 제외하고 adapter 개수 경계와 로그인 가드·라이브 라우팅 로직 테스트만 유지한다.
- 2026-08-04: 코드 리뷰 `REV-P19-001`에서 초기 `Loading` 상태가 content를 bind하지 않아 XML 기본 `VISIBLE`인 고정 `전체`가 단독 노출될 수 있음을 확인했다. 저장소 정책상 레이아웃 속성 테스트는 남기지 않고 `ll_home_live_section` 기본값을 `GONE`으로 최소 수정했으며 기존 `bindLiveSection()`의 빈 목록 숨김·비어 있지 않은 목록 표시 로직은 유지했다.
- 2026-08-04: focused `HomeMainFragmentLayoutTest`·`HomeMainFragmentLoginGuardSourceTest`, `mergeDebugResources`, `compileDebugKotlin`이 성공했다. 전체 `testDebugUnitTest` 첫 실행은 이번 diff 밖의 기존 `CreatorChannelHomeViewModelTest` 비결정적 assertion 1건이 실패했지만 해당 클래스 격리 실행과 전체 재실행은 성공했다. 전역 `ktlintCheck`는 기존 `audio_content` package underscore 등 이번 diff 밖 위반으로 실패했고 변경된 Kotlin/test 파일은 오류 목록에 없었다.
- 2026-08-04: 독립 최종 리뷰에서 adapter item-count 테스트에 남은 미사용 `context`·`RecyclerView` setup 3줄을 제거했다. focused 로직 테스트와 `git diff --check`를 재실행해 성공했고 재리뷰에서 차단 사항 없이 `APPROVE` 판정을 받았다.
### 완료 조건
- [x] 라이브 목록만 가로 스크롤되고 `전체` 항목은 오른쪽 끝에 고정된다.
- [x] 고정 `전체` 항목이 라이브 item을 가리지 않고 별도 영역을 차지한다.
- [x] 빈 목록은 adapter item 0개이며 라이브 섹션 전체가 숨겨진다.
- [x] 라이브 1~20개는 adapter item 수가 입력 개수와 같고, 21개 이상은 정확히 20개다.
- [x] 기존 `전체` 디자인·문구·끝 여백·클릭 목적지·로그인 가드와 라이브 item 간격·입장 흐름이 유지된다.
- [x] 신규 abstraction·dependency·overlay·커스텀 LayoutManager·레거시 수정·commit이 없다.
### 검증 방법
#### Task R19.1: 초기 로딩 중 고정 `전체` 단독 노출 방지
**Goal 실행 `P19-R1`:** `REV-P19-001`에서 확인한 초기 데이터 바인딩 전 고정 `전체` 단독 노출 가능성을 제거한다.
- **시작 조건:** `REV-P19-001` 확정, `HomeRecommendationUiState.Loading`이 content를 bind하지 않는 현재 흐름 확인.
- **완료 증거:** `ll_home_live_section` 기본 숨김, 기존 `bindLiveSection()` 표시 정책 유지, resource merge·compile과 Phase 19 로직 회귀 성공.
- **범위 밖:** UI/계측 테스트, 레이아웃 속성 unit test, 로딩 상태 구조 변경, 관련 없는 홈 섹션 수정.
- **TDD 예외 사유:** 수정 대상이 XML 초기 visibility 속성이며 저장소 정책상 레이아웃 속성 테스트를 작성하지 않는다. 코드 흐름 대조와 resource merge·compile로 검증한다.
- [x] `ll_home_live_section`을 XML에서 기본 `GONE`으로 설정한다.
- [x] `bindLiveSection()`이 빈 목록은 숨기고 비어 있지 않은 목록은 표시하는 기존 로직을 유지한다.
- [x] focused 로직 테스트, resource merge, compile, ktlint와 diff check를 실행해 결과를 기록한다.
#### Phase 19 Gate
**Goal 실행 `P19-GATE`:** S1~S3의 adapter 경계·라우팅 로직과 resource/compile 계약을 최종 판정한다.
- **시작 조건:** `P19-T1`, `P19-R1` 체크박스와 검증 기록 완료.
- **완료 증거:** focused·전체 unit test, resource merge, compile, ktlint 결과 분리, diff check와 아래 자동·정적 대조 기록.
- **범위 밖:** Gate 통과를 위한 test 삭제·완화, 범위 밖 실패 수정, API·레거시·디자인 변경.
자동·정적 대조:
- [x] adapter가 빈 입력 0개, 1~20개 입력 원본 개수, 21개 이상 입력 20개를 반환한다.
- [x] adapter에 `전체` item용 view type·ViewHolder·callback이 남지 않는다.
- [x] `tv_home_live_all`은 기존 `openHomeOnAirLive()` 로그인 가드 경로를 사용한다.
- [x] resource merge와 compile이 성공하고 `app/src/androidTest` 신규 테스트가 없다.
- [x] UI/계측 테스트, 기기·에뮬레이터 조작, 스크린샷·시각 QA를 완료 증거로 사용하지 않는다.
중단 조건: S1~S3 로직 회귀, 전체 unit test, resource merge, compile, ktlint, diff check와 리뷰 승인이 끝나면 즉시 중단한다.
## Decision Log
| 날짜 | ID | 상태 | 결정 | 근거 | 영향 Goal/문서 |
@@ -1361,8 +1474,13 @@ git diff --check
| 2026-07-31 | `DEC-018` | 확정 | Phase 7의 2026-06-05 보안 재리뷰에서 승인된 유료 미구매 원본 이미지 미로드 정책을 기준으로 삼고, commit `2d58a876`에서 재도입된 client-side blur 원본 로드를 회귀로 수정한다. | 보안 재리뷰·검증 기록, 현재 `HomePopularCommunityAdapter`와 반대 방향의 test 변경 이력 교차 검토 | `REV-P7-001`, `P7-R1` |
| 2026-07-31 | `DEC-019` | 확정 | 두 번째 `Phase 12`와 그 하위 `Task 10.*`는 기존 기록을 보존하면서 `P12B` 계열 고유 별칭을 추가해 이후 리뷰와 회귀 작업에서 사용한다. | `work-plan-docs.md`의 Goal/Task 추적성 규칙과 현재 문서의 중복 식별자 | `REV-P12B-001`, `P12B-R1` |
| 2026-07-31 | `DEC-020` | 확정 | 현재 진행 중인 라이브가 0개이면 섹션을 숨기고, 1개 이상이면 최대 20개 라이브 뒤에 `전체` 항목을 항상 표시한다. | 사용자 인터뷰 승인과 기존 `HomeLiveAdapter`·전체 라이브 이동 계약 확인 | `P18-T1`, `P18-GATE`, `prd.md``2026-07-31 변경: 현재 진행 중인 라이브 전체 항목 상시 표시` |
| 2026-08-03 | `DEC-021` | 확정 | Phase 18의 `전체` 상시 표시 정책은 유지하되 adapter 마지막 item 배치를 폐기하고, 라이브 목록과 영역을 나누는 오른쪽 고정 형제 뷰로 변경한다. | 사용자 권고안 승인, 현재 수평 RecyclerView·adapter·로그인 가드 흐름 조사 | `P19-T1`, `P19-GATE`, `prd.md``2026-08-03 변경: 현재 진행 중인 라이브 전체 항목 오른쪽 고정` |
| 2026-08-04 | `DEC-022` | 확정 | 자동 테스트는 `app/src/test`의 로직/local unit test만 사용하고, 사용자 명시 요청 없이는 UI/계측 테스트·기기/에뮬레이터 조작·스크린샷·시각 QA를 실행하지 않는다. | 사용자 직접 확인과 기존 `code-style.md`의 로직 테스트 전용 원칙 | `AGENTS.md`, 테스트/계획 가이드, `P19-T1`, `P19-R1`, `P19-GATE` |
## Verification Log
- 2026-08-04: 사용자 확인에 따라 UI 테스트 없이 로직/local unit test만 수행하는 정책을 `AGENTS.md`, `build-test-style.md`, `code-style.md`, `work-plan-docs.md`, `sample-plan-task.md`에 명시했다. Phase 19의 임시 `androidTest`, 레이아웃 속성 테스트, 스크린샷·시각 QA 산출물과 QA 전용 에뮬레이터를 제거하고 계획·Gate를 adapter 경계와 라우팅 로직 검증 기준으로 정정했다. focused 로직 테스트와 resource merge·compile은 성공했고, 전체 unit test는 기존 비결정적 1건 격리 성공 후 전체 재실행이 성공했다. 전역 ktlint는 이번 diff 밖의 기존 위반으로 실패해 분리 기록했다.
- 2026-08-04: Phase 19 최종 독립 리뷰의 유일한 차단 사항이었던 item-count 테스트 미사용 UI setup을 제거했다. focused 로직 테스트와 diff check 성공 후 재리뷰가 `APPROVE`로 종료됐으며 UI/계측 테스트는 추가하거나 재실행하지 않았다.
- 2026-08-03: Phase 19 문서 준비로 현재 v2 홈 데이터·adapter·layout·클릭 흐름과 최근 Phase 18 변경을 확인하고, 사용자 승인 설계를 PRD와 `P19-T1`·`P19-GATE`에 반영했다. S1 빈 목록, S2 1~20개 스크롤 고정, S3 21개 이상 cap·기존 라우팅 회귀를 자동·표면 검증 계약으로 정의했다. production·test 구현은 시작하지 않았다.
- 2026-07-31: Phase 18 변경 코드 리뷰 및 최신 검증을 수행했다. `HomeLiveAdapter`의 0개·1~20개·21개 이상 경계와 기존 `MoreViewHolder`, 로그인 가드, `HomeOnAirLiveActivity` 이동 경로를 대조해 확정 발견 사항이 없음을 판정했다. focused 74 tests·compile·ktlint와 `--rerun-tasks` 전체 162 suites·1,199 tests가 실패 0·오류 0으로 성공했고 staged·unstaged diff check와 문서 변경 후 `./gradlew tasks --all --console=plain`도 성공했다. 프로젝트 전체 `lintDebug`는 이번 diff 밖의 기존 28 errors·767 warnings로 실패했으며 첫 오류는 `AndroidManifest.xml:316``FacebookActivity` `MissingClass`다. 상세 결과는 `reviews/phase18-live-all-item-review.md`에 기록했다.
- 2026-07-31: Phase 18 TDD와 Gate 검증을 완료했다. 구현 전 focused baseline 73개 성공 후 신규 경계 테스트가 라이브 1개에서 `expected:<2> but was:<1>`로 RED 실패했고, `HomeLiveAdapter``hasMore``showAllItem`으로 바꾸고 `items.isNotEmpty()` 조건을 적용한 뒤 focused 74개가 성공했다. `:app:testDebugUnitTest --no-parallel --console=plain` 전체 실행은 162 suites, 1,199 tests, 실패 0·오류 0·skip 0으로 성공했고 `:app:compileDebugKotlin`, `:app:ktlintCheck`, `git diff --check`도 성공했다. 기존 deprecation warning 외 실패는 없었으며 신규 파일·dependency·commit은 생성하지 않았다.
- 2026-07-31: Phase 18 문서 준비 검증으로 PRD의 0개·1~20개·21개 이상 표시 계약과 Non-Goal을 `P18-T1``P18-GATE`에 대조했고 누락된 확정 요구사항이 없음을 확인했다. `P18`, `Task 18`, `showAllItem` 추적 검색과 placeholder/TODO 검색, `git diff --check`가 성공했으며 변경 파일은 `prd.md`, `plan-task.md` 두 문서뿐이다. `./gradlew tasks --all --console=plain` 최초 sandbox 실행은 사용자 Gradle cache lock 접근 제한으로 실패했고, 승인된 동일 명령 재실행은 `BUILD SUCCESSFUL`로 완료됐다. production·test 구현은 시작하지 않았고 커밋은 생성하지 않았다.