diff --git a/SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelDonationSection.swift b/SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelDonationSection.swift index 80ec1b5f..ce18af50 100644 --- a/SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelDonationSection.swift +++ b/SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelDonationSection.swift @@ -1,10 +1,17 @@ import SwiftUI struct CreatorChannelDonationSection: View { + private static let baseDeviceWidth: CGFloat = 402 + private static let maxCardWidth: CGFloat = 374 + let channelDonations: [CreatorChannelDonationResponse] let onSelectTab: (CreatorChannelTab) -> Void let onTapDonate: () -> Void + private var cardWidth: CGFloat { + Self.maxCardWidth * min(UIScreen.main.bounds.width / Self.baseDeviceWidth, 1) + } + init( channelDonations: [CreatorChannelDonationResponse], onSelectTab: @escaping (CreatorChannelTab) -> Void, @@ -29,7 +36,7 @@ struct CreatorChannelDonationSection: View { HStack(alignment: .top, spacing: SodaSpacing.s4) { ForEach(channelDonations.indices, id: \.self) { index in CreatorChannelDonationCard(donation: channelDonations[index]) - .frame(width: 374) + .frame(width: cardWidth) } } .padding(.leading, SodaSpacing.s14) diff --git a/docs/20260701_크리에이터_채널_홈/plan-task.md b/docs/20260701_크리에이터_채널_홈/plan-task.md index 089815f2..ac35d734 100644 --- a/docs/20260701_크리에이터_채널_홈/plan-task.md +++ b/docs/20260701_크리에이터_채널_홈/plan-task.md @@ -785,3 +785,35 @@ - 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 **`로 완료됐다. 프로젝트에 테스트 번들 타깃이 없어 별도 단위 테스트는 실행하지 않았다. diff --git a/docs/20260701_크리에이터_채널_홈/prd.md b/docs/20260701_크리에이터_채널_홈/prd.md index 08066f78..3986ae6e 100644 --- a/docs/20260701_크리에이터_채널_홈/prd.md +++ b/docs/20260701_크리에이터_채널_홈/prd.md @@ -38,6 +38,7 @@ Figma 전체 화면 기준 공통 shell은 아래를 따른다. - 후속 버그(2026-07-20): `AppState.liveDetailSheet`는 전역 상태지만 `LiveDetailView`가 `NavigationStack`의 루트 콘텐츠 안에서 렌더링된다. 크리에이터 채널은 navigation destination으로 루트 콘텐츠 위에 표시되므로, 채널 스케줄의 라이브 예약 항목을 눌러도 상세가 현재 화면 위에 나타나지 않고 메인 화면으로 돌아간 뒤에야 보인다. - 후속 버그(2026-07-20): `.liveReplay`는 다시 듣기 카테고리의 업로드 콘텐츠이고 `targetId`도 콘텐츠 ID이지만, 현재 크리에이터 채널과 메인 스케줄 분기에서는 `.live`와 함께 처리되어 라이브 진입 경로로 잘못 전달된다. `.liveReplay`의 상세 화면은 `.audio`와 동일한 오디오 콘텐츠 상세 화면이다. - 후속 버그(2026-07-20): `LiveDetailView`가 이미 종료된 라이브의 상세 조회 실패 응답을 받으면 `이미 종료된 라이브 입니다.` 토스트는 표시되지만 `LiveDetailView` overlay가 닫히지 않아 DIM 배경이 화면에 남는다. +- 후속 UI 보정(2026-07-20): 채널 홈 후원 섹션의 카드 너비가 `374pt`로 고정되어 있어 디바이스 너비가 `402pt`보다 작은 화면에서는 기준 디자인과 같은 비율을 유지하지 못한다. ## 3. Goals - `creatorId`로 `GET /api/v2/creator-channels/{creatorId}/home`을 호출하고 응답 데이터로 크리에이터 채널 공통 shell과 홈 탭 콘텐츠를 구성한다. @@ -57,6 +58,7 @@ Figma 전체 화면 기준 공통 shell은 아래를 따른다. - 크리에이터 채널의 라이브 예약 스케줄 항목을 누르면 현재 채널 화면 위에 기존 `LiveDetailView`가 즉시 표시되고, 닫으면 같은 채널 화면으로 돌아온다. - `.liveReplay` 스케줄 항목을 누르면 `targetId`를 콘텐츠 ID로 사용해 `.audio`와 동일한 기존 오디오 콘텐츠 상세 페이지로 이동한다. - 이미 종료된 라이브의 `LiveDetailView`를 열면 `이미 종료된 라이브 입니다.` 토스트를 표시하고 `LiveDetailView`/DIM overlay를 닫아 진입 전 화면을 유지한다. +- 채널 홈 후원 섹션 카드 너비는 디바이스 너비가 `402pt` 이상이면 `374pt`를 유지하고, `402pt` 미만이면 디바이스 너비에 비례해 축소한다. ## 4. Non-Goals - `화보` 탭과 화보 섹션은 구현하지 않는다. @@ -247,6 +249,10 @@ Figma 참조: - `channelDonations`가 비어 있으면 채널 후원 섹션을 숨긴다. - 전체보기 action은 기존 `AppStep.channelDonationAll(creatorId:)` 또는 동일한 기존 흐름이 있으면 연결한다. - 기존 흐름 연결이 어렵다면 후원 tab placeholder로 이동한다. +- 카드 너비는 기준 디바이스 너비 `402pt`, 최대 카드 너비 `374pt`를 사용한다. +- 디바이스 너비가 `402pt` 이상이면 카드 너비를 `374pt`로 유지한다. +- 디바이스 너비가 `402pt` 미만이면 `374 × deviceWidth / 402`로 카드 너비만 비례 축소한다. +- 카드 높이, 내부 레이아웃, 패딩, 카드 간격과 후원 탭의 세로 목록 너비는 변경하지 않는다. ### 7.10 공지 섹션 @@ -415,6 +421,8 @@ Figma 참조: - 메인 화면과 다른 navigation destination에서 사용하는 기존 `.liveDetail` 진입도 동일한 전역 호스트에서 한 번만 표시된다. - `ContentView`의 `if let liveDetailSheet` 상태 기반 렌더 분기는 외부 overlay에 하나만 남고 기존 루트 `ZStack`에는 남지 않는다. - 이미 종료된 라이브 상세 조회 실패 시 `AppState.liveDetailSheet` 또는 각 화면의 `LiveDetailView` 표시 상태가 해제되어 DIM 배경이 남지 않는다. +- 채널 홈 후원 섹션 카드 너비는 `402pt` 이상의 디바이스에서 `374pt`이고, `402pt` 미만에서는 `374 × deviceWidth / 402`로 계산된다. +- 후원 카드의 높이, 내부 콘텐츠, 패딩, 카드 간격 및 후원 탭 세로 목록 레이아웃은 기존 상태를 유지한다. ## 11. Open Questions 해당 없음.