Files
sodalive-ios/docs/20260701_크리에이터_채널_홈/plan-task.md

820 lines
80 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 크리에이터 채널 홈 구현 계획
## 기준 문서
- PRD: `docs/20260701_크리에이터_채널_홈/prd.md`
- 코드 스타일: `docs/agent-guides/code-style.md`
- 빌드/검증: `docs/agent-guides/build-test-verification.md`
- Figma 전체 화면: `296:14890`
- Figma Unfollow title bar: `296:14287`, `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=296-14287&m=dev`
- Figma Follow + 알림 설정 title bar: `296:14288`, `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=296-14288&m=dev`
- Figma Follow + 알림 해제 title bar: `296:14289`, `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=296-14289&m=dev`
## 구현 원칙
- API, Response model, Repository, ViewModel을 먼저 구현한 뒤 UI phase를 진행한다.
- 크리에이터 채널 진입점은 `CreatorChannelView`로 두고, 최초 선택 tab은 `.home`으로 둔다.
- `CreatorChannelView`는 title bar, 크리에이터 프로필 header, tab-bar, 선택된 tab 상태, 홈 API 호출 상태를 소유한다.
- `CreatorChannelHomeView`는 tab-bar 아래 홈 탭 콘텐츠만 렌더링하며, 대화하기 action은 최신 코드 기준처럼 `CreatorChannelHeaderSection` 내부에서 렌더링한다.
- 홈 API 응답의 `creator` 정보는 공통 shell의 header/title/follow/notification/대화하기 action 상태가 공유한다.
- 크리에이터 채널 공통 shell 구현은 `SodaLive/Sources/V2/CreatorChannel/**` 아래에 둔다.
- 크리에이터 채널 홈 탭 내부에서만 쓰는 UI는 `SodaLive/Sources/V2/CreatorChannel/Home/Components/**` 아래에 둔다.
- 크리에이터 채널 공통 shell 내부에서만 쓰는 UI는 `SodaLive/Sources/V2/CreatorChannel/Components/**` 아래에 둔다.
- 여러 화면에서 재사용할 근거가 명확한 UI만 `SodaLive/Sources/V2/Component/**`로 올린다.
- 기존 V2 홈의 `TargetType`, Repository, ViewModel, `ApiResponse<T>` 디코딩 패턴을 따른다.
- `Long` 계열 id/count는 Swift `Int`로 통일한다.
- 빈 섹션은 section title과 empty state까지 포함해 표시하지 않는다.
- API 실패 시 전체 화면 error state로 전환하지 않고 title bar, tab-bar, 홈 외 tab placeholder처럼 서버 데이터 없이 가능한 기본 shell을 유지한다.
- `화보` 탭, 화보 섹션, 활동의 화보 row는 구현하지 않는다.
- DM 버튼과 DM 진입은 구현하지 않는다.
- Figma localhost asset URL은 앱 코드에 사용하지 않는다.
- title bar 아이콘은 `ic_new_bar_back`, `ic_new_follow`, `ic_new_following`, `ic_new_more`, `ic_bar_bell`, `ic_bar_bell_colored` asset을 사용한다.
- SNS 아이콘은 `ic_sns_instagram`, `ic_sns_youtube`, `ic_sns_x`, `ic_sns_kakao`, `ic_sns_fancimm` asset을 사용한다.
- 기존 `AppStep.creatorDetail(userId:)` 진입은 새 크리에이터 채널 화면으로 연결한다.
## 주요 대상 파일
### 생성
- `SodaLive/Sources/V2/CreatorChannel/Home/Models/CreatorChannelHomeResponse.swift`
- `SodaLive/Sources/V2/CreatorChannel/Models/CreatorChannelTab.swift`
- `SodaLive/Sources/V2/CreatorChannel/Home/Repository/CreatorChannelHomeApi.swift`
- `SodaLive/Sources/V2/CreatorChannel/Home/Repository/CreatorChannelHomeRepository.swift`
- `SodaLive/Sources/V2/CreatorChannel/CreatorChannelViewModel.swift`
- `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift`
- `SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- `SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelTitleBar.swift`
- `SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelHeaderSection.swift`
- `SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelTabBar.swift`
- `SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelPlaceholderTabView.swift`
- `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelTalkActionSection.swift`
- `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelCurrentLiveSection.swift`
- `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelLatestAudioSection.swift`
- `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelDonationSection.swift`
- `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelNoticeSection.swift`
- `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelScheduleSection.swift`
- `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelAudioSection.swift`
- `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelSeriesSection.swift`
- `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelCommunitySection.swift`
- `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelFanTalkSection.swift`
- `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelIntroduceSection.swift`
- `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelActivitySection.swift`
- `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelSnsSection.swift`
### 수정
- `SodaLive/Sources/ContentView.swift`
- `SodaLive/Sources/I18n/I18n.swift`
- 필요 시 `SodaLive.xcodeproj/project.pbxproj`
### 확인
- `SodaLive/Sources/V2/Main/Home/Ranking/Repository/MainHomeRankingApi.swift`
- `SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingViewModel.swift`
- `SodaLive/Sources/V2/Main/Home/Following/Models/HomeFollowingTabResponse.swift`
- `SodaLive/Sources/V2/Main/MainView.swift`
- `SodaLive/Sources/Chat/ChatTabView.swift`
- `SodaLive/Sources/User/UserApi.swift`
- `SodaLive/Sources/Content/Series/Detail/SeriesDetailViewModel.swift`
- `SodaLive/Sources/V2/Component/SectionTitle.swift`
- `SodaLive/Sources/V2/Component/AudioContentCard.swift`
- `SodaLive/Sources/V2/Component/Card/CommunityPostCard.swift`
- `SodaLive/Sources/Explorer/Profile/Detail/CreatorDetailDialogView.swift`
## TASK 체크리스트
### Phase 1: API와 상태 모델
- [x] **Task 1.1: 크리에이터 채널 홈 Response model 생성**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Home/Models/CreatorChannelHomeResponse.swift`
- 확인: `SodaLive/Sources/V2/Main/Home/Following/Models/HomeFollowingTabResponse.swift`
- 작업 내용:
- PRD의 `CreatorChannelHomeResponse`와 하위 응답 모델을 Swift `Decodable` struct로 작성한다.
- Kotlin `Long` 필드는 Swift `Int`로 선언한다.
- list item은 필요한 경우 `Identifiable`을 채택하고 id는 서버 id 필드를 사용한다.
- `CreatorActivityType`은 기존 enum을 재사용하거나 공용 위치로 이동한다. 중복 enum을 만들지 않는다.
- nullable URL 필드는 PRD nullable 여부에 맞춰 optional로 선언한다.
- 검증 기준:
- 실행 명령: `rg "struct CreatorChannelHomeResponse|struct CreatorChannelCreatorResponse|struct CreatorChannelLiveResponse|struct CreatorChannelAudioContentResponse|struct CreatorChannelDonationResponse|struct CreatorChannelScheduleResponse|struct CreatorChannelSeriesResponse|struct CreatorChannelCommunityPostResponse|struct CreatorChannelFanTalkSummaryResponse|struct CreatorChannelActivityResponse|struct CreatorChannelSnsResponse" SodaLive/Sources/V2/CreatorChannel/Home/Models/CreatorChannelHomeResponse.swift`
- 기대 결과: PRD의 응답 모델이 Swift 파일에 모두 존재하고 id/count 타입이 `Int`이다.
- [x] **Task 1.2: 채널 tab enum 생성**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Home/Models/CreatorChannelHomeTab.swift`
- 작업 내용:
- `CreatorChannelHomeTab` enum을 `CaseIterable, Hashable`로 만든다.
- case는 `home`, `live`, `audio`, `series`, `community`, `fanTalk`, `donation` 순서로 둔다.
- title은 `홈`, `라이브`, `오디오`, `시리즈`, `커뮤니티`, `팬Talk`, `후원`을 반환한다.
- `photo` 또는 `화보` case는 만들지 않는다.
- 검증 기준:
- 실행 명령: `rg "enum CreatorChannelHomeTab|case home|case live|case audio|case series|case community|case fanTalk|case donation|화보|photo" SodaLive/Sources/V2/CreatorChannel/Home/Models/CreatorChannelHomeTab.swift`
- 기대 결과: 7개 tab case가 있고 `화보`/`photo` case가 없다.
- 후속 구조 변경:
- tab enum은 홈 탭 전용이 아니라 채널 공통 shell 상태이므로 Task 2.4에서 `CreatorChannelTab`으로 이름과 위치를 정리한다.
- [x] **Task 1.3: API endpoint 생성**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Home/Repository/CreatorChannelHomeApi.swift`
- 확인: `SodaLive/Sources/V2/Main/Home/Ranking/Repository/MainHomeRankingApi.swift`
- 작업 내용:
- `CreatorChannelHomeApi``TargetType`으로 만든다.
- case는 `getHome(creatorId: Int)` 하나로 시작한다.
- path는 `/api/v2/creator-channels/{creatorId}/home` 형식으로 구성한다.
- method는 `.get`, task는 `.requestPlain`로 둔다.
- headers는 기존 V2 API와 동일하게 `Authorization: Bearer {token}` 패턴을 따른다.
- 검증 기준:
- 실행 명령: `rg "enum CreatorChannelHomeApi|getHome\\(creatorId: Int\\)|/api/v2/creator-channels/|/home|TargetType|requestPlain|Authorization" SodaLive/Sources/V2/CreatorChannel/Home/Repository/CreatorChannelHomeApi.swift`
- 기대 결과: 신규 endpoint와 인증 헤더가 확인된다.
- [x] **Task 1.4: Repository 생성**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Home/Repository/CreatorChannelHomeRepository.swift`
- 확인: `SodaLive/Sources/V2/Main/Home/Ranking/Repository/MainHomeRankingRepository.swift`
- 작업 내용:
- `CreatorChannelHomeRepository`를 만들고 `MoyaProvider<CreatorChannelHomeApi>`를 사용한다.
- `getHome(creatorId:) -> AnyPublisher<Response, MoyaError>` 메서드를 제공한다.
- 검증 기준:
- 실행 명령: `rg "final class CreatorChannelHomeRepository|MoyaProvider<CreatorChannelHomeApi>|getHome\\(creatorId: Int\\)|AnyPublisher<Response, MoyaError>" SodaLive/Sources/V2/CreatorChannel/Home/Repository/CreatorChannelHomeRepository.swift`
- 기대 결과: 기존 V2 Repository와 같은 형태가 확인된다.
- [x] **Task 1.5: ViewModel 생성 및 API 실패 placeholder 상태 정의**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeViewModel.swift`
- 확인: `SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingViewModel.swift`
- 확인: `SodaLive/Sources/User/UserApi.swift`
- 확인: `SodaLive/Sources/Content/Series/Detail/SeriesDetailViewModel.swift`
- 작업 내용:
- `CreatorChannelHomeViewModel``ObservableObject`로 만든다.
- `@Published var isLoading`, `@Published var response`, `@Published var hasLoaded`, `@Published var selectedTab`, `@Published var isApiFailedPlaceholderVisible`를 둔다.
- `fetchHome(creatorId:)`에서 `ApiResponse<CreatorChannelHomeResponse>`를 디코딩한다.
- 성공 시 `response`, `selectedTab = .home`, `isApiFailedPlaceholderVisible = false`를 갱신한다.
- 실패/디코딩 실패 시 `response = nil`, `isApiFailedPlaceholderVisible = true`, `hasLoaded = true`로 두어 기본 shell은 유지할 수 있게 한다.
- 초기 구현에서는 `follow(creatorId:notify:)`, `unfollow(creatorId:)`에 기존 `UserRepository.creatorFollow`, `UserRepository.creatorUnFollow` 패턴을 재사용한다.
- mutation 성공 시 `response?.creator.isFollow`, `response?.creator.isNotify`가 갱신될 수 있도록 모델 갱신 방식을 구현한다. struct 불변성 때문에 필요한 경우 creator 복사 helper를 둔다.
- 검증 기준:
- 실행 명령: `rg "final class CreatorChannelHomeViewModel|fetchHome\\(creatorId: Int\\)|ApiResponse<CreatorChannelHomeResponse>|isApiFailedPlaceholderVisible|creatorFollow|creatorUnFollow|selectedTab|hasLoaded" SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeViewModel.swift`
- 기대 결과: 홈 API 디코딩, API 실패 placeholder 상태, 기존 follow/unfollow API 호출이 확인된다.
- 후속 구조 변경:
- title bar mutation은 기존 `UserProfile` 현재 동작과 동일하게 Task 2.4에서 `CreatorChannelViewModel``creatorFollow(follow:notify:)` 기반으로 정리한다.
### Phase 2: 라우팅과 기본 화면 shell
- [x] **Task 2.1: 기존 creatorDetail 라우팅을 신규 홈으로 연결**
- 대상 파일:
- 수정: `SodaLive/Sources/ContentView.swift`
- 생성: `SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 확인: `SodaLive/Sources/App/AppStep.swift`
- 작업 내용:
- `AppStep.creatorDetail(userId:)` case는 유지한다.
- `ContentView`에서 `.creatorDetail(let userId)` destination을 `CreatorChannelHomeView(creatorId: userId)`로 변경한다.
- 기존 `UserProfileView`를 삭제하거나 수정하지 않는다.
- 초기 구현에서는 `CreatorChannelHomeView``creatorId: Int`를 받아 화면을 구성한다.
- 검증 기준:
- 실행 명령: `rg "case \\.creatorDetail|CreatorChannelHomeView\\(creatorId: userId\\)|UserProfileView\\(userId:" SodaLive/Sources/ContentView.swift SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 기대 결과: `.creatorDetail`이 신규 `CreatorChannelHomeView`로 연결되고 기존 `UserProfileView` 파일은 유지된다.
- 후속 구조 변경:
- 크리에이터 채널 공통 shell 분리 결정에 따라 Task 2.4에서 `.creatorDetail` destination을 `CreatorChannelView(creatorId:)`로 변경한다.
- [x] **Task 2.2: 기본 shell과 로딩/API 실패 placeholder 구성**
- 대상 파일:
- 수정: `SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 생성: `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelPlaceholderTabView.swift`
- 작업 내용:
- `@StateObject private var viewModel = CreatorChannelHomeViewModel()`를 사용한다.
- 최초 `onAppear`에서 `hasLoaded == false`일 때 `fetchHome(creatorId:)`를 호출한다.
- root background는 black으로 둔다.
- `response == nil && isApiFailedPlaceholderVisible == true`이면 title bar, tab-bar, 선택된 tab placeholder만 표시한다.
- 로딩 중에는 기존 `LoadingView` 또는 V2 관례에 맞는 loading view를 사용한다.
- 홈 외 tab 선택 시 `CreatorChannelPlaceholderTabView(title: selectedTab.title)`을 표시한다.
- 검증 기준:
- 실행 명령: `rg "@StateObject private var viewModel|fetchHome\\(creatorId: creatorId\\)|isApiFailedPlaceholderVisible|CreatorChannelPlaceholderTabView|LoadingView|Color.black" SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelPlaceholderTabView.swift`
- 기대 결과: API 호출, 실패 placeholder, 홈 외 tab placeholder shell이 확인된다.
- 후속 구조 변경:
- 공통 shell과 선택 tab/API 상태는 Task 2.4에서 `CreatorChannelView`/`CreatorChannelViewModel`로 이동하고, `CreatorChannelHomeView`는 홈 탭 콘텐츠만 담당한다.
- [x] **Task 2.3: I18n 문구 추가**
- 대상 파일:
- 수정: `SodaLive/Sources/I18n/I18n.swift`
- 작업 내용:
- `I18n.CreatorChannelHome` enum을 추가한다.
- 섹션명 `currentLive`, `latestAudio`, `donation`, `notice`, `schedule`, `audio`, `series`, `community`, `fanTalk`, `introduce`, `activity`, `sns`를 추가한다.
- 액션 문구 `follow`, `talk`, `viewAll`를 추가한다.
- 활동 row 문구 `debut`, `liveCount`, `liveDurationHours`, `liveContributorCount`, `audioContentCount`, `seriesCount`를 추가한다.
- ko/en/ja 문구를 모두 제공한다.
- 검증 기준:
- 실행 명령: `rg "enum CreatorChannelHome|currentLive|latestAudio|fanTalk|introduce|activity|viewAll|liveDurationHours|seriesCount" SodaLive/Sources/I18n/I18n.swift`
- 기대 결과: 신규 사용자 노출 문구가 `I18n`에 존재한다.
- [x] **Task 2.4: 공통 CreatorChannel shell로 구조 정리**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift`
- 생성: `SodaLive/Sources/V2/CreatorChannel/CreatorChannelViewModel.swift`
- 생성/이동: `SodaLive/Sources/V2/CreatorChannel/Models/CreatorChannelTab.swift`
- 수정: `SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 수정/정리: `SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeViewModel.swift`
- 수정: `SodaLive/Sources/ContentView.swift`
- 확인: `SodaLive/Sources/App/AppStep.swift`
- 작업 내용:
- `ContentView``.creatorDetail(let userId)` destination을 `CreatorChannelView(creatorId: userId)`로 연결한다.
- `CreatorChannelView``@StateObject private var viewModel = CreatorChannelViewModel()`을 사용한다.
- `CreatorChannelViewModel``isLoading`, `response`, `hasLoaded`, `selectedTab`, `isApiFailedPlaceholderVisible`, follow/notify mutation을 소유한다.
- 기존 `CreatorChannelHomeViewModel`의 홈 API 호출/공통 상태 책임은 `CreatorChannelViewModel`로 이동하고, 남는 책임이 없으면 파일을 제거한다.
- title bar의 팔로우/팔로우 취소/알림 설정/알림 설정 취소는 기존 `UserProfile` 현재 UI 흐름과 동일하게 `UserRepository.creatorFollow(creatorId:follow:notify:)`를 사용한다.
- 팔로우는 `creatorFollow(follow: true, notify: true)`, 팔로우 취소는 `creatorFollow(follow: false, notify: false)`로 호출한다.
- `ic_bar_bell` 터치 시 `creatorFollow(follow: true, notify: true)`, `ic_bar_bell_colored` 터치 시 `creatorFollow(follow: true, notify: false)`로 호출한다.
- `CreatorChannelHomeTab``CreatorChannelTab`으로 이름을 바꾸고 공통 shell 경로로 이동한다.
- `CreatorChannelHomeView``CreatorChannelHomeResponse`와 tab 전환 closure를 주입받아 tab-bar 아래 홈 탭 콘텐츠만 렌더링하고, 대화하기 action은 `CreatorChannelHeaderSection`에 유지한다.
- API 실패 placeholder 상태에서는 `CreatorChannelView`가 서버 데이터 없이 가능한 title bar, tab-bar, 선택된 tab placeholder를 유지한다.
- 검증 기준:
- 실행 명령: `rg "struct CreatorChannelView|final class CreatorChannelViewModel|enum CreatorChannelTab|CreatorChannelView\\(creatorId: userId\\)|CreatorChannelHomeView\\(|selectedTab|fetchHome\\(creatorId:|creatorFollow\\(.*follow: true, notify: true|creatorFollow\\(.*follow: false, notify: false|creatorFollow\\(.*follow: true, notify: false" SodaLive/Sources/V2/CreatorChannel SodaLive/Sources/ContentView.swift`
- 기대 결과: 라우팅 진입점과 선택 tab/API 상태가 `CreatorChannelView`/`CreatorChannelViewModel`로 이동했고, title bar follow/notify mutation이 기존 `UserProfile``creatorFollow(follow:notify:)` 기준으로 연결되어 있으며, `CreatorChannelHomeView`는 홈 탭 콘텐츠 렌더링만 담당한다.
### Phase 3: Header / Title Bar / Tab Bar / Sticky
- [x] **Task 3.1: title bar 컴포넌트 생성**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelTitleBar.swift`
- 확인: `SodaLive/Sources/Common/FollowButtonImageAsset.swift`
- 작업 내용:
- back 버튼, 팔로우/팔로잉 상태 버튼, 알림 상태 아이콘, 더보기 버튼을 표시한다.
- back 버튼은 `ic_new_bar_back` asset을 사용한다.
- 더보기 버튼은 `ic_new_more` asset을 사용한다.
- `isFollow == false`이면 `팔로우` capsule을 표시한다.
- `isFollow == false` 상태의 follow capsule에는 `ic_new_follow` asset을 사용한다.
- `isFollow == true` 상태의 following capsule에는 `ic_new_following` asset을 사용한다.
- `isFollow == true && isNotify == true`이면 팔로잉 상태 capsule과 알림 설정 아이콘 `ic_bar_bell_colored`를 표시한다.
- `isFollow == true && isNotify == false`이면 팔로잉 상태 capsule과 알림 해제 아이콘 `ic_bar_bell`을 표시한다.
- `ic_new_follow` 터치 시 부모의 팔로우 action으로 `creatorFollow(follow: true, notify: true)`가 호출되도록 연결한다.
- `ic_new_following` 터치 시 부모의 팔로우 취소 action으로 `creatorFollow(follow: false, notify: false)`가 호출되도록 연결한다.
- `ic_bar_bell` 터치 시 부모의 알림 설정 action으로 `creatorFollow(follow: true, notify: true)`가 호출되도록 연결한다.
- `ic_bar_bell_colored` 터치 시 부모의 알림 설정 취소 action으로 `creatorFollow(follow: true, notify: false)`가 호출되도록 연결한다.
- 더보기 버튼은 항상 표시한다.
- 배경 opacity는 외부에서 전달받는 `backgroundProgress`로 제어한다.
- 구현 기준은 Figma `296:14287`, `296:14288`, `296:14289` 3개 title bar 상태를 따른다.
- title bar 아이콘은 `ic_new_bar_back`, `ic_new_follow`, `ic_new_following`, `ic_new_more`, `ic_bar_bell`, `ic_bar_bell_colored` 외 다른 asset으로 대체하지 않는다.
- 검증 기준:
- 실행 명령: `rg "struct CreatorChannelTitleBar|isFollow|isNotify|backgroundProgress|onTapBack|onTapFollow|onTapUnfollow|onTapNotify|onTapUnnotify|onTapMore|ic_new_bar_back|ic_new_follow|ic_new_following|ic_new_more|ic_bar_bell|ic_bar_bell_colored|팔로우" SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelTitleBar.swift`
- 기대 결과: Figma 3개 title bar 상태를 표현하는 조건 분기, 지정된 6개 icon asset 사용, 팔로우/팔로우 취소/알림 설정/알림 설정 취소 tap closure가 확인된다.
- [x] **Task 3.2: 크리에이터 이미지/프로필 헤더 생성**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelHeaderSection.swift`
- 수정: `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift`
- 작업 내용:
- HeaderView의 표시 내용은 `CreatorChannelHomeResponse.creator`를 사용해 채운다.
- 별도 원형/소형 프로필 이미지는 표시하지 않는다.
- `creator.profileImageUrl`은 큰 배경 이미지에만 사용한다.
- 큰 배경 이미지 영역은 OS status bar까지 확장되도록 top safe area를 무시한다.
- `creator.nickname`, `creator.followerCount`를 표시한다.
- 이미지 로드 실패 시 기존 `DownsampledKFImage` 또는 `KFImage` placeholder 관례를 따른다.
- 검증 기준:
- 실행 명령: `rg "struct CreatorChannelHeaderSection|CreatorChannelCreatorResponse|profileImageUrl|ignoresSafeArea\\(.*top|nickname|followerCount|DownsampledKFImage|KFImage" SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelHeaderSection.swift SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift`
- 기대 결과: `CreatorChannelHomeResponse.creator` 기반 데이터 표시, 큰 배경 이미지의 top safe area 확장, 별도 프로필 이미지 미표시가 확인된다.
- [x] **Task 3.3: horizontal tab-bar 생성**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelTabBar.swift`
- 수정: `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift`
- 확인: `SodaLive/Sources/V2/Component/TextTabBar.swift`
- 작업 내용:
- `CreatorChannelTab.allCases`를 horizontal scroll로 표시한다.
- 선택된 tab은 white text와 soda color bottom indicator로 표시한다.
- 비선택 tab은 gray text로 표시한다.
- `화보` tab은 표시하지 않는다.
- sticky 상태일 때도 같은 컴포넌트를 사용한다.
- 검증 기준:
- 실행 명령: `rg "struct CreatorChannelTabBar|ScrollView\\(\\.horizontal|CreatorChannelTab.allCases|selectedTab|soda|indicator|화보" SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelTabBar.swift SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift`
- 기대 결과: 7개 tab만 표시되고 선택 상태 UI가 확인된다.
- [x] **Task 3.4: scroll progress와 sticky 계산 연결**
- 대상 파일:
- 수정: `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift`
- 수정: `SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelTitleBar.swift`
- 수정: `SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelTabBar.swift`
- 작업 내용:
- `GeometryReader``PreferenceKey` 기반 scroll offset 또는 geometry preference로 tab-bar와 title bar의 거리를 계산한다.
- 거리에 따라 title bar background progress를 0...1로 clamp한다.
- title bar는 header view 위에 `ZStack` 또는 `overlay` 형태로 띄운다.
- title bar 뒤로 큰 배경 이미지가 보이도록 기본 `backgroundProgress`는 0, 기본 배경은 투명으로 둔다.
- tab-bar가 title bar에 닿으면 tab-bar를 sticky 상태로 고정한다.
- sticky 기준은 디바이스 최상단 `y=0`이 아니라 title bar 하단이며, sticky 이후 tab-bar는 title bar 하단까지만 스크롤된 뒤 멈춘다.
- sticky 이후 OS status bar 영역이 title bar와 동일한 black 배경으로 보이게 상단 overlay를 둔다.
- 검증 기준:
- 실행 명령: `rg "PreferenceKey|GeometryReader|ZStack|overlay|backgroundProgress|Color\\.clear|opacity|isTabBarSticky|clamp|safeAreaInsets|CreatorChannelTitleBar|CreatorChannelTabBar" SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelTitleBar.swift SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelTabBar.swift`
- 기대 결과: geometry preference 기반 계산, 기본 투명 title bar overlay, scroll 기반 title bar opacity, sticky tab-bar 상태 계산이 확인되고, tab-bar sticky 기준이 디바이스 최상단이 아니라 title bar 하단임이 확인된다.
- [x] **Task 3.5: title bar 닉네임 표시 연결**
- 대상 파일:
- 수정: `SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelTitleBar.swift`
- 수정: `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift`
- 작업 내용:
- `CreatorChannelTitleBar`에 크리에이터 닉네임을 주입받는 값을 추가한다.
- title bar의 back 버튼 우측 Text에는 크리에이터 닉네임을 표시한다.
- 닉네임은 한 줄로 표시하고, 영역을 넘으면 말줄임 처리한다.
- 닉네임이 길어도 우측 팔로우/알림/더보기 영역은 항상 먼저 보이도록 layout priority 또는 고정 영역 우선순위를 적용한다.
- 닉네임은 title bar background opacity가 1일 때만 표시한다.
- `CreatorChannelView``viewModel.response?.creator.nickname``CreatorChannelTitleBar`에 전달한다.
- 검증 기준:
- 실행 명령: `rg "nickname|lineLimit\\(1\\)|truncationMode|layoutPriority|backgroundProgress|opacity|CreatorChannelTitleBar\\(" SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelTitleBar.swift SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift`
- 기대 결과: title bar에 크리에이터 닉네임이 전달되고, 한 줄 말줄임, 우측 action 영역 우선 노출, `backgroundProgress`가 1일 때만 닉네임 표시 조건이 확인된다.
### Phase 4: 홈 외 placeholder
- [x] **Task 4.1: 홈 외 tab placeholder 연결**
- 대상 파일:
- 수정: `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift`
- 생성: `SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelPlaceholderTabView.swift`
- 작업 내용:
- `selectedTab != .home`이면 실제 목록 대신 `CreatorChannelPlaceholderTabView(title: selectedTab.title)`을 표시한다.
- 홈 섹션의 전체보기 action 중 별도 기존 route가 없는 경우 대응 tab을 선택하도록 연결한다.
- 팬Talk 전체보기는 `selectedTab = .fanTalk`로 이동한다.
- 검증 기준:
- 실행 명령: `rg "selectedTab != \\.home|CreatorChannelPlaceholderTabView\\(title: selectedTab.title\\)|selectedTab = \\.fanTalk|selectedTab = \\.audio|selectedTab = \\.series|selectedTab = \\.community|selectedTab = \\.donation" SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift`
- 기대 결과: 홈 외 tab과 전체보기 placeholder 전환이 확인된다.
### Phase 5: 현재 라이브 섹션
- [x] **Task 5.1: 현재 라이브 섹션 구현**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelCurrentLiveSection.swift`
- 수정: `SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 작업 내용:
- `currentLive == nil`이면 섹션을 표시하지 않는다.
- `liveId`, `title`, `coverImageUrl`, `beginDateTimeUtc`, `price`, `isAdult`를 표시한다.
- `coverImageUrl`이 없으면 기존 이미지 placeholder 패턴을 사용한다.
- `isAdult == true`이면 기존 성인 콘텐츠 표시 asset/pattern을 사용한다.
- card tap은 기존 live detail/live room 진입 흐름이 확인되면 연결하고, 없으면 action closure만 둔다.
- 검증 기준:
- 실행 명령: `rg "struct CreatorChannelCurrentLiveSection|CreatorChannelLiveResponse|currentLive|liveId|coverImageUrl|isAdult|onTapLive" SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelCurrentLiveSection.swift SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 기대 결과: `currentLive` optional 기반 숨김과 live item 표시가 확인된다.
### Phase 6: 최신 오디오 콘텐츠 섹션
- [x] **Task 6.1: 최신 오디오 콘텐츠 섹션 구현**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelLatestAudioSection.swift`
- 수정: `SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 확인: `SodaLive/Sources/V2/Component/AudioContentCard.swift`
- 작업 내용:
- `latestAudioContent == nil`이면 섹션을 표시하지 않는다.
- `AudioContentCard` 재사용이 맞으면 사용하고, 태그/잠금 표시가 부족하면 채널 홈 전용 thumbnail wrapper를 만든다.
- `isAdult`, `isPointAvailable`, `isFirstContent`, `seriesName`, `isOriginalSeries` 표시를 기존 태그 관례에 맞춘다.
- card tap은 기존 `AppStep.contentDetail(contentId:)` 흐름이 맞으면 연결한다.
- 검증 기준:
- 실행 명령: `rg "struct CreatorChannelLatestAudioSection|latestAudioContent|AudioContentCard|isPointAvailable|isFirstContent|isOriginalSeries|contentDetail" SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelLatestAudioSection.swift SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 기대 결과: 최신 오디오 optional 기반 숨김, 태그 표시, 상세 진입이 확인된다.
### Phase 7: 채널 후원 섹션
- [x] **Task 7.1: 채널 후원 섹션 구현**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelDonationSection.swift`
- 수정: `SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 확인: `SodaLive/Sources/App/AppStep.swift`
- 확인: `SodaLive/Sources/ContentView.swift`
- 디자인 기준:
- Figma 후원 아이템: `567:17927`, `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=567-17927&m=dev`
- 작업 내용:
- `channelDonations.isEmpty`여도 섹션을 숨기지 않고 Figma `290:8820` 기준의 빈 후원 UI를 표시한다.
- 각 item은 Figma 후원 아이템처럼 외곽 card 배경을 `#202020`, corner radius 14, 내부 vertical spacing 14 기준으로 구성한다.
- item 상단의 팬 정보/후원 금액 영역은 `profileImageUrl`, `nickname`, `createdAtUtc`, `can`을 표시한다.
- `profileImageUrl`은 42x42 원형 이미지로 표시하고, 이미지가 없거나 로드 실패 시 기존 프로필 placeholder 패턴을 따른다.
- `nickname`은 14pt medium, `createdAtUtc` 상대 시간은 14pt regular로 표시한다.
- 후원 금액은 우측 pill에 기존 can icon과 `{can}캔` 형식으로 표시한다.
- 팬 이름과 후원 금액을 포함하는 상단 영역의 배경색은 후원 금액 `can`에 따라 다르게 적용한다.
- `1...50`: `#E2E2E2`
- `51...100`: `#73EE01`
- `101...500`: `#00EAFF`
- `501...`: `#FF4C3C`
- 상단 영역 배경이 밝은 색상인 `1...500` 구간에서는 `nickname`은 black, `createdAtUtc`는 기존 gray 700 계열로 표시한다. `501...` 구간은 red 배경 대비를 위해 `nickname`을 white, `createdAtUtc``gray100`으로 표시한다. 금액 pill은 모든 구간에서 `#202020` 배경과 white text를 유지한다.
- `message`는 상단 영역 아래에 16pt regular, white text로 표시하고, 긴 문장은 화면 폭 안에서 줄바꿈한다.
- 전체보기는 기존 `AppStep.channelDonationAll(creatorId:)`로 이동하지 않고, 부모에서 주입받은 `onSelectTab(.donation)` closure로 현재 크리에이터 채널의 후원 tab을 선택한다.
- 빈 후원 UI는 section title 아래 `#202020` card, `처음으로 크리에이터를\n후원해 보세요!` 문구, white capsule `후원하기` 버튼, black `ic_new_donation` icon/text를 표시한다.
- 후원하기 버튼은 기존 `UserProfileView`에서 사용하던 `LiveRoomDonationDialogView`를 그대로 표시한다.
- Dialog 설정은 기존 채널 후원과 동일하게 `isAudioContentDonation: false`, `messageLimit: 100`, `I18n.MemberChannel.secretDonationLabel`, `I18n.MemberChannel.secretDonationMinimumCanMessage`, `shouldPrefixSecretInMessagePlaceholder: false`를 사용한다.
- 후원 전송은 기존 `ChannelDonationViewModel.postChannelDonation`을 재사용하고, 성공 시 현재 채널 홈 API를 다시 호출해 후원 섹션에 즉시 반영한다.
- 검증 기준:
- 실행 명령: `rg "struct CreatorChannelDonationSection|CreatorChannelDonationEmptyCard|처음으로 크리에이터를|ic_new_donation|LiveRoomDonationDialogView|ChannelDonationViewModel|postChannelDonation|fetchHome\(creatorId: creatorId\)|createdAtUtc|can|#E2E2E2|#73EE01|#00EAFF|#FF4C3C|onSelectTab|\\.donation|channelDonationAll" SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelDonationSection.swift`
- 기대 결과: 빈 후원 UI, 후원 금액별 상단 영역 컬러, 기존 후원 Dialog 재사용, 후원 성공 후 홈 API 재조회, 후원 tab 전체보기 연결이 확인되고 신규 구현 경로에서 `channelDonationAll` route를 사용하지 않는다.
### Phase 8: 공지 섹션
- [x] **Task 8.1: 공지 섹션 구현**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelNoticeSection.swift`
- 수정: `SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 확인: `SodaLive/Sources/V2/Component/Card/CommunityPostCard.swift`
- 작업 내용:
- `notices.isEmpty`이면 섹션을 표시하지 않는다.
- `CreatorChannelCommunityPostResponse`를 사용한다.
- 공지 섹션은 커뮤니티와 별도 component로 둔다.
- `imageUrl`, `audioUrl`, `price`, `existOrdered`, `likeCount`, `commentCount`를 기존 커뮤니티 카드 패턴으로 표시한다.
- item tap/댓글/구매 action은 기존 route가 확인되는 범위에서만 연결하고, 불명확한 action은 빈 closure로 둔다.
- 검증 기준:
- 실행 명령: `rg "struct CreatorChannelNoticeSection|notices|CreatorChannelCommunityPostResponse|CommunityPostCard|existOrdered|commentCount" SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelNoticeSection.swift SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 기대 결과: 공지 섹션이 커뮤니티 섹션과 분리되어 있고 빈 배열 숨김이 확인된다.
### Phase 9: 스케줄 섹션
- [x] **Task 9.1: 스케줄 섹션 구현**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelScheduleSection.swift`
- 수정: `SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 확인: `SodaLive/Sources/V2/Main/MainView.swift`
- 작업 내용:
- `schedules.isEmpty`이면 섹션을 표시하지 않는다.
- 각 item의 `scheduledAtUtc`, `title`, `type`, `targetId`를 표시한다.
- tap action은 closure로 분리하고, `.live`는 라이브 상세, `.liveReplay``.audio`는 동일한 오디오 콘텐츠 상세, `.community`는 커뮤니티 화면으로 분기한다.
- `.unknown`은 crash 없이 무시한다.
- 검증 기준:
- 실행 명령: `rg "struct CreatorChannelScheduleSection|schedules|scheduledAtUtc|CreatorActivityType|targetId|unknown|onTapSchedule" SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelScheduleSection.swift SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 기대 결과: 스케줄 표시와 type별 tap closure가 확인된다.
### Phase 10: 오디오 섹션
- [x] **Task 10.1: 오디오 목록 섹션 구현**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelAudioSection.swift`
- 수정: `SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 확인: `SodaLive/Sources/V2/Component/AudioContentCard.swift`
- 작업 내용:
- `audioContents.isEmpty`이면 섹션을 표시하지 않는다.
- Figma 홈 노출 개수만 표시한다.
- `AudioContentCard`와 채널 홈 전용 thumbnail wrapper를 재사용한다.
- 전체보기는 부모에서 주입받은 `onSelectTab(.audio)` closure로 연결한다.
- item tap은 기존 `AppStep.contentDetail(contentId:)` 흐름으로 연결한다.
- 검증 기준:
- 실행 명령: `rg "struct CreatorChannelAudioSection|audioContents|AudioContentCard|prefix|onSelectTab|\\.audio|contentDetail" SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelAudioSection.swift SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 기대 결과: 빈 배열 숨김, 홈 노출 개수 제한, 오디오 tab 전체보기 연결이 확인된다.
### Phase 11: 시리즈 섹션
- [x] **Task 11.1: 시리즈 섹션 구현**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelSeriesSection.swift`
- 수정: `SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 작업 내용:
- `series.isEmpty`이면 섹션을 표시하지 않는다.
- 각 item의 `seriesId`, `coverImageUrl`, `isNew`, `isOriginal`을 사용한다.
- 카드에는 시리즈 제목과 콘텐츠 수를 표시하지 않는다.
- 전체보기는 부모에서 주입받은 `onSelectTab(.series)` closure로 연결한다.
- item tap은 기존 `AppStep.seriesDetail(seriesId:)` 흐름으로 연결한다.
- 검증 기준:
- 실행 명령: `rg "struct CreatorChannelSeriesSection|seriesId|coverImageUrl|isNew|isOriginal|onSelectTab|\\.series|seriesDetail" SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelSeriesSection.swift SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 기대 결과: 시리즈 카드, 전체보기, 상세 진입이 확인된다.
### Phase 12: 커뮤니티 섹션
- [x] **Task 12.1: 커뮤니티 섹션 구현**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelCommunitySection.swift`
- 수정: `SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 확인: `SodaLive/Sources/V2/Component/Card/CommunityPostCard.swift`
- 작업 내용:
- `communities.isEmpty`이면 섹션을 표시하지 않는다.
- `CommunityPostCard`가 요구사항과 맞으면 재사용한다.
- 유료/잠금 게시물은 기존 `price`, `existOrdered` 처리 패턴을 따른다.
- 전체보기는 부모에서 주입받은 `onSelectTab(.community)` closure로 연결한다.
- 검증 기준:
- 실행 명령: `rg "struct CreatorChannelCommunitySection|communities|CommunityPostCard|existOrdered|onTapPurchase|onSelectTab|\\.community" SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelCommunitySection.swift SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 기대 결과: 커뮤니티 섹션과 전체보기 tab 전환이 확인된다.
### Phase 13: 팬Talk 섹션
- [x] **Task 13.1: 팬Talk 섹션 구현**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelFanTalkSection.swift`
- 수정: `SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 작업 내용:
- `fanTalk.latestFanTalk == nil`이면 섹션을 표시하지 않는다.
- `totalCount`, `nickname`, `profileImageUrl`, `content`, `languageCode`, `createdAtUtc`를 표시한다.
- 전체보기는 부모에서 주입받은 `onSelectTab(.fanTalk)` closure로 연결한다.
- 검증 기준:
- 실행 명령: `rg "struct CreatorChannelFanTalkSection|latestFanTalk|totalCount|languageCode|createdAtUtc|onSelectTab|\\.fanTalk" SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelFanTalkSection.swift SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 기대 결과: 최신 팬Talk가 있을 때만 표시되고 전체보기는 팬Talk tab으로 이동한다.
### Phase 14: 소개 섹션
- [x] **Task 14.1: 소개 섹션 구현**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelIntroduceSection.swift`
- 수정: `SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 작업 내용:
- `introduce`가 빈 문자열이면 섹션을 표시하지 않는다.
- 긴 텍스트는 화면 폭을 넘지 않게 줄바꿈한다.
- 검증 기준:
- 실행 명령: `rg "struct CreatorChannelIntroduceSection|introduce|trimmingCharacters|fixedSize|lineLimit" SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelIntroduceSection.swift SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 기대 결과: 빈 소개 숨김과 긴 텍스트 줄바꿈 처리가 확인된다.
### Phase 15: 활동 섹션
- [x] **Task 15.1: 활동 섹션 구현**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelActivitySection.swift`
- 수정: `SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 작업 내용:
- `debutDateUtc``dDay``2026.06.11(D+1)` 형식으로 표시한다.
- `liveCount`, `liveDurationHours`, `liveContributorCount`, `audioContentCount`, `seriesCount`를 표시한다.
- `화보` row는 만들지 않는다.
- 숫자는 기존 `.comma()` 등 프로젝트 formatting 패턴을 따른다.
- 검증 기준:
- 실행 명령: `rg "struct CreatorChannelActivitySection|debutDateUtc|dDay|liveCount|liveDurationHours|liveContributorCount|audioContentCount|seriesCount|화보|comma" SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelActivitySection.swift`
- 기대 결과: 활동 6개 항목만 표시되고 화보 row가 없다.
### Phase 16: SNS 섹션
- [x] **Task 16.1: SNS 섹션 구현**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelSnsSection.swift`
- 수정: `SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 확인: `SodaLive/Sources/Explorer/Profile/Detail/CreatorDetailDialogView.swift`
- 작업 내용:
- `instagramUrl`, `youtubeUrl`, `xUrl`, `kakaoOpenChatUrl`, `fancimmUrl` 중 유효한 URL만 표시한다.
- 표시 순서는 `Instagram`, `YouTube`, `X`, `KakaoTalk`, `Fancimm(팬심M)`이다.
- 아이콘은 각각 `ic_sns_instagram`, `ic_sns_youtube`, `ic_sns_x`, `ic_sns_kakao`, `ic_sns_fancimm`을 사용한다.
- tap 시 `openURL` 또는 기존 in-app web 흐름을 따른다.
- 유효한 SNS URL이 하나도 없으면 섹션을 표시하지 않는다.
- 검증 기준:
- 실행 명령: `rg "struct CreatorChannelSnsSection|ic_sns_instagram|ic_sns_youtube|ic_sns_x|ic_sns_kakao|ic_sns_fancimm|openURL|fancimmUrl" SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelSnsSection.swift`
- 기대 결과: SNS asset과 URL 필터링, tap 처리 방식이 확인된다.
### Phase 17: 대화하기 액션 영역
- [x] **Task 17.1: 대화하기 액션 섹션 생성**
- 대상 파일:
- 생성: `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelTalkActionSection.swift`
- 수정: `SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 작업 내용:
- `isAiChatAvailable == true`일 때만 대화하기 버튼을 표시한다.
- 버튼 아이콘은 `ic_new_talk`를 사용한다.
- 버튼은 영역 중앙에 배치한다.
- 버튼이 없어도 영역 높이와 vertical spacing을 유지한다.
- `isDmAvailable``ic_new_dm`은 이번 UI에 사용하지 않는다.
- 버튼 tap은 `CreatorChannelHeaderSection`이 부모에서 주입받은 `onTapTalk` closure로 전달한다.
- 검증 기준:
- 실행 명령: `rg "struct CreatorChannelTalkActionSection|isAiChatAvailable|ic_new_talk|ic_new_dm|frame\\(height|onTapTalk" SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelTalkActionSection.swift`
- 기대 결과: 대화하기만 조건부 표시되고 DM 버튼 구현이 없다.
- [x] **Task 17.2: 기존 AI 채팅방 진입 guard 연결**
- 대상 파일:
- 수정: `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift`
- 수정: `SodaLive/Sources/V2/CreatorChannel/CreatorChannelViewModel.swift`
- 수정: `SodaLive/Sources/V2/CreatorChannel/Home/Repository/CreatorChannelHomeRepository.swift`
- 수정: `SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelHeaderSection.swift`
- 확인: `SodaLive/Sources/Chat/Character/Detail/CharacterDetailView.swift`
- 확인: `SodaLive/Sources/Chat/Character/Detail/CharacterDetailViewModel.swift`
- 확인: `SodaLive/Sources/Chat/ChatTabView.swift`
- 확인: `SodaLive/Sources/App/AppStep.swift`
- 작업 내용:
- `CreatorChannelView``CreatorChannelHeaderSection``onTapTalk` closure를 주입한다.
- token이 없으면 `.login`으로 이동한다.
- 국가 코드를 trim/uppercase 처리하고, 비어 있거나 `KR`이면 한국으로 취급한다.
- 한국 사용자는 `auth == false`일 때 기존 본인인증 confirm/fullScreenCover 흐름을 재사용하거나 같은 조건으로 연결한다.
- 비한국 사용자는 기존 `ChatTabView`의 비한국 분기와 동일하게 `auth` 조건 비교를 적용한다.
- 본인인증과 콘텐츠 보기 설정 guard를 통과하면 `creator.characterId``TalkApi.createChatRoom`을 호출하고, 성공 응답의 `chatRoomId``.chatRoom(id:)`에 바로 진입한다.
- 본인인증 완료 후 실행되는 `pendingAction``.characterDetail(characterId:)`가 아니라 같은 채팅방 생성 후 `.chatRoom(id:)` 진입 helper를 사용한다.
- 검증 기준:
- 실행 명령: `rg "onTapTalk|countryCode|uppercased\\(\\)|isKoreanCountry|auth == false|isShowAuthConfirmView|createChatRoom|chatRoom\\(id:|setAppStep\\(step: \\.login\\)" SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift SodaLive/Sources/V2/CreatorChannel/CreatorChannelViewModel.swift SodaLive/Sources/V2/CreatorChannel/Home/Repository/CreatorChannelHomeRepository.swift SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelHeaderSection.swift`
- 기대 결과: 기존 AI 채팅 guard와 동일한 국가/본인인증 분기가 유지되고, guard 통과 후 캐릭터 상세가 아닌 채팅방으로 직접 이동한다.
- 실행 명령: `rg "characterDetail\\(characterId: characterId\\)" SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift`
- 기대 결과: 검색 결과가 없다.
### Phase 18: 최종 조립과 검증
- [x] **Task 18.1: 홈 섹션 순서 조립**
- 대상 파일:
- 수정: `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift`
- 수정: `SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 작업 내용:
- `CreatorChannelView`에서 공통 shell 순서를 PRD와 동일하게 조립한다.
- 공통 shell 배치: 크리에이터 이미지 header를 먼저 배치하고, title bar는 header 위에 overlay로 띄우며, 그 아래 tab-bar와 선택된 tab content를 배치한다.
- title bar 기본 배경은 투명이고 scroll progress에 따라 black으로 전환된다.
- 대화하기 액션은 최신 코드 기준처럼 `CreatorChannelHeaderSection` 내부에 유지한다.
- `CreatorChannelHomeView`에서 홈 탭 콘텐츠 순서를 최신 코드 기준과 동일하게 조립한다.
- 홈 탭 콘텐츠 순서: 현재 라이브, 최신 오디오, 채널 후원, 공지, 스케줄, 오디오, 시리즈, 커뮤니티, 팬Talk, 소개, 활동, SNS.
- 각 섹션은 자신의 데이터가 없으면 숨긴다.
- API 실패 placeholder 상태에서는 데이터 기반 섹션을 표시하지 않는다.
- 검증 기준:
- 실행 명령: `rg -n "ZStack|overlay|Color\\.clear|backgroundProgress|CreatorChannelTitleBar|CreatorChannelHeaderSection|CreatorChannelTabBar|CreatorChannelHomeView|CreatorChannelTalkActionSection|CreatorChannelCurrentLiveSection|CreatorChannelLatestAudioSection|CreatorChannelDonationSection|CreatorChannelNoticeSection|CreatorChannelScheduleSection|CreatorChannelAudioSection|CreatorChannelSeriesSection|CreatorChannelCommunitySection|CreatorChannelFanTalkSection|CreatorChannelIntroduceSection|CreatorChannelActivitySection|CreatorChannelSnsSection" SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- 기대 결과: 최신 코드 기준의 공통 shell, header 대화하기 액션, 홈 탭 콘텐츠 순서대로 조립되어 있다.
- [x] **Task 18.2: 프로젝트 파일 포함 여부 확인**
- 대상 파일:
- 확인/필요 시 수정: `SodaLive.xcodeproj/project.pbxproj`
- 작업 내용:
- 신규 Swift 파일이 Xcode target에 포함되는지 확인한다.
- 자동 포함되지 않는 프로젝트 구조이면 `project.pbxproj`에 신규 파일을 추가한다.
- 기존 사용자 변경이 있는 경우 해당 변경을 되돌리지 않는다.
- 검증 기준:
- 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`
- 기대 결과: 신규 파일 누락으로 인한 compile error가 없다.
- [x] **Task 18.3: PRD 성공 기준 회귀 점검**
- 대상 파일:
- 확인: `docs/20260701_크리에이터_채널_홈/prd.md`
- 확인: `SodaLive/Sources/V2/CreatorChannel/**`
- 확인: `SodaLive/Sources/V2/CreatorChannel/Home/**`
- 작업 내용:
- PRD `Success Criteria`를 한 줄씩 대조한다.
- `화보`, DM 버튼, API 실패 전체 error state가 구현에 들어가지 않았는지 검색한다.
- `ic_sns_*`, `ic_new_talk`, title bar icon asset, 기존 `UserProfile` 기준 follow/notify mutation, title bar/header overlay, AI 채팅 guard가 구현에 포함됐는지 검색한다.
- 검증 기준:
- 실행 명령: `rg -n "화보|photo|ic_new_dm|DM|ic_new_talk|ic_sns_|ic_new_bar_back|ic_new_follow|ic_new_following|ic_new_more|ic_bar_bell|ic_bar_bell_colored|creatorFollow|follow: true, notify: true|follow: false, notify: false|follow: true, notify: false|onTapNotify|onTapUnnotify|ZStack|overlay|Color\\.clear|isApiFailedPlaceholderVisible|countryCode|auth == false" SodaLive/Sources/V2/CreatorChannel SodaLive/Sources/ContentView.swift`
- 기대 결과: 제외 범위는 UI 구현에 없고, 필수 요구사항 키워드는 확인된다.
- [x] **Task 18.4: 빌드 검증**
- 대상 파일:
- 확인: `docs/agent-guides/build-test-verification.md`
- 작업 내용:
- 공식 검증 명령으로 Debug build를 실행한다.
- 실패 시 에러 로그를 기준으로 최소 수정하고 같은 명령을 다시 실행한다.
- 검증 기준:
- 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`
- 기대 결과: exit code 0으로 빌드가 완료된다.
### Phase 19: 스케줄 라우팅 및 라이브 상세 전역 표시 계층 보정
- [x] **Task 19.1: 스케줄 라우팅과 라이브 상세 표시 흐름 조사**
- 대상 파일:
- 확인: `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelScheduleSection.swift`
- 확인: `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift`
- 확인: `SodaLive/Sources/App/AppState.swift`
- 확인: `SodaLive/Sources/ContentView.swift`
- 비교: `SodaLive/Sources/V2/Main/MainView.swift`
- 비교: `docs/20260714_라이브룸_전역_표시와_키보드_레이아웃_수정/prd.md`
- 작업 내용:
- `.live``.liveReplay`가 두 스케줄 handler에서 같은 라이브 분기로 처리되는지 확인한다.
- `.live``targetId`는 room ID, `.liveReplay``.audio``targetId`는 콘텐츠 ID라는 타입 의미를 대조한다.
- `liveDetailSheet` 상태 갱신 지점과 `LiveDetailView` 렌더 위치를 분리해 추적한다.
- 메인 화면에서는 보이고 navigation destination에서는 가려지는 계층 차이를 기존 전역 라이브룸 표시 방식과 비교한다.
- 검증 기준:
- 실행 명령: `rg -n -A14 "func handle(Following)?ScheduleTap" SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift SodaLive/Sources/V2/Main/MainView.swift`
- 기대 결과: `.liveReplay``.live`와 묶여 잘못된 라이브 경로로 전달되는 라우팅 문제를 확인한다.
- 실행 명령: `rg -n "liveDetailSheet|LiveDetailView\\(|\\.overlay" SodaLive/Sources/App/AppState.swift SodaLive/Sources/ContentView.swift`
- 기대 결과: `.live` 상태 갱신은 정상이며 `LiveDetailView` 렌더 위치가 `NavigationStack` 루트 콘텐츠 내부인 표시 계층 문제를 확인한다.
- [x] **Task 19.2: 스케줄 type별 라우팅 보정**
- 대상 파일:
- 수정: `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift`
- 수정: `SodaLive/Sources/V2/Main/MainView.swift`
- 작업 내용:
- `CreatorChannelView.handleScheduleTap`에서 `.live``showLiveDetail(targetId)`로 전달한다.
- `CreatorChannelView.handleScheduleTap`에서 `.liveReplay``.audio`는 동일한 오디오 콘텐츠 상세 화면을 여는 `showContentDetail(targetId)`로 전달한다.
- `MainView.handleFollowingScheduleTap`에서 `.live`는 기존 `handleRecommendationLiveTap(roomId:)` 흐름을 유지한다.
- `MainView.handleFollowingScheduleTap`에서 `.liveReplay``.audio`는 동일한 오디오 콘텐츠 상세 화면을 여는 `handleRecommendationContentTap(contentId:)`로 전달한다.
- 신규 route나 helper를 추가하지 않고 기존 상세 진입 함수를 재사용한다.
- 검증 기준:
- 실행 명령: `rg -n -A14 "func handle(Following)?ScheduleTap" SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift SodaLive/Sources/V2/Main/MainView.swift`
- 기대 결과: 두 handler 모두 `.live`는 라이브 경로, `.liveReplay``.audio`는 동일한 오디오 콘텐츠 상세 경로로 분리되어 있다.
- 실행 명령: `rg -n "case \\.live, \\.liveReplay" SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift SodaLive/Sources/V2/Main/MainView.swift`
- 기대 결과: 검색 결과가 없다.
- [x] **Task 19.3: 라이브 상세 렌더 호스트를 앱 전역 overlay로 이동**
- 대상 파일:
- 수정: `SodaLive/Sources/ContentView.swift`
- 작업 내용:
- `NavigationStack` 내부 루트 `ZStack`의 기존 `liveDetailSheet` 렌더 분기를 복제하지 않고 `NavigationStack.overlay``ZStack` 첫 번째 계층으로 이동한다.
- 외부 overlay에서 동일 상태를 처리한 뒤 불필요해진 기존 위치의 렌더 분기는 제거한다.
- `roomId`, 네 개의 action callback, `onClickClose``hideLiveDetailSheet()` 호출과 animation은 변경하지 않는다.
- 전역 라이브룸과 외부 이동 확인 다이얼로그는 라이브 상세 뒤에 배치해 기존 우선순위를 유지한다.
- 검증 기준:
- 실행 명령: `rg -n "NavigationStack|if let liveDetailSheet|LiveDetailView\\(|LiveRoomViewV2\\(|leaveLiveNavigationDialog|\\.overlay" SodaLive/Sources/ContentView.swift`
- 기대 결과: `if let liveDetailSheet` 상태 기반 렌더 분기는 `NavigationStack` 바깥 overlay에 하나만 존재하고 기존 루트 `ZStack`에는 남지 않으며, 표시 순서가 라이브 상세 → 라이브룸 → 외부 이동 확인 다이얼로그다.
- [x] **Task 19.4: 정적 검사와 Debug 빌드 검증**
- 대상 파일:
- 확인: `SodaLive/Sources/ContentView.swift`
- 확인: `SodaLive/Sources/App/AppState.swift`
- 확인: `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift`
- 확인: `SodaLive/Sources/V2/Main/MainView.swift`
- 작업 내용:
- 전역 `LiveDetailView` 호스트 수와 기존 `.liveDetail` 호출부를 검색한다.
- `.liveReplay`가 라이브 분기로 남아 있지 않은지 검색한다.
- whitespace 오류를 확인하고 공식 `SodaLive-dev` Debug 빌드를 실행한다.
- 검증 기준:
- 실행 명령: `git diff --check`
- 기대 결과: 출력 없이 exit code 0이다.
- 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`
- 기대 결과: `** BUILD SUCCEEDED **`.
- [ ] **Task 19.5: 진입점별 수동 동작 검증**
- 대상 화면:
- 크리에이터 채널 홈의 `.live` 예약 스케줄
- 크리에이터 채널 홈의 `.liveReplay` 스케줄
- 메인 팔로잉 화면의 `.liveReplay` 스케줄
- 크리에이터 채널 라이브 탭
- 메인 화면의 기존 라이브 상세 진입점
- 작업 내용:
- 크리에이터 채널의 `.live` 항목 tap 직후 현재 화면 위에 라이브 상세가 보이는지 확인한다.
- 크리에이터 채널과 메인의 `.liveReplay` 항목 tap 시 `.audio`와 동일한 오디오 콘텐츠 상세로 이동하고 라이브 상세나 라이브룸이 표시되지 않는지 확인한다.
- dim 영역 또는 닫기 버튼으로 상세를 닫았을 때 진입 전 화면과 navigation path가 유지되는지 확인한다.
- 라이브 상세에서 라이브룸에 입장할 수 있는 기존 경로는 라이브룸이 상세 위에 표시되는지 확인한다.
- 검증 기준:
- 기대 결과: `.live`만 라이브 상세를 표시하고 `.liveReplay``.audio`는 동일한 오디오 콘텐츠 상세로 이동하며, 닫기와 라이브룸 표시 우선순위에 회귀가 없다.
## 검증 기록
- 2026-07-01: `docs/20260701_크리에이터_채널_홈/prd.md`를 기준으로 API 선구현, Figma 섹션별 phase, placeholder tab, API 실패 placeholder, DM/화보 제외 범위를 계획에 반영했다.
- 2026-07-01: `docs/agent-guides/documentation-policy.md`를 확인해 `plan-task.md` 경로와 phase/task/checklist 형식을 검증했다.
- 2026-07-01: `docs/agent-guides/build-test-verification.md`를 확인해 최종 빌드 검증 명령을 `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`로 반영했다.
- 2026-07-01: 기존 V2 홈 API/Repository/ViewModel, AI 채팅 guard, follow/unfollow API, SNS asset 사용처를 확인해 대상 파일과 검증 기준에 반영했다.
- 2026-07-01: 사용자 요청에 따라 기존 Phase 4 `대화하기 액션 영역`을 Phase 17로 이동하고, 기존 Phase 5~17을 Phase 4~16으로 한 칸씩 당겼다.
- 2026-07-01: Header, TitleBar, TabBar만으로 sticky 동작을 먼저 확인할 수 있도록 Phase 3에 `CreatorChannelHomeView_Previews` 기반 debug-only sticky scaffold 검증 task를 추가했다. 2026-07-02 구조 변경으로 실제 검증 대상은 `CreatorChannelView_Previews`로 갱신했다.
- 2026-07-01: Phase 1~2 구현을 완료하고 `rg` 체크리스트 및 `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 성공으로 신규 API/상태 모델, 라우팅, 기본 shell, I18n, 프로젝트 파일 포함을 검증했다.
- 2026-07-02: 사용자 확인 사항과 최신 코드 기준을 반영해 title bar, 크리에이터 프로필 header, tab-bar를 `CreatorChannelView` 공통 shell로 분리하고, `CreatorChannelHomeView`는 tab-bar 아래 홈 탭 콘텐츠만 담당하며 대화하기 action은 `CreatorChannelHeaderSection` 내부에 유지하도록 PRD와 계획을 갱신했다.
- 2026-07-02: 이미 완료된 Phase 1~2 기록은 보존하고, 후속 구조 정리를 위해 Task 2.4 `공통 CreatorChannel shell로 구조 정리`를 추가했다.
- 2026-07-02: 사용자 확인 사항과 최신 코드 기준을 반영해 title bar Figma URL 3종과 사용할 아이콘 asset(`ic_new_bar_back`, `ic_new_follow`, `ic_new_following`, `ic_new_more`, `ic_bar_bell`, `ic_bar_bell_colored`)을 Task 3.1 및 최종 회귀 검색 기준에 추가했다.
- 2026-07-03: Phase 5 현재 라이브 섹션을 구현했다. Figma `290:8950` 기준으로 LIVE pill, 시작 시간, 제목, 커버 이미지/placeholder, 유료 캔 배지, 성인 표시를 반영하고 `CreatorChannelHomeView``currentLive` optional 기반으로 연결했다.
- 2026-07-03: Phase 7 채널 후원 섹션을 구현했다. Figma `296:14908`, 후원 아이템 `567:17927` 기준으로 후원 금액별 상단 컬러(`1...50` `#E2E2E2`, `51...100` `#73EE01`, `101...500` `#00EAFF`, `501...` `#FF4C3C`), `ic_new_donation` 후원하기 버튼, `.donation` tab 전체보기 전환, Preview 금액 구간별 샘플을 반영했다. `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 성공으로 신규 파일 target 포함과 컴파일을 검증했다.
- 2026-07-03: 사용자 요청에 따라 Phase 7 후원하기 버튼을 기존 `LiveRoomDonationDialogView`/`ChannelDonationViewModel.postChannelDonation` 흐름으로 연결하고, 성공 시 `CreatorChannelViewModel.fetchHome(creatorId:)`로 현재 채널 홈 데이터를 다시 불러와 후원 섹션에 즉시 반영하도록 계획을 갱신했다. 후원 내역이 없는 경우 Figma `290:8820` 기준의 빈 후원 UI를 표시하도록 Task 7.1에 반영했다.
- 2026-07-03: Phase 6 최신 오디오 콘텐츠 섹션을 구현했다. Figma `296:14898`, tag `567:18272`, `567:18274` 기준으로 88px 썸네일, 상단/하단 tag overlay, `New` label, 제목, 재생시간을 반영하고 `latestAudioContent` optional 기반 숨김 및 `AppStep.contentDetail(contentId:)` 상세 진입을 연결했다. `rg` 체크리스트와 `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 성공으로 검증했다.
- 2026-07-03: Phase 4를 완료해 `CreatorChannelView`에서 홈 탭은 `CreatorChannelHomeView`, 홈 외 탭은 `CreatorChannelPlaceholderTabView(title: selectedTab.title)`로 분기하고, 홈 섹션 전체보기용 tab 선택 closure가 `.audio`, `.series`, `.community`, `.fanTalk`, `.donation`으로 전환되도록 연결했다.
- 2026-07-02: 기존 `UserProfile` 구현과 최신 코드 기준을 확인해 title bar 팔로우/팔로우 취소/알림 설정/알림 설정 취소를 `UserRepository.creatorFollow(creatorId:follow:notify:)`로 처리하고, `ic_bar_bell`/`ic_bar_bell_colored` 터치 시 각각 `notify: true`/`notify: false` 호출이 일어나도록 계획에 반영했다.
- 2026-07-03: Task 3.4 구현을 완료했다. `CreatorChannelView`에서 geometry preference로 tab-bar 위치를 추적하고, sticky 기준을 디바이스 최상단이 아닌 title bar 하단(`safeAreaInsets.top + 56`)으로 계산하도록 변경했다. `rg "PreferenceKey|GeometryReader|ZStack|overlay|backgroundProgress|Color\\.clear|opacity|isTabBarSticky|clamp|safeAreaInsets|CreatorChannelTitleBar|CreatorChannelTabBar" ...` 기준 키워드를 확인했고, `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build``BUILD SUCCEEDED`로 완료됐다.
- 2026-07-03: Task 3.5 구현을 완료했다. `CreatorChannelTitleBar`에 크리에이터 닉네임 주입값을 추가하고, `backgroundProgress`가 1일 때만 한 줄 말줄임으로 표시되도록 연결했다. 우측 팔로우/알림/더보기 영역은 `layoutPriority`로 우선 노출되도록 처리했다. `rg "nickname|lineLimit\\(1\\)|truncationMode|layoutPriority|backgroundProgress|opacity|CreatorChannelTitleBar\\(" ...` 기준 키워드를 확인했고, `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build``BUILD SUCCEEDED`로 완료됐다.
- 2026-07-02: Task 3.2 구현을 완료하고 `CreatorChannelHeaderSection`을 공통 shell에 연결했다. `rg "struct CreatorChannelHeaderSection|CreatorChannelCreatorResponse|profileImageUrl|ignoresSafeArea\\(.*top|nickname|followerCount|DownsampledKFImage|KFImage" SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelHeaderSection.swift SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift``xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 성공으로 검증했다.
- 2026-07-02: Task 3.1 title bar 컴포넌트를 `SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelTitleBar.swift`로 생성하고 `CreatorChannelView`의 inline title bar를 교체했다. `rg "struct CreatorChannelTitleBar|isFollow|isNotify|backgroundProgress|onTapBack|onTapFollow|onTapUnfollow|onTapNotify|onTapUnnotify|onTapMore|ic_new_bar_back|ic_new_follow|ic_new_following|ic_new_more|ic_bar_bell|ic_bar_bell_colored|팔로우" SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelTitleBar.swift``xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 성공으로 검증했다.
- 2026-07-02: 사용자 확인 사항을 반영해 HeaderView는 `CreatorChannelHomeResponse.creator` 데이터로 채우고, 별도 프로필 이미지 없이 큰 배경 이미지만 표시하며, title bar는 기본 투명 배경으로 header 위에 overlay되도록 Task 3.2/3.4/3.5와 최종 검증 기준을 갱신했다.
- 2026-07-03: 사용자 요청에 따라 Task 17.2의 대화하기 액션을 캐릭터 상세 이동이 아니라 기존 Character Detail 하단 `대화하기`와 동일한 `TalkApi.createChatRoom` 호출 후 `.chatRoom(id:)` 직접 진입으로 변경했다. `CreatorChannelView`의 로그인/한국 본인인증/content settings guard는 유지했고, 본인인증 완료 후 `pendingAction`도 같은 `startChat(characterId:)` helper를 사용하도록 연결했다. `rg -n "characterDetail\\(characterId: characterId\\)|startChat\\(characterId:|createChatRoom\\(characterId:|chatRoom\\(id:" "SodaLive/Sources/V2/CreatorChannel"`와 ast-grep `AppState.shared.setAppStep(step: .characterDetail(characterId: $ID))` 검색으로 CreatorChannel 대화하기 경로에 캐릭터 상세 이동이 남지 않았음을 확인했다. 테스트는 `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test` 실행 결과 `Scheme SodaLive-dev is not currently configured for the test action.`으로 RED/GREEN 자동화가 불가했다. 빌드는 `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build``BUILD SUCCEEDED`로 완료됐다.
- 2026-07-03: Phase 18 최종 조립과 검증을 완료했다. 최신 코드 기준에 맞춰 대화하기 액션은 `CreatorChannelHeaderSection` 내부에 유지하고, `CreatorChannelHomeView` 홈 콘텐츠 순서는 현재 라이브, 최신 오디오, 채널 후원, 공지, 스케줄, 오디오, 시리즈, 커뮤니티, 팬Talk, 소개, 활동, SNS 순서로 확인했다. title bar 알림 설정 asset은 최신 코드 기준인 `ic_bar_bell_colored`를 유지했다. `rg -n "ZStack|overlay|Color\\.clear|backgroundProgress|CreatorChannelTitleBar|CreatorChannelHeaderSection|CreatorChannelTabBar|CreatorChannelHomeView|CreatorChannelTalkActionSection|CreatorChannelCurrentLiveSection|CreatorChannelLatestAudioSection|CreatorChannelDonationSection|CreatorChannelNoticeSection|CreatorChannelScheduleSection|CreatorChannelAudioSection|CreatorChannelSeriesSection|CreatorChannelCommunitySection|CreatorChannelFanTalkSection|CreatorChannelIntroduceSection|CreatorChannelActivitySection|CreatorChannelSnsSection" ...``rg -n "화보|photo|ic_new_dm|DM|ic_new_talk|ic_sns_|ic_new_bar_back|ic_new_follow|ic_new_following|ic_new_more|ic_bar_bell|ic_bar_bell_colored|creatorFollow|follow: true, notify: true|follow: false, notify: false|follow: true, notify: false|onTapNotify|onTapUnnotify|ZStack|overlay|Color\\.clear|isApiFailedPlaceholderVisible|countryCode|auth == false" ...`로 성공 기준을 회귀 점검했고, 신규 CreatorChannel Swift 파일은 `project.pbxproj`의 Sources phase에 포함되어 있음을 확인했다. `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build``BUILD SUCCEEDED`로 완료됐다.
- 2026-07-20: 크리에이터 채널 홈 스케줄 item에서 `CreatorChannelScheduleSection``CreatorChannelView.handleScheduleTap``showLiveDetail``AppState.setAppStep(.liveDetail)`까지 이벤트와 상태 갱신이 정상임을 확인했다. `liveDetailSheet`의 렌더 호스트가 `NavigationStack` 루트 `ZStack` 안에 있어 destination인 `CreatorChannelView` 뒤에 가려지는 것이 근본 원인이다. 기존 `AppState` 상태나 채널 callback을 복제하지 않고 `SodaLive/Sources/ContentView.swift`에서 해당 렌더 분기만 기존 외부 overlay로 옮기는 최소 수정안을 계획했다. 구현과 빌드·수동 검증은 아직 수행하지 않았다.
- 2026-07-20: 사용자 확인에 따라 Task 19.3에 기존 `liveDetailSheet` 렌더 분기를 복제하지 않고 외부 overlay로 이동하며, 이동 후 기존 루트 `ZStack`의 분기를 제거하고 상태 기반 호스트가 하나만 남는지 검증하도록 명시했다.
- 2026-07-20: 사용자 확인에 따라 `LiveDetailView` 표시 대상을 `.live`로 한정하고, `.liveReplay`는 다시 듣기 콘텐츠의 `contentId``.audio`와 동일한 오디오 콘텐츠 상세에 진입하도록 요구사항을 수정했다. 현재 `CreatorChannelView.handleScheduleTap``MainView.handleFollowingScheduleTap`이 모두 `.liveReplay``.live`와 묶고 있어 두 파일의 분기를 수정하는 Task 19.2를 추가하고 기존 후속 task를 19.3~19.5로 재번호화했다.
- 2026-07-20: Task 19.2~19.4 구현을 완료했다. `CreatorChannelView.handleScheduleTap``MainView.handleFollowingScheduleTap`에서 `.live`만 라이브 경로로 두고 `.audio, .liveReplay`를 콘텐츠 상세 경로로 분리했다. `ContentView``liveDetailSheet` 상태 기반 `LiveDetailView` 렌더 호스트는 기존 루트 `ZStack`에서 제거하고 `NavigationStack.overlay`의 첫 번째 계층으로 이동해 라이브 상세 → 전역 라이브룸 → 외부 이동 확인 다이얼로그 순서를 맞췄다. `rg -n -A14 "func handle(Following)?ScheduleTap|if let liveDetailSheet|LiveDetailView\\(|LiveRoomViewV2\\(|leaveLiveNavigationDialog|\\.overlay|case \\.live, \\.liveReplay" ...``.liveReplay` 묶음 제거와 overlay 위치를 확인했고, `git diff --check`는 출력 없이 통과했다. `sourcekit-lsp`는 active 상태였으나 변경 파일별 `lsp_diagnostics`가 3초 내 fresh diagnostics를 반환하지 않았다. 공식 `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build``** BUILD SUCCEEDED **`로 완료됐다. Task 19.5의 실제 진입점별 기기/시뮬레이터 수동 검증은 아직 수행하지 않았다.
### Phase 20: 종료된 라이브 상세 DIM 잔류 보정
- [x] **Task 20.1: 종료된 라이브 상세 실패 흐름 문서화와 원인 확인**
- 대상 파일:
- 확인: `SodaLive/Sources/Live/Room/Detail/LiveDetailView.swift`
- 확인: `SodaLive/Sources/Live/Room/Detail/LiveDetailViewModel.swift`
- 확인: `SodaLive/Sources/ContentView.swift`
- 작업 내용:
- `LiveDetailViewModel.getDetail` 실패 시 로컬 `isShowPopup`만 켜지고 `LiveDetailView` 표시 상태가 해제되지 않는 흐름을 확인한다.
- `LiveDetailView`가 닫히지 않으면 `Color.black.opacity(0.7)` DIM 배경이 계속 남는 것을 수정 대상으로 기록한다.
- 검증 기준:
- 실행 명령: `rg -n "getDetail\(roomId:|isShowPopup = true|hideView\(|onClickClose|liveDetailSheet" SodaLive/Sources/Live/Room/Detail/LiveDetailView.swift SodaLive/Sources/Live/Room/Detail/LiveDetailViewModel.swift SodaLive/Sources/ContentView.swift`
- 기대 결과: 상세 조회 실패와 overlay 닫기 경로가 확인된다.
- [x] **Task 20.2: 종료된 라이브 상세 실패 시 DIM 해제**
- 대상 파일:
- 수정: `SodaLive/Sources/Live/Room/Detail/LiveDetailView.swift`
- 작업 내용:
- `LiveDetailView`가 room 데이터 없이 상세 조회 실패 토스트를 표시해야 하는 경우 전역 토스트로 메시지를 전달한다.
- 같은 실패 흐름에서 기존 `onClickClose`/`AppState.back()` 기반 close 경로를 호출해 `LiveDetailView`와 DIM overlay를 닫는다.
- 정상적으로 room 데이터를 받은 라이브 상세 표시와 기존 닫기 버튼 동작은 변경하지 않는다.
- 검증 기준:
- 실행 명령: `rg -n "handleDetailLoadFailure|AppState.shared.errorMessage|AppState.shared.isShowErrorPopup|hideView\(" SodaLive/Sources/Live/Room/Detail/LiveDetailView.swift`
- 기대 결과: 상세 조회 실패 시 전역 토스트와 close 경로 호출이 확인된다.
- [x] **Task 20.3: 정적 검사와 Debug 빌드 검증**
- 대상 파일:
- 확인: `SodaLive/Sources/Live/Room/Detail/LiveDetailView.swift`
- 확인: `SodaLive/Sources/ContentView.swift`
- 작업 내용:
- 종료 라이브 실패 흐름과 기존 overlay 순서를 정적 검색으로 확인한다.
- `git diff --check`와 공식 Debug 빌드를 실행한다.
- 검증 기준:
- 실행 명령: `git diff --check`
- 기대 결과: 출력 없이 exit code 0이다.
- 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`
- 기대 결과: `** BUILD SUCCEEDED **`.
- 2026-07-20: Phase 20 구현을 완료했다. `LiveDetailViewModel.getDetail` 실패 시 `viewModel.isShowPopup = true`만 설정되고 `LiveDetailView` 자체 표시 상태는 해제되지 않아 `Color.black.opacity(0.7)` DIM 배경이 남는 원인을 확인했다. `LiveDetailView`에서 `viewModel.isShowPopup` 변경을 감지하되 `viewModel.room == nil`인 상세 조회 실패에만 `handleDetailLoadFailure()`를 호출하도록 했고, 해당 helper는 기존 메시지를 `AppState.shared.errorMessage`/`isShowErrorPopup`으로 넘긴 뒤 `hideView()`로 기존 `onClickClose` 또는 `AppState.back()` 닫기 경로를 실행한다. `rg -n "handleDetailLoadFailure|AppState.shared.errorMessage|AppState.shared.isShowErrorPopup|viewModel.room == nil|hideView\\(|Phase 20|이미 종료된 라이브 상세 조회 실패" ...``git diff --check`를 통과했다. `lsp_diagnostics``Kingfisher` 모듈 해석 문제로 실패했으나, 공식 `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build``** BUILD SUCCEEDED **`로 완료됐다. 실제 종료된 라이브 API 응답을 사용하는 기기/시뮬레이터 수동 검증은 수행하지 않았다.
- 2026-07-20: Phase 20 후속 보정을 적용했다. 종료된 라이브 상세 실패 처리에서 전역 토스트 상태를 먼저 켠 뒤 `LiveDetailView`를 닫으면 overlay 해제와 함께 토스트 표시가 누락될 수 있어, 실패 메시지를 지역 변수로 보존하고 `viewModel.isShowPopup = false``hideView()`로 DIM overlay를 먼저 해제한 다음 다음 main runloop에서 `AppState.shared.errorMessage`/`isShowErrorPopup`을 설정하도록 순서를 조정했다.
- 2026-07-20: Phase 20 토스트 계층 보정을 적용했다. `sodaToast`는 적용된 content 위에 overlay를 붙이는 modifier인데, 기존 전역 토스트 host가 `NavigationStack` 내부 루트 `ZStack`에 붙어 있어 navigation destination 및 전역 `LiveDetailView` overlay보다 아래 계층에 남을 수 있었다. `ContentView`의 전역 `.sodaToast(isPresented: $appState.isShowErrorPopup, ...)``NavigationStack.overlay` 뒤의 최상위 modifier로 이동해 DIM overlay 해제 후에도 토스트가 최상위에서 표시되도록 했다.
- 2026-07-20: Phase 20 추가 보정을 적용했다. 전역 `AppState.isShowErrorPopup`이 이미 `true`인 상태에서 같은 값을 다시 설정하면 `SodaToastModifier.onChange(of: isPresented)`가 재실행되지 않을 수 있고, `LiveDetailView` close animation과 같은 runloop에서 토스트를 켜면 표시 타이밍이 겹칠 수 있어 종료 상세 실패 처리에서 `AppState.shared.isShowErrorPopup = false`로 먼저 reset한 뒤 `hideView()`를 호출하고, 0.25초 후 `AppState.shared.errorMessage`/`isShowErrorPopup`을 설정하도록 조정했다. `lsp_diagnostics`는 기존 SourceKit 환경의 `No such module 'Kingfisher'`로 실패했으나, `git diff --check`는 통과했고 `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build``** BUILD SUCCEEDED **`로 완료됐다.
### Phase 21: 채널 홈 후원 카드 반응형 너비 보정
- [x] **Task 21.1: 후원 카드 너비 계산 보정**
- 대상 파일:
- 수정: `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelDonationSection.swift`
- 작업 내용:
- 기준 디바이스 너비 `402pt`와 최대 카드 너비 `374pt`를 사용한다.
- 디바이스 너비가 `402pt` 이상이면 기존 `374pt`를 유지한다.
- 디바이스 너비가 `402pt` 미만이면 `374 × deviceWidth / 402` 비율로 카드 너비만 축소한다.
- 카드 높이, 내부 구성, 패딩, 카드 간격과 후원 탭 세로 목록은 변경하지 않는다.
- 검증 기준:
- 실행 명령: `swift -e 'import Foundation; let widths = [320.0, 375.0, 402.0, 430.0]; print(widths.map { 374.0 * min($0 / 402.0, 1.0) })'`
- 기대 결과: `320pt`, `375pt`에서는 비례 축소되고 `402pt`, `430pt`에서는 모두 `374pt`다.
- 실행 명령: `rg -n "baseDeviceWidth|maxCardWidth|cardWidth|frame\\(width:" SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelDonationSection.swift`
- 기대 결과: 후원 섹션 카드에만 반응형 너비 계산이 적용되어 있다.
- [x] **Task 21.2: 정적 검사와 Debug 빌드 검증**
- 대상 파일:
- 확인: `docs/20260701_크리에이터_채널_홈/prd.md`
- 확인: `docs/20260701_크리에이터_채널_홈/plan-task.md`
- 확인: `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelDonationSection.swift`
- 작업 내용:
- 문서 요구사항과 구현 계산식이 일치하는지 확인한다.
- whitespace 오류를 확인하고 공식 `SodaLive-dev` Debug 빌드를 실행한다.
- 검증 기준:
- 실행 명령: `git diff --check`
- 기대 결과: 출력 없이 exit code 0이다.
- 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`
- 기대 결과: `** BUILD SUCCEEDED **`.
- 2026-07-20: Phase 21 구현을 완료했다. `CreatorChannelDonationSection`의 기존 고정 `374pt` 카드 너비를 `374 × min(deviceWidth / 402, 1)` 계산값으로 교체해 `402pt` 이상에서는 기존 너비를 유지하고 작은 디바이스에서만 비례 축소되도록 했다. `swift -module-cache-path /tmp/sodalive-swift-module-cache -e 'import Foundation; let widths = [320.0, 375.0, 402.0, 430.0]; print(widths.map { 374.0 * min($0 / 402.0, 1.0) })'` 실행 결과는 `[297.71144278606965, 348.8805970149254, 374.0, 374.0]`이었다. `rg`로 계산식 적용 위치를 확인했고 `git diff --check`를 통과했다. `xcodebuild -workspace SodaLive.xcworkspace -scheme SodaLive-dev -configuration Debug -destination 'platform=iOS Simulator,id=224FBAAE-B5C7-4A10-9143-3944A3280E48' -derivedDataPath /tmp/sodalive-shell-derived-escalated build``** BUILD SUCCEEDED **`로 완료됐다. 프로젝트에 테스트 번들 타깃이 없어 별도 단위 테스트는 실행하지 않았다.