16 KiB
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위, 2
7위, 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 순위 변화 표시 규칙
showRankChange는items와 같은 레벨에 내려오는 화면 제어 값이다.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은 Figmanode-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_newasset을 사용한다. - 다국어가 필요한 신규 문구는
I18n에 추가한다. 신규 진입 표시는 텍스트가 아니라ic_rank_new아이콘으로 처리한다.
10. Success Criteria
랭킹탭 진입 시GET /api/v2/home/rankings/creators호출이 발생한다.- 응답의
items가 rank 규칙에 따라 1위 단독, 27위 2열, 810위 3열, 11위 이상 row로 표시된다. - 1위, 2
7위, 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.md와docs/20260602_메인_홈_추천_UI_API_연동/prd.md를 확인해 저장소의 PRD 작성 스타일을 맞췄다.SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingView.swift가 현재 placeholder임을 확인했다.SodaLive/Sources/V2/Main/Home/MainHomeView.swift가MainHomeRankingView를 탭 콘텐츠로 조합하고 있음을 확인했다.- 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 금지 제약을 반영했다.