docs(home): 메인 홈 추천 문서를 정리한다

This commit is contained in:
Yu Sung
2026-06-02 14:21:27 +09:00
parent 90003f6ce1
commit 606db35de8
5 changed files with 715 additions and 1 deletions

View File

@@ -98,6 +98,8 @@ Strong success criteria let you loop independently. Weak criteria ("make it work
### 수정 우선순위
- 기능 변경은 `SodaLive/Sources/**`에서 해결한다.
- 기존 로직 수정이 아닌 신규 `View`, `ViewModel`, `Repository` 및 그와 연결된 하위 코드는 `SodaLive/Sources/V2/**` 아래에 작성한다.
- 여러 페이지에서 재사용 가능한 공용 컴포넌트는 `SodaLive/Sources/V2/Component/**` 아래에 형태별 폴더(`Card`, `Banner`, `Text`, `Button`, `Creator` 등)로 배치한다.
- 특정 페이지 내부에서만 사용하는 컴포넌트는 해당 페이지 폴더 하위 `Components`에 배치한다.
- 프로젝트 설정 변경은 필요한 경우에만 수행한다.
- `Pods/**`, `generated/**`는 직접 수정하지 않는다.
- `build/**`는 빌드 산출물로 간주하며 수정 대상이 아니다.
@@ -149,6 +151,8 @@ Strong success criteria let you loop independently. Weak criteria ("make it work
## 문서 작성 규칙
- 구현 전 PRD 작성, 사용자 인터뷰, 계획/TASK 문서 작성, 체크리스트 갱신, 검증 기록 누적, 문서 분리 기준은 `docs/agent-guides/documentation-policy.md`를 따른다.
- 연속된 하나의 작업은 새 문서를 만들지 말고 기존 PRD와 `plan-task.md`에 이어서 기록한다.
- 계획/TASK 문서는 phase heading(`### Phase N: ...`)과 단계별 체크박스(`- [ ] **Task N.N: ...**`)를 사용하고, 각 task에는 대상 파일 경로와 검증 기준을 함께 적는다.
## 문서 유지보수 규칙
- 상세 문서 유지보수 규칙은 `docs/agent-guides/documentation-policy.md`를 참조한다.

View File

@@ -0,0 +1,391 @@
# 메인 홈 추천 UI와 API 연동 구현 계획
## 기준 문서
- PRD: `docs/20260602_메인_홈_추천_UI_API_연동/prd.md`
- Figma 추천 화면: `24:5514`
- Figma 모두 팔로우 완료 버튼: `24:9092`
- Figma 커뮤니티 카드:
- Text Only: `446:9688`
- Text + Img, 유료 + 구매하지 않음: `446:9690`
- Text + Img, 유료/무료 + 구매함: `446:9691`
- 코드 스타일: `docs/agent-guides/code-style.md`
- 빌드/검증: `docs/agent-guides/build-test-verification.md`
## 작업 원칙
- PRD의 성공 기준과 제외 범위를 벗어나지 않는다.
- `추천 필모그래피`, `또 다른 모습` 섹션은 만들지 않는다.
- 기존 `SodaLive/Sources/Home/HomeApi.swift`에는 추천 API를 추가하지 않는다.
- 신규 페이지 루트, ViewModel, Repository, API, Models, MainHome 전용 컴포넌트는 `SodaLive/Sources/V2/Main/Home/**` 아래에 작성한다.
- 여러 페이지에서 재사용 가능한 UI widget은 `SodaLive/Sources/V2/Component/**` 아래에 형태별 폴더로 작성한다.
- 외부 라이브러리는 추가하지 않는다.
- Figma localhost asset URL은 앱 코드에 사용하지 않는다.
- 서버/기획 확인이 필요한 이동 규칙과 최대 표시 개수는 임의 확장하지 않고, 현재 화면 렌더링과 API 연동에 필요한 최소 동작만 구현한다.
- 수정 후 검증 기록은 이 문서 하단에 누적한다.
## 구현 대상 파일
### 생성
- `SodaLive/Sources/V2/Main/Home/MainHomeView.swift`
- `MainView` 홈 탭에서 표시할 실제 홈 추천 페이지 루트.
- `SodaLive/Sources/V2/Main/Home/MainHomeViewModel.swift`
- 추천 API 로딩/에러/섹션 데이터/모두 팔로우 완료 상태 관리.
- `SodaLive/Sources/V2/Main/Home/Repository/MainHomeApi.swift`
- `/api/v2/home/recommendations`, `/api/v2/home/recommendations/creators/follow` 전용 Moya `TargetType`.
- `SodaLive/Sources/V2/Main/Home/Repository/MainHomeRepository.swift`
- `MainHomeApi` 호출을 `AnyPublisher<Response, MoyaError>`로 제공.
- `SodaLive/Sources/V2/Main/Home/Models/MainHomeRecommendationResponse.swift`
- `HomeRecommendationResponse`와 하위 응답 모델.
- `SodaLive/Sources/V2/Main/Home/Models/FollowRecommendedCreatorsRequest.swift`
- 모두 팔로우 요청 모델.
- `SodaLive/Sources/V2/Main/Home/Models/RecommendedActivityType.swift`
- 서버 활동 타입 enum 변환과 I18n 표시.
- `SodaLive/Sources/V2/Main/Home/Components/MainHomeLiveSection.swift`
- `SodaLive/Sources/V2/Main/Home/Components/MainHomeLiveItem.swift`
- `SodaLive/Sources/V2/Main/Home/Components/MainHomeActiveCreatorSection.swift`
- `SodaLive/Sources/V2/Main/Home/Components/MainHomeActiveCreatorItem.swift`
- `SodaLive/Sources/V2/Main/Home/Components/MainHomeRecentDebutCreatorSection.swift`
- `SodaLive/Sources/V2/Main/Home/Components/MainHomeFirstAudioContentSection.swift`
- `SodaLive/Sources/V2/Main/Home/Components/MainHomeAiCharacterSection.swift`
- `SodaLive/Sources/V2/Main/Home/Components/MainHomeGenreCreatorSection.swift`
- `SodaLive/Sources/V2/Main/Home/Components/MainHomeCheerCreatorSection.swift`
- `SodaLive/Sources/V2/Main/Home/Components/MainHomeCreatorGroupSection.swift`
- `SodaLive/Sources/V2/Main/Home/Components/MainHomeCreatorGrid.swift`
- `SodaLive/Sources/V2/Main/Home/Components/MainHomePopularCommunitySection.swift`
- `SodaLive/Sources/V2/Main/Home/Components/MainHomeBusinessInfoSection.swift`
- `SodaLive/Sources/V2/Component/Banner/BannerCarousel.swift`
- `SodaLive/Sources/V2/Component/Card/AiCharacterCard.swift`
- `SodaLive/Sources/V2/Component/Card/CommunityPostCard.swift`
- `SodaLive/Sources/V2/Component/Creator/CreatorProfileGrid.swift`
- `SodaLive/Sources/V2/Component/Creator/CreatorProfileItem.swift`
- `SodaLive/Sources/V2/Component/Button/FollowAllButton.swift`
- `SodaLive/Sources/V2/Component/Text/ExpandableTextView.swift`
### 수정
- `SodaLive/Sources/V2/Main/MainView.swift`
- `.home` 탭에서 `MainPlaceholderTabView` 대신 `MainHomeView` 표시.
- `SodaLive/Sources/I18n/I18n.swift`
- 홈 추천 섹션 제목, 활동 타입, 모두 팔로우 문구, 더보기/접기 문구 추가.
- `SodaLive.xcodeproj/project.pbxproj`
- 신규 Swift 파일이 Xcode 빌드 대상에 포함되지 않는 경우에만 수정.
- `docs/20260602_메인_홈_추천_UI_API_연동/plan-task.md`
- 체크리스트와 검증 기록 유지.
## TASK 체크리스트
### Phase 1: 구현 기준과 데이터 계층 준비
- [ ] **Task 1.1: 구현 전 구조 확인**
- 대상 파일:
- 확인: `SodaLive/Sources/V2/Main/MainView.swift`
- 확인: `SodaLive/Sources/V2/Component/SectionTitle.swift`
- 확인: `SodaLive/Sources/V2/Component/AudioContentCard.swift`
- 확인: `SodaLive/Sources/V2/Component/HomeTitleBar.swift`
- 확인: `SodaLive/Sources/V2/Main/MainTabBarView.swift`
- 확인: `SodaLive/Sources/Home/HomeApi.swift`
- 확인: `SodaLive/Sources/I18n/I18n.swift`
- 작업 내용:
- `.home` 탭이 현재 `MainPlaceholderTabView`를 표시하는지 확인한다.
- 기존 V2 공용 컴포넌트 재사용 범위를 확인한다.
- 기존 `HomeApi.swift`를 수정하지 않는 것을 기준으로 신규 `MainHomeApi` 위치를 확정한다.
- `I18n.swift`의 기존 enum 배치 방식과 `pick(ko:en:ja:)` 사용 방식을 확인한다.
- 검증 기준:
- 실행 명령: `rg "case \\.home|MainPlaceholderTabView|enum HomeApi|enum I18n" SodaLive/Sources/V2/Main SodaLive/Sources/Home SodaLive/Sources/I18n/I18n.swift`
- 기대 결과: `.home` placeholder 위치, 기존 `HomeApi`, `I18n` 구조를 확인할 수 있다.
- 수동 확인: 신규 추천 API를 기존 `HomeApi.swift`에 추가하지 않아야 한다.
- [ ] **Task 1.2: 추천 API 모델 작성**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/Main/Home/Models/MainHomeRecommendationResponse.swift`
- 생성: `SodaLive/Sources/V2/Main/Home/Models/FollowRecommendedCreatorsRequest.swift`
- 생성: `SodaLive/Sources/V2/Main/Home/Models/RecommendedActivityType.swift`
- 작업 내용:
- `MainHomeRecommendationResponse.swift``HomeRecommendationResponse`와 하위 응답 모델을 `Decodable`로 작성한다.
- 서버 nullable 필드는 Swift optional로 선언한다.
- 서버 Long 값은 우선 기존 앱 라우팅 관례와 맞추기 쉬운 `Int`로 작성하고, 빌드 또는 호출부에서 `Int64`가 필요하면 보정한다.
- `FollowRecommendedCreatorsRequest``creatorIds: [Int]?`를 갖는 `Encodable`로 작성한다.
- `RecommendedActivityType``LIVE`, `LIVE_REPLAY`, `AUDIO`, `COMMUNITY`, unknown 처리를 제공한다.
- 검증 기준:
- 실행 명령: `rg "struct HomeRecommendationResponse|struct FollowRecommendedCreatorsRequest|enum RecommendedActivityType" SodaLive/Sources/V2/Main/Home/Models`
- 기대 결과: 세 모델/enum 정의가 검색된다.
- 수동 확인: PRD의 응답 필드명이 Swift 모델에 누락 없이 반영되어야 한다.
- [ ] **Task 1.3: I18n 문구 추가**
- 대상 파일:
- 수정: `SodaLive/Sources/I18n/I18n.swift`
- 작업 내용:
- `I18n.HomeRecommendation` enum을 추가한다.
- 활동 타입 문구를 추가한다.
- `activityLive`: ko `라이브`, en `Live`, ja `ライブ`
- `activityAudio`: ko `오디오`, en `Audio`, ja `オーディオ`
- `activityCommunity`: ko `커뮤니티`, en `Community`, ja `コミュニティ`
- 섹션 제목 문구를 추가한다.
- 현재 라이브
- 방금 활동한 크리에이터
- 최근 데뷔한 크리에이터
- 처음 만나는 오디오
- AI 캐릭터
- 장르의 크리에이터
- 최근 응원이 많은 크리에이터
- 인기 커뮤니티
- 버튼/텍스트 문구를 추가한다.
- `followAll`: `모두 팔로우하기`
- `followAllCompleted`: `모두 팔로우 완료`
- `more`: `더보기`
- `collapse`: `접기`
- 검증 기준:
- 실행 명령: `rg "enum HomeRecommendation|activityLive|followAllCompleted|collapse" SodaLive/Sources/I18n/I18n.swift`
- 기대 결과: 홈 추천 I18n enum과 핵심 문구가 검색된다.
- 수동 확인: 신규 사용자 노출 문자열이 하드코딩 View 텍스트로 흩어지지 않아야 한다.
- [ ] **Task 1.4: 신규 API와 Repository 작성**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/Main/Home/Repository/MainHomeApi.swift`
- 생성: `SodaLive/Sources/V2/Main/Home/Repository/MainHomeRepository.swift`
- 확인: `SodaLive/Sources/Home/HomeApi.swift`
- 확인: `SodaLive/Sources/Home/HomeTabRepository.swift`
- 작업 내용:
- `MainHomeApi``getRecommendations`, `followRecommendedCreators(request:)` 케이스를 작성한다.
- `getRecommendations``GET /api/v2/home/recommendations`로 작성한다.
- `followRecommendedCreators(request:)``POST /api/v2/home/recommendations/creators/follow`로 작성한다.
- 인증 헤더는 기존 패턴대로 `Authorization: Bearer \(UserDefaults.string(forKey: UserDefaultsKey.token))`를 사용한다.
- `MainHomeRepository`에서 위 두 API를 `requestPublisher`로 감싼다.
- 기존 `HomeApi.swift`, `HomeTabRepository.swift`는 수정하지 않는다.
- 검증 기준:
- 실행 명령: `rg "/api/v2/home/recommendations|followRecommendedCreators|getRecommendations" SodaLive/Sources/V2/Main/Home/Repository`
- 기대 결과: 신규 API 경로와 repository 메서드가 신규 폴더에서만 검색된다.
- 실행 명령: `rg "/api/v2/home/recommendations" SodaLive/Sources/Home`
- 기대 결과: 검색 결과가 없어야 한다.
### Phase 2: ViewModel 상태와 공용 UI 컴포넌트 작성
- [ ] **Task 2.1: MainHomeViewModel 작성**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/Main/Home/MainHomeViewModel.swift`
- 확인: `SodaLive/Sources/V2/Main/Home/Repository/MainHomeRepository.swift`
- 확인: `SodaLive/Sources/V2/Main/Home/Models/MainHomeRecommendationResponse.swift`
- 작업 내용:
- `@Published` 상태를 작성한다.
- `isLoading`
- `isShowPopup`
- `errorMessage`
- `recommendations`
- 장르/응원 그룹별 모두 팔로우 완료 상태
- 모두 팔로우 호출 중 상태
- `fetchRecommendations()`에서 `ApiResponse<HomeRecommendationResponse>`를 디코딩한다.
- `success == true`이고 `data`가 있으면 화면 상태를 갱신한다.
- 실패, 디코딩 실패, 네트워크 실패는 기존 앱 관례대로 `errorMessage``isShowPopup`으로 처리한다.
- `followAll(creatorIds:completionKey:)`에서 `ApiResponseWithoutData`를 디코딩한다.
- 모두 팔로우 성공 시 해당 섹션 완료 상태만 true로 바꾼다.
-`creatorIds`인 경우 API를 호출하지 않고 버튼을 숨기거나 비활성화하는 방식으로 처리한다.
- 검증 기준:
- 실행 명령: `rg "final class MainHomeViewModel|fetchRecommendations|followAll|ApiResponse<HomeRecommendationResponse>|ApiResponseWithoutData" SodaLive/Sources/V2/Main/Home/MainHomeViewModel.swift`
- 기대 결과: ViewModel 상태와 API 처리 메서드가 검색된다.
- 수동 확인: 실패 처리에서 빈 `catch`를 사용하지 않아야 한다.
- [ ] **Task 2.2: 공용 `ExpandableTextView` 작성**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/Component/Text/ExpandableTextView.swift`
- 작업 내용:
- `text`, `lineLimit`, `moreTitle`, `collapseTitle`, `font`, `foregroundColor`를 입력으로 받는다.
- 기본 상태는 `lineLimit(3)`과 말줄임표를 적용한다.
- `더보기` 터치 시 전체 표시로 전환한다.
- `접기` 터치 시 다시 3줄 제한 상태로 전환한다.
- 외부 라이브러리를 사용하지 않는다.
- 텍스트가 3줄 이하이면 더보기/접기 버튼을 숨긴다.
- 검증 기준:
- 실행 명령: `rg "struct ExpandableTextView|lineLimit|moreTitle|collapseTitle|isExpanded" SodaLive/Sources/V2/Component/Text/ExpandableTextView.swift`
- 기대 결과: 확장/접기 상태와 3줄 제한 구현 키워드가 검색된다.
- 수동 확인: 외부 라이브러리 import 없이 `SwiftUI` 기반으로 구현되어야 한다.
- [ ] **Task 2.3: 공용 Creator 컴포넌트 작성**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/Component/Creator/CreatorProfileItem.swift`
- 생성: `SodaLive/Sources/V2/Component/Creator/CreatorProfileGrid.swift`
- 작업 내용:
- `CreatorProfileItem`은 프로필 이미지, 이름, 선택적 보조 텍스트, 아이템 탭 액션을 입력으로 받는다.
- `CreatorProfileItem`은 API 응답 모델에 직접 의존하지 않는다.
- `CreatorProfileGrid`는 표시 모델 배열, 열/간격, 탭 액션을 입력으로 받는다.
- 최근 데뷔 크리에이터와 장르/응원 그룹의 개별 아이템에 재사용할 수 있게 작성한다.
- 검증 기준:
- 실행 명령: `rg "struct CreatorProfileItem|struct CreatorProfileGrid|creatorNickname|HomeCreatorItem|HomeRecommendationResponse" SodaLive/Sources/V2/Component/Creator`
- 기대 결과: `CreatorProfileItem`, `CreatorProfileGrid`는 검색되고 API 응답 타입 의존은 없어야 한다.
- 수동 확인: 공용 컴포넌트 타입명에 `MainHome` 또는 `HomeRecommendation` 접두사를 붙이지 않아야 한다.
- [ ] **Task 2.4: 공용 Button/Banner/Card 컴포넌트 작성**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/Component/Button/FollowAllButton.swift`
- 생성: `SodaLive/Sources/V2/Component/Banner/BannerCarousel.swift`
- 생성: `SodaLive/Sources/V2/Component/Card/AiCharacterCard.swift`
- 생성: `SodaLive/Sources/V2/Component/Card/CommunityPostCard.swift`
- 작업 내용:
- `FollowAllButton`은 기본 상태 `모두 팔로우하기`, 완료 상태 `모두 팔로우 완료`, 완료 아이콘 `ic_new_following`, 호출 중 중복 터치 방지를 지원한다.
- `BannerCarousel`은 배너 이미지 URL 목록과 탭 액션을 입력으로 받는다.
- `AiCharacterCard`는 캐릭터명, 설명, 프로필 이미지, 채팅 수, 원작 제목을 입력으로 받는다.
- `CommunityPostCard`는 Text Only, Text + Img 잠금, Text + Img 노출 variant를 `imageUrl`, `price`, `existOrdered` 기준으로 분기한다.
- `CommunityPostCard``구매완료` 캡슐을 구현하지 않는다.
- 검증 기준:
- 실행 명령: `rg "struct FollowAllButton|ic_new_following|struct BannerCarousel|struct AiCharacterCard|struct CommunityPostCard|구매완료" SodaLive/Sources/V2/Component`
- 기대 결과: 네 공용 컴포넌트와 `ic_new_following`은 검색되고, `구매완료` 캡슐 구현 텍스트는 production UI로 남지 않아야 한다.
- 수동 확인: 공용 컴포넌트가 특정 API 응답 모델을 직접 받지 않아야 한다.
### Phase 3: MainHome 전용 섹션과 페이지 조립
- [ ] **Task 3.1: MainHome 전용 섹션 컴포넌트 작성**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/Main/Home/Components/MainHomeLiveSection.swift`
- 생성: `SodaLive/Sources/V2/Main/Home/Components/MainHomeLiveItem.swift`
- 생성: `SodaLive/Sources/V2/Main/Home/Components/MainHomeActiveCreatorSection.swift`
- 생성: `SodaLive/Sources/V2/Main/Home/Components/MainHomeActiveCreatorItem.swift`
- 생성: `SodaLive/Sources/V2/Main/Home/Components/MainHomeRecentDebutCreatorSection.swift`
- 생성: `SodaLive/Sources/V2/Main/Home/Components/MainHomeFirstAudioContentSection.swift`
- 생성: `SodaLive/Sources/V2/Main/Home/Components/MainHomeAiCharacterSection.swift`
- 생성: `SodaLive/Sources/V2/Main/Home/Components/MainHomeGenreCreatorSection.swift`
- 생성: `SodaLive/Sources/V2/Main/Home/Components/MainHomeCheerCreatorSection.swift`
- 생성: `SodaLive/Sources/V2/Main/Home/Components/MainHomeCreatorGroupSection.swift`
- 생성: `SodaLive/Sources/V2/Main/Home/Components/MainHomeCreatorGrid.swift`
- 생성: `SodaLive/Sources/V2/Main/Home/Components/MainHomePopularCommunitySection.swift`
- 생성: `SodaLive/Sources/V2/Main/Home/Components/MainHomeBusinessInfoSection.swift`
- 작업 내용:
- 라이브/최근 활동/최근 데뷔/첫 오디오/AI 캐릭터/장르/응원/커뮤니티/사업자 정보 섹션을 작성한다.
- `MainHomeRecentDebutCreatorSection`은 공용 `CreatorProfileGrid`를 사용한다.
- `MainHomeFirstAudioContentSection`은 기존 `AudioContentCard`를 사용한다.
- `MainHomeAiCharacterSection`은 공용 `AiCharacterCard`를 사용한다.
- `MainHomeGenreCreatorSection`, `MainHomeCheerCreatorSection``MainHomeCreatorGroupSection`을 조합한다.
- `MainHomePopularCommunitySection`은 공용 `CommunityPostCard`를 사용한다.
- `MainHomeBusinessInfoSection`은 공용 `ExpandableTextView`를 사용한다.
- 각 섹션은 데이터 배열이 비어 있으면 렌더링하지 않는다.
- 검증 기준:
- 실행 명령: `rg "struct MainHome.*Section|struct MainHome.*Item|CreatorProfileGrid|AudioContentCard|AiCharacterCard|CommunityPostCard|ExpandableTextView" SodaLive/Sources/V2/Main/Home/Components`
- 기대 결과: MainHome 전용 섹션과 공용 컴포넌트 조합이 검색된다.
- 수동 확인: `추천 필모그래피`, `또 다른 모습` 섹션 파일이나 View를 만들지 않아야 한다.
- [ ] **Task 3.2: MainHomeView 작성**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/Main/Home/MainHomeView.swift`
- 확인: `SodaLive/Sources/V2/Main/Home/MainHomeViewModel.swift`
- 확인: `SodaLive/Sources/V2/Component/HomeTitleBar.swift`
- 작업 내용:
- `HomeTitleBar`, 상단 탭 UI, 세로 `ScrollView`, 각 섹션 컴포넌트를 조합한다.
- `.onAppear`에서 최초 `fetchRecommendations()`를 호출한다.
- 로딩/에러 표시 방식은 기존 앱 관례를 따른다.
- 배너/카드 탭 액션은 PRD의 미결정 항목이므로 구현 범위에서는 비워 두거나 기존 라우팅이 명확한 경우에만 연결한다.
- 사업자 정보 텍스트는 Figma의 사업자 정보 문구를 사용한다.
- 검증 기준:
- 실행 명령: `rg "struct MainHomeView|HomeTitleBar|ScrollView|fetchRecommendations|MainHomeBusinessInfoSection" SodaLive/Sources/V2/Main/Home/MainHomeView.swift`
- 기대 결과: 페이지 루트와 주요 조합 요소가 검색된다.
- 수동 확인: `MainTabBarView``MainView`의 safe area inset에서 유지하고, `MainHomeView` 내부에서 중복 생성하지 않아야 한다.
- [ ] **Task 3.3: MainView 홈 탭 연결**
- 대상 파일:
- 수정: `SodaLive/Sources/V2/Main/MainView.swift`
- 작업 내용:
- `.home` 분기에서 `MainPlaceholderTabView(title: MainTab.home.title)``MainHomeView()`로 교체한다.
- 다른 탭의 placeholder 동작은 변경하지 않는다.
- 기존 `legacyHomeViewModel`의 이벤트 팝업/회원 정보 초기화 흐름은 변경하지 않는다.
- 검증 기준:
- 실행 명령: `rg "case \\.home|MainHomeView|MainPlaceholderTabView" SodaLive/Sources/V2/Main/MainView.swift`
- 기대 결과: `.home` 분기는 `MainHomeView()`를 표시하고, 다른 탭에는 기존 placeholder가 남아 있다.
- 수동 확인: 홈 탭 외 content/chat/my 분기를 불필요하게 수정하지 않아야 한다.
### Phase 4: 프로젝트 포함과 정적 점검
- [ ] **Task 4.1: Xcode 프로젝트 포함 여부 확인**
- 대상 파일:
- 확인: `SodaLive.xcodeproj/project.pbxproj`
- 확인: `SodaLive/Sources/V2/Main/Home/**`
- 확인: `SodaLive/Sources/V2/Component/**`
- 작업 내용:
- 신규 Swift 파일이 Xcode 빌드 대상에 포함되는지 확인한다.
- 파일 시스템 동기화 방식으로 자동 포함되면 `SodaLive.xcodeproj/project.pbxproj`를 수정하지 않는다.
- 프로젝트 파일 수정이 필요한 경우에만 `SodaLive.xcodeproj/project.pbxproj`를 갱신한다.
- 검증 기준:
- 실행 명령: `plutil -lint SodaLive.xcodeproj/project.pbxproj`
- 기대 결과: 프로젝트 파일을 수정했거나 확인이 필요할 때 `OK`가 출력된다.
- 수동 확인: 프로젝트 파일 수정이 불필요하면 변경하지 않아야 한다.
- [ ] **Task 4.2: 정적 점검**
- 대상 파일:
- 확인: `SodaLive/Sources/V2/Main/Home/**`
- 확인: `SodaLive/Sources/V2/Component/**`
- 확인: `SodaLive/Sources/Home/**`
- 작업 내용:
- 제외 섹션, 기존 홈 API 오염, Figma URL, 공백 오류를 확인한다.
- 검증 기준:
- 실행 명령: `rg "추천 필모그래피|또 다른 모습" SodaLive/Sources/V2/Main/Home SodaLive/Sources/V2/Component`
- 기대 결과: 검색 결과가 없어야 한다.
- 실행 명령: `rg "api/v2/home/recommendations" SodaLive/Sources/Home`
- 기대 결과: 검색 결과가 없어야 한다.
- 실행 명령: `rg "localhost:3845|figma.com" SodaLive/Sources/V2/Main/Home SodaLive/Sources/V2/Component`
- 기대 결과: 검색 결과가 없어야 한다.
- 실행 명령: `git diff --check`
- 기대 결과: 출력 없이 성공한다.
### Phase 5: 빌드와 기능 검증
- [ ] **Task 5.1: 빌드 검증**
- 대상 파일:
- 확인: `SodaLive.xcworkspace`
- 확인: `SodaLive.xcodeproj`
- 확인: `SodaLive/Sources/V2/Main/Home/**`
- 확인: `SodaLive/Sources/V2/Component/**`
- 작업 내용:
- 기본 빌드를 실행한다.
- 필요 시 운영 스킴 빌드를 실행한다.
- 테스트 액션 확인이 필요하면 테스트 명령을 실행하고, 스킴 미구성 시 결과를 검증 기록에 남긴다.
- 검증 기준:
- 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`
- 기대 결과: `BUILD SUCCEEDED`
- 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build`
- 기대 결과: `BUILD SUCCEEDED`
- 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test`
- 기대 결과: 테스트가 실행되거나, 스킴 미구성 메시지를 검증 기록에 남긴다.
- [ ] **Task 5.2: 기능 검증**
- 대상 파일:
- 확인: `SodaLive/Sources/V2/Main/Home/MainHomeView.swift`
- 확인: `SodaLive/Sources/V2/Main/Home/MainHomeViewModel.swift`
- 확인: `SodaLive/Sources/V2/Main/Home/Components/**`
- 확인: `SodaLive/Sources/V2/Component/**`
- 작업 내용:
- 추천 API 성공/실패, 빈 섹션, 모두 팔로우 성공/실패, 사업자 정보 확장/접기, 활동 타입 I18n, 커뮤니티 카드 variant를 확인한다.
- 검증 기준:
- 수동 확인: 추천 API 성공 시 각 섹션이 응답 데이터로 표시된다.
- 수동 확인: 빈 배열 섹션은 제목과 컨테이너까지 숨겨진다.
- 수동 확인: 모두 팔로우 버튼 터치 후 API 성공 시 해당 버튼만 `모두 팔로우 완료`로 바뀐다.
- 수동 확인: 모두 팔로우 실패 시 버튼 상태가 변경되지 않고 오류가 표시된다.
- 수동 확인: 사업자 정보 기본 3줄 말줄임표, 더보기, 전체 표시, 접기가 동작한다.
- 수동 확인: 활동 타입 `LIVE`, `LIVE_REPLAY`, `AUDIO`, `COMMUNITY`가 I18n 문구로 표시된다.
- 수동 확인: 커뮤니티 카드 3개 variant가 조건에 맞게 표시되고 `구매완료` 캡슐이 표시되지 않는다.
### Phase 6: 문서와 체크리스트 갱신
- [ ] **Task 6.1: 문서/체크리스트 갱신**
- 대상 파일:
- 수정: `docs/20260602_메인_홈_추천_UI_API_연동/prd.md`
- 수정: `docs/20260602_메인_홈_추천_UI_API_연동/plan-task.md`
- 작업 내용:
- 구현 중 확정된 배너 이동 규칙, 표시 개수, 타입 보정 사항이 있으면 PRD를 먼저 갱신한다.
- 완료한 task 체크박스를 실제 상태에 맞게 `- [x]`로 갱신한다.
- 실행한 검증 명령과 결과를 이 문서 하단 검증 기록에 누적한다.
- 검증 기준:
- 실행 명령: `rg "### Phase [0-9]+:|- \\[[ x]\\] \\*\\*Task [0-9]+\\.[0-9]+:" docs/20260602_메인_홈_추천_UI_API_연동/plan-task.md`
- 기대 결과: phase heading과 `Task N.N` 형식 체크박스가 검색된다.
- 수동 확인: 새 문서를 만들지 않고 기존 PRD/계획 문서에 이어서 기록해야 한다.
## 검증 기록
### 2026-06-02 계획 문서 생성
- 목적: `docs/20260602_메인_홈_추천_UI_API_연동/prd.md`를 기준으로 구현 전 계획/TASK 문서 작성
- 수행 내용:
- MainHome 페이지 루트, 신규 API 계층, 공용 컴포넌트, MainHome 전용 컴포넌트, 연결/검증 작업을 TASK 단위로 분리
- 공용 컴포넌트는 `SodaLive/Sources/V2/Component/**` 형태별 폴더 기준으로 배치
- MainHome 전용 컴포넌트는 `SodaLive/Sources/V2/Main/Home/Components` 기준으로 배치
- 아직 수행하지 않은 작업:
- Swift 코드 구현
- Xcode 프로젝트 포함 여부 확인
- 빌드/기능 검증

View File

@@ -0,0 +1,300 @@
# PRD: 메인 홈 추천 UI와 API 연동
## 1. Overview
메인 홈의 `추천` 탭을 Figma 디자인 기준으로 구성하고, 신규 홈 추천 API(`/api/v2/home/recommendations`)와 모두 팔로우 API(`/api/v2/home/recommendations/creators/follow`)를 연동한다.
## 2. Problem
- 기존 홈 API에 추천 탭 전용 응답을 추가하지 않고 신규 V2 API로 분리해야 한다.
- Figma 기준 추천 탭에는 라이브, 배너, 최근 활동 크리에이터, 데뷔/첫 오디오/AI 캐릭터/장르/응원/커뮤니티/사업자 정보 등 여러 반복 UI가 포함되어 있다.
- 반복 UI가 많아 단일 화면에 직접 구현하면 유지보수 비용이 커진다.
- 사업자 정보 텍스트는 외부 라이브러리 없이 3줄 말줄임표와 더보기/접기 토글을 제공해야 한다.
## 3. Goals
- `추천` 탭 진입 시 `/api/v2/home/recommendations`를 호출하고 응답 데이터로 화면을 구성한다.
- 기존 `HomeApi`에 케이스를 추가하지 않고 신규 API/Repository/ViewModel 계층을 만든다.
- Figma에서 확인한 기존 widget과 저장소 내 V2 공용 컴포넌트를 가능한 범위에서 재사용한다.
- 반복되는 UI는 Custom Widget으로 분리해 재사용 가능하게 한다.
- 모두 팔로우 API 호출이 정상 완료되고 `success == true`이면 버튼 문구를 `모두 팔로우 완료`로 변경한다.
- `RecommendedActivityType` 서버 enum 값은 앱 enum으로 변환하고 다국어 문구로 표시한다.
- 빈 섹션 데이터는 제목과 빈 컨테이너를 노출하지 않아 화면 밀도를 유지한다.
## 4. Non-Goals
- `추천 필모그래피` 섹션은 만들지 않는다.
- `또 다른 모습` 섹션은 만들지 않는다.
- 기존 홈 API(`/api/home`) 또는 기존 `HomeApi`에 추천 API를 추가하지 않는다.
- 외부 라이브러리를 추가하지 않는다.
- 서버 응답 스펙에 없는 팔로우 개별 상태, 페이지네이션, 정렬/필터 기능은 이번 범위에 포함하지 않는다.
- Figma 로컬 asset URL을 앱 코드에 직접 사용하지 않는다.
## 5. Target Users
- 앱 홈에서 추천 크리에이터, 라이브, 콘텐츠, 커뮤니티를 빠르게 탐색하는 일반 사용자
- 추천된 크리에이터 그룹을 한 번에 팔로우하려는 사용자
## 6. User Stories
- 사용자는 홈 `추천` 탭에서 현재 라이브 중인 크리에이터와 추천 콘텐츠를 한 화면에서 보고 싶다.
- 사용자는 관심 있는 장르/응원 크리에이터 그룹을 한 번에 팔로우하고 싶다.
- 사용자는 사업자 정보를 기본적으로 짧게 보고, 필요할 때 전체 내용을 펼쳐 보고 싶다.
- 사용자는 최근 활동 타입을 한국어/영어/일본어 등 현재 앱 언어에 맞게 보고 싶다.
## 7. Core Features
### 7.1 추천 홈 데이터 조회
#### API
- Method: `GET`
- Path: `/api/v2/home/recommendations`
- 인증: 기존 인증 헤더 패턴과 동일하게 `Authorization: Bearer {token}` 사용
- 응답 래퍼: 기존 관례대로 `ApiResponse<HomeRecommendationResponse>` 디코딩
#### Response
```kotlin
data class HomeRecommendationResponse(
val lives: List<HomeLiveItem>,
val banners: List<HomeBannerItem>,
val recentlyActiveCreators: List<HomeActiveCreatorItem>,
val recentDebutCreators: List<HomeCreatorItem>,
val firstAudioContents: List<HomeFirstAudioContentItem>,
val aiCharacters: List<HomeAiCharacterItem>,
val genreCreators: List<HomeGenreCreatorGroupItem>,
val cheerCreators: List<HomeCreatorItem>,
val popularCommunityPosts: List<HomePopularCommunityPostItem>
)
```
Swift 모델은 위 필드명을 그대로 `Decodable`로 구성한다. 서버 nullable 필드는 Swift optional로 선언한다.
### 7.2 모두 팔로우 하기
#### API
- Method: `POST`
- Path: `/api/v2/home/recommendations/creators/follow`
- Request body:
```kotlin
data class FollowRecommendedCreatorsRequest(
val creatorIds: List<Long>?
)
```
- 응답 래퍼: 기존 관례대로 `ApiResponseWithoutData` 디코딩
#### 동작
- 장르 크리에이터 그룹과 최근 응원이 많은 크리에이터 섹션의 `모두 팔로우하기` 버튼에서 호출한다.
- 요청 `creatorIds`는 해당 섹션에 표시된 크리에이터의 `creatorId` 목록을 사용한다.
- API 호출 중 중복 터치를 막는다.
- 호출 완료 후 `success == true`이면 해당 섹션 버튼 상태를 완료로 전환한다.
- 완료 상태 버튼 문구는 `모두 팔로우 완료`로 표시한다.
- 완료 상태 버튼 아이콘은 `ic_new_following`을 사용한다.
- 실패 시 기존 앱 에러 표시 관례에 맞춰 공통 에러 또는 서버 message를 노출하고 버튼 상태는 변경하지 않는다.
### 7.3 활동 타입 다국어 표시
서버 enum:
```kotlin
enum class RecommendedActivityType(val code: String) {
LIVE("LIVE"),
AUDIO("AUDIO"),
COMMUNITY("COMMUNITY"),
LIVE_REPLAY("LIVE_REPLAY")
}
```
앱 변환:
- `LIVE`, `LIVE_REPLAY` -> `I18n.HomeRecommendation.activityLive`
- `AUDIO` -> `I18n.HomeRecommendation.activityAudio`
- `COMMUNITY` -> `I18n.HomeRecommendation.activityCommunity`
- 알 수 없는 값은 빈 문자열 또는 서버 코드 직접 표시 대신 해당 아이템의 보조 문구를 숨긴다.
다국어 기본 문구:
- ko: `라이브`, `오디오`, `커뮤니티`
- en: `Live`, `Audio`, `Community`
- ja: `ライブ`, `オーディオ`, `コミュニティ`
### 7.4 사업자 정보 더보기/접기
요구사항:
- 외부 라이브러리를 사용하지 않는다.
- 기본 상태는 최대 3줄 표시, 말줄임표 적용, `더보기` 액션 제공
- `더보기` 터치 시 전체 표시로 전환하고 `접기` 액션 제공
- `접기` 터치 시 다시 3줄 말줄임표 상태로 돌아간다.
구현 방향:
- SwiftUI `Text``lineLimit(isExpanded ? nil : 3)`를 사용한다.
- 더보기/접기 버튼은 텍스트 하단 우측 또는 Figma 사업자 정보 섹션 내 자연스러운 위치에 배치한다.
- 실제 텍스트가 3줄 이하이면 더보기 버튼은 숨긴다. 줄 수 판정은 Geometry 기반 측정 또는 제한/무제한 높이 비교 방식으로 구현한다.
### 7.5 커뮤니티 포스트 카드
참조 Figma:
- Text Only: `node-id=446-9688`
- Text + Img, 유료 + 구매하지 않음: `node-id=446-9690`
- Text + Img, 유료/무료 + 구매함: `node-id=446-9691`
요구사항:
- `CommunityPostCard`는 다른 페이지에서도 재사용 가능하도록 `SodaLive/Sources/V2/Component/Card` 아래에 둔다.
- 텍스트 전용, 이미지 포함, 유료 이미지 잠금 상태를 지원한다.
- `imageUrl == nil`이면 Text Only variant로 표시한다.
- `imageUrl != nil && price > 0 && existOrdered == false`이면 이미지 영역에 blur/lock/pay capsule을 표시한다.
- `imageUrl != nil && (price <= 0 || existOrdered == true)`이면 이미지를 일반 노출한다.
- 유료/무료 + 구매함 Figma 카드의 우측 상단 `구매완료` 캡슐은 구현하지 않는다.
- 본문, 작성자, 생성 시간, 좋아요 수, 댓글 수는 표시한다.
## 8. UX / UI Expectations
### 8.1 Figma 기준 화면 구성
참조 Figma:
- 추천 화면: `node-id=24-5514`
- 모두 팔로우 완료 버튼: `node-id=24-9092`
Figma 확인 결과 추천 화면은 검정 배경, 상단 홈 타이틀/탭, 하단 메인 탭바 사이에 세로 스크롤 콘텐츠로 구성된다. 주요 섹션은 카드/가로 스크롤/그리드 조합이며, 반복 프로필과 버튼은 재사용 위젯화가 필요하다.
### 8.2 UI 배치 도식
아래 도식은 구현 대상만 포함한다. `추천 필모그래피`, `또 다른 모습`은 제외한다.
```text
MainHomeView
└─ HomeTitleBar 재사용
└─ 홈 상단 탭(추천/랭킹/팔로잉)
└─ ScrollView
├─ 현재 라이브 섹션
│ └─ MainHomeLiveSection 신규
│ └─ MainHomeLiveItem 신규
├─ 배너 섹션
│ └─ BannerCarousel 신규
├─ 최근 활동 크리에이터 섹션
│ └─ MainHomeActiveCreatorSection 신규
│ └─ MainHomeActiveCreatorItem 신규
├─ 최근 데뷔한 크리에이터 섹션
│ ├─ SectionTitle 재사용
│ └─ CreatorProfileGrid 재사용
│ └─ CreatorProfileItem 재사용
├─ 처음 만나는 오디오 섹션
│ ├─ SectionTitle 재사용
│ └─ AudioContentCard 재사용/확장
├─ AI 캐릭터 섹션
│ ├─ SectionTitle 재사용
│ └─ AiCharacterCard 신규
├─ 장르의 크리에이터 섹션
│ └─ MainHomeCreatorGroupSection 신규
│ ├─ SectionTitle 재사용(size 보정 필요 시 신규 variant)
│ ├─ MainHomeCreatorGrid 신규
│ ├─ CreatorProfileItem 재사용
│ └─ FollowAllButton 재사용
├─ 최근 응원이 많은 크리에이터 섹션
│ └─ MainHomeCreatorGroupSection 재사용
├─ 인기 커뮤니티 섹션
│ ├─ SectionTitle 재사용
│ └─ CommunityPostCard 재사용
└─ 사업자 정보 섹션
└─ MainHomeBusinessInfoSection 신규
└─ ExpandableTextView 재사용
└─ MainTabBarView 재사용 가능 여부 확인 후 적용
```
### 8.3 재사용 컴포넌트 후보
- `SodaLive/Sources/V2/Component/HomeTitleBar.swift`: 홈 상단 로고/메뉴 타이틀바
- `SodaLive/Sources/V2/Component/SectionTitle.swift`: 섹션 타이틀과 우측 액션 아이콘
- `SodaLive/Sources/V2/Component/AudioContentCard.swift`: `firstAudioContents` 카드 기본 구조
- `SodaLive/Sources/V2/Main/MainTabBarView.swift`: 하단 탭바가 V2 홈 구조와 맞는 경우 재사용
- `SodaLive/Sources/Chat/Character/Banner/AutoSlideCharacterBannerView.swift`: 배너 carousel 패턴 참고. 홈 배너 타입과 이동 규칙이 달라 직접 재사용 여부는 계획 단계에서 재확인한다.
- `SodaLive/Sources/Explorer/Profile/CreatorCommunity/CreatorCommunityItemView.swift`: 커뮤니티 카드의 작성자/본문/리액션 패턴 참고. 이번 홈 추천 응답 타입과 더보기 요구가 달라 직접 재사용보다는 신규 위젯 생성이 우선이다.
### 8.4 신규 파일/그룹 후보
`MainView`의 홈 탭에서 표시되는 페이지 조립 계층은 `SodaLive/Sources/V2/Main/Home` 아래에 둔다.
```text
SodaLive/Sources/V2/Main/Home
├─ MainHomeView.swift
├─ MainHomeViewModel.swift
├─ Repository
│ ├─ MainHomeApi.swift
│ └─ MainHomeRepository.swift
├─ Models
│ ├─ MainHomeRecommendationResponse.swift
│ ├─ FollowRecommendedCreatorsRequest.swift
│ └─ RecommendedActivityType.swift
└─ Components
├─ MainHomeLiveSection.swift
├─ MainHomeLiveItem.swift
├─ MainHomeActiveCreatorSection.swift
├─ MainHomeActiveCreatorItem.swift
├─ MainHomeRecentDebutCreatorSection.swift
├─ MainHomeFirstAudioContentSection.swift
├─ MainHomeAiCharacterSection.swift
├─ MainHomeGenreCreatorSection.swift
├─ MainHomeCheerCreatorSection.swift
├─ MainHomeCreatorGroupSection.swift
├─ MainHomeCreatorGrid.swift
├─ MainHomePopularCommunitySection.swift
└─ MainHomeBusinessInfoSection.swift
```
`MainHome`에서만 사용하는 섹션 조립 컴포넌트는 `SodaLive/Sources/V2/Main/Home/Components` 아래에 둔다. 여러 페이지에서 재사용 가능성이 있는 UI widget은 `SodaLive/Sources/V2/Component` 아래에서 형태별 폴더에 둔다. 공용 widget은 특정 페이지나 API 이름 접두사를 붙이지 않고, 가능한 한 API 모델에 직접 의존하지 않으며 표시용 프로퍼티 또는 작은 display model을 받아 재사용성을 확보한다.
```text
SodaLive/Sources/V2/Component
├─ Banner
│ └─ BannerCarousel.swift
├─ Card
│ ├─ AiCharacterCard.swift
│ └─ CommunityPostCard.swift
├─ Creator
│ ├─ CreatorProfileGrid.swift
│ └─ CreatorProfileItem.swift
├─ Button
│ └─ FollowAllButton.swift
└─ Text
└─ ExpandableTextView.swift
```
단, 구현 중 특정 widget이 홈 탭에서만 의미가 있고 재사용성이 없다고 판단되면 `SodaLive/Sources/V2/Main/Home/Components` 안에 유지한다. 반대로 이미 존재하는 공용 컴포넌트로 충분한 경우 신규 파일을 만들지 않는다.
### 8.5 컴포넌트 위치 결정 기준
- `BannerCarousel`: 다른 페이지에서도 배너 carousel로 재사용 가능하므로 `SodaLive/Sources/V2/Component/Banner`에 둔다.
- 방금 활동한 크리에이터 UI: MainHome에서만 사용하므로 `MainHomeActiveCreatorSection`, `MainHomeActiveCreatorItem``SodaLive/Sources/V2/Main/Home/Components`에 둔다.
- 현재 라이브 UI: 별도 재사용 요구가 없으므로 `MainHomeLiveSection`, `MainHomeLiveItem``SodaLive/Sources/V2/Main/Home/Components`에 둔다.
- 최근 데뷔한 크리에이터 UI: 다른 페이지에서도 재사용 가능하므로 공용 `CreatorProfileGrid`, `CreatorProfileItem`을 사용하고, 섹션 조립만 `MainHomeRecentDebutCreatorSection`에 둔다.
- `AiCharacterCard`: 다른 페이지에서도 캐릭터 카드로 재사용 가능하므로 `SodaLive/Sources/V2/Component/Card`에 둔다.
- 장르/응원이 많은 크리에이터: 그리드 그룹 구조는 MainHome 전용이므로 `MainHomeCreatorGroupSection`, `MainHomeCreatorGrid``SodaLive/Sources/V2/Main/Home/Components`에 둔다. 개별 크리에이터 아이템만 `CreatorProfileItem`으로 재사용한다.
- 커뮤니티 섹션: 섹션 조립은 `MainHomePopularCommunitySection`에 두고, `CommunityPostCard`는 다른 페이지에서도 재사용 가능하므로 `SodaLive/Sources/V2/Component/Card`에 둔다.
- 사업자 정보 섹션: 섹션 wrapper는 `MainHomeBusinessInfoSection`으로 `SodaLive/Sources/V2/Main/Home/Components`에 둔다. 3줄 말줄임표, 더보기, 접기를 담당하는 텍스트 UI는 다른 곳에서도 사용할 수 있는 `ExpandableTextView`로 분리해 `SodaLive/Sources/V2/Component/Text`에 둔다.
## 9. Technical Constraints
- 앱 소스 변경은 기본적으로 `SodaLive/Sources/**`에서 수행한다.
- `MainView` 홈 탭에서 표시되는 페이지 루트, ViewModel, Repository, API, Models와 MainHome 전용 섹션 컴포넌트는 `SodaLive/Sources/V2/Main/Home/**` 아래에 작성한다.
- 여러 페이지에서 재사용 가능한 UI widget은 `SodaLive/Sources/V2/Component/**` 아래에 형태별 폴더로 작성한다.
- 순수 공용성이 더 큰 컴포넌트는 구현 시점에 `SodaLive/Sources/V2/Component/**`의 더 적합한 하위 그룹으로 이동할 수 있다.
- `Pods/**`, `generated/**`, `build/**`는 수정하지 않는다.
- 기존 `HomeApi`에 추천 API를 추가하지 않는다.
- 외부 라이브러리를 추가하지 않는다.
- 이미지 로딩은 기존 앱에서 사용하는 이미지 컴포넌트/패턴을 따른다.
- 인증 헤더는 기존 `UserDefaultsKey.token` 기반 패턴을 따른다.
- `creatorId`, `liveRoomId`, `bannerId`, `postId` 등 서버 Long 값은 Swift에서 `Int` 또는 `Int64` 중 기존 라우팅/모델 관례와 맞는 타입을 사용한다. 계획 단계에서 실제 이동 대상 API 타입과 맞춰 확정한다.
- API 날짜 문자열(`beginDateTime`, `activityAt`, `releaseDate`, `createdAt`)은 기존 날짜 포맷 유틸이 있으면 재사용한다.
## 10. Success Criteria
- 추천 탭에서 `/api/v2/home/recommendations`를 호출하고 `success == true` 응답 데이터를 섹션별로 렌더링한다.
- 응답 배열이 비어 있는 섹션은 화면에 표시하지 않는다.
- 제외 섹션인 `추천 필모그래피`, `또 다른 모습`은 코드와 화면에 포함하지 않는다.
- 모두 팔로우 API 성공 시 해당 버튼이 Figma 완료 디자인에 맞게 `모두 팔로우 완료``ic_new_following` 상태로 변경된다.
- 사업자 정보는 기본 3줄 말줄임표, 더보기, 전체 표시, 접기 전환이 동작한다.
- `LIVE`, `LIVE_REPLAY`, `AUDIO`, `COMMUNITY` 활동 타입이 I18n 문구로 표시된다.
- 반복 UI가 Custom Widget으로 분리되어 같은 프로필/그룹/버튼 구조를 중복 구현하지 않는다.
- 빌드가 성공하고, 가능하면 ViewModel 단위의 응답 디코딩 및 모두 팔로우 성공 상태 테스트가 통과한다.
## 11. Metrics
- 추천 API 성공/실패 여부
- 모두 팔로우 API 호출 성공/실패 여부
- 추천 탭 첫 로딩 완료 시간
- 모두 팔로우 버튼 터치 후 완료 상태 전환 여부
## 12. Open Questions
- 배너 `type`별 이동 규칙(`eventId`, `creatorId`, `seriesId`, `link`)을 어떤 기존 라우팅과 연결할지 확인이 필요하다.
- 각 섹션별 최대 표시 개수와 가로/세로 스크롤 정책이 Figma 기준 그대로인지, 서버 응답 전체를 모두 표시해야 하는지 확인이 필요하다.
- `creatorIds == null` 요청을 허용해야 하는지, 앱에서는 빈 배열/비어 있는 섹션일 때 버튼을 숨기는 것으로 제한할지 확인이 필요하다.
- 모두 팔로우 완료 상태를 앱 세션 동안만 유지할지, 추천 API 재조회 후에도 서버 상태 기반으로 유지할지 확인이 필요하다.
- 각 카드 터치 시 상세 이동 대상이 모두 정의되어 있는지 확인이 필요하다.
## 13. Verification Plan
- PRD 검증: 요구사항, 제외 범위, API URL, 응답 모델, Figma 노드가 문서에 반영되었는지 확인한다.
- 구현 후 빌드 검증: `docs/agent-guides/build-test-verification.md` 기준 명령으로 iOS 빌드 또는 가능한 최소 검증을 실행한다.
- 구현 후 기능 검증: 추천 API 성공/실패, 빈 섹션, 모두 팔로우 성공/실패, 사업자 정보 더보기/접기, 활동 타입 I18n 표시를 확인한다.

View File

@@ -8,6 +8,21 @@
- API는 `enum ...Api: TargetType`, 저장소는 `final class ...Repository` 형태를 우선 사용한다.
- 상태 모델은 `struct`/`enum` 중심으로 두고, 화면 상태는 `ObservableObject`에서 관리한다.
## 컴포넌트 위치 규칙
- 여러 페이지에서 재사용 가능한 컴포넌트는 `SodaLive/Sources/V2/Component/**` 아래에 작성한다.
- 공용 컴포넌트는 기능/페이지 접두사가 아니라 UI 형태나 역할 기준 폴더에 배치한다.
- 카드 형태: `SodaLive/Sources/V2/Component/Card`
- 배너/캐러셀 형태: `SodaLive/Sources/V2/Component/Banner`
- 텍스트 표시/확장/축약 형태: `SodaLive/Sources/V2/Component/Text`
- 버튼 형태: `SodaLive/Sources/V2/Component/Button`
- 크리에이터 프로필/그리드 형태: `SodaLive/Sources/V2/Component/Creator`
- 공용 컴포넌트 타입명은 `HomeRecommendation...`, `MainHome...`처럼 특정 페이지나 API 이름을 접두사로 붙이지 않는다. 예: `CommunityPostCard`, `BannerCarousel`, `ExpandableTextView`.
- 공용 컴포넌트는 특정 API 응답 모델에 직접 의존하지 않는 것을 우선한다. 필요한 값은 표시용 프로퍼티 또는 작은 display model로 받는다.
- 특정 페이지 내부에서만 사용하는 컴포넌트는 해당 페이지 폴더 하위 `Components`에 둔다. 예: `SodaLive/Sources/V2/Main/Home/Components/MainHomeLiveSection.swift`.
- 페이지 전용 컴포넌트는 페이지 문맥을 드러내기 위해 `MainHome...`처럼 페이지 접두사를 사용할 수 있다.
- 처음에는 페이지 전용으로 만들었더라도 다른 페이지에서 재사용 요구가 생기면 `SodaLive/Sources/V2/Component/**`의 적절한 형태별 폴더로 이동한다.
- 공용으로 만들지 페이지 전용으로 둘지 애매하면, 현재 요청 범위 기준으로만 판단한다. 재사용 근거가 없으면 페이지 전용으로 시작한다.
## 임포트 규칙
- 시스템 프레임워크(`Foundation`, `SwiftUI`, `Combine`)를 먼저 배치한다.
- 서드파티(`Moya`, `CombineMoya`, SDK들)는 이후 배치한다.

View File

@@ -23,7 +23,11 @@
- 계획/TASK 문서 경로는 `docs/[날짜]_구현할내용한글/plan-task.md` 형식을 사용한다.
- 날짜는 `YYYYMMDD` 8자리 숫자를 사용한다.
- 계획/TASK 문서는 보강 완료된 PRD를 기준으로 작성하고, 구현 범위가 PRD의 성공 기준과 제외 범위를 벗어나지 않게 한다.
- 구현 항목은 기능/작업 단위 체크박스(`- [ ]`)로 작성하고 완료 즉시 `- [x]`로 갱신한다.
- 연속된 하나의 작업이라면 별도 새 문서를 만들지 말고 기존 PRD와 계획/TASK 문서에 추가 작업으로 이어서 기록한다.
- 계획/TASK 문서는 의미 단위 phase로 나누고 `### Phase 1: ...`, `### Phase 2: ...` 형식의 heading을 사용한다.
- 각 phase 아래에는 단계별 task를 체크박스(`- [ ] **Task N.N: ...**`) 형태로 작성하고 완료 즉시 `- [x]`로 갱신한다.
- 각 task에는 구현 시 생성/수정/확인할 파일 경로를 명시한다.
- 각 phase 또는 task에는 실행 명령, 기대 결과, 수동 확인 항목 등 검증 기준을 함께 작성한다.
- 작업 도중 범위가 변경되면 PRD를 먼저 보강하고 계획/TASK 문서 체크리스트를 업데이트한 뒤 구현한다.
- 결과 보고 시 문서 하단에 검증 기록(무엇/왜/어떻게, 실행 명령, 결과)을 한국어로 남긴다.
- 후속 수정이 발생해도 기존 검증 기록은 삭제/덮어쓰기 없이 누적한다.