docs(content): 콘텐츠 전체보기 문서를 정리한다

This commit is contained in:
2026-06-27 05:29:35 +09:00
parent 0454980fae
commit 8da39949e5
2 changed files with 759 additions and 0 deletions

View File

@@ -0,0 +1,248 @@
# PRD: 콘텐츠 전체보기
## 1. Overview
홈 추천 탭과 콘텐츠 추천 탭의 일부 섹션에서 `전체보기` 진입을 제공하고, `New&Hot``처음부터 함께 성장!`은 신규 콘텐츠 전체보기 화면에서 `GET /api/v2/contents` API로 페이징 목록을 표시한다.
작성일: 2026-06-27
---
## 2. Problem
- 콘텐츠 추천 탭의 섹션 타이틀 우측 chevron이 현재 전체보기 이동 정책과 연결되어 있지 않다.
- `New&Hot``처음부터 함께 성장!`은 기존 콘텐츠 탭 내부 `전체` 탭의 카테고리/정렬 조합으로 표현하지 않고, 별도 API 기반 전체보기 화면이 필요하다.
- 그 외 콘텐츠 추천 섹션은 신규 화면을 만들지 않고 기존 `콘텐츠 탭 - 전체`의 특정 카테고리/정렬 상태로 이동해야 한다.
- 동일한 오디오 카드, 태그, 페이징 목록 UI가 이미 V2 패키지 하위에 있으므로 신규 UI를 중복 작성하지 않도록 재사용 후보를 먼저 정리해야 한다.
---
## 3. Goals
- 홈 추천 탭의 `처음부터 함께 성장!` 섹션 우측에 chevron을 표시하고 신규 콘텐츠 전체보기 화면으로 이동한다.
- 콘텐츠 추천 탭에서 전체보기가 필요한 섹션 제목 우측에 chevron을 표시한다.
- 콘텐츠 추천 탭의 `New&Hot` 섹션은 신규 콘텐츠 전체보기 화면으로 이동한다.
- 콘텐츠 추천 탭의 `오직 보이스온에서만!`, `새로 올라온 오디오`, `무료 오디오`, `포인트 오디오`는 기존 `콘텐츠 탭 - 전체`의 지정 상태로 이동한다.
- 신규 콘텐츠 전체보기 화면은 `type = NEW_AND_HOT_AUDIO` 또는 `type = FIRST_AUDIO_CONTENT``GET /api/v2/contents`를 호출한다.
- 신규 콘텐츠 전체보기 화면은 Figma node `482:15105``detail_ado_001` 구조를 기준으로 검은 배경, title bar, 2열 오디오 카드 그리드, 스크롤 페이징을 제공한다.
- V2 패키지 하위 기존 위젯 중 재사용 가능한 후보를 문서에 기록한다.
---
## 4. Non-Goals
- 이번 PRD 작성 단계에서는 코드, 리소스, 레이아웃 파일을 구현하지 않는다.
- 콘텐츠 상세, 시리즈 상세, 결제, 보관함 기능은 변경하지 않는다.
- 레거시 화면 또는 레거시 API 파일을 직접 수정하지 않는다.
- `GET /api/v2/audio/contents` API 계약은 변경하지 않는다.
- 신규 콘텐츠 전체보기 화면에 별도 정렬, 필터, 검색, pull-to-refresh, skeleton loading을 추가하지 않는다.
- `New&Hot``처음부터 함께 성장!` 외 섹션을 신규 전체보기 API로 조회하지 않는다.
- Figma localhost asset URL을 앱 코드에 직접 의존하지 않는다.
---
## 5. Target Users
- 홈 추천 탭에서 `처음부터 함께 성장!` 콘텐츠를 더 많이 탐색하려는 사용자.
- 콘텐츠 추천 탭에서 `New&Hot`, 최신 오디오, 무료/포인트 오디오, 오리지널 콘텐츠를 섹션별로 더 보고 싶은 사용자.
- V2 메인 홈/콘텐츠 화면과 신규 전체보기 화면을 구현/유지보수하는 Android 개발자.
---
## 6. User Stories
- 사용자는 홈 추천 탭의 `처음부터 함께 성장!` 섹션 제목 우측 chevron을 눌러 같은 성격의 콘텐츠 전체 목록을 보고 싶다.
- 사용자는 콘텐츠 추천 탭의 `New&Hot` 섹션 제목 우측 chevron을 눌러 `New&Hot` 전체 목록을 보고 싶다.
- 사용자는 콘텐츠 추천 탭의 `오직 보이스온에서만!`을 누르면 콘텐츠 탭의 `전체` 내부에서 오리지널 카테고리가 선택된 화면으로 이동하길 기대한다.
- 사용자는 콘텐츠 추천 탭의 `새로 올라온 오디오`를 누르면 콘텐츠 탭의 `전체` 내부에서 오디오 카테고리가 선택된 화면으로 이동하길 기대한다.
- 사용자는 콘텐츠 추천 탭의 `무료 오디오` 또는 `포인트 오디오`를 누르면 콘텐츠 탭의 `전체` 내부에서 해당 카테고리와 인기순 정렬이 선택된 화면으로 이동하길 기대한다.
- 사용자는 신규 전체보기 화면에서 콘텐츠를 2열 그리드로 탐색하고, 목록 하단에 도달하면 다음 페이지가 이어서 로드되길 기대한다.
---
## 7. Core Features
### Feature A. 전체보기 섹션 chevron 표시
#### Requirements
- 전체보기 진입이 필요한 섹션 제목 우측에는 `view_section_title.xml``iv_section_title_chevron`을 표시한다.
- 홈 추천 탭에서는 `처음부터 함께 성장!` 섹션에 chevron을 표시하고, 클릭 시 신규 콘텐츠 전체보기 화면에 `type = FIRST_AUDIO_CONTENT`로 진입한다.
- 콘텐츠 추천 탭에서는 아래 섹션에 chevron을 표시한다.
- `오직 보이스온에서만!`
- `새로 올라온 오디오`
- `New&Hot`
- `무료 오디오`
- `포인트 오디오`
- 콘텐츠 추천 탭의 `댓글 많은 오디오`, `추천 오디오`는 이번 요구사항에 전체보기 목적지가 없으므로 chevron을 표시하지 않는다.
- 섹션 데이터가 비어 섹션 자체가 숨겨지는 경우 chevron도 함께 노출되지 않는다.
#### Edge Cases
- 빠르게 chevron을 중복 탭해도 동일 화면이 중복으로 여러 개 쌓이지 않도록 기존 navigation guard 패턴을 우선 따른다.
- 전체보기 목적지에 필요한 enum 또는 tab 상태가 유효하지 않으면 이동하지 않는다.
### Feature B. 콘텐츠 추천 탭 전체보기 라우팅
#### Requirements
- 콘텐츠 추천 탭의 전체보기 이동 규칙은 아래와 같다.
| 섹션 | 이동 목적지 |
| --- | --- |
| `오직 보이스온에서만!` | `콘텐츠 탭 - 전체` -> `오리지널` 카테고리 선택 |
| `새로 올라온 오디오` | `콘텐츠 탭 - 전체` -> `오디오` 카테고리 선택 |
| `New&Hot` | 신규 콘텐츠 전체보기 화면 -> `type = NEW_AND_HOT_AUDIO` |
| `무료 오디오` | `콘텐츠 탭 - 전체` -> `무료` 카테고리 선택 -> `인기순` 정렬 |
| `포인트 오디오` | `콘텐츠 탭 - 전체` -> `포인트` 카테고리 선택 -> `인기순` 정렬 |
- 기존 `콘텐츠 탭 - 전체`로 이동하는 경우 `ContentMainFragment` 내부 탭은 `전체`가 선택되어야 한다.
- `무료 오디오`, `포인트 오디오``ContentSort.POPULAR`에 해당하는 정렬 상태로 진입한다.
- `오직 보이스온에서만!``오리지널` 카테고리로 진입한다.
#### Edge Cases
- 이미 콘텐츠 탭에 있는 상태에서 전체보기 이동을 누르면 새 메인 화면을 중복 생성하지 않고 현재 `ContentMainFragment`의 내부 상태 전환을 우선 검토한다.
- 홈 탭에서 콘텐츠 탭 내부 `전체`로 이동해야 하는 후속 요구가 생기면 `MainActivity`/`MainV2` 탭 전환 계약을 별도 계획에서 확인한다.
### Feature C. 신규 콘텐츠 전체보기 화면
#### Requirements
- 신규 화면은 기존 로직 수정이 아닌 신규 `Activity`, `ViewModel`, API, Repository, DTO, adapter/helper로 구현한다면 `kr.co.vividnext.sodalive.v2` 패키지 하위에 작성한다.
- 화면 title bar 제목은 진입 type에 따라 아래처럼 표시한다.
- `NEW_AND_HOT_AUDIO`: `New&Hot`
- `FIRST_AUDIO_CONTENT`: `처음부터 함께 성장!`
- title bar는 검은 배경, 좌측 back chevron, 22sp bold 제목 구조를 따른다.
- 콘텐츠 목록은 Figma node `482:15105` 기준으로 2열 오디오 카드 그리드로 표시한다.
- 카드에는 썸네일, 제목, 크리에이터 닉네임, 무료/포인트/FIRST/오리지널/성인 태그를 응답 값에 따라 표시한다.
- 카드 터치 시 기존 오디오 콘텐츠 상세 화면으로 이동한다.
- 목록은 첫 페이지 로딩, 빈 목록, 에러, 추가 페이지 로딩 상태를 구분한다.
- `hasNext = true`이고 사용자가 목록 하단에 접근하면 다음 `page`를 요청한다.
#### Figma Reference
- URL: `https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-공유용-보이스온-UI-UX-기획문서?node-id=482-15105&m=dev`
- node: `482:15105`
- frame name: `detail_ado_001`
- 확인된 구조:
- 화면 배경: black
- title bar height: 60
- title: `New&Hot`
- content start: title bar 하단 이후
- grid: 2열, 카드 폭 약 185, 카드 간격 약 4
- thumbnail: 정사각형, radius 14
- label: 제목 18sp bold, 크리에이터명 14sp medium, 한 줄 말줄임
- tags: FIRST, point, free, original audio, adult badge 조합
#### Edge Cases
- `contentId <= 0`인 item은 상세 이동을 무시한다.
- `coverImage`가 null 또는 blank이면 기존 이미지 로딩 placeholder/null 처리 정책을 따른다.
- 제목 또는 크리에이터명이 길면 한 줄 말줄임 처리한다.
- 첫 페이지 응답의 `items`가 비어 있으면 빈 목록 상태를 표시한다.
- 추가 페이지 실패 시 기존 목록은 유지하고 재시도 가능한 상태를 제공한다.
### Feature D. 신규 콘텐츠 전체보기 API
#### API Contract
```kotlin
GET /api/v2/contents
```
#### Query Parameters
```kotlin
page: Int = 0
size: Int = 20
type: ContentOverviewType = ContentOverviewType.NEW_AND_HOT_AUDIO
```
#### Response Data Class
```kotlin
data class ContentOverviewPageResponse(
val type: ContentOverviewType,
val items: List<ContentOverviewItemResponse>,
val page: Int,
val size: Int,
@SerializedName("hasNext")
val hasNext: Boolean
)
enum class ContentOverviewType {
NEW_AND_HOT_AUDIO,
FIRST_AUDIO_CONTENT
}
data class ContentOverviewItemResponse(
val contentId: Long,
val title: String,
val coverImage: String?,
val price: Int,
@SerializedName("isPointAvailable")
val isPointAvailable: Boolean,
val creatorNickname: String,
@SerializedName("isAdult")
val isAdult: Boolean,
@SerializedName("isFirstContent")
val isFirstContent: Boolean,
@SerializedName("isOriginalSeries")
val isOriginalSeries: Boolean
)
```
#### Requirements
- API 기본값은 `page = 0`, `size = 20`, `type = NEW_AND_HOT_AUDIO`로 취급한다.
- query parameter key는 모두 소문자 `page`, `size`, `type`을 사용한다.
- 앱에서는 진입 목적에 맞게 `type`을 명시적으로 전달한다.
- 응답 DTO는 서버 계약을 변경하지 않는다.
- 서버 예시 class에 Jackson `@JsonProperty`가 포함되어 있더라도 앱 구현에서는 기존 Gson 관례에 맞춰 `@SerializedName`을 사용한다.
- UI model에서는 `price == 0`이면 무료 태그, `isPointAvailable == true`이면 포인트 태그, `isFirstContent == true`이면 FIRST 태그, `isOriginalSeries == true`이면 오리지널 태그, `isAdult == true`이면 성인 배지로 매핑한다.
#### Edge Cases
- 응답 `type`이 요청 `type`과 다르면 현재 요청 type 기준으로 화면 제목을 유지하고, 데이터 혼입 방지 정책은 구현 계획에서 확정한다.
- `hasNext = false`이면 다음 페이지를 요청하지 않는다.
- 동일 type에서 추가 페이지 요청 중 중복 요청을 방지한다.
- 다른 type의 신규 전체보기 화면을 열 때는 이전 화면의 page/items 상태를 공유하지 않는다.
---
## 8. UX / UI Expectations
- 신규 전체보기 화면은 V2의 검은 배경과 콘텐츠 카드 스타일을 유지한다.
- 상단 title bar는 스크롤되지 않고, 목록만 세로 스크롤된다.
- 2열 그리드는 화면 폭에 맞춰 item width를 계산하되, Figma의 185px 카드와 4px 간격 비율을 Android 화면에서 자연스럽게 유지한다.
- 오디오 카드는 기존 `AudioContentCardView`의 태그 표현과 최대한 일치시킨다.
- 성인 배지는 썸네일 우측 상단에 표시한다.
- 무료/포인트 태그는 썸네일 하단 좌측, FIRST/오리지널 태그는 썸네일 상단 좌측의 기존 패턴을 우선 따른다.
- 홈 추천 탭과 콘텐츠 추천 탭의 섹션 chevron은 기존 `view_section_title.xml``ic_chevron_right`를 사용한다.
### 재사용 가능한 V2 위젯/코드 후보
- `app/src/main/res/layout/view_section_title.xml`
- 섹션 제목과 우측 chevron 표시/숨김에 재사용 가능하다.
- `kr.co.vividnext.sodalive.v2.widget.AudioContentCardView`
- 신규 전체보기 2열 오디오 카드의 기본 카드 UI 후보이다.
- `kr.co.vividnext.sodalive.v2.widget.AudioContentCardSize`
- 기존 카드 크기 variant를 확인해 신규 2열 grid width 적용 가능성을 검토한다.
- `kr.co.vividnext.sodalive.v2.widget.AudioContentTag`
- 무료/포인트/FIRST/오리지널 태그 매핑에 재사용 가능하다.
- `kr.co.vividnext.sodalive.v2.main.content.ui.ContentAllAudioCardAdapter`
- 기존 콘텐츠 `전체` 탭 3열 grid adapter이며, 동적 grid item width 적용 패턴을 참고할 수 있다.
- `kr.co.vividnext.sodalive.v2.main.content.ui.ContentAudioCardAdapter`
- 추천 탭의 가로 오디오 카드 바인딩 패턴을 참고할 수 있다.
- `kr.co.vividnext.sodalive.v2.main.content.ui.ContentNewAndHotAdapter`
- `New&Hot` 섹션의 리스트형 표현에는 이미 사용 중이지만, 신규 전체보기 2열 grid에는 직접 재사용보다 item mapping/tag binding 참고 후보이다.
- `kr.co.vividnext.sodalive.v2.main.home.ui.HomeFirstAudioAdapter`
-`처음부터 함께 성장!` 섹션의 FIRST/point/free 태그 바인딩 패턴 참고 후보이다.
- `kr.co.vividnext.sodalive.v2.main.content.ContentAllTabViewModel`
- page/size/hasNext 기반 페이징 상태 관리 패턴 참고 후보이다.
- `kr.co.vividnext.sodalive.v2.main.content.data.MainContentAllTabApi`
- V2 콘텐츠 API의 Retrofit/Rx/`ApiResponse` 계약 참고 후보이다.
---
## 9. Technical Constraints
- Android Gradle 단일 `:app` 모듈에서 작업한다.
- 모든 명령은 저장소 루트에서 실행한다.
- 신규 화면/하위 코드는 `kr.co.vividnext.sodalive.v2` 패키지 하위에 작성한다.
- 레거시 파일은 직접 수정하지 않고, 필요한 기존 화면은 Intent 또는 wrapper/adapter로 호출한다.
- API 흐름은 기존 관례인 `Api -> Repository -> ViewModel -> Activity/Fragment`를 따른다.
- DI 추가가 필요하면 `AppDI.kt`의 Koin 구성 관례를 따른다.
- 외부 라이브러리를 추가하지 않는다.
- 공개 API 스키마와 서버 enum 값을 임의 변경하지 않는다.
- `BuildConfig` 값이나 민감정보를 로그/Toast/크래시 메시지에 노출하지 않는다.
---
## 10. Metrics
- 홈 추천 탭 `처음부터 함께 성장!` chevron 클릭 수.
- 콘텐츠 추천 탭 섹션별 chevron 클릭 수.
- 신규 콘텐츠 전체보기 화면 진입 수.
- 신규 콘텐츠 전체보기 화면의 다음 페이지 로드 성공/실패 수.
- 신규 콘텐츠 전체보기 화면에서 콘텐츠 상세로 이동한 클릭 수.
---
## 11. Open Questions
- 기존 `ContentMainFragment` 상태를 외부에서 특정 내부 탭/카테고리/정렬로 열기 위한 public navigation contract가 충분한지 구현 계획에서 확인해야 한다.