docs(creator): 오디오 탭 구현 계획을 기록한다

This commit is contained in:
2026-06-19 14:26:11 +09:00
parent dacd3a67a1
commit 88dd7cc04c
2 changed files with 868 additions and 0 deletions

View File

@@ -0,0 +1,322 @@
# PRD: 크리에이터 채널 오디오 탭
## 1. Overview
크리에이터 채널의 `오디오` 탭에서 오디오 콘텐츠 소장률, 테마 필터, 정렬, 오디오 콘텐츠 목록, 내 채널 업로드 CTA와 스크롤 pagination을 제공한다.
---
## 2. Problem
- 크리에이터 채널 컨테이너와 `라이브` 탭은 별도 문서에서 정의되었지만, `오디오` 탭의 API 계약과 Figma 기반 UI 요구사항은 별도 정의가 필요하다.
- 사용자는 크리에이터 채널에서 전체 오디오 수, 구매/소장 현황, 테마별 콘텐츠 목록을 한 화면에서 탐색할 수 있어야 한다.
- 오디오 콘텐츠 item은 기존 라이브 다시듣기 item과 동일한 구매/재생/보유 상태를 유지하되, `duration` 뒤에 `seriesName`이 표시될 수 있어야 한다.
- 테마 필터와 정렬이 함께 존재하므로 query parameter 상태와 UI 선택 상태가 일관되게 유지되어야 한다.
- 사용자가 크리에이터 본인인 경우 소장률 대신 오디오 업로드 진입 CTA를 제공해야 한다.
- 목록이 길어질 수 있으므로 `CreatorChannelAudioTabResponse.hasNext == true`일 때 다음 페이지를 자동 로딩해야 한다.
---
## 3. Goals
- Figma `290:9015` 기준으로 크리에이터 채널 `오디오` 탭 UI 요구사항을 정의한다.
- API endpoint `GET /api/v2/creator-channels/{creatorId}/audio`를 기준으로 최초 조회, 테마 변경, 정렬 변경, pagination 요구사항을 정의한다.
- 최초 조회 query parameter 기본값은 `page=0`, `size=20`, `sort=LATEST`로 둔다.
- 최초 조회 시 `themeId` query parameter는 보내지 않는다.
- 응답의 `themes`에는 `전체` 항목이 포함되지 않으므로, 클라이언트가 맨 앞에 `전체` tab을 추가한다.
- 응답의 `themeId == null` 상태는 클라이언트가 추가한 `전체` tab 선택 상태로 표현한다.
- Sort-bar에는 전체 오디오 콘텐츠 수와 현재 정렬 label을 표시한다.
- 정렬 선택 방식과 옵션은 `라이브` 탭과 동일하게 적용한다.
- 소장률 섹션은 내 채널이 아니고 `전체` 테마 선택 상태일 때만 표시한다.
- 소장률 섹션에는 전체 유료 오디오 중 소장한 비율과 `purchasedAudioContentCount/paidAudioContentCount`를 표시한다.
- 내 채널인 경우 소장률 섹션을 숨기고 하단 고정 `오디오 올리기` CTA를 표시한다.
- 오디오 콘텐츠 목록은 기존 라이브 다시듣기 item UI를 재사용하되, `seriesName`이 있으면 `duration • seriesName` 형식으로 표시한다.
- `seriesName`이 없으면 시리즈 이름 UI를 숨기고 `duration`만 표시한다.
- 오디오 콘텐츠가 0개이면 오디오 콘텐츠가 있을 때 표시하는 UI를 모두 숨기고 empty 문구만 표시한다.
- 응답의 `hasNext``true`이면 현재 `page + 1` 페이지를 추가 로딩한다.
---
## 4. Non-Goals
- 크리에이터 채널 상단 header, title bar, 공통 main tab-bar 구조 자체를 재설계하지 않는다.
- `홈`, `라이브`, `시리즈`, `화보`, `커뮤니티`, `팬Talk`, `후원` 탭의 상세 구현은 이번 범위에서 제외한다.
- 오디오 상세, 결제, 대여, 소장, 재생 플로우 내부 동작 변경은 이번 범위에서 제외한다.
- 오디오 업로드/등록 화면 내부 구현은 이번 범위에서 제외하고 CTA 진입점만 정의한다.
- API schema를 임의 변경하거나 서버 응답 필드명을 클라이언트에서 새로 정의하지 않는다.
- 정렬/테마 외 별도 검색, 복수 필터, pull-to-refresh, skeleton/shimmer는 이번 범위에서 제외한다.
- Figma asset을 localhost URL 그대로 앱 코드에 직접 의존하지 않는다.
---
## 5. Target Users
- 크리에이터 채널에서 오디오 콘텐츠를 테마별로 탐색하는 앱 사용자.
- 구매 가능/보유/대여 상태를 확인한 뒤 오디오 콘텐츠를 재생하거나 상세로 이동하려는 앱 사용자.
- 특정 크리에이터의 오디오 콘텐츠 중 자신이 얼마나 소장했는지 확인하려는 앱 사용자.
- 본인 채널에서 오디오 업로드 화면으로 진입하려는 크리에이터.
- `kr.co.vividnext.sodalive.v2` 하위 크리에이터 채널 탭을 구현/유지보수하는 Android 개발자.
---
## 6. User Stories
- 사용자는 크리에이터 채널의 `오디오` 탭에서 전체 오디오 콘텐츠 수를 확인하고 싶다.
- 사용자는 전체 오디오 중 자신이 소장한 비율과 소장 개수를 확인하고 싶다.
- 사용자는 테마 tab을 선택해 특정 테마의 오디오 콘텐츠만 보고 싶다.
- 사용자는 최신순 등 라이브 탭과 동일한 방식으로 오디오 콘텐츠를 정렬하고 싶다.
- 사용자는 오디오 콘텐츠의 제목, 재생 시간, 시리즈 이름, 가격/무료/소장중/대여중 상태를 목록에서 구분하고 싶다.
- 사용자는 시리즈 이름이 없는 콘텐츠에서는 불필요한 빈 구분자나 빈 텍스트가 보이지 않길 기대한다.
- 사용자는 목록 하단까지 스크롤하면 다음 페이지가 자연스럽게 이어서 로딩되길 기대한다.
- 사용자는 크리에이터가 아직 오디오를 준비 중인 경우 불필요한 필터/정렬 UI 없이 empty 문구만 보고 싶다.
- 크리에이터 본인은 본인 채널의 `오디오` 탭에서 하단 고정 버튼으로 오디오 업로드 화면에 진입하고 싶다.
---
## 7. Core Features
### Creator Channel Audio Tab API
`오디오` 탭 진입, 테마 변경, 정렬 변경, 추가 로딩 시 크리에이터별 오디오 탭 데이터를 조회한다.
#### Requirements
- API endpoint는 `GET /api/v2/creator-channels/{creatorId}/audio`이다.
- `creatorId`는 path variable로 전달한다.
- Query parameters는 `sort`, `page`, `size`, `themeId`를 사용한다.
- 최초 조회 기본값은 `page=0`, `size=20`, `sort=ContentSort.LATEST`이다.
- 최초 조회 시 `themeId`는 보내지 않는다.
- `sort`는 기존 `ContentSort` enum 값을 그대로 전달한다.
- `ContentSort`는 기존에 만들어져 있는 타입을 재사용한다.
- `CreatorChannelAudioContentResponse`는 기존에 만들어져 있는 타입을 재사용한다.
- `hasNext == true`일 때 다음 페이지 요청은 현재 응답의 `page + 1` 값을 사용한다.
- 중복 pagination 요청이 발생하지 않도록 loading 중 추가 요청을 막아야 한다.
- 정렬 변경 시 기존 목록과 page 상태를 초기화하고 첫 페이지부터 다시 조회한다.
- 테마 변경 시 기존 목록과 page 상태를 초기화하고 첫 페이지부터 다시 조회한다.
- `themeId == null` 또는 전체 테마 선택 상태에서는 `themeId` query parameter를 보내지 않는다.
- 서버 응답의 `themes`에는 `전체` 항목이 포함되지 않는다.
- 클라이언트는 `themes` 응답 앞에 `전체` tab을 synthetic item으로 추가해 표시한다.
- 서버 응답의 `themeId == null``전체` tab 선택 상태로 해석한다.
#### Response Contract
```kotlin
data class CreatorChannelAudioTabResponse(
val audioContentCount: Int,
val paidAudioContentCount: Int,
val purchasedAudioContentCount: Int,
val purchasedAudioContentRate: Double,
val themes: List<CreatorChannelAudioThemeResponse>,
val audioContents: List<CreatorChannelAudioContentResponse>,
val sort: ContentSort,
val themeId: Long?,
val page: Int,
val size: Int,
val hasNext: Boolean
)
data class CreatorChannelAudioThemeResponse(
val themeId: Long,
val themeName: String
)
```
#### Edge Cases
- 최초 조회 실패 시 기존 크리에이터 채널 탭의 error/retry 패턴을 따른다.
- 정렬 변경 실패 또는 테마 변경 실패 시 현재 프로젝트의 에러 표시/재시도 패턴을 구현 계획 단계에서 확인해 따른다.
- 다음 페이지 로딩 실패 시 기존 목록은 유지하고 기존 pagination 실패 표시 정책을 따른다.
- 다음 페이지 응답의 `audioContents`가 비어 있어도 `hasNext` 값 기준으로 이후 로딩 가능 여부를 갱신한다.
- 서버 응답의 `sort`, `themeId`, `page`, `size`가 요청 상태와 다를 경우 구현 계획 단계에서 기존 ViewModel 상태 동기화 패턴을 확인해 따른다.
- `duration == null`인 오디오 콘텐츠는 서버에서 내려오지 않는 것이 계약이다.
- 예외적으로 `duration == null`인 오디오 콘텐츠가 내려오면 해당 콘텐츠 item 자체를 목록에서 숨긴다.
### Theme Tab Bar
오디오 탭 상단의 테마 tab-bar는 클라이언트가 추가한 `전체` tab과 서버가 내려준 테마 목록을 표시하고 선택된 테마로 목록을 필터링한다.
#### Requirements
- Figma 전체 화면 기준 노드는 `290:9015`이며, 테마 tab-bar는 main tab-bar 아래에 위치한다.
- 첫 번째 tab은 클라이언트가 추가하는 `전체`이며 `themeId == null` 상태를 나타낸다.
- `CreatorChannelAudioTabResponse.themes`에는 `전체`가 포함되지 않는다.
- `themes` 응답 순서를 유지하되, 클라이언트에서 추가한 `전체` tab 뒤에 서버 테마 tab을 표시한다.
- 각 theme tab은 `CreatorChannelAudioThemeResponse.themeId`, `themeName`을 사용한다.
- `CreatorChannelAudioTabResponse.themeId == null`이면 `전체` tab을 선택 상태로 표시한다.
- `CreatorChannelAudioTabResponse.themeId`가 특정 theme id이면 해당 `themeId`를 가진 서버 테마 tab을 선택 상태로 표시한다.
- 선택된 tab은 Figma처럼 white background와 black text로 표시한다.
- 선택되지 않은 tab은 black background, gray border, white text로 표시한다.
- theme tab을 선택하면 `page=0`, 현재 `sort`, 선택한 `themeId`로 API를 다시 조회한다.
- 이미 선택된 theme tab을 다시 선택하면 API를 재호출하지 않는다.
- theme tab-bar는 가로 스크롤을 지원한다.
#### Edge Cases
- `themes`가 비어도 `전체` tab은 표시한다.
- 서버 응답 `themes`에 실수로 `전체`와 동일한 이름의 테마가 포함되어도 클라이언트가 추가한 `전체` tab과 서버 테마 tab을 별개로 취급한다.
- `themeName`이 긴 경우 tab 내부에서 텍스트와 주변 tab이 겹치지 않도록 기존 capsule tab 정책을 따른다.
- 선택된 `themeId`가 응답의 `themes`에 없으면 `전체` 선택 상태로 fallback하는지 여부는 구현 계획 단계에서 기존 탭 상태 정책을 확인해 결정한다.
### Sort Bar and Sort Menu
Sort-bar는 전체 오디오 콘텐츠 수와 현재 정렬 상태를 표시하고, 라이브 탭과 동일한 정렬 메뉴를 연다.
#### Requirements
- Figma 전체 화면 기준 Sort-bar는 `290:9015``sort-bar`이다.
- 좌측에는 `전체``audioContentCount`를 표시한다.
- 우측에는 현재 정렬 label과 정렬 icon을 표시한다.
- 정렬 선택 방식은 `라이브` 탭과 동일하다.
- 정렬 기본값은 `ContentSort.LATEST`이며 label은 한국어 기준 `최신순`이다.
- 정렬 옵션, label, 선택 표시, 메뉴 닫힘 동작은 `라이브` 탭 구현과 동일하게 재사용한다.
- 정렬 옵션을 선택하면 `page=0`, 선택된 `sort`, 현재 `themeId`로 API를 다시 조회한다.
- 선택 중인 정렬 옵션을 다시 선택하면 API 재호출 없이 메뉴만 닫는다.
#### Edge Cases
- 작은 화면에서 정렬 메뉴가 화면 우측 또는 하단을 벗어나지 않도록 라이브 탭과 동일한 위치 보정 정책을 적용한다.
- 다국어 label 길이가 길어져도 Sort-bar text와 icon이 겹치지 않아야 한다.
### Purchased Audio Rate Section
소장률 섹션은 사용자가 전체 유료 오디오 중 소장한 비율과 개수를 확인할 수 있게 한다.
#### Requirements
- Figma 기준 노드는 `290:9029`이다.
- Sort-bar 아래, 오디오 콘텐츠 목록 위에 표시한다.
- 내 채널이 아닌 경우에만 표시한다.
- `themeId == null`인 전체 테마 선택 상태에서만 표시한다.
- 특정 테마가 선택된 필터 상태에서는 표시하지 않는다.
- 카드 배경은 Figma 기준 `gray/900 #202020`, radius 14dp, 내부 padding 14dp를 따른다.
- 좌측 문구는 `전체 오디오의 n%를 소장하고 있어요.` 형식으로 표시한다.
- `purchasedAudioContentRate`는 서버에서 percent 값으로 내려오며, `n%`에 그대로 사용자 표시용 포맷만 적용한다.
- 우측에는 `purchasedAudioContentCount/paidAudioContentCount개`를 표시한다.
- 하단 progress bar는 `purchasedAudioContentRate`를 기준으로 채움 비율을 표시한다.
- `paidAudioContentCount`, `purchasedAudioContentCount`, `purchasedAudioContentRate`는 응답이 의미하는 서버 기준 값을 그대로 사용한다.
#### Edge Cases
- 내 채널인 경우 `themeId == null`이어도 소장률 섹션을 표시하지 않는다.
- `paidAudioContentCount == 0`이면 0 나누기 계산을 클라이언트에서 수행하지 않고, 서버의 `purchasedAudioContentRate` 또는 0% 표시 정책을 따른다.
- 긴 숫자나 다국어 문구에서도 좌측 문구와 우측 카운트가 겹치지 않아야 한다.
### Audio Content List
오디오 콘텐츠 목록은 기존 라이브 다시듣기 item UI와 동일하게 표시하되, 시리즈 이름 표시 규칙을 추가한다.
#### Requirements
- Figma 콘텐츠 item 기준 노드는 `290:9026`이다.
- `audioContents`를 세로 목록으로 표시한다.
- 각 item은 기존 라이브 다시듣기 item UI, 상태 표시, tag 표시, 가격/재생 CTA 정책을 재사용한다.
- 각 item은 `imageUrl`, `title`, `duration`, `seriesName`을 표시한다.
- `seriesName`이 있으면 secondary text를 `duration • seriesName` 형식으로 표시한다.
- `seriesName`이 없으면 시리즈 이름 UI를 숨기고 `duration`만 표시한다.
- `duration`은 필수 표시 값이며, `duration == null`인 콘텐츠는 목록에서 숨긴다.
- `isAdult`, `isPointAvailable`, `isFirstContent`, `isOriginalSeries`, `price`, `isOwned`, `isRented`는 기존 라이브 다시듣기/오디오 item 정책과 동일하게 매핑한다.
- item 터치 시 오디오 콘텐츠 상세 또는 재생 진입은 기존 오디오 콘텐츠 item 정책을 재사용한다.
#### Edge Cases
- `seriesName`이 blank string이면 없는 값으로 취급한다.
- `duration == null`이면 `seriesName` 존재 여부와 관계없이 해당 콘텐츠를 숨긴다.
- `isOwned == true``isRented == true`가 동시에 내려오면 기존 라이브 다시듣기 정책과 동일하게 우선순위를 적용한다.
- `title`이 긴 경우 Figma처럼 최대 2줄까지 표시하고 이후 말줄임 처리한다.
- secondary text가 긴 경우 한 줄 말줄임 처리한다.
- `imageUrl == null` 또는 이미지 로딩 실패 시 기존 이미지 placeholder 정책을 따른다.
### Pagination
오디오 콘텐츠 목록은 스크롤 하단 접근 시 다음 페이지를 로딩한다.
#### Requirements
- `CreatorChannelAudioTabResponse.hasNext == true`일 때만 다음 페이지를 요청한다.
- 다음 페이지는 마지막 성공 응답의 `page + 1`로 요청한다.
- 다음 페이지 요청에는 현재 `sort`, 현재 `themeId`, `size=20`을 유지한다.
- `themeId == null`이면 다음 페이지 요청에서도 `themeId`를 보내지 않는다.
- 다음 페이지 로딩 중에는 추가 page 요청을 중복으로 보내지 않는다.
- 다음 페이지 성공 시 기존 `audioContents` 뒤에 append한다.
- 정렬 변경 또는 테마 변경 시 pagination 상태를 초기화한다.
#### Edge Cases
- 빠른 스크롤로 load-more trigger가 반복 발생해도 page가 중복 append되지 않아야 한다.
- Fragment/View 재생성 후 현재 목록, 정렬, 테마, page 상태는 ViewModel 상태 보존 정책에 따라 유지되어야 한다.
### Empty State
오디오 콘텐츠가 없으면 오디오 콘텐츠가 있을 때 표시하는 UI를 모두 숨기고 empty 문구만 표시한다.
#### Requirements
- Figma 기준 노드는 `290:8965`이다.
- `audioContentCount == 0` 또는 표시 가능한 `audioContents`가 없는 전체 empty 상태이면 empty 상태를 표시한다.
- empty 문구는 `크리에이터가 오디오를 준비 중입니다.\n기대해 주세요!`이다.
- empty 상태에서는 테마 tab-bar, Sort-bar, 소장률 섹션, 오디오 콘텐츠 목록을 표시하지 않는다.
- empty 상태 표시 방식은 라이브 탭 empty 상태와 동일하게 적용한다.
- empty 문구는 화면 중앙 영역에 gray text로 표시하고 다국어 문자열 리소스로 관리한다.
- 내 채널 empty 상태에서도 empty 문구는 동일하게 표시한다.
- 내 채널 empty 상태에서 하단 CTA 표시 여부는 내 채널 CTA 정책을 따른다.
#### Edge Cases
- API 최초 조회 실패 상태는 empty 상태로 취급하지 않고 기존 error/retry 패턴을 따른다.
- `duration == null`인 콘텐츠를 숨긴 결과 표시 가능한 콘텐츠가 0개가 되면 empty 상태 정책을 적용한다.
### Owner Audio Upload CTA
로그인 사용자가 해당 크리에이터 본인인 경우 오디오 탭 하단에 고정된 오디오 업로드 버튼을 표시한다.
#### Requirements
- Figma 기준 노드는 `665:19008`이다.
- CTA 버튼 표시 방식은 라이브 탭의 `라이브 시작하기` CTA와 동일하게 적용한다.
- 로그인 사용자가 현재 크리에이터 채널의 본인이면 하단 고정 CTA 영역을 표시한다.
- 로그인 사용자가 현재 크리에이터 채널의 본인이 아니면 하단 고정 CTA 영역을 표시하지 않는다.
- 내 채널인 경우 소장률 섹션은 표시하지 않는다.
- CTA 영역은 화면 하단에 고정하고, 목록 스크롤과 함께 움직이지 않는다.
- CTA 버튼 label은 `오디오 올리기`이며 다국어 문자열 리소스로 관리한다.
- CTA 버튼 icon은 `ic_new_upload_audio` drawable 리소스를 사용한다.
- CTA가 표시되는 경우 목록 마지막 item 또는 empty 문구가 CTA에 가려지지 않도록 하단 padding 또는 inset을 추가한다.
- Android gesture navigation, soft navigation bar, display cutout 환경에서 CTA가 system navigation 영역과 겹치지 않도록 bottom inset을 반영한다.
- 버튼 터치 시 기존 또는 신규 오디오 업로드 진입 플로우로 이동한다. 업로드 화면 내부 구현은 이번 PRD 범위에서 제외한다.
#### Edge Cases
- 본인 여부 판정 데이터가 아직 로딩 중이면 CTA를 먼저 노출하지 않는다.
- 본인 여부 판정 실패 또는 API 실패 상태에서는 기존 에러 상태 정책을 따르고 CTA를 임의로 노출하지 않는다.
- 오디오 업로드 플로우 진입 중 중복 터치를 막는다.
### Suggested Implementation Phases
Figma 섹션 기준으로 구현 계획/TASK 문서를 작성할 때 아래 단위로 Phase를 나누는 것을 권장한다.
#### Phase Candidates
- Phase 1: API/DTO/Repository 계약 추가
- Phase 2: ViewModel 상태, sort/theme/pagination 로직
- Phase 3: Theme tab-bar 및 Sort-bar UI
- Phase 4: 소장률 섹션 UI와 표시 조건
- Phase 5: 오디오 콘텐츠 item/list UI와 시리즈 이름 표시
- Phase 6: empty 상태와 내 채널 CTA UI
- Phase 7: error/loading/pagination 검증 및 통합 확인
---
## 8. UX / UI Expectations
- 전체 배경은 Figma 기준 black이다.
- 공통 크리에이터 채널 header와 main tab-bar는 기존 크리에이터 채널 화면 정책을 따른다.
- 오디오 탭 선택 상태는 main tab-bar에서 cyan underline과 white text로 표시한다.
- 테마 tab-bar는 main tab-bar 아래에 위치하고, 선택된 tab은 white capsule로 표시한다.
- Sort-bar 높이, 좌우 여백, 텍스트 스타일은 라이브 탭 Sort-bar와 동일하게 맞춘다.
- 소장률 카드는 Sort-bar 아래에 14dp 좌우 여백을 두고, progress bar는 cyan fill과 dark gray track을 사용한다.
- 소장률 카드는 내 채널 또는 특정 테마 필터 상태에서는 표시하지 않는다.
- 오디오 콘텐츠 item은 88dp 정사각 썸네일, 14dp corner, title 영역, 우측 CTA/status 영역의 가로형 목록 구조를 따른다.
- empty 상태에서는 콘텐츠 UI를 모두 숨기고 Figma `290:8965`처럼 중앙 안내 문구만 표시한다.
- 내 채널 CTA는 라이브 탭 하단 CTA와 동일한 고정 방식으로 표시하고, cyan capsule 버튼과 `ic_new_upload_audio` icon을 사용한다.
- item 간 세로 간격과 좌우 여백은 Figma `290:9015`, `290:9026` 기준을 따른다.
- 문자열은 다국어 문자열 리소스로 관리한다.
- 텍스트와 아이콘은 작은 화면과 긴 다국어 문구에서도 겹치지 않아야 한다.
---
## 9. Technical Constraints
- Android Gradle 프로젝트의 `:app` 단일 모듈 구조를 유지한다.
- 신규 `Activity`, `Fragment`, `ViewModel` 및 연결된 하위 코드는 `kr.co.vividnext.sodalive.v2` 패키지 하위에 작성한다.
- 기존 크리에이터 채널 컨테이너, 탭 전환, sticky 동작을 불필요하게 재설계하지 않는다.
- 기존 `ContentSort``CreatorChannelAudioContentResponse`를 재사용한다.
- 정렬 메뉴는 라이브 탭과 동일한 구현/리소스/문자열 매핑을 우선 재사용한다.
- 내 채널 하단 CTA는 라이브 탭 CTA 구현/레이아웃/inset 처리 방식을 우선 재사용한다.
- 이미지 placeholder, 가격/포인트/무료/소장중/대여중/19금/original tag 표시 정책은 기존 라이브 다시듣기 및 오디오 item 구현과 맞춘다.
- API 실패, retry, pagination 실패 표시는 기존 프로젝트 패턴을 구현 계획 단계에서 확인해 따른다.
---
## 10. Metrics
- 오디오 탭 최초 진입 시 API 요청이 `page=0`, `size=20`, `sort=LATEST`, `themeId` 미전송으로 발생한다.
- 테마 선택 시 선택한 `themeId`로 첫 페이지가 재조회된다.
- 전체 테마 선택 시 `themeId` query parameter가 전송되지 않는다.
- 서버 응답 `themes``전체`가 없어도 화면 맨 앞에 `전체` tab이 표시된다.
- 서버 응답 `themeId == null`이면 `전체` tab이 선택 상태로 표시된다.
- 정렬 선택 시 라이브 탭과 동일한 정렬 옵션과 UI 동작이 적용된다.
- 내 채널이 아니고 전체 테마 상태일 때 `purchasedAudioContentRate`, `purchasedAudioContentCount`, `paidAudioContentCount`가 소장률 섹션에 정확히 표시된다.
- 내 채널 또는 특정 테마 필터 상태에서는 소장률 섹션이 표시되지 않는다.
- `seriesName`이 있는 콘텐츠는 `duration • seriesName`, 없는 콘텐츠는 `duration`만 표시된다.
- `duration == null`인 콘텐츠는 목록에서 제외된다.
- 오디오 콘텐츠가 0개이면 `크리에이터가 오디오를 준비 중입니다.\n기대해 주세요!` 문구만 표시된다.
- 내 채널에서는 하단 고정 `오디오 올리기` CTA가 `ic_new_upload_audio` icon과 함께 표시된다.
- `hasNext == true`일 때 다음 페이지가 한 번씩만 append된다.
---
## 11. Open Questions
- 현재 PRD 기준 추가 확인이 필요한 사항은 없다.