docs(creator): 오디오 탭 공통 UI 재사용 계획을 갱신한다
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
|
||||
**Goal:** `GET /api/v2/creator-channels/{creatorId}/audio` 응답을 기반으로 크리에이터 채널의 `오디오` 탭에 테마 필터, 정렬, 소장률, 오디오 콘텐츠 목록, empty 상태, 본인 채널 전용 하단 `오디오 올리기` CTA와 pagination을 표시한다.
|
||||
|
||||
**Architecture:** 기존 `CreatorChannelActivity`의 `ViewPager2`/`CreatorChannelPagerAdapter` 구조를 유지하고, `CreatorChannelTab.Audio`의 placeholder를 신규 `CreatorChannelAudioFragment`로 교체한다. 오디오 탭 전용 Fragment/ViewModel/DTO/mapper/UI model/adapter는 `kr.co.vividnext.sodalive.v2.creator.channel.audio` 하위에 두되, API/Repository는 기존 채널 공통 `CreatorChannelApi`/`CreatorChannelRepository`에 endpoint만 추가한다. 라이브 탭에서 검증된 sort popup, replay item 상태 표시, 하단 CTA inset 처리, 상세 이동 경로는 가능한 한 재사용하거나 동일 패턴으로 최소 구현한다.
|
||||
**Architecture:** 기존 `CreatorChannelActivity`의 `ViewPager2`/`CreatorChannelPagerAdapter` 구조를 유지하고, `CreatorChannelTab.Audio`의 placeholder를 신규 `CreatorChannelAudioFragment`로 교체한다. 오디오 탭 전용 Fragment/ViewModel/DTO/mapper는 `kr.co.vividnext.sodalive.v2.creator.channel.audio` 하위에 두되, API/Repository는 기존 채널 공통 `CreatorChannelApi`/`CreatorChannelRepository`에 endpoint만 추가한다. 테마 tab-bar는 v2 공통 `CapsuleTabBarView`를 사용하고, 라이브 탭에서 검증된 replay item layout/adapter/model/status, sort popup, 하단 owner CTA, scroll-to-bottom hook, 상세 이동 경로는 크리에이터 채널 공통 이름으로 rename/move해 재사용한다.
|
||||
|
||||
**Tech Stack:** Kotlin, Android XML Views, ViewBinding, RecyclerView, Retrofit, Gson, RxJava3, Koin, JUnit4/Robolectric local unit test.
|
||||
|
||||
@@ -37,8 +37,14 @@
|
||||
- 클라이언트가 `themes` 맨 앞에 `전체` synthetic tab을 추가한다.
|
||||
- `CreatorChannelAudioTabResponse.themeId == null`이면 `전체` tab 선택 상태로 표시한다.
|
||||
- `ContentSort`와 `CreatorChannelAudioContentResponse`는 기존 타입을 재사용한다.
|
||||
- theme tab-bar는 v2 공통 `CapsuleTabBarView`를 재사용하고 별도 theme adapter/item layout을 만들지 않는다.
|
||||
- original/first/point/free tag 표현과 순서는 v2 공통 `AudioContentTag`를 재사용한다.
|
||||
- `duration == null`인 오디오 콘텐츠는 서버에서 내려오지 않는 계약이며, 예외적으로 내려오면 해당 item을 숨긴다.
|
||||
- `seriesName`이 있으면 `duration • seriesName`, 없으면 `duration`만 표시한다.
|
||||
- 라이브 다시듣기 item과 오디오 콘텐츠 item은 동일한 UI를 사용하므로, 별도 중복 layout/adapter를 만들지 않고 기존 `item_creator_channel_live_replay.xml`, `CreatorChannelLiveReplayAdapter`, live replay UI model/status를 creator channel 공통 오디오 콘텐츠 item으로 rename/move해 재사용한다.
|
||||
- 기존 `CreatorChannelLiveSortPopup`과 `view_creator_channel_live_sort_menu.xml`은 creator channel 공통 sort popup 이름으로 rename/move해 라이브/오디오가 함께 사용한다.
|
||||
- 기존 live owner CTA container는 creator channel 공통 owner CTA로 rename하고 현재 탭에 따라 label/icon/click action만 바꿔 재사용한다.
|
||||
- 기존 live scroll-to-bottom hook은 creator channel 공통 scroll hook으로 확장해 현재 탭이 Live/Audio일 때 각 Fragment의 load-more entry를 호출한다.
|
||||
- 내 채널이 아니고 `themeId == null`인 전체 테마 상태에서만 소장률 UI를 표시한다.
|
||||
- 소장률 UI는 `purchasedAudioContentRate` percent 값, `purchasedAudioContentCount`, `paidAudioContentCount`를 사용한다.
|
||||
- 내 채널이면 소장률 UI를 숨기고 하단 고정 `오디오 올리기` CTA를 표시한다.
|
||||
@@ -58,17 +64,17 @@
|
||||
---
|
||||
|
||||
## Figma 참조 필요 Phase
|
||||
- Phase 1: 부분 참조
|
||||
- Phase 1: 제한 참조
|
||||
- 기존 코드 경계, endpoint, 리소스 존재 여부 확인이 중심이며 Figma는 PRD 기준만 확인한다.
|
||||
- Phase 2: Figma 참조 불필요
|
||||
- API/DTO/Repository/ViewModel 상태 모델은 서버 계약과 기존 라이브 탭 패턴을 따른다.
|
||||
- Phase 3: 부분 참조
|
||||
- Phase 3: 제한 참조
|
||||
- mapper는 PRD와 Figma item variant의 상태 표시를 함께 확인한다.
|
||||
- Phase 4: 필수 참조
|
||||
- 테마 tab-bar, Sort-bar, 소장률 카드, empty, CTA는 Figma `290:9015`, `290:9029`, `290:8965`, `665:19008`을 기준으로 구현한다.
|
||||
- Phase 5: 필수 참조
|
||||
- 오디오 콘텐츠 item은 Figma `290:9026`과 기존 `item_creator_channel_live_replay.xml` 구조를 대조한다.
|
||||
- Phase 6: 부분 참조
|
||||
- 오디오 콘텐츠 item은 Figma `290:9026`과 공통화 전 기존 `item_creator_channel_live_replay.xml` 구조를 대조한다.
|
||||
- Phase 6: 제한 참조
|
||||
- 탭 연결, pagination, navigation, owner CTA 동작은 기존 코드 패턴 중심으로 검증한다.
|
||||
- Phase 7: 필수 참조
|
||||
- 최종 수동 화면 검증은 PRD의 모든 Figma 노드와 실제 화면을 대조한다.
|
||||
@@ -79,9 +85,9 @@
|
||||
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapter.kt`
|
||||
- `CreatorChannelTab.Audio`를 신규 `CreatorChannelAudioFragment`로 연결한다.
|
||||
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt`
|
||||
- 오디오 탭 scroll-to-bottom, owner CTA 표시/클릭, 오디오 콘텐츠 클릭 callback을 연결한다.
|
||||
- 기존 live 전용 scroll-to-bottom hook과 owner CTA 표시/클릭을 creator channel 공통 처리로 확장하고, 오디오 콘텐츠 클릭 callback을 연결한다.
|
||||
- 수정: `app/src/main/res/layout/activity_creator_channel.xml`
|
||||
- 라이브 CTA와 동일한 하단 고정 방식으로 오디오 CTA를 추가하거나, 기존 CTA 영역을 현재 탭에 맞게 label/icon을 바꾸는 방식으로 재사용한다.
|
||||
- 기존 `layout_creator_channel_live_owner_cta`/`btn_creator_channel_live_owner_cta`를 creator channel 공통 owner CTA 이름으로 rename하고 현재 탭에 맞게 label/icon을 바꿔 재사용한다.
|
||||
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelApi.kt`
|
||||
- 오디오 탭 endpoint를 추가한다.
|
||||
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelRepository.kt`
|
||||
@@ -91,25 +97,25 @@
|
||||
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/CreatorChannelAudioViewModel.kt`
|
||||
- 최초 조회, 정렬 변경, 테마 변경, retry, pagination, loading/error/empty/content 상태를 관리한다.
|
||||
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/model/CreatorChannelAudioUiModels.kt`
|
||||
- theme tab, 소장률, item, 상태, 화면 상태 UI model을 정의한다.
|
||||
- theme tab, 소장률, 화면 상태 UI model을 정의한다. item model/status는 공통 오디오 콘텐츠 item model/status를 재사용한다.
|
||||
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/model/CreatorChannelAudioMappers.kt`
|
||||
- DTO를 UI model로 변환하고 `전체` synthetic theme, series secondary text, 소장률 표시 여부, item status를 결정한다.
|
||||
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/CreatorChannelAudioFragment.kt`
|
||||
- 오디오 탭 UI, adapter, sort popup, theme click, pagination, host callback 연결을 담당한다.
|
||||
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/ui/CreatorChannelAudioContentAdapter.kt`
|
||||
- 오디오 콘텐츠 목록 RecyclerView adapter를 담당한다.
|
||||
- 생성 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/ui/CreatorChannelAudioThemeAdapter.kt`
|
||||
- theme tab-bar를 RecyclerView로 구성할 때만 추가한다. 단순 `LinearLayout` 동적 view로 충분하면 생성하지 않는다.
|
||||
- 재사용 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/ui/CreatorChannelLiveSortPopup.kt`
|
||||
- 이름이 라이브 전용이라 오디오에서 직접 사용하기 어렵다면 `creator/channel/ui/CreatorChannelSortPopup.kt`로 이동/rename하고 라이브/오디오가 함께 사용한다.
|
||||
- 오디오 탭 UI, 공통 오디오 콘텐츠 adapter, 공통 sort popup, `CapsuleTabBarView` theme click, pagination, host callback 연결을 담당한다.
|
||||
- move/rename: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/ui/CreatorChannelLiveReplayAdapter.kt` → `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelAudioContentAdapter.kt`
|
||||
- 라이브 다시듣기와 오디오 콘텐츠 목록 RecyclerView adapter를 함께 담당하며, 공통 오디오 콘텐츠 item layout을 inflate한다.
|
||||
- move/rename: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/ui/CreatorChannelLiveSortPopup.kt` → `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelSortPopup.kt`
|
||||
- 기존 라이브 sort popup 구현을 크리에이터 채널 공통 sort popup으로 사용한다.
|
||||
- move/rename: `ContentSort.toLabelResId()`/`toSortOptionUiModel()`과 sort option UI model
|
||||
- `creator/channel/live/model`에서 creator channel 공통 model/ui 위치로 이동해 라이브/오디오가 함께 사용한다.
|
||||
- 생성: `app/src/main/res/layout/fragment_creator_channel_audio.xml`
|
||||
- theme tab-bar, Sort-bar, 소장률 카드, RecyclerView, empty/error/retry 영역을 포함한다.
|
||||
- 생성: `app/src/main/res/layout/item_creator_channel_audio_content.xml`
|
||||
- 오디오 콘텐츠 item layout이다. 라이브 다시듣기 item과 동일 구조를 유지하되 id prefix는 audio로 둔다.
|
||||
- 생성 후보: `app/src/main/res/layout/item_creator_channel_audio_theme.xml`
|
||||
- theme tab-bar를 RecyclerView로 구성할 때만 추가한다.
|
||||
- 수정 또는 생성: `app/src/main/res/layout/view_creator_channel_live_sort_menu.xml`
|
||||
- sort popup을 공용화하면 `view_creator_channel_sort_menu.xml`로 rename하고 라이브/오디오 참조를 함께 갱신한다.
|
||||
- `@layout/view_capsule_tab_bar` 기반 theme tab-bar, Sort-bar, 소장률 카드, RecyclerView, empty/error/retry 영역을 포함한다.
|
||||
- rename: `app/src/main/res/layout/item_creator_channel_live_replay.xml` → `app/src/main/res/layout/item_creator_channel_audio_content.xml`
|
||||
- 크리에이터 채널 공통 오디오 콘텐츠 item layout이다. 기존 라이브 다시듣기 item과 동일 UI를 유지하고 id prefix는 `creator_channel_audio_content`로 둔다.
|
||||
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/ui/CreatorChannelLiveReplayAdapter.kt`
|
||||
- 공통 adapter로 move/rename하면서 live Fragment 참조를 함께 갱신한다.
|
||||
- rename: `app/src/main/res/layout/view_creator_channel_live_sort_menu.xml` → `app/src/main/res/layout/view_creator_channel_sort_menu.xml`
|
||||
- 기존 live sort popup layout을 creator channel 공통 sort popup layout으로 사용한다.
|
||||
- 수정: `app/src/main/res/values/strings.xml`
|
||||
- 오디오 탭 empty/error/retry/CTA/소장률 문구를 추가한다.
|
||||
- 수정: `app/src/main/res/values-en/strings.xml`, `app/src/main/res/values-ja/strings.xml`
|
||||
@@ -139,13 +145,13 @@
|
||||
- 작업:
|
||||
- Sort popup을 공용화할지, 오디오 전용으로 얇게 복제할지 결정한다.
|
||||
- replay item adapter의 상태 표시 정책을 오디오 item에 그대로 사용할 수 있는지 확인한다.
|
||||
- 라이브 탭 파일을 무리하게 리팩터링하지 않고 오디오 탭 구현에 필요한 최소 공용화만 계획에 반영한다.
|
||||
- 라이브 탭 파일은 오디오 탭과 동일하게 쓰는 item/sort/CTA/scroll-to-bottom 영역만 creator channel 공통 이름으로 rename/move한다.
|
||||
- 검증:
|
||||
- 라이브 탭 기존 테스트가 공용화 후에도 유지되어야 할 검증 목록을 기록한다.
|
||||
- 검증 기록:
|
||||
- 2026-06-19 확인 완료. `CreatorChannelLiveFragment`는 `CreatorChannelLiveSortPopup`, `CreatorChannelLiveReplayAdapter`, `toReplayUiModel()`을 직접 사용하고, sort label은 `ContentSort.toLabelResId()` 확장으로 분리되어 있다.
|
||||
- `CreatorChannelLiveSortPopup`은 class/package명과 `view_creator_channel_live_sort_menu`, `layout_creator_channel_live_sort_options`, `tv_creator_channel_live_sort_option_sample` id가 모두 live 전용이므로 Phase 3에서 `creator/channel/ui/CreatorChannelSortPopup.kt`와 공용 layout으로 이동하는 방식이 오디오 참조에 적합하다.
|
||||
- replay item 상태 정책은 mapper의 `Owned > Rented > Play(무료) > Price` 우선순위와 adapter의 badge/tag/status bind를 오디오 item에 동일 적용할 수 있다. 단, layout id prefix는 오디오 전용으로 유지해 라이브 adapter를 무리하게 공용화하지 않는다.
|
||||
- replay item 상태 정책은 mapper의 `Owned > Rented > Play(무료) > Price` 우선순위와 adapter의 badge/tag/status bind를 오디오 item에 동일 적용할 수 있다. 후속 재검토 결과 item UI가 동일하므로 live replay adapter/model/status/layout을 creator channel 공통 오디오 콘텐츠 item 이름으로 rename/move해 라이브와 오디오가 함께 사용한다.
|
||||
- 공용화 후 유지해야 할 회귀 검증은 `CreatorChannelLiveFragmentLayoutTest`, `CreatorChannelActivitySourceTest`의 live sort/pagination/owner CTA 관련 source 검증, `CreatorChannelPagerAdapterTest`이다.
|
||||
|
||||
- [x] **Task 1.2: 오디오 업로드 CTA와 기존 진입점 확인**
|
||||
@@ -328,17 +334,19 @@
|
||||
- 2026-06-19 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.audio.CreatorChannelAudioPaginationTest"` 실행 결과 BUILD SUCCESSFUL.
|
||||
- 2026-06-19 후속 리뷰 지적으로 선택 테마 요청 뒤 서버가 없는 `themeId`를 응답하면 ViewModel 내부 `selectedThemeId`가 이전 선택값으로 남는 문제가 확인됐다. `CreatorChannelAudioViewModelTest`에 정렬 후속 요청이 정규화된 `null` themeId를 사용하는 회귀 테스트를 추가하고, 첫 페이지 성공 응답 처리 시 private `selectedThemeId`도 `effectiveSelectedThemeId()`로 갱신하도록 수정했다.
|
||||
|
||||
- [x] **Task 3.3: sort popup 공용화 여부 반영**
|
||||
- 수정 후보:
|
||||
- [x] **Task 3.3: sort popup 공통 rename/move 결정 반영**
|
||||
- move/rename:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/ui/CreatorChannelLiveSortPopup.kt`
|
||||
→ `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelSortPopup.kt`
|
||||
- `app/src/main/res/layout/view_creator_channel_live_sort_menu.xml`
|
||||
→ `app/src/main/res/layout/view_creator_channel_sort_menu.xml`
|
||||
- 수정:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveFragment.kt`
|
||||
- 생성 후보:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelSortPopup.kt`
|
||||
- `app/src/main/res/layout/view_creator_channel_sort_menu.xml`
|
||||
- 작업:
|
||||
- 라이브 전용 이름이 오디오 참조를 어렵게 만들면 공용 `CreatorChannelSortPopup`으로 이동한다.
|
||||
- 공용화하더라도 기존 라이브 탭 UI와 테스트 기대값을 깨지 않는다.
|
||||
- 라이브 전용 sort popup을 `CreatorChannelSortPopup`으로 rename/move해 라이브/오디오가 함께 사용한다.
|
||||
- popup layout id prefix도 live 전용 이름에서 creator channel sort 공통 이름으로 변경한다.
|
||||
- `ContentSort.toLabelResId()`와 sort option UI model/mapping이 live 전용 파일에 있으면 creator channel 공통 model/ui 위치로 이동한다.
|
||||
- 기존 라이브 탭 UI와 테스트 기대값을 깨지 않는다.
|
||||
- 검증 명령:
|
||||
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveFragmentLayoutTest"`
|
||||
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.audio.CreatorChannelAudioMapperTest"`
|
||||
@@ -346,7 +354,7 @@
|
||||
- 라이브 탭 정렬 테스트와 오디오 mapper 테스트가 모두 PASS한다.
|
||||
- 검증 기록:
|
||||
- 2026-06-19 탐색 결과 `CreatorChannelLiveSortPopup`과 `view_creator_channel_live_sort_menu.xml`은 class/layout/id/test가 live 전용 이름에 강하게 묶여 있고, Phase 3 mapper 구현에는 즉시 필요하지 않음을 확인했다.
|
||||
- 2026-06-19 Phase 3에서는 sort popup 공용화를 보류하고 live sort 관련 production/resource 파일을 변경하지 않았다. 공용화 필요 여부는 오디오 Sort-bar 연결이 진행되는 Phase 5에서 다시 판단한다.
|
||||
- 2026-06-19 Phase 3에서는 sort popup 공통 rename/move를 실제 코드에 적용하지 않았다. 후속 재사용 검토 결과 오디오 Sort-bar에서 동일 UI/동작을 사용하므로 Phase 5에서 `CreatorChannelSortPopup` 공통 이름으로 rename/move해 적용한다.
|
||||
|
||||
---
|
||||
|
||||
@@ -356,11 +364,11 @@
|
||||
- 생성:
|
||||
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/CreatorChannelAudioFragmentLayoutTest.kt`
|
||||
- 테스트 케이스:
|
||||
- `fragment_creator_channel_audio.xml`에 theme tab container, Sort-bar, 소장률 card, RecyclerView, empty container, error message, retry button이 존재한다.
|
||||
- `fragment_creator_channel_audio.xml`에 `CapsuleTabBarView`, Sort-bar, 소장률 card, RecyclerView, empty container, error message, retry button이 존재한다.
|
||||
- empty message text는 `@string/creator_channel_audio_empty_message`를 사용한다.
|
||||
- Sort-bar는 `전체`, count, sort label, `ic_new_sort` icon을 가진다.
|
||||
- 소장률 card는 percent 문구, count 문구, progress bar track/fill view를 가진다.
|
||||
- item layout은 88dp thumbnail, title, secondary text, play/price/status 영역을 가진다.
|
||||
- 공통 item layout `item_creator_channel_audio_content.xml`은 88dp thumbnail, title, secondary text, play/price/status 영역을 가진다.
|
||||
- 검증 명령:
|
||||
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.audio.CreatorChannelAudioFragmentLayoutTest"`
|
||||
- 기대 결과:
|
||||
@@ -374,7 +382,7 @@
|
||||
- `app/src/main/res/values-en/strings.xml`
|
||||
- `app/src/main/res/values-ja/strings.xml`
|
||||
- 작업:
|
||||
- Figma `290:9015` 기준으로 black background, theme tab-bar, Sort-bar, 소장률 card, list, empty/error 영역을 배치한다.
|
||||
- Figma `290:9015` 기준으로 black background, `@layout/view_capsule_tab_bar`, Sort-bar, 소장률 card, list, empty/error 영역을 배치한다.
|
||||
- 소장률 card는 `themeId == null`/내 채널 여부에 따라 Fragment에서 visibility를 제어할 수 있도록 별도 container id를 둔다.
|
||||
- empty 상태에서 콘텐츠 UI를 모두 숨길 수 있도록 각 section id를 분리한다.
|
||||
- `creator_channel_audio_empty_message`, `creator_channel_audio_error_message`, `creator_channel_audio_upload_button`, `creator_channel_audio_total_label`, `creator_channel_audio_owned_rate_message` 문자열을 추가한다.
|
||||
@@ -384,18 +392,21 @@
|
||||
- 기대 결과:
|
||||
- 리소스 merge와 layout 테스트가 PASS한다.
|
||||
|
||||
- [ ] **Task 4.3: 오디오 콘텐츠 item layout 작성**
|
||||
- 생성:
|
||||
- `app/src/main/res/layout/item_creator_channel_audio_content.xml`
|
||||
- [ ] **Task 4.3: 라이브 다시듣기 item layout을 공통 오디오 콘텐츠 item layout으로 rename**
|
||||
- rename:
|
||||
- `app/src/main/res/layout/item_creator_channel_live_replay.xml` → `app/src/main/res/layout/item_creator_channel_audio_content.xml`
|
||||
- 작업:
|
||||
- `item_creator_channel_live_replay.xml`과 동일한 구조를 따르되 id prefix를 `creator_channel_audio_content`로 둔다.
|
||||
- 별도 중복 layout을 생성하지 않고 기존 라이브 다시듣기 item layout을 크리에이터 채널 공통 오디오 콘텐츠 item layout으로 사용한다.
|
||||
- layout id prefix를 `creator_channel_live_replay`에서 `creator_channel_audio_content`로 변경한다.
|
||||
- thumbnail 88dp, radius 14dp clip, adult badge, original/first/point/free tag, title, secondary text, play/price/status 영역을 포함한다.
|
||||
- secondary text는 `duration • seriesName` 또는 `duration`이 한 줄 말줄임 처리되도록 한다.
|
||||
- 기존 duration TextView는 별도 seriesName TextView를 추가하지 않고 공통 secondary text TextView로 rename한다.
|
||||
- secondary text TextView는 라이브 다시듣기에서 `duration`, 오디오 탭에서 `duration • seriesName` 또는 `duration`을 한 줄 말줄임 처리한다.
|
||||
- 검증 명령:
|
||||
- `./gradlew :app:mergeDebugResources`
|
||||
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.audio.CreatorChannelAudioFragmentLayoutTest"`
|
||||
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveFragmentLayoutTest"`
|
||||
- 기대 결과:
|
||||
- item layout test가 PASS한다.
|
||||
- 공통 item layout test와 기존 라이브 다시듣기 layout test가 모두 PASS한다.
|
||||
|
||||
- [ ] **Task 4.4: `CreatorChannelAudioFragment` 골격 구현**
|
||||
- 생성:
|
||||
@@ -416,31 +427,42 @@
|
||||
|
||||
### Phase 5: Adapter, item bind, theme tab, sort UI 구현
|
||||
|
||||
- [ ] **Task 5.1: 오디오 콘텐츠 adapter 구현**
|
||||
- 생성:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/ui/CreatorChannelAudioContentAdapter.kt`
|
||||
- [ ] **Task 5.1: 라이브 다시듣기 adapter를 공통 오디오 콘텐츠 adapter로 rename/move**
|
||||
- move/rename:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/ui/CreatorChannelLiveReplayAdapter.kt`
|
||||
→ `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelAudioContentAdapter.kt`
|
||||
- 수정:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveFragment.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/model/CreatorChannelLiveUiModels.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/model/CreatorChannelLiveMappers.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/model/CreatorChannelAudioUiModels.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/model/CreatorChannelAudioMappers.kt`
|
||||
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveFragmentLayoutTest.kt`
|
||||
- 작업:
|
||||
- `CreatorChannelAudioContentUiModel` list를 bind한다.
|
||||
- 별도 오디오 전용 adapter를 만들지 않고 기존 live replay adapter를 creator channel 공통 `CreatorChannelAudioContentAdapter`로 사용한다.
|
||||
- live replay UI model/status 중 item bind에 필요한 필드는 creator channel 공통 오디오 콘텐츠 item model/status로 rename/move한다.
|
||||
- 오디오 mapper와 라이브 mapper가 같은 공통 item model/status를 반환하도록 조정한다.
|
||||
- `ItemCreatorChannelAudioContentBinding`을 사용한다.
|
||||
- 이미지 로딩은 기존 `loadUrl`/placeholder 정책을 따른다.
|
||||
- adult badge, original/first/point/free tag, play/owned/rented/price 상태를 라이브 다시듣기 item과 동일하게 bind한다.
|
||||
- 오디오 탭의 `secondaryText`는 mapper 결과 그대로 표시하고, 라이브 다시듣기는 기존처럼 `duration`만 표시한다.
|
||||
- item 클릭 시 `audioContentId`를 host callback으로 전달한다.
|
||||
- 검증 명령:
|
||||
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.audio.CreatorChannelAudioFragmentLayoutTest"`
|
||||
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveFragmentLayoutTest"`
|
||||
- `./gradlew :app:compileDebugKotlin`
|
||||
- 기대 결과:
|
||||
- layout/source 테스트와 컴파일이 PASS한다.
|
||||
- 오디오 layout/source 테스트, 기존 라이브 다시듣기 layout/source 테스트, 컴파일이 모두 PASS한다.
|
||||
|
||||
- [ ] **Task 5.2: theme tab-bar 구현**
|
||||
- [ ] **Task 5.2: `CapsuleTabBarView`로 theme tab-bar 연결**
|
||||
- 수정:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/CreatorChannelAudioFragment.kt`
|
||||
- 생성 후보:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/ui/CreatorChannelAudioThemeAdapter.kt`
|
||||
- `app/src/main/res/layout/item_creator_channel_audio_theme.xml`
|
||||
- 작업:
|
||||
- `CreatorChannelAudioThemeUiModel`을 화면에 표시한다.
|
||||
- 별도 theme adapter/item layout을 만들지 않고 v2 공통 `CapsuleTabBarView`를 사용한다.
|
||||
- `CreatorChannelAudioThemeUiModel.title` 목록을 `CapsuleTabBarView.setMenus()`에 전달한다.
|
||||
- `isSelected == true`인 theme의 index를 selected index로 전달한다.
|
||||
- 첫 번째 `전체` tab은 mapper가 만든 synthetic model을 사용한다.
|
||||
- 선택 tab은 white background/black text, 미선택 tab은 black background/gray border/white text로 표시한다.
|
||||
- tab click은 `viewModel.changeTheme(themeId)`를 호출한다.
|
||||
- `setOnTabSelectedListener`의 index를 `CreatorChannelAudioThemeUiModel.themeId`로 역매핑해 `viewModel.changeTheme(themeId)`를 호출한다.
|
||||
- 같은 tab click은 ViewModel에서 no-op 처리한다.
|
||||
- 검증 명령:
|
||||
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.audio.CreatorChannelAudioViewModelTest"`
|
||||
@@ -451,10 +473,16 @@
|
||||
- [ ] **Task 5.3: Sort-bar와 sort popup 연결**
|
||||
- 수정:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/CreatorChannelAudioFragment.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveFragment.kt`
|
||||
- move/rename:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/ui/CreatorChannelLiveSortPopup.kt`
|
||||
→ `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelSortPopup.kt`
|
||||
- `app/src/main/res/layout/view_creator_channel_live_sort_menu.xml`
|
||||
→ `app/src/main/res/layout/view_creator_channel_sort_menu.xml`
|
||||
- 작업:
|
||||
- Sort-bar count는 `audioContentCount`를 표시한다.
|
||||
- sort label은 `ContentSort.toLabelResId()`를 사용한다.
|
||||
- sort button click 시 라이브 탭과 동일한 sort popup을 표시한다.
|
||||
- sort button click 시 라이브 탭에서 쓰던 popup을 공통 `CreatorChannelSortPopup` 이름으로 rename/move해 표시한다.
|
||||
- popup option 선택 시 `viewModel.changeSort(sort)`를 호출한다.
|
||||
- 같은 sort 선택은 ViewModel에서 no-op 처리하고 popup만 닫는다.
|
||||
- 검증 명령:
|
||||
@@ -500,14 +528,14 @@
|
||||
- 작업:
|
||||
- `CreatorChannelAudioFragment.Host`를 구현한다.
|
||||
- item click은 `startAudioContentDetail(audioContentId)`로 연결한다.
|
||||
- 스크롤 하단 도달 시 현재 탭이 Audio이면 `findAudioFragment()?.onCreatorChannelAudioScrolledToBottom()`를 호출한다.
|
||||
- content height 변화 시 기존 live tab content changed 처리와 같은 방식으로 sticky/viewport 보정을 호출한다.
|
||||
- 기존 live 전용 scroll-to-bottom hook을 creator channel 공통 scroll hook 이름으로 rename하고, 현재 탭이 Live/Audio인지에 따라 해당 Fragment의 load-more 진입점을 호출한다.
|
||||
- content height 변화 시 기존 live tab content changed 처리를 creator channel 공통 content changed 처리로 rename해 Live/Audio가 같은 sticky/viewport 보정을 사용한다.
|
||||
- 검증 명령:
|
||||
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"`
|
||||
- 기대 결과:
|
||||
- Activity source test가 PASS한다.
|
||||
|
||||
- [ ] **Task 6.3: 내 채널 오디오 CTA 구현**
|
||||
- [ ] **Task 6.3: owner CTA를 공통 이름으로 rename하고 오디오 탭에 재사용**
|
||||
- 수정:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt`
|
||||
- `app/src/main/res/layout/activity_creator_channel.xml`
|
||||
@@ -515,10 +543,10 @@
|
||||
- `app/src/main/res/values-en/strings.xml`
|
||||
- `app/src/main/res/values-ja/strings.xml`
|
||||
- 작업:
|
||||
- 라이브 CTA와 동일한 하단 고정 방식으로 오디오 CTA를 표시한다.
|
||||
- label은 `오디오 올리기`, icon은 `ic_new_upload_audio`를 사용한다.
|
||||
- 현재 tab이 Audio이고 `currentHeader?.isOwner == true`일 때만 표시한다.
|
||||
- CTA click은 기존 `onOwnerFabAudioClicked()`를 호출한다.
|
||||
- 기존 `layout_creator_channel_live_owner_cta`/`btn_creator_channel_live_owner_cta`를 `layout_creator_channel_owner_cta`/`btn_creator_channel_owner_cta` 같은 creator channel 공통 owner CTA 이름으로 rename한다.
|
||||
- CTA 내부 icon/text view도 live 전용 id이면 creator channel owner CTA 공통 id로 rename한다.
|
||||
- 현재 tab이 Live이면 기존 라이브 생성 label/icon/action을 유지하고, Audio이면 label `오디오 올리기`, icon `ic_new_upload_audio`, action `onOwnerFabAudioClicked()`를 사용한다.
|
||||
- 현재 tab이 Audio이고 `currentHeader?.isOwner == true`일 때 오디오 CTA를 표시한다.
|
||||
- CTA 표시 시 오디오 Fragment list/empty 하단 padding을 업데이트한다.
|
||||
- API error 또는 본인 여부 미확정 상태에서는 임의로 CTA를 노출하지 않는다.
|
||||
- 검증 명령:
|
||||
@@ -532,7 +560,7 @@
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/CreatorChannelAudioFragment.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/CreatorChannelAudioViewModel.kt`
|
||||
- 작업:
|
||||
- Activity scroll callback에서 Fragment `onCreatorChannelAudioScrolledToBottom()`가 호출되면 `viewModel.loadMore()`를 실행한다.
|
||||
- Activity의 공통 scroll-to-bottom hook에서 현재 탭이 Audio일 때 오디오 Fragment의 load-more 진입점이 호출되면 `viewModel.loadMore()`를 실행한다.
|
||||
- 다음 페이지 요청에는 현재 sort/themeId/size를 유지한다.
|
||||
- load-more 실패 시 기존 목록은 유지하고 pagination error message를 toast/snackbar로 표시한 뒤 consume한다.
|
||||
- 검증 명령:
|
||||
|
||||
@@ -22,13 +22,14 @@
|
||||
- 최초 조회 시 `themeId` query parameter는 보내지 않는다.
|
||||
- 응답의 `themes`에는 `전체` 항목이 포함되지 않으므로, 클라이언트가 맨 앞에 `전체` tab을 추가한다.
|
||||
- 응답의 `themeId == null` 상태는 클라이언트가 추가한 `전체` tab 선택 상태로 표현한다.
|
||||
- 테마 tab-bar는 v2 공통 `CapsuleTabBarView`를 재사용한다.
|
||||
- Sort-bar에는 전체 오디오 콘텐츠 수와 현재 정렬 label을 표시한다.
|
||||
- 정렬 선택 방식과 옵션은 `라이브` 탭과 동일하게 적용한다.
|
||||
- 정렬 선택 방식과 옵션은 `라이브` 탭 구현을 크리에이터 채널 공통 sort popup으로 rename/move해 재사용한다.
|
||||
- 소장률 섹션은 내 채널이 아니고 `전체` 테마 선택 상태일 때만 표시한다.
|
||||
- 소장률 섹션에는 전체 유료 오디오 중 소장한 비율과 `purchasedAudioContentCount/paidAudioContentCount`를 표시한다.
|
||||
- 내 채널인 경우 소장률 섹션을 숨기고 하단 고정 `오디오 올리기` CTA를 표시한다.
|
||||
- 오디오 콘텐츠 목록은 기존 라이브 다시듣기 item UI를 재사용하되, `seriesName`이 있으면 `duration • seriesName` 형식으로 표시한다.
|
||||
- `seriesName`이 없으면 시리즈 이름 UI를 숨기고 `duration`만 표시한다.
|
||||
- 오디오 콘텐츠 목록은 기존 라이브 다시듣기 item UI/adapter를 크리에이터 채널 공통 오디오 콘텐츠 item으로 rename/move해 재사용하되, `seriesName`이 있으면 `duration • seriesName` 형식으로 표시한다.
|
||||
- `seriesName`이 없으면 공통 secondary text에 `duration`만 표시하고 빈 구분자나 별도 시리즈 영역은 노출하지 않는다.
|
||||
- 오디오 콘텐츠가 0개이면 오디오 콘텐츠가 있을 때 표시하는 UI를 모두 숨기고 empty 문구만 표시한다.
|
||||
- 응답의 `hasNext`가 `true`이면 현재 `page + 1` 페이지를 추가 로딩한다.
|
||||
|
||||
@@ -134,6 +135,9 @@ data class CreatorChannelAudioThemeResponse(
|
||||
- `CreatorChannelAudioTabResponse.themeId`가 특정 theme id이면 해당 `themeId`를 가진 서버 테마 tab을 선택 상태로 표시한다.
|
||||
- 선택된 tab은 Figma처럼 white background와 black text로 표시한다.
|
||||
- 선택되지 않은 tab은 black background, gray border, white text로 표시한다.
|
||||
- theme tab-bar는 v2 공통 `CapsuleTabBarView`를 사용한다.
|
||||
- `CapsuleTabBarView.setMenus()`에는 `CreatorChannelAudioThemeUiModel.title` 목록을 전달하고, 선택 index는 `isSelected == true`인 theme의 index를 사용한다.
|
||||
- `CapsuleTabBarView.setOnTabSelectedListener`에서 선택 index를 `CreatorChannelAudioThemeUiModel.themeId`로 역매핑해 `themeId`를 결정한다.
|
||||
- theme tab을 선택하면 `page=0`, 현재 `sort`, 선택한 `themeId`로 API를 다시 조회한다.
|
||||
- 이미 선택된 theme tab을 다시 선택하면 API를 재호출하지 않는다.
|
||||
- theme tab-bar는 가로 스크롤을 지원한다.
|
||||
@@ -153,7 +157,7 @@ Sort-bar는 전체 오디오 콘텐츠 수와 현재 정렬 상태를 표시하
|
||||
- 우측에는 현재 정렬 label과 정렬 icon을 표시한다.
|
||||
- 정렬 선택 방식은 `라이브` 탭과 동일하다.
|
||||
- 정렬 기본값은 `ContentSort.LATEST`이며 label은 한국어 기준 `최신순`이다.
|
||||
- 정렬 옵션, label, 선택 표시, 메뉴 닫힘 동작은 `라이브` 탭 구현과 동일하게 재사용한다.
|
||||
- 정렬 옵션, label, 선택 표시, 메뉴 닫힘 동작은 기존 `CreatorChannelLiveSortPopup`을 `CreatorChannelSortPopup`으로 rename/move해 라이브/오디오가 함께 재사용한다.
|
||||
- 정렬 옵션을 선택하면 `page=0`, 선택된 `sort`, 현재 `themeId`로 API를 다시 조회한다.
|
||||
- 선택 중인 정렬 옵션을 다시 선택하면 API 재호출 없이 메뉴만 닫는다.
|
||||
|
||||
@@ -188,12 +192,12 @@ Sort-bar는 전체 오디오 콘텐츠 수와 현재 정렬 상태를 표시하
|
||||
#### Requirements
|
||||
- Figma 콘텐츠 item 기준 노드는 `290:9026`이다.
|
||||
- `audioContents`를 세로 목록으로 표시한다.
|
||||
- 각 item은 기존 라이브 다시듣기 item UI, 상태 표시, tag 표시, 가격/재생 CTA 정책을 재사용한다.
|
||||
- 각 item은 기존 라이브 다시듣기 item UI, adapter, 상태 표시, tag 표시, 가격/재생 CTA 정책을 크리에이터 채널 공통 오디오 콘텐츠 item으로 rename/move해 재사용한다.
|
||||
- 각 item은 `imageUrl`, `title`, `duration`, `seriesName`을 표시한다.
|
||||
- `seriesName`이 있으면 secondary text를 `duration • seriesName` 형식으로 표시한다.
|
||||
- `seriesName`이 없으면 시리즈 이름 UI를 숨기고 `duration`만 표시한다.
|
||||
- `seriesName`이 없으면 공통 secondary text에 `duration`만 표시하고 빈 구분자나 별도 시리즈 영역은 노출하지 않는다.
|
||||
- `duration`은 필수 표시 값이며, `duration == null`인 콘텐츠는 목록에서 숨긴다.
|
||||
- `isAdult`, `isPointAvailable`, `isFirstContent`, `isOriginalSeries`, `price`, `isOwned`, `isRented`는 기존 라이브 다시듣기/오디오 item 정책과 동일하게 매핑한다.
|
||||
- `isAdult`, `isPointAvailable`, `isFirstContent`, `isOriginalSeries`, `price`, `isOwned`, `isRented`는 기존 라이브 다시듣기 item 정책과 v2 공통 `AudioContentTag`를 재사용해 매핑한다.
|
||||
- item 터치 시 오디오 콘텐츠 상세 또는 재생 진입은 기존 오디오 콘텐츠 item 정책을 재사용한다.
|
||||
|
||||
#### Edge Cases
|
||||
@@ -242,7 +246,7 @@ Sort-bar는 전체 오디오 콘텐츠 수와 현재 정렬 상태를 표시하
|
||||
|
||||
#### Requirements
|
||||
- Figma 기준 노드는 `665:19008`이다.
|
||||
- CTA 버튼 표시 방식은 라이브 탭의 `라이브 시작하기` CTA와 동일하게 적용한다.
|
||||
- CTA 버튼 표시 방식은 라이브 탭의 `라이브 시작하기` CTA 컨테이너를 공통 owner CTA로 rename해 재사용한다.
|
||||
- 로그인 사용자가 현재 크리에이터 채널의 본인이면 하단 고정 CTA 영역을 표시한다.
|
||||
- 로그인 사용자가 현재 크리에이터 채널의 본인이 아니면 하단 고정 CTA 영역을 표시하지 않는다.
|
||||
- 내 채널인 경우 소장률 섹션은 표시하지 않는다.
|
||||
@@ -251,7 +255,7 @@ Sort-bar는 전체 오디오 콘텐츠 수와 현재 정렬 상태를 표시하
|
||||
- CTA 버튼 icon은 `ic_new_upload_audio` drawable 리소스를 사용한다.
|
||||
- CTA가 표시되는 경우 목록 마지막 item 또는 empty 문구가 CTA에 가려지지 않도록 하단 padding 또는 inset을 추가한다.
|
||||
- Android gesture navigation, soft navigation bar, display cutout 환경에서 CTA가 system navigation 영역과 겹치지 않도록 bottom inset을 반영한다.
|
||||
- 버튼 터치 시 기존 또는 신규 오디오 업로드 진입 플로우로 이동한다. 업로드 화면 내부 구현은 이번 PRD 범위에서 제외한다.
|
||||
- 버튼 터치 시 기존 `onOwnerFabAudioClicked()`의 `AudioContentUploadActivity` 진입 경로를 재사용한다. 업로드 화면 내부 구현은 이번 PRD 범위에서 제외한다.
|
||||
|
||||
#### Edge Cases
|
||||
- 본인 여부 판정 데이터가 아직 로딩 중이면 CTA를 먼저 노출하지 않는다.
|
||||
@@ -294,9 +298,11 @@ Figma 섹션 기준으로 구현 계획/TASK 문서를 작성할 때 아래 단
|
||||
- 신규 `Activity`, `Fragment`, `ViewModel` 및 연결된 하위 코드는 `kr.co.vividnext.sodalive.v2` 패키지 하위에 작성한다.
|
||||
- 기존 크리에이터 채널 컨테이너, 탭 전환, sticky 동작을 불필요하게 재설계하지 않는다.
|
||||
- 기존 `ContentSort`와 `CreatorChannelAudioContentResponse`를 재사용한다.
|
||||
- 정렬 메뉴는 라이브 탭과 동일한 구현/리소스/문자열 매핑을 우선 재사용한다.
|
||||
- 내 채널 하단 CTA는 라이브 탭 CTA 구현/레이아웃/inset 처리 방식을 우선 재사용한다.
|
||||
- 이미지 placeholder, 가격/포인트/무료/소장중/대여중/19금/original tag 표시 정책은 기존 라이브 다시듣기 및 오디오 item 구현과 맞춘다.
|
||||
- 테마 tab-bar는 v2 공통 `CapsuleTabBarView`를 재사용하고 별도 theme adapter/item layout을 만들지 않는다.
|
||||
- 정렬 메뉴는 기존 라이브 탭 sort popup 구현/리소스/문자열 매핑을 크리에이터 채널 공통 이름으로 rename/move해 재사용한다.
|
||||
- 내 채널 하단 CTA는 라이브 탭 CTA 구현/레이아웃/inset 처리 방식을 공통 owner CTA로 rename해 재사용한다.
|
||||
- 오디오 콘텐츠 item은 기존 라이브 다시듣기 item layout/adapter/model/status를 크리에이터 채널 공통 오디오 콘텐츠 item으로 rename/move해 재사용한다.
|
||||
- 이미지 placeholder, 가격/포인트/무료/소장중/대여중/19금/original tag 표시 정책은 기존 라이브 다시듣기 item 구현과 v2 공통 `AudioContentTag`를 따른다.
|
||||
- API 실패, retry, pagination 실패 표시는 기존 프로젝트 패턴을 구현 계획 단계에서 확인해 따른다.
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user