# PRD: 메인 콘텐츠 탭 내부 추천 탭 ## 1. Overview 메인 하단 `콘텐츠` 탭 안의 내부 `추천` 탭에서 `GET /api/v2/audio/recommendations` 응답을 사용해 오디오 추천 화면을 제공한다. 화면 상단 title bar와 내부 tab bar는 고정 영역으로 유지하고, Figma 기준 `배너`부터 하위 콘텐츠만 세로 스크롤한다. 신규 기능이므로 구현 대상 API, 모델, ViewModel, 화면, 섹션 컴포넌트는 `SodaLive/Sources/V2/Main/Content/Recommendation/**` 하위에 둔다. Figma 참조: - 전체 화면: `24:6737`, `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-6737&m=dev` - title bar: `24:6738` - 배너: `24:6741` - 새로 올라온 오디오: `24:6751` - New&Hot: `24:6758` - 오직 보이스온에서만: `24:6745` - 추천 시리즈: `24:6770` (이번 범위 제외) - 무료 오디오: `24:6807` - 포인트 오디오: `24:6813` - 최근 댓글이 많은 오디오: `24:6820` - 키워드의 오디오: `24:6829` (이번 범위 제외) - 추천 오디오: `24:6842` ## 2. Problem - 현재 V2 `MainView`의 `.content` 탭은 placeholder 상태라 메인 콘텐츠 탭에서 실제 추천 오디오 콘텐츠를 제공하지 못한다. - 기존 홈 추천 탭은 크리에이터/커뮤니티/라이브 중심이고, 콘텐츠 탭 내부 추천 화면은 오디오 추천 API와 섹션 구성이 다르다. - Figma 화면은 카드형, 리스트형, 댓글 카드형, grid형 등 섹션마다 레이아웃이 달라 단일 컴포넌트로 단순 렌더링하면 디자인과 맞지 않는다. - 오디오 카드에는 `ONLY`, original audio, first, free, point, adult 등 여러 태그가 겹쳐 표시되므로 기존 V2 컴포넌트 재사용 범위를 먼저 정해야 한다. ## 3. Goals - 메인 하단 `콘텐츠` 탭에서 내부 `추천` 탭 화면을 표시한다. - `GET /api/v2/audio/recommendations`를 호출하고 응답 데이터로 섹션을 구성한다. - title bar와 내부 tab bar는 고정하고, 배너부터 하위 콘텐츠만 스크롤한다. - Figma에 보이는 구현 대상 섹션을 섹션별로 분리한다. - `banners`, `originalSeries`, `latestAudios`, `newAndHotAudios`, `freeAudios`, `pointAudios`, `mostCommentedAudios`, `recommendedAudios`를 응답 모델에 반영한다. - `오직 보이스온에서만!` 섹션에는 `originalSeries`를 사용하고, `ic_series_original` + `img_new_only` 조합으로 `ONLY` 표시를 노출한다. - `새로 올라온 오디오`, `무료 오디오`, `포인트 오디오`, `추천 오디오`는 기존 `AudioContentCard` 재사용 또는 최소 보완을 우선 검토한다. - `New&Hot`, `최근 댓글이 많은 오디오`는 기존 `CreatorChannelAudioContentListItem`의 row/태그 표시 규칙을 참고한다. - 오디오 item 탭 시 기존 content detail 진입 guard 흐름을 재사용한다. ## 4. Non-Goals - `추천 시리즈` 섹션은 이번 범위에서 구현하지 않는다. - `#키워드의 오디오` 섹션은 이번 범위에서 구현하지 않는다. - API 스펙에 없는 추천 시리즈 상세 카드, 키워드 추천 API, 키워드별 더보기 화면을 임의로 추가하지 않는다. - 기존 홈 `추천`/`랭킹`/`팔로잉` 탭 구조를 변경하지 않는다. - 기존 legacy `SodaLive/Sources/Content/**` 화면을 리팩터링하지 않는다. - Figma localhost asset URL을 앱 코드에 직접 사용하지 않는다. - 외부 라이브러리를 추가하지 않는다. - `Pods/**`, `generated/**`, `build/**`는 수정하지 않는다. ## 5. Target Users - 메인 콘텐츠 탭에서 새 오디오와 추천 오디오를 빠르게 탐색하려는 사용자 - 무료/포인트/신규/오리지널 여부를 보고 오디오 콘텐츠를 선택하려는 사용자 - 댓글 반응이 많은 오디오를 확인하고 상세로 이동하려는 사용자 - 보이스온 오리지널 시리즈를 별도 섹션에서 발견하려는 사용자 ## 6. User Stories - 사용자는 콘텐츠 탭의 추천 화면에서 최신 오디오를 가로 카드 목록으로 보고 싶다. - 사용자는 `New&Hot` 오디오를 리스트 형태로 빠르게 훑어보고 싶다. - 사용자는 보이스온에서만 제공되는 오리지널 시리즈를 `ONLY` 표시로 구분하고 싶다. - 사용자는 무료 오디오와 포인트 사용 가능 오디오를 별도 섹션으로 탐색하고 싶다. - 사용자는 최근 댓글이 많은 오디오와 최신 댓글 작성자 프로필을 함께 보고 싶다. - 사용자는 추천 오디오를 2열 grid로 탐색하고 마음에 드는 콘텐츠 상세로 이동하고 싶다. ## 7. Core Requirements ### 7.1 API - Method: `GET` - Path: `/api/v2/audio/recommendations` - 인증: 기존 V2 API 인증 헤더 패턴을 따른다. - 응답 래퍼: 기존 관례대로 `ApiResponse` 디코딩을 우선한다. ### 7.2 Response model ```kotlin data class AudioRecommendationsResponse( val banners: List, val originalSeries: List, val latestAudios: List, val newAndHotAudios: List, val freeAudios: List, val pointAudios: List, val mostCommentedAudios: List, val recommendedAudios: List ) ``` Swift 모델은 기존 V2 관례에 맞춰 `Decodable` 구조체로 선언한다. `audioContentId`, `seriesId`, `creatorId` 같은 Kotlin `Long` 대응 값은 프로젝트 기존 모델 관례에 맞춰 Swift `Int`를 우선 사용한다. ### 7.3 Fixed shell and scrolling - 콘텐츠 탭 title bar는 화면 상단 고정 영역이다. - 콘텐츠 탭 title bar 좌측은 홈 로고나 내부 탭명이 아니라 메인 탭명 `콘텐츠`를 고정 표시한다. - 콘텐츠 탭 title bar 우측은 Figma `24:6738` 기준 `cash`, `search`, `storage` 액션 아이콘을 표시하고 알림 bell 아이콘은 사용하지 않는다. - 내부 tab bar도 title bar 아래 고정 영역이다. - Figma `24:6741` 배너부터 하위 콘텐츠만 세로 `ScrollView` 안에 둔다. - 하단 main tab bar와 mini player 영역은 기존 `MainView` 구조를 유지한다. ### 7.4 Section visibility - 섹션별 배열이 비어 있으면 해당 섹션은 렌더링하지 않는다. - 모든 구현 대상 섹션이 비어 있으면 empty state를 표시한다. - API 실패 또는 디코딩 실패 시 사용자 노출 문구를 `I18n`에 둔다. ### 7.5 구현 대상 섹션 - 배너: `banners` - 새로 올라온 오디오: `latestAudios` - New&Hot: `newAndHotAudios` - 오직 보이스온에서만: `originalSeries` - 무료 오디오: `freeAudios` - 포인트 오디오: `pointAudios` - 최근 댓글이 많은 오디오: `mostCommentedAudios` - 추천 오디오: `recommendedAudios` ### 7.6 제외 대상 섹션 - 추천 시리즈: Figma `24:6770` - 키워드의 오디오: Figma `24:6829` `originalSeries`는 이번 범위에서 `오직 보이스온에서만!` 섹션에만 사용한다. 추천 시리즈 카드형 상세 UI는 만들지 않는다. ### 7.7 Tag display - `isOriginalSeries == true`이면 original audio/series 태그를 표시한다. - `isFirstContent == true`이면 first 태그를 표시한다. - `isPointAvailable == true`이면 point 태그를 표시한다. - `price == 0`이면 free 태그를 표시한다. - `isAdult == true`이면 adult shield 태그를 표시한다. - `오직 보이스온에서만!`의 `ONLY` 표시는 `ic_series_original` + `img_new_only` asset 조합을 사용한다. ### 7.8 Navigation - title bar의 storage 아이콘 탭은 기존 보관함 진입 경로인 `AppState.shared.setAppStep(step: .myBox(currentTab: .orderlist))`를 사용한다. - 오디오 item 탭은 `audioContentId`로 기존 content detail 진입 흐름을 사용한다. - 배너 탭은 `eventItem`, `creatorId`, `seriesId`, `link`를 명시적으로 분기한다. - `eventItem`은 기존 event detail 흐름이 있으면 재사용한다. - `creatorId`는 기존 creator detail guard callback을 재사용한다. - `seriesId`는 `AppStep.seriesDetail(seriesId:)` route로 연결한다. - `AudioBannerResponse.link`는 먼저 `AppDeepLinkHandler.handle(urlString:)`로 deep link 여부를 확인한다. - `AudioBannerResponse.link`가 deep link로 처리되지 않으면 `UIApplication.shared.open`으로 외부 브라우저를 연다. ## 8. UX / UI Expectations - 전체 배경은 기존 V2 dark UI 기준을 따른다. - 섹션 title은 Figma 텍스트를 우선 사용하되 신규 사용자 노출 문자열은 `I18n`에 둔다. - 배너는 Figma `24:6741` 기준으로 가로 carousel과 `01 / 20` page indicator를 표시한다. - 일반 오디오 카드 섹션은 가로 스크롤 카드 목록으로 표시한다. - `New&Hot`은 3개 row를 한 묶음으로 구성하고 묶음 단위로 가로 스크롤한다. - `최근 댓글이 많은 오디오`는 오디오 row와 댓글 preview bubble을 하나의 카드로 묶어 가로 스크롤한다. - `최근 댓글이 많은 오디오`의 댓글 preview bubble에는 nickname을 표시하지 않는다. Figma `24:6820` 기준으로 `latestCommentWriterProfileImageUrl`과 `latestComment` 댓글 텍스트 영역만 사용한다. - `추천 오디오`는 2열 grid로 표시한다. - 텍스트가 길면 한 줄 또는 Figma 기준 line limit로 말줄임 처리한다. - 카드/row의 루트 레이아웃은 가능한 부모 width, grid column, aspect ratio 기반으로 구성한다. ## 9. Technical Constraints - 신규 기능 파일은 `SodaLive/Sources/V2/Main/Content/Recommendation/**` 하위에 둔다. - 콘텐츠 탭 root는 `SodaLive/Sources/V2/Main/Content/**` 하위에 둔다. - 여러 화면에서 재사용할 명확한 근거가 생긴 컴포넌트만 `SodaLive/Sources/V2/Component/**`로 승격한다. - 기존 `MainHomeRecommendation` API/ViewModel을 직접 확장하지 않는다. - 기존 `AudioContentCard`, `BannerCarousel`, `SectionTitle`, `TextTabBar`, `HomeTitleBar` 재사용을 먼저 검토한다. - 신규 사용자 노출 문구는 `SodaLive/Sources/I18n/I18n.swift`에 ko/en/ja로 추가한다. - Figma localhost asset URL은 앱 코드에 사용하지 않는다. - 외부 라이브러리는 추가하지 않는다. ## 10. Success Criteria - `.content` 탭에서 placeholder 대신 콘텐츠 탭 root가 표시된다. - 콘텐츠 탭 내부 `추천` 탭 진입 시 `GET /api/v2/audio/recommendations` 호출이 발생한다. - 콘텐츠 탭 title bar는 좌측 고정 title `콘텐츠`와 우측 `cash`/`search`/`storage` 액션을 표시하고, storage 탭 시 보관함으로 이동한다. - title bar와 내부 tab bar는 고정되고, 배너부터 하위 섹션만 세로 스크롤된다. - API 응답의 구현 대상 배열이 각 Figma 섹션에 매핑된다. - `추천 시리즈`, `#키워드의 오디오` 섹션은 구현되지 않는다. - `오직 보이스온에서만!` 섹션은 `ic_series_original` + `img_new_only` 조합으로 `ONLY` 표시를 노출한다. - 오디오 item 탭 시 기존 content detail guard 흐름을 통해 상세로 이동한다. - `AudioBannerResponse.seriesId`와 `OriginalSeriesResponse.seriesId` 탭 시 `seriesDetail`로 이동한다. - `AudioBannerResponse.link`는 deep link 우선 처리 후, deep link가 아니면 외부 브라우저로 열린다. - 빈 섹션은 숨겨지고, 전체 empty/API 실패 상태는 사용자에게 안내된다. - `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`가 성공한다. ## 11. Open Questions - 현재 확정된 요구사항 기준으로 미결정 사항은 없다. ## 12. Verification Notes - 2026-07-05: Figma `get_design_context`와 `get_screenshot`으로 `24:6737`, `24:6741`, `24:6751`, `24:6758`, `24:6745`, `24:6770`, `24:6807`, `24:6813`, `24:6820`, `24:6829`, `24:6842`를 확인했다. - 2026-07-05: `MainView`, `MainHomeView`, `AudioContentCard`, `BannerCarousel`, `CreatorChannelAudioContentListItem`, `CreatorChannelSeriesListItem`을 확인해 재사용 후보를 정리했다. - 2026-07-05: 최초 문서 작성 시 `plan-task.md`만 작성되어 신규 기능 문서 정책과 맞지 않았다. 본 PRD를 추가해 정책 누락을 보완한다. - 2026-07-05: 사용자 확인으로 댓글 preview bubble에는 nickname을 표시하지 않고, 배너 `link`는 deep link 우선 후 외부 브라우저로 열며, `seriesId`는 `seriesDetail` route로 이동하도록 확정했다. - 2026-07-08: Figma `24:6738` 기준으로 콘텐츠 탭 title bar가 홈 탭 로고/bell 구성이 아니라 좌측 고정 title `콘텐츠`와 우측 `ic_bar_cash`/`ic_bar_search`/`ic_bar_storage` 구성을 사용하도록 요구사항을 보완했다.