18 KiB
18 KiB
PRD: 메인 콘텐츠 추천 탭
1. Overview
Figma cont_001 화면(24:6737)을 기준으로 메인 콘텐츠 탭의 내부 추천 탭을 구성하고, 오디오 추천 API 응답을 V2 패키지 하위 화면과 위젯 중심으로 바인딩한다.
2. Problem
ContentMainFragment는 현재 빈 화면 수준이므로 메인 콘텐츠 탭 내부의 추천 화면 구조와 데이터 계약을 새로 정의해야 한다.- 화면 상단의 title-bar와 tab-bar는 고정되어야 하고, 배너부터 하위 추천 콘텐츠만 세로 스크롤되어야 한다.
- 추천 화면은 배너, 오리지널 시리즈, 최신/인기/무료/포인트/댓글/추천 오디오 등 여러 섹션을 단일 API 응답으로 표시해야 한다.
- Figma에는 가로 카드, 리스트, 배너, 댓글 카드, 2열 그리드가 혼재되어 있어 화면에 보이는 섹션별로 Phase를 분리해 구현 계획을 세워야 한다.
- V2 패키지 하위에 이미 구현된 배너, 오디오 카드, 시리즈 카드, 탭 위젯이 있으므로 재사용 가능한 후보를 먼저 식별하고 중복 UI 생성을 줄여야 한다.
3. Goals
- API endpoint
GET /api/v2/audio/recommendations응답을 받아 메인 콘텐츠 추천 탭에 표시한다. - Figma
24:6737의 고정/스크롤 구조를 따른다. - title-bar와 tab-bar는 화면 상단에 고정하고, 배너부터
추천 오디오까지의 콘텐츠 영역만 스크롤한다. - 화면에 보이는 주요 섹션별로 구현 Phase를 분리할 수 있도록 요구사항을 정리한다.
- 이번 범위에서
추천 시리즈,키워드의 오디오섹션은 구현하지 않는다. - V2 패키지 하위 기존 위젯 중 재사용 가능한 후보를 문서에 기록한다.
- API DTO, 화면 상태, empty/error/loading, click routing은 구현 계획에서 검증 가능하도록 정리한다.
4. Non-Goals
- 이번 PRD 작성 단계에서는 코드, 리소스, 레이아웃 파일을 구현하지 않는다.
추천 시리즈섹션은 이번 범위에서 구현하지 않는다.키워드의 오디오섹션은 이번 범위에서 구현하지 않는다.- 추천 알고리즘, API 응답 정렬 기준, 서버 필드명은 변경하지 않는다.
- 외부 라이브러리를 추가하지 않는다.
- Compose 화면으로 전환하지 않는다.
- 기존 레거시 오디오 메인 화면을 직접 수정하지 않는다.
- Figma asset localhost URL을 앱 코드에 직접 의존하지 않는다.
- Figma에 없는 skeleton loading, shimmer, pull-to-refresh, 임의 애니메이션, 추가 badge는 만들지 않는다.
5. Target Users
- 메인 콘텐츠 탭에서 추천 오디오와 오리지널 시리즈를 탐색하는 앱 사용자.
- 무료/포인트/오리지널/첫 콘텐츠 여부를 카드에서 빠르게 구분하려는 앱 사용자.
- 댓글이 많은 오디오와 추천 오디오 목록을 통해 오디오 콘텐츠 상세로 이동하려는 앱 사용자.
kr.co.vividnext.sodalive.v2하위 메인 콘텐츠 화면을 구현/유지보수하는 Android 개발자.
6. User Stories
- 사용자는 콘텐츠 탭에서
추천내부 탭을 열자마자 배너와 추천 오디오 섹션을 순서대로 보고 싶다. - 사용자는 상단 title-bar와 tab-bar가 스크롤 중에도 유지되어 현재 위치와 탭 맥락을 잃지 않길 기대한다.
- 사용자는 배너를 터치해 이벤트, 크리에이터, 시리즈 또는 링크 목적지로 이동하고 싶다.
- 사용자는 오디오 카드에서 제목, 크리에이터, 썸네일, 무료/포인트/오리지널/첫 콘텐츠/성인 태그를 구분하고 싶다.
- 사용자는 댓글이 많은 오디오에서 최근 댓글 작성자의 프로필 이미지를 함께 보고 콘텐츠에 관심을 가질 수 있어야 한다.
- 개발자는 기존 V2 위젯을 최대한 재사용해 콘텐츠 추천 탭과 다른 V2 화면의 카드 스타일 차이를 줄이고 싶다.
7. Core Features
메인 콘텐츠 추천 API
메인 콘텐츠 추천 탭은 다음 응답을 단일 API 결과로 받아 섹션별 UI 모델로 변환한다.
API Contract
GET /api/v2/audio/recommendations
Response Contract
data class AudioRecommendationsResponse(
val banners: List<AudioBannerResponse>,
val originalSeries: List<OriginalSeriesResponse>,
val latestAudios: List<AudioCardResponse>,
val newAndHotAudios: List<AudioCardResponse>,
val freeAudios: List<AudioCardResponse>,
val pointAudios: List<AudioCardResponse>,
val mostCommentedAudios: List<CommentedAudioResponse>,
val recommendedAudios: List<AudioCardResponse>
)
data class AudioBannerResponse(
val imageUrl: String,
val eventItem: EventItem?,
val creatorId: Long?,
val seriesId: Long?,
val link: String?
)
data class OriginalSeriesResponse(
val seriesId: Long,
val coverImageUrl: String?
)
data class AudioCardResponse(
val audioContentId: Long,
val title: String,
val duration: String?,
val imageUrl: String?,
val price: Int,
val isAdult: Boolean,
val isPointAvailable: Boolean,
val isFirstContent: Boolean,
val isOriginalSeries: Boolean,
val creatorNickname: String
)
data class CommentedAudioResponse(
val audioContentId: Long,
val title: String,
val imageUrl: String?,
val latestComment: String,
val latestCommentWriterProfileImageUrl: String
)
Requirements
- 신규
ContentMainFragment, API, Repository, ViewModel, adapter/helper 등 메인 콘텐츠 추천 탭 연결 하위 코드는kr.co.vividnext.sodalive.v2.main.content.*패키지를 기본 패키지로 작성한다. - 기존 레거시 API 또는 화면 파일을 직접 수정하지 않는다.
- 응답 DTO는 서버 필드명을 임의 변경하지 않고, Jackson
@JsonProperty는 사용하지 않는다. - JSON 매핑 annotation이 필요한 경우 Gson 기반 기존 프로젝트 관례에 맞춰
@SerializedName을 사용한다. - 화면 UI는 DTO를 직접 노출하지 않고 섹션별 UI model 또는 adapter item으로 변환한다.
AudioCardResponse.duration은 DTO에 유지하지만, Figma 카드에 표시 영역이 없으므로 이번 화면 UI에는 표시하지 않는다.- 각 리스트가 비어 있으면 해당 섹션은 숨기는 것을 기본 정책으로 한다.
- 전체 API 실패 시 기존 V2 홈 추천 화면의 loading/error/toast 패턴을 우선 참고한다.
- 이미지 URL이 null이면 기존 이미지 로딩 placeholder 또는 숨김 정책을 구현 계획에서 확인해 따른다.
Edge Cases
banners가 비어 있으면 배너 영역을 숨긴다.imageUrl또는coverImageUrl이 null/blank이면 해당 위젯의 기존 placeholder 정책을 따른다.duration == null이어도 이번 화면 UI에는 표시하지 않으므로 카드 표시는 유지한다.audioContentId <= 0또는seriesId <= 0처럼 목적지 식별자가 유효하지 않은 item은 터치 이동을 무시한다.
고정 Title Bar와 내부 Tab Bar
Figma 24:6738, 24:6739는 화면 상단 고정 영역이다.
Requirements
- title-bar와 tab-bar는 세로 스크롤 대상에 포함하지 않는다.
- 배너 시작 지점부터 하위 콘텐츠만 세로 스크롤한다.
- 내부 tab-bar의
추천탭이 선택된 상태의 화면을 이 PRD 범위로 한다. - 다른 내부 탭의 실제 화면 구현은 이번 범위에 포함하지 않는다.
- title-bar 우측 아이콘은 왼쪽부터
ic_bar_cash,ic_bar_search,ic_bar_storage순서로 배치한다. - title-bar 우측 아이콘 asset은
app/src/main/res/drawable-mdpi/에 위치한 drawable을 사용한다. - title-bar/tab-bar에 사용할 기존 위젯 또는 include layout은 구현 계획에서 현재 메인 V2 구조를 확인해 결정한다.
Phase 1: 배너 섹션
Figma 24:6741 기준으로 최상단 배너 carousel을 표시한다.
Requirements
banners를 가로 스와이프 가능한 배너로 표시한다.- 배너 item은
imageUrl,eventItem,creatorId,seriesId,link를 유지한다. - 배너 터치 시 기존 홈 배너 routing 정책을 우선 참고해 이벤트, 크리에이터, 시리즈, 링크 이동을 처리한다.
- 기존
v2.widget.banner.BannerView와BannerItem재사용을 우선 검토한다.
Edge Cases
- 배너가 1개이면 무한 carousel 또는 auto-scroll 여부는 기존
BannerView정책을 따른다. - 이동 목적지가 없는 배너는 터치해도 아무 동작하지 않는다.
Phase 2: 오직 보이스온에서만!
Figma 24:6745 기준으로 오리지널 시리즈 커버를 가로 목록으로 표시한다.
Requirements
- 섹션 타이틀은
오직 보이스온에서만!이다. originalSeries를 가로 스크롤 시리즈 카드 목록으로 표시한다.- 각 item은
seriesId,coverImageUrl을 사용한다. - Figma에서는 label 없이 커버만 표시되므로 제목/크리에이터명은 노출하지 않는다.
- 기존
SeriesContentCardView또는 시리즈 썸네일 위젯을 재사용할 수 있는지 구현 계획에서 검증한다. - item 터치 시 시리즈 상세 화면으로 이동한다.
Edge Cases
coverImageUrl == null이면 기존 이미지 placeholder 정책을 따른다.seriesId <= 0이면 item 터치 이동을 무시한다.
Phase 3: 새로 올라온 오디오
Figma 24:6751 기준으로 최신 오디오를 가로 카드 목록으로 표시한다.
Requirements
- 섹션 타이틀은
새로 올라온 오디오이다. latestAudios를AudioContentCardSize.Medium에 가까운 가로 카드 목록으로 표시한다.- 카드에는 썸네일, 제목, 크리에이터 닉네임을 표시한다.
isOriginalSeries,isFirstContent,isPointAvailable,price,isAdult는 기존AudioContentTag기반 태그로 매핑한다.- item 터치 시 오디오 콘텐츠 상세로 이동한다.
Edge Cases
title또는creatorNickname이 긴 경우 Figma처럼 한 줄 말줄임 처리한다.price == 0이면 무료 태그 표시 대상으로 본다.
Phase 4: New&Hot
Figma 24:6758 기준으로 New&Hot 오디오를 가로 paging 가능한 리스트 묶음으로 표시한다.
Requirements
- 섹션 타이틀은
New&Hot이다. newAndHotAudios를 88dp 썸네일 기반 세로 리스트 3개 단위의 가로 묶음으로 표시한다.- 각 리스트 item에는 썸네일, 제목, 크리에이터 닉네임, 태그를 표시한다.
- 기존
AudioContentCardView만으로 대응이 어렵다면 최소 범위의 리스트 item view 또는 adapter item을 만든다. - item 터치 시 오디오 콘텐츠 상세로 이동한다.
Edge Cases
- item 수가 3개 미만이면 남은 빈 row를 만들지 않는다.
- 마지막 묶음이 3개 미만이어도 기존 item 간격을 유지한다.
Phase 5: 무료 오디오
Figma 24:6807 기준으로 무료 오디오 가로 카드 목록을 표시한다.
Requirements
- 섹션 타이틀은
무료 오디오이다. freeAudios를 가로 카드 목록으로 표시한다.- 무료 태그는 섹션 membership으로 강제하지 않고, 공통 태그 정책에 따라
price == 0일 때 표시한다. - 오리지널/첫 콘텐츠/성인/포인트 태그도 응답 값에 따라 함께 표시한다.
- item 터치 시 오디오 콘텐츠 상세로 이동한다.
Phase 6: 포인트 오디오
Figma 24:6813 기준으로 포인트 오디오 가로 카드 목록을 표시한다.
Requirements
- 섹션 타이틀은
포인트 오디오이다. pointAudios를 가로 카드 목록으로 표시한다.- 포인트 태그는 섹션 membership으로 강제하지 않고, 공통 태그 정책에 따라
isPointAvailable == true일 때 표시한다. - 무료/오리지널/첫 콘텐츠/성인 태그도 응답 값에 따라 함께 표시한다.
- item 터치 시 오디오 콘텐츠 상세로 이동한다.
Phase 7: 최근 댓글이 많은 오디오
Figma 24:6820 기준으로 댓글이 많은 오디오를 가로 카드 묶음으로 표시한다.
Requirements
- 섹션 타이틀은
최근 댓글이 많은 오디오이다. mostCommentedAudios를 가로 스크롤 가능한 카드 묶음으로 표시한다.- 각 카드에는 오디오 리스트 item, 최근 댓글 본문, 최근 댓글 작성자 프로필 이미지 영역을 함께 표시한다.
- 댓글 영역에는
latestComment를 표시한다. - item 터치 시 오디오 콘텐츠 상세로 이동한다.
Edge Cases
latestCommentWriterProfileImageUrl이 blank이면 프로필 이미지 placeholder 정책을 따른다.latestComment가 blank이면 댓글 영역을 숨긴다.
Phase 8: 추천 오디오
Figma 24:6842 기준으로 추천 오디오를 2열 그리드로 표시한다.
Requirements
- 섹션 타이틀은
추천 오디오이다. recommendedAudios를 2열 그리드로 표시한다.- 카드에는 썸네일, 제목, 크리에이터 닉네임, 태그를 표시한다.
- 기존
AudioContentCardView의AudioContentCardSize.Large재사용을 우선 검토한다. - item 터치 시 오디오 콘텐츠 상세로 이동한다.
Edge Cases
- 홀수 개수이면 마지막 item은 좌측 정렬로 표시한다.
- 긴 제목/크리에이터명은 한 줄 말줄임 처리한다.
제외 섹션
Figma에는 존재하지만 이번 범위에서는 구현하지 않는다.
Non-Implemented Sections
추천 시리즈: Figma24:6770키워드의 오디오: Figma24:6829
Requirements
- API 응답에 대응 필드가 없거나 이번 범위에서 명시 제외된 섹션은 화면에 placeholder로도 만들지 않는다.
- 구현 계획 문서에서는 이 두 섹션을 Non-Goals로 유지한다.
재사용 가능한 V2 위젯 후보
구현 전 다음 V2 위젯과 기존 화면 패턴을 우선 검토한다.
Widget Candidates
app/src/main/java/kr/co/vividnext/sodalive/v2/widget/banner/BannerView.kt: 배너 carousel, counter, auto-scroll.app/src/main/java/kr/co/vividnext/sodalive/v2/widget/banner/BannerItem.kt: 배너 item 계약 후보.app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentCardView.kt: 오디오 카드 썸네일/라벨/태그 표시.app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentCardSize.kt:Large,Medium,Small카드 크기.app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentTag.kt:Original,Point,First,Free태그 매핑.app/src/main/java/kr/co/vividnext/sodalive/v2/widget/SeriesContentCardView.kt: 시리즈 커버 카드 후보.app/src/main/java/kr/co/vividnext/sodalive/v2/widget/SeriesContentCardSize.kt: 시리즈 카드 크기.app/src/main/java/kr/co/vividnext/sodalive/v2/widget/TextTabBarView.kt: 텍스트 탭바 후보.app/src/main/res/layout/view_section_title.xml: 섹션 타이틀 include 후보.app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt: V2 홈 추천의 loading/error/empty, 배너 routing, section visibility 참고.app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeBannerBinder.kt: 배너 바인딩 및 이미지 로딩 참고.
Reuse Constraints
- 기존 위젯을 재사용하되, 현재 동작을 깨는 방식으로 기존 위젯을 확장하지 않는다.
- 재사용 후보가 Figma 요구사항과 맞지 않으면 신규 wrapper/adapter/item view를 V2 하위에 최소 범위로 추가한다.
- 레거시 파일 수정이 필요해 보이면 구현 전에 사용자 확인을 받는다.
8. UX / UI Expectations
- 전체 배경은 Figma처럼 black 계열을 유지한다.
- title-bar와 tab-bar는 고정되고, 콘텐츠 영역만 세로 스크롤된다.
- 섹션은 Figma 순서대로 배치한다: 배너 → 오직 보이스온에서만! → 새로 올라온 오디오 → New&Hot → 무료 오디오 → 포인트 오디오 → 최근 댓글이 많은 오디오 → 추천 오디오.
- 각 섹션은 데이터가 없으면 숨겨 스크롤 중 빈 영역이 생기지 않게 한다.
- 가로 스크롤 섹션은 Figma 기준 좌측 inset과 item spacing을 구현 계획에서 수치화한다.
- 카드 썸네일은 centerCrop과 radius 14dp를 기본으로 한다.
- 섹션 타이틀, 카드 타이틀, 크리에이터명은 기존 typography resource를 우선 사용한다.
- 성인 콘텐츠는
isAdult값에 따라 기존 성인 태그 또는 이에 준하는 표시를 한다. - 접근성을 위해 주요 카드와 배너는 터치 가능한 item임을 유지하고, 장식용 태그 아이콘은 기존 위젯 정책처럼 불필요한 접근성 읽기를 피한다.
9. Technical Constraints
- Android Gradle 단일
:app모듈 내에서 작업한다. - 메인 콘텐츠 추천 탭의 기본 패키지는
kr.co.vividnext.sodalive.v2.main.content.*로 한다. - 기존
ContentMainFragment도kr.co.vividnext.sodalive.v2.main.content패키지 안쪽에 배치한다. - 신규
Activity,Fragment,ViewModel, API, Repository, adapter/helper 코드는 기본 패키지 하위에 작성한다. - 레이어 흐름은 기존 관례인
Api -> Repository -> ViewModel -> Fragment를 따른다. - DI는
app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt의 Koin 구성을 따른다. - 네트워크 응답은 기존
ApiResponse<T>와 RxSingle사용 패턴을 우선 따른다. - 기존 레거시 오디오 화면 파일은 직접 수정하지 않는다.
- 공개 API 스키마를 임의 변경하지 않는다.
- 문구는 string resource 기반으로 관리한다.
- 구현 계획 작성 시 화면에 보이는 섹션별 Phase를 분리하고 각 Phase별 검증 기준을 둔다.
10. Metrics
- API 성공 시 각 응답 리스트가 대응 섹션에 표시되는지 확인한다.
- 비어 있는 리스트의 섹션이 화면에서 숨겨지는지 확인한다.
- title-bar와 tab-bar가 스크롤 중 고정되는지 확인한다.
- 배너, 오디오 카드, 시리즈 카드 터치 시 유효한 목적지로 이동하는지 확인한다.
- 태그 표시가
price,isPointAvailable,isFirstContent,isOriginalSeries,isAdult값과 일치하는지 확인한다. - Figma 기준 섹션 순서와 제외 섹션 미노출 여부를 수동 확인한다.
11. Open Questions
- title-bar와 내부 tab-bar에 사용할 기존 layout/include가 확정되어 있는지 구현 계획 단계에서 확인이 필요하다.
- 배너
link가 외부 URL, 앱 딥링크, 웹뷰 중 어떤 방식으로 처리되어야 하는지 기존 홈 배너 routing을 확인한 뒤 결정한다.