# 메인 홈 추천 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`를 실행한다. - [x] **Task 6.7: 라이브 섹션 Figma 정합 수정** - 기준: Figma `24:5516` - 수정: `app/src/main/res/layout/fragment_v2_main_home.xml` - 수정: `app/src/main/res/values/dimens.xml` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeLiveAdapter.kt` - 수정: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - 구현 내용: - `rv_home_lives` 높이는 아이템 높이에 따라가도록 `wrap_content` 유지 - `rv_home_lives` 시작 padding `20dp` 적용 - 라이브 아이템 간격을 Figma 기준 `14dp`로 적용 - 라이브는 최대 20개까지 표시하고, 20개 초과 시 `HomeLiveAdapter` 마지막 item으로 `58dp x 102dp` 검은 배경 `전체` item 추가 - 검증: `HomeMainFragmentLayoutTest`에 live row `wrap_content`, live adapter item gap, 20개 초과 시 `전체` item 추가 회귀 테스트를 추가한다. - [x] **Task 6.8: 최근 데뷔한 크리에이터 Figma 정합 수정** - 기준: Figma `24:5534` - 수정: `app/src/main/res/layout/item_home_recent_debut_creator.xml` - 수정: `app/src/main/res/layout/fragment_v2_main_home.xml` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeRecentDebutCreatorAdapter.kt` - 수정: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - 생성: `app/src/main/res/drawable/bg_home_recent_debut_card.xml` - 생성: `app/src/main/res/drawable/bg_home_recent_debut_dim_gradient.xml` - 구현 내용: - 기존 원형 profile item을 `205dp x 259dp` 이미지 카드로 변경 - 카드 radius `14dp`, 하단 dim gradient, 닉네임 `24sp` bold 중앙 정렬 적용 - `rv_home_recent_debut_creators` 시작 padding `14dp`, item gap `4dp` 적용 - 검증: `HomeMainFragmentLayoutTest`에 recent debut 카드 치수와 목록 간격 회귀 테스트를 추가한다. - [x] **Task 6.9: 처음부터 함께 성장! Figma 정합 수정** - 기준: Figma `24:5539` - 생성: `app/src/main/res/layout/item_home_first_audio_content.xml` - 수정: `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/HomeRecyclerItemLayoutParams.kt` - 수정: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - 구현 내용: - `AudioContentCardView` 재사용 대신 첫 오디오 전용 item layout을 사용한다. - 썸네일을 `185dp x 185dp`, radius `14dp` 카드로 구성한다. - creator profile을 `42dp x 42dp`, creator nickname `14sp`로 구성하고 상세 시간은 표시하지 않는다. - `rv_home_first_audio_contents` 시작 padding `14dp`, item gap `4dp`를 적용한다. - `First`, `Point`, `Free` tag 표시 조건은 기존 UI model의 `tags`를 그대로 사용한다. - 검증: `HomeMainFragmentLayoutTest`에 first audio 카드 치수, 목록 간격, tag visibility 회귀 테스트를 추가한다. - [x] **Task 6.10: AI 캐릭터 섹션 Figma 정합 수정** - 기준: Figma `24:5551` - 확인: `app/src/main/res/layout/view_character_chat_thumbnail.xml` - 수정: `app/src/main/res/layout/fragment_v2_main_home.xml` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeAiCharacterAdapter.kt` - 수정: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - 구현 내용: - `크리에이터와 이야기를 나눠요!` 목록 시작/끝 padding을 `14dp`로 적용한다. - AI 캐릭터 item gap을 `4dp`로 적용한다. - adapter에서 `view_character_chat_thumbnail.xml`의 root `185dp x wrap_content` 치수를 generic layout params로 덮어쓰지 않는다. - 검증: `HomeMainFragmentLayoutTest`에 AI 캐릭터 목록 spacing과 adapter item dimension 회귀 테스트를 추가한다. - [x] **Task 6.11: 장르 크리에이터 모두 팔로우 버튼 Figma 정합 수정** - 기준: Figma `24:5611`, 완료 버튼 `24:9092` - 수정: `app/src/main/res/layout/view_home_follow_all_button.xml` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeFollowAllButtonBinder.kt` - 수정: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - 구현 내용: - 기본 상태는 `모두 팔로우하기`, `ic_new_follow`, 투명 배경과 흰색 30% stroke, 흰색 텍스트를 적용한다. - 완료 상태는 `모두 팔로우 완료`, `ic_new_following`, 흰색 배경, 검은색 텍스트를 적용한다. - 완료 상태에서는 터치해도 callback을 호출하지 않는다. - 검증: `HomeMainFragmentLayoutTest`에 모두 팔로우 기본/완료 상태와 완료 상태 no-op 회귀 테스트를 추가한다. - [x] **Task 6.12: 장르의 크리에이터 Figma 정합 추가 수정** - 기준: Figma `24:5611` - 수정: `app/src/main/res/layout/fragment_v2_main_home.xml` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeGenreCreatorAdapter.kt` - 생성 후보: `app/src/main/res/layout/item_home_genre_creator_group.xml` - 생성 후보: `app/src/main/res/layout/item_home_genre_creator_profile.xml` - 생성 후보: `app/src/main/res/drawable/bg_home_genre_creator_group.xml` - 구현 내용: - `genreCreators: List`를 그룹 단위 page로 표시한다. - 각 장르 page는 `374dp` 폭, `14dp` padding/radius/gap, dark-blue gradient 배경을 적용한다. - 각 page 내부에 장르 제목, 8명 creator profile grid, 모두 팔로우 버튼을 포함한다. - 전체 장르 page 목록은 free scroll이 아니라 `PagerSnapHelper` 기반 banner snapping으로 이동한다. - 빈 creator group은 숨기고 최대 5개 장르 group만 표시한다. - 검증: `HomeMainFragmentLayoutTest`에 genre page 치수/gradient/grid/snap/helper/filtering 회귀 테스트를 추가한다. - [x] **Task 6.13: 최근 응원이 많은 크리에이터 Figma 정합 추가 수정** - 기준: Figma `24:5636` - 수정: `app/src/main/res/layout/fragment_v2_main_home.xml` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeCheerCreatorAdapter.kt` - 생성 후보: `app/src/main/res/layout/item_home_cheer_creator_group.xml` - 생성 후보: `app/src/main/res/drawable/bg_home_cheer_creator_group.xml` - 구현 내용: - 섹션은 특정 width로 고정하지 않고 화면 폭을 채우며 horizontal padding으로 card 폭을 조정한다. - `최근 응원이 많은 크리에이터` card는 `14dp` padding/radius/gap, `gray_900` 배경을 적용한다. - creator profile은 장르 크리에이터와 유사한 simple profile grid로 표시하고 최대 8명만 노출한다. - 모두 팔로우 버튼은 card 내부 하단에 포함하고 기존 기본/완료 상태를 유지한다. - 검증: `HomeMainFragmentLayoutTest`에 cheer card/grid/width/follow callback 회귀 테스트를 추가한다. --- ### 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`로 분리했다. 동작 변경 없이 파일 구조만 정리하는 리팩터링이다. - 2026-06-02: Figma `24:5516` 기준 `ll_home_live_section`을 확인해 라이브 행 높이, `전체` item, 아이템 간격이 디자인과 다름을 확인했다. `HomeMainFragmentLayoutTest`에 live row 치수, live adapter item gap, 20개 초과 시 `전체` item 추가 회귀 테스트를 먼저 추가했고, 구현 전 adapter `전체` view type 미지원과 20개 cap 미지원으로 RED 실패를 확인했다. 이후 `fragment_v2_main_home.xml`에서 별도 `tv_home_live_more` overlay를 제거하고, `HomeLiveAdapter`가 live를 최대 20개까지 표시한 뒤 20개 초과 시 마지막 item으로 `58dp x 102dp` 검은 배경 `전체`를 추가하도록 수정했으며, 동일 테스트가 BUILD SUCCESSFUL로 통과함을 확인했다. - 2026-06-02: 라이브 섹션 `전체` item 방식 변경 후 `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:testDebugUnitTest`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. `lsp_diagnostics`는 Kotlin/XML LSP 미구성으로 실행하지 못해 Gradle compile/test/ktlint로 보완했다. - 2026-06-02: Figma `24:5534` 기준 `최근 데뷔한 크리에이터` 섹션을 확인해 기존 `112dp` 세로 원형 profile item이 디자인과 다름을 확인했다. `HomeMainFragmentLayoutTest`에 recent debut 카드 치수와 목록 간격 테스트를 먼저 추가했고, 기존 구현에서 `recent debut creator item matches figma card dimensions`, `home recent debut section matches figma list spacing` 두 테스트가 실패하는 RED 상태를 확인했다. - 2026-06-02: `item_home_recent_debut_creator.xml`을 `205dp x 259dp` 이미지 카드 구조로 변경하고, radius `14dp` 배경/하단 dim gradient/`24sp` bold 중앙 닉네임을 적용했다. `HomeRecentDebutCreatorAdapter`에는 최근 데뷔 전용 `4dp` item gap을 적용했고, `rv_home_recent_debut_creators` 시작 padding을 Figma 기준 `14dp`로 맞췄다. 동일 targeted 테스트 재실행 결과 BUILD SUCCESSFUL을 확인했다. - 2026-06-02: 최근 데뷔 수정 후 `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`는 BUILD SUCCESSFUL을 확인했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`는 이번 최근 데뷔 테스트는 통과했지만 기존 `home live section matches figma row dimensions`가 `rv_home_lives` 높이 기대값(`102dp`)과 현재 XML `wrap_content`가 맞지 않아 실패했다. 이후 사용자 확인에 따라 `rv_home_lives` 높이는 아이템 높이를 따라가야 하므로 `wrap_content`가 올바른 계약으로 정정했다. - 2026-06-02: Figma `24:5539` 기준 `처음부터 함께 성장!` 섹션을 확인해 기존 `AudioContentCardView` 기반 item이 전용 profile row 구조와 item gap을 재현하기 어렵다는 점을 확인했다. `HomeMainFragmentLayoutTest`에 first audio 전용 item 치수, 목록 간격, tag visibility 테스트를 먼저 추가했고, 구현 전 `item_home_first_audio_content`와 관련 view ID 미존재로 RED 컴파일 실패를 확인했다. - 2026-06-02: `item_home_first_audio_content.xml`을 추가하고 `HomeFirstAudioAdapter`가 `AudioContentCardView` 대신 전용 item을 inflate하도록 변경했다. 썸네일은 `185dp x 185dp`, creator profile은 `42dp x 42dp`, item gap은 `4dp`로 맞췄고, `First`/`Point`/`Free` tag visibility는 기존 `tags` 모델로 바인딩했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"` 재실행 결과 BUILD SUCCESSFUL을 확인했다. - 2026-06-02: 리뷰에서 first audio 썸네일 이미지 clipping과 `Free` tag visibility 테스트 누락이 차단 이슈로 지적되어 `item_home_first_audio_content.xml`에 `clipToOutline="true"`를 추가하고 `HomeMainFragmentLayoutTest`에서 `First`/`Point`/`Free` 개별 tag visibility를 모두 검증하도록 보강했다. 이후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `./gradlew :app:testDebugUnitTest`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-02: first audio adapter에서 nullable image URL이 재사용된 ViewHolder의 이전 이미지를 남길 수 있는 케이스를 확인했다. `first audio adapter clears nullable images` 테스트를 추가해 수정 전 실패를 확인한 뒤, `HomeFirstAudioAdapter`에서 `coverImage`/`creatorProfileImage`가 null이면 drawable을 명시적으로 비우도록 수정했다. 이후 해당 테스트, `HomeMainFragmentLayoutTest` 전체, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:ktlintCheck`, `./gradlew :app:testDebugUnitTest`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-02: first audio 썸네일은 이미지뿐 아니라 `FIRST`/`Point`/`Free` overlay까지 같은 radius로 잘려야 한다는 사용자 확인에 따라 Coil transformation 방식이 아니라 썸네일 parent `FrameLayout` 전체에 Kotlin `clipToOutline = true`와 `ViewOutlineProvider.setRoundRect(..., 14dp)`를 적용했다. XML `android:clipToOutline`은 lint 경고를 피하기 위해 사용하지 않았다. `first audio adapter clips thumbnail container` 테스트를 추가했고, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `./gradlew :app:testDebugUnitTest`를 실행해 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-02: `AudioContentCardView`의 이미지 표시 영역도 first audio 전용 item과 동일하게 이미지와 overlay tag가 함께 roundRect clipping되도록 확인하고, `First`/`Point`/`Free` tag 속성을 `item_home_first_audio_content.xml`과 맞췄다. `HomeMainFragmentLayoutTest`에 `audio content card clips image area and overlay tags together`, `audio content card tag attributes match first audio item` 회귀 테스트를 추가했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `./gradlew :app:testDebugUnitTest`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-02: `AudioContentCardView`와 `item_home_first_audio_content.xml`의 badge별 제어를 위해 `Original`/`First`/`Point`/`Free` badge에 안정적인 id를 추가했다. `AudioContentCardView`의 runtime 생성 badge에도 동일 id를 부여하고, `First`와 `Free` badge 높이를 `24dp`로 유지하도록 맞췄다. `HomeMainFragmentLayoutTest`에서 badge id/높이 계약을 검증하도록 보강했으며 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `./gradlew :app:testDebugUnitTest`를 실행해 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-02: Figma `24:5551` 기준 `크리에이터와 이야기를 나눠요!` 섹션을 확인해 기존 목록 padding `20dp`, adapter item gap `12dp`, generic `RecyclerView.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)` 적용이 `view_character_chat_thumbnail.xml`의 root `185dp x wrap_content` 계약을 보존하지 못함을 확인했다. `HomeMainFragmentLayoutTest`에 character thumbnail root 치수, AI 캐릭터 목록 spacing, adapter item dimension 회귀 테스트를 먼저 추가했고, 수정 전 targeted test에서 새 테스트 2건 실패로 RED를 확인했다. - 2026-06-02: `rv_home_ai_characters` padding을 `14dp`로 맞추고, `HomeAiCharacterAdapter`에서 inflate된 `view_character_chat_thumbnail.xml` root layout params를 유지한 채 `marginEnd`만 `4dp`로 적용했다. 수정 후 `./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/XML LSP 미구성으로 실행하지 못해 Gradle compile/test/ktlint로 보완했다. - 2026-06-04: Figma `24:5611`, 완료 버튼 `24:9092` 기준 장르 크리에이터 모두 팔로우 버튼을 확인해 기본 상태는 `ic_new_follow`, 투명 배경과 흰색 30% stroke, 흰색 텍스트이고 완료 상태는 `ic_new_following`, 흰색 배경, 검은색 텍스트여야 함을 확인했다. `HomeMainFragmentLayoutTest`에 모두 팔로우 기본/완료 상태와 완료 상태 no-op 회귀 테스트를 먼저 추가했고, 구현 전 `bg_home_follow_all_button` 리소스 미존재로 RED 컴파일 실패를 확인했다. - 2026-06-04: `bg_home_follow_all_button.xml`을 추가하고 `view_home_follow_all_button.xml`의 기본 상태를 Figma 활성 상태로 변경했다. `HomeFollowAllButtonBinder`는 완료 상태에서 `bg_round_corner_999_white`, 검은 텍스트, `ic_new_following`, `모두 팔로우 완료`를 적용하고 터치 시 callback을 호출하지 않도록 수정했다. 이후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `./gradlew :app:testDebugUnitTest`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. `./gradlew :app:installDebug`는 연결된 기기가 없어 `No connected devices!`로 실패해 실기기 설치 확인은 수행하지 못했다. - 2026-06-04: 사용자 추가 요청에 따라 Figma `24:5611` 기준 `장르의 크리에이터` 섹션을 그룹 단위 snapping card 구조로 수정했다. 구현 전 `HomeMainFragmentLayoutTest`에 genre group card/profile/snapping/filtering/follow callback 회귀 테스트를 추가했고, 기존 구현에서 `item_home_genre_creator_group`, `bg_home_genre_creator_group`, `submitGroups` 등 새 계약 미존재로 RED 컴파일 실패를 확인했다. 이후 `HomeGenreCreatorAdapter`를 group page adapter로 변경하고, `HomePagerSnapRecyclerView`, `item_home_genre_creator_group.xml`, `item_home_genre_creator_profile.xml`, `bg_home_genre_creator_group.xml`을 추가해 각 page에 장르 제목, 8명 grid, 모두 팔로우 버튼, dark-blue gradient, `PagerSnapHelper` snapping을 적용했다. targeted 테스트 최초 재실행에서는 null image가 Coil `ImageLoaderProvider`를 요구해 실패했고, nullable image는 drawable을 명시적으로 비우도록 수정한 뒤 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`가 BUILD SUCCESSFUL임을 확인했다. 추가 검증으로 `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `./gradlew :app:testDebugUnitTest`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. ktlint 최초 실행은 테스트 파일 긴 줄 2곳으로 실패했고 줄바꿈 수정 후 재실행으로 성공했다. `lsp_diagnostics`는 Kotlin/XML LSP 미구성으로 실행하지 못해 Gradle compile/test/ktlint로 보완했다. 실기기 수동 확인은 연결 기기가 없어 수행하지 못했다. - 2026-06-04: 사용자 추가 요청에 따라 Figma `24:5636` 기준 `최근 응원이 많은 크리에이터` 섹션을 card 내부 profile grid 구조로 수정했다. 구현 전 `HomeMainFragmentLayoutTest`에 cheer card/grid/width/follow callback 회귀 테스트를 추가했고, 기존 구현에서 `item_home_cheer_creator_group`, `bg_home_cheer_creator_group`, `HomeCheerCreatorAdapter(onFollowAllClick)`, `submitSection` 등 새 계약 미존재로 RED 컴파일 실패를 확인했다. 이후 `HomeCheerCreatorAdapter`를 단일 card adapter로 변경하고, `item_home_cheer_creator_group.xml`, `bg_home_cheer_creator_group.xml`을 추가해 `14dp` horizontal padding 기반 full-width card, 최대 8명 4열 grid, card 내부 모두 팔로우 버튼을 적용했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `./gradlew :app:testDebugUnitTest`, `./gradlew :app:installDebug`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. `adb shell monkey -p kr.co.vividnext.sodalive.debug -c android.intent.category.LAUNCHER 1`로 설치된 debug 앱 실행까지 확인했다. 리뷰 게이트에서 무조건 승인도 확인했다. `lsp_diagnostics`는 Kotlin/XML LSP 미구성으로 실행하지 못해 Gradle compile/test/ktlint로 보완했다. - 2026-06-04: 리뷰에서 모든 genre group의 creators가 비어 있을 때 adapter는 빈 목록이 되지만 `ll_home_genre_creator_section`이 `VISIBLE`로 남을 수 있다는 차단 이슈를 확인했다. `HomeMainFragmentLayoutTest`에 `visibleHomeGenreCreatorGroups()` all-empty 회귀 테스트를 추가했고, helper 미구현으로 RED 컴파일 실패를 확인했다. 이후 `HomeRecommendationUiModels.kt`에 visible group helper를 추가하고 `HomeMainFragment.bindGenreCreatorSection()`의 visibility와 submit 기준을 같은 filtered/capped 목록으로 맞췄으며, adapter 자체의 empty/max 5 방어도 유지했다. 수정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `./gradlew :app:testDebugUnitTest`를 재실행했고 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-04: 장르 크리에이터 group card 폭을 고정 `374dp`가 아니라 현재 화면 가로 크기에서 좌우 여백 `28dp`를 뺀 값으로 계산하도록 수정했다. 구현 전 `HomeMainFragmentLayoutTest`의 group layout 계약을 `match_parent`로 바꾸고 adapter item width가 parent width `402dp - 28dp = 374dp`가 되는 회귀 테스트를 추가했으며, 기존 구현에서 XML `374dp` 고정 및 adapter 폭 미계산으로 RED 실패를 확인했다. 이후 `item_home_genre_creator_group.xml` root width를 `match_parent`로 바꾸고 `HomeGenreCreatorAdapter`에서 parent width 기반 `RecyclerView.LayoutParams`를 설정하도록 변경했다. 수정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-04: 작은 해상도에서 장르 크리에이터 grid의 profile item/image `75dp` 고정값 때문에 4열 끝부분이 잘릴 수 있어, group card 내부 grid 가용 폭에 맞춰 profile cell 크기를 계산하도록 수정했다. 구현 전 `HomeMainFragmentLayoutTest`에 작은 parent width `360dp` 기준 cell size가 `(360 - 28 - 28 - 14*3) / 4`로 계산되는 회귀 테스트를 추가했고, 기존 `75dp` 고정 구현에서 RED 실패를 확인했다. 이후 `item_home_genre_creator_group.xml`의 grid width를 `match_parent`로 변경하고, `HomeGenreCreatorAdapter`에서 card width, padding, column gap을 기준으로 profile item/image width/height를 동적으로 설정하도록 수정했다. `home_genre_creator_profile_size` dimen은 더 이상 사용하지 않아 제거했다. 수정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-04: `item_home_genre_creator_profile.xml` 자체에도 profile root와 image가 `75dp`로 고정되어 있어 XML 계약을 `ConstraintLayout` 기반의 가변 cell + 1:1 image ratio로 변경했다. 구현 전 `HomeMainFragmentLayoutTest`의 profile XML 테스트를 root `match_parent`, image `0dp x 0dp`, `layout_constraintDimensionRatio="1:1"` 계약으로 바꿨고 기존 `LinearLayout`/`75dp` XML에서 RED 실패를 확인했다. 이후 root를 `ConstraintLayout`으로 전환하고 image는 parent width를 따르는 1:1 ratio로, nickname은 image 하단에 constraint 되도록 수정했다. adapter에서는 image width/height 직접 덮어쓰기를 제거하고 root cell width만 grid layout params로 설정하게 정리했다. 수정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-04: 실제 화면에서 장르 크리에이터 grid 우측 끝 이미지가 절반 정도 잘리는 문제를 수정했다. 원인은 profile size 계산 시 group item width가 아직 `MATCH_PARENT`이거나 미측정일 때 display width fallback을 사용해 cell이 과대 계산될 수 있는 점이었다. 구현 전 `HomeMainFragmentLayoutTest`에 group width가 미확정인 상황에서도 parent RecyclerView width `360dp` 기준으로 cell size가 계산되는 회귀 테스트를 추가했고, 기존 display width fallback에서 RED 실패를 확인했다. 이후 `HomeGenreCreatorAdapter.GenreCreatorGroupViewHolder`가 parent RecyclerView를 보관하고, card width가 확정되지 않은 경우 `parent.width - 28dp`를 사용해 profile cell 크기를 계산하도록 수정했다. 수정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-04: 한국어/영어 장르 suffix의 선행 공백이 실제 기기에서 보이지 않고, 우측 끝 profile image가 일부 잘리는 문제를 추가 수정했다. 일본어에는 공백을 넣으면 안 되므로 title row margin 방식은 사용하지 않고 한국어/영어 string에 `\u0020`를 사용해 선행 공백을 보존했다. `xml:space="preserve"` 시도는 Android resource merge에서 실패해 제거했고, stale incremental resource 산출물(`app/build/intermediates/incremental/debug/packageDebugResources`, `mergeDebugResources`)을 삭제한 뒤 재검증했다. profile cell 크기는 itemView 측정 폭이 parent RecyclerView content 폭보다 크게 잡히는 경우를 방지하도록 parent padding을 제외한 content width로 cap 했다. 구현 전 `HomeMainFragmentLayoutTest`에 parent padding이 있는 상태에서 measured group width가 과대 측정되어도 cell size가 `RecyclerView content width` 기준으로 계산되는 회귀 테스트와 한국어/영어는 선행 공백, 일본어는 선행 공백 없음 테스트를 추가해 RED 실패를 확인했다. 수정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-04: 우측 profile image 잘림 범위가 group card/RecyclerView가 아니라 `item_home_genre_creator_group.xml` 내부 `GridLayout`임을 확인해, profile cell 계산 기준을 `GridLayout`의 실제 측정 width 우선으로 수정했다. 구현 전 `HomeMainFragmentLayoutTest`에 GridLayout width가 `300dp`로 측정된 경우 cell size가 `(300 - 14*3) / 4`로 계산되어야 하는 회귀 테스트를 추가했고, 기존 card width 기준 계산에서 RED 실패를 확인했다. 이후 `HomeGenreCreatorAdapter`의 `calculateProfileSize()`가 `creatorGrid.width`를 우선 사용하고, layout 이후 width가 확정되는 경우 `creatorGrid.post { ... }`에서 다시 바인딩해 재계산하도록 변경했다. 수정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다.