18 KiB
18 KiB
PRD: 메인 콘텐츠 탭 내부 전체 탭
1. Overview
메인 하단 콘텐츠 탭 안의 내부 전체 탭에서 GET /api/v2/audio/contents 응답을 사용해 오디오/시리즈/오리지널/무료/포인트 콘텐츠 목록을 제공한다.
상단 title bar와 내부 추천/랭킹/전체 tab bar는 기존 콘텐츠 탭 구조를 유지한다. 내부 전체 탭 화면에서는 콘텐츠 타입 chip, 요일 필터, 정렬 필터, 콘텐츠 grid를 구성하고, 목록에는 스크롤 페이징을 적용한다. Figma 타입 chip에는 전체가 보이지만 API type enum에 대응 값이 없으므로 전체 chip은 구현 대상에서 제외한다.
Figma 참조:
- 오디오 선택 상태:
35:5857,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=35-5857&m=dev - 시리즈 선택 상태:
24:6909,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=24-6909&m=dev - 오리지널 선택 상태:
24:9105,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=24-9105&m=dev
2. Problem
- 현재 V2 메인 콘텐츠 탭은 내부
추천탭 중심으로 구성되어 있고, 내부전체탭에서 타입별 콘텐츠 목록을 API 기반으로 탐색하는 요구사항이 분리되어 있지 않다. - 같은 endpoint가
type에 따라audios또는series배열을 내려주므로, 선택 타입별 데이터 매핑 규칙을 명확히 해야 한다. SERIES타입에서만dayOfWeekquery parameter를 보내야 하므로, 요일 필터 UI와 API 요청 조건을 분리해야 한다.- 기존
SeriesPublishedDaysOfWeek는 legacy 영역에 있고, 짧은 요일 다국어 표기 중기타의 영어/일본어 요구사항이 기존Random표기와 다르다.
3. Goals
- 메인 콘텐츠 탭 내부
전체탭을 추가한다. - 콘텐츠 타입 기본 선택값은
AUDIO로 둔다. - Figma 타입 chip 중
전체chip은 구현하지 않고오디오,시리즈,오리지널,무료,포인트만 제공한다. GET /api/v2/audio/contents를 호출하고type,sort,page,size,dayOfWeekquery parameter를 요구사항대로 전송한다.- 기본 query 값은
page = 0,size = 20,sort = LATEST,type = AUDIO로 둔다. dayOfWeek기본값은 현재 디바이스에 설정된 요일로 둔다.dayOfWeek는type == SERIES일 때만 요청 query에 포함한다.AUDIO,FREE,POINT선택 시 response의audios를 표시한다.SERIES,ORIGINAL선택 시 response의series를 표시한다.- 목록 하단 도달 시
hasNext == true이면 다음 page를 로드해 append한다. - 내부
추천/랭킹/전체탭 전환 시전체탭의 선택 타입, 정렬, 요일, 로드된 목록 상태를 유지한다. - response의
totalCount는 모델에는 반영하되 UI에는 표시하지 않는다. - Figma 기준 오디오/무료/포인트는
AudioContentThumbnailCard재사용을 우선한다. - Figma 기준 시리즈/오리지널은 기존 시리즈 카드 UI 후보를 확인하고, 응답 모델에 맞는 전용 adapter 또는 전용 thumbnail card를 사용한다.
- 요일 짧은 표기에서
기타는 영어OTHER, 일본어その他로 표시한다.
4. Non-Goals
- 실제 구현과 Xcode 프로젝트 수정은 이번 범위에 포함하지 않는다.
- 추천 탭의
GET /api/v2/audio/recommendations구현 범위는 변경하지 않는다. - 랭킹 탭 API/화면은 이번 범위에 포함하지 않는다.
- Figma 타입 chip의
전체chip은 구현하지 않는다. - Figma sort menu의
추천순은 구현하지 않는다. - response의
totalCount를 화면에 표시하지 않는다. - Figma에 보이는 localhost asset URL을 앱 코드에 직접 사용하지 않는다.
- API 명세에 없는 신규
type값을 임의로 추가하지 않는다. - 외부 라이브러리를 추가하지 않는다.
Pods/**,generated/**,build/**는 수정하지 않는다.
5. Target Users
- 메인 콘텐츠 탭에서 전체 콘텐츠를 타입별로 빠르게 탐색하려는 사용자
- 오디오/무료/포인트 콘텐츠를 썸네일 grid로 훑고 상세로 이동하려는 사용자
- 시리즈와 오리지널 시리즈를 구분해 탐색하려는 사용자
- 요일별 시리즈를 현재 디바이스 요일 기준으로 바로 확인하려는 사용자
6. User Stories
- 사용자는 콘텐츠 탭의 내부
전체탭에 진입하면 기본으로 오디오 목록을 보고 싶다. - 사용자는 타입 chip을 눌러 오디오, 시리즈, 오리지널, 무료, 포인트 목록을 전환하고 싶다.
- 사용자는 시리즈 타입에서 현재 요일의 시리즈가 기본으로 선택되어 있기를 기대한다.
- 사용자는 시리즈 타입에서 요일을 바꾸면 해당 요일 시리즈 목록이 새로 로드되기를 기대한다.
- 사용자는 정렬 메뉴에서 최신순 등 정렬 기준을 바꾸고 첫 페이지부터 다시 보고 싶다.
- 사용자는 스크롤을 내리면 다음 페이지가 자연스럽게 이어서 로드되기를 기대한다.
- 사용자는
추천/랭킹/전체탭을 오가도전체탭에서 보던 필터와 목록 상태가 유지되기를 기대한다.
7. Core Requirements
7.1 API
- Method:
GET - Path:
/api/v2/audio/contents - 인증: 기존 V2 API 인증 헤더 패턴을 따른다.
- 응답 래퍼: 기존 관례대로
ApiResponse<MainContentAllTabResponse>디코딩을 우선한다.
Query parameter:
type: MainContentAllTypesort: ContentSortpage: Intsize: IntdayOfWeek: SeriesPublishedDaysOfWeek
기본값:
type = AUDIOsort = LATESTpage = 0size = 20dayOfWeek = Calendar.current기준 현재 디바이스 요일
전송 조건:
dayOfWeek는type == SERIES일 때만 query에 포함한다.type이AUDIO,ORIGINAL,FREE,POINT이면dayOfWeek를 보내지 않는다.- 타입, 정렬, 요일 변경 시
page = 0부터 다시 요청한다. - 스크롤 페이징 요청은 현재 filter 상태를 유지한 채
page + 1로 요청한다.
7.2 Type enum
enum class MainContentAllType {
AUDIO,
SERIES,
ORIGINAL,
FREE,
POINT
}
Swift 모델은 기존 enum 스타일에 맞춰 String, Decodable, Encodable, CaseIterable, Hashable 채택을 우선한다.
7.3 Response model
data class MainContentAllTabResponse(
val type: MainContentAllType,
val totalCount: Int,
val audios: List<MainContentAudioResponse>,
val series: List<MainContentSeriesResponse>,
val sort: ContentSort,
val dayOfWeek: SeriesPublishedDaysOfWeek?,
val page: Int,
val size: Int,
@JsonProperty("hasNext")
val hasNext: Boolean
)
data class MainContentAudioResponse(
val audioContentId: Long,
val title: String,
val imageUrl: String?,
val price: Int,
@JsonProperty("isAdult")
val isAdult: Boolean,
@JsonProperty("isPointAvailable")
val isPointAvailable: Boolean,
@JsonProperty("isFirstContent")
val isFirstContent: Boolean,
@JsonProperty("isOriginalSeries")
val isOriginalSeries: Boolean,
val creatorNickname: String
)
data class MainContentSeriesResponse(
val seriesId: Long,
val title: String,
val coverImageUrl: String?,
val creatorNickname: String,
@JsonProperty("isOriginal")
val isOriginal: Boolean,
@JsonProperty("isAdult")
val isAdult: Boolean
)
Swift 모델은 기존 V2 관례에 맞춰 Decodable 구조체로 선언한다. Long 대응 id는 프로젝트 기존 모델 관례에 맞춰 Swift Int를 우선 사용한다.
7.4 Type별 data mapping
AUDIO: responseaudios를 오디오 grid로 표시한다.FREE: responseaudios를 오디오 grid로 표시한다.POINT: responseaudios를 오디오 grid로 표시한다.SERIES: responseseries를 시리즈 grid로 표시하고, 요일 필터를 노출한다.ORIGINAL: responseseries를 시리즈 grid로 표시하고, 요일 필터는 노출하지 않는다.- API enum에
ALL이 없으므로 Figma의전체type chip은 구현하지 않는다. - response의
totalCount는 pagination 판단이나 디버깅 보조 값으로만 유지하고, 화면에는 표시하지 않는다.
7.5 Paging
page = 0응답은 기존 목록을 교체한다.page > 0응답은 현재 선택 타입에 맞는 목록 뒤에 append한다.hasNext == false이면 추가 요청을 막는다.- 이미 다음 페이지 로딩 중이면 중복 요청을 막는다.
- 타입, 정렬, 요일 변경 시 기존 목록을 비우고 첫 페이지를 다시 요청한다.
- API 실패 시 기존 목록이 있으면 유지하고 toast 또는 error message를 표시한다.
- 첫 페이지 실패 또는 결과 없음은 empty state를 표시한다.
7.6 Sort
- 기본 정렬은
ContentSort.latest/ API 값LATEST이다. - 정렬 menu는 Figma처럼 sort bar 우측에 표시한다.
- 정렬 변경 시 첫 페이지부터 다시 요청한다.
추천순은 구현하지 않는다.FREE타입에서는최신순,인기순만 제공한다.AUDIO,SERIES,ORIGINAL,POINT타입에서 제공할 sort 후보:LATESTPOPULARPRICE_HIGHPRICE_LOW
FREE타입에서 제공할 sort 후보:LATESTPOPULAR
- 현재 선택된 sort가 타입 변경 후 허용되지 않는 값이면
LATEST로 초기화하고 첫 페이지부터 다시 요청한다.
7.7 Day of week
SeriesPublishedDaysOfWeek기존 enum을 사용한다.- 현재 디바이스 요일 기본값은
Calendar.current.component(.weekday, from: Date())를 기준으로 매핑한다.- Sunday:
.SUN - Monday:
.MON - Tuesday:
.TUE - Wednesday:
.WED - Thursday:
.THU - Friday:
.FRI - Saturday:
.SAT
- Sunday:
RANDOM은 Figma의기타표시와 매핑한다.- 요일 필터는
SERIES타입에서만 노출한다. ORIGINAL타입은 responseseries를 쓰지만dayOfWeek를 보내지 않고 요일 필터도 노출하지 않는다.
짧은 요일 다국어 표기:
MON: ko월, enMON, ja月TUE: ko화, enTUE, ja火WED: ko수, enWED, ja水THU: ko목, enTHU, ja木FRI: ko금, enFRI, ja金SAT: ko토, enSAT, ja土SUN: ko일, enSUN, ja日RANDOM: ko기타, enOTHER, jaその他
7.8 UI layout
- 내부 tab bar에서
전체가 선택된 상태로 표시된다. - 콘텐츠 타입 chip은 Figma 기준 수평 스크롤 형태를 따른다.
- 콘텐츠 타입 chip은
오디오,시리즈,오리지널,무료,포인트만 표시한다. - Figma에 보이는
전체chip은 표시하지 않는다. - 콘텐츠 타입 chip 기본 선택은
오디오이다. - sort bar는 좌측에 현재 범위 label, 우측에 현재 sort label과 펼침 아이콘을 둔다.
- sort menu에는
추천순을 표시하지 않는다. - 오디오/무료/포인트 grid는 Figma 오디오 상태와 동일한 3열 정사각형 썸네일 grid를 따른다.
- 시리즈/오리지널 grid는 Figma 시리즈/오리지널 상태와 동일한 3열 세로형 썸네일 grid를 따른다.
- response의
totalCount또는 전체 개수 문구는 표시하지 않는다. - 하단 main tab bar와 mini player 영역은 기존
MainView구조를 유지한다. - 빈 목록, 로딩, pagination loading 상태는 기존 V2 콘텐츠/추천 탭 패턴을 따른다.
7.9 Tab state preservation
- 내부
추천/랭킹/전체탭 전환 시전체탭 ViewModel 상태를 유지한다. - 유지 대상은 선택된 콘텐츠 타입, sort, dayOfWeek, 현재 page, hasNext, 로드된
audios/series목록이다. 전체탭으로 돌아왔을 때 사용자가 명시적으로 filter를 변경하지 않았다면 첫 페이지를 자동 재요청하지 않는다.
7.10 Navigation
- 오디오 item 탭은
audioContentId로 기존 content detail 진입 흐름을 사용한다. - 시리즈 item 탭은
seriesId로 기존 series detail 진입 흐름을 사용한다. - 로그인/성인 콘텐츠 guard가 기존 상세 진입 흐름에 있다면 재사용한다.
8. UX / UI Expectations
- 전체 배경은 기존 V2 dark UI 기준을 따른다.
- 신규 사용자 노출 문자열은
I18n에 ko/en/ja로 추가한다. - Figma localhost asset URL은 앱 코드에 사용하지 않는다.
- 타입 chip의
전체와 sort menu의추천순은 표시하지 않는다. - 전체 개수 또는
totalCount는 표시하지 않는다. - 오디오 카드의 title은
title, subtitle은creatorNickname을 표시한다. - 시리즈 카드의 title은
title, subtitle은creatorNickname을 표시한다. MainContentAudioResponse.isOriginalSeries == true이면 오디오 original tag를 표시한다.MainContentAudioResponse.isFirstContent == true이면 first tag를 표시한다.MainContentAudioResponse.isPointAvailable == true이면 point tag를 표시한다.MainContentAudioResponse.price == 0이면 free tag를 표시한다.isAdult == true이면 adult shield tag를 표시한다.MainContentSeriesResponse.isOriginal == true이면ic_series_original+img_new_only조합의ONLYtag를 표시한다.- 텍스트가 길면 Figma 기준 line limit로 말줄임 처리한다.
9. 재사용 가능한 V2 위젯 후보
SodaLive/Sources/V2/Main/Content/MainContentView.swift- 콘텐츠 탭 root와 title bar, 내부 tab bar 구조를 확장할 후보.
SodaLive/Sources/V2/Main/Content/MainContentTab.swift- 내부
전체탭 case 추가 후보.
- 내부
SodaLive/Sources/V2/Component/HomeTitleBar.swift- 콘텐츠 탭 title bar 재사용 후보.
SodaLive/Sources/V2/Component/TextTabBar.swift- 내부
추천/랭킹/전체tab bar 재사용 후보.
- 내부
SodaLive/Sources/V2/Component/AudioContent/AudioContentThumbnailCard.swiftAUDIO,FREE,POINT타입 카드의 1차 재사용 후보.AudioContentThumbnailCardItem에MainContentAudioResponseadapter init 추가를 검토한다.
SodaLive/Sources/V2/CreatorChannel/Series/Components/CreatorChannelSeriesListItem.swiftic_series_original+img_new_onlytag와 세로형 시리즈 썸네일 스타일 참고 후보.- 현재
CreatorChannelSeriesResponse전용이고 요일/진행/소장률 필드를 요구하므로 그대로 재사용하기보다MainContentSeriesResponse전용 thumbnail card 또는 adapter를 우선 검토한다.
SodaLive/Sources/V2/CreatorChannel/Models/ContentSort.swiftsortquery와 sort menu label 재사용 후보.
SodaLive/Sources/Home/HomeTabViewModel.swift- 기존
SeriesPublishedDaysOfWeekenum 위치 확인 대상.
- 기존
SodaLive/Sources/Content/Series/Main/DayOfWeek/SeriesMainDayOfWeekView.swift- 현재 디바이스 요일 매핑과 요일 selector 구현 참고 대상.
10. Technical Constraints
- 신규 구현이 이어질 경우 기능 변경은
SodaLive/Sources/V2/Main/Content/**하위에서 해결한다. - API, Repository, ViewModel, Model은 내부 전체 탭 전용 경로로 분리한다.
- 여러 화면에서 재사용할 명확한 근거가 있는 UI만
SodaLive/Sources/V2/Component/**로 승격한다. - 기존 legacy
SodaLive/Sources/Content/**화면을 리팩터링하지 않는다. - 기존
MainContentRecommendationAPI/ViewModel을 직접 확장하지 않는다. - 외부 라이브러리는 추가하지 않는다.
Pods/**,generated/**,build/**는 수정하지 않는다.
11. Success Criteria
- 콘텐츠 탭 내부 tab bar에
전체탭이 추가되고 선택 가능하다. - 내부
전체탭 최초 진입 시GET /api/v2/audio/contents?type=AUDIO&sort=LATEST&page=0&size=20요청이 발생한다. - 최초 진입 요청에는
dayOfWeek가 포함되지 않는다. SERIES타입 선택 시 현재 디바이스 요일이 선택되고,dayOfWeekquery가 포함된다.SERIES외 타입 요청에는dayOfWeek가 포함되지 않는다.AUDIO,FREE,POINT선택 시 responseaudios가AudioContentThumbnailCard기반 grid로 표시된다.SERIES,ORIGINAL선택 시 responseseries가 세로형 시리즈 grid로 표시된다.- Figma에 보이는 타입 chip
전체는 표시되지 않는다. - sort menu에
추천순이 표시되지 않는다. FREE타입의 sort menu에는최신순,인기순만 표시된다.- response의
totalCount또는 전체 개수 문구가 표시되지 않는다. - 내부
추천/랭킹/전체탭 전환 후전체탭으로 돌아오면 선택 타입, 정렬, 요일, 로드된 목록이 유지된다. hasNext == true인 상태에서 목록 하단 도달 시 다음 page가 요청되고 기존 목록 뒤에 append된다.- 타입, 정렬, 요일 변경 시 page가 0으로 초기화되고 목록이 새 응답으로 교체된다.
기타요일은 영어에서OTHER, 일본어에서その他로 표시된다.- 오디오 item 탭 시 오디오 상세로 이동하고, 시리즈 item 탭 시 시리즈 상세로 이동한다.
12. Open Questions
- 현재 확정된 요구사항 기준으로 미결정 사항은 없다.
13. Decisions
- Figma 타입 chip의
전체는 구현 대상에서 제외한다. - Figma sort menu의
추천순은 구현하지 않는다. - 내부
추천/랭킹/전체탭 전환 시전체탭 상태를 유지한다. FREE타입의 sort menu는LATEST,POPULAR만 제공한다.- response의
totalCount는 UI에 표시하지 않는다.
14. Verification Notes
- 2026-07-06:
docs/agent-guides/documentation-policy.md를 확인해 PRD-only 작업은 필요한 최소 문서만 작성해도 됨을 확인했다. - 2026-07-06: 기존
docs/20260705_메인_콘텐츠_내부_추천_탭/prd.md,plan-task.md를 확인해 이번 작업이 추천 탭이 아닌 내부 전체 탭 별도 범위임을 확인했다. - 2026-07-06: Figma
get_design_context와get_screenshot으로35:5857,24:6909,24:9105를 확인했다. - 2026-07-06:
AudioContentThumbnailCard,CreatorChannelSeriesListItem,ContentSort,SeriesPublishedDaysOfWeek,SeriesMainDayOfWeekView,I18n.Series를 확인해 재사용 후보와 제약을 정리했다. - 2026-07-06: 사용자 결정 사항을 반영해
전체chip 제외,추천순제외, 탭 전환 상태 유지,FREEsort 제한,totalCount미표시를 확정했다.