From 9a47c4295835a0c9bee8cf44478c55d25eb9f8e6 Mon Sep 17 00:00:00 2001 From: klaus Date: Thu, 28 May 2026 14:41:24 +0900 Subject: [PATCH] =?UTF-8?q?docs(banner):=20Phase=209=EC=99=80=2010=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=EC=9D=84=20=EA=B8=B0=EB=A1=9D=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/plan-task/20260527_배너컴포넌트.md | 83 +++++++++++++++++++++++++ docs/prd/20260527_배너컴포넌트_prd.md | 6 ++ 2 files changed, 89 insertions(+) diff --git a/docs/plan-task/20260527_배너컴포넌트.md b/docs/plan-task/20260527_배너컴포넌트.md index c43ce742..22013323 100644 --- a/docs/plan-task/20260527_배너컴포넌트.md +++ b/docs/plan-task/20260527_배너컴포넌트.md @@ -421,6 +421,79 @@ - 기록 위치: 이 문서 하단 `검증 기록` 섹션. - 기대 결과: 모두 `BUILD SUCCESSFUL`이며, 실제 기기 또는 Android Studio XML layout editor 확인이 가능하면 counter 공백/typography, preview, peek, radius를 수동 확인한다. +### Phase 9: 실제 기기 시각 보정 + +- [x] **Task 9.1: carousel item 중심 쏠림 회귀 테스트 작성** + - 수정 파일: `app/src/test/java/kr/co/vividnext/sodalive/v2/widget/banner/BannerViewTest.kt` + - 대상 리스크: item 간격을 오른쪽 offset으로만 적용하면 `PagerSnapHelper`의 decorated center 기준과 실제 이미지 중심이 달라져 현재 banner가 왼쪽으로 치우쳐 보일 수 있다. + - 테스트 항목: + - 복수 banner에서 item 사이 실제 간격은 `8dp`를 유지한다. + - spacing decoration은 좌우 대칭이거나, snap 기준 중심과 item image 중심이 일치하는 값을 반환한다. + - 단일 banner에서는 기존처럼 spacing을 적용하지 않는다. + - 실행: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.banner.BannerViewTest"` + - 기대: 현재 구현이 오른쪽 offset만 사용하면 RED가 되고, 대칭 offset 또는 동등한 중심 보정 구현 후 GREEN이 된다. + +- [x] **Task 9.2: carousel item 중심 쏠림 보정 구현** + - 수정 파일: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/banner/BannerView.kt` + - 요구사항: + - 복수 banner item spacing `8dp`는 유지한다. + - `BannerSpacingDecoration`은 item 오른쪽에만 spacing을 몰아주지 않고 좌우 `4dp`씩 나누거나, `PagerSnapHelper` 기준 중심과 실제 이미지 중심이 일치하는 방식으로 구현한다. + - 단일 item은 spacing `0`을 유지한다. + - 검증: Task 9.1 테스트와 기존 layout size, wrap content, counter, auto scroll 테스트가 모두 통과한다. + +- [x] **Task 9.3: counter item 기준 위치 회귀 테스트 작성** + - 수정 파일: `app/src/test/java/kr/co/vividnext/sodalive/v2/widget/banner/BannerViewTest.kt` + - 대상 리스크: counter가 `BannerView` root 우상단 `14dp`에 붙으면 Figma의 "중앙 banner item 내부 우상단 `14dp`" 위치보다 오른쪽으로 치우친다. + - 테스트 항목: + - width `402dp`, side inset `20dp` 기준 counter root end margin은 `34dp`다. + - top margin은 `14dp`를 유지한다. + - width가 바뀌어도 counter는 현재 banner item의 우측 경계에서 `14dp` 안쪽에 위치한다. + - 실행: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.banner.BannerViewTest"` + - 기대: 현재 구현이 root end `14dp`이면 RED가 되고, item 기준 end `34dp` 적용 후 GREEN이 된다. + +- [x] **Task 9.4: counter item 기준 위치 보정 구현** + - 수정 파일: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/banner/BannerView.kt`, 필요 시 `app/src/main/res/layout/view_banner.xml` + - 요구사항: + - counter top margin은 Figma 기준 `14dp`를 유지한다. + - counter end margin은 `sideInsetDp + 14dp`로 계산해 현재 banner item 내부 우상단 기준에 맞춘다. + - `view_banner.xml`에는 기본값을 유지하되, runtime layout size 적용 시 동적으로 보정한다. + - 검증: Task 9.3 테스트와 기존 counter 형식/색상/typography 테스트가 모두 통과한다. + +- [x] **Task 9.5: 실제 기기 수동 확인 기록** + - 확인 항목: + - 현재 banner image가 화면 중앙에 정렬되어 보인다. + - 좌우 이전/다음 banner 일부가 대칭적으로 노출된다. + - counter가 중앙 banner item 내부 우상단 `14dp` 위치에 보인다. + - 기록 위치: 이 문서 하단 `검증 기록` 섹션. + +### Phase 10: 초기 표시 상태 중심 정렬 보완 + +- [x] **Task 10.1: 최초 렌더링 중심 정렬 회귀 테스트 작성** + - 수정 파일: `app/src/test/java/kr/co/vividnext/sodalive/v2/widget/banner/BannerViewTest.kt` + - 대상 리스크: Phase 9 보정 후 자동 또는 수동 슬라이드가 1회 이상 발생하면 중심이 맞지만, 최초 표시 상태에서는 초기 adapter position 적용 시점과 item size/padding/decoration 적용 시점 차이로 현재 banner가 약간 우측으로 치우쳐 보일 수 있다. + - 테스트 항목: + - 복수 banner를 `setItems()`로 설정하고 최초 `measure/layout`이 끝난 직후, 별도 scroll 이벤트 없이 현재 banner item 중심이 `BannerView` 중심과 일치한다. + - 최초 표시 상태의 좌우 peek 노출량이 대칭이다. + - 자동/수동 슬라이드 이후 중심 정렬 테스트는 기존 Phase 9 결과를 유지한다. + - 실행: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.banner.BannerViewTest"` + - 기대: 현재 구현이 최초 layout 직후 우측 쏠림을 재현하면 RED가 되고, 초기 scroll position 재적용 또는 layout 완료 후 snap 보정 구현 후 GREEN이 된다. + +- [x] **Task 10.2: 최초 렌더링 중심 정렬 보정 구현** + - 수정 파일: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/banner/BannerView.kt` + - 요구사항: + - `setItems()` 시점에 계산한 `currentAdapterPosition`이 item size, RecyclerView padding, spacing decoration 적용 이후에도 유지되도록 보정한다. + - `onSizeChanged()` 또는 layout size 적용 이후 복수 banner이면 최초 표시 position을 다시 적용하거나, `RecyclerView.post { ... }` 등 기존 구조에 맞는 최소 보정으로 첫 frame의 중심 정렬을 맞춘다. + - 보정은 최초 표시 또는 size 변경 시점에만 적용하고, 사용자가 수동으로 이동한 이후의 현재 위치를 불필요하게 되돌리지 않는다. + - 기존 자동 전환, 수동 스와이프, 무한 순환, counter 동기화 동작을 변경하지 않는다. + - 검증: Task 10.1 테스트와 Phase 9 중심/peek/counter 테스트, 기존 banner 전체 테스트가 모두 통과한다. + +- [x] **Task 10.3: 실제 기기 수동 확인 기록** + - 확인 항목: + - 앱 진입 후 슬라이드 전 최초 banner가 화면 중앙에 보인다. + - 자동 또는 수동 슬라이드 후에도 중앙 정렬이 유지된다. + - 좌우 peek 노출량이 최초 표시와 슬라이드 이후 모두 대칭이다. + - 기록 위치: 이 문서 하단 `검증 기록` 섹션. + ## 검증 기록 - 2026-05-27 Phase 1 완료: PRD `docs/prd/20260527_배너컴포넌트_prd.md`의 Goals, Visual Requirements, Behavior Requirements, Metrics가 계획의 Phase 2~7 task로 매핑됨을 확인했다. Figma 기준 `24:5525`에서 계획에 명시된 `1:1`, `screenWidth - 40dp`, 좌우 `20dp`, item 간격 `8dp`, 좌우 peek, `radius_14`, counter `top/right 14dp`, `01 / 20` 형식과 불일치하는 항목은 발견하지 못했다. - 2026-05-27 Phase 1 패턴 확인: `AudioContentCardView`의 `@JvmOverloads` custom view, `thumbnailView(): ImageView` 노출, `radius_14` outline clipping 패턴을 확인했다. `FeedAdapter`의 `RecyclerView.Adapter`, click callback, image binding callback 패턴과 `FeedViewTest`의 Robolectric XML inflate test 패턴을 확인했다. @@ -459,3 +532,13 @@ - 2026-05-28 Phase 8 구현: `view_banner.xml` counter typography를 `Typography.Body5`로 맞추고, `BannerView`에서 separator를 ` / `로 표시하도록 보완했다. `findViewTreeLifecycleOwner()`와 `DefaultLifecycleObserver`로 lifecycle stop/start timer 제어를 추가하고, detach 시 observer를 제거한다. `setItems()`는 새 목록을 첫 번째 배너 기준으로 초기화하며, idle 시 snap view가 없으면 현재 adapter position 기준으로 counter를 동기화한다. - 2026-05-28 Phase 8 GREEN: 구현 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.banner.BannerViewTest"` 실행 결과 `BUILD SUCCESSFUL`을 확인했다. - 2026-05-28 Phase 8 최종 검증: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.banner.*"`, `./gradlew :app:assembleDebug`, `./gradlew :app:ktlintCheck` 실행 결과 모두 `BUILD SUCCESSFUL`을 확인했다. 실제 기기/Android Studio XML layout editor는 이 환경에서 열 수 없어 직접 시각 확인은 수행하지 못했고, Robolectric/빌드 검증으로 counter 공백/typography, lifecycle stop/start, 목록 갱신 초기화, 수동 경계/연속 위치 변경 counter 동기화를 확인했다. +- 2026-05-28 Phase 9 RED: `BannerViewTest`에 carousel item spacing 좌우 대칭 검증과 counter item 내부 우상단 기준 margin 검증을 추가했다. Production 변경 전 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.banner.BannerViewTest"` 실행 결과 `배너 view는 정사각형 item 크기와 좌우 padding 및 간격을 적용한다`, `배너 view는 carousel item 간격을 좌우 대칭으로 적용한다`, `배너 view counter는 현재 item 내부 우상단 기준 margin을 적용한다` 3개 테스트 실패를 확인했다. +- 2026-05-28 Phase 9 구현: `BannerSpacingDecoration`이 복수 banner spacing `8dp`를 좌우 `4dp`씩 대칭 적용하도록 수정했다. `BannerView.applyLayoutSize()`에서 counter end margin을 `sideInset 20dp + item 내부 margin 14dp = 34dp`로 동적 보정해 root 우측이 아닌 현재 banner item 내부 우상단 기준에 맞췄다. +- 2026-05-28 Phase 9 GREEN: 구현 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.banner.BannerViewTest"` 실행 결과 `BUILD SUCCESSFUL`을 확인했다. +- 2026-05-28 Phase 9 수동 확인 기록: 실제 기기/Android Studio XML layout editor는 이 환경에서 열 수 없어 직접 시각 확인은 수행하지 못했다. 대신 Robolectric 검증으로 현재 banner 중심 기준 spacing 좌우 대칭, 단일 item spacing 0 유지, counter top `14dp`, counter end `34dp` 적용을 확인했다. +- 2026-05-28 Phase 9 최종 검증: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.banner.*"`, `./gradlew :app:assembleDebug`, `./gradlew :app:ktlintCheck` 실행 결과 모두 `BUILD SUCCESSFUL`을 확인했다. +- 2026-05-28 Phase 10 RED: `BannerViewTest`에 최초 `measure/layout` 직후 현재 adapter position의 item 중심이 `BannerView` 중심과 일치하는지 검증하는 회귀 테스트를 추가했다. Production 보정 전 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.banner.BannerViewTest"` 실행 결과 `배너 view는 최초 layout 직후 현재 item 중심을 view 중심에 맞춘다` 테스트 실패를 확인했다. +- 2026-05-28 Phase 10 구현: `setItems()` 이후 최초 layout size 적용 시점에만 현재 adapter position을 다시 정렬하도록 `shouldAlignCurrentPositionAfterLayout` 플래그를 추가했다. 복수 banner에서는 `LinearLayoutManager.scrollToPositionWithOffset()`에 decoration 절반 offset을 적용해 초기 child content 중심을 view 중심에 맞추고, 수동 이동 이후에는 해당 보정을 반복하지 않도록 했다. +- 2026-05-28 Phase 10 GREEN: 구현 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.banner.BannerViewTest"` 실행 결과 `BUILD SUCCESSFUL`을 확인했다. +- 2026-05-28 Phase 10 수동 확인 기록: 실제 기기/Android Studio XML layout editor는 이 환경에서 열 수 없어 직접 시각 확인은 수행하지 못했다. 대신 Robolectric 검증으로 최초 layout 직후 현재 item 중심과 view 중심 일치, 좌우 `20dp` 기준 위치, 기존 자동/수동 슬라이드 테스트 유지를 확인했다. +- 2026-05-28 Phase 10 최종 검증: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.banner.*"`, `./gradlew :app:assembleDebug`, `./gradlew :app:ktlintCheck` 실행 결과 모두 `BUILD SUCCESSFUL`을 확인했다. diff --git a/docs/prd/20260527_배너컴포넌트_prd.md b/docs/prd/20260527_배너컴포넌트_prd.md index 228232f9..7c302f48 100644 --- a/docs/prd/20260527_배너컴포넌트_prd.md +++ b/docs/prd/20260527_배너컴포넌트_prd.md @@ -69,11 +69,14 @@ Figma `24:5525` 기준의 이미지 배너를 XML + Kotlin custom view 기반 - 현재 배너 item은 부모 영역의 가로 가운데에 정렬한다. - 현재 배너 item은 화면 왼쪽에서 `20dp`, 오른쪽에서 `20dp` 떨어진 위치를 기준으로 표시한다. - 배너가 여러 개이면 item 사이 간격은 `8dp`다. +- 배너가 여러 개일 때 item 간격은 시각 중심이 밀리지 않도록 좌우 대칭 offset으로 적용한다. 예: 실제 item 사이 간격 `8dp`를 유지하되 각 item의 좌우 offset을 `4dp`씩 나누거나, 동등하게 `PagerSnapHelper` 기준 중심과 이미지 중심이 일치하는 방식을 사용한다. +- 배너가 여러 개일 때 최초 렌더링 직후, 자동/수동 슬라이드가 한 번도 발생하기 전에도 현재 banner item은 화면 중앙에 정렬되어야 한다. - 배너가 여러 개이면 현재 배너 좌우 빈 공간에 이전/다음 배너 일부가 보여야 한다. - 배너 이미지는 전체 영역을 채우며 `centerCrop` 기준으로 표시한다. - 배너 root와 이미지 영역은 `radius_14` 또는 `14dp` radius를 적용한다. - 카운터는 배너 우상단에 배치한다. - 카운터 위치는 상단 `14dp`, 오른쪽 `14dp`를 기준으로 한다. +- 카운터의 오른쪽 `14dp` 기준은 `BannerView` root가 아니라 현재 표시 중인 중앙 banner item의 우측 경계 기준이다. `BannerView`가 `match_parent`이고 item이 좌우 `20dp` inset 안쪽에 있으면 root 기준 end margin은 `20dp + 14dp`가 되어야 한다. - 카운터 배경은 `rgba(0,0,0,0.7)`에 가까운 반투명 검정, capsule radius를 적용한다. - 카운터 내부 padding은 horizontal `6dp`, vertical `4dp`를 기준으로 한다. - 카운터 텍스트는 Pretendard Medium, `14sp`, line-height `1.45`를 기준으로 한다. @@ -153,6 +156,9 @@ Figma `24:5525` 기준의 이미지 배너를 XML + Kotlin custom view 기반 - `BannerView`는 `layout_width="match_parent"`, `layout_height="wrap_content"` 선언에서 기기 폭에 따라 적절한 높이로 측정된다. - 현재 배너는 가운데 정렬되고 화면 좌우에 각각 `20dp` 기준 여백을 가진다. - 배너 목록 2개 이상에서는 item 간격 `8dp`와 좌우 이전/다음 배너 일부 노출이 적용된다. +- 배너 목록 2개 이상에서도 현재 banner item의 이미지 중심과 snap 기준 중심이 일치하며, item spacing 때문에 이미지가 좌우 어느 한쪽으로 치우쳐 보이지 않는다. +- 배너 목록 2개 이상에서 최초 표시 상태와 슬라이드 이후 상태 모두 현재 banner item이 동일한 중심 정렬 기준을 만족한다. +- 카운터는 중앙 banner item 내부 우상단 `14dp` 위치에 표시되며, root 기준 우상단이 아니라 item 기준 우상단과 일치한다. - 배너 목록 0개에서는 컴포넌트 영역이 숨겨진다. - 배너 목록 1개에서는 단일 이미지만 표시되고 자동 전환, 스와이프, 카운터가 비활성화된다. - 배너 목록 2개 이상에서는 5초 자동 전환과 350ms 전환 애니메이션이 동작한다.