Files
sodalive-android/docs/prd/20260520_시리즈컴포넌트_prd.md

139 lines
8.8 KiB
Markdown

# PRD: 시리즈 컴포넌트
## 1. Overview
Figma `20:3875`, `20:3887`, `20:3906`, `20:3914` 디자인을 기준으로 Android XML Views 기반 화면에서 재사용할 수 있는 Series Content Card Component와 ORIGINAL series tag를 개발한다.
---
## 2. Problem
- 시리즈 콘텐츠 카드는 기존 오디오 콘텐츠 카드와 텍스트 구조는 유사하지만 썸네일이 정사각형이 아니라 세로형 poster ratio다.
- 시리즈 카드가 화면마다 개별 구현되면 poster 크기, radius, label 폭, typography, ORIGINAL 태그 위치가 달라질 수 있다.
- ORIGINAL 태그는 시리즈 카드 위에 overlay로 쓰이고 단독 태그 컴포넌트로도 관리되어야 하므로, 카드와 태그의 계약을 분리해야 한다.
- ORIGINAL tag node는 `20:3906`이다.
---
## 3. Goals
- 동일한 구조를 갖는 시리즈 콘텐츠 카드의 `large`, `small` 크기 변형을 제공한다.
- 썸네일은 Figma 기준 세로형 poster ratio를 유지하고, corner radius `14dp`, `centerCrop` 기준으로 표시한다.
- 제목과 크리에이터명은 한 줄 말줄임 처리하고, 크기별 Figma typography에 맞춘다.
- ORIGINAL 태그는 `ic_series_original` 아이콘, `ORIGINAL` 텍스트, `gray_900` 배경, `24dp` 높이를 가진 재사용 가능한 tag view 또는 include layout으로 제공한다.
- 시리즈 카드에는 ORIGINAL 태그 overlay 표시 여부를 선택할 수 있는 API를 제공한다.
- 기존 오디오 콘텐츠 카드와 기존 화면 일괄 적용은 변경하지 않고, 컴포넌트 추가와 사용 계약 문서화에 한정한다.
---
## 4. Non-Goals
- 이번 범위에서는 기존 `AudioContentCardView`를 시리즈 카드로 리팩터링하거나 통합하지 않는다.
- 기존 RecyclerView adapter나 기존 콘텐츠 목록 화면에 신규 시리즈 카드를 일괄 적용하지 않는다.
- 신규 Activity, Fragment, ViewModel을 만들지 않는다.
- Compose 컴포넌트 또는 Compose Theme를 추가하지 않는다.
- Figma에 표시된 하단 유료/무료 태그는 이번 범위에서 구현하지 않는다. 사용자 요청은 ORIGINAL 태그에 한정한다.
- Figma에 없는 그림자, dim overlay 색상, pressed animation, skeleton loading, placeholder 정책은 추가하지 않는다.
- 이미지 로딩 라이브러리 선택이나 실제 URL 로딩 정책을 컴포넌트 내부에 고정하지 않는다.
---
## 5. Target Users
- XML 레이아웃을 작성하거나 유지보수하는 Android 개발자.
- v2 화면에서 시리즈 콘텐츠 카드 UI를 리스트/캐러셀/그리드에 재사용하려는 개발자.
---
## 6. User Stories
- 개발자는 같은 시리즈 카드 형태를 크기만 바꿔 재사용하고 싶다.
- 개발자는 시리즈 콘텐츠가 ORIGINAL인지 여부에 따라 상단 태그를 표시하고 싶다.
- 개발자는 썸네일, 콘텐츠 제목, 크리에이터명을 ViewBinding 또는 custom view API로 바인딩하고 싶다.
- 개발자는 긴 제목과 크리에이터명이 레이아웃을 밀어내지 않고 한 줄 말줄임되기를 원한다.
---
## 7. Core Features
### Series Content Card Component
Figma 2개 카드 크기와 ORIGINAL tag 사용 예시를 하나의 XML + Kotlin custom view 컴포넌트로 제공한다.
#### Figma References
- Series card large: https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=20-3875&m=dev
- Series card small: https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=20-3887&m=dev
- ORIGINAL tag: https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=20-3906&m=dev
- ORIGINAL tag usage example: https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=20-3914&m=dev
#### Requirements
- 공통 구조: 세로형 thumbnail + 하단 label contents 영역.
- Thumbnail corner radius: `radius_14`.
- Thumbnail scale type: `centerCrop`.
- Thumbnail과 label 영역 사이 gap은 `spacing_8` 기준으로 맞춘다.
- Title: `maxLines=1`, `ellipsize=end`, text color white.
- Creator name: `maxLines=1`, `ellipsize=end`, text color `gray_500`.
- Title과 creator name 사이 gap은 `2dp`로 맞춘다. 기존 `spacing_2` 토큰이 없으므로 구현 단계에서 XML 직접값 또는 컴포넌트 내부 상수로 처리한다.
- ORIGINAL 태그는 thumbnail 좌상단에 붙고, 태그 container는 thumbnail radius와 충돌하지 않도록 우하단 corner radius `8dp`를 가진다.
- ORIGINAL 태그는 `ic_series_original` 아이콘을 사용한다.
#### Size Variants
| Size | Figma node | Card width | Thumbnail | Label width | Title style | Creator style | Thumbnail-label gap |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `large` | `20:3875` | `163dp` | `163dp x 230dp` | `151dp` | `Typography.Heading4` | `Typography.Body5` | `spacing_8` |
| `small` | `20:3887` | `122dp` | `122dp x 172dp` | `114dp` | `Typography.Body1` | `Typography.Caption2` | `spacing_8` |
#### ORIGINAL Tag Contract
| Property | Value |
| --- | --- |
| Figma node | `20:3906` |
| Width | `101dp` |
| Height | `24dp` |
| Background | `gray_900` (`#202020`) |
| Icon | `ic_series_original`, `14dp x 14dp`, left `8dp`, vertically centered |
| Text | `ORIGINAL`, white, Phosphate Solid style if available |
| Text fallback | 프로젝트에 Phosphate font가 없으면 이미지/폰트 자산 추가 여부를 확인하고, 없을 때는 기존 font 리소스 중 가장 가까운 bold 스타일을 사용한다 |
| Text position | left `26dp`, top `2dp` 기준 |
| Overlay position | thumbnail top-left |
| Overlay corner | `bottomEnd` radius `8dp` |
#### Edge Cases
- 제목이 길면 한 줄 말줄임 처리한다.
- 크리에이터명이 길면 한 줄 말줄임 처리한다.
- 제목 또는 크리에이터명이 비어 있으면 호출부 데이터 문제로 간주하고 컴포넌트는 전달된 값을 그대로 표시한다.
- 썸네일 이미지가 없거나 로딩 실패한 경우의 placeholder 정책은 호출부 또는 이미지 로딩 계층에서 결정한다.
- size가 지정되지 않으면 `large`를 기본값으로 사용한다.
- ORIGINAL 여부가 false이면 태그 영역은 `gone` 처리되어 thumbnail만 표시된다.
---
## 8. UX / UI Expectations
- 두 크기 모두 같은 카드 구조와 세로형 poster thumbnail 형태를 유지한다.
- 썸네일 radius는 모든 크기에서 14dp로 동일하다.
- `large` 카드는 카드 폭 163dp, thumbnail 163dp x 230dp, label 폭 151dp로 사용한다.
- `small` 카드는 카드 폭 122dp, thumbnail 122dp x 172dp, label 폭 114dp로 사용한다.
- 텍스트는 어두운 배경 위 사용을 전제로 white/`gray_500` 색상 대비를 유지한다.
- ORIGINAL 태그는 thumbnail 좌상단에 고정되어 Figma `20:3914`처럼 이미지 위에 overlay된다.
---
## 9. Technical Constraints
- 현재 프로젝트는 XML Views + ViewBinding 기반이므로 XML 레이아웃과 Kotlin custom view 패턴을 우선한다.
- 신규 Kotlin 코드는 `kr.co.vividnext.sodalive.v2.widget` 패키지 하위에 둔다.
- 재사용 레이아웃은 `app/src/main/res/layout` 아래에 둔다.
- 색상, spacing, radius, typography는 기존 `colors.xml`, `dimens.xml`, `typography.xml` 토큰을 우선 사용한다.
- 기존 `AudioContentCardSize`처럼 size contract는 순수 Kotlin 객체 또는 enum으로 분리해 단위 테스트 가능하게 한다.
- `ic_series_original.png` 리소스가 이미 존재하면 그대로 사용하고, 없으면 구현 전 디자인 에셋을 추가한다.
- 기존 화면의 동작이나 레이아웃을 요청 없이 변경하지 않는다.
---
## 10. Metrics
- `large`, `small` 2개 size variant의 카드 폭, 썸네일 크기, label 폭이 문서와 구현에서 일치한다.
- 제목과 크리에이터명은 한 줄 말줄임 처리된다.
- ORIGINAL 태그는 `ic_series_original` 아이콘과 `ORIGINAL` 텍스트를 표시한다.
- ORIGINAL 태그 표시 여부를 호출부에서 제어할 수 있다.
- size contract 단위 테스트가 통과한다.
- Android 리소스 병합 및 디버그 빌드가 성공한다.
- 기존 오디오 콘텐츠 카드와 기존 화면 파일은 변경되지 않는다.
---
## 11. Open Questions
- 사용자가 `20:3096`은 오타이고 `20:3906`이 맞다고 정정했으므로, 이 문서는 `20:3906`을 ORIGINAL 태그 구현 기준으로 삼는다.
- Figma의 ORIGINAL 텍스트는 Phosphate Solid이며, 프로젝트의 `@font/phosphate_solid` 리소스를 사용한다.
- 하단 유료/무료 태그는 Figma generated code에 포함되지만 사용자 요청에 없으므로 제외한다.