# 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, 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가 충분한지 구현 계획에서 확인해야 한다.