29 KiB
29 KiB
메인 홈 랭킹 탭 구현 계획
기준 문서
- PRD:
docs/20260630_메인_홈_랭킹_탭/prd.md - 코드 스타일:
docs/agent-guides/code-style.md - 빌드/검증:
docs/agent-guides/build-test-verification.md - Figma 전체 화면:
24:5654 - Figma 1위 카드:
24:5659 - Figma 2~7위 카드:
24:5661 - Figma 8~10위 카드:
24:5667 - Figma 11위 이상 row:
24:5670 - Figma 상승 badge:
24:5674 - Figma 하락 badge:
24:5675 - Figma 변동 없음 badge 참고:
24:5678
구현 원칙
MainHomeView는 홈 상단 title bar,추천/랭킹/팔로잉Text tab shell만 담당한다.- 랭킹 탭의 API, 모델, ViewModel, 세부 UI는
SodaLive/Sources/V2/Main/Home/Ranking/**아래에 둔다. - 기존 추천 탭 API/ViewModel/Moya 패턴을 따르고, 기존 구 홈 API/랭킹 구현은 수정하지 않는다.
- Capsule Tab bar와 랭킹 필터는 구현하지 않는다.
- 서버가 내려준
items[].rank를 화면 순위로 사용하고 클라이언트에서 index 기반 순위를 재계산하지 않는다. - item은 최대 20개로 취급한다.
- 1~10위 카드는 모양이 동일하므로
MainHomeRankingCard하나를 재사용하고, 1열/2열/3열 차이는 부모 grid/layout에서 처리한다. - 11위 이상은 row 방식으로 모양이 다르므로
MainHomeRankingRow로 분리한다. - 카드/row 컴포넌트의 루트 레이아웃은 고정 숫자 width/height로 제한하지 않고, grid column, parent width,
aspectRatio, relative sizing 기반으로 화면 크기에 대응한다. - 랭킹 페이지 노출 자체는 로그인 guard 없이 허용하고, item 탭으로 상세 진입할 때만 기존 creator 상세 guard를 재사용한다.
- 외부 라이브러리는 추가하지 않는다.
주요 대상 파일
생성
SodaLive/Sources/V2/Main/Home/Ranking/Models/MainHomeCreatorRankingResponse.swiftSodaLive/Sources/V2/Main/Home/Ranking/Repository/MainHomeRankingApi.swiftSodaLive/Sources/V2/Main/Home/Ranking/Repository/MainHomeRankingRepository.swiftSodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingViewModel.swiftSodaLive/Sources/V2/Main/Home/Ranking/Components/MainHomeRankChangeBadge.swiftSodaLive/Sources/V2/Main/Home/Ranking/Components/MainHomeRankingCard.swiftSodaLive/Sources/V2/Main/Home/Ranking/Components/MainHomeRankingRow.swiftSodaLive/Sources/V2/Main/Home/Ranking/Components/MainHomeRankingEmptyStateView.swift
수정
SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingView.swiftSodaLive/Sources/V2/Main/Home/MainHomeView.swiftSodaLive/Sources/I18n/I18n.swift- 필요 시
SodaLive.xcodeproj/project.pbxproj
확인
SodaLive/Sources/V2/Main/Home/Recommendation/Repository/MainHomeRecommendationApi.swiftSodaLive/Sources/V2/Main/Home/Recommendation/Repository/MainHomeRecommendationRepository.swiftSodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationViewModel.swiftSodaLive/Sources/V2/Main/MainView.swiftSodaLive/Resources/Assets.xcassets/v2/ic_rank_caret_decrease.imagesetSodaLive/Resources/Assets.xcassets/v2/ic_rank_caret_increase.imagesetSodaLive/Resources/Assets.xcassets/v2/ic_rank_caret_stay.imagesetSodaLive/Resources/Assets.xcassets/v2/ic_rank_new.imageset
TASK 체크리스트
Phase 1: 기준점 점검
-
Task 1.1: PRD와 기존 랭킹 placeholder 확인
- 대상 파일:
- 확인:
docs/20260630_메인_홈_랭킹_탭/prd.md - 확인:
SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingView.swift - 확인:
SodaLive/Sources/V2/Main/Home/MainHomeView.swift
- 확인:
- 작업 내용:
- PRD 성공 기준과 제외 범위를 다시 읽고 구현 범위를 확정한다.
MainHomeRankingView가 placeholder인지 확인한다.MainHomeView가 랭킹 탭에onTapCreator를 아직 전달하지 않는지 확인한다.
- 검증 기준:
- 실행 명령:
sed -n '1,140p' SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingView.swift && sed -n '1,120p' SodaLive/Sources/V2/Main/Home/MainHomeView.swift - 기대 결과: 랭킹 탭은 placeholder이고,
MainHomeView는 탭 shell 역할만 담당한다.
- 실행 명령:
- 대상 파일:
-
Task 1.2: 순위 변화 아이콘 asset 확인
- 대상 파일:
- 확인:
SodaLive/Resources/Assets.xcassets/v2/ic_rank_caret_decrease.imageset - 확인:
SodaLive/Resources/Assets.xcassets/v2/ic_rank_caret_increase.imageset - 확인:
SodaLive/Resources/Assets.xcassets/v2/ic_rank_caret_stay.imageset - 확인:
SodaLive/Resources/Assets.xcassets/v2/ic_rank_new.imageset
- 확인:
- 작업 내용:
- PRD에서 지정한 순위 변화 아이콘 asset이 존재하는지 확인한다.
- asset이 없으면 구현을 진행하지 말고 PRD/사용자 확인을 먼저 받는다.
- 검증 기준:
- 실행 명령:
find SodaLive/Resources/Assets.xcassets/v2 -maxdepth 2 -name 'ic_rank_*' | sort - 기대 결과:
ic_rank_caret_decrease,ic_rank_caret_increase,ic_rank_caret_stay,ic_rank_newimageset이 모두 확인된다.
- 실행 명령:
- 대상 파일:
Phase 2: API와 응답 모델
-
Task 2.1: 랭킹 응답 모델 생성
- 대상 파일:
- 생성:
SodaLive/Sources/V2/Main/Home/Ranking/Models/MainHomeCreatorRankingResponse.swift
- 생성:
- 작업 내용:
MainHomeCreatorRankingResponse와MainHomeCreatorRankingItem을 PRD 필드명 그대로 만든다.profileImageUrl은 optional로 둔다.MainHomeCreatorRankingItem은Identifiable을 채택하고id는creatorId를 사용한다.
- 검증 기준:
- 실행 명령:
rg "struct MainHomeCreatorRankingResponse|struct MainHomeCreatorRankingItem|showRankChange|rankChange|profileImageUrl|var id" SodaLive/Sources/V2/Main/Home/Ranking/Models/MainHomeCreatorRankingResponse.swift - 기대 결과: PRD의 응답 필드와
Identifiableid 기준이 확인된다.
- 실행 명령:
- 대상 파일:
-
Task 2.2: 랭킹 API endpoint 생성
- 대상 파일:
- 생성:
SodaLive/Sources/V2/Main/Home/Ranking/Repository/MainHomeRankingApi.swift - 확인:
SodaLive/Sources/V2/Main/Home/Recommendation/Repository/MainHomeRecommendationApi.swift
- 생성:
- 작업 내용:
MainHomeRankingApi를TargetType으로 만든다.- case는
getCreatorRankings하나만 둔다. - path는
/api/v2/home/rankings/creators, method는.get, task는.requestPlain로 둔다. - headers는 기존 V2 홈 추천 API와 동일하게
Authorization: Bearer {token}패턴을 사용한다.
- 검증 기준:
- 실행 명령:
rg "/api/v2/home/rankings/creators|getCreatorRankings|Authorization|requestPlain" SodaLive/Sources/V2/Main/Home/Ranking/Repository/MainHomeRankingApi.swift - 기대 결과: 신규 endpoint와 인증 헤더가 랭킹 전용 API에만 존재한다.
- 실행 명령:
- 대상 파일:
-
Task 2.3: 랭킹 Repository 생성
- 대상 파일:
- 생성:
SodaLive/Sources/V2/Main/Home/Ranking/Repository/MainHomeRankingRepository.swift - 확인:
SodaLive/Sources/V2/Main/Home/Recommendation/Repository/MainHomeRecommendationRepository.swift
- 생성:
- 작업 내용:
MainHomeRankingRepository를 만들고MoyaProvider<MainHomeRankingApi>를 사용한다.getCreatorRankings() -> AnyPublisher<Response, MoyaError>메서드를 제공한다.
- 검증 기준:
- 실행 명령:
rg "final class MainHomeRankingRepository|MoyaProvider<MainHomeRankingApi>|getCreatorRankings\\(\\)|AnyPublisher<Response, MoyaError>" SodaLive/Sources/V2/Main/Home/Ranking/Repository/MainHomeRankingRepository.swift - 기대 결과: 추천 탭과 같은 Repository 패턴이 확인된다.
- 실행 명령:
- 대상 파일:
Phase 3: ViewModel 상태와 데이터 로딩
-
Task 3.1: 랭킹 ViewModel 생성
- 대상 파일:
- 생성:
SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingViewModel.swift - 확인:
SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationViewModel.swift
- 생성:
- 작업 내용:
MainHomeRankingViewModel을ObservableObject로 만든다.isLoading,items,showRankChange,emptyMessage,hasLoaded상태를 둔다.fetchRankings()에서ApiResponse<MainHomeCreatorRankingResponse>를 디코딩한다.- 성공 시
items는 서버 순서를 유지하되 최대 20개만 보관한다. - 빈 배열 또는 API 실패/디코딩 실패 시
emptyMessage에I18n.HomeRanking.emptyStateMessage를 설정한다. - 실패 시 toast 대신 empty state를 표시한다.
- 검증 기준:
- 실행 명령:
rg "final class MainHomeRankingViewModel|fetchRankings|ApiResponse<MainHomeCreatorRankingResponse>|prefix\\(20\\)|emptyStateMessage|hasLoaded" SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingViewModel.swift - 기대 결과: 최대 20개 처리, empty state 처리, API 디코딩 로직이 확인된다.
- 실행 명령:
- 대상 파일:
-
Task 3.2: 랭킹 empty state I18n 추가
- 대상 파일:
- 수정:
SodaLive/Sources/I18n/I18n.swift
- 수정:
- 작업 내용:
I18n.HomeRanking.emptyStateMessage를 추가한다.- ko는
순위 집계 중입니다.\n잠시 후 다시 시도해 주세요.로 둔다. - en/ja는 기능 검증을 막지 않는 자연스러운 대응 문구로 추가한다.
- 검증 기준:
- 실행 명령:
rg "enum HomeRanking|emptyStateMessage|순위 집계 중입니다|잠시 후 다시 시도해 주세요" SodaLive/Sources/I18n/I18n.swift - 기대 결과: 랭킹 empty state 문구가 I18n에 존재한다.
- 실행 명령:
- 대상 파일:
Phase 4: 순위 변화 UI 컴포넌트
- Task 4.1: 순위 변화 badge 컴포넌트 생성
- 대상 파일:
- 생성:
SodaLive/Sources/V2/Main/Home/Ranking/Components/MainHomeRankChangeBadge.swift
- 생성:
- 작업 내용:
- 입력값은
showRankChange: Bool,rankChange: Int?,isNew: Bool로 둔다. showRankChange == false이면 아무것도 표시하지 않는다.isNew == true이면Image("ic_rank_new")만 표시한다.rankChange > 0이면 절댓값 숫자와Image("ic_rank_caret_increase")를 표시한다.rankChange < 0이면 절댓값 숫자와Image("ic_rank_caret_decrease")를 표시한다.rankChange == 0이면 숫자 없이Image("ic_rank_caret_stay")만 표시한다.rankChange == nil && isNew == false이면 아무것도 표시하지 않는다.- Figma 기준 rank 변화 badge는 컴포넌트 내부에서
Color.gray900(#202020) 배경, horizontal padding 4, corner radius 4를 적용한다. - Preview에는 상승, 하락, 변동 없음, 신규 진입,
showRankChange == false,rankChange == nil && isNew == false상태를 모두 배치해 한 화면에서 UI 차이를 확인할 수 있게 한다.
- 입력값은
- 검증 기준:
- 실행 명령:
rg "struct MainHomeRankChangeBadge|ic_rank_new|ic_rank_caret_increase|ic_rank_caret_decrease|ic_rank_caret_stay|abs\\(|Color.gray900|cornerRadius\\(4\\)|padding\\(\\.horizontal, 4\\)|PreviewProvider|showRankChange: false|rankChange: nil|isNew: true|rankChange: 0" SodaLive/Sources/V2/Main/Home/Ranking/Components/MainHomeRankChangeBadge.swift - 기대 결과: PRD의 순위 변화 표시 규칙, Figma badge 배경/라운드/패딩, 아이콘명, 조건별 Preview가 모두 확인된다.
- 실행 명령:
- 대상 파일:
Phase 5: 순위별 랭킹 UI 컴포넌트
-
Task 5.1: 1~10위 공용 카드 컴포넌트 생성
- 대상 파일:
- 생성:
SodaLive/Sources/V2/Main/Home/Ranking/Components/MainHomeRankingCard.swift - 확인:
SodaLive/Sources/V2/Component/Creator/CreatorProfileItem.swift - 확인:
SodaLive/Sources/Home/HomeCreatorRankingItemView.swift
- 생성:
- 작업 내용:
rank >= 1 && rank <= 10카드형 item을 모두 담당하는 공용 컴포넌트로 만든다.- Figma 기준으로 1위, 2
7위, 810위 카드는 크기만 다르고 모양은 동일하므로 별도 카드 파일을 만들지 않는다. - 1열/2열/3열 배치와 카드 크기는 부모
MainHomeRankingView의 grid/layout이 결정한다. DownsampledKFImage로profileImageUrl을 표시한다.- 카드 하단에 dim gradient를 적용하고 nickname을 한 줄 말줄임으로 표시한다.
- rank 숫자는
item.rank를 사용한다. - 순위 변화 표시는
MainHomeRankChangeBadge를 사용한다. - 카드 tap 시
onTapCreator(item.creatorId)를 호출한다. - 루트 container에 고정 숫자
frame(width:),frame(height:)를 사용하지 않는다. - 전체 카드 비율은 부모 width 기반
aspectRatio또는 container-relative 제약으로 잡는다. - Preview에는 1위 크기, 2열 크기, 3열 크기 예시를 함께 배치한다.
- Preview에는 상승, 하락, 변동 없음, 신규 진입, 순위 변화 숨김 상태를 모두 배치해 한 화면에서 UI 차이를 확인할 수 있게 한다.
- 검증 기준:
- 실행 명령:
rg "struct MainHomeRankingCard|DownsampledKFImage|LinearGradient|item.rank|MainHomeRankChangeBadge|onTapCreator\\(item.creatorId\\)|aspectRatio|PreviewProvider|rankChange: 5|rankChange: -|rankChange: 0|isNew: true|showRankChange: false" SodaLive/Sources/V2/Main/Home/Ranking/Components/MainHomeRankingCard.swift - 기대 결과: 1~10위 카드가 하나의 공용 컴포넌트로 구현되고, 서버 rank와 공용 badge를 사용하며, 전체 크기는 부모 layout에 의해 결정되고 조건별 Preview가 확인된다.
- 실행 명령:
- 대상 파일:
-
Task 5.2: 11위 이상 row 컴포넌트 생성
- 대상 파일:
- 생성:
SodaLive/Sources/V2/Main/Home/Ranking/Components/MainHomeRankingRow.swift
- 생성:
- 작업 내용:
rank >= 11item을 row 방식으로 표시한다.- rank 숫자, profile image, nickname, 순위 변화 badge를 Figma row 구조에 맞춰 배치한다.
- rank 숫자는
item.rank를 사용한다. - 순위 변화 표시는
MainHomeRankChangeBadge를 사용한다. - row tap 시
onTapCreator(item.creatorId)를 호출한다. - 루트 container에 고정 숫자
frame(width:),frame(height:)를 사용하지 않는다. - row의 전체 높이는 고정 숫자로 박지 않고 내부 padding, image aspect ratio, parent width 기반 제약으로 구성한다.
- Preview에는 11위 이상 row의 상승, 하락, 변동 없음, 신규 진입, 순위 변화 숨김 상태를 모두 배치해 한 화면에서 UI 차이를 확인할 수 있게 한다.
- 검증 기준:
- 실행 명령:
rg "struct MainHomeRankingRow|DownsampledKFImage|item.rank|MainHomeRankChangeBadge|onTapCreator\\(item.creatorId\\)|aspectRatio|PreviewProvider|rankChange: 5|rankChange: -|rankChange: 0|isNew: true|showRankChange: false" SodaLive/Sources/V2/Main/Home/Ranking/Components/MainHomeRankingRow.swift - 기대 결과: row형 순위 UI가 서버 rank와 공용 badge를 사용하고, 전체 크기는 반응형 제약으로 구성되며 조건별 Preview가 확인된다.
- 실행 명령:
- 대상 파일:
-
Task 5.3: empty state 컴포넌트 생성
- 대상 파일:
- 생성:
SodaLive/Sources/V2/Main/Home/Ranking/Components/MainHomeRankingEmptyStateView.swift
- 생성:
- 작업 내용:
I18n.HomeRanking.emptyStateMessage를 중앙 또는 랭킹 콘텐츠 영역 안에 표시한다.- 배경은 검정, 텍스트는 기존 V2 empty state 관례와 맞춘다.
- 검증 기준:
- 실행 명령:
rg "struct MainHomeRankingEmptyStateView|I18n.HomeRanking.emptyStateMessage|Color.black" SodaLive/Sources/V2/Main/Home/Ranking/Components/MainHomeRankingEmptyStateView.swift - 기대 결과: empty/API 실패 공통 문구 표시 컴포넌트가 확인된다.
- 실행 명령:
- 대상 파일:
Phase 6: 랭킹 탭 화면 조립과 상세 진입 연결
-
Task 6.1: MainHomeRankingView 구현
- 대상 파일:
- 수정:
SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingView.swift
- 수정:
- 작업 내용:
- placeholder를 제거한다.
@StateObject private var viewModel = MainHomeRankingViewModel()를 사용한다.- 최초
onAppear에서hasLoaded == false일 때만fetchRankings()를 호출한다. - loading, empty, content 상태를 분기한다.
- content 상태에서는 1위 단독, 2
7위 2열, 810위 3열, 11위 이상 row로 조립한다. - 1~10위는 공용
MainHomeRankingCard, 11위 이상은MainHomeRankingRow를 사용한다. - 2열/3열 grid는 고정 숫자 card width가 아니라
GridItem(.flexible())기반 column으로 구성한다. MainHomeRankingView에서 카드/row 컴포넌트의 루트 크기를 고정 숫자로 강제하지 않는다.- Capsule Tab bar는 추가하지 않는다.
- 생성한 카드/row 컴포넌트에
showRankChange와onTapCreator를 전달한다.
- 검증 기준:
- 실행 명령:
rg "MainHomeRankingViewModel|fetchRankings|MainHomeRankingCard|MainHomeRankingRow|MainHomeRankingEmptyStateView|GridItem\\(\\.flexible\\(\\)\\)|onTapCreator|MainPlaceholderTabView|CapsuleTabBar" SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingView.swift - 기대 결과:
MainPlaceholderTabView와CapsuleTabBar는 검색되지 않고, 랭킹 ViewModel/공용 카드/row 조립과 flexible grid가 확인된다.
- 실행 명령:
- 대상 파일:
-
Task 6.2: MainHomeView에서 creator tap callback 전달
- 대상 파일:
- 수정:
SodaLive/Sources/V2/Main/Home/MainHomeView.swift - 확인:
SodaLive/Sources/V2/Main/MainView.swift
- 수정:
- 작업 내용:
- 랭킹 탭에서
MainHomeRankingView(onTapCreator: onTapCreator)를 호출하도록 변경한다. MainView의 기존handleRecommendationCreatorTap로그인 guard 흐름을 그대로 재사용한다.- 랭킹 페이지 노출 자체에는 별도 guard를 추가하지 않는다.
- 랭킹 탭에서
- 검증 기준:
- 실행 명령:
rg "MainHomeRankingView\\(onTapCreator: onTapCreator\\)|handleRecommendationCreatorTap|setAppStep\\(step: \\.login\\)|creatorDetail" SodaLive/Sources/V2/Main/Home/MainHomeView.swift SodaLive/Sources/V2/Main/MainView.swift - 기대 결과: 랭킹 item tap이 기존 creator 상세 guard callback으로 연결된다.
- 실행 명령:
- 대상 파일:
Phase 7: 프로젝트 연결과 전체 검증
-
Task 7.1: Xcode 프로젝트 포함 여부 확인
- 대상 파일:
- 확인/필요 시 수정:
SodaLive.xcodeproj/project.pbxproj - 확인:
SodaLive/Sources/V2/Main/Home/Ranking/**
- 확인/필요 시 수정:
- 작업 내용:
- 신규 Swift 파일이 빌드 대상에 포함되는지 확인한다.
- 현재 프로젝트 구조에서 파일 시스템 동기화가 자동이 아니면
project.pbxproj에 필요한 file reference/build phase를 추가한다.
- 검증 기준:
- 실행 명령:
rg "MainHomeCreatorRankingResponse|MainHomeRankingApi|MainHomeRankingRepository|MainHomeRankingViewModel|MainHomeRankChangeBadge|MainHomeRankingCard|MainHomeRankingRow|MainHomeRankingEmptyStateView" SodaLive.xcodeproj/project.pbxproj SodaLive/Sources/V2/Main/Home/Ranking - 기대 결과: 신규 Swift 파일이 소스 경로와 필요 시 Xcode 프로젝트에서 확인된다.
- 실행 명령:
- 대상 파일:
-
Task 7.2: 정적 검색 검증
- 대상 파일:
- 확인:
SodaLive/Sources/V2/Main/Home/Ranking/** - 확인:
SodaLive/Sources/V2/Main/Home/MainHomeView.swift - 확인:
SodaLive/Sources/I18n/I18n.swift
- 확인:
- 작업 내용:
- endpoint, 아이콘명, empty state, 공용 카드/row 컴포넌트 분리, Capsule Tab bar 제외, rank index 미사용 여부를 검색으로 확인한다.
- 카드/row 컴포넌트의 루트 container가 고정 숫자
frame(width:)/frame(height:)로 제한되지 않는지는 코드 리뷰로 확인한다. 단, 아이콘/내부 이미지의 작은 고정 크기는 이 금지 대상이 아니다.
- 검증 기준:
- 실행 명령:
rg "/api/v2/home/rankings/creators|ic_rank_caret_decrease|ic_rank_caret_increase|ic_rank_caret_stay|ic_rank_new|순위 집계 중입니다|MainHomeRankingCard|MainHomeRankingRow|CapsuleTabBar|enumerated\\(\\)" SodaLive/Sources/V2/Main/Home/Ranking SodaLive/Sources/V2/Main/Home/MainHomeView.swift SodaLive/Sources/I18n/I18n.swift - 기대 결과: endpoint/아이콘/문구/공용 카드/row 컴포넌트는 확인되고, 랭킹 UI에서
CapsuleTabBar와 rank 계산용enumerated()사용은 없어야 한다.
- 실행 명령:
- 대상 파일:
-
Task 7.3: 빌드 검증
- 대상 파일:
- 확인:
SodaLive.xcworkspace - 확인:
SodaLive.xcodeproj
- 확인:
- 작업 내용:
- 공식 빌드 명령으로 컴파일 오류를 확인한다.
- 테스트 번들 타깃이 확인되지 않는 저장소 상태이므로, 우선 Debug build를 필수 검증으로 둔다.
- 검증 기준:
- 실행 명령:
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build - 기대 결과: build succeeded
- 실행 명령:
- 대상 파일:
-
Task 7.4: 수동 QA 체크
- 대상 파일:
- 확인:
SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingView.swift - 확인:
SodaLive/Sources/V2/Main/Home/Ranking/Components/**
- 확인:
- 작업 내용:
- 랭킹 탭 진입 시 API가 1회 호출되는지 확인한다.
- 1위, 2
7위, 810위, 11위 이상 배치가 Figma 기준과 맞는지 확인한다. - 1~10위가
MainHomeRankingCard하나를 재사용하고, 11위 이상만MainHomeRankingRow를 사용하는지 확인한다. - 카드/row 컴포넌트의 루트 레이아웃이 화면 폭에 따라 자연스럽게 대응하는지 확인한다.
- Phase 4/5 컴포넌트 Preview에서 상승, 하락, 변동 없음, 신규 진입, 순위 변화 숨김 상태가 한 화면에서 모두 보이는지 확인한다.
showRankChange == false일 때 순위 변화 UI가 숨겨지는지 확인한다.isNew == true일 때ic_rank_new가 표시되는지 확인한다.rankChange > 0,< 0,== 0각각 increase/decrease/stay 아이콘 규칙을 확인한다.- 빈 배열 또는 API 실패 시 empty state 문구가 표시되는지 확인한다.
- 랭킹 item tap 시 로그인 guard 이후 creator 상세로 이동하는지 확인한다.
- 검증 기준:
- 수동 확인: PRD Success Criteria 전 항목을 체크한다.
- 대상 파일:
검증 기록
-
2026-06-30 문서 생성:
- 목적:
prd.md를 기반으로 메인 홈 랭킹 탭 구현 전 plan-task 문서 생성 - 확인한 기준 문서:
docs/20260630_메인_홈_랭킹_탭/prd.mddocs/agent-guides/documentation-policy.mddocs/agent-guides/build-test-verification.mddocs/20260602_메인_홈_추천_UI_API_연동/plan-task.md
- 확인한 코드:
SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingView.swift가 placeholder 상태임SodaLive/Sources/V2/Main/Home/MainHomeView.swift가 랭킹 탭에MainHomeRankingView()를 조합 중임SodaLive/Sources/V2/Main/Home/Recommendation/**의 API/Repository/ViewModel 패턴SodaLive/Sources/V2/Main/MainView.swift의 기존 creator 상세 guard callbackSodaLive/Resources/Assets.xcassets/v2/ic_rank_*아이콘 asset 존재
- 목적:
-
2026-06-30 문서 보완:
- 목적: 순위별 UI 검증 Task를 명확히 하고, 카드/row 루트 레이아웃의 고정 숫자 width/height 금지 제약을 반영
- 변경 내용:
- PRD에 1~10위 공용 카드와 11위 이상 row 분리 기준 추가
- PRD에 카드/row 컴포넌트 루트 레이아웃의 반응형 제약 기준 추가
- plan-task Phase 5를
MainHomeRankingCard,MainHomeRankingRowTask로 정리 - Phase 4/5 Preview에서 상승, 하락, 변동 없음, 신규 진입, 순위 변화 숨김 상태를 모두 확인하도록 검증 기준 추가
-
2026-06-30 문서 보완:
- 목적: Figma상 1~10위 카드는 크기만 다르고 모양이 동일하므로 카드 컴포넌트를 하나로 통합
- 변경 내용:
- 순위 구간별로 나뉘어 있던 카드 컴포넌트 계획을 제거
MainHomeRankingCard단일 카드 컴포넌트와 부모 grid 기반 1열/2열/3열 배치로 계획 수정
-
2026-06-30 Phase 1~3 진행:
- 목적: 랭킹 탭 API 연동을 위한 기준점 확인, 응답 모델/API/Repository/ViewModel/I18n 추가
- 확인한 기준:
MainHomeRankingView.swift는 placeholder 상태 유지MainHomeView.swift는 랭킹 탭에onTapCreator를 아직 전달하지 않는 Phase 1 기준 상태 유지ic_rank_caret_decrease,ic_rank_caret_increase,ic_rank_caret_stay,ic_rank_newasset 존재 확인
- 변경 내용:
MainHomeCreatorRankingResponse,MainHomeCreatorRankingItem추가MainHomeRankingApi,MainHomeRankingRepository추가MainHomeRankingViewModel추가I18n.HomeRanking.emptyStateMessage추가
- 검증 메모:
- 현재 저장소 가이드상 테스트 번들 타깃이 확인되지 않아 신규 단위 테스트는 추가하지 않고 정적 검색 및 빌드 검증으로 확인
-
2026-06-30 Phase 4 진행:
- 목적: 랭킹 카드/row에서 재사용할 순위 변화 badge 컴포넌트 추가
- 변경 내용:
MainHomeRankChangeBadge추가showRankChange,isNew,rankChange조건에 따라 신규/상승/하락/변동 없음/미표시 상태 분기- Phase 4 Preview에 상승, 하락, 변동 없음, 신규 진입, 순위 변화 숨김,
rankChange == nil && isNew == false상태 추가
- 검증 메모:
- 문서의 Phase 4 정적 검색 기준으로 아이콘명, 조건별 Preview,
abs()사용을 확인 - Figma badge 배경 기준 확인 후
Color.gray900(#202020) 배경, horizontal padding 4, corner radius 4를MainHomeRankChangeBadge에 반영
- 문서의 Phase 4 정적 검색 기준으로 아이콘명, 조건별 Preview,
-
2026-06-30 Phase 5 진행:
- 목적: 랭킹 순위별 UI 조립에 사용할 카드/row/empty state 컴포넌트 추가
- 확인한 기준:
- Figma
24:5659,24:5670기준 카드/row 구조 확인 - 기존 V2 추천 탭의
DownsampledKFImage,LinearGradient,SodaSpacing,Color.gray*,.buttonStyle(.plain)사용 관례 확인
- Figma
- 변경 내용:
MainHomeRankingCard추가: 1~10위 공용 카드, 서버item.rank, 하단 dim gradient,MainHomeRankChangeBadge, creator tap callback 사용MainHomeRankingRow추가: 11위 이상 row, 서버item.rank, 프로필 이미지, nickname,MainHomeRankChangeBadge, creator tap callback 사용MainHomeRankingEmptyStateView추가:I18n.HomeRanking.emptyStateMessage를 검정 배경 위에 표시- Phase 5 Preview에 상승, 하락, 변동 없음, 신규 진입, 순위 변화 숨김 상태 추가
- 검증 메모:
- 문서의 Phase 5 정적 검색 기준으로 카드/row/empty state 필수 심볼과 Preview 상태를 확인
- 카드/row 루트 container에 고정 숫자
frame(width:),frame(height:)를 두지 않고, 카드 전체 비율은aspectRatio기반으로 구성
-
2026-06-30 Phase 6 진행:
- 목적: 랭킹 탭 화면을 ViewModel 상태 기반으로 조립하고, 랭킹 item tap을 기존 creator 상세 진입 callback에 연결
- 변경 내용:
MainHomeRankingViewplaceholder 제거MainHomeRankingViewModel을@StateObject로 연결하고 최초onAppear에서hasLoaded == false일 때만fetchRankings()호출- loading, empty, content 상태 분기 추가
- content 상태에서 1위 단독, 2
7위 2열, 810위 3열, 11위 이상 row로 조립 - 1~10위는
MainHomeRankingCard, 11위 이상은MainHomeRankingRow를 사용하고showRankChange,onTapCreator전달 MainHomeView의 랭킹 탭에서MainHomeRankingView(onTapCreator: onTapCreator)를 호출하도록 변경
- 검증 메모:
- Phase 6 정적 검색 기준으로 ViewModel, fetch, 공용 카드/row, empty state, flexible grid, creator tap callback 연결을 확인
MainHomeRankingView에서MainPlaceholderTabView,CapsuleTabBar가 사용되지 않음을 확인
-
2026-06-30 Phase 7 진행:
- 목적: 랭킹 탭 신규 파일의 Xcode 프로젝트 포함 여부, 정적 검색 기준, 빌드 가능 여부 확인
- 확인한 기준:
SodaLive.xcodeproj/project.pbxproj에서MainHomeCreatorRankingResponse,MainHomeRankingApi,MainHomeRankingRepository,MainHomeRankingViewModel,MainHomeRankChangeBadge,MainHomeRankingCard,MainHomeRankingRow,MainHomeRankingEmptyStateView의PBXFileReference와PBXBuildFile등록 확인- 정적 검색으로 endpoint, 순위 변화 아이콘명, empty state 문구, 공용 카드/row 컴포넌트 사용 확인
- 정적 검색 결과 랭킹 UI 범위에서
CapsuleTabBar, rank 계산용enumerated()사용 없음 확인 MainHomeRankingCard,MainHomeRankingRow,MainHomeRankingView코드 리뷰로 루트 레이아웃이aspectRatio, flexible grid, parent width 기반임을 확인
- 검증 메모:
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build실행 결과** BUILD SUCCEEDED **xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug -destination "id=C012DC2D-6F85-4C9F-AF22-A10621385D53" build실행 결과** BUILD SUCCEEDED **- 빌드 로그 검색 결과 duplicate build file warning은 없었고, AppIntents metadata 및 Alamofire/BootpayUI dependency scan warning만 확인
- booted simulator에
SodaLive-dev.app설치 후xcrun simctl launch booted kr.co.vividnext.sodalive.debug2실행 결과 PID21238로 launch 확인 - Task 7.4의 Figma 육안 대조, 랭킹 API 1회 호출, item tap 후 상세 진입은 CLI 검증만으로 확정할 수 없어 별도 수동 QA 대상으로 남김