feat(content): 추천 전체보기를 연결한다
This commit is contained in:
217
docs/20260707_메인_콘텐츠_추천_전체보기/plan-task.md
Normal file
217
docs/20260707_메인_콘텐츠_추천_전체보기/plan-task.md
Normal file
@@ -0,0 +1,217 @@
|
||||
# 메인 콘텐츠 추천 전체보기 구현 계획
|
||||
|
||||
## 기준 문서
|
||||
|
||||
- PRD: `docs/20260707_메인_콘텐츠_추천_전체보기/prd.md`
|
||||
- 문서 정책: `docs/agent-guides/documentation-policy.md`
|
||||
- 코드 스타일: `docs/agent-guides/code-style.md`
|
||||
- 빌드/검증: `docs/agent-guides/build-test-verification.md`
|
||||
|
||||
## 구현할 UI의 Figma URL
|
||||
|
||||
- New&Hot 전체보기 화면: `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=482-15105&m=dev`
|
||||
|
||||
## 확정 요구사항 요약
|
||||
|
||||
- `처음부터 함께 성장!` 섹션과 `FIRST_AUDIO_CONTENT` 전체보기 진입은 이번 구현 범위에서 제외한다.
|
||||
- `New&Hot` 전체보기는 탭 외부 전환이므로 `AppStep` route로 추가한다.
|
||||
- `New&Hot` 전체보기 API는 `GET /api/v2/contents`를 사용한다.
|
||||
- `New&Hot` 전체보기 기본 query는 `page=0`, `size=20`, `type=NEW_AND_HOT_AUDIO`이다.
|
||||
- `New&Hot` 전체보기 UI는 Figma 기준 2열 오디오 카드 grid와 back title bar를 제공한다.
|
||||
- `새로 올라온 오디오`, `오직 보이스온에서만`, `무료 오디오`, `포인트 오디오` 전체보기는 별도 route 없이 하단 `콘텐츠` 탭 내부 상단 `전체` 탭 전환으로 처리한다.
|
||||
- 기존 `SectionTitle(title:action:)` chevron 표시 기능과 `AudioContentThumbnailCard`를 우선 재사용한다.
|
||||
|
||||
## TASK 체크리스트
|
||||
|
||||
### Phase 0: 구현 전 문구 결정
|
||||
|
||||
- [ ] **Task 0.1: 신규 전체보기 실패/빈 목록 문구 확정**
|
||||
- 대상 파일:
|
||||
- 확인: `docs/20260707_메인_콘텐츠_추천_전체보기/prd.md`
|
||||
- 수정 후보: `SodaLive/Sources/I18n/I18n.swift`
|
||||
- 작업 내용:
|
||||
- PRD의 Open Questions에 남은 신규 API 실패 toast 문구와 empty state 문구를 확정한다.
|
||||
- 기존 문구를 재사용할 경우 어떤 namespace의 문구를 재사용할지 명시한다.
|
||||
- 신규 문구가 필요하면 `I18n`에 추가할 key와 ko/en/ja 문구를 확정한다.
|
||||
- 검증 기준:
|
||||
- PRD의 Open Questions가 해소되거나, 구현 계획에 사용할 문구 source가 명확히 기록된다.
|
||||
|
||||
### Phase 1: New&Hot 전체보기 API 모델과 repository
|
||||
|
||||
- [ ] **Task 1.1: ContentOverview API response 모델 추가**
|
||||
- 대상 파일:
|
||||
- 생성: `SodaLive/Sources/V2/Main/Content/Overview/Models/ContentOverviewPageResponse.swift`
|
||||
- 작업 내용:
|
||||
- `ContentOverviewType` enum을 추가한다.
|
||||
- 이번 범위에서는 `NEW_AND_HOT_AUDIO`만 case로 둔다.
|
||||
- `ContentOverviewPageResponse`, `ContentOverviewItemResponse`를 추가한다.
|
||||
- `ContentOverviewItemResponse`는 `Identifiable`을 채택하고 `contentId`를 `id`로 사용한다.
|
||||
- 검증 기준:
|
||||
- PRD의 response field가 Swift 모델에 누락 없이 반영된다.
|
||||
- `FIRST_AUDIO_CONTENT` case는 추가하지 않는다.
|
||||
|
||||
- [ ] **Task 1.2: ContentOverview API와 repository 추가**
|
||||
- 대상 파일:
|
||||
- 생성: `SodaLive/Sources/V2/Main/Content/Overview/Repository/ContentOverviewApi.swift`
|
||||
- 생성: `SodaLive/Sources/V2/Main/Content/Overview/Repository/ContentOverviewRepository.swift`
|
||||
- 작업 내용:
|
||||
- `GET /api/v2/contents` endpoint를 추가한다.
|
||||
- query parameter `page`, `size`, `type`을 `URLEncoding.queryString`으로 전송한다.
|
||||
- 기존 V2 API와 동일하게 bearer token header를 사용한다.
|
||||
- repository는 `MoyaProvider<ContentOverviewApi>`와 `requestPublisher` 패턴을 따른다.
|
||||
- 검증 기준:
|
||||
- 기본 호출에서 `page=0`, `size=20`, `type=NEW_AND_HOT_AUDIO`를 보낼 수 있다.
|
||||
- 기존 `GET /api/v2/audio/contents` API 파일은 수정하지 않는다.
|
||||
|
||||
### Phase 2: New&Hot 전체보기 화면과 페이징
|
||||
|
||||
- [ ] **Task 2.1: ContentOverview ViewModel 구현**
|
||||
- 대상 파일:
|
||||
- 생성: `SodaLive/Sources/V2/Main/Content/Overview/ContentOverviewViewModel.swift`
|
||||
- 작업 내용:
|
||||
- `ContentOverviewRepository`로 첫 페이지와 다음 페이지를 요청한다.
|
||||
- 기본값은 `type = .newAndHotAudio`, `page = 0`, `pageSize = 20`으로 둔다.
|
||||
- `items`, `page`, `size`, `hasNext`, `hasLoaded`, `isLoading`, `isLoadingNextPage`, `errorMessage`, `isShowPopup` 상태를 관리한다.
|
||||
- 첫 페이지 성공은 목록 교체, 다음 페이지 성공은 append로 처리한다.
|
||||
- 실패 시 첫 페이지/다음 페이지 로딩 상태를 해제하고 toast 또는 empty state 문구를 표시한다.
|
||||
- 검증 기준:
|
||||
- `hasNext == true`이고 마지막 item이 노출될 때 다음 page 요청이 가능하다.
|
||||
- 중복 next page 요청이 발생하지 않는다.
|
||||
- `FIRST_AUDIO_CONTENT` type 분기는 없다.
|
||||
|
||||
- [ ] **Task 2.2: AudioContentThumbnailCard adapter 추가**
|
||||
- 대상 파일:
|
||||
- 수정: `SodaLive/Sources/V2/Component/AudioContent/AudioContentThumbnailCard.swift`
|
||||
- 작업 내용:
|
||||
- `AudioContentThumbnailCardItem`에 `init(contentOverviewItem:)` adapter를 추가한다.
|
||||
- `contentId`는 `id`, `coverImage`는 `imageUrl`, `creatorNickname`은 `subtitle`로 매핑한다.
|
||||
- `price`, `isAdult`, `isPointAvailable`, `isFirstContent`, `isOriginalSeries`는 기존 card tag 규칙에 그대로 연결한다.
|
||||
- 검증 기준:
|
||||
- 신규 adapter 외 기존 `audioCard`, `mainContentAudio` adapter 동작은 변경하지 않는다.
|
||||
- New&Hot 카드에서 original/first/point/free/adult tag 표시 조건을 재사용할 수 있다.
|
||||
|
||||
- [ ] **Task 2.3: ContentOverviewView 구현**
|
||||
- 대상 파일:
|
||||
- 생성: `SodaLive/Sources/V2/Main/Content/Overview/ContentOverviewView.swift`
|
||||
- 작업 내용:
|
||||
- title은 `New&Hot`으로 표시한다.
|
||||
- 좌측 back chevron은 `AppState.shared.back()`을 호출한다.
|
||||
- Figma 기준 2열 `LazyVGrid`로 `AudioContentThumbnailCard`를 표시한다.
|
||||
- 카드 tap 시 `contentId`를 받아 기존 오디오 상세 이동 callback으로 전달한다.
|
||||
- 첫 로딩, empty state, pagination loading, toast 상태를 기존 V2 콘텐츠 화면 패턴으로 표시한다.
|
||||
- 검증 기준:
|
||||
- 화면 진입 시 첫 페이지 요청이 발생한다.
|
||||
- back chevron tap 시 이전 route로 돌아간다.
|
||||
- 카드 tap 시 기존 오디오 상세 route로 이동할 수 있는 callback이 호출된다.
|
||||
|
||||
### Phase 3: AppStep route 연결
|
||||
|
||||
- [ ] **Task 3.1: New&Hot AppStep route 추가**
|
||||
- 대상 파일:
|
||||
- 수정: `SodaLive/Sources/App/AppStep.swift`
|
||||
- 수정: `SodaLive/Sources/ContentView.swift`
|
||||
- 작업 내용:
|
||||
- `AppStep`에 `case contentOverview(type: ContentOverviewType = .newAndHotAudio)` route를 추가한다.
|
||||
- `ContentView`의 `AppStepLayerView` switch에 `ContentOverviewView` mapping을 추가한다.
|
||||
- route는 이번 범위에서 `NEW_AND_HOT_AUDIO`만 열 수 있게 구성한다.
|
||||
- 검증 기준:
|
||||
- `AppState.shared.setAppStep(step:)`로 New&Hot 전체보기 화면을 push할 수 있다.
|
||||
- `ContentView` switch 누락으로 인한 compile error가 없다.
|
||||
|
||||
- [ ] **Task 3.2: MainView에서 New&Hot route와 오디오 상세 이동 연결**
|
||||
- 대상 파일:
|
||||
- 수정: `SodaLive/Sources/V2/Main/MainView.swift`
|
||||
- 수정: `SodaLive/Sources/V2/Main/Content/MainContentView.swift`
|
||||
- 작업 내용:
|
||||
- `MainContentView`에 `onTapNewAndHotAll` callback을 추가한다.
|
||||
- `MainView`에서 해당 callback을 받아 `AppState.shared.setAppStep(step:)`로 New&Hot 전체보기 route를 연다.
|
||||
- `ContentOverviewView`의 card tap은 기존 `handleRecommendationContentTap(contentId:)` 흐름을 재사용한다.
|
||||
- 검증 기준:
|
||||
- 추천 탭 `New&Hot` chevron tap 시 하단 탭 내부 전환이 아니라 새 `AppStep` route로 진입한다.
|
||||
- New&Hot 전체보기 card tap 시 기존 오디오 상세 화면으로 진입한다.
|
||||
|
||||
### Phase 4: 추천 탭 섹션 action과 내부 전체 탭 전환
|
||||
|
||||
- [ ] **Task 4.1: 추천 섹션 컴포넌트에 action parameter 추가**
|
||||
- 대상 파일:
|
||||
- 수정: `SodaLive/Sources/V2/Main/Content/Recommendation/Components/MainContentAudioHorizontalCardSection.swift`
|
||||
- 수정: `SodaLive/Sources/V2/Main/Content/Recommendation/Components/MainContentAudioListCarouselSection.swift`
|
||||
- 수정: `SodaLive/Sources/V2/Main/Content/Recommendation/Components/MainContentVoiceOnOnlySection.swift`
|
||||
- 작업 내용:
|
||||
- 각 섹션에 optional `action: (() -> Void)? = nil` parameter를 추가한다.
|
||||
- `SectionTitle(title:)` 호출을 `SectionTitle(title:action:)`로 변경한다.
|
||||
- item이 비어 섹션이 숨겨질 때는 chevron도 표시하지 않는다.
|
||||
- 검증 기준:
|
||||
- action을 전달한 섹션만 chevron이 표시된다.
|
||||
- 기존 item tap callback은 변경 없이 유지된다.
|
||||
|
||||
- [ ] **Task 4.2: MainContentAllViewModel 외부 적용 API 추가**
|
||||
- 대상 파일:
|
||||
- 수정: `SodaLive/Sources/V2/Main/Content/All/MainContentAllViewModel.swift`
|
||||
- 작업 내용:
|
||||
- 추천 섹션 전체보기 진입을 위한 `apply(type:sort:)` method를 추가한다.
|
||||
- 목표 타입/정렬이 현재 상태와 다르면 items를 비우고 첫 페이지를 다시 요청한다.
|
||||
- 목표 타입/정렬이 현재 상태와 같고 이미 로드된 상태라면 불필요한 중복 요청을 피한다.
|
||||
- `FREE` 타입에 허용되지 않는 정렬을 넣지 않도록 기존 `allowedSorts` 규칙과 충돌하지 않게 한다.
|
||||
- 검증 기준:
|
||||
- `.audio + .latest`, `.original + .latest`, `.free + .popular`, `.point + .popular` 적용이 가능하다.
|
||||
- 기존 타입 chip, sort menu, pagination 동작은 유지된다.
|
||||
|
||||
- [ ] **Task 4.3: MainContentView 내부 전체 탭 전환 연결**
|
||||
- 대상 파일:
|
||||
- 수정: `SodaLive/Sources/V2/Main/Content/MainContentView.swift`
|
||||
- 수정: `SodaLive/Sources/V2/Main/Content/Recommendation/MainContentRecommendationView.swift`
|
||||
- 작업 내용:
|
||||
- `MainContentRecommendationView`에 섹션별 전체보기 callback을 추가한다.
|
||||
- `MainContentView`에서 callback을 받아 `selectedTab = .all`로 전환하고 `allViewModel`에 목표 타입/정렬을 적용한다.
|
||||
- `새로 올라온 오디오`: `.audio`, `.latest`
|
||||
- `오직 보이스온에서만`: `.original`, `.latest`
|
||||
- `무료 오디오`: `.free`, `.popular`
|
||||
- `포인트 오디오`: `.point`, `.popular`
|
||||
- `New&Hot`: Phase 3의 `onTapNewAndHotAll` callback을 호출한다.
|
||||
- 검증 기준:
|
||||
- New&Hot 외 섹션 chevron tap은 `AppStep` route를 추가로 push하지 않고 내부 `전체` 탭으로 전환된다.
|
||||
- 내부 `전체` 탭 전환 후 타입/정렬이 PRD의 매핑과 일치한다.
|
||||
|
||||
### Phase 5: 문구, 접근성, 회귀 검증
|
||||
|
||||
- [ ] **Task 5.1: I18n 문구 반영**
|
||||
- 대상 파일:
|
||||
- 수정 후보: `SodaLive/Sources/I18n/I18n.swift`
|
||||
- 작업 내용:
|
||||
- Task 0.1에서 확정한 New&Hot 전체보기 empty/failure 문구를 반영한다.
|
||||
- 기존 문구 재사용으로 결정된 경우 불필요한 신규 key를 만들지 않는다.
|
||||
- 검증 기준:
|
||||
- 신규 화면에서 표시하는 모든 사용자 노출 문구가 `I18n` 또는 기존 상수 정책과 일치한다.
|
||||
|
||||
- [ ] **Task 5.2: 빌드 검증**
|
||||
- 대상 파일:
|
||||
- 확인: `SodaLive.xcworkspace`
|
||||
- 확인: `SodaLive.xcodeproj`
|
||||
- 실행 명령:
|
||||
- `xcodebuild -workspace "SodaLive.xcworkspace" -list`
|
||||
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug -sdk iphonesimulator build`
|
||||
- 검증 기준:
|
||||
- workspace와 scheme가 확인된다.
|
||||
- Debug simulator build가 성공한다.
|
||||
|
||||
- [ ] **Task 5.3: 수동 동작 검증**
|
||||
- 대상 파일:
|
||||
- 확인: `SodaLive/Sources/V2/Main/Content/Recommendation/MainContentRecommendationView.swift`
|
||||
- 확인: `SodaLive/Sources/V2/Main/Content/Overview/ContentOverviewView.swift`
|
||||
- 확인: `SodaLive/Sources/V2/Main/Content/All/MainContentAllView.swift`
|
||||
- 수동 확인 항목:
|
||||
- `새로 올라온 오디오` chevron tap 시 `전체 > 오디오 > 최신순` 상태로 전환된다.
|
||||
- `오직 보이스온에서만` chevron tap 시 `전체 > 오리지널 > 최신순` 상태로 전환된다.
|
||||
- `무료 오디오` chevron tap 시 `전체 > 무료 > 인기순` 상태로 전환된다.
|
||||
- `포인트 오디오` chevron tap 시 `전체 > 포인트 > 인기순` 상태로 전환된다.
|
||||
- `New&Hot` chevron tap 시 신규 `AppStep` route 화면으로 이동한다.
|
||||
- `New&Hot` 전체보기에서 paging, back, card tap 상세 이동이 동작한다.
|
||||
- `처음부터 함께 성장!` 섹션과 `FIRST_AUDIO_CONTENT` 진입은 노출되지 않는다.
|
||||
- 검증 기준:
|
||||
- PRD의 Success Criteria가 모두 충족된다.
|
||||
- 기존 추천 탭의 item tap, banner tap, series tap 동작이 회귀하지 않는다.
|
||||
|
||||
## 검증 기록
|
||||
|
||||
- 2026-07-07: PRD를 기준으로 plan-task 문서를 생성했다. 구현은 아직 수행하지 않았다.
|
||||
211
docs/20260707_메인_콘텐츠_추천_전체보기/prd.md
Normal file
211
docs/20260707_메인_콘텐츠_추천_전체보기/prd.md
Normal file
@@ -0,0 +1,211 @@
|
||||
# PRD: 메인 콘텐츠 추천 전체보기
|
||||
|
||||
## 1. Overview
|
||||
메인 하단 `콘텐츠` 탭의 내부 `추천` 탭에서 일부 콘텐츠 섹션에 전체보기 action을 추가한다. `새로 올라온 오디오`, `오직 보이스온에서만`, `무료 오디오`, `포인트 오디오`는 하단 `콘텐츠` 탭 내부의 상단 `전체` 탭으로 전환해 해당 타입과 정렬 상태를 보여준다.
|
||||
|
||||
`New&Hot`은 기존 전체 탭이 아니라 신규 콘텐츠 전체보기 화면을 만들어 `GET /api/v2/contents` API를 적용한다. `New&Hot`은 탭 외부 화면 전환이므로 `AppStep` route로 추가한다. 신규 화면은 Figma `482:15105` 기준으로 상단 title bar와 2열 오디오 카드 grid를 제공하고, 목록에는 스크롤 페이징을 적용한다.
|
||||
|
||||
Figma 참조:
|
||||
- 신규 콘텐츠 전체보기 화면: `482:15105`, `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=482-15105&m=dev`
|
||||
|
||||
## 2. Problem
|
||||
- 현재 V2 `MainContentRecommendationView`의 추천 섹션 title은 전체보기 action을 받지 않아 chevron이 표시되지 않는다.
|
||||
- `SectionTitle`은 이미 action을 제공하면 chevron을 표시할 수 있으므로, 섹션별 전체보기 이동 callback을 연결해야 한다.
|
||||
- 대부분의 추천 섹션 전체보기는 이미 구현된 하단 `콘텐츠` 탭 내부 `전체` 탭으로 대체할 수 있지만, `New&Hot`은 별도 API와 별도 화면이 필요하다.
|
||||
- 신규 `GET /api/v2/contents` 응답은 기존 `AudioCardResponse` / `MainContentAudioResponse`와 필드명이 일부 달라 adapter 또는 전용 모델이 필요하다.
|
||||
|
||||
## 3. Goals
|
||||
- 추천 탭에서 전체보기가 필요한 섹션에 action을 추가해 title 우측 chevron을 표시한다.
|
||||
- `새로 올라온 오디오` 전체보기는 하단 `콘텐츠` 탭의 상단 `전체` 탭에서 `오디오` 타입, `최신순` 정렬로 전환한다.
|
||||
- `오직 보이스온에서만` 전체보기는 하단 `콘텐츠` 탭의 상단 `전체` 탭에서 `오리지널` 타입, `최신순` 정렬로 전환한다.
|
||||
- `무료 오디오` 전체보기는 하단 `콘텐츠` 탭의 상단 `전체` 탭에서 `무료` 타입, `인기순` 정렬로 전환한다.
|
||||
- `포인트 오디오` 전체보기는 하단 `콘텐츠` 탭의 상단 `전체` 탭에서 `포인트` 타입, `인기순` 정렬로 전환한다.
|
||||
- `New&Hot` 전체보기는 `AppStep` route 기반 신규 콘텐츠 전체보기 화면으로 이동하고 `type = NEW_AND_HOT_AUDIO`를 요청한다.
|
||||
- 신규 전체보기 화면은 `GET /api/v2/contents`를 호출하고 기본 query 값은 `page = 0`, `size = 20`, `type = NEW_AND_HOT_AUDIO`로 둔다.
|
||||
- 신규 전체보기 화면은 `hasNext == true`일 때 다음 page를 append한다.
|
||||
- 신규 전체보기 화면의 오디오 카드는 V2 공용 `AudioContentThumbnailCard` 재사용을 우선한다.
|
||||
|
||||
## 4. Non-Goals
|
||||
- 실제 구현과 Xcode 프로젝트 수정은 이번 PRD 작성 범위에 포함하지 않는다.
|
||||
- 추천 탭의 기존 API `GET /api/v2/audio/recommendations` 응답 구조를 변경하지 않는다.
|
||||
- `새로 올라온 오디오`, `오직 보이스온에서만`, `무료 오디오`, `포인트 오디오`용 별도 전체보기 화면을 새로 만들지 않는다.
|
||||
- `처음부터 함께 성장!` 섹션과 `FIRST_AUDIO_CONTENT` 전체보기 진입은 이번 범위에 포함하지 않는다.
|
||||
- `콘텐츠 탭 - 전체`의 기존 API `GET /api/v2/audio/contents` 명세를 변경하지 않는다.
|
||||
- API 명세에 없는 신규 `type` 값을 임의로 추가하지 않는다.
|
||||
- Figma localhost asset URL을 앱 코드에 직접 사용하지 않는다.
|
||||
- 외부 라이브러리를 추가하지 않는다.
|
||||
- `Pods/**`, `generated/**`, `build/**`는 수정하지 않는다.
|
||||
|
||||
## 5. Target Users
|
||||
- 추천 탭에서 관심 섹션의 콘텐츠를 더 많이 탐색하려는 사용자
|
||||
- 새롭거나 반응이 좋은 오디오를 연속해서 보고 싶은 사용자
|
||||
- 첫 콘텐츠, 무료, 포인트, 오리지널 등 조건별 콘텐츠를 빠르게 훑고 상세로 이동하려는 사용자
|
||||
|
||||
## 6. User Stories
|
||||
- 사용자는 추천 탭 섹션 title 우측 chevron을 눌러 해당 섹션의 전체 콘텐츠를 보고 싶다.
|
||||
- 사용자는 `새로 올라온 오디오` 전체보기에서 최신 오디오 목록을 바로 보고 싶다.
|
||||
- 사용자는 `무료 오디오`와 `포인트 오디오` 전체보기에서 인기순 목록을 바로 보고 싶다.
|
||||
- 사용자는 `New&Hot` 전체보기에서 Figma처럼 2열 카드 목록을 스크롤하며 더 많은 콘텐츠를 보고 싶다.
|
||||
- 사용자는 전체보기 목록의 콘텐츠 카드를 누르면 기존 오디오 상세 화면으로 이동하기를 기대한다.
|
||||
|
||||
## 7. Core Requirements
|
||||
|
||||
### 7.1 추천 탭 섹션 action
|
||||
- 전체보기 action이 필요한 섹션은 title 우측 chevron을 표시한다.
|
||||
- `SectionTitle(title:action:)`의 기존 chevron 표시 동작을 우선 재사용한다.
|
||||
- action이 없는 섹션은 기존처럼 chevron을 표시하지 않는다.
|
||||
- 기존 추천 섹션의 empty 조건은 유지한다. 섹션 item이 비어 화면에 보이지 않는 경우 chevron도 표시하지 않는다.
|
||||
|
||||
대상 섹션과 이동 규칙:
|
||||
- `새로 올라온 오디오`
|
||||
- 이동 대상: 하단 `콘텐츠` 탭 내부 상단 `전체` 탭
|
||||
- 초기 타입: `MainContentAllType.audio`
|
||||
- 초기 정렬: `ContentSort.latest`
|
||||
- `오직 보이스온에서만`
|
||||
- 이동 대상: 하단 `콘텐츠` 탭 내부 상단 `전체` 탭
|
||||
- 초기 타입: `MainContentAllType.original`
|
||||
- 초기 정렬: `ContentSort.latest`
|
||||
- `무료 오디오`
|
||||
- 이동 대상: 하단 `콘텐츠` 탭 내부 상단 `전체` 탭
|
||||
- 초기 타입: `MainContentAllType.free`
|
||||
- 초기 정렬: `ContentSort.popular`
|
||||
- `포인트 오디오`
|
||||
- 이동 대상: 하단 `콘텐츠` 탭 내부 상단 `전체` 탭
|
||||
- 초기 타입: `MainContentAllType.point`
|
||||
- 초기 정렬: `ContentSort.popular`
|
||||
- `New&Hot`
|
||||
- 이동 대상: `AppStep` route 기반 신규 콘텐츠 전체보기 화면
|
||||
- API type: `ContentOverviewType.newAndHotAudio`
|
||||
|
||||
### 7.2 신규 콘텐츠 전체보기 API
|
||||
- Method: `GET`
|
||||
- Path: `/api/v2/contents`
|
||||
- 인증: 기존 V2 API 인증 헤더 패턴을 따른다.
|
||||
- 응답 래퍼: 기존 V2 관례대로 `ApiResponse<ContentOverviewPageResponse>` 디코딩을 우선한다.
|
||||
|
||||
Query parameter:
|
||||
- `page: Int`
|
||||
- `size: Int`
|
||||
- `type: ContentOverviewType`
|
||||
|
||||
기본값:
|
||||
- `page = 0`
|
||||
- `size = 20`
|
||||
- `type = NEW_AND_HOT_AUDIO`
|
||||
|
||||
Swift enum:
|
||||
```swift
|
||||
enum ContentOverviewType: String, Decodable, Encodable, Hashable {
|
||||
case newAndHotAudio = "NEW_AND_HOT_AUDIO"
|
||||
}
|
||||
```
|
||||
|
||||
Swift response model:
|
||||
```swift
|
||||
struct ContentOverviewPageResponse: Decodable {
|
||||
let type: ContentOverviewType
|
||||
let items: [ContentOverviewItemResponse]
|
||||
let page: Int
|
||||
let size: Int
|
||||
let hasNext: Bool
|
||||
}
|
||||
|
||||
struct ContentOverviewItemResponse: Decodable, Identifiable {
|
||||
let contentId: Int
|
||||
let title: String
|
||||
let coverImage: String?
|
||||
let price: Int
|
||||
let isPointAvailable: Bool
|
||||
let creatorNickname: String
|
||||
let isAdult: Bool
|
||||
let isFirstContent: Bool
|
||||
let isOriginalSeries: Bool
|
||||
|
||||
var id: Int { contentId }
|
||||
}
|
||||
```
|
||||
|
||||
### 7.3 신규 콘텐츠 전체보기 화면
|
||||
- 화면은 `SodaLive/Sources/V2/**` 하위에 신규 `View`, `ViewModel`, `Repository`, `Models`를 작성한다.
|
||||
- 신규 화면은 `AppStep`에 route를 추가하고 `ContentView`에서 해당 route를 화면에 매핑한다.
|
||||
- 화면 title은 `New&Hot`으로 표시한다.
|
||||
- Figma 기준 상단 title bar는 좌측 back chevron과 title을 가진다.
|
||||
- 콘텐츠 목록은 2열 grid로 표시한다.
|
||||
- 각 카드는 정사각형 cover image, title, creator nickname, 태그를 표시한다.
|
||||
- 카드 태그는 응답 필드에 따라 기존 `AudioContentThumbnailCard`의 태그 표시 규칙을 따른다.
|
||||
- `isOriginalSeries == true`: original tag
|
||||
- `isFirstContent == true`: first tag
|
||||
- `isPointAvailable == true`: point tag
|
||||
- `price == 0`: free tag
|
||||
- `isAdult == true`: adult tag
|
||||
- 카드 tap 시 `contentId`로 기존 오디오 상세 화면에 진입한다.
|
||||
- 첫 페이지 로딩, 빈 목록, 실패, pagination loading 상태는 기존 V2 콘텐츠 탭 패턴을 따른다.
|
||||
|
||||
### 7.4 Paging
|
||||
- `page = 0` 응답은 기존 목록을 교체한다.
|
||||
- `page > 0` 응답은 기존 목록 뒤에 append한다.
|
||||
- `hasNext == false`이면 추가 요청을 막는다.
|
||||
- 이미 다음 페이지를 로딩 중이면 중복 요청을 막는다.
|
||||
- 첫 페이지 실패 시 empty/error 상태를 표시한다.
|
||||
- 다음 페이지 실패 시 기존 목록은 유지하고 toast 또는 error message를 표시한다.
|
||||
|
||||
### 7.5 하단 콘텐츠 탭 내부 전체 탭 전환
|
||||
- `New&Hot`을 제외한 추천 섹션 전체보기는 별도 route를 추가하지 않고 하단 `콘텐츠` 탭 내부에서 처리한다.
|
||||
- 추천 탭에서 하단 `콘텐츠` 탭 내부 상단 `전체` 탭으로 이동할 때 `MainContentView`의 내부 탭을 `.all`로 전환한다.
|
||||
- 전환 직후 `MainContentAllViewModel`에 목표 타입과 정렬을 적용하고 첫 페이지를 다시 요청한다.
|
||||
- 같은 타입/정렬 상태로 다시 진입하는 경우 불필요한 중복 요청은 피한다.
|
||||
- 기존 `MainContentAllViewModel` 상태 유지 구조는 보존하되, 추천 섹션 전체보기 진입 시 명시적으로 선택 상태를 갱신할 수 있어야 한다.
|
||||
|
||||
## 8. Reusable V2 Candidates
|
||||
- `SodaLive/Sources/V2/Component/SectionTitle.swift`
|
||||
- `action`이 있으면 `ic_chevron_right`를 표시하므로 추천 섹션 title chevron에 재사용 가능하다.
|
||||
- `SodaLive/Sources/V2/Component/AudioContent/AudioContentThumbnailCard.swift`
|
||||
- Figma 신규 전체보기의 2열 오디오 카드와 태그 표시 구조가 일치한다.
|
||||
- `ContentOverviewItemResponse`를 `AudioContentThumbnailCardItem`으로 바꾸는 initializer 추가만으로 재사용 가능하다.
|
||||
- `SodaLive/Sources/V2/Main/Content/All/MainContentAllView.swift`
|
||||
- 하단 `콘텐츠` 탭 내부 상단 `전체` 탭 전환 대상 화면으로 재사용한다.
|
||||
- 외부에서 초기 타입/정렬을 주입하거나 갱신하는 API가 필요하다.
|
||||
- `SodaLive/Sources/V2/Main/Content/All/MainContentAllViewModel.swift`
|
||||
- 타입/정렬 선택, 첫 페이지 reload, append paging 구조가 이미 있다.
|
||||
- 추천 전체보기 진입용 `apply(type:sort:)` 같은 최소 API 추가 후보이다.
|
||||
- `SodaLive/Sources/V2/Main/Content/Recommendation/Components/MainContentAudioHorizontalCardSection.swift`
|
||||
- `새로 올라온 오디오`, `무료 오디오`, `포인트 오디오` 섹션에 action parameter를 추가하는 후보이다.
|
||||
- `SodaLive/Sources/V2/Main/Content/Recommendation/Components/MainContentAudioListCarouselSection.swift`
|
||||
- `New&Hot` 섹션에 action parameter를 추가하는 후보이다.
|
||||
- `SodaLive/Sources/V2/Main/Content/Recommendation/Components/MainContentVoiceOnOnlySection.swift`
|
||||
- `오직 보이스온에서만` 섹션에 action parameter를 추가하는 후보이다.
|
||||
- `SodaLive/Sources/V2/Component/DefaultTitleBar.swift` / `SodaLive/Sources/V2/Component/TitleBar.swift`
|
||||
- 신규 전체보기 화면 title bar 재사용 후보이다. Figma의 좌측 back chevron 구조와 정확히 맞지 않으면 기존 title bar 패턴 안에서 최소 확장한다.
|
||||
- `SodaLive/Sources/V2/Main/Content/Recommendation/Repository/MainContentRecommendationApi.swift`
|
||||
- V2 Moya `TargetType`, bearer token header 패턴 참고 대상이다.
|
||||
- `SodaLive/Sources/V2/Main/Content/All/Repository/MainContentAllApi.swift`
|
||||
- query parameter 기반 V2 API 구현 패턴 참고 대상이다.
|
||||
- `SodaLive/Sources/V2/Main/Content/Recommendation/Components/MainContentAudioEmptyStateView.swift`
|
||||
- 신규 전체보기의 빈 목록/실패 상태 재사용 후보이다.
|
||||
|
||||
## 9. Technical Constraints
|
||||
- 기능 변경은 `SodaLive/Sources/**`, 신규 V2 코드는 `SodaLive/Sources/V2/**` 하위에서 해결한다.
|
||||
- 공용 컴포넌트 변경은 재사용성이 명확할 때만 `SodaLive/Sources/V2/Component/**`에서 최소 수정한다.
|
||||
- 특정 화면 내부에서만 쓰는 컴포넌트는 해당 화면 폴더 하위 `Components`에 둔다.
|
||||
- 기존 navigation은 `AppState.shared.setAppStep(step:)` 패턴을 따른다.
|
||||
- `New&Hot` 신규 전체보기 화면은 탭 외부 전환이므로 `AppStep`과 `ContentView` mapping을 추가한다.
|
||||
- `New&Hot`을 제외한 섹션 전체보기는 `AppStep` route를 추가하지 않고 `MainContentView` 내부 탭 상태 변경으로 처리한다.
|
||||
- 인증 헤더는 기존 V2 API와 동일하게 `Authorization: Bearer <token>` 패턴을 따른다.
|
||||
- `Long` id는 기존 V2 모델 관례에 맞춰 Swift `Int`를 우선 사용한다.
|
||||
- 기존 `AudioContentThumbnailCard`는 `imageUrl` 필드명을 사용하므로 `coverImage` adapter가 필요하다.
|
||||
- 구현 시 빌드/검증 명령은 `docs/agent-guides/build-test-verification.md`를 따른다.
|
||||
|
||||
## 10. Success Criteria
|
||||
- 전체보기 대상 추천 섹션 title 우측에 chevron이 표시된다.
|
||||
- `새로 올라온 오디오` chevron tap 시 하단 `콘텐츠` 탭 내부 상단 `전체 > 오디오 > 최신순` 상태로 전환된다.
|
||||
- `오직 보이스온에서만` chevron tap 시 하단 `콘텐츠` 탭 내부 상단 `전체 > 오리지널 > 최신순` 상태로 전환된다.
|
||||
- `무료 오디오` chevron tap 시 하단 `콘텐츠` 탭 내부 상단 `전체 > 무료 > 인기순` 상태로 전환된다.
|
||||
- `포인트 오디오` chevron tap 시 하단 `콘텐츠` 탭 내부 상단 `전체 > 포인트 > 인기순` 상태로 전환된다.
|
||||
- `New&Hot` chevron tap 시 `AppStep` route로 신규 전체보기 화면에 진입하고 `GET /api/v2/contents?page=0&size=20&type=NEW_AND_HOT_AUDIO`를 요청한다.
|
||||
- 신규 전체보기 화면은 Figma 기준 2열 오디오 card grid, title bar, back navigation을 제공한다.
|
||||
- 신규 전체보기 화면은 `hasNext` 기반 다음 페이지 append를 제공한다.
|
||||
- 신규 전체보기 화면의 card tap은 기존 오디오 상세 이동과 동일하게 동작한다.
|
||||
- 기존 추천 탭 섹션의 item tap, banner tap, series tap 동작은 회귀하지 않는다.
|
||||
|
||||
## 11. Open Questions
|
||||
- 신규 API 실패 시 toast 문구와 empty state 문구의 정확한 I18n 문구는 확인이 필요하다.
|
||||
Reference in New Issue
Block a user