Files
sodalive-ios/docs/20260704_크리에이터_채널_시리즈_탭/prd.md
2026-07-04 18:50:24 +09:00

13 KiB

PRD: 크리에이터 채널 시리즈 탭

1. Overview

크리에이터 채널 공통 shell의 시리즈 탭에서 크리에이터의 시리즈 목록, 정렬, 시리즈 콘텐츠 소장률 정보를 제공한다. 상단 title bar, header, sticky tab-bar는 기존 크리에이터 채널 홈/오디오 탭 구현을 재사용하고, tab-bar 아래 콘텐츠만 시리즈 탭 전용 API 응답으로 구성한다.

API는 GET /api/v2/creator-channels/{creatorId}/series를 사용한다. 기본 query는 page=0, size=20, sort=LATEST이다.

브레인스토밍 결과, 홈 탭 시리즈 모델을 확장하거나 오디오 콘텐츠 item을 변형하기보다 시리즈 탭 전용 Series/** 모듈을 추가하는 방식을 선택한다. 홈 탭 시리즈 모델은 커버 이미지 중심 카드 데이터이고, 시리즈 탭 API는 요일/진행 여부/콘텐츠 수/소장률 데이터를 중심으로 하므로 책임을 분리하는 편이 가장 단순하다. 기존 홈 탭 모델은 CreatorChannelHomeSeriesResponse로 변경하고, 시리즈 탭 API 모델은 명세와 동일하게 CreatorChannelSeriesResponse를 사용한다.

Figma 참조:

  • 전체 화면: 290:9031, 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=290-9031&m=dev
  • 시리즈 아이템: 290:9036, 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=290-9036&m=dev
  • 시리즈 콘텐츠 소장률: 290:9038, 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=290-9038&m=dev

2. Problem

  • 현재 CreatorChannelTab.series는 placeholder로 표시되어 실제 시리즈 목록, 정렬, 소장률, 페이지네이션이 없다.
  • 홈 탭의 시리즈 카드는 커버 중심의 가로 섹션이므로, 시리즈 탭의 리스트형 아이템과 API 응답 필드를 그대로 재사용할 수 없다.
  • 시리즈 탭은 오디오 탭과 같은 정렬 선택 UX를 사용해야 하지만, 시리즈별 콘텐츠 수와 유료 콘텐츠 소장률 표시 조건이 추가된다.
  • 본인 채널에서는 일반 사용자용 소장률을 숨기고 상단 정보만 표시해야 한다.

3. Goals

  • CreatorChannelTab.series 선택 시 시리즈 탭 API를 호출하고 응답 데이터로 화면을 구성한다.
  • page, size, sort를 query parameter로 전달한다.
  • 기본값은 page=0, size=20, sort=LATEST이다.
  • 정렬 선택 방식은 오디오 탭과 동일한 CreatorChannelSortBar + context popup을 재사용한다.
  • sort-bar는 seriesCount0 이상이면 전체 {seriesCount}를 표시한다.
  • 시리즈 아이템은 Figma 290:9036 기준으로 신규 리스트 item을 구현한다.
  • 시리즈 아이템 우측 끝 버튼은 표시하지 않고, 해당 버튼 왼쪽 UI가 play button 영역까지 차지하도록 구성한다.
  • 본인 채널이 아닐 때만 시리즈 콘텐츠 소장률 UI를 표시한다.
  • 본인 채널이면 소장률을 숨기고 상단 info만 표시한다.
  • 목록 하단 도달 시 hasNext == true이면 다음 페이지를 조회해 append한다.

4. Non-Goals

  • 크리에이터 채널 공통 shell, header, sticky tab-bar 동작을 다시 설계하지 않는다.
  • 시리즈 상세 화면, 시리즈 재생, 구매, 업로드 화면 자체를 새로 구현하지 않는다.
  • 오디오 콘텐츠 목록 item을 시리즈 item으로 억지 재사용하지 않는다.
  • Figma 시리즈 아이템의 가장 우측 버튼은 이번 범위에서 구현하지 않는다.
  • 이미지 크기처럼 명확한 제한이 필요한 경우를 제외하고 불필요한 고정 크기 상수를 추가하지 않는다.
  • Figma localhost asset URL을 앱 코드에 직접 사용하지 않는다.
  • Pods/**, generated/**, build/**는 수정하지 않는다.

5. Core Requirements

5.1 API

  • Method: GET
  • Path: /api/v2/creator-channels/{creatorId}/series
  • Path parameter: creatorId
  • Query parameters:
    • sort
    • page
    • size
  • 기본 query:
    • page=0
    • size=20
    • sort=LATEST
  • 응답 래퍼는 기존 패턴대로 ApiResponse<CreatorChannelSeriesTabResponse>로 디코딩한다.
data class CreatorChannelSeriesTabResponse(
    val seriesCount: Int,
    val series: List<CreatorChannelSeriesResponse>,
    val sort: ContentSort,
    val page: Int,
    val size: Int,
    val hasNext: Boolean
)

data class CreatorChannelSeriesResponse(
    val seriesId: Long,
    val title: String,
    val publishedDaysOfWeek: String,
    val isOriginal: Boolean,
    val isAdult: Boolean,
    val isProceeding: Boolean,
    val contentCount: Int,
    val purchasedContentCount: Int?,
    val paidContentCount: Int?,
    val purchasedPaidContentRate: Int?
)
  • ContentSort는 기존 타입을 재사용한다.
  • Kotlin Long은 기존 V2 모델 관례대로 Swift Int로 선언한다.
  • 기존 홈 탭의 CreatorChannelSeriesResponsecoverImageUrl, numberOfContent, isNew 등 필드가 달라 시리즈 탭 응답 모델로 재사용하지 않는다.
  • 기존 홈 탭 타입명을 CreatorChannelHomeSeriesResponse로 변경한다.
  • 시리즈 탭 item 모델명은 API 명세와 동일하게 CreatorChannelSeriesResponse로 선언한다.
  • CreatorChannelSeriesTabResponse.series[CreatorChannelSeriesResponse]로 선언한다.

5.2 Sort

  • 정렬 선택 UI는 오디오 탭과 동일하게 sort-bar 우측 버튼 아래 anchored context popup으로 표시한다.
  • 정렬 변경 시 첫 페이지부터 다시 조회한다.
  • 시리즈 탭에서 사용하는 ContentSort 값은 현재 오디오 탭과 동일하게 4개만 사용한다.
    • LATEST
    • POPULAR
    • PRICE_HIGH
    • PRICE_LOW
  • OWNED는 사용하지 않는다.

5.3 Series item

  • 시리즈 목록은 세로 리스트로 표시한다.
  • 아이템 tap 시 기존 showSeriesDetail(_:) 흐름을 재사용해 seriesDetail(seriesId:)로 이동한다.
  • 표시 정보는 API 응답의 title, publishedDaysOfWeek, isOriginal, isAdult, isProceeding, contentCount를 사용한다.
  • publishedDaysOfWeek는 서버 문자열을 그대로 표시한다.
  • isProceeding은 진행 중/완결 상태 표시 UI에 사용한다.
  • 썸네일 overlay의 ONLY 표시는 isOriginal == true일 때만 표시한다.
  • ONLY 표시는 홈 탭 시리즈 카드와 동일하게 ic_series_originalimg_new_only를 조합해 사용한다.
  • 아이템 우측에 표시되는 성인 태그는 CreatorChannelAudioAdultTag를 재사용한다.
  • Figma 290:9036 기준 성인 태그는 top: 6, right: 6 위치에 있으므로, isAdult == true일 때 CreatorChannelAudioAdultTag().padding(.top, 6).padding(.trailing, 6) 형태로 우측 상단에 배치한다.
  • Figma 290:9036에서 가장 우측에 있는 버튼은 표시하지 않는다.
  • 숨긴 버튼의 왼쪽 UI는 썸네일 overlay asset이 아니라, 아이템 가로 레이아웃에서 가장 우측 버튼 바로 왼쪽에 배치된 정보 영역을 의미한다.
  • 해당 정보 영역은 기존 play button 영역까지 확장되도록 frame(maxWidth: .infinity, alignment: ...) 등 SwiftUI의 유연한 레이아웃을 우선 사용한다.
  • 이미지 크기처럼 Figma와 기능상 일치가 필요한 영역만 명시적으로 크기를 제한한다.

5.4 Ownership rate

  • 소장률 UI는 본인 채널이 아닐 때만 표시한다.
  • 본인 채널에서는 소장률 progress/count 영역을 숨기고 Figma 290:9038의 상단 info 영역만 표시한다.
  • purchasedPaidContentRate는 percent 값으로 내려오므로 그대로 % 문구에 사용한다.
  • 우측 카운트는 purchasedContentCount/paidContentCount개 형식으로 표시한다.
  • progress 값은 purchasedPaidContentRate / 100을 0...1로 clamp해서 사용한다.
  • purchasedContentCount, paidContentCount, purchasedPaidContentRate 중 표시 필수 값이 nil이면 소장률 영역은 표시하지 않는다.

5.5 Empty and pagination

  • Empty 상태 판정은 서버의 seriesCount == 0 기준으로 한다.
  • Empty UI는 라이브/오디오 탭 empty와 동일한 표시 방식을 우선 재사용한다.
  • Empty 문구는 크리에이터가 시리즈를 준비 중입니다.\n기대해 주세요!를 기본안으로 I18n에 추가한다.
  • 목록 하단 도달 시 hasNext == true이면 page + 1을 조회해 append한다.

6. Success Criteria

  • 시리즈 탭 진입 시 GET /api/v2/creator-channels/{creatorId}/series?page=0&size=20&sort=LATEST가 호출된다.
  • 정렬 선택 UI는 BottomSheet가 아니라 sort-bar 우측 버튼 아래 anchored context popup으로 표시되고, 동작은 오디오 탭과 동일하다.
  • 정렬 변경 시 선택한 sort로 첫 페이지를 다시 조회한다.
  • seriesCount가 sort-bar에 전체 {seriesCount} 형식으로 표시된다.
  • 시리즈 리스트 item은 Figma 290:9036 기준으로 표시하되, 가장 우측 버튼은 표시하지 않는다.
  • 숨긴 버튼의 왼쪽 UI가 play button 영역까지 자연스럽게 확장된다.
  • 본인 채널이 아니고 소장률 필수 값이 모두 있을 때만 시리즈 콘텐츠 소장률 UI가 표시된다.
  • 본인 채널에서는 소장률이 표시되지 않고 상단 info만 표시된다.
  • seriesCount == 0이면 empty UI가 표시된다.
  • hasNext == true인 상태에서 목록 하단에 도달하면 다음 페이지를 append한다.
  • 시리즈 item tap 시 기존 시리즈 상세 이동 흐름으로 이동한다.

7. Technical Constraints

  • 기능 변경은 SodaLive/Sources/V2/CreatorChannel/** 하위에서 해결한다.
  • 시리즈 탭 전용 View, ViewModel, Repository, API, 모델은 SodaLive/Sources/V2/CreatorChannel/Series/** 아래에 둔다.
  • 공용 sort bar, sort context popup은 기존 구현을 재사용한다.
  • 기존 홈 탭 시리즈 카드/모델과 시리즈 탭 리스트/모델의 책임을 분리하고, 홈 탭 타입은 CreatorChannelHomeSeriesResponse로 이름을 변경한다.
  • 신규 문구가 필요하면 SodaLive/Sources/I18n/I18n.swift에 ko/en/ja를 추가한다.

8. Design Decisions

  • 시리즈 탭은 홈 탭 시리즈 모델을 확장하지 않고 Series/** 하위 전용 모델/ViewModel/View를 둔다.
  • 기존 홈 탭 시리즈 모델은 CreatorChannelHomeSeriesResponse로 변경하고, 신규 시리즈 탭 모델은 CreatorChannelSeriesResponse로 둔다.
  • 정렬은 오디오 탭과 동일한 4개 값(LATEST, POPULAR, PRICE_HIGH, PRICE_LOW)만 사용하고 OWNED는 제외한다.
  • 시리즈 item은 오디오 콘텐츠 item을 변형하지 않고 신규 CreatorChannelSeriesListItem으로 구현한다.
  • 썸네일 overlay는 isOriginal == true일 때의 ONLY 표시만 담당하고, 우측 성인 태그는 CreatorChannelAudioAdultTagtop 6, trailing 6 padding으로 재사용한다.
  • 시리즈 item의 우측 버튼 제거 후 확장 대상은 썸네일 overlay asset이 아니라 버튼 바로 왼쪽의 정보 영역이다.
  • 본인 채널 여부에 따른 소장률 숨김은 item 내부가 아니라 시리즈 탭 view에서 제어한다.

9. Open Questions

  • 해당 없음.

10. Verification Notes

  • 2026-07-04: 사용자 제공 API 명세, 정렬 방식, Figma 링크, 본인 채널 소장률 표시 조건을 문서에 반영했다.
  • 2026-07-04: 기존 CreatorChannelAudioTabResponse, CreatorChannelSortBar, CreatorChannelSortContextPopup, 홈 탭 CreatorChannelSeriesResponse, showSeriesDetail(_:) 경로를 확인했다.
  • 2026-07-04: 현재 Swift ContentSort가 오디오 탭 정렬 4개(LATEST, POPULAR, PRICE_HIGH, PRICE_LOW)를 제공하는 것을 확인했다.
  • 2026-07-04: 사용자 확인에 따라 OWNED는 시리즈 탭 정렬 범위에서 제외했다.
  • 2026-07-04: superpowers:brainstorming 관점으로 모델 재사용/확장 선택지를 검토하고, 시리즈 탭 전용 모듈을 선택한 이유를 문서에 추가했다.
  • 2026-07-04: "버튼 왼쪽 UI"는 썸네일 overlay asset이 아니라 시리즈 item 우측 버튼 바로 왼쪽의 정보 영역을 의미하는 것으로 명확히 정리했다.
  • 2026-07-04: 썸네일 overlay는 isOriginal == true일 때 ic_series_original + img_new_only로 표시하고, 우측 성인 태그는 CreatorChannelAudioAdultTag().padding(.top, 6).padding(.trailing, 6)로 재사용하도록 반영했다.
  • 2026-07-04: Figma 290:9036의 design context에서 성인 태그 위치가 top: 6, right: 6임을 확인해 padding 값을 유지했다.
  • 2026-07-04: 기존 홈 탭 시리즈 모델은 CreatorChannelHomeSeriesResponse로 변경하고, 신규 시리즈 탭 API 모델은 명세와 동일한 CreatorChannelSeriesResponse를 사용하도록 결정했다.