Files
sodalive-ios/docs/20260706_메인_콘텐츠_내부_랭킹_탭/prd.md
2026-07-06 20:19:27 +09:00

12 KiB

PRD: 메인 콘텐츠 탭 내부 랭킹 탭

1. Overview

메인 하단 콘텐츠 탭 안의 내부 랭킹 탭에서 GET /api/v2/audio/rankings 응답을 사용해 오디오 콘텐츠 순위를 제공한다.

상단 title bar와 내부 추천/랭킹/전체 tab bar는 기존 콘텐츠 탭 구조를 유지한다. 내부 랭킹 탭 화면에서는 서버가 내려준 순위와 순위 변동 정보를 기준으로 1위 전용 UI, 2~20위 row UI를 구성한다. 신규 기능이므로 API, 모델, ViewModel, 화면, 랭킹 전용 컴포넌트는 SodaLive/Sources/V2/Main/Content/Ranking/** 하위에 둔다.

Figma 참조:

  • 전체: 24:6857, 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-6857&m=dev
  • 1위: 24:6861, 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-6861&m=dev
  • 2~10위: 24:6867, 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-6867&m=dev
  • 11~20위: 24:6872, 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-6872&m=dev

2. Problem

  • 현재 V2 메인 콘텐츠 탭의 내부 랭킹 탭은 placeholder 상태라 사용자가 콘텐츠 랭킹을 볼 수 없다.
  • 기존 메인 홈 랭킹 탭은 크리에이터 순위용 응답과 UI에 맞춰져 있어 오디오 콘텐츠 랭킹의 title, creatorNickname, coverImageUrl, contentId 요구사항을 그대로 수용하기 어렵다.
  • Figma에서 1위 UI는 2~20위와 형태가 다르므로 순위별 컴포넌트 경계를 명확히 나눠야 한다.
  • 2~20위는 표시 데이터만 다르고 UI 형태가 메인 홈 랭킹 탭의 크리에이터 순위 row와 매우 유사하므로, 전용 컴포넌트를 만들더라도 MainHomeRankingRow 구조를 거의 그대로 복사해 사용할 수 있는지 먼저 검토한다.
  • 2~20위 row에서 달라지는 핵심 영역은 텍스트 영역이며, 위에는 콘텐츠 타이틀, 아래에는 크리에이터 닉네임을 표시한다.
  • 고정 width/height를 직접 박으면 다양한 기기 폭에서 Figma 구조가 깨질 수 있으므로 부모 width, aspect ratio, padding 기반의 반응형 제약이 필요하다.

3. Goals

  • 메인 콘텐츠 탭 내부 랭킹 탭에서 GET /api/v2/audio/rankings 응답 기반 UI를 표시한다.
  • AudioRankingResponse, AudioRankingType, AudioRankingItemResponse를 Swift Decodable 모델로 반영한다.
  • 서버가 내려준 items[].rank를 화면 순위로 사용하고 클라이언트에서 index 기반 순위를 재계산하지 않는다.
  • item은 최대 20개까지 표시한다.
  • 1위는 Figma 24:6861 기준 전용 UI로 구현한다.
  • 2~20위는 단일 row 컴포넌트로 구현하고, 메인 홈 랭킹 탭의 크리에이터 순위 row 구조를 거의 그대로 복사한 뒤 오디오 콘텐츠 필드에 맞게 치환하는 방식을 우선한다.
  • 2~20위 row의 텍스트 영역은 위에 콘텐츠 타이틀, 아래에 크리에이터 닉네임을 표시한다.
  • showRankChange, rankChange, isNew 값에 따라 순위 변화 UI를 표시하거나 숨긴다.
  • 오디오 item 탭 시 기존 콘텐츠 상세 진입 callback을 재사용한다.
  • V2 패키지 하위에 구현된 재사용 가능한 위젯 후보를 확인하고, 직접 재사용이 어렵더라도 구조 복사에 가까운 재활용이 가능한 부분을 명시한다.

4. Non-Goals

  • 실제 구현과 Xcode 프로젝트 수정은 이 PRD 작성 범위에 포함하지 않는다.
  • API 스펙에 없는 랭킹 타입 filter UI를 임의로 추가하지 않는다.
  • 서버 응답의 rank를 무시하고 클라이언트 index로 순위를 재계산하지 않는다.
  • 20위를 초과한 item 노출 정책을 새로 만들지 않는다.
  • 기존 메인 홈 랭킹 탭 동작을 변경하지 않는다.
  • 기존 legacy SodaLive/Sources/Content/** 화면을 리팩터링하지 않는다.
  • Figma localhost asset URL을 앱 코드에 직접 사용하지 않는다.
  • 외부 라이브러리를 추가하지 않는다.
  • Pods/**, generated/**, build/**는 수정하지 않는다.

5. Target Users

  • 메인 콘텐츠 탭에서 인기 오디오 콘텐츠 순위를 빠르게 확인하려는 사용자
  • 순위 변화, 신규 진입 여부를 보고 콘텐츠를 선택하려는 사용자
  • 콘텐츠 타이틀과 크리에이터 닉네임을 함께 확인한 뒤 오디오 상세로 이동하려는 사용자

6. User Stories

  • 사용자는 콘텐츠 탭의 내부 랭킹 탭을 누르면 현재 오디오 랭킹을 보고 싶다.
  • 사용자는 1위 콘텐츠를 다른 순위보다 더 큰 시각적 강조로 확인하고 싶다.
  • 사용자는 2~20위 콘텐츠를 같은 row 형식으로 빠르게 훑어보고 싶다.
  • 사용자는 각 row에서 콘텐츠 타이틀과 크리에이터 닉네임을 구분해 보고 싶다.
  • 사용자는 순위 상승, 하락, 유지, 신규 진입 상태를 확인하고 싶다.
  • 사용자는 랭킹 item을 눌러 기존 오디오 상세 화면으로 이동하고 싶다.

7. Core Requirements

7.1 API

  • Method: GET
  • Path: /api/v2/audio/rankings
  • 인증: 기존 V2 API 인증 헤더 패턴을 따른다.
  • Request query: 현재 제공 명세 기준 없음.
  • 응답 래퍼: 기존 관례대로 ApiResponse<AudioRankingResponse> 디코딩을 우선한다.

7.2 Response model

data class AudioRankingResponse(
    val showRankChange: Boolean,
    val type: AudioRankingType,
    val items: List<AudioRankingItemResponse>
)

enum class AudioRankingType {
    WEEKLY_POPULAR,
    RISING,
    REVENUE,
    SALES_COUNT,
    COMMENT_COUNT,
    LIKE_COUNT
}

data class AudioRankingItemResponse(
    val contentId: Long,
    val title: String,
    val creatorNickname: String,
    val rank: Int,
    val rankChange: Int?,
    @JsonProperty("isNew")
    val isNew: Boolean,
    val coverImageUrl: String?
)

Swift 모델은 기존 V2 관례에 맞춰 Decodable 구조체와 String raw value enum으로 선언한다. Kotlin Long 대응 값은 프로젝트 기존 모델 관례에 맞춰 Swift Int를 우선 사용한다. AudioRankingItemResponse 대응 모델은 Identifiable을 채택하고 id == contentId로 둔다.

7.3 Ranking display

  • 서버 응답 items 순서를 유지한다.
  • 화면 순위 숫자는 item.rank를 사용한다.
  • 클라이언트에서 index 기반으로 순위를 다시 계산하지 않는다.
  • 표시 대상은 최대 20개로 제한한다.
  • item이 없으면 empty state를 표시한다.

7.4 Rank change display

  • showRankChange == false이면 순위 변화 영역을 표시하지 않는다.
  • showRankChange == true이고 isNew == true이면 신규 진입 표시를 우선한다.
  • rankChange > 0이면 상승 표시와 절댓값 숫자를 표시한다.
  • rankChange < 0이면 하락 표시와 절댓값 숫자를 표시한다.
  • rankChange == 0이면 변동 없음 표시를 사용한다.
  • rankChange == nil && isNew == false이면 순위 변화 영역을 표시하지 않는다.
  • 기존 asset 후보는 ic_rank_new, ic_rank_caret_increase, ic_rank_caret_decrease, ic_rank_caret_stay를 우선 확인한다.

7.5 UI layout

  • 내부 tab bar에서 랭킹이 선택된 상태로 표시된다.
  • title bar와 내부 tab bar는 기존 MainContentView shell을 유지한다.
  • 랭킹 탭 본문은 자체 ScrollView로 구성한다.
  • 1위 item은 Figma 24:6861 기준 전용 card로 표시한다.
  • 2~20위 item은 Figma 24:6867, 24:6872 기준 row로 표시한다.
  • 2~20위 row는 MainHomeRankingRow의 순위 컬럼, 썸네일, padding, aspectRatio, 터치 영역 구조를 최대한 유지하고, 데이터 모델과 텍스트 영역만 오디오 랭킹에 맞게 바꾼다.
  • 2~20위 row의 텍스트 영역은 위 title, 아래 creatorNickname 순서로 표시한다.
  • 텍스트가 길면 Figma 기준 line limit 안에서 말줄임 처리한다.
  • 루트 layout과 반복 item에는 고정 숫자 width/height를 사용하지 않는다.
  • 필요한 숫자 치수는 아이콘, 내부 padding, 최소 글자 크기 같은 내부 요소에만 제한적으로 사용한다.
  • card/row 전체 크기는 parent width, GeometryReader, aspectRatio, flexible layout, padding 기반으로 정한다.

7.6 Navigation

  • 랭킹 item 탭은 contentId로 기존 content detail 진입 흐름을 사용한다.
  • MainContentView는 기존 onTapContent callback을 MainContentRankingView에 전달한다.
  • 기존 MainView.handleRecommendationContentTap의 로그인/성인 콘텐츠 guard 흐름을 재사용한다.

7.7 Empty and error state

  • 첫 로드 중에는 loading 상태를 표시한다.
  • 첫 페이지 응답이 비어 있으면 empty state를 표시한다.
  • API 실패 또는 디코딩 실패 시 사용자 노출 문구를 I18n.MainContentRanking에 둔다.
  • 기존 목록이 없는 실패 상태에서는 랭킹 탭 내부 안내를 표시한다.

8. UX / UI Expectations

  • 전체 배경은 기존 V2 dark UI 기준을 따른다.
  • 1위는 Figma 기준으로 다른 순위보다 시각적 우선순위가 높게 보이도록 구성한다.
  • 2~20위는 반복 row로 스캔하기 쉬워야 한다.
  • 순위 숫자와 순위 변화 표시는 홈 랭킹 탭과 시각적 일관성을 유지한다.
  • 썸네일은 coverImageUrl을 사용하고, 값이 없거나 비어 있으면 기존 V2 이미지 fallback 배경을 따른다.
  • 콘텐츠 타이틀은 크리에이터 닉네임보다 우선되는 텍스트 계층으로 표시한다.
  • 카드와 row의 터치 영역은 사용자가 오디오 상세 진입 가능성을 인지할 수 있게 item 전체 영역으로 둔다.

9. Technical Constraints

  • 신규 기능 파일은 SodaLive/Sources/V2/Main/Content/Ranking/** 하위에 둔다.
  • 콘텐츠 탭 shell은 기존 SodaLive/Sources/V2/Main/Content/MainContentView.swift 구조를 유지한다.
  • 여러 화면에서 재사용할 명확한 근거가 생긴 컴포넌트만 SodaLive/Sources/V2/Component/**로 승격한다.
  • 기존 홈 랭킹 row 컴포넌트는 모델 결합이 있으므로 직접 재사용은 어렵지만, 2~20위 오디오 row 전용 컴포넌트 작성 시 레이아웃 구조는 거의 그대로 복사하는 것을 우선 검토한다.
  • 기존 TextTabBar, HomeTitleBar, 홈 랭킹 badge/row/card, AudioContentThumbnailCard, AudioContentListRow를 재사용 후보로 먼저 확인한다.
  • 신규 사용자 노출 문구는 SodaLive/Sources/I18n/I18n.swift에 ko/en/ja로 추가한다.
  • 외부 라이브러리는 추가하지 않는다.

10. Success Criteria

  • docs/20260706_메인_콘텐츠_내부_랭킹_탭/plan-task.md가 본 PRD를 기준 문서로 참조한다.
  • .content 탭 내부 랭킹 분기에서 placeholder 대신 랭킹 화면을 표시하는 구현 계획이 있다.
  • GET /api/v2/audio/rankings endpoint와 응답 모델이 문서화되어 있다.
  • Figma 전체, 1위, 210위, 1120위 URL이 PRD와 계획 문서에 기록되어 있다.
  • 1위 전용 UI와 2~20위 row UI가 분리되어 계획되어 있다.
  • 2~20위 row의 텍스트 영역이 title/creatorNickname 구조임이 명시되어 있다.
  • 고정 width/height 금지와 반응형 layout 원칙이 명시되어 있다.
  • V2 재사용 가능한 위젯 후보가 계획 문서에 기록되어 있다.
  • 구현 단계에서는 xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug -sdk iphonesimulator build가 성공해야 한다.

11. Open Questions

  • 현재 제공 요구사항 기준으로 미결정 사항은 없다.

12. Verification Notes

  • 2026-07-06: 사용자 요청으로 최초 plan-task.md가 먼저 작성되었고, 이후 PRD 누락 지적에 따라 본 PRD를 추가했다.
  • 2026-07-06: docs/prd/sample-prd.md, docs/20260705_메인_콘텐츠_내부_추천_탭/prd.md, docs/20260706_메인_콘텐츠_내부_랭킹_탭/plan-task.md를 확인해 PRD 형식과 계획 문서 내용을 맞췄다.
  • 2026-07-06: 사용자 요청 범위가 문서 생성이므로 Swift 소스, Xcode 프로젝트, asset은 수정하지 않는다.