Files
sodalive-android/docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md

95 KiB

메인 홈 추천 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 하위에 둔다. 화면은 HomeMainFragmentfragment_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: 기존 구조 확인과 구현 경계 고정

  • 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.ktnetworkModule, repositoryModule, viewModelModule에 추가해야 함을 확인한다.
  • 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 추가

  • 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를 모두 포함한다.
  • 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}" 형태로 전달한다.
  • Task 2.3: Koin DI 등록

    • 수정: app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt
    • 구현 내용:
      • networkModuleHomeRecommendationApi 등록
      • repositoryModuleHomeRecommendationRepository 등록
      • 이후 Phase 4에서 생성할 HomeRecommendationViewModel 등록
    • 검증 명령: ./gradlew :app:compileDebugKotlin
    • 기대 결과: 신규 API/Repository import 및 Koin 등록 컴파일 성공.

Phase 3: 순수 mapper와 unit test 작성

  • 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 구현 전 실패.
  • 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.
  • 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이면 기존 CharacterChatThumbnailViewshouldShowOriginalTitle = false
      • HomePopularCommunityPostItemprice, 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 == trueAudioContentTag.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 호출하지 않고 버튼 상태 유지.
  • 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 치수 회귀 테스트를 추가한다.
  • 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를 실행한다.
  • 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 추가 회귀 테스트를 추가한다.
  • 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 카드 치수와 목록 간격 회귀 테스트를 추가한다.
  • 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 회귀 테스트를 추가한다.
  • 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 회귀 테스트를 추가한다.
  • 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 회귀 테스트를 추가한다.
  • 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<HomeGenreCreatorGroupItem>를 그룹 단위 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 회귀 테스트를 추가한다.
  • 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.
  • Task 7.4: 실기기 확인용 인기 커뮤니티 샘플 데이터 주입

    • 수정: app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt
    • 구현 내용:
      • 기존 phase6SampleContent()popularCommunityPosts 빈 목록을 무료/유료 미구매/구매 완료 샘플 3건으로 교체
      • 추천용 커뮤니티 샘플은 showKeyword = false로 유지
      • 유료 미구매 샘플은 lock overlay와 가격 capsule 확인용으로 구성
    • 주의: Phase 9 실제 API/ViewModel 바인딩 시 임시 샘플 데이터는 실제 상태 바인딩으로 교체한다.
    • 검증: 실제 기기에서 인기 커뮤니티 섹션과 카드 상태 3종을 육안 확인할 수 있다.
  • Task 7.5: 커뮤니티 이미지 고정 크기 제거와 동적 비율 적용

    • 수정: app/src/main/res/layout/view_feed_community.xml
    • 수정: app/src/main/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedCommunityView.kt
    • 수정: app/src/test/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedViewTest.kt
    • 구현 내용:
      • FeedCommunityView root와 이미지 container의 고정 폭을 제거하고 match_parent로 변경
      • 이미지 container 높이는 실제 card content width 기준으로 Figma 346:236 비율을 유지해 계산
      • Kotlin clipToOutline/ViewOutlineProvider.setRoundRect(...) clipping 계약 유지
    • 검증: 인기 커뮤니티 이미지 영역이 화면 폭에 맞춰 계산되어 고정 폭 때문에 잘리지 않는다.

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 또는 홈 화면 패턴 확인 후 적용
      • toastLiveDataBaseFragment.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 컴포넌트 내부에서 목적지를 결정하지 않는다.
  • Task 9.3: 홈 추천 응답 스키마 변경 대응

    • 수정: app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeRecommendationModels.kt
    • 수정: app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationUiModels.kt
    • 수정: app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationMappers.kt
    • 수정: 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
    • 수정: 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/HomeFirstAudioAdapter.kt
    • 수정: 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
    • 수정: app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt
    • 수정: app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt
    • 구현 내용:
      • 변경된 HomeLiveItem(roomId, creatorNickname, creatorProfileImage)을 라이브 UI model과 LiveThumbnailSimpleView 바인딩에 반영한다.
      • 변경된 HomeBannerItem(imageUrl, eventItem, creatorId, seriesId, link)을 배너 UI model과 BannerView 바인딩에 반영한다.
      • 변경된 HomeActiveCreatorItem(creatorNickname, creatorProfileImage, activityType, activityAt, targetId)을 최근 활동 UI model/mapper에 반영하고, 응답에서 제거된 creatorId 기반 프로필 이동은 제거한다.
      • 변경된 HomeCreatorItem(creatorNickname, creatorProfileImage), HomeFirstAudioContentItemreleaseDate 제거, HomeGenreCreatorGroupItem.genreName을 mapper/UI model에 반영한다.
      • 실제 UI에서 사용하지 않거나 응답에서 더 이상 제공하지 않는 nullable placeholder 값은 UI model에 추가하지 않는다.
    • 검증:
      • HomeMainFragmentLayoutTest에 변경된 응답 필드 기반 mapper 회귀 테스트를 추가한다.
      • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home recommendation mapper uses changed response fields"로 RED/GREEN을 확인한다.
      • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest", ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck로 회귀를 확인한다.
  • Task 9.4: 홈 추천 이미지 캐시 ANR 대응

    • 수정: app/src/main/java/kr/co/vividnext/sodalive/common/ImageLoaderProvider.kt
    • 생성: app/src/test/java/kr/co/vividnext/sodalive/common/ImageLoaderProviderTest.kt
    • 구현 내용:
      • 홈 추천 API 바인딩 후 다수 이미지 로딩 시 unexpected journal header 경고와 ANR이 발생하는 현상을 ImageLoaderProvider의 OkHttp cache journal 충돌 가능성으로 분리한다.
      • 기존 cacheDir/image_cache를 legacy OkHttp image cache로 보고 앱 시작 시 삭제한다.
      • 신규 이미지 loader OkHttp cache는 cacheDir/coil_image_cache로 분리해 손상된 기존 journal을 다시 읽지 않도록 한다.
    • 검증:
      • ImageLoaderProviderTest에 legacy cache directory와 신규 cache directory가 분리되는 회귀 테스트를 추가한다.
      • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.common.ImageLoaderProviderTest", ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest", ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck로 회귀를 확인한다.
  • Task 9.5: BannerView 홈 추천 배너 모델 직접 바인딩

    • 수정: app/src/main/java/kr/co/vividnext/sodalive/v2/widget/banner/BannerView.kt
    • 수정: app/src/main/java/kr/co/vividnext/sodalive/v2/widget/banner/BannerAdapter.kt
    • 수정: app/src/main/java/kr/co/vividnext/sodalive/v2/widget/banner/BannerItem.kt
    • 수정: app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeBannerBinder.kt
    • 수정: app/src/test/java/kr/co/vividnext/sodalive/v2/widget/banner/BannerViewTest.kt
    • 수정: app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt
    • 구현 내용:
      • BannerItemHomeRecommendationBannerUiModel과 동일한 배너 payload(imageUrl, eventItem, creatorId, seriesId, link)를 담는 위젯 전용 모델로 변경한다.
      • BannerView/BannerAdapter는 공용 위젯 독립성을 유지하기 위해 BannerItem만 사용하고, HomeBannerBinder에서 홈 추천 배너 UI model과 BannerItem을 변환한다.
      • HomeBannerBinder의 synthetic id 매칭 로직을 제거하고 클릭 시 BannerItem payload를 다시 HomeRecommendationBannerUiModel로 전달한다.
      • 2개 이상 배너에서 BannerAdapter.itemCount == Int.MAX_VALUE가 되더라도 submitItems()와 size/preview 갱신이 notifyItemRangeInserted(0, Int.MAX_VALUE) 또는 notifyItemRangeChanged(0, Int.MAX_VALUE) 같은 huge range notify를 호출하지 않도록 한다.
      • 단일 배너 상태에서는 notifyItemInserted/notifyItemChanged/notifyItemRemoved를 사용하고, Int.MAX_VALUE 가상 adapter 상태에서만 NotifyDataSetChanged lint를 좁은 helper에 제한해 전체 갱신을 수행한다.
    • 검증:
      • BannerViewTest에 carousel 설정 시 max range insert/change notify를 호출하지 않는 회귀 테스트와 단일 배너 specific notify 회귀 테스트를 추가한다.
      • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.banner.BannerViewTest", ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest", ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck로 회귀를 확인한다.
  • Task 9.6: 홈 추천 프로필 이미지 원형 처리와 상대 시간 표시 적용

    • 수정: app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/GetCommunityPostListResponse.kt
    • 수정: app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationMappers.kt
    • 수정: 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/HomeGenreCreatorAdapter.kt
    • 수정: app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeCheerCreatorAdapter.kt
    • 수정: app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt
    • 구현 내용:
      • 기존 GetCommunityPostListResponse.relativeTimeText(context)가 사용하는 UTC 서버 시간 파싱과 상대 시간 문자열 계산을 DTO 전용 확장 함수에서 재사용 가능한 일반 함수 또는 공통 확장 함수로 분리한다.
      • HomeActiveCreatorItem.activityAt은 UTC 값을 디바이스 기본 타임존 기준으로 해석한 뒤 time-ago timestamp 문자열로 변환해 HomeRecommendationRecentlyActiveCreatorUiModel.activityAt에 매핑한다.
      • HomePopularCommunityPostItem.createdAt도 같은 상대 시간 변환 함수를 사용해 FeedItem.Community.createdAtText에 매핑한다.
      • 방금 활동한 크리에이터, [장르] 크리에이터, 최근 응원이 많은 크리에이터의 creator profile image는 기존 Coil image loading 패턴에 CircleCropTransformation 또는 동일한 원형 clipping 방식을 적용한다.
      • 상대 시간 변환 실패 시 크래시 없이 기존 커뮤니티 포스트 정책과 동일하게 character_comment_time_just_now 문자열을 표시한다.
    • 검증:
      • HomeMainFragmentLayoutTest에 최근 활동 activityAt과 인기 커뮤니티 createdAt이 UTC 입력에서 상대 시간 문자열로 매핑되는 회귀 테스트를 추가한다.
      • HomeMainFragmentLayoutTest에 최근 활동/장르/응원 creator profile image가 원형 처리 계약을 적용하는 회귀 테스트를 추가한다.
      • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest", ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck로 회귀를 확인한다.

Phase 10: 배너 이동 정책 연결

  • Task 10.1: 홈 추천 배너 클릭 라우팅 구현
    • 수정: app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt
    • 확인: 기존 EventDetailActivity, UserProfileActivity, SeriesDetailActivity 라우팅 extra 계약
    • 구현 내용:
      • 배너 응답에서 type 값은 제거되었으므로 사용하지 않는다.
      • eventItem != null이면 EventDetailActivity로 이동한다.
      • creatorId != null이면 UserProfileActivity로 이동한다.
      • seriesId != null이면 SeriesDetailActivity로 이동한다.
      • link != null && link가 웹 URL이면 Intent.ACTION_VIEW를 사용해 외부 웹 URL로 이동한다.
      • link != null && link가 내부 딥링크이면 기존 딥링크 실행 정책에 따라 앱 내부 딥링크를 실행한다.
      • 여러 이동 대상 값이 함께 내려오면 위 순서대로 우선 처리한다.
      • 웹 URL도 내부 딥링크도 아닌 link는 이동하지 않는다.
    • 검증:
      • HomeMainFragmentLayoutTest 또는 라우팅 단위 테스트에 배너 목적지별 Intent 계약 회귀 테스트를 추가한다.
      • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest", ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck로 회귀를 확인한다.

Phase 11: 최종 검증과 문서 기록

  • Task 11.1: 단위 테스트와 빌드 검증

    • 실행: ./gradlew :app:testDebugUnitTest
    • 실행: ./gradlew :app:compileDebugKotlin
    • 실행: ./gradlew :app:mergeDebugResources
    • 실행: ./gradlew :app:ktlintCheck
    • 기대 결과: 모두 성공.
  • Task 11.2: 수동 확인

    • 확인 항목:
      • 추천 tab 선택 상태
      • tab 전환은 글자 터치로만 동작하고 swipe 전환은 없음
      • 세로 스크롤 시 title-bar와 TextTabBarView는 유지되고 TextTabBarView 아래 추천 content만 스크롤됨
      • 섹션 순서가 PRD 도식과 일치
      • 제외 섹션 미노출
      • 빈 리스트 섹션 숨김
      • 첫 오디오 태그 조건
      • activity type 다국어 문자열
      • 모두 팔로우 success 후 완료 상태
      • 배너 목적지별 클릭 이동
      • 인기 커뮤니티 유료/무료/구매 완료 이미지 상태
      • 사업자 정보 더보기/접기
    • 기대 결과: PRD Metrics 항목 충족.
  • Task 11.3: 검증 기록 누적

    • 수정: docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md
    • 수정: docs/20260601_메인_홈_추천_UI와_API_연동/prd.md
    • 구현 내용: 실행 명령, 결과, 실패 시 원인과 후속 조치를 문서 하단 검증 기록에 누적한다.
    • 주의: 기존 검증 기록은 삭제하거나 덮어쓰지 않는다.

확인 완료 사항

  • 배너 이동은 type 없이 eventItem, creatorId, seriesId, link 값 기준으로 처리하며, link는 웹 URL이면 외부 브라우저, 내부 딥링크이면 앱 내부 딥링크로 실행한다. 세부 구현은 Phase 10에서 진행한다.
  • 시간 표시는 RelativeTimeFormatter에 재사용 가능한 함수가 있으므로 activityAt, createdAt 변환에 해당 formatter를 사용한다.
  • 모두 팔로우 API는 success response의 data 형태에 의존하지 않고 ApiResponse.success == true만으로 완료 처리한다.
  • 사업자 정보 텍스트는 서버/설정값이 아니라 strings.xml에 정의된 문자열을 사용한다.

검증 기록

  • 2026-06-05: Task 10.1 구현으로 홈 추천 배너 클릭 라우팅을 연결했다. eventItemEventDetailActivity + Constants.EXTRA_EVENT, creatorIdUserProfileActivity + Constants.EXTRA_USER_ID, seriesIdSeriesDetailActivity + Constants.EXTRA_SERIES_ID, linkIntent.ACTION_VIEW로 실행한다. 여러 대상이 함께 내려오면 eventItemcreatorIdseriesIdlink 순서로 우선 처리한다. RED 검증은 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home banner route uses event creator series link priority"에서 HomeRecommendationBannerRoute, toHomeRecommendationBannerRoute() 미정의 컴파일 실패로 확인했다. 구현 후 동일 테스트는 최초 :app:kspDebugKotlinjava.io.EOFException으로 중단되었고, --rerun-tasks 재실행에서 BUILD SUCCESSFUL을 확인했다. 이후 내부 딥링크 판별 누락 지적에 따라 BuildConfig.APPSCHEME custom scheme과 https://${BuildConfig.APPSCHEME}.onelink.me app link만 내부 딥링크로 인정하고, mailto: 같은 외부 custom scheme은 이동하지 않도록 보강했다. RED 검증은 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home banner route maps creator series web and internal deeplink" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home banner route ignores blank malformed and scheme-less links"에서 2개 테스트 실패로 확인했고, 수정 후 동일 명령은 BUILD SUCCESSFUL을 확인했다.

  • 2026-06-05: Task 10.1 최종 회귀 검증으로 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest", ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. ktlintCheck에서는 기존 .editorconfigdisabled_rules 속성 deprecation warning만 출력되었고 신규 실패는 없었다.

  • 2026-06-05: Task 10.1 리뷰 반려 대응으로 라우팅 결정 테스트에 더해 실제 Intent 생성 계약 테스트를 추가했다. HomeRecommendationBannerRoute.toHomeRecommendationBannerIntent(context)를 추가해 EventDetailActivity/UserProfileActivity/SeriesDetailActivity component와 Constants.EXTRA_EVENT/Constants.EXTRA_USER_ID/Constants.EXTRA_SERIES_ID extra, Intent.ACTION_VIEW link data를 검증하도록 했다. 또한 creatorId + seriesId + link, seriesId + link 조합 우선순위 테스트를 추가했다. RED 검증은 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home banner route creates activity and link intents with expected extras"에서 toHomeRecommendationBannerIntent() 미정의 컴파일 실패로 확인했고, 구현 후 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home banner route creates activity and link intents with expected extras" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home banner route uses creator over series and series over link priority"는 BUILD SUCCESSFUL을 확인했다. 이후 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest", ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck를 재실행했고 모두 BUILD SUCCESSFUL을 확인했다.

  • 2026-06-05: 사용자 추가 요청에 따라 배너 link 값이 내부 딥링크이면 기존 딥링크 실행 정책에 따라 앱 내부 딥링크를 실행하도록 Task 10.1과 확인 완료 사항에 반영했다. 문서 변경만 수행했으므로 Gradle 검증은 실행하지 않고 문서 재읽기로 확인한다.

  • 2026-06-05: 사용자 요청에 따라 구현 중 확인 필요 섹션을 확인 완료 사항으로 정리하고, 배너 type 제거 후 eventItem/creatorId/seriesId/웹 URL link 기준 이동 정책을 새 Phase 10: 배너 이동 정책 연결로 추가했다. 기존 Phase 10: 최종 검증과 문서 기록Phase 11로 변경하고 task 번호와 수동 확인 항목에 배너 목적지별 클릭 이동 확인을 반영했다. 문서 변경만 수행했으므로 Gradle 검증은 실행하지 않았고, plan-task.md 재읽기로 Phase 10/11 및 확인 완료 사항 반영을 확인했다.

  • 2026-06-05: 사용자 요청에 따라 Phase 9에 Task 9.6: 홈 추천 프로필 이미지 원형 처리와 상대 시간 표시 적용을 추가했다. 기존 GetCommunityPostListResponse.relativeTimeText(context)dateUtc를 UTC로 파싱해 디바이스 타임존 기준 상대 시간 문자열을 만들고, 홈 추천 mapper가 현재 activityAt/createdAt 원문을 그대로 전달하는 것을 확인해 공통 상대 시간 함수 분리와 최근 활동/인기 커뮤니티 재사용 항목으로 기록했다. 문서 변경만 수행했으므로 Gradle 검증은 실행하지 않고 문서 재읽기로 확인한다.

  • 2026-06-05: Task 9.6 구현으로 RelativeTimeFormatter를 추가해 UTC timestamp를 디바이스 기본 타임존 기준 상대 시간 문자열로 변환하고, 홈 추천 응답 매핑에서 HomeActiveCreatorItem.activityAtHomePopularCommunityPostItem.createdAt에 적용했다. 최근 활동/장르/응원 creator profile image는 CircleCropTransformation 기반 공통 helper로 원형 로딩하도록 변경했다. RED 검증은 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"에서 homeCreatorProfileImageTransformations, toUiModel(context) 미정의 컴파일 실패로 확인했고, 구현 후 동일 targeted test와 ./gradlew :app:compileDebugKotlin은 BUILD SUCCESSFUL을 확인했다. ./gradlew :app:ktlintCheck는 최종 diff에 없는 기존 GetCommunityPostListResponse.ktcreator_community package-name 위반으로 실패해 별도 기존 이슈로 분리했다.

  • 2026-06-05: Task 9.6 후속 수정으로 HomeRecommendationViewModelContext를 주입해 보관하던 구조를 제거했다. HomeRecommendationViewModel은 다시 HomeRecommendationViewModel(get())로 Repository만 주입받고, UTC 상대 시간 문자열 변환은 HomeMainFragment의 UI 바인딩 경계에서 requireContext()로 처리하도록 이동했다. RED 검증은 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home recommendation viewmodel does not keep android context"에서 ViewModel source에 android.content.Context가 남아 실패하는 것으로 확인했다. 수정 후 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest", ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck가 모두 BUILD SUCCESSFUL임을 확인했다.

  • 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.ktnetworkModule/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_diagnosticskotlin-lsp 미설치로 실행되지 않아 Gradle compile/test/ktlint 결과로 보완했다.

  • 2026-06-02: 백엔드 activity type code가 대소문자와 무관하게 들어와도 매핑되도록 RecommendedActivityTypeTestlive, 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.ktHomeRecommendationViewModel import와 viewModel { HomeRecommendationViewModel(get()) } 등록을 추가했다. lsp_diagnosticskotlin-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.xmlConstraintLayout 기반으로 구성해 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_diagnosticskotlin-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_diagnosticskotlin-lsp 미설치와 XML LSP 미구성으로 실행되지 않았다. 연결 기기에서 런처 실행은 성공했지만 MainV2Activity 직접 실행은 exported=false로 차단되었고, 런처 경로는 splash의 검은 화면에 머물러 홈 추천 UI 실기기 육안 확인은 완료하지 못했다.

  • 2026-06-02: Phase 6 리뷰에서 Phase 7 전 빈 popularCommunityPosts 상태에도 인기 커뮤니티 섹션 container가 기본 노출될 수 있음을 확인했다. HomeMainFragmentLayoutTestpopular 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 기준 방금 활동한 크리에이터 섹션을 확인해 기존 세로 프로필형 카드가 디자인과 다름을 확인했다. HomeMainFragmentLayoutTestrecent activity creator item matches figma capsule dimensions 테스트를 먼저 추가했고, 기존 구현에서 실패하는 RED 상태를 확인했다. 이후 item_home_recent_activity_creator.xml244dp 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.xml205dp 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 dimensionsrv_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을 추가하고 HomeFirstAudioAdapterAudioContentCardView 대신 전용 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.xmlclipToOutline="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 = trueViewOutlineProvider.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과 맞췄다. HomeMainFragmentLayoutTestaudio 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: AudioContentCardViewitem_home_first_audio_content.xml의 badge별 제어를 위해 Original/First/Point/Free badge에 안정적인 id를 추가했다. AudioContentCardView의 runtime 생성 badge에도 동일 id를 부여하고, FirstFree 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를 유지한 채 marginEnd4dp로 적용했다. 수정 후 ./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_sectionVISIBLE로 남을 수 있다는 차단 이슈를 확인했다. HomeMainFragmentLayoutTestvisibleHomeGenreCreatorGroups() 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 실패를 확인했다. 이후 HomeGenreCreatorAdaptercalculateProfileSize()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을 확인했다.

  • 2026-06-05: Figma 24:5645 기준 Phase 7 인기 커뮤니티 섹션을 구현했다. 구현 전 FeedViewTestHomeMainFragmentLayoutTest에 기존 Community keyword 기본 노출, 추천용 keyword 숨김, 이미지 null 시 이미지 영역 GONE, 유료 미구매 overlay/가격 capsule, 구매 완료 overlay 숨김, 인기 커뮤니티 최대 10개 노출 테스트를 추가했고, 기존 구현에서 새 필드/뷰/adapter/helper 미존재로 RED 컴파일 실패를 확인했다. 이후 FeedItem.CommunityimageUrl, price, existOrdered, showKeyword 기본값 필드를 추가하고, FeedCommunityView/FeedAdapter가 커뮤니티 primary image와 유료 overlay를 지원하도록 확장했으며, HomePopularCommunityAdapterHomeMainFragment 바인딩을 추가했다. popularCommunityPosts는 최대 10개만 표시한다.

  • 2026-06-05: Phase 7 검증으로 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.feed.FeedViewTest" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest", ./gradlew :app:mergeDebugResources, ./gradlew :app:compileDebugKotlin, ./gradlew :app:testDebugUnitTest, ./gradlew :app:ktlintCheck를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. 최초 병렬 Gradle 실행은 KSP cache 경합으로 실패해 ./gradlew --stop 후 순차 재실행했다. ktlint 최초 실행은 새 긴 줄 위반으로 실패했고 줄바꿈 수정 후 재실행으로 성공했다. lsp_diagnostics는 Kotlin/XML LSP 미구성으로 실행하지 못해 Gradle compile/test/ktlint로 보완했다.

  • 2026-06-05: Phase 7 리뷰 게이트에서 audioUrl 상세 이동 데이터 유실과 유료 미구매 커뮤니티 원본 이미지 로드 가능성이 차단 이슈로 지적됐다. HomeMainFragmentLayoutTestpopular community mapper preserves audio url for detail data, home popular community adapter does not load original image for locked paid post 회귀 테스트를 추가했고, 구현 전 audioUrl 필드 미존재로 RED 컴파일 실패를 확인했다. 이후 FeedItem.Community.audioUrl 기본값 필드와 mapper 전달을 추가하고, HomePopularCommunityAdapterprice > 0 && existOrdered == false인 item의 원본 imageUrl을 로드하지 않도록 수정했다.

  • 2026-06-05: Phase 7 차단 이슈 수정 후 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest", ./gradlew :app:mergeDebugResources, ./gradlew :app:ktlintCheck, ./gradlew :app:compileDebugKotlin, ./gradlew :app:testDebugUnitTest를 실행했고 모두 BUILD SUCCESSFUL을 확인했다.

  • 2026-06-05: 보안 재리뷰에서 유료 미구매 item으로 재바인딩할 때 기존 Coil 비동기 요청이 늦게 완료될 가능성이 차단 이슈로 지적됐다. HomePopularCommunityAdapter.bindImage()의 null/blank 경로에서 imageView.dispose()로 기존 Coil 요청을 취소한 뒤 drawable을 비우도록 보완했다. 이후 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest", ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck, ./gradlew :app:testDebugUnitTest를 실행했고 모두 BUILD SUCCESSFUL을 확인했다.

  • 2026-06-05: Phase 7 인기 커뮤니티 UI를 실제 기기에서 확인할 수 있도록 HomeMainFragment.phase6SampleContent()popularCommunityPosts에 무료/유료 미구매/구매 완료 샘플 3건을 임시 주입했다. 샘플은 추천용 계약에 맞춰 keyword를 숨기고, 유료 미구매 샘플은 lock overlay와 가격 capsule을 확인할 수 있게 구성했다. ./gradlew :app:compileDebugKotlin, ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest", ./gradlew :app:ktlintCheck, ./gradlew :app:mergeDebugResources는 모두 BUILD SUCCESSFUL을 확인했다. ./gradlew :app:installDebug는 연결 기기가 없어 No connected devices!로 실패해 실기기 설치/육안 확인은 수행하지 못했다.

  • 2026-06-05: 사용자 확인 중 커뮤니티 게시물 이미지가 잘리는 문제가 있어 view_feed_community.xml의 root 374dp, 이미지 container 346dp x 236dp 고정 크기를 제거했다. root와 이미지 container는 match_parent로 두고, FeedCommunityView.onMeasure()에서 실제 card content width 기준 Figma 346:236 비율로 이미지 높이를 계산하도록 변경했다. Kotlin clipToOutline/ViewOutlineProvider.setRoundRect(...) 계약은 유지했다. FeedViewTest에 동적 이미지 폭/높이 회귀 테스트를 추가했으며, 최초 테스트는 이미지 URL 미바인딩으로 container가 GONE이라 실패했고 테스트 조건을 실제 표시 상태로 수정한 뒤 성공했다. 이후 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.feed.FeedViewTest", ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest", ./gradlew :app:compileDebugKotlin, ./gradlew :app:mergeDebugResources, ./gradlew :app:ktlintCheck가 모두 BUILD SUCCESSFUL임을 확인했다.

  • 2026-06-05: Phase 8 사업자 정보 UI 구현으로 fragment_v2_main_home.xml에 기본 maxLines=3/ellipsize=endtv_home_business_info_toggle을 추가하고, HomeBusinessInfoBinderHomeMainFragment.setUpBusinessInfo()에서 더보기/접기 토글과 TextView.post { lineCount } 기반 3줄 이하 toggle 숨김 처리를 연결했다. RED 검증으로 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"를 먼저 실행해 HomeBusinessInfoBinder, tv_home_business_info_toggle 미존재 컴파일 실패를 확인했고, 구현 후 동일 targeted test와 ./gradlew :app:mergeDebugResources가 BUILD SUCCESSFUL임을 확인했다.

  • 2026-06-05: 사용자 확인에 따라 home_recommendation_business_info와 기존 company_info 중복을 제거했다. fragment_v2_main_home.xml은 공용 @string/company_info를 참조하도록 변경하고, 한국어/영어/일본어 home_recommendation_business_info 리소스는 삭제했다. 리뷰 게이트에서 지적된 접힘 상태 lineCount 측정 문제는 전체 줄 수 측정 시 일시적으로 maxLines=Int.MAX_VALUE/ellipsize=null로 측정한 뒤 다시 접힘 상태로 되돌리도록 보완했고, 4줄 이상일 때 toggle이 노출되는 회귀 테스트를 추가했다.

  • 2026-06-05: HomeBusinessInfoBinder의 const naming warning을 해결하기 위해 CollapsedMaxLinesCOLLAPSED_MAX_LINES로 변경했다. 또한 긴 텍스트 줄 수 측정은 접힘 상태의 기존 layout을 읽지 않도록 HomeMainFragment에서 전체 줄 표시 상태를 적용한 뒤 다음 post에서 lineCount를 읽고 다시 접힘 상태로 되돌리도록 보완했다.

  • 2026-06-05: 사용자 추가 요청에 따라 사업자 정보 더보기/접기를 별도 하단 버튼이 아니라 본문 말줄임표 우측에 붙는 inline text action으로 변경하는 후속 작업을 진행한다.

  • 2026-06-05: 사업자 정보 더보기/접기를 하단 별도 버튼에서 본문 inline action으로 변경했다. tv_home_business_info_toggle View를 제거하고 HomeBusinessInfoBinder에서 접힘 상태는 … 더보기, 펼침 상태는 본문 뒤 접기를 clickable span으로 붙이도록 구현했다. HomeMainFragmentLayoutTest에 별도 toggle View 제거, inline 더보기 suffix, clickable span 확장/접기, 3줄 이하 action 미노출 테스트를 추가했다. 검증으로 ./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-05: 사업자 정보 inline 더보기가 말줄임표와 너무 붙어 보여 … 더보기에서 … 더보기로 간격을 한 칸 늘렸다. 관련 inline suffix 테스트 기대값도 함께 갱신한다.

  • 2026-06-05: 펼침 상태의 접기 action도 본문에 붙어 보이지 않도록 action 앞에 공백 2칸을 추가했다. 접힘 상태의 더보기 간격과 동일한 시각적 여백을 유지하도록 테스트 기대값을 갱신했다.

  • 2026-06-05: Phase 9 RED 검증으로 HomeMainFragmentLayoutTesthome main fragment phase9 replaces sample content with viewmodel state binding, home main fragment phase9 connects loading toast and follow all callbacks 소스 계약 테스트를 추가했다. 최초 실행은 테스트 source 경로 기준 문제로 FileNotFoundException이 발생해 경로를 보정했고, 이후 phase6SampleContent가 남아 있어 AssertionError로 RED 실패하는 것을 확인했다.

  • 2026-06-05: Phase 9.1로 HomeMainFragment에서 임시 phase6SampleContent()와 sample helper를 제거하고 HomeRecommendationViewModel을 주입해 recommendationStateLiveData, isLoading, toastLiveData observe 및 loadRecommendations() 호출을 연결했다. Content는 섹션 bind, Empty/Error는 빈 content bind로 섹션을 숨기고, loading은 LoadingDialog, toast는 BaseFragment.showToast()로 처리한다.

  • 2026-06-05: Phase 9.2로 live/banner/popular community callback을 Fragment에 연결하고, 기존 라우팅 extra가 확인된 creator/audio/AI/community만 최소 Intent 이동으로 연결했다. 목적지가 확정되지 않은 live/banner는 adapter callback을 Fragment로 위임하되 no-op으로 유지했다. HomeRecentActivityCreatorAdapter, HomeRecentDebutCreatorAdapter, HomeFirstAudioAdapter, HomeAiCharacterAdapter, HomeGenreCreatorAdapter, HomeCheerCreatorAdapter에는 기존 호출부 영향이 없도록 기본값 있는 click callback을 추가했다.

  • 2026-06-05: Phase 9 GREEN/검증으로 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home main fragment phase9*", ./gradlew :app:compileDebugKotlin, ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest", ./gradlew :app:mergeDebugResources, ./gradlew :app:ktlintCheck, ./gradlew :app:testDebugUnitTest를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. 최초 병렬 compileDebugKotlin은 Kotlin output directory 경합으로 실패했으나 순차 재실행에서 성공해 캐시/경합 문제로 분리했다. lsp_diagnostics는 Kotlin/XML LSP 미구성으로 실행하지 못해 Gradle compile/test/ktlint로 보완했다.

  • 2026-06-05: Phase 9.3으로 변경된 홈 추천 응답 스키마를 반영했다. HomeLiveItemroomId, creatorNickname, creatorProfileImage만 사용하고, HomeBannerItemimageUrl, eventItem, creatorId, seriesId, link로 매핑한다. HomeActiveCreatorItemcreatorNickname, creatorProfileImage, activityType, activityAt, targetId로 매핑하고 응답에서 제거된 creatorId 기반 최근 활동 프로필 이동은 제거했다. HomeCreatorItemcreatorNickname/creatorProfileImage, HomeFirstAudioContentItemreleaseDate 제거, HomeGenreCreatorGroupItem.genreName도 mapper/UI model에 반영했다. 실제 UI에서 쓰지 않거나 응답에서 제공하지 않는 nullable placeholder 값은 추가하지 않았다.

  • 2026-06-05: Phase 9.3 RED 검증으로 HomeMainFragmentLayoutTesthome recommendation mapper uses changed response fields를 추가하고 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home recommendation mapper uses changed response fields"를 실행했다. 구현 전 HomeRecommendationMappers.kt가 제거된 liveId, creatorId, imageUrl, title, beginDateTime, bannerId, type, linkUrl, targetId, releaseDate, genre 필드를 참조해 :app:compileDebugKotlin 컴파일 실패하는 RED 상태를 확인했다. mapper/UI model/adapter 수정 후 동일 테스트를 재실행해 BUILD SUCCESSFUL을 확인했다.

  • 2026-06-05: Phase 9.3 회귀 검증으로 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest", ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. 전체 홈 테스트 최초 실행에서는 non-null profile image helper가 Coil ImageLoaderProvider 초기화를 요구해 실패했고, 빈 문자열 이미지를 이미지 없음으로 처리하도록 HomeFirstAudioAdapter, HomeGenreCreatorAdapter, HomeCheerCreatorAdapter를 보완한 뒤 재실행해 성공했다. ktlintCheck에서는 기존 .editorconfig disabled_rules deprecation warning만 출력됐고 lint 실패는 없었다.

  • 2026-06-05: Phase 9.3 리뷰에서 HomeBannerBinderimageUrl을 click source key로 쓰면 중복 이미지 URL 배너에서 첫 번째 항목으로 잘못 매칭될 수 있다는 차단 이슈를 확인했다. HomeBannerBinder를 position 기반 synthetic id 매칭으로 수정하고, HomeMainFragmentLayoutTesthome banner binder matches duplicate image banners by position 회귀 테스트를 추가했다.

  • 2026-06-05: Phase 9.3 리뷰 지적 수정 후 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home banner binder matches duplicate image banners by position", ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest", ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck를 재실행했고 모두 BUILD SUCCESSFUL을 확인했다. 배너 회귀 테스트 최초 실행은 HomeRecommendationBannerSection, HomeRecommendationBannerUiModel import 누락으로 컴파일 실패했고, import 보정 후 성공했다.

  • 2026-06-05: Phase 9.4로 홈 추천 API 응답 바인딩 후 java.io.IOException: unexpected journal header: [libcore.io.DiskLruCache, 1, 2, ], allocator 3.x is not supported 경고와 함께 앱이 멈추는 문제를 이미지 캐시 journal 충돌 가능성으로 분리했다. ImageLoaderProvider에서 기존 cacheDir/image_cache를 앱 시작 시 삭제하고, 신규 OkHttp image cache directory를 cacheDir/coil_image_cache로 분리했다. 구현 전 ImageLoaderProviderTest는 cache directory 상수 미정의로 RED 컴파일 실패했고, 구현 후 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.common.ImageLoaderProviderTest"가 BUILD SUCCESSFUL임을 확인했다.

  • 2026-06-05: Phase 9.4 회귀 검증으로 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest", ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. ktlintCheck에서는 기존 .editorconfig disabled_rules deprecation warning만 출력됐고 lint 실패는 없었다.

  • 2026-06-05: Phase 9.4 리뷰에서 ImageLoaderProviderTest가 상수명 차이만 검증해 init()의 legacy cache 삭제와 신규 cache 생성 계약을 충분히 고정하지 못한다는 차단 이슈를 확인했다. 테스트를 보강해 Robolectric 환경에서 cacheDir/image_cache/journal을 만든 뒤 ImageLoaderProvider.init(context) 호출 후 legacy directory 삭제와 coil_image_cache 생성까지 검증하도록 수정했다. 보강 후 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.common.ImageLoaderProviderTest", ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest", ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck를 재실행했고 모두 BUILD SUCCESSFUL을 확인했다.

  • 2026-06-05: Phase 9.5로 BannerView.setItems() 직후 앱이 멈추는 문제를 배너 모델 payload 누락과 BannerAdapter의 huge range notify 가능성으로 분리했다. BannerItem을 삭제하지 않고 HomeRecommendationBannerUiModel과 동일한 payload(imageUrl, eventItem, creatorId, seriesId, link)를 담도록 확장해 공용 위젯 독립성을 유지했으며, HomeBannerBinder는 synthetic id 변환 없이 홈 배너 UI model과 BannerItem을 변환하도록 단순화했다. 또한 carousel 목록에서 itemCount == Int.MAX_VALUE여도 notifyItemRangeInserted(0, Int.MAX_VALUE)notifyItemRangeChanged(0, Int.MAX_VALUE)를 호출하지 않도록 했고, 단일 배너는 specific notify를 쓰되 Int.MAX_VALUE 가상 adapter 상태에서만 NotifyDataSetChanged lint를 좁은 helper에 제한했다. 구현 전 BannerViewTest는 기존 BannerItem이 변경된 홈 배너 payload를 담지 못해 클릭 데이터 보존을 검증할 수 없는 RED 상태였고, 구현 후 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.banner.BannerViewTest"가 BUILD SUCCESSFUL임을 확인했다.

  • 2026-06-05: Phase 9.5 회귀 검증으로 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest", ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck를 추가 실행했고 모두 BUILD SUCCESSFUL을 확인했다. ktlintCheck에서는 기존 .editorconfig disabled_rules deprecation warning만 출력됐고 lint 실패는 없었다.