docs(home): 메인 홈 추천 요구사항을 문서화한다
This commit is contained in:
355
docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md
Normal file
355
docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md
Normal file
@@ -0,0 +1,355 @@
|
||||
# 메인 홈 추천 UI와 API 연동 Plan / Task
|
||||
|
||||
> **For agentic workers:** 구현 시 `superpowers:subagent-driven-development` 또는 `superpowers:executing-plans`를 사용해 task 단위로 진행한다. 각 단계는 체크박스(`- [ ]`)로 추적하고, 완료 즉시 `- [x]`로 갱신한다.
|
||||
|
||||
**Goal:** Figma `home_001`(`24:5514`) 기준 메인 홈 추천 화면을 `HomeMainFragment`에 구성하고, `GET /api/v2/home/recommendations` 및 모두 팔로우 API를 기존 XML View + v2 widget 패턴으로 연동한다.
|
||||
|
||||
**Architecture:** 신규 홈 추천 API, Repository, ViewModel, DTO/UI model/mapper는 `kr.co.vividnext.sodalive.v2.main.home` 하위에 둔다. 화면은 `HomeMainFragment`와 `fragment_v2_main_home.xml`을 확장하고, 기존 `v2.widget` 컴포넌트는 재사용하되 추천 화면에 필요한 최소 확장만 수행한다.
|
||||
|
||||
**Tech Stack:** Kotlin, Android XML Views, ViewBinding, RecyclerView, RxJava3, Retrofit, Gson, Koin, Coil, local unit test.
|
||||
|
||||
---
|
||||
|
||||
## 전제와 성공 기준
|
||||
- PRD: `docs/20260601_메인_홈_추천_UI와_API_연동/prd.md`
|
||||
- 기존 `HomeApi`에는 메서드를 추가하지 않고 신규 `HomeRecommendationApi`를 만든다.
|
||||
- 신규 화면 관련 하위 코드는 `kr.co.vividnext.sodalive.v2` 패키지 하위에 작성한다.
|
||||
- `추천 필모그래피`, `또 다른 모습` 섹션은 만들지 않는다.
|
||||
- 각 API 리스트가 비어 있으면 해당 section-title과 목록을 숨긴다.
|
||||
- 구현 완료 후 최소 `./gradlew :app:testDebugUnitTest`와 `./gradlew :app:ktlintCheck`를 실행한다.
|
||||
|
||||
---
|
||||
|
||||
### Phase 1: 기존 구조 확인과 구현 경계 고정
|
||||
|
||||
- [x] **Task 1.1: 기존 홈/위젯/네트워크 패턴 확인**
|
||||
- 확인: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt`
|
||||
- 확인: `app/src/main/res/layout/fragment_v2_main_home.xml`
|
||||
- 확인: `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt`
|
||||
- 확인: `app/src/main/java/kr/co/vividnext/sodalive/home/HomeApi.kt`
|
||||
- 확인: `app/src/main/java/kr/co/vividnext/sodalive/common/ApiResponse.kt`
|
||||
- 확인: `app/src/main/java/kr/co/vividnext/sodalive/extensions/ImageExtensions.kt`
|
||||
- 검증: `HomeMainFragment`는 현재 빈 `FrameLayout` 바인딩만 가지고 있고, 신규 API 등록은 `AppDI.kt`의 `networkModule`, `repositoryModule`, `viewModelModule`에 추가해야 함을 확인한다.
|
||||
|
||||
- [x] **Task 1.2: 재사용 위젯과 신규 UI 범위 확정**
|
||||
- 확인: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/banner/BannerView.kt`
|
||||
- 확인: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/livethumbnail/LiveThumbnailSimpleView.kt`
|
||||
- 확인: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentCardView.kt`
|
||||
- 확인: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/characterchatthumbnail/CharacterChatThumbnailView.kt`
|
||||
- 확인: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedCommunityView.kt`
|
||||
- 확인: `app/src/main/res/layout/view_section_title.xml`
|
||||
- 신규 필요 후보: 최근 활동 카드, 최근 데뷔 크리에이터 카드, 장르별/응원 크리에이터 profile grid, 모두 팔로우 버튼, 사업자 정보 접기 영역.
|
||||
- 검증: PRD의 Section Mapping 표와 구현 대상/제외 대상이 일치하는지 문서 체크한다.
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: API DTO, Repository, DI 추가
|
||||
|
||||
- [x] **Task 2.1: 홈 추천 API/DTO 파일 생성**
|
||||
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeRecommendationApi.kt`
|
||||
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeRecommendationModels.kt`
|
||||
- 구현 내용:
|
||||
- `GET /api/v2/home/recommendations`
|
||||
- `POST /api/v2/home/recommendations/creators/follow`
|
||||
- `FollowRecommendedCreatorsRequest(val creatorIds: List<Long>)`
|
||||
- `HomeRecommendationResponse`와 PRD의 모든 item DTO
|
||||
- 주의: 서버 필드명은 임의 변경하지 않고 필요할 때만 `@SerializedName`을 추가한다.
|
||||
- 검증: `HomeRecommendationResponse` 필드가 `lives`, `banners`, `recentlyActiveCreators`, `recentDebutCreators`, `firstAudioContents`, `aiCharacters`, `genreCreators`, `cheerCreators`, `popularCommunityPosts`를 모두 포함한다.
|
||||
|
||||
- [x] **Task 2.2: Repository 생성**
|
||||
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeRecommendationRepository.kt`
|
||||
- 구현 내용:
|
||||
- `getRecommendations(token: String)`
|
||||
- `followRecommendedCreators(request: FollowRecommendedCreatorsRequest, token: String)`
|
||||
- 검증: 기존 Repository처럼 Retrofit `Single<ApiResponse<...>>`를 그대로 반환하고, token은 호출부에서 `"Bearer ${SharedPreferenceManager.token}"` 형태로 전달한다.
|
||||
|
||||
- [x] **Task 2.3: Koin DI 등록**
|
||||
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt`
|
||||
- 구현 내용:
|
||||
- `networkModule`에 `HomeRecommendationApi` 등록
|
||||
- `repositoryModule`에 `HomeRecommendationRepository` 등록
|
||||
- 이후 Phase 4에서 생성할 `HomeRecommendationViewModel` 등록
|
||||
- 검증 명령: `./gradlew :app:compileDebugKotlin`
|
||||
- 기대 결과: 신규 API/Repository import 및 Koin 등록 컴파일 성공.
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: 순수 mapper와 unit test 작성
|
||||
|
||||
- [x] **Task 3.1: activity type 표시 mapper 테스트 작성**
|
||||
- 생성: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/RecommendedActivityTypeTest.kt`
|
||||
- 대상: `LIVE`, `LIVE_REPLAY`, `AUDIO`, `COMMUNITY`, 알 수 없는 code.
|
||||
- 기대:
|
||||
- `LIVE`, `LIVE_REPLAY` -> `R.string.home_recommendation_activity_live`
|
||||
- `AUDIO` -> `R.string.home_recommendation_activity_audio`
|
||||
- `COMMUNITY` -> `R.string.home_recommendation_activity_community`
|
||||
- unknown -> null 또는 미표시 정책
|
||||
- 검증 명령: `./gradlew :app:testDebugUnitTest --tests kr.co.vividnext.sodalive.v2.main.home.RecommendedActivityTypeTest`
|
||||
- 기대 결과: mapper 구현 전 실패.
|
||||
|
||||
- [x] **Task 3.2: activity type mapper 구현**
|
||||
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/RecommendedActivityType.kt`
|
||||
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationMappers.kt`
|
||||
- 구현 내용:
|
||||
- 백엔드 code 문자열을 앱 enum으로 변환
|
||||
- 표시 문자열은 `@StringRes`로 반환해 Fragment/ViewHolder에서 `getString()` 처리
|
||||
- 알 수 없는 code는 크래시 없이 null 반환 후 UI에서 activity label 미표시
|
||||
- 검증 명령: `./gradlew :app:testDebugUnitTest --tests kr.co.vividnext.sodalive.v2.main.home.RecommendedActivityTypeTest`
|
||||
- 기대 결과: PASS.
|
||||
|
||||
- [x] **Task 3.3: string resource 추가**
|
||||
- 수정: `app/src/main/res/values/strings.xml`
|
||||
- 수정: `app/src/main/res/values-en/strings.xml`
|
||||
- 수정: `app/src/main/res/values-ja/strings.xml`
|
||||
- 추가 문자열:
|
||||
- 추천/랭킹/팔로잉 tab
|
||||
- 섹션 제목
|
||||
- `라이브`, `오디오`, `커뮤니티`
|
||||
- `모두 팔로우 하기`, `모두 팔로우 완료`
|
||||
- `더보기`, `접기`
|
||||
- 사업자 정보 텍스트
|
||||
- 검증 명령: `./gradlew :app:mergeDebugResources`
|
||||
- 기대 결과: resource merge 성공.
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: ViewModel과 화면 상태 구성
|
||||
|
||||
- [ ] **Task 4.1: UI model 정의**
|
||||
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationUiState.kt`
|
||||
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationUiModels.kt`
|
||||
- 구현 내용:
|
||||
- `Loading`, `Content`, `Empty`, `Error` 상태
|
||||
- 섹션별 UI model 또는 adapter item
|
||||
- `HomeFirstAudioContentItem` -> `AudioContentTag.First`, `Point`, `Free` 조건 매핑
|
||||
- `HomeAiCharacterItem.originalWorkTitle == null`이면 기존 `CharacterChatThumbnailView`의 `shouldShowOriginalTitle = false`
|
||||
- `HomePopularCommunityPostItem`의 `price`, `existOrdered` 기반 유료 상태 모델
|
||||
- 검증: DTO를 Fragment/ViewHolder에 직접 노출하지 않는다.
|
||||
|
||||
- [ ] **Task 4.2: ViewModel 생성**
|
||||
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeRecommendationViewModel.kt`
|
||||
- 구현 내용:
|
||||
- `recommendationStateLiveData`
|
||||
- `toastLiveData`
|
||||
- `isLoading`
|
||||
- `loadRecommendations()`
|
||||
- `followCreators(sectionKey: String, creatorIds: List<Long>)`
|
||||
- 빈 `creatorIds`면 API 미호출
|
||||
- 모두 팔로우 success 후 해당 section/button 상태 완료 처리
|
||||
- 검증: `ApiResponse.success == false`, `data == null`, Throwable 상황에서 기존 패턴처럼 unknown error toast를 노출한다.
|
||||
|
||||
- [ ] **Task 4.3: ViewModel DI 등록 완료**
|
||||
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt`
|
||||
- 구현 내용: `viewModel { HomeRecommendationViewModel(get()) }`
|
||||
- 검증 명령: `./gradlew :app:compileDebugKotlin`
|
||||
- 기대 결과: Koin 등록 및 ViewModel 생성 컴파일 성공.
|
||||
|
||||
---
|
||||
|
||||
### Phase 5: HomeMainFragment 레이아웃과 상단 UI 구현
|
||||
|
||||
- [ ] **Task 5.1: 홈 추천 화면 레이아웃 작성**
|
||||
- 수정: `app/src/main/res/layout/fragment_v2_main_home.xml`
|
||||
- 포함:
|
||||
- `include` 또는 직접 배치로 `view_title_bar_home`
|
||||
- `TextTabBarView`
|
||||
- `TextTabBarView` 아래 추천 content 전용 세로 `NestedScrollView` 또는 동등한 scroll container
|
||||
- 추천 content 내부 섹션별 컨테이너와 가로 `RecyclerView`
|
||||
- 추천 content 내부 사업자 정보 영역
|
||||
- 제외:
|
||||
- `ViewPager2`
|
||||
- `FragmentStateAdapter`
|
||||
- `랭킹`, `팔로잉`용 별도 Fragment
|
||||
- `랭킹`, `팔로잉`용 숨김 View를 미리 배치하는 `VISIBLE`/`GONE` 구조
|
||||
- 검증: root background는 기존처럼 `@color/black` 유지하고, 세로 스크롤 시 title-bar와 `TextTabBarView`는 화면에 남으며 그 아래 추천 content만 스크롤된다.
|
||||
|
||||
- [ ] **Task 5.2: title bar와 tab bar 바인딩**
|
||||
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt`
|
||||
- 확인: `app/src/main/res/layout/view_title_bar_home.xml`
|
||||
- 확인: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/TextTabBarView.kt`
|
||||
- 구현 내용:
|
||||
- right icons 순서: `ic_bar_cash`, `ic_bar_search`, `ic_bar_bell`
|
||||
- tabs 순서: 추천, 랭킹, 팔로잉
|
||||
- 추천 tab selected 상태
|
||||
- tab 전환은 글자 터치 callback으로만 처리
|
||||
- 좌우 swipe 전환은 추가하지 않음
|
||||
- 이번 범위에서는 추천 tab content만 바인딩하고 `랭킹`, `팔로잉` content는 생성하지 않음
|
||||
- 검증: 앱 실행 시 상단 bar와 tab이 표시되고 text resource가 적용된다. 좌우 swipe로 tab 전환이 발생하지 않는다.
|
||||
|
||||
---
|
||||
|
||||
### Phase 6: 섹션 adapter와 재사용 위젯 바인딩
|
||||
|
||||
> Phase 6까지는 실제 API/ViewModel 연동 전 단계이므로, 실제 기기에서 UI 배치와 섹션 표시 형태를 확인할 수 있도록 Fragment 또는 adapter 호출부에 샘플 데이터를 임시 주입할 수 있다. 샘플 데이터는 Phase 9의 실제 `HomeRecommendationViewModel` observe/API 바인딩 전 제거하거나 실제 상태 바인딩으로 대체한다.
|
||||
|
||||
- [ ] **Task 6.1: 라이브, 배너 섹션 바인딩**
|
||||
- 생성 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeLiveAdapter.kt`
|
||||
- 생성 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeBannerBinder.kt`
|
||||
- 재사용: `LiveThumbnailSimpleView`, `BannerView`
|
||||
- 구현 내용:
|
||||
- `lives`가 비면 라이브 섹션 숨김
|
||||
- `banners`가 비면 배너 섹션 숨김
|
||||
- 이미지 로딩은 `ImageView.loadUrl()` 또는 기존 widget adapter의 방식 사용
|
||||
- 검증: 샘플 `lives`, `banners` 데이터로 실제 기기에서 라이브 가로 목록과 배너 캐러셀이 의도한 위치/크기로 보인다. 빈 리스트일 때 title/RecyclerView가 함께 `GONE`.
|
||||
|
||||
- [ ] **Task 6.2: 최근 활동/최근 데뷔 크리에이터 UI 구현**
|
||||
- 생성 후보: `app/src/main/res/layout/item_home_recent_activity_creator.xml`
|
||||
- 생성 후보: `app/src/main/res/layout/item_home_recent_debut_creator.xml`
|
||||
- 생성 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeRecentActivityCreatorAdapter.kt`
|
||||
- 생성 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeRecentDebutCreatorAdapter.kt`
|
||||
- 구현 내용:
|
||||
- 최근 활동 activity type label은 Phase 3 mapper 결과 사용
|
||||
- unknown activity type은 label view 숨김
|
||||
- 닉네임/제목은 기존 typography와 말줄임 정책 적용
|
||||
- 검증: 샘플 creator 데이터로 실제 기기에서 최근 활동/최근 데뷔 카드 배치가 보인다. `LIVE_REPLAY`도 `라이브`로 표시.
|
||||
|
||||
- [ ] **Task 6.3: 첫 오디오 콘텐츠와 AI 캐릭터 섹션 바인딩**
|
||||
- 생성 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeFirstAudioAdapter.kt`
|
||||
- 생성 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeAiCharacterAdapter.kt`
|
||||
- 재사용: `AudioContentCardView`, `CharacterChatThumbnailView`
|
||||
- 구현 내용:
|
||||
- 첫 오디오 콘텐츠는 `AudioContentTag.First` 항상 포함
|
||||
- `isPointAvailable == true`면 `AudioContentTag.Point`
|
||||
- `price == 0`이면 `AudioContentTag.Free`
|
||||
- `AudioContentTag.Original`은 포함하지 않음
|
||||
- AI character image는 `profileImage` 사용
|
||||
- 검증: 샘플 audio/AI character 데이터로 실제 기기에서 카드 배치와 이미지/텍스트 영역이 보인다. 오디오 태그 조합이 PRD 조건과 일치.
|
||||
|
||||
- [ ] **Task 6.4: 장르별/응원 크리에이터와 모두 팔로우 버튼 구현**
|
||||
- 생성 후보: `app/src/main/res/layout/item_home_creator_profile.xml`
|
||||
- 생성 후보: `app/src/main/res/layout/view_home_follow_all_button.xml`
|
||||
- 생성 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeGenreCreatorAdapter.kt`
|
||||
- 생성 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeCheerCreatorAdapter.kt`
|
||||
- 구현 내용:
|
||||
- `cheerCreators.creatorId` 목록으로 모두 팔로우 API 호출
|
||||
- `genreCreators`의 해당 그룹 `creators.creatorId` 목록으로 모두 팔로우 API 호출
|
||||
- success 후 `모두 팔로우 완료`, `ic_new_following`, click disabled
|
||||
- 실패 시 toast/error 표시
|
||||
- 검증: 샘플 genre/cheer creator 데이터로 실제 기기에서 profile grid와 모두 팔로우 버튼 배치가 보인다. 빈 creator list에서는 API 호출하지 않고 버튼 상태 유지.
|
||||
|
||||
---
|
||||
|
||||
### Phase 7: FeedCommunityView 추천 페이지 확장
|
||||
|
||||
- [ ] **Task 7.1: FeedItem.Community 추천 필드 확장**
|
||||
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedItem.kt`
|
||||
- 추가 후보:
|
||||
- `imageUrl: String?`
|
||||
- `price: Int`
|
||||
- `existOrdered: Boolean`
|
||||
- `showKeyword: Boolean`
|
||||
- 주의: 기존 호출부 기본값을 제공해 기존 feed 사용처 영향 최소화.
|
||||
- 검증 명령: `./gradlew :app:compileDebugKotlin`
|
||||
|
||||
- [ ] **Task 7.2: 커뮤니티 이미지/유료 overlay 레이아웃 추가**
|
||||
- 수정: `app/src/main/res/layout/view_feed_community.xml`
|
||||
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedCommunityView.kt`
|
||||
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedAdapter.kt`
|
||||
- 구현 내용:
|
||||
- keyword는 추천 페이지에서 숨김
|
||||
- `imageUrl != null`이면 `346dp x 236dp`, radius `14dp`, centerCrop 이미지 표시
|
||||
- `price > 0 && existOrdered == false`면 blur/lock overlay와 가격 capsule 표시
|
||||
- 무료/구매 완료는 overlay와 가격 capsule 숨김
|
||||
- `FeedImageViews.primary`로 커뮤니티 이미지도 외부에서 로드 가능하게 노출
|
||||
- 검증: 기존 Community variant는 기본값으로 keyword를 유지하고, 추천용 item만 keyword를 숨긴다.
|
||||
|
||||
- [ ] **Task 7.3: 인기 커뮤니티 섹션 바인딩**
|
||||
- 생성 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomePopularCommunityAdapter.kt`
|
||||
- 재사용: `FeedAdapter` 또는 `FeedCommunityView`
|
||||
- 구현 내용:
|
||||
- `postId`, `creatorId`, `creatorNickname`, `creatorProfileImage`, `content`, `createdAt`, `likeCount`, `commentCount`, `imageUrl`, `price`, `existOrdered` 매핑
|
||||
- `audioUrl`은 별도 플레이어 UI 없이 상세 이동 데이터로만 유지
|
||||
- 검증: keyword 미노출, 이미지 null이면 이미지 영역 `GONE`.
|
||||
|
||||
---
|
||||
|
||||
### Phase 8: 사업자 정보 접기/더보기 구현
|
||||
|
||||
- [ ] **Task 8.1: 사업자 정보 UI 구현**
|
||||
- 수정: `app/src/main/res/layout/fragment_v2_main_home.xml`
|
||||
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt`
|
||||
- 구현 내용:
|
||||
- 기본 `maxLines = 3`, `ellipsize = end`
|
||||
- `더보기` 클릭 시 전체 표시
|
||||
- `접기` 클릭 시 다시 3줄 말줄임
|
||||
- 3줄 이하 텍스트면 toggle 숨김
|
||||
- 검증: 외부 라이브러리 없이 `TextView.post { lineCount }` 기반으로 toggle 노출 여부 결정.
|
||||
|
||||
---
|
||||
|
||||
### Phase 9: 라우팅, empty/error/loading 정책 연결
|
||||
|
||||
- [ ] **Task 9.1: Fragment observe와 loading/toast 연결**
|
||||
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt`
|
||||
- 구현 내용:
|
||||
- `HomeRecommendationViewModel` 주입
|
||||
- `loadRecommendations()` 호출
|
||||
- `recommendationStateLiveData` observe 후 각 섹션 bind
|
||||
- `isLoading`은 기존 `LoadingDialog` 또는 홈 화면 패턴 확인 후 적용
|
||||
- `toastLiveData`는 `BaseFragment.showToast()` 사용
|
||||
- 검증: API 실패 시 앱 크래시 없이 toast/error 처리.
|
||||
|
||||
- [ ] **Task 9.2: 클릭 라우팅 연결**
|
||||
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt`
|
||||
- 확인: 기존 상세 화면 Activity/Fragment 라우팅 파일
|
||||
- 구현 내용:
|
||||
- 배너 type별 이동은 기존 딥링크/이동 정책 확인 후 연결
|
||||
- live, creator, audio content, AI character, community post 클릭은 adapter callback으로 Fragment에 위임
|
||||
- 확정되지 않은 목적지는 임시 주석으로 남기지 않고 no-op 또는 기존 정책 확인 후 최소 연결
|
||||
- 검증: UI 컴포넌트 내부에서 목적지를 결정하지 않는다.
|
||||
|
||||
---
|
||||
|
||||
### Phase 10: 최종 검증과 문서 기록
|
||||
|
||||
- [ ] **Task 10.1: 단위 테스트와 빌드 검증**
|
||||
- 실행: `./gradlew :app:testDebugUnitTest`
|
||||
- 실행: `./gradlew :app:compileDebugKotlin`
|
||||
- 실행: `./gradlew :app:mergeDebugResources`
|
||||
- 실행: `./gradlew :app:ktlintCheck`
|
||||
- 기대 결과: 모두 성공.
|
||||
|
||||
- [ ] **Task 10.2: 수동 확인**
|
||||
- 확인 항목:
|
||||
- 추천 tab 선택 상태
|
||||
- tab 전환은 글자 터치로만 동작하고 swipe 전환은 없음
|
||||
- 세로 스크롤 시 title-bar와 `TextTabBarView`는 유지되고 `TextTabBarView` 아래 추천 content만 스크롤됨
|
||||
- 섹션 순서가 PRD 도식과 일치
|
||||
- 제외 섹션 미노출
|
||||
- 빈 리스트 섹션 숨김
|
||||
- 첫 오디오 태그 조건
|
||||
- activity type 다국어 문자열
|
||||
- 모두 팔로우 success 후 완료 상태
|
||||
- 인기 커뮤니티 유료/무료/구매 완료 이미지 상태
|
||||
- 사업자 정보 더보기/접기
|
||||
- 기대 결과: PRD Metrics 항목 충족.
|
||||
|
||||
- [ ] **Task 10.3: 검증 기록 누적**
|
||||
- 수정: `docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md`
|
||||
- 수정: `docs/20260601_메인_홈_추천_UI와_API_연동/prd.md`
|
||||
- 구현 내용: 실행 명령, 결과, 실패 시 원인과 후속 조치를 문서 하단 `검증 기록`에 누적한다.
|
||||
- 주의: 기존 검증 기록은 삭제하거나 덮어쓰지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 중 확인 필요
|
||||
- 배너 `type`별 이동 정책과 서버 code 목록.
|
||||
- `HomeLiveItem`, `HomeBannerItem`, `HomeActiveCreatorItem`, `HomeCreatorItem`, `HomeGenreCreatorGroupItem`의 최종 서버 필드명.
|
||||
- 시간 표시 포맷: `activityAt`, `releaseDate`, `createdAt`, `beginDateTime`에 기존 formatter를 재사용할 수 있는지 확인.
|
||||
- 모두 팔로우 API success response의 `data` 형태. `ApiResponse.success == true`만으로 완료 처리 가능한지 백엔드 계약 확인.
|
||||
- 사업자 정보 텍스트를 `strings.xml`로 둘지 서버/설정값으로 받을지 운영 정책 확인.
|
||||
|
||||
---
|
||||
|
||||
## 검증 기록
|
||||
- 2026-06-02: `docs/20260601_메인_홈_추천_UI와_API_연동/prd.md`, `docs/agent-guides/work-plan-docs.md`를 확인해 계획 문서 위치와 phase/task 형식을 맞췄다.
|
||||
- 2026-06-02: `HomeMainFragment`, `fragment_v2_main_home.xml`, `HomeApi`, `HomeRepository`, `AppDI`, `ApiResponse`, 주요 v2 widget 파일을 확인해 신규 API/Repository/ViewModel과 UI 작업 범위를 계획에 반영했다.
|
||||
- 2026-06-02: 사용자 추가 제공 정보에 따라 `ViewPager2`/swipe 전환과 tab별 Fragment 선행 생성을 제외하고, `TextTabBarView` 아래 추천 content만 세로 스크롤되도록 Phase 5와 수동 확인 항목을 갱신했다.
|
||||
- 2026-06-02: Phase 1 파일과 PRD Section Mapping을 확인해 `HomeMainFragment`/`fragment_v2_main_home.xml`은 아직 빈 홈 v2 shell이고, Phase 1-3 범위에서는 UI/ViewModel/adapter를 만들지 않는 것으로 구현 경계를 고정했다.
|
||||
- 2026-06-02: `RecommendedActivityTypeTest`를 먼저 추가하고 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.RecommendedActivityTypeTest"`를 실행해 `model` 패키지, mapper 함수, `home_recommendation_activity_*` 리소스 미존재로 실패하는 RED 상태를 확인했다.
|
||||
- 2026-06-02: `HomeRecommendationApi`, `HomeRecommendationModels`, `HomeRecommendationRepository`를 추가하고 `AppDI.kt`의 `networkModule`/`repositoryModule`에 신규 API/Repository를 등록했다. Phase 4 대상인 `HomeRecommendationViewModel` 등록은 추가하지 않았다.
|
||||
- 2026-06-02: `RecommendedActivityType`, `HomeRecommendationMappers`, 한국어/영어/일본어 string resource를 추가한 뒤 targeted test를 재실행해 PASS로 GREEN 전환을 확인했다.
|
||||
- 2026-06-02: `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:testDebugUnitTest`, `./gradlew :app:ktlintCheck`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. `lsp_diagnostics`는 `kotlin-lsp` 미설치로 실행되지 않아 Gradle compile/test/ktlint 결과로 보완했다.
|
||||
- 2026-06-02: 백엔드 activity type code가 대소문자와 무관하게 들어와도 매핑되도록 `RecommendedActivityTypeTest`에 `live`, `Live_RePlay` 케이스를 추가했다. 수정 전 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.RecommendedActivityTypeTest"`에서 해당 2개 테스트 실패로 RED를 확인했다.
|
||||
- 2026-06-02: `RecommendedActivityType.from()`의 code 비교를 `equals(ignoreCase = true)`로 변경한 뒤 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.RecommendedActivityTypeTest"`, `./gradlew :app:compileDebugKotlin`을 실행했고 모두 BUILD SUCCESSFUL을 확인했다.
|
||||
- 2026-06-02: Phase 6까지 진행했을 때 실제 API/ViewModel 연동 전에도 UI 배치와 섹션 표시 형태를 확인할 수 있도록 샘플 데이터를 임시 주입하는 검증 방식을 Phase 6 설명과 섹션별 검증 항목에 추가했다. 샘플 데이터는 Phase 9 실제 상태 바인딩 전 제거하거나 대체해야 한다.
|
||||
416
docs/20260601_메인_홈_추천_UI와_API_연동/prd.md
Normal file
416
docs/20260601_메인_홈_추천_UI와_API_연동/prd.md
Normal file
@@ -0,0 +1,416 @@
|
||||
# PRD: 메인 홈 추천 UI와 API 연동
|
||||
|
||||
## 1. Overview
|
||||
Figma `home_001` 화면(`24:5514`)을 기준으로 메인 홈 추천 영역을 구성하고, 홈 추천 API 응답을 기존 v2 widget 중심으로 바인딩한다.
|
||||
|
||||
---
|
||||
|
||||
## 2. Problem
|
||||
- 메인 홈 추천 화면은 라이브, 배너, 최근 활동, 신규 데뷔, 첫 오디오 콘텐츠, AI 캐릭터, 장르별 크리에이터, 응원 크리에이터, 인기 커뮤니티 등 여러 데이터 섹션을 한 화면에 표시해야 한다.
|
||||
- Figma에는 이미 `live`, `banner`, `section-title`, `contents`, `chat-thumbnail`, `profile`, `feed` 등 재사용 가능한 형태가 다수 포함되어 있으므로, 기존에 생성된 widget을 최대한 활용해야 한다.
|
||||
- `HomeActiveCreatorItem.activityType`은 백엔드 enum code 그대로 표시하면 사용자에게 부자연스러우므로 `LIVE`, `LIVE_REPLAY`, `AUDIO`, `COMMUNITY`를 다국어 처리 가능한 표시 문자열로 변환해야 한다.
|
||||
- 사업자 정보 섹션은 별도 외부 라이브러리 없이 최대 3줄 말줄임과 더보기/접기 토글을 제공해야 한다.
|
||||
- Figma에 포함된 일부 섹션은 이번 범위에서 제외되어야 하므로 구현 범위를 명확히 분리해야 한다.
|
||||
|
||||
---
|
||||
|
||||
## 3. Goals
|
||||
- 홈 추천 API 응답 `HomeRecommendationResponse`를 받아 Figma `24:5514`의 홈 추천 섹션 순서에 맞게 표시한다.
|
||||
- 기존 `HomeApi`에 메서드를 추가하지 않고, 메인 홈 추천 전용 신규 API 인터페이스/Repository 흐름을 만든다.
|
||||
- 추천 탭이 선택된 `HomeMainFragment`에 메인 홈 추천 UI를 구현한다.
|
||||
- Figma에서 기존 widget으로 대응 가능한 항목은 기존 v2 widget을 우선 재사용한다.
|
||||
- 재사용이 어려운 영역만 최소 범위 신규 UI 또는 adapter item으로 정의한다.
|
||||
- `추천 필모그래피`, `또 다른 모습` 섹션은 만들지 않는다.
|
||||
- 최하단 사업자 정보 섹션은 표시하되, 기본 최대 3줄 말줄임과 `더보기`/`접기` 토글을 제공한다.
|
||||
- `HomeActiveCreatorItem.activityType`은 백엔드 code를 앱 내부 enum 또는 mapper로 변환하고, 표시 문구는 string resource 기반 다국어 처리를 적용한다.
|
||||
- API DTO, 화면 상태, empty/error/loading 정책, click routing은 구현 계획에서 검증 가능하도록 정리한다.
|
||||
|
||||
---
|
||||
|
||||
## 4. Non-Goals
|
||||
- 이번 PRD 작성 단계에서는 코드, 리소스, 레이아웃 파일을 구현하지 않는다.
|
||||
- `추천 필모그래피` 섹션은 구현하지 않는다.
|
||||
- `또 다른 모습` 섹션은 구현하지 않는다.
|
||||
- 추천 알고리즘, API 응답 정렬 기준, 서버 필드명은 변경하지 않는다.
|
||||
- 외부 라이브러리를 추가하지 않는다.
|
||||
- Compose 화면으로 전환하지 않는다.
|
||||
- `ViewPager` 또는 swipe 기반 tab 전환은 추가하지 않는다.
|
||||
- 이번 범위에서 `랭킹`, `팔로잉`용 별도 Fragment 또는 숨김 View를 미리 만들지 않는다.
|
||||
- 기존 레거시 홈 화면 전체 리팩터링은 포함하지 않는다.
|
||||
- Figma에 없는 skeleton loading, shimmer, 임의 애니메이션, 추가 badge는 만들지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 5. Target Users
|
||||
- 메인 홈에서 추천 라이브, 크리에이터, 오디오 콘텐츠, AI 캐릭터, 커뮤니티를 탐색하는 앱 사용자.
|
||||
- 기존 XML Views와 v2 widget을 재사용해 홈 추천 화면을 구현/유지보수하는 Android 개발자.
|
||||
|
||||
---
|
||||
|
||||
## 6. User Stories
|
||||
- 사용자는 홈에서 현재 라이브 중인 크리에이터와 추천 콘텐츠를 빠르게 탐색하고 싶다.
|
||||
- 사용자는 배너를 스와이프하거나 터치해 관련 이벤트, 크리에이터, 시리즈, 외부 링크로 이동하고 싶다.
|
||||
- 사용자는 최근 활동한 크리에이터의 활동 유형을 `라이브`, `오디오`, `커뮤니티`처럼 이해 가능한 문구로 보고 싶다.
|
||||
- 사용자는 인기 커뮤니티 글을 읽다가 사업자 정보가 길어도 홈 화면 탐색이 과도하게 방해받지 않기를 기대한다.
|
||||
- 개발자는 기존 v2 widget을 최대한 재사용해 화면별 UI 중복과 스타일 차이를 줄이고 싶다.
|
||||
|
||||
---
|
||||
|
||||
## 7. Core Features
|
||||
|
||||
### 홈 추천 API 연동
|
||||
메인 홈 추천 화면은 다음 응답을 단일 API 결과로 받아 섹션별 UI 모델로 변환한다.
|
||||
|
||||
#### Response Contract
|
||||
```kotlin
|
||||
data class HomeRecommendationResponse(
|
||||
val lives: List<HomeLiveItem>,
|
||||
val banners: List<HomeBannerItem>,
|
||||
val recentlyActiveCreators: List<HomeActiveCreatorItem>,
|
||||
val recentDebutCreators: List<HomeCreatorItem>,
|
||||
val firstAudioContents: List<HomeFirstAudioContentItem>,
|
||||
val aiCharacters: List<HomeAiCharacterItem>,
|
||||
val genreCreators: List<HomeGenreCreatorGroupItem>,
|
||||
val cheerCreators: List<HomeCreatorItem>,
|
||||
val popularCommunityPosts: List<HomePopularCommunityPostItem>
|
||||
)
|
||||
```
|
||||
|
||||
#### Requirements
|
||||
- API endpoint는 `GET /api/v2/home/recommendations`를 사용한다.
|
||||
- 기존 `HomeApi`에 추가하지 않고 홈 추천 전용 신규 API 인터페이스를 만든다.
|
||||
- Repository/ViewModel 위치는 구현 계획에서 `HomeMainFragment`와 기존 v2 홈 구조를 확인한 뒤 결정한다.
|
||||
- 응답 DTO는 서버 필드명을 임의 변경하지 않고, 필요한 경우 `@SerializedName`만 추가한다.
|
||||
- 화면 UI는 DTO를 직접 노출하지 않고 섹션별 UI model 또는 adapter item으로 변환한다.
|
||||
- 각 리스트가 비어 있으면 해당 섹션은 숨기는 것을 기본 정책으로 한다.
|
||||
- 전체 API 실패 시 기존 홈 화면의 error/toast/loading 패턴을 따른다.
|
||||
- 이미지 URL이 null이면 호출부의 기존 placeholder 정책을 따른다.
|
||||
|
||||
#### Changed Item Contracts
|
||||
```kotlin
|
||||
data class HomeFirstAudioContentItem(
|
||||
val contentId: Long,
|
||||
val creatorId: Long,
|
||||
val creatorNickname: String,
|
||||
val creatorProfileImage: String?,
|
||||
val title: String,
|
||||
val price: Int,
|
||||
val coverImage: String?,
|
||||
val releaseDate: String,
|
||||
val isPointAvailable: Boolean
|
||||
)
|
||||
|
||||
data class HomeAiCharacterItem(
|
||||
val characterId: Long,
|
||||
val name: String,
|
||||
val description: String,
|
||||
val profileImage: String?,
|
||||
val totalChatCount: Long,
|
||||
val originalWorkTitle: String?
|
||||
)
|
||||
|
||||
data class HomePopularCommunityPostItem(
|
||||
val postId: Long,
|
||||
val creatorId: Long,
|
||||
val creatorNickname: String,
|
||||
val creatorProfileImage: String?,
|
||||
val imageUrl: String?,
|
||||
val audioUrl: String?,
|
||||
val content: String,
|
||||
val price: Int,
|
||||
val createdAt: String,
|
||||
val likeCount: Long,
|
||||
val commentCount: Long,
|
||||
val existOrdered: Boolean
|
||||
)
|
||||
```
|
||||
|
||||
#### Changed Item Requirements
|
||||
- `HomeFirstAudioContentItem.price`는 첫 오디오 콘텐츠의 무료 여부 표시 정책에 사용한다.
|
||||
- `HomeFirstAudioContentItem.isPointAvailable`은 첫 오디오 콘텐츠 카드 바인딩 시 포인트 사용 가능 표시 정책에 반영한다.
|
||||
- `HomeAiCharacterItem.profileImage`는 `CharacterChatThumbnailView`의 실제 이미지 URL로 사용한다.
|
||||
- `HomeAiCharacterItem.originalWorkTitle`이 null이면 기존 `CharacterChatThumbnailView`의 원작 없음 표시 정책에 맞춰 원작 영역을 유지하거나 숨기는 방식을 구현 계획에서 확정한다.
|
||||
- `HomePopularCommunityPostItem.postId`는 커뮤니티 게시글 이동/식별 값으로 사용한다.
|
||||
- `HomePopularCommunityPostItem.price`, `existOrdered`는 추천 페이지용 `FeedCommunityView`의 유료 미구매 UI 판정에 사용한다.
|
||||
- `HomePopularCommunityPostItem.content`, `price`, `createdAt`, `likeCount`, `commentCount`는 인기 커뮤니티 feed 표시 데이터로 사용한다.
|
||||
|
||||
### Activity Type 다국어 표시
|
||||
`HomeActiveCreatorItem.activityType`은 백엔드 enum code를 앱 표시용 enum으로 변환한다.
|
||||
|
||||
#### Backend Codes
|
||||
```kotlin
|
||||
enum class RecommendedActivityType(val code: String) {
|
||||
LIVE("LIVE"),
|
||||
AUDIO("AUDIO"),
|
||||
COMMUNITY("COMMUNITY"),
|
||||
LIVE_REPLAY("LIVE_REPLAY")
|
||||
}
|
||||
```
|
||||
|
||||
#### Requirements
|
||||
- `LIVE`, `LIVE_REPLAY`는 동일하게 `라이브`로 표시한다.
|
||||
- `AUDIO`는 `오디오`로 표시한다.
|
||||
- `COMMUNITY`는 `커뮤니티`로 표시한다.
|
||||
- 표시 문구는 `strings.xml`, `values-en/strings.xml` 등 string resource를 통해 다국어 처리한다.
|
||||
- 알 수 없는 code는 크래시시키지 않고 기본값 또는 미표시 정책을 구현 계획에서 확정한다.
|
||||
- mapper 또는 enum 변환 로직은 local unit test로 검증한다.
|
||||
|
||||
### 사업자 정보 접기/더보기
|
||||
Figma 최하단 사업자 정보 섹션(`218:2058`)은 외부 라이브러리 없이 TextView와 클릭 가능한 컨트롤로 구현한다.
|
||||
|
||||
#### Requirements
|
||||
- 기본 상태는 최대 3줄, 끝 말줄임표로 표시한다.
|
||||
- 기본 상태에서 `더보기`를 터치하면 전체 텍스트를 표시한다.
|
||||
- 펼침 상태에서 `접기`를 터치하면 다시 최대 3줄 말줄임 상태로 돌아간다.
|
||||
- `더보기`/`접기` 표시 문구는 string resource로 관리한다.
|
||||
- 외부 라이브러리를 사용하지 않는다.
|
||||
- 사업자 정보 텍스트는 하드코딩 여부를 구현 계획에서 확인하되, 다국어/운영 변경 가능성이 있으면 string resource 또는 서버/설정값 사용을 우선 검토한다.
|
||||
- 텍스트가 3줄 이하인 경우 `더보기`를 표시하지 않는다.
|
||||
|
||||
### 상단 Bar와 추천 Tab
|
||||
`HomeMainFragment`에서 추천 탭 선택 상태의 메인 홈 추천 페이지를 표시한다.
|
||||
|
||||
#### Requirements
|
||||
- title-bar는 기존 `view_title_bar_home`을 사용한다.
|
||||
- title-bar 우측 아이콘은 왼쪽부터 `ic_bar_cash`, `ic_bar_search`, `ic_bar_bell` 순서로 배치한다.
|
||||
- tab-bar는 기존 `TextTabBarView`를 사용한다.
|
||||
- tab 항목은 `추천`, `랭킹`, `팔로잉` 순서로 구성한다.
|
||||
- 이 PRD의 화면은 `추천` tab이 선택되었을 때 표시되는 page다.
|
||||
- tab 표시 문구는 string resource로 관리한다.
|
||||
- tab 전환은 글자 터치로만 처리하고, 좌우 swipe 또는 `ViewPager` 기반 전환은 제공하지 않는다.
|
||||
- `TextTabBarView` 아래에는 이번 범위의 추천 page view만 구성한다.
|
||||
- `랭킹`, `팔로잉` page는 이번 PRD에서 Fragment나 `VISIBLE`/`GONE` 대상 View로 미리 만들지 않는다.
|
||||
- 세로 스크롤 시 title-bar와 `TextTabBarView`는 화면에 유지하고, `TextTabBarView` 아래 추천 content 영역만 스크롤되도록 구성한다.
|
||||
|
||||
### 모두 팔로우 하기
|
||||
`최근 응원이 많은 크리에이터`, `장르별 크리에이터` 섹션에는 모두 팔로우 버튼을 제공한다.
|
||||
|
||||
#### API Requirements
|
||||
- API는 `POST /api/v2/home/recommendations/creators/follow`를 사용한다.
|
||||
- request body는 `FollowRecommendedCreatorsRequest`를 사용한다.
|
||||
- 기존 API에 추가하지 않고, 홈 추천 전용 신규 API 인터페이스에 포함한다.
|
||||
- API 호출이 정상 완료되고 response가 success이면 버튼 상태를 `모두 팔로우 완료`로 변경한다.
|
||||
- 실패 시 기존 화면의 toast/error 표시 패턴을 따른다.
|
||||
|
||||
#### Request Contract
|
||||
```kotlin
|
||||
data class FollowRecommendedCreatorsRequest(
|
||||
val creatorIds: List<Long>
|
||||
)
|
||||
```
|
||||
|
||||
#### Request Requirements
|
||||
- `creatorIds`에는 사용자가 터치한 `모두 팔로우 하기` 버튼이 속한 섹션의 크리에이터 id 목록을 전달한다.
|
||||
- `최근 응원이 많은 크리에이터` 섹션에서는 `cheerCreators.creatorId` 목록을 전달한다.
|
||||
- `장르별 크리에이터` 섹션에서는 해당 장르 그룹의 `creators.creatorId` 목록을 전달한다.
|
||||
- `creatorIds`가 빈 리스트인 경우 API를 호출하지 않고 버튼 상태를 변경하지 않는다.
|
||||
|
||||
#### Button Requirements
|
||||
- 기본 상태 버튼 문구는 `모두 팔로우 하기`다.
|
||||
- 기본 상태 아이콘은 `ic_new_follow`를 사용한다.
|
||||
- 완료 상태 버튼 문구는 `모두 팔로우 완료`다.
|
||||
- 완료 상태 아이콘은 `ic_new_following`을 사용한다.
|
||||
- 완료 상태 Figma reference: 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=24-9092&m=dev
|
||||
- 완료 상태 버튼은 Figma `24:9092` 기준 white background, capsule radius `100dp`, horizontal/vertical padding `12dp`, icon `20dp`, icon/text gap `6dp`, Pretendard Medium `16sp`, black text를 기준으로 한다.
|
||||
- 문구는 string resource로 관리한다.
|
||||
- 버튼 상태는 API success 후 중복 호출되지 않도록 disabled 또는 click 무시 상태로 전환한다.
|
||||
|
||||
### 추천 페이지 위젯 수정
|
||||
추천 페이지에 표시되는 기존 widget은 화면 요구사항에 맞춰 최소 범위로 확장한다. 별도 PRD로 분리하지 않고, 메인 홈 추천 UI의 표시 계약으로 이 문서에 함께 기록한다.
|
||||
|
||||
#### FeedCommunityView Requirements
|
||||
- 추천 페이지의 인기 커뮤니티는 `HomePopularCommunityPostItem`을 `FeedCommunityView` 또는 `FeedAdapter`의 Community variant로 매핑한다.
|
||||
- 기존 keyword 영역은 추천 페이지에서는 제거한다.
|
||||
- `HomePopularCommunityPostItem.content`는 본문 영역에 표시한다.
|
||||
- `HomePopularCommunityPostItem.imageUrl`이 있으면 본문 아래 이미지 영역을 표시한다.
|
||||
- 이미지 영역은 Figma `309:19774`, `309:19775` 기준 `346dp x 236dp`, radius `14dp`, centerCrop을 기준으로 한다.
|
||||
- `imageUrl`이 null이면 이미지 영역은 `GONE` 처리한다.
|
||||
- `audioUrl`은 이번 추천 카드에서 별도 플레이어 UI를 만들지 않고, 게시글 상세 이동 또는 후속 오디오 정책에서 사용한다.
|
||||
- `price > 0 && existOrdered == false`이면 유료 미구매 커뮤니티 포스트로 판단한다.
|
||||
- 유료 미구매 커뮤니티 포스트는 Figma `309:19774` 기준으로 이미지 위에 blur/lock overlay와 가격 capsule을 표시한다.
|
||||
- 가격 capsule에는 `HomePopularCommunityPostItem.price`를 표시한다.
|
||||
- `price > 0 && existOrdered == true`이면 유료 구매 완료 포스트로 판단한다.
|
||||
- `price == 0`이면 무료 포스트로 판단한다.
|
||||
- 유료 구매 완료 포스트 또는 무료 포스트는 Figma `309:19775`의 이미지 표시 상태를 기준으로 하되, Figma에 있는 `구매완료` 버튼/태그는 표시하지 않는다.
|
||||
- 댓글 수와 좋아요 수는 기존 reaction row를 유지한다.
|
||||
- creator profile, creator name, createdAt, body, reaction row typography와 색상은 기존 `FeedCommunityView` 기준을 유지한다.
|
||||
|
||||
#### FeedCommunityView Figma References
|
||||
- 유료이고 구매하지 않은 UI: 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=309-19774&m=dev
|
||||
- 유료인데 구매함 또는 무료 UI: 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=309-19775&m=dev
|
||||
|
||||
#### AudioContentCardView Requirements
|
||||
- 첫 오디오 콘텐츠는 `v2.widget.AudioContentCardView`를 사용한다.
|
||||
- `HomeFirstAudioContentItem.isPointAvailable == true`이면 `ic_content_tag_point`를 쓰는 `ImageView`를 표시한다.
|
||||
- `HomeFirstAudioContentItem`에서는 오리지널 여부를 판단하지 않는다.
|
||||
- 오리지널 판단 변수가 없으므로 `ic_content_tag_original`을 쓰는 `ImageView`는 항상 `GONE` 처리한다.
|
||||
- `HomeFirstAudioContentItem.price == 0`이면 무료 콘텐츠로 판단하고 `무료` TextView를 `VISIBLE`로 표시한다.
|
||||
- `HomeFirstAudioContentItem.price > 0`이면 `무료` TextView를 `GONE` 처리한다.
|
||||
- `HomeFirstAudioContentItem`으로 표시하는 아이템은 모두 크리에이터의 첫 콘텐츠이므로 `FIRST` 글자를 포함하는 `LinearLayout`을 항상 표시한다.
|
||||
- 위 조건에 해당하지 않는 태그는 모두 `GONE` 처리한다.
|
||||
- 기존 `AudioContentCardView`의 `AudioContentTag.Original`, `AudioContentTag.Point`, `AudioContentTag.First`, `AudioContentTag.Free` 계약을 우선 재사용한다.
|
||||
- `HomeFirstAudioContentItem` 응답에는 오리지널 여부 필드가 없으므로 오리지널 태그는 표시하지 않는다.
|
||||
|
||||
### 섹션 구성과 UI 재사용 도식
|
||||
Figma `24:5514` 기준 홈 추천 화면은 아래 순서로 배치한다. 제외 섹션은 구현하지 않는다.
|
||||
|
||||
```text
|
||||
HomeRecommendation 화면
|
||||
├─ 상단 title-bar / tab-bar / nav
|
||||
│ ├─ title-bar: view_title_bar_home
|
||||
│ │ └─ right icons: ic_bar_cash, ic_bar_search, ic_bar_bell
|
||||
│ └─ tab-bar: TextTabBarView(추천 selected, 랭킹, 팔로잉)
|
||||
└─ 추천 content scroll area
|
||||
├─ 라이브 가로 목록: lives
|
||||
│ ├─ Figma instance: live, more
|
||||
│ ├─ 재사용: v2.widget.livethumbnail.LiveThumbnailSimpleView
|
||||
│ └─ 신규: 더보기 item 또는 기존 more UI 대응 adapter item 필요
|
||||
├─ 배너 캐러셀: banners
|
||||
│ ├─ Figma instance: banner
|
||||
│ └─ 재사용: v2.widget.banner.BannerView
|
||||
├─ 최근 활동한 크리에이터: recentlyActiveCreators
|
||||
│ ├─ Figma instance: section-title, list-act
|
||||
│ ├─ 재사용: view_section_title
|
||||
│ └─ 신규: list-act 형태의 최근 활동 카드 UI 필요
|
||||
├─ 최근 데뷔한 크리에이터: recentDebutCreators
|
||||
│ ├─ Figma instance: section-title, creater
|
||||
│ ├─ 재사용: view_section_title
|
||||
│ └─ 신규: 최근 데뷔 크리에이터 카드 UI
|
||||
├─ 첫 오디오 콘텐츠: firstAudioContents
|
||||
│ ├─ Figma instance: contents, profile
|
||||
│ ├─ 재사용: v2.widget.AudioContentCardView
|
||||
│ └─ 신규: 카드 하단 profile 결합 adapter item 필요 여부 확인
|
||||
├─ AI 캐릭터: aiCharacters
|
||||
│ ├─ Figma instance: section-title, chat-thumbnail
|
||||
│ ├─ 재사용: view_section_title
|
||||
│ └─ 재사용: v2.widget.characterchatthumbnail.CharacterChatThumbnailView
|
||||
├─ 장르별 크리에이터: genreCreators
|
||||
│ ├─ Figma instance: section-title, profile, button-capsule
|
||||
│ ├─ 재사용: view_section_title
|
||||
│ └─ 신규: 장르 그룹 카드, 2행 profile grid, 모두 팔로우 버튼
|
||||
├─ 추천 필모그래피
|
||||
│ └─ 제외: 만들지 않음
|
||||
├─ 최근 응원이 많은 크리에이터: cheerCreators
|
||||
│ ├─ Figma instance: section-title, profile, button-capsule
|
||||
│ ├─ 재사용: view_section_title
|
||||
│ └─ 신규: profile grid, 모두 팔로우 버튼
|
||||
├─ 또 다른 모습
|
||||
│ └─ 제외: 만들지 않음
|
||||
├─ 인기 커뮤니티: popularCommunityPosts
|
||||
│ ├─ Figma instance: section-title, feed
|
||||
│ └─ 재사용/수정: v2.widget.feed.FeedCommunityView 또는 FeedAdapter Community variant
|
||||
└─ 사업자 정보
|
||||
└─ 신규: 3줄 말줄임 + 더보기/접기 TextView 영역
|
||||
```
|
||||
|
||||
#### Section Mapping
|
||||
| API field | Figma node range | 표시 섹션 | 재사용 후보 | 신규 필요 여부 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `lives` | `24:5516` | 라이브 가로 목록 | `LiveThumbnailSimpleView` | `more` item 확인 필요 |
|
||||
| `banners` | `24:5525` | 배너 캐러셀 | `BannerView` | 없음 |
|
||||
| `recentlyActiveCreators` | `24:5529` | 방금 활동한 크리에이터 | `view_section_title` | `list-act` 신규 필요 |
|
||||
| `recentDebutCreators` | `24:5534` | 최근 데뷔한 크리에이터 | `view_section_title` | 신규 카드 필요 |
|
||||
| `firstAudioContents` | `24:5539` | 첫 오디오 콘텐츠 | `AudioContentCardView` | 태그 조건 매핑 확인 필요 |
|
||||
| `aiCharacters` | `24:5551` | AI 캐릭터 | `view_section_title`, `CharacterChatThumbnailView` | DTO mapping 필요 |
|
||||
| `genreCreators` | `24:5611`, `24:5636` 후보 | 장르별 크리에이터 | `view_section_title` | 그룹 카드, 모두 팔로우 버튼 신규 필요 |
|
||||
| `cheerCreators` | `24:5636` 후보 | 최근 응원이 많은 크리에이터 | `view_section_title` | profile grid, 모두 팔로우 버튼 신규 필요 |
|
||||
| `popularCommunityPosts` | `24:5645`, `309:19774`, `309:19775` | 인기 커뮤니티 | `view_section_title`, `FeedCommunityView` | 이미지/유료 상태 UI 수정 필요 |
|
||||
| 사업자 정보 | `218:2058` | 사업자 정보 | 없음 | 접기/더보기 신규 필요 |
|
||||
|
||||
### 제외 섹션
|
||||
- Figma `24:5557` 근처의 `추천 필모그래피`로 판단되는 카드형 시리즈/필모그래피 영역은 구현하지 않는다.
|
||||
- Figma `24:5602` 근처의 `또 다른 모습`으로 판단되는 콘텐츠 grid 영역은 구현하지 않는다.
|
||||
- 제외 섹션과 연결되는 서버 필드가 현재 응답에 없으므로 adapter item도 만들지 않는다.
|
||||
|
||||
#### Edge Cases
|
||||
- 모든 추천 리스트가 비어 있으면 홈 추천 영역은 비어 있는 섹션을 노출하지 않고, 기존 홈 화면 empty 정책을 따른다.
|
||||
- 특정 섹션만 비어 있으면 해당 section-title과 목록 전체를 숨긴다.
|
||||
- 긴 제목, 닉네임, 커뮤니티 본문은 각 widget의 말줄임 정책을 따른다.
|
||||
- `HomeAiCharacterItem.profileImage`가 null이면 호출부 이미지 로딩 정책에 따라 placeholder 또는 빈 상태를 표시한다.
|
||||
- `HomePopularCommunityPostItem.imageUrl` 또는 `audioUrl`이 null이어도 커뮤니티 feed는 본문/반응 수 중심으로 표시 가능해야 한다.
|
||||
- 유료 미구매 포스트의 가격 capsule은 `price` 값을 사용하고, 유료 구매 완료 또는 무료 포스트에는 가격 capsule을 표시하지 않는다.
|
||||
- `HomeBannerItem.type`별 이동 정책은 서버 정의와 기존 딥링크/이동 정책을 확인한 뒤 구현한다.
|
||||
|
||||
---
|
||||
|
||||
## 8. UX / UI Expectations
|
||||
- Figma `24:5514`의 세로 순서, 좌우 여백, 가로 스크롤 형태를 유지한다.
|
||||
- `HomeMainFragment`의 추천 tab page로 표시하며, `TextTabBarView`에서 추천 선택 상태가 명확해야 한다.
|
||||
- `ViewPager`나 swipe로 tab을 넘기는 동작은 없어야 하며, tab 글자 터치로만 전환한다.
|
||||
- 세로 스크롤 중에도 `TextTabBarView`가 화면에 남아 있어야 하므로, 스크롤 컨테이너는 `TextTabBarView` 아래 content 영역에만 적용한다.
|
||||
- 상단 라이브 목록, 배너, 추천 카드들은 어두운 홈 배경 위에서 기존 v2 widget 색상/typography/radius를 유지한다.
|
||||
- 각 섹션 제목은 기존 `view_section_title`을 사용한다.
|
||||
- `최근 응원이 많은 크리에이터`, `장르별 크리에이터`의 모두 팔로우 버튼은 success 전/후 상태가 명확히 구분되어야 한다.
|
||||
- 인기 커뮤니티는 keyword 없이 본문, 선택적 이미지, 유료 잠금 상태, reaction row를 보여야 한다.
|
||||
- 유료 미구매 커뮤니티 이미지는 내용을 바로 읽을 수 없도록 blur/lock overlay가 적용되어야 한다.
|
||||
- 유료 구매 완료 또는 무료 커뮤니티 이미지는 overlay 없이 표시하고, `구매완료` 태그는 표시하지 않는다.
|
||||
- 가로 목록은 화면 밖 다음 item이 일부 보이도록 Figma의 peek 느낌을 유지한다.
|
||||
- 커뮤니티 본문과 사업자 정보는 긴 텍스트가 화면 폭을 밀어내지 않아야 한다.
|
||||
- 사업자 정보는 기본 3줄만 보여 홈 탐색을 방해하지 않고, 사용자가 원할 때 전체 내용을 확인할 수 있어야 한다.
|
||||
- 터치 동작은 UI 컴포넌트 내부에서 목적지를 결정하지 않고 화면/adapter callback으로 위임한다.
|
||||
|
||||
---
|
||||
|
||||
## 9. Technical Constraints
|
||||
- 현재 프로젝트는 Android XML Views + Kotlin + ViewBinding 기반이므로 XML layout, RecyclerView/adapter, custom view 패턴을 우선한다.
|
||||
- UI는 기존 `kr.co.vividnext.sodalive.v2.main.HomeMainFragment`에 구현한다.
|
||||
- `HomeMainFragment`는 이번 범위에서 추천 content만 직접 구성하며, `ViewPager2`, `FragmentStateAdapter`, tab별 신규 Fragment 3개 구조는 도입하지 않는다.
|
||||
- `TextTabBarView` 아래 추천 content 영역만 `NestedScrollView` 또는 동등한 단일 세로 스크롤 컨테이너로 구성한다.
|
||||
- 신규 API/Repository/ViewModel 및 그와 연결된 하위 코드는 `kr.co.vividnext.sodalive.v2` 패키지 하위에 작성한다.
|
||||
- 기존 `kr.co.vividnext.sodalive.v2.widget.*` 컴포넌트를 우선 재사용한다.
|
||||
- 기존 API 인터페이스에 메서드를 추가하지 않고, 홈 추천 전용 신규 API 인터페이스를 만든다.
|
||||
- 이미지 로딩 라이브러리를 새로 추가하지 않고 기존 호출부의 이미지 로딩 방식을 따른다.
|
||||
- 외부 라이브러리를 추가하지 않는다.
|
||||
- API 모델은 기존 네트워크 스택과 JSON 파서 규칙을 따른다.
|
||||
- string resource 기반 다국어 처리를 적용하며, UI 표시 문자열을 Kotlin 코드에 직접 하드코딩하지 않는다.
|
||||
- Figma에 있는 기존 widget과 다른 신규 UI는 구현 계획에서 파일 경로와 테스트 범위를 명시한 뒤 최소 범위로 작성한다.
|
||||
- 신규 순수 변환 로직(activity type mapper, count/time formatter 등)은 가능하면 local unit test로 검증한다.
|
||||
|
||||
---
|
||||
|
||||
## 10. Metrics
|
||||
- PRD와 구현 계획에 Figma 섹션 배치, 재사용 widget, 신규 필요 컴포넌트가 한눈에 확인 가능하게 정리된다.
|
||||
- `HomeRecommendationResponse`의 각 필드가 노출/제외/확인필요 중 하나로 분류된다.
|
||||
- `LIVE`, `LIVE_REPLAY`, `AUDIO`, `COMMUNITY` 표시 문자열이 string resource 기반으로 매핑된다.
|
||||
- `HomeMainFragment`에서 `view_title_bar_home`, `TextTabBarView`, `view_section_title` 재사용이 확인된다.
|
||||
- `FeedCommunityView`에서 keyword 영역이 추천 페이지에 노출되지 않고, 이미지와 유료 미구매 overlay가 표시된다.
|
||||
- `AudioContentCardView`에서 `ic_content_tag_point`, `무료`, `FIRST` 태그가 조건에 맞게 표시되고, `ic_content_tag_original`은 표시되지 않는다.
|
||||
- 제외 대상인 `추천 필모그래피`, `또 다른 모습` 섹션이 화면에 생성되지 않는다.
|
||||
- 모두 팔로우 API success 후 버튼이 `모두 팔로우 완료`와 `ic_new_following` 상태로 변경된다.
|
||||
- 사업자 정보는 기본 3줄 말줄임, 더보기 후 전체 표시, 접기 후 3줄 복귀가 동작한다.
|
||||
- 기존 v2 widget 재사용 범위가 구현 diff에서 확인된다.
|
||||
- 관련 unit test, resource merge, `./gradlew :app:testDebugUnitTest` 또는 구현 범위에 맞는 단일 테스트가 성공한다.
|
||||
|
||||
---
|
||||
|
||||
## 11. Open Questions
|
||||
- 홈 추천 API endpoint는 `/api/v2/home/recommendations`로 확정됐다. HTTP method는 `GET`으로 가정하되, 구현 전 백엔드 계약 또는 기존 `HomeApi` 패턴에서 최종 확인한다.
|
||||
- 모두 팔로우 API는 `POST /api/v2/home/recommendations/creators/follow`, request body는 `FollowRecommendedCreatorsRequest(creatorIds: List<Long>)`로 확정됐다.
|
||||
- 인증 token 처리 방식은 아직 제공되지 않았다. 구현 전 기존 `HomeApi` 확장 지점과 공통 인증 interceptor 사용 여부를 확인해야 한다.
|
||||
- `genreCreators`와 `cheerCreators`의 profile grid는 기존 profile widget이 저장소에 있는지 추가 확인 후 재사용/신규를 결정한다.
|
||||
- 배너 `type`별 이동 정책(`eventId`, `creatorId`, `seriesId`, `link`)은 기존 딥링크/화면 이동 규칙과 백엔드 code 목록 확인이 필요하다.
|
||||
- `activityAt`, `releaseDate`, `createdAt`, `beginDateTime`의 표시 포맷은 기존 시간 formatter 재사용 여부를 구현 계획에서 확인한다.
|
||||
|
||||
---
|
||||
|
||||
## 12. 검증 기록
|
||||
- 2026-06-01: `docs/prd/sample-prd.md`와 `docs/agent-guides/work-plan-docs.md`를 확인해 신규 문서 위치와 PRD 작성 형식을 맞췄다.
|
||||
- 2026-06-01: Figma `24:5514`의 최상위 구조를 확인해 `live`, `banner`, `section-title`, `contents`, `chat-thumbnail`, `profile`, `feed`, 사업자 정보 섹션의 배치와 제외 후보 섹션을 PRD에 반영했다.
|
||||
- 2026-06-01: 저장소의 기존 v2 widget 패키지(`banner`, `livethumbnail`, `characterchatthumbnail`, `feed`, `AudioContentCardView`)와 관련 PRD를 확인해 재사용 후보를 문서화했다.
|
||||
- 2026-06-01: 사용자 추가 제공 정보에 따라 홈 추천 API URL을 `GET /api/v2/home/recommendations`로 PRD에 반영했다.
|
||||
- 2026-06-01: 사용자 추가 제공 정보에 따라 신규 API 인터페이스 방침, `HomeMainFragment` 구현 위치, `view_title_bar_home`, `TextTabBarView`, `view_section_title` 재사용, 모두 팔로우 API/버튼 상태, 변경된 `HomeFirstAudioContentItem`, `HomeAiCharacterItem` 응답 계약을 PRD에 반영했다.
|
||||
- 2026-06-01: 저장소에서 `HomeMainFragment`, `view_title_bar_home`, `view_section_title`, `TextTabBarView`, `ic_bar_cash`, `ic_bar_search`, `ic_bar_bell`, `ic_new_follow`, `ic_new_following` 존재를 확인했다.
|
||||
- 2026-06-01: Figma `24:9092`를 확인해 모두 팔로우 완료 버튼의 white capsule, `20dp` icon, `6dp` gap, `16sp` medium text 요구사항을 PRD에 반영했다.
|
||||
- 2026-06-01: 사용자 추가 제공 정보에 따라 모두 팔로우 request body `FollowRecommendedCreatorsRequest(creatorIds: List<Long>?)`와 섹션별 `creatorIds` 전달 정책을 PRD에 반영했다.
|
||||
- 2026-06-01: 사용자 추가 제공 정보에 따라 모두 팔로우 request body의 `creatorIds`를 nullable이 아닌 `List<Long>`으로 변경하고 null 의미 확인 항목을 제거했다.
|
||||
- 2026-06-01: 사용자 추가 제공 정보에 따라 모두 팔로우 API HTTP method를 `POST`로 확정해 PRD에 반영했다.
|
||||
- 2026-06-01: 사용자 추가 제공 정보에 따라 `HomeRecommendationResponse.popularCommunities`를 `popularCommunityPosts`로 변경하고, `HomePopularCommunityPostItem` 응답 계약을 PRD에 반영했다.
|
||||
- 2026-06-01: 사용자 추가 제공 정보에 따라 추천 페이지의 `FeedCommunityView` 수정 요구사항(keyword 제거, 이미지 추가, 유료 미구매 overlay, 구매완료 태그 제외)과 `AudioContentCardView` 태그 표시 정책을 PRD에 반영했다.
|
||||
- 2026-06-01: Figma `309:19774`, `309:19775`의 design context와 screenshot을 확인해 유료 미구매/구매 또는 무료 커뮤니티 feed의 이미지, blur/lock overlay, reaction row 요구사항을 문서화했다.
|
||||
- 2026-06-01: 저장소에서 `AudioContentCardView`가 `AudioContentTag.Original`, `Point`, `First`, `Free`와 `ic_content_tag_original`, `ic_content_tag_point`를 이미 지원하는 것을 확인했다.
|
||||
- 2026-06-01: 사용자 추가 제공 정보에 따라 `HomeFirstAudioContentItem`으로 표시하는 모든 아이템은 첫 콘텐츠로 간주해 `FIRST` 태그를 항상 표시하도록 PRD에 반영했다.
|
||||
- 2026-06-01: 사용자 추가 제공 정보에 따라 `HomePopularCommunityPostItem.content` 아래에 `price: Int`를 추가하고, 유료 미구매 가격 capsule 표시값으로 사용하도록 PRD에 반영했다.
|
||||
- 2026-06-01: 사용자 추가 제공 정보에 따라 `HomeFirstAudioContentItem.title` 아래에 `price: Int`를 추가하고, `price == 0`이면 무료 태그를 표시하도록 PRD에 반영했다.
|
||||
- 2026-06-01: 사용자 추가 제공 정보에 따라 `HomeFirstAudioContentItem`에서는 오리지널 여부를 판단하지 않고, 오리지널 태그를 항상 숨기는 정책으로 PRD에 반영했다.
|
||||
- 2026-06-01: 사용자 추가 제공 정보에 따라 `popularCommunityPosts.price`와 `existOrdered` 조합으로 유료 미구매/유료 구매 완료/무료 포스트 UI를 판정하도록 PRD에 반영했다.
|
||||
- 2026-06-02: 사용자 추가 제공 정보에 따라 `ViewPager`/swipe tab 전환을 제외하고, tab 글자 터치 전환만 허용하며, `TextTabBarView` 아래 추천 content 영역만 세로 스크롤되도록 요구사항을 반영했다.
|
||||
Reference in New Issue
Block a user