Files
sodalive-ios/docs/20260630_메인_홈_랭킹_탭/prd.md

16 KiB

PRD: 메인 홈 랭킹 탭

1. Overview

메인 홈 화면의 랭킹 탭에서 신규 API GET /api/v2/home/rankings/creators 응답을 사용해 인기 크리에이터 랭킹을 표시한다.

기존 MainHomeView는 홈 상단 공통 shell과 추천/랭킹/팔로잉 탭 전환만 담당한다. 랭킹 탭의 API 호출, 상태 관리, 화면 조립은 SodaLive/Sources/V2/Main/Home/Ranking/** 하위에서 처리한다.

Figma 전체 화면의 3번째 줄에 있는 Capsule Tab bar는 이번 랭킹 탭 화면에 배치하지 않는다. 즉, 주간 인기, 지금 뜨는 중, 남성 인기, 여성 인기 필터 UI는 구현 범위에서 제외한다.

2. Problem

  • 현재 MainHomeRankingView는 placeholder 상태라 홈의 랭킹 탭에서 실제 크리에이터 랭킹 데이터를 제공하지 못한다.
  • 랭킹 화면은 순위별로 카드 크기와 배치 방식이 다르므로, 단순 리스트로 처리하면 Figma 기준 UI와 맞지 않는다.
  • 서버는 순위 변화 표시 여부(showRankChange)와 각 item의 변화 값(rankChange, isNew)을 함께 내려주므로, 클라이언트가 이를 일관된 규칙으로 표현해야 한다.
  • 신규 랭킹 API를 기존 추천 탭 API나 기존 구 홈 API에 섞으면 홈 탭별 책임이 흐려지고 회귀 위험이 커진다.

3. Goals

  • 랭킹 탭 진입 시 GET /api/v2/home/rankings/creators를 호출하고 응답 데이터로 화면을 구성한다.
  • 랭킹 탭 전용 API, Repository, ViewModel, Response model을 SodaLive/Sources/V2/Main/Home/Ranking/** 아래에 둔다.
  • Figma 기준으로 1위, 27위, 810위, 11위 이상 item의 배치와 시각 구조를 구분한다.
  • items[].rank 값을 화면 순위로 사용하고 배열 index로 순위를 재계산하지 않는다.
  • showRankChange == true일 때만 순위 변화 badge를 표시한다.
  • items[].isNew == true이면 순위 변화 숫자 대신 ic_rank_new 아이콘을 표시한다.
  • items[].rankChange가 양수이면 순위 상승, 음수이면 순위 하락으로 표시한다.
  • 직전 공개 스냅샷이 없어 showRankChange == false이면 모든 item에서 순위 변화 UI를 숨긴다.
  • 최대 item 개수는 20개로 고정한다.
  • 크리에이터 item 탭 시 기존 크리에이터 상세 진입 흐름을 사용한다.
  • Capsule Tab bar는 배치하지 않는다.

4. Non-Goals

  • Figma 전체 화면 3번째 줄의 Capsule Tab bar를 구현하지 않는다.
  • 주간 인기, 지금 뜨는 중, 남성 인기, 여성 인기 같은 랭킹 필터/세그먼트 기능은 구현하지 않는다.
  • 이번 API 스펙에 없는 페이지네이션, 무한 스크롤, 정렬 변경, 성별 필터, 기간 필터를 추가하지 않는다.
  • 추천 탭, 팔로잉 탭, 하단 메인 탭 구조를 변경하지 않는다.
  • 기존 구 홈 화면의 HomeCreatorRankingItemView, HomeTabViewModel.creatorRanking 동작을 변경하지 않는다.
  • Figma localhost asset URL을 앱 코드에 직접 사용하지 않는다.
  • 서버가 내려주지 않는 팔로우 상태, 라이브 상태, 후원 정보, 카테고리 정보를 임의로 표시하지 않는다.

5. Target Users

  • 홈에서 현재 인기 크리에이터를 빠르게 탐색하려는 사용자
  • 크리에이터의 순위와 직전 공개 스냅샷 대비 변화 여부를 확인하려는 사용자
  • 신규 진입 크리에이터를 랭킹 화면에서 구분해 보고 싶은 사용자

6. User Stories

  • 사용자는 홈 랭킹 탭에서 인기 크리에이터 목록을 순위별 강조도에 맞게 보고 싶다.
  • 사용자는 순위가 오른 크리에이터와 내려간 크리에이터를 구분하고 싶다.
  • 사용자는 새로 랭킹에 진입한 크리에이터를 신규 진입 아이콘으로 확인하고 싶다.
  • 사용자는 마음에 드는 크리에이터를 탭해 상세 화면으로 이동하고 싶다.

7. Core Requirements

7.1 랭킹 데이터 조회

API

  • Method: GET
  • Path: /api/v2/home/rankings/creators
  • 인증: 기존 V2 홈 API 인증 헤더 패턴을 따른다.
  • 응답 래퍼: 기존 관례대로 ApiResponse<MainHomeCreatorRankingResponse> 디코딩을 우선한다.

Response

{
  "showRankChange": true,
  "items": [
    {
      "rank": 1,
      "rankChange": 5,
      "isNew": false,
      "creatorId": 123,
      "nickname": "creator",
      "profileImageUrl": "https://cdn.example.com/profile.png"
    },
    {
      "rank": 2,
      "rankChange": null,
      "isNew": true,
      "creatorId": 456,
      "nickname": "new creator",
      "profileImageUrl": "https://cdn.example.com/profile-new.png"
    }
  ]
}

Swift 모델 기준

struct MainHomeCreatorRankingResponse: Decodable {
    let showRankChange: Bool
    let items: [MainHomeCreatorRankingItem]
}

struct MainHomeCreatorRankingItem: Decodable, Identifiable {
    let rank: Int
    let rankChange: Int?
    let isNew: Bool
    let creatorId: Int
    let nickname: String
    let profileImageUrl: String?

    var id: Int { creatorId }
}

profileImageUrl은 서버 예시에서 문자열로 내려오지만, 이미지가 없을 수 있는 운영 데이터를 고려해 Swift 모델에서는 optional로 선언한다. 이미지가 비어 있거나 로드 실패하면 기존 프로젝트의 기본 프로필 이미지 처리 패턴을 따른다.

7.2 순위 변화 표시 규칙

  • showRankChangeitems와 같은 레벨에 내려오는 화면 제어 값이다.
  • showRankChange == false이면 모든 item에서 rankChange, isNew 값과 관계없이 순위 변화 UI를 표시하지 않는다.
  • showRankChange == true && item.isNew == true이면 ic_rank_new 아이콘을 표시한다.
  • isNew == true인 item은 비교 가능한 이전 순위가 없으므로 rankChange == nil로 내려오는 것을 정상으로 취급한다.
  • showRankChange == true && item.isNew == false && item.rankChange != nil이면 순위 변화 숫자 badge를 표시한다.
  • rankChange > 0이면 순위 상승으로 표시한다. 예: 직전 10위, 최신 5위는 rankChange == 5.
  • rankChange < 0이면 순위 하락으로 표시한다. 예: 직전 1위, 최신 10위는 rankChange == -9.
  • rankChange == 0이면 숫자 0은 표시하지 않고 ic_rank_caret_stay 아이콘으로 변동 없음 상태를 표시한다.
  • 숫자 badge에는 변화량의 절댓값을 표시하고, 상승/하락 방향은 전용 caret 아이콘으로 구분한다.
  • 상승 badge는 Figma node-id=24-5674 기준으로 표시하고 ic_rank_caret_increase 아이콘을 사용한다.
  • 하락 badge는 Figma node-id=24-5675 기준으로 표시하고 ic_rank_caret_decrease 아이콘을 사용한다.
  • 신규 진입 표시는 텍스트 New 대신 ic_rank_new 아이콘을 사용한다.
  • rankChange == nil && isNew == false이면 순위 변화 badge를 표시하지 않는다.

직전 공개 스냅샷이 없을 때 서버 응답은 아래처럼 처리된다는 전제를 둔다.

  • showRankChange == false
  • 각 item의 rankChange == nil
  • 각 item의 isNew == false

7.3 랭킹 item 레이아웃

Figma 참조:

  • 전체 화면: node-id=24-5654
  • 1위 카드: node-id=24-5659
  • 2~7위 카드: node-id=24-5661
  • 8~10위 카드: node-id=24-5667
  • 11위 이상 row: node-id=24-5670

배치 규칙:

  • rank == 1: 한 줄에 1개, 화면 width 기준 큰 카드로 표시한다.
  • rank >= 2 && rank <= 7: 한 줄에 2개 grid 카드로 표시한다.
  • rank >= 8 && rank <= 10: 한 줄에 3개 grid 카드로 표시한다.
  • rank >= 11: row 방식으로 표시한다.
  • rank == 1, rank >= 2 && rank <= 7, rank >= 8 && rank <= 10은 크기와 배치만 다르고 카드 모양은 동일하므로 하나의 카드 컴포넌트를 재사용한다.
  • 1열/2열/3열 배치는 카드 컴포넌트가 아니라 부모 grid/layout이 결정한다.
  • rank >= 11은 row 방식으로 모양이 다르므로 카드와 별도 row 컴포넌트로 분리한다.
  • 카드/row 컴포넌트의 전체 레이아웃 크기는 고정 숫자 width/height로 제한하지 않고, 화면 크기에 대응할 수 있도록 부모 width, grid column, aspect ratio, relative sizing 기반으로 구성한다.
  • API 응답 item은 최대 20개로 취급한다.
  • item은 서버가 내려준 items 순서를 기본으로 렌더링하되, 화면에 표시하는 순위 숫자는 item.rank를 사용한다.
  • 서버 순서와 item.rank가 불일치해도 클라이언트에서 임의 재정렬하지 않는다. 불일치는 서버 데이터 문제로 보고 QA/로그 확인 대상으로 남긴다.
  • 카드/row는 전체적으로 검정 배경 위에 배치한다.
  • profile image는 카드/row의 주요 이미지로 사용한다.
  • nickname은 Figma의 텍스트 위치에 표시한다.
  • rank 숫자는 Figma의 rank number 위치와 크기 계층을 따른다.
  • 카드형 item은 이미지 하단에 dim gradient를 적용해 nickname 가독성을 확보한다.

7.4 화면 상태

  • 최초 진입 또는 탭 전환으로 랭킹 탭이 처음 노출될 때 API를 호출한다.
  • 동일 화면 생명주기 안에서 이미 성공적으로 로드한 데이터가 있으면 불필요한 중복 호출은 피한다.
  • items가 빈 배열이면 empty state를 표시한다.
  • API 실패 시에도 empty state 문구를 표시한다.
  • empty state 문구는 순위 집계 중입니다.\n잠시 후 다시 시도해 주세요.로 표시한다.
  • pull-to-refresh가 기존 MainHomeView 또는 추천 탭에 있다면 같은 패턴으로 재조회한다. 기존 패턴이 없다면 이번 범위에서 새로 추가하지 않는다.

7.5 상세 진입

  • 랭킹 item 탭 시 creatorId로 기존 크리에이터 상세 화면에 진입한다.
  • 랭킹 조회 자체는 로그인, 본인인증, 민감 콘텐츠 설정으로 막지 않는다.
  • 랭킹 페이지 노출에는 로그인 guard가 필요하지 않다.
  • 터치 등의 사용자 액션으로 상세 페이지에 진입할 때는 로그인 guard가 필요하다.
  • 상세 진입 guard는 기존 메인/추천 탭에서 사용하는 guard 패턴을 재사용한다.
  • 상세 진입 전 서버 응답에 없는 성인/민감 콘텐츠 여부를 클라이언트에서 임의 추정하지 않는다.

8. UX / UI Expectations

8.1 포함하는 UI

  • 홈 공통 title bar
  • 홈 상단 추천/랭킹/팔로잉 Text tab bar
  • 선택된 랭킹 탭 콘텐츠
  • 크리에이터 랭킹 카드/row 목록
  • 순위 변화 숫자 badge 또는 신규 진입 아이콘
  • 하단 메인 tab bar는 기존 MainView 구조에서 유지

8.2 제외하는 UI

  • Figma 전체 화면 3번째 줄의 Capsule Tab bar
  • 랭킹 필터 chip/segment
  • 화면 내 별도 섹션 타이틀

8.3 시각 규칙

  • 선택된 홈 상단 탭 랭킹은 흰색 bold text로 표시한다.
  • 비선택 탭 추천, 팔로잉은 gray text로 표시한다.
  • 카드 corner radius, 이미지 crop, dim gradient, badge 위치는 Figma 기준을 따른다.
  • 카드/row의 루트 레이아웃에는 고정 숫자 width/height를 사용하지 않는다.
  • 카드 비율과 row 높이는 aspectRatio, grid column width, container-relative size 등 반응형 제약을 우선 사용한다.
  • 신규 진입 표시는 텍스트 New 대신 ic_rank_new 아이콘을 사용한다.
  • rankChange 상승 badge는 Figma node-id=24-5674 기준으로 어두운 배경 위 숫자와 ic_rank_caret_increase 아이콘을 사용한다.
  • rankChange 하락 badge는 Figma node-id=24-5675 기준으로 어두운 배경 위 숫자와 ic_rank_caret_decrease 아이콘을 사용한다.
  • rankChange == 0은 Figma node-id=24-5678 예시가 있더라도 숫자 0을 표시하지 않고 ic_rank_caret_stay 아이콘으로 변동 없음 상태를 표시한다.
  • nickname이 길면 카드/row 영역을 넘지 않도록 한 줄 말줄임 또는 기존 프로젝트의 nickname 표시 관례를 따른다.

8.4 순위 변화 아이콘

  • 상승: ic_rank_caret_increase
  • 하락: ic_rank_caret_decrease
  • 변동 없음: ic_rank_caret_stay
  • 신규 진입: ic_rank_new

9. Technical Constraints

  • 신규 랭킹 탭 관련 파일은 SodaLive/Sources/V2/Main/Home/Ranking/** 아래에 둔다.
  • MainHomeView에는 API 호출, ranking item 변환, 랭킹 세부 UI를 넣지 않는다.
  • 기존 MainHomeRecommendationView와 추천 탭 API 동작을 변경하지 않는다.
  • 기존 TextTabBar 또는 홈 상단 탭 컴포넌트를 재사용한다.
  • profile image 로딩은 기존 프로젝트의 이미지 로딩 패턴을 따른다.
  • 카드/row UI 컴포넌트의 루트 container는 고정 숫자 frame(width:), frame(height:)로 제한하지 않는다.
  • 외부 라이브러리를 추가하지 않는다.
  • Figma에서 제공되는 localhost asset URL을 앱 코드에 직접 사용하지 않는다.
  • 신규 순위 변화 아이콘은 ic_rank_caret_decrease, ic_rank_caret_increase, ic_rank_caret_stay, ic_rank_new asset을 사용한다.
  • 다국어가 필요한 신규 문구는 I18n에 추가한다. 신규 진입 표시는 텍스트가 아니라 ic_rank_new 아이콘으로 처리한다.

10. Success Criteria

  • 랭킹 탭 진입 시 GET /api/v2/home/rankings/creators 호출이 발생한다.
  • 응답의 items가 rank 규칙에 따라 1위 단독, 27위 2열, 810위 3열, 11위 이상 row로 표시된다.
  • 1위, 27위, 810위는 하나의 카드 컴포넌트를 재사용하고, 11위 이상은 별도 row 컴포넌트를 사용한다.
  • 카드/row 컴포넌트의 루트 레이아웃은 고정 숫자 width/height가 아니라 화면 크기 대응 가능한 제약으로 구성되어 있다.
  • 최대 20개 item을 표시 대상으로 처리한다.
  • 화면에 표시되는 순위 숫자는 배열 index가 아니라 items[].rank와 일치한다.
  • showRankChange == false이면 모든 item에서 순위 변화 UI가 표시되지 않는다.
  • showRankChange == true && isNew == true이면 ic_rank_new 아이콘이 표시된다.
  • showRankChange == true && rankChange > 0이면 상승 변화량과 ic_rank_caret_increase 아이콘이 표시된다.
  • showRankChange == true && rankChange < 0이면 하락 변화량과 ic_rank_caret_decrease 아이콘이 표시된다.
  • showRankChange == true && rankChange == 0이면 숫자 0은 표시되지 않고 ic_rank_caret_stay 아이콘이 표시된다.
  • 신규 진입 item의 rankChange == nil은 오류로 처리하지 않는다.
  • items가 빈 배열이거나 API 호출이 실패하면 순위 집계 중입니다.\n잠시 후 다시 시도해 주세요. 문구가 표시된다.
  • Figma 전체 화면 3번째 줄의 Capsule Tab bar가 화면에 배치되지 않는다.
  • 랭킹 페이지 자체는 로그인 guard 없이 노출된다.
  • item 탭 시 로그인 guard를 거친 뒤 해당 creatorId의 크리에이터 상세 화면으로 이동한다.
  • MainHomeView는 탭 shell 역할만 유지하고 랭킹 탭 세부 구현은 Ranking 하위로 분리된다.

11. Open Questions

해당 없음.

12. Verification Notes

  • docs/agent-guides/documentation-policy.md를 확인해 신규 PRD 경로와 필수 섹션 기준을 검증했다.
  • docs/prd/sample-prd.mddocs/20260602_메인_홈_추천_UI_API_연동/prd.md를 확인해 저장소의 PRD 작성 스타일을 맞췄다.
  • SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingView.swift가 현재 placeholder임을 확인했다.
  • SodaLive/Sources/V2/Main/Home/MainHomeView.swiftMainHomeRankingView를 탭 콘텐츠로 조합하고 있음을 확인했다.
  • Figma node-id=24-5654, 24-5659, 24-5661, 24-5667, 24-5670의 design context와 screenshot을 확인해 랭킹 화면 및 item 형태를 검증했다.
  • 사용자 추가 결정 사항을 반영해 최대 item 개수, empty state 문구, API 실패 표시 방식, 로그인 guard 범위, 신규 진입 표시 정책을 확정 요구사항으로 이동했다.
  • Figma node-id=24-5674, 24-5675, 24-5678의 design context와 screenshot을 확인해 상승/하락/0 변화 badge 기준을 검증했다.
  • 순위 변화 아이콘 리소스명 ic_rank_caret_decrease, ic_rank_caret_increase, ic_rank_caret_stay, ic_rank_new를 요구사항에 반영했다.
  • 1~10위 카드 컴포넌트 재사용, 11위 이상 row 분리, 루트 레이아웃의 고정 숫자 width/height 금지 제약을 반영했다.