# 메인 홈 추천 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)` - `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>`를 그대로 반환하고, 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과 화면 상태 구성 - [x] **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에 직접 노출하지 않는다. - [x] **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)` - 빈 `creatorIds`면 API 미호출 - 모두 팔로우 success 후 해당 section/button 상태 완료 처리 - 검증: `ApiResponse.success == false`, `data == null`, Throwable 상황에서 기존 패턴처럼 unknown error toast를 노출한다. - [x] **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 구현 - [x] **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만 스크롤된다. - [x] **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 바인딩 전 제거하거나 실제 상태 바인딩으로 대체한다. - [x] **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`. - [x] **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`도 `라이브`로 표시. - [x] **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 조건과 일치. - [x] **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 호출하지 않고 버튼 상태 유지. - [x] **Task 6.5: 방금 활동한 크리에이터 Figma 정합 수정** - 기준: Figma `24:5529` - 수정: `app/src/main/res/layout/item_home_recent_activity_creator.xml` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomePhase6Adapters.kt` - 생성: `app/src/main/res/drawable/bg_home_recent_activity_card.xml` - 생성: `app/src/main/res/drawable/bg_home_recent_activity_type.xml` - 구현 내용: - 세로 프로필형 카드를 `244dp x 76dp` 가로 capsule 카드로 변경 - profile image를 `52dp x 52dp`로 변경 - 닉네임은 `18sp` bold, activity type은 `gray_700` tag, 보조 텍스트는 `activityAt` 표시 - 검증: `HomeMainFragmentLayoutTest`에 Figma capsule 치수 회귀 테스트를 추가한다. - [x] **Task 6.6: 홈 추천 섹션 adapter 파일 분리** - 삭제: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomePhase6Adapters.kt` - 생성: `HomeLiveAdapter.kt`, `HomeBannerBinder.kt`, `HomeRecentActivityCreatorAdapter.kt`, `HomeRecentDebutCreatorAdapter.kt`, `HomeFirstAudioAdapter.kt`, `HomeAiCharacterAdapter.kt`, `HomeCreatorProfileAdapter.kt`, `HomeGenreCreatorAdapter.kt`, `HomeCheerCreatorAdapter.kt`, `HomeFollowAllButtonBinder.kt`, `HomeRecyclerItemLayoutParams.kt` - 구현 내용: - 한 파일에 모여 있던 섹션별 adapter/binder를 섹션 단위 파일로 분리 - 공통 `RecyclerView.LayoutParams` 생성 로직은 package-private helper로 분리 - UI 동작과 바인딩 계약은 변경하지 않음 - 검증: `./gradlew :app:compileDebugKotlin`, `./gradlew :app:testDebugUnitTest --tests kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest`, `./gradlew :app:ktlintCheck`를 실행한다. --- ### 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 실제 상태 바인딩 전 제거하거나 대체해야 한다. - 2026-06-02: Phase 4.1로 `HomeRecommendationUiState`, `HomeRecommendationUiModels`, `HomeRecommendationResponse.toContent()`와 섹션별 mapper를 추가했다. DTO를 Fragment/ViewHolder 계약으로 직접 노출하지 않고, 첫 오디오 태그(`First`/`Point`/`Free`), AI character original title 표시 여부, 인기 커뮤니티 유료 상태 모델을 UI model로 변환하도록 구성했다. - 2026-06-02: Phase 4.2로 `HomeRecommendationViewModel`을 추가해 `recommendationStateLiveData`, `toastLiveData`, `isLoading`, `loadRecommendations()`, `followCreators(sectionKey, creatorIds)`를 구현했다. 추천 API 성공 시 Content/Empty 상태로 분기하고, 실패/data null/Throwable은 Error 상태와 unknown error toast를 노출하도록 했다. 빈 creatorIds에서는 모두 팔로우 API를 호출하지 않는다. - 2026-06-02: Phase 4.3으로 `AppDI.kt`에 `HomeRecommendationViewModel` import와 `viewModel { HomeRecommendationViewModel(get()) }` 등록을 추가했다. `lsp_diagnostics`는 `kotlin-lsp` 미설치로 실행되지 않아 Gradle 검증으로 보완할 예정이다. - 2026-06-02: Phase 4 검증으로 `./gradlew :app:compileDebugKotlin --rerun-tasks`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*"`, `./gradlew :app:testDebugUnitTest`, `./gradlew :app:ktlintCheck`, `./gradlew :app:compileDebugKotlin`을 실행했고 모두 BUILD SUCCESSFUL을 확인했다. 최초 증분 컴파일에서 `recentContentModule`, `chatTalkRoomModule` unresolved가 발생했으나 해당 파일이 존재하고 tracked 상태임을 확인했으며, `--rerun-tasks` 재실행 후 성공해 증분 캐시 문제로 분리했다. ktlint 최초 실행에서는 `HomeRecommendationMappers.kt`의 긴 줄 2건이 실패해 줄바꿈 수정 후 재실행으로 성공했다. - 2026-06-02: Phase 5 RED 검증으로 `HomeMainFragmentLayoutTest`를 추가하고 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`를 실행했다. 구현 전 `view_home_title_bar`, `text_tab_bar_home`, `nsv_home_recommendation_content`, 섹션/RecyclerView/title icon ID가 없어 컴파일 실패하는 RED 상태를 확인했다. - 2026-06-02: Phase 5.1로 `fragment_v2_main_home.xml`을 `ConstraintLayout` 기반으로 구성해 `view_title_bar_home`, `view_text_tab_bar`, `NestedScrollView` 추천 content shell을 배치했다. title bar와 tab bar는 scroll container 밖에 두고, 추천 content 내부에 섹션별 container와 가로 `RecyclerView`, 사업자 정보 영역만 추가했다. `ViewPager2`, `FragmentStateAdapter`, 랭킹/팔로잉 전용 content는 추가하지 않았다. - 2026-06-02: Phase 5.2로 `view_title_bar_home.xml`의 right icon을 `ic_bar_cash`, `ic_bar_search`, `ic_bar_bell` 순서로 확장하고, `HomeMainFragment`에서 `TextTabBarView` 메뉴를 추천/랭킹/팔로잉 순서와 selected index 0으로 바인딩했다. 좌우 swipe 전환은 추가하지 않았다. - 2026-06-02: Phase 5 GREEN/검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. `lsp_diagnostics`는 `kotlin-lsp` 미설치로 실행되지 않아 Gradle compile/test/ktlint 결과로 보완했다. - 2026-06-02: 홈 상단 `TextTabBarView`가 추천 화면 전용이 아니라 홈 탭 공용 탭(추천/랭킹/팔로잉)임을 반영해 string resource 이름을 `home_recommendation_tab_*`에서 `screen_home_tab_*`으로 변경했다. 구현 전 `HomeMainFragment` 참조만 먼저 바꾼 `./gradlew :app:compileDebugKotlin`에서 새 string ID 미정의로 RED 실패를 확인했다. - 2026-06-02: 배너 아래 섹션 제목 요구사항을 반영해 `최근 데뷔한 크리에이터`, `처음부터 함께 성장!`, `크리에이터와 이야기를 나눠요!`에는 `view_section_title`의 더보기 chevron을 표시하고, `방금 활동한 크리에이터`, `최근 응원이 많은 크리에이터`, `인기 커뮤니티`에는 chevron을 숨기도록 `HomeMainFragment`에서 초기화했다. 장르 섹션은 백엔드 장르명 바인딩을 위해 `view_section_title`을 쓰지 않고 별도 title row로 분리했으며 장르명 색상은 `@color/color_3bb9f1`로 지정했다. 구현 전 `HomeMainFragmentLayoutTest`에서 새 section title ID 미정의로 RED 실패를 확인했고, 이후 `./gradlew :app:compileDebugKotlin --rerun-tasks`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest" --rerun-tasks`, `./gradlew :app:mergeDebugResources`가 BUILD SUCCESSFUL임을 확인했다. 최초 병렬 Gradle 실행 중 KSP incremental cache 오류가 있었으나 순차 재실행으로 성공해 캐시 경합 문제로 분리했다. - 2026-06-02: Phase 6.1-6.4로 라이브/배너/최근 활동/최근 데뷔/첫 오디오/AI 캐릭터/장르 크리에이터/응원 크리에이터 adapter와 binder를 추가하고, `HomeMainFragment`에서 빈 리스트 섹션을 `GONE` 처리하도록 바인딩했다. 배너는 `BannerView`, 라이브는 `LiveThumbnailSimpleView`, 첫 오디오는 `AudioContentCardView`, AI 캐릭터는 `CharacterChatThumbnailView`를 재사용했고, 임시 샘플 데이터는 `phase6SampleContent()`에 격리해 Phase 9에서 실제 상태 바인딩으로 교체할 수 있게 했다. 모두 팔로우 버튼은 creatorId가 비어 있으면 callback을 호출하지 않도록 연결했다. - 2026-06-02: Phase 6 검증으로 `./gradlew :app:compileDebugKotlin`, `./gradlew :app:testDebugUnitTest --tests kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:ktlintCheck`, `./gradlew :app:installDebug`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. 최초 병렬 Gradle 실행은 Kotlin 출력 디렉터리 잠금 경합으로 실패해 `./gradlew --stop` 후 순차 재실행했다. `lsp_diagnostics`는 `kotlin-lsp` 미설치와 XML LSP 미구성으로 실행되지 않았다. 연결 기기에서 런처 실행은 성공했지만 `MainV2Activity` 직접 실행은 `exported=false`로 차단되었고, 런처 경로는 splash의 검은 화면에 머물러 홈 추천 UI 실기기 육안 확인은 완료하지 못했다. - 2026-06-02: Phase 6 리뷰에서 Phase 7 전 빈 `popularCommunityPosts` 상태에도 `인기 커뮤니티` 섹션 container가 기본 노출될 수 있음을 확인했다. `HomeMainFragmentLayoutTest`에 `popular community section is hidden until phase7 binding is implemented` 회귀 테스트를 추가해 수정 전 실패(AssertionError)를 확인했고, `fragment_v2_main_home.xml` 기본 visibility와 `HomeMainFragment.bindPopularCommunitySection()`을 추가한 뒤 동일 테스트가 BUILD SUCCESSFUL로 통과함을 확인했다. - 2026-06-02: Phase 6 수정 후 `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `./gradlew :app:testDebugUnitTest`를 재실행했고 모두 BUILD SUCCESSFUL을 확인했다. 재리뷰에서 이전 `인기 커뮤니티` 빈 섹션 노출 지적이 수정됐고, Phase 6.1-6.4 범위의 남은 승인 차단 이슈가 없다는 무조건 승인을 받았다. - 2026-06-02: Figma `24:5529` 기준 `방금 활동한 크리에이터` 섹션을 확인해 기존 세로 프로필형 카드가 디자인과 다름을 확인했다. `HomeMainFragmentLayoutTest`에 `recent activity creator item matches figma capsule dimensions` 테스트를 먼저 추가했고, 기존 구현에서 실패하는 RED 상태를 확인했다. 이후 `item_home_recent_activity_creator.xml`을 `244dp x 76dp` 가로 capsule 구조로 변경하고 `HomePhase6Adapters.kt`에서 보조 텍스트를 `activityAt`으로 바인딩하도록 수정했으며, 동일 테스트가 BUILD SUCCESSFUL로 통과함을 확인했다. - 2026-06-02: `HomePhase6Adapters.kt`에 모든 섹션 adapter/binder가 모여 있어 섹션별 유지보수가 어렵다는 지적에 따라 adapter/binder를 섹션 단위 파일로 분리했다. `HomePhase6Adapters.kt`는 삭제하고, 공통 item margin 생성 로직은 `HomeRecyclerItemLayoutParams.kt`로 분리했다. 동작 변경 없이 파일 구조만 정리하는 리팩터링이다.