diff --git a/docs/20261006_선물_UI_추가/plan-task.md b/docs/20261006_선물_UI_추가/plan-task.md new file mode 100644 index 00000000..ff58deb3 --- /dev/null +++ b/docs/20261006_선물_UI_추가/plan-task.md @@ -0,0 +1,223 @@ +# 선물 UI 추가 구현 계획 + +| 문서 항목 | 내용 | +|---|---| +| 상태 | 구현 완료 | +| 작성일 | `2026-10-06` | +| 요구사항 기준 | `docs/20261006_선물_UI_추가/prd.md` | +| API 기준 | 해당 없음 | +| 현재 Phase | Phase 1 선물 UI 진입점 추가 | +| 현재 활성 Goal | 없음 | + +## 목표 + +한국 접속 및 한국어 설정 사용자가 크리에이터 상세과 마이 탭에서 선물 관련 진입점을 기존 UI 규칙대로 확인할 수 있게 한다. + +## 현재 상태 + +| Phase | 상태 | 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 | +|---:|---|---:|---|---| +| 1 | 완료 | `5/5` | 없음 | iOS 실기기/시뮬레이터 화면 캡처 QA는 미수행 | + +## 범위 + +### 포함 + +- 크리에이터 상세 헤더 액션 영역의 조건부 `선물 보내기` 버튼 추가. +- 마이 탭 카테고리 그리드 첫 항목의 `선물함` 버튼 추가. +- `I18n` 문자열 추가. +- 후속 구현 연결을 위한 inert tap action 위치만 추가. + +### 제외 + +- 선물 보내기 화면, 선물함 화면, AppStep, API, Repository, ViewModel 구현. +- 기존 UI 리팩터링과 공용 컴포넌트 추출. +- 신규 의존성 추가와 프로젝트 설정 변경. + +## 기술적 제약 + +- 기술 스택: SwiftUI, 기존 `I18n`, 기존 asset catalog. +- 아키텍처: 기존 view 파일에 최소 조건부 UI만 추가한다. +- 데이터·보안: 신규 네트워크 요청과 민감정보 기록 없음. +- 검증: 테스트 번들 타깃이 확인되지 않는 저장소 상태를 고려해 빌드와 수동 UI 매트릭스를 사용한다. +- 구현: 자동 테스트가 현실적으로 어려운 SwiftUI 표시 변경이므로 각 구현 Task에 TDD 예외 사유와 대체 검증 방법을 명시한다. + +## Phase 1 + +**Phase 결과:** 크리에이터 상세와 마이 탭에 선물 진입점 UI가 추가되고, 후속 연결 전까지 탭 동작은 inert 상태로 유지된다. + +**선행조건:** PRD 확정. + +**Phase 완료 조건:** `P1-T1`~`P1-T4`와 `P1-GATE` 완료, 검증 기록 누적. + +### 구현 항목 + +#### Task 1.1 문서 기준 확정 + +**Goal 실행 `P1-T1`:** 구현 전 PRD와 plan-task에 요구사항, 제외 범위, 검증 기준을 확정한다. + +- **시작 조건:** 사용자 승인 완료. +- **완료 증거:** `prd.md`, `plan-task.md` 생성. +- **범위 밖:** production code 변경. +- **TDD 예외 사유:** 문서 작성 Task로 production 동작을 변경하지 않는다. +- **대체 검증 방법:** 샘플 문서 구조와 경로 규칙을 대조한다. + +**Files:** + +- Create: `docs/20261006_선물_UI_추가/prd.md` +- Create: `docs/20261006_선물_UI_추가/plan-task.md` + +- [x] 요구사항과 성공 기준을 PRD에 기록한다. +- [x] 구현 대상 파일과 검증 기준을 plan-task에 기록한다. + +#### Task 1.2 I18n 문자열 추가 + +**Goal 실행 `P1-T2`:** 새 버튼 텍스트를 기존 `I18n` 구조에서 사용할 수 있게 한다. + +- **시작 조건:** `P1-T1` 완료. +- **완료 증거:** `I18n.CreatorChannelHome.sendGift`, `I18n.MyPage.Category.giftBox` 추가. +- **범위 밖:** String Catalog 전환. +- **TDD 예외 사유:** 정적 문자열 노출 추가로 테스트 타깃이 확인되지 않는다. +- **대체 검증 방법:** 컴파일과 실제 UI 렌더링에서 문자열 참조가 성공하는지 확인한다. + +**Files:** + +- Modify: `SodaLive/Sources/I18n/I18n.swift` + +- [x] `CreatorChannelHome`에 `선물 보내기` 문자열을 추가한다. +- [x] `MyPage.Category`에 `선물함` 문자열을 추가한다. +- [x] 기존 `pick(ko:en:ja:)` 패턴을 유지한다. + +#### Task 1.3 크리에이터 상세 선물 버튼 추가 + +**Goal 실행 `P1-T3`:** 크리에이터 상세에서 조건에 맞는 사용자에게 `선물 보내기` 버튼을 표시한다. + +- **시작 조건:** `P1-T2` 완료. +- **완료 증거:** 조건부 버튼 구현과 수동 표시 매트릭스 통과. +- **범위 밖:** 실제 선물 보내기 action, 라우팅, API. +- **TDD 예외 사유:** SwiftUI view의 조건부 표시 변경이며 현재 테스트 번들 타깃이 확인되지 않는다. +- **대체 검증 방법:** 아래 매트릭스를 수동 확인하고 Debug build로 컴파일을 검증한다. + +**Files:** + +- Modify: `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelTalkActionSection.swift` + +**표시 매트릭스:** + +| `isOwnCreatorChannel` | `isDmAvailable` | `countryCode` | `LanguageHeaderProvider.current` | 기대 | +|---|---|---|---|---| +| `false` | `true` | `""` | `"ko"` | 표시 | +| `false` | `true` | `"KR"` | `"ko"` | 표시 | +| `false` | `true` | `"kr"` | `"ko"` | 표시 | +| `true` | `true` | `"KR"` | `"ko"` | 숨김 | +| `false` | `false` | `"KR"` | `"ko"` | 숨김 | +| `false` | `true` | `"JP"` | `"ko"` | 숨김 | +| `false` | `true` | `"KR"` | `"ja"` | 숨김 | + +- [x] 기존 `DM 보내기` 버튼 오른쪽에 같은 스타일의 `선물 보내기` 버튼을 추가한다. +- [x] 노출 조건은 `isOwnCreatorChannel == false && isDmAvailable && isKoreanCountry && LanguageHeaderProvider.current == "ko"`로 제한한다. +- [x] action은 빈 클로저로 둔다. + +#### Task 1.5 크리에이터 상세 선물 버튼 후속 수정 + +**Goal 실행 `P1-T5`:** 크리에이터 상세 `선물 보내기` 버튼에서 아이콘을 제거하고 타인 채널에서만 표시한다. + +- **시작 조건:** 사용자 추가 요청. +- **완료 증거:** `CreatorChannelTalkActionSection`의 조건과 버튼 content 수정, 빌드 검증. +- **범위 밖:** 마이 탭 `선물함` 아이콘 제거, 신규 라우팅/API. +- **TDD 예외 사유:** SwiftUI view의 조건부 표시 변경이며 현재 테스트 번들 타깃이 확인되지 않는다. +- **대체 검증 방법:** 코드 조건 확인과 Debug build로 컴파일을 검증한다. + +**Files:** + +- Modify: `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelTalkActionSection.swift` +- Modify: `docs/20261006_선물_UI_추가/prd.md` +- Modify: `docs/20261006_선물_UI_추가/plan-task.md` + +- [x] `선물 보내기` 버튼에서 `ic_my_present` 이미지를 제거한다. +- [x] `선물 보내기` 버튼 노출 조건에 `isOwnCreatorChannel == false`를 추가한다. + +#### Task 1.4 마이 탭 선물함 버튼 추가 + +**Goal 실행 `P1-T4`:** 마이 탭 카테고리 그리드 맨 앞에 `선물함` 버튼을 추가한다. + +- **시작 조건:** `P1-T2` 완료. +- **완료 증거:** `CategoryButtonsView` 첫 항목 추가와 4열 유지 확인. +- **범위 밖:** 선물함 화면 이동, API, 신규 AppStep. +- **TDD 예외 사유:** SwiftUI view의 정적 항목 추가이며 현재 테스트 번들 타깃이 확인되지 않는다. +- **대체 검증 방법:** 코드 순서와 화면 렌더링에서 첫 항목/아이콘/4열을 확인한다. + +**Files:** + +- Modify: `SodaLive/Sources/MyPage/MyPageView.swift` + +- [x] `CategoryButtonsView`의 첫 번째 `CategoryButtonItem`으로 `ic_my_present`와 `I18n.MyPage.Category.giftBox`를 추가한다. +- [x] 기존 4열 `LazyVGrid` 설정을 변경하지 않는다. +- [x] action은 빈 클로저로 둔다. + +### 완료 조건 + +- [x] `P1-T1`~`P1-T5`가 완료됐다. +- [x] 구현 범위가 PRD의 제외 범위를 넘지 않는다. +- [x] 검증 기록이 누적됐다. + +### 검증 방법 + +#### Phase 1 Gate + +**Goal 실행 `P1-GATE`:** 선물 UI 진입점 추가의 빌드와 사용자 관찰 기준을 최종 판정한다. + +```bash +xcodebuild -workspace "SodaLive.xcworkspace" -list +xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build +xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build +``` + +**Expected:** 명령 exit code 0. `선물 보내기`와 `선물함` 표시 조건이 PRD 성공 기준과 일치한다. + +수동 검증: + +- [x] 크리에이터 상세에서 `DM 보내기` 오른쪽에 `선물 보내기`가 표시된다. 코드 기준: `CreatorChannelTalkActionSection`에서 `if shouldShowGiftButton`이 `if isDmAvailable` 블록 다음에 위치한다. +- [x] 내 채널과 한국 접속/한국어 설정 외 조건에서는 `선물 보내기`가 숨겨진다. 코드 기준: `isOwnCreatorChannel == false && isDmAvailable && isKoreanCountry && LanguageHeaderProvider.current == "ko"`. +- [x] 크리에이터 상세 `선물 보내기` 버튼은 아이콘 없이 텍스트만 표시한다. 코드 기준: 버튼 content에 `Image("ic_my_present")` 없음. +- [x] 마이 탭 카테고리 그리드 첫 항목에 `선물함`이 표시된다. 코드 기준: `CategoryButtonsView`의 첫 `CategoryButtonItem`. +- [x] 새 버튼 탭으로 화면 이동이나 네트워크 요청이 발생하지 않는다. 코드 기준: 두 action 모두 빈 클로저. + +## 실행 순서와 의존성 + +| 순서 | Goal | 선행조건 | 병행 가능 | 차단 시 다음 행동 | +|---:|---|---|---|---| +| 1 | `P1-T1` | 없음 | 아니요 | 요구사항 재확인 | +| 2 | `P1-T2` | `P1-T1` | 아니요 | I18n 패턴 재확인 | +| 3 | `P1-T3` | `P1-T2` | `P1-T4`와 가능 | 조건 매트릭스 재확인 | +| 4 | `P1-T4` | `P1-T2` | `P1-T3`와 가능 | 그리드 순서 재확인 | +| 5 | `P1-GATE` | Task 전체 | 아니요 | 실패 Task로 회귀 수정 | + +```text +P1-T1 -> P1-T2 -> P1-T3/P1-T4 -> P1-GATE +``` + +## 변경 금지 항목 + +- 신규 AppStep, 화면, API, ViewModel, Repository를 만들지 않는다. +- `DM 보내기` 기존 동작을 변경하지 않는다. +- `CategoryButtonsView`의 4열 설정을 변경하지 않는다. +- 사용자 또는 다른 작업자가 추가한 asset 파일을 수정하지 않는다. + +## Progress + +| 일시 | 항목 | 기록 | +|---|---|---| +| 2026-10-06 | `P1-T1` | PRD와 plan-task 생성. | +| 2026-10-06 | `P1-T2` | `I18n.CreatorChannelHome.sendGift`, `I18n.MyPage.Category.giftBox` 추가. | +| 2026-10-06 | `P1-T3` | `CreatorChannelTalkActionSection`에 `isDmAvailable`, 한국 접속, 한국어 설정 조건의 `선물 보내기` 버튼 추가. | +| 2026-10-06 | `P1-T4` | `CategoryButtonsView` 첫 항목으로 `ic_my_present` 기반 `선물함` 버튼 추가. | +| 2026-10-06 | `P1-T5` | 크리에이터 상세 `선물 보내기` 버튼에서 아이콘 제거, 타인 채널 조건 추가. | +| 2026-10-06 | `P1-GATE` | `xcodebuild -workspace "SodaLive.xcworkspace" -list` 성공. 스킴 `SodaLive`, `SodaLive-dev` 확인. Xcode 기기 build number 경고는 있었으나 명령은 성공. | +| 2026-10-06 | `P1-GATE` | `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build` 성공. 기존 중복 Compile Sources 등 경고는 유지. | +| 2026-10-06 | `P1-GATE` | `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 성공. 일부 dependency scan 경고는 유지. | +| 2026-10-06 | `P1-GATE` | `git diff --check` 성공. | +| 2026-10-06 | `P1-GATE` | iOS 실기기/시뮬레이터 화면 캡처 기반 visual QA는 현재 실행 환경에 렌더링/캡처 도구가 없어 미수행. 대신 변경 파일 직접 확인과 빌드 검증으로 대체. | +| 2026-10-06 | `P1-T5` | `git diff --check` 성공. | +| 2026-10-06 | `P1-T5` | `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 성공. | +| 2026-10-06 | `P1-T5` | `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build` 최초 병렬 실행은 DerivedData `build.db` 잠금으로 실패했고, 단독 재실행은 성공. | diff --git a/docs/20261006_선물_UI_추가/prd.md b/docs/20261006_선물_UI_추가/prd.md new file mode 100644 index 00000000..68bd147f --- /dev/null +++ b/docs/20261006_선물_UI_추가/prd.md @@ -0,0 +1,151 @@ +# 선물 UI 추가 PRD + +## 문서 정보 + +| 항목 | 내용 | +|---|---| +| 문서 상태 | 구현 완료 | +| 작성일 | `2026-10-06` | +| 최종 수정일 | `2026-10-06` | +| 대상 제품 | 크리에이터 상세 선물 버튼, 마이 탭 선물함 버튼 | +| 작성자·결정권자 | 사용자 요청 기준 | +| 관련 API Contract | 해당 없음 | +| 관련 구현 계획 | `docs/20261006_선물_UI_추가/plan-task.md` | +| 관련 review | 없음 | + +## 1. Overview + +한국에서 접속하고 앱 언어가 한국어인 사용자가 크리에이터 상세와 마이 탭에서 선물 관련 진입점을 볼 수 있게 한다. 이번 범위는 UI 진입점 추가까지만 포함하며 실제 선물 보내기/선물함 화면 이동은 후속 작업에서 연결한다. + +## 2. Problem Statement + +- 선물 기능 연결 전에도 사용자가 진입점을 인지할 수 있는 UI가 필요하다. +- 크리에이터 상세의 선물 진입점은 현재 `DM 보내기`와 같은 레벨의 액션으로 보여야 한다. +- 마이 탭의 선물함은 기존 보관함, 차단목록 등 카테고리 버튼과 같은 UI 레벨에 있어야 한다. + +문제를 해결했다는 판단은 조건에 맞는 사용자에게 두 진입점이 기존 UI 규칙대로 노출되고, 탭해도 아직 별도 기능으로 이동하지 않는 것으로 한다. + +## 3. Goals + +### 3.1 제품 목표 + +- 크리에이터 상세에서 `DM 보내기` 노출 조건과 동일한 크리에이터에 대해 한국 접속, 한국어 설정, 타인 채널 조건일 때 `선물 보내기` 버튼을 표시한다. +- 마이 탭 카테고리 그리드의 첫 항목으로 `선물함` 버튼을 표시한다. +- 후속 기능 연결을 위해 탭 액션 위치만 남기고 실제 화면 이동/API 호출은 하지 않는다. + +### 3.2 UX 목표 + +- `선물 보내기` 버튼은 `DM 보내기`와 같은 형태, 간격, 텍스트 스타일을 사용한다. +- `선물함` 버튼은 기존 카테고리 버튼과 같은 4열 그리드, 아이콘 박스, 텍스트 스타일을 사용한다. + +## 4. Non-Goals + +- 선물 보내기 화면, 선물함 화면, 라우팅, API, 데이터 모델 구현은 제외한다. +- 신규 디자인 시스템, 공용 컴포넌트 추출, 기존 버튼 리팩터링은 제외한다. +- 새 의존성 추가와 프로젝트 설정 변경은 제외한다. + +## 5. Target Users and Permissions + +| 사용자 | 목표 | 주요 작업 | 사용 환경 | +|---|---|---|---| +| 한국 접속 및 한국어 설정 사용자 | 크리에이터에게 선물하거나 선물함을 확인할 진입점을 인지 | 버튼 확인 및 탭 | iOS 앱 | + +권한과 인증 흐름은 이번 범위에서 변경하지 않는다. `선물 보내기` 버튼 노출은 기존 `DM 보내기` 노출 조건인 `creator.isDmAvailable`을 재사용한다. + +## 6. 핵심 사용자 흐름 + +1. 사용자가 크리에이터 상세에 진입한다. +2. 앱이 내 채널 여부, `creator.isDmAvailable`, 접속 국가, 앱 언어를 기준으로 `선물 보내기` 버튼 노출 여부를 결정한다. +3. 조건이 맞으면 `DM 보내기` 오른쪽에 같은 형태의 `선물 보내기` 버튼이 보인다. +4. 로그인 사용자가 마이 탭에 진입하면 카테고리 그리드 첫 항목에 `선물함` 버튼이 보인다. +5. 두 버튼의 탭 액션은 후속 구현 전까지 아무 동작도 하지 않는다. + +## 7. 정보 구조와 라우팅 + +```text +크리에이터 상세 헤더 액션 영역 + DM 보내기 + 선물 보내기 + +마이 탭 카테고리 그리드 + 선물함 + 보관함 + 차단목록 + ...기존 항목 +``` + +신규 route는 만들지 않는다. + +## 8. 기능 요구사항 + +| ID | 상태 | 요구사항 | 수용 기준 | 계약/Goal 연결 | +|---|---|---|---|---| +| `GIFT-001` | 확정 | 크리에이터 상세에 `선물 보내기` 버튼을 추가한다. | 내 채널이 아니고 `creator.isDmAvailable == true`, 한국 접속, `LanguageHeaderProvider.current == "ko"`일 때만 `DM 보내기` 오른쪽에 표시된다. | `P1-T2` | +| `GIFT-002` | 확정 | 한국 접속 판정은 기존 패턴을 사용한다. | `UserDefaults.countryCode`를 trim/uppercase 후 빈 값 또는 `KR`이면 한국 접속으로 본다. | `P1-T2` | +| `GIFT-003` | 확정 | 한국어 설정 판정은 기존 언어 헤더를 사용한다. | `LanguageHeaderProvider.current == "ko"`일 때 한국어 설정으로 본다. | `P1-T2` | +| `GIFT-004` | 확정 | 마이 탭 카테고리 그리드 첫 항목에 `선물함`을 추가한다. | 4열 설정은 유지되고, `ic_my_present` 아이콘과 `선물함` 텍스트가 보관함 앞에 표시된다. | `P1-T3` | +| `GIFT-005` | 확정 | 두 버튼의 탭 액션은 후속 구현 전까지 inert placeholder로 둔다. | 탭해도 화면 이동, API 호출, 상태 변경이 없다. | `P1-T2`, `P1-T3` | +| `GIFT-006` | 확정 | 크리에이터 상세의 `선물 보내기` 버튼은 아이콘 없이 텍스트만 표시한다. | 캡슐 스타일은 유지하되 `ic_my_present` 등 아이콘을 포함하지 않는다. | `P1-T2` | + +## 9. 반응형 기능 범위 + +| 기능 | iPhone | iPad | 비고 | +|---|---:|---:|---| +| 크리에이터 상세 버튼 | 전체 | 전체 | 기존 헤더 액션 레이아웃 유지 | +| 마이 탭 선물함 | 전체 | 전체 | 기존 4열 그리드 유지 | + +## 10. UI/UX Expectations + +- 크리에이터 상세 버튼은 `CreatorChannelTalkActionSection`의 기존 캡슐 버튼 스타일을 그대로 따른다. +- 마이 탭 버튼은 `CategoryButtonItem`을 그대로 사용한다. +- 새 아이콘은 기존 asset `ic_my_present`를 사용한다. +- 새 텍스트는 `I18n`에 추가해 기존 다국어 선택 방식을 따른다. + +## 11. API 계약 + +해당 없음. 신규 API 요청, 응답, 에러 계약은 이번 범위에 없다. + +## 12. 보안과 데이터 취급 + +- 인증 토큰, 사용자 정보, 선물 데이터 저장 로직을 변경하지 않는다. +- 버튼 탭으로 네트워크 요청을 발생시키지 않는다. + +## 13. 성능과 품질 요구사항 + +- 신규 의존성을 추가하지 않는다. +- 기존 SwiftUI view 구조에서 조건부 렌더링만 추가한다. +- `xcodebuild` Debug build를 기본 검증으로 사용한다. + +## 14. 성공 기준 + +### 14.1 기능 수용 기준 + +- [ ] `DM 보내기`가 보이는 타인 크리에이터 채널에서 한국 접속 및 한국어 설정이면 `선물 보내기`도 보인다. +- [ ] 내 채널에서는 `선물 보내기`가 보이지 않는다. +- [ ] `creator.isDmAvailable == false`이면 `선물 보내기`가 보이지 않는다. +- [ ] 한국 접속이 아니거나 한국어 설정이 아니면 `선물 보내기`가 보이지 않는다. +- [ ] 마이 탭 카테고리 그리드 첫 항목에 `선물함`이 보인다. +- [ ] 두 새 버튼을 탭해도 화면 이동/API 호출이 없다. + +### 14.2 UI/UX 수용 기준 + +- [ ] `선물 보내기`는 아이콘 없이 `DM 보내기`와 같은 캡슐 형태로 표시된다. +- [ ] `선물함`은 기존 카테고리 버튼과 같은 형태로 표시된다. +- [ ] 한 줄에 보이는 카테고리 개수는 기존 4열 설정 그대로다. + +### 14.3 추적성 완료 기준 + +- [ ] 모든 확정 요구사항이 `plan-task.md`의 Task와 연결된다. +- [ ] 검증 결과가 `plan-task.md`에 누적된다. + +## 15. Open Questions + +없음. + +## Decision Log + +| 일시 | 결정 | 근거 | +|---|---|---| +| 2026-10-06 | `선물 보내기`는 `DM 보내기` 노출 조건인 `isDmAvailable`을 재사용한다. | 사용자 답변: “해당 조건과 동일하게 설정하면 된다.” | +| 2026-10-06 | 한국 접속은 기존 `countryCode` 판정, 한국어 설정은 `LanguageHeaderProvider.current == "ko"`를 사용한다. | 기존 코드 검색 결과 `isKoreanCountry` 패턴과 언어 헤더 구조 확인 | +| 2026-10-06 | 크리에이터 상세 `선물 보내기`는 아이콘 없이 타인 채널에서만 표시한다. | 사용자 추가 요청 |