187 lines
13 KiB
Markdown
187 lines
13 KiB
Markdown
# PRD: 메인 페이지 신규 개발
|
|
|
|
## 1. Overview
|
|
|
|
앱 진입 후 표시되는 메인 페이지를 하단 내비게이션 4개 탭 구조로 정리한다. 탭은 `홈`, `콘텐츠`, `채팅`, `마이` 순서로 제공하며, `마이`는 기존 `MyPageView`를 재사용하고 나머지 탭은 신규 빈 페이지로 시작한다.
|
|
|
|
---
|
|
|
|
## 2. Background
|
|
|
|
현재 앱의 전역 진입 흐름은 `SodaLiveApp` → `ContentView` → `HomeView` 구조다. `ContentView`는 `NavigationStack(path: $appState.navigationPath)`로 전역 화면 전환을 관리하고, `.main` 상태에서는 별도 화면을 push하지 않고 항상 `HomeView()`를 루트 화면으로 노출한다.
|
|
|
|
현재 메인 탭 관련 구현은 아래 파일에 분산되어 있다.
|
|
|
|
- `SodaLive/Sources/Main/Home/HomeView.swift`: 현재 메인 루트 컨테이너와 미니 플레이어, 하단 탭 포함
|
|
- `SodaLive/Sources/Main/Home/HomeViewModel.swift`: 현재 탭 상태 `CurrentTab = home, chat, live, mypage`
|
|
- `SodaLive/Sources/Main/Home/BottomTabView.swift`: 하단 탭 4개 UI
|
|
- `SodaLive/Sources/Main/Home/TabButton.swift`: 탭 버튼 공용 UI
|
|
- `SodaLive/Sources/MyPage/MyPageView.swift`: 신규 `마이` 탭에서 재사용할 기존 마이페이지
|
|
|
|
---
|
|
|
|
## 3. Goals
|
|
|
|
- 메인 페이지가 하단 내비게이션 4개 탭을 제공한다.
|
|
- 탭 순서는 `홈`, `콘텐츠`, `채팅`, `마이`로 고정한다.
|
|
- 탭 선택 시 각 탭에 대응하는 페이지가 표시된다.
|
|
- `마이` 탭은 기존 `SodaLive/Sources/MyPage/MyPageView.swift`를 그대로 표시한다.
|
|
- `홈`, `콘텐츠`, `채팅` 탭은 신규 빈 페이지를 만들어 표시한다.
|
|
- 하단 내비게이션 아이콘은 요청된 `ic_nav_*` 계열을 사용한다.
|
|
- 현재 없는 `ic_nav_my_selected`는 임시로 `ic_tabbar_my_selected`를 사용한다.
|
|
- 후속 요구사항(2026-07-20): 크리에이터 계정으로 앱을 사용할 때 메인 `홈` 탭에서 라이브 생성, 콘텐츠 등록, 커뮤니티 게시글 등록으로 진입할 수 있는 plus floating menu를 제공한다.
|
|
- 메인 `홈` 탭의 plus 아이콘, 펼침 UI, 생성 액션은 내 채널 홈 탭의 `CreatorChannelFloatingActionMenu`와 동일하게 동작한다.
|
|
|
|
---
|
|
|
|
## 4. Non-Goals
|
|
|
|
- 신규 `홈`, `콘텐츠`, `채팅` 탭의 실제 콘텐츠 구현은 하지 않는다.
|
|
- 기존 마이페이지 내부 UI/비즈니스 로직은 변경하지 않는다.
|
|
- 전역 라우팅 구조(`AppState`, `AppStep`, `ContentView`)의 대규모 개편은 하지 않는다.
|
|
- 미니 플레이어, 팝업, 본인인증, 딥링크 등 기존 메인 주변 기능의 동작 변경은 하지 않는다.
|
|
- 누락된 실제 이미지 리소스 `ic_nav_my_selected`를 새로 제작하거나 추가하지 않는다.
|
|
- 라이브 생성, 콘텐츠 등록, 커뮤니티 게시글 등록 화면과 API를 새로 구현하지 않는다.
|
|
- 일반 사용자 계정에는 생성용 plus floating menu를 표시하지 않는다.
|
|
|
|
---
|
|
|
|
## 5. Core Requirements
|
|
|
|
### 5.1 하단 내비게이션 탭
|
|
|
|
하단 내비게이션은 4개 탭으로 구성한다.
|
|
|
|
| 순서 | 탭 | 표시 페이지 | 구현 상태 |
|
|
| --- | --- | --- | --- |
|
|
| 1 | 홈 | 신규 빈 페이지 | 신규 생성 |
|
|
| 2 | 콘텐츠 | 신규 빈 페이지 | 신규 생성 |
|
|
| 3 | 채팅 | 신규 빈 페이지 | 신규 생성 |
|
|
| 4 | 마이 | `MyPageView` | 기존 재사용 |
|
|
|
|
### 5.2 탭 선택 상태
|
|
|
|
탭을 선택하면 선택된 탭의 페이지를 메인 콘텐츠 영역에 표시한다. 선택된 탭과 선택되지 않은 탭의 아이콘은 아래 매핑을 따른다.
|
|
|
|
| 탭 | 선택 아이콘 | 미선택 아이콘 |
|
|
| --- | --- | --- |
|
|
| 홈 | `ic_nav_home_selected` | `ic_nav_home` |
|
|
| 콘텐츠 | `ic_nav_content_selected` | `ic_nav_content` |
|
|
| 채팅 | `ic_nav_chat_selected` | `ic_nav_chat` |
|
|
| 마이 | `ic_tabbar_my_selected` 임시 사용 | `ic_nav_my` |
|
|
|
|
`ic_nav_my_selected` 리소스가 추가되면 `마이` 탭 선택 아이콘은 `ic_tabbar_my_selected`에서 `ic_nav_my_selected`로 교체한다.
|
|
|
|
### 5.3 탭 버튼 정렬
|
|
|
|
탭 버튼의 아이콘과 타이틀 정렬 우선순위는 아래 기준을 따른다.
|
|
|
|
- 가로 정렬 1순위: 탭 버튼 내부 콘텐츠는 가운데 정렬한다.
|
|
- 세로 정렬 1순위: 아이콘과 타이틀 묶음은 가운데 정렬한다.
|
|
- 세로 정렬 2순위: 탭별 타이틀의 하단 라인을 맞춘다.
|
|
|
|
구현 시 탭 버튼별 콘텐츠 높이가 달라도 타이틀 기준선이 흔들리지 않도록 아이콘 영역 높이와 타이틀 영역 높이를 명확히 분리한다.
|
|
|
|
### 5.4 크리에이터용 생성 Floating Menu
|
|
|
|
- 크리에이터 여부는 기존 앱의 역할 판별 기준인 `UserDefaults.string(forKey: .role) == MemberRole.CREATOR.rawValue`를 사용한다.
|
|
- 크리에이터이고 메인 하단 탭이 `.home`일 때만 생성용 plus floating menu를 표시한다.
|
|
- 메인 홈 내부의 `추천`, `랭킹`, `팔로잉` 선택과 관계없이 메인 하단 탭이 `.home`인 동안 동일한 floating menu를 유지한다.
|
|
- 일반 사용자이거나 메인 하단 탭이 `.content`, `.chat`, `.my`이면 floating menu와 dim overlay를 표시하지 않는다.
|
|
- 닫힘 상태 plus 버튼, 열림 상태 dim, 버튼 순서, spring animation은 내 채널 홈 탭의 `CreatorChannelFloatingActionMenu`와 동일하게 사용한다.
|
|
- 닫힘 상태 아이콘은 `ic_plus_no_bg`를 사용한다.
|
|
- 열림 상태의 버튼은 위에서부터 아래 순서로 다음과 같이 표시한다.
|
|
1. 커뮤니티 게시글 등록: `ic_new_upload_community_post`
|
|
2. 콘텐츠 등록: `ic_new_upload_audio`
|
|
3. 라이브 생성: `ic_new_create_live`
|
|
4. 닫기: `ic_new_x_black`
|
|
- 각 버튼은 내 채널 홈 탭과 동일한 기존 action을 사용한다.
|
|
- 커뮤니티 게시글 등록: `AppStep.creatorCommunityWrite(onSuccess:)`
|
|
- 콘텐츠 등록: `AppStep.createContent(onSuccess:)`
|
|
- 라이브 생성: `AppStep.createLive(timeSettingMode: .NOW, onSuccess:)`
|
|
- 라이브 생성 성공 시 기존 내 채널 홈과 `LiveView.onCreateSuccess`가 사용하는 라이브 메인 갱신 및 생성된 room 입장 흐름을 유지한다.
|
|
- 액션 선택, 닫기 버튼 탭, dim 영역 탭 시 menu를 닫는 동작은 내 채널 홈과 동일하게 유지한다.
|
|
- 메인 미니 플레이어와 하단 탭바가 표시될 때 floating menu가 겹치지 않도록 기존 `MainView`의 overlay와 safe area 구조 안에서 배치한다.
|
|
|
|
---
|
|
|
|
## 6. Recommended Approach
|
|
|
|
### 접근안 A: 기존 `HomeView`/`BottomTabView` 구조를 최소 변경으로 확장
|
|
|
|
현재 앱 구조와 가장 잘 맞는다. `HomeView`가 이미 메인 루트 컨테이너 역할을 하고 있고, `BottomTabView`가 4개 탭을 `width / 4`로 고정 렌더링하고 있으므로 신규 요구사항을 반영하기 쉽다.
|
|
|
|
장점은 변경 범위가 작고 기존 미니 플레이어, 전역 팝업, `AppState.startTab` 연동을 유지하기 쉽다는 점이다. 단점은 현재 `HomeView`가 이미 많은 책임을 갖고 있어, 구현 시 탭 콘텐츠 분리 경계를 문서대로 지키지 않으면 파일이 더 커질 수 있다.
|
|
|
|
### 접근안 B: 신규 `MainView` 컨테이너를 만들고 `ContentView`에서 교체
|
|
|
|
신규 메인 페이지의 의미가 명확해진다. 기존 `HomeView`를 직접 확장하지 않고 `MainView`, `MainTab`, `MainTabBarView` 등으로 책임을 분리할 수 있다.
|
|
|
|
장점은 구조가 명확하고 새 요구사항을 독립적으로 구현하기 좋다는 점이다. 단점은 기존 `HomeView`에 포함된 미니 플레이어, 팝업, 인증, 이벤트 팝업 흐름을 이관해야 하므로 문서 단계의 요구사항보다 구현 영향이 커질 수 있다.
|
|
|
|
### 접근안 C: SwiftUI 기본 `TabView(selection:)` 기반으로 전환
|
|
|
|
SwiftUI 표준 탭 내비게이션 방식이다. 탭별 독립 상태 관리와 접근성 측면에서 유리하다.
|
|
|
|
단, 현재 프로젝트는 커스텀 하단 바와 전역 `NavigationStack`/`AppStep` 구조를 이미 사용하고 있으므로 기본 `TabView` 전환은 기존 UI/안전영역/미니 플레이어 연동 방식과 충돌할 수 있다.
|
|
|
|
### 권장
|
|
|
|
접근안 B로 결정한다. 새 요구사항을 기존 `HomeView`에 덧붙이기보다 신규 `MainView` 컨테이너에서 독립적으로 구현한 뒤, 기존 `HomeView`에 있던 미니 플레이어, 인증, 팝업 등 메인 주변 기능을 필요한 범위만 이관하는 방향이 낫다고 판단한다. 구현 계획 단계에서는 신규 파일을 `SodaLive/Sources/V2/Main/**` 아래에 작성하고, `ContentView`의 루트 렌더링 지점에서 신규 메인 컨테이너를 사용하도록 연결한다.
|
|
|
|
---
|
|
|
|
## 7. Technical Constraints
|
|
|
|
- `AppStep.main`은 기존처럼 메인 복귀 의미로 유지한다.
|
|
- `ContentView`의 전역 `NavigationStack` 구조와 `AppStepLayerView` 매핑은 변경하지 않는다.
|
|
- 현재 `HomeViewModel.CurrentTab`에는 `content`가 없고 `live`가 있으므로, 구현 시 탭 enum을 `home, content, chat, mypage`로 변경하거나 신규 메인 탭 enum을 도입해야 한다.
|
|
- `I18n.Main.Tab`에는 현재 `home`, `live`, `chat`, `my`만 있으므로 `content` 라벨을 추가해야 한다.
|
|
- `ic_nav_my_selected`는 현재 에셋에 없으므로 구현 시 `ic_tabbar_my_selected`를 임시 사용한다.
|
|
- 신규로 생성되는 메인 페이지 관련 파일은 모두 `SodaLive/Sources/V2/Main/**` 아래에 둔다.
|
|
- 두 페이지에서 재사용하는 생성 floating menu는 `SodaLive/Sources/V2/Component/Button/**` 아래 공용 컴포넌트로 이동하고, 내 채널 홈과 메인 홈이 같은 구현을 사용한다.
|
|
- 기존 `AppStep.creatorCommunityWrite`, `AppStep.createContent`, `AppStep.createLive` case와 각 작성/생성 화면은 변경하지 않는다.
|
|
|
|
---
|
|
|
|
## 8. Success Criteria
|
|
|
|
- 문서 기준 구현 후 앱 메인 화면에 하단 탭 4개가 표시된다.
|
|
- 탭 순서가 `홈`, `콘텐츠`, `채팅`, `마이`와 일치한다.
|
|
- 각 탭 선택 시 대응 페이지가 표시된다.
|
|
- `마이` 탭 선택 시 기존 `MyPageView`가 표시된다.
|
|
- `홈`, `콘텐츠`, `채팅` 탭 선택 시 빈 페이지가 표시된다.
|
|
- 탭 아이콘 선택/미선택 매핑이 요구사항과 일치한다.
|
|
- `마이` 선택 아이콘은 임시로 `ic_tabbar_my_selected`를 사용한다.
|
|
- 탭 버튼 내부 콘텐츠는 가로/세로 가운데 정렬을 우선하고, 타이틀 하단 라인이 맞는다.
|
|
- 크리에이터 계정의 메인 `홈` 탭에는 내 채널 홈과 동일한 plus floating menu가 표시된다.
|
|
- 일반 사용자 계정과 메인 `홈` 이외 탭에는 생성용 plus floating menu가 표시되지 않는다.
|
|
- Plus 버튼을 펼치면 커뮤니티 게시글 등록, 콘텐츠 등록, 라이브 생성, 닫기 버튼이 내 채널 홈과 동일한 아이콘과 순서로 표시된다.
|
|
- 세 생성 버튼은 각각 기존 커뮤니티 게시글 등록, 콘텐츠 등록, 즉시 라이브 생성 화면으로 이동한다.
|
|
|
|
---
|
|
|
|
## 9. Decisions
|
|
|
|
- 신규 빈 페이지는 검정 배경을 사용하고 탭명만 표시한다.
|
|
- 신규로 생성되는 파일은 모두 `SodaLive/Sources/V2/Main/**` 아래에 둔다.
|
|
- 메인 컨테이너는 접근안 B에 따라 신규 구현 후 기존 미니 플레이어, 인증, 팝업 등 필요한 주변 기능을 이관한다.
|
|
- 크리에이터용 생성 메뉴는 내 채널 홈의 UI와 action을 복제하지 않고 공용 컴포넌트와 기존 `AppStep` route를 재사용한다.
|
|
- 메인 홈에서의 크리에이터 판별은 별도 API 없이 저장된 `MemberRole.CREATOR` 값을 사용한다.
|
|
|
|
---
|
|
|
|
## 10. Verification Notes
|
|
|
|
- 문서 작성 전 `SodaLive/Sources/MyPage/MyPageView.swift`를 확인해 `마이` 탭 재사용 대상을 검증했다.
|
|
- `SodaLive/Sources/Main/Home/HomeView.swift`, `HomeViewModel.swift`, `BottomTabView.swift`, `TabButton.swift`를 확인해 현재 메인/탭 구조를 검증했다.
|
|
- `SodaLive/Sources/App/AppState.swift`, `AppStep.swift`, `ContentView.swift`를 확인해 `.main` 라우팅과 전역 내비게이션 구조를 검증했다.
|
|
- `SodaLive/Resources/Assets.xcassets` 검색으로 `ic_nav_home(_selected)`, `ic_nav_content(_selected)`, `ic_nav_chat(_selected)`, `ic_nav_my`, `ic_tabbar_my_selected` 존재와 `ic_nav_my_selected` 부재를 확인했다.
|
|
- `SodaLive/Sources/I18n/I18n.swift`를 확인해 `I18n.Main.Tab.content`가 아직 없음을 확인했다.
|
|
- 2026-07-20: `SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelFloatingActionMenu.swift`와 `CreatorChannelView.swift`를 확인해 내 채널 홈의 plus 아이콘, 4개 메뉴 아이콘, spring animation, 커뮤니티 게시글 등록·콘텐츠 등록·즉시 라이브 생성 route를 확인했다.
|
|
- 2026-07-20: `SodaLive/Sources/MyPage/MyPageView.swift`와 `SodaLive/Sources/Extensions/UserDefaultsExtension.swift`를 확인해 기존 크리에이터 역할 판별 기준이 `UserDefaults.string(forKey: .role) == MemberRole.CREATOR.rawValue`임을 확인했다.
|
|
|
|
## 11. Open Questions
|
|
|
|
- 해당 없음.
|