Files
sodalive-android/docs/20260622_크리에이터_채널_후원_탭/plan-task.md

66 KiB

크리에이터 채널 후원 탭 구현 계획/TASK

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development 또는 superpowers:executing-plans to implement this plan task-by-task. 각 단계는 체크박스(- [ ])로 추적하고, 완료 즉시 - [x]로 갱신한다. 구현 범위 변경이 생기면 이 문서를 먼저 수정한 뒤 코드에 반영한다.

Goal: GET /api/v2/creator-channels/{creatorId}/donations 응답을 기반으로 크리에이터 채널 후원 탭에 후원 랭킹, 전체 후원 수, 후원 내역 목록, empty 상태, 후원하기 액션, pagination을 표시한다.

Architecture: 기존 CreatorChannelActivityViewPager2/CreatorChannelPagerAdapter 구조를 유지하고, CreatorChannelTab.Donation의 placeholder를 신규 CreatorChannelDonationFragment로 교체한다. 후원 탭 전용 Fragment/ViewModel/DTO/mapper/adapter는 kr.co.vividnext.sodalive.v2.creator.channel.donation 하위에 두되, API/Repository는 기존 채널 공통 CreatorChannelApi/CreatorChannelRepository에 endpoint만 최소 추가한다. 후원하기 dialog는 홈 탭과 동일한 LiveRoomDonationDialog 표시 경로를 Activity에 공통 helper로 분리해 재사용하고, 후원 탭 ViewModel은 후원 성공 후 첫 페이지를 재조회한다.

Tech Stack: Kotlin, Android XML Views, ViewBinding, RecyclerView, Retrofit, Gson, RxJava3, Koin, JUnit4/Robolectric local unit test.


전제와 성공 기준

  • PRD: docs/20260622_크리에이터_채널_후원_탭/prd.md
  • 기존 채널 컨테이너:
    • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt
    • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapter.kt
    • app/src/main/res/layout/activity_creator_channel.xml
  • 기존 채널 API/Repository:
    • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelApi.kt
    • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelRepository.kt
  • 기존 홈 탭 후원 UI/후원 액션 참조:
    • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelHomeSectionAdapter.kt
    • app/src/main/res/layout/item_creator_channel_home_donation.xml
    • app/src/main/res/layout/item_creator_channel_home_donation_row.xml
    • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeViewModel.kt
    • app/src/main/java/kr/co/vividnext/sodalive/live/room/donation/LiveRoomDonationDialog.kt
  • 기존 후원 전체보기 참조:
    • app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/donation/UserProfileDonationAllViewActivity.kt
    • Constants.EXTRA_USER_ID
  • Figma:
    • 전체 후원 탭: 290:9093
    • 후원 랭킹 섹션: 290:9097
    • 후원 empty 상태: 290:9008
    • 후원 empty UI 컨텐츠: 290:9009
  • API endpoint는 GET /api/v2/creator-channels/{creatorId}/donations이다.
  • 첫 페이지 page0, 기본 size20이다.
  • query parameter는 page, size만 전달한다.
  • 정렬 query parameter와 sort popup은 후원 탭에서 사용하지 않는다.
  • rankings는 서버가 항상 최대 8명까지만 내려준다.
  • 후원 랭킹 전체보기 버튼은 기존 UserProfileDonationAllViewActivity로 이동한다.
  • 현재 API는 비밀 후원 여부를 별도 필드로 내려주지 않으므로 비밀 후원 전용 UI/표시 분기는 구현하지 않는다.
  • 본인 채널에서는 content/empty 상태 모두 floating 후원하기 버튼을 숨긴다.
  • 본인 채널 empty 상태에서는 중앙 후원하기 button도 숨긴다.
  • empty UI는 별도 minHeight 없이 표시한다.
  • 후원 랭킹 섹션이 표시되는 경우에도 채널 후원 내역이 없으면 랭킹 섹션 아래에 empty UI를 표시한다.
  • 후원 성공 후에는 후원 탭의 donationCount, rankings, donations가 최신화되도록 첫 페이지를 재조회한다.
  • 구현 완료 후 최소 다음 명령을 실행한다.
    • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.*"
    • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Donation*"
    • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*"
    • ./gradlew :app:mergeDebugResources
    • ./gradlew :app:compileDebugKotlin
    • ./gradlew :app:ktlintCheck
    • git diff --check

Figma 참조 필요 Phase

  • Phase 1: 제한 참조
    • 기존 placeholder, 후원 dialog, 전체보기 Activity, owner 판정 경계 확인이 중심이며 Figma는 PRD 기준만 확인한다.
  • Phase 2: Figma 참조 불필요
    • API/DTO/Repository/ViewModel 계약은 서버 응답과 기존 FanTalk/Community 탭 패턴을 따른다.
  • Phase 3: 제한 참조
    • mapper와 UI model은 PRD와 기존 홈 후원 카드 정책을 함께 확인한다.
  • Phase 4: 필수 참조
    • 랭킹 카드, Sort-bar without sort, 후원 내역 item은 Figma 290:9093, 290:9097 기준으로 구현한다.
  • Phase 5: 필수 참조
    • empty 상태와 empty 후원하기 button은 Figma 290:9008 기준으로 구현한다.
  • Phase 6: 제한 참조
    • 탭 연결, pagination, 후원 dialog 재사용, 전체보기 이동은 기존 코드 패턴 중심으로 검증한다.
  • Phase 7: 필수 참조
    • 최종 수동 화면 검증은 PRD의 Figma 노드와 실제 화면을 대조한다.

파일 구조

  • 수정: app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapter.kt
    • CreatorChannelTab.Donation을 신규 CreatorChannelDonationFragment로 연결한다.
  • 수정: app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt
    • CreatorChannelDonationFragment.Host 구현, 후원 탭 선택 시 최초 로드, pagination trigger, ViewPager 높이 갱신, 전체보기 이동, 공통 후원 dialog 표시 helper, 후원 성공 후 홈 탭 refresh hook을 추가한다.
  • 수정: app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelApi.kt
    • 후원 탭 endpoint를 추가한다.
  • 수정: app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelRepository.kt
    • 후원 목록 조회 method를 추가한다. 기존 postChannelDonation()은 재사용한다.
  • 생성: app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/data/CreatorChannelDonationTabResponse.kt
    • CreatorChannelDonationTabResponse, MemberDonationRankingResponse, CreatorChannelDonationResponse를 정의한다.
  • 생성: app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationViewModel.kt
    • 최초 조회, retry, refresh, pagination, 후원 성공 후 재조회, loading/error/empty/content 상태를 관리한다.
  • 생성: app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/model/CreatorChannelDonationUiModels.kt
    • 랭킹 item, 후원 item, 화면 상태 UI model을 정의한다.
  • 생성: app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/model/CreatorChannelDonationMappers.kt
    • DTO를 UI model로 변환하고 순위 번호, 날짜 포맷, message fallback, header 색상을 결정한다.
  • 생성: app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationFragment.kt
    • 후원 탭 UI, adapter, retry, pagination error toast, 후원하기 callback, 전체보기 callback, host callback 연결을 담당한다.
  • 생성: app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/ui/CreatorChannelDonationAdapter.kt
    • 랭킹 섹션, count bar, 후원 내역 목록을 하나의 RecyclerView 또는 섹션 adapter로 표시한다.
  • 생성 가능: app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/ui/CreatorChannelDonationRankingAdapter.kt
    • 랭킹 grid가 nested RecyclerView로 구현될 때만 추가한다.
  • 생성: app/src/main/res/layout/fragment_creator_channel_donation.xml
    • RecyclerView, empty, error/retry, floating 후원하기 버튼 영역을 포함한다.
  • 생성: app/src/main/res/layout/item_creator_channel_donation_ranking.xml
    • 후원 랭킹 카드, 4열 x 2행 랭킹 grid, 전체보기 button을 구현한다.
  • 생성: app/src/main/res/layout/item_creator_channel_donation.xml
    • 후원 내역 item header, 캔 badge, 메시지를 구현한다.
  • 생성 가능: app/src/main/res/layout/item_creator_channel_donation_ranking_member.xml
    • 랭킹 member cell을 별도 adapter로 구현할 때만 추가한다.
  • 생성 가능: app/src/main/res/drawable/bg_creator_channel_donation_empty_button.xml
    • empty 후원하기 capsule 배경이 기존 drawable로 대응되지 않을 때만 추가한다.
  • 수정: app/src/main/res/values/strings.xml
    • 후원 탭 empty/error/retry/action/count/fallback 문구를 추가하거나 기존 문자열을 재사용한다.
  • 수정: app/src/main/res/values-en/strings.xml, app/src/main/res/values-ja/strings.xml
    • 신규 문자열의 다국어 값을 추가한다.
  • 수정: app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt
    • CreatorChannelDonationViewModel binding을 추가한다.
  • 테스트 생성:
    • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationMapperTest.kt
    • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationViewModelTest.kt
    • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationPaginationTest.kt
    • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationFragmentLayoutTest.kt
    • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationActionTest.kt
  • 테스트 수정:
    • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapterTest.kt
    • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt

Phase 1: 기존 구조 확인과 작업 경계 고정

  • Task 1.1: Donation 탭 placeholder 연결 지점 확인

    • 확인 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapter.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/model/CreatorChannelHomeUiModels.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapterTest.kt
    • 작업:
      • CreatorChannelTab.Donation이 현재 CreatorChannelPlaceholderFragment로 연결되는지 확인한다.
      • 신규 CreatorChannelDonationFragment.newInstance(creatorId)로 교체할 위치를 고정한다.
    • 검증:
      • 실행: rg -n "CreatorChannelTab\\.Donation|CreatorChannelPlaceholderFragment|createFragment" app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel
      • 기대 결과: Donation placeholder와 관련 테스트 갱신 지점이 확인된다.
    • 검증 기록:
      • 2026-06-22: rg -n "CreatorChannelTab\\.Donation|CreatorChannelPlaceholderFragment|createFragment" app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel 실행. CreatorChannelPagerAdapter.createFragment()에서 Home/Live/Audio/Series/Community/FanTalk만 실제 Fragment로 연결되고 Donationelse -> CreatorChannelPlaceholderFragment.newInstance(tab) 경로를 타는 것을 확인했다. CreatorChannelPagerAdapterTest도 현재 실제 Fragment 탭 외 나머지는 placeholder로 유지된다는 기대를 가진다. 향후 교체 위치는 CreatorChannelPagerAdapter.ktwhen (tab) 분기다.
  • Task 1.2: 기존 후원 dialog와 홈 후원 성공 처리 확인

    • 확인 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeFragment.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeViewModel.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelRepository.kt
    • 작업:
      • CreatorChannelActivity.onCreatorChannelDonationClicked()에서 LiveRoomDonationDialog를 여는 옵션을 확인한다.
      • 홈 탭의 CreatorChannelHomeViewModel.postChannelDonation()SharedPreferenceManager.can 차감 후 홈을 재조회하는지 확인한다.
      • 후원 탭에서도 같은 dialog 옵션과 CreatorChannelRepository.postChannelDonation()을 재사용하되, 성공 후 후원 탭 첫 페이지를 재조회하도록 설계한다.
    • 검증:
      • 실행: rg -n "onCreatorChannelDonationClicked|LiveRoomDonationDialog|postChannelDonation|SharedPreferenceManager.can" app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel
      • 기대 결과: 홈 후원 dialog 옵션, repository 위임, can 차감, 홈 재조회 흐름이 확인된다.
    • 검증 기록:
      • 2026-06-22: rg -n "onCreatorChannelDonationClicked|LiveRoomDonationDialog|postChannelDonation|SharedPreferenceManager.can" app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel 실행. CreatorChannelActivity.onCreatorChannelDonationClicked()가 owner가 아닐 때 LiveRoomDonationDialogisLiveDonation = true, messageMaxLength = 100, secretToggleLabelResId = R.string.screen_user_profile_channel_donation_secret, applySecretMissionMessageHint = false 옵션으로 열고, submit 시 homeActionDelegate?.postChannelDonation(...)로 위임하는 것을 확인했다. CreatorChannelHomeViewModel.postChannelDonation()CreatorChannelRepository.postChannelDonation() 호출 성공 시 SharedPreferenceManager.can = (SharedPreferenceManager.can - can).coerceAtLeast(0)로 차감하고 loadHome(content.header.creatorId)로 홈을 재조회한다.
  • Task 1.3: 기존 후원 전체보기 Activity 호출 계약 확인

    • 확인 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/donation/UserProfileDonationAllViewActivity.kt
      • app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileActivity.kt
      • app/src/main/java/kr/co/vividnext/sodalive/common/Constants.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/model/CreatorChannelHomeUiModels.kt
    • 작업:
      • 기존 UserProfileDonationAllViewActivityConstants.EXTRA_USER_ID를 요구하는지 확인한다.
      • 크리에이터 채널에서 보유한 creatorId가 기존 Activity 호출에 그대로 사용 가능한지 API/기존 호출 경로를 대조한다.
      • 식별자 불일치가 확인되면 구현 전에 PRD/계획 문서를 갱신하고 사용자에게 확인한다.
    • 검증:
      • 실행: rg -n "UserProfileDonationAllViewActivity|EXTRA_USER_ID|creatorId|userId" app/src/main/java/kr/co/vividnext/sodalive/explorer/profile app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel app/src/main/java/kr/co/vividnext/sodalive/common/Constants.kt
      • 기대 결과: 전체보기 Activity 인자 계약과 크리에이터 채널 보유 식별자가 확인된다.
    • 검증 기록:
      • 2026-06-22: rg -n "UserProfileDonationAllViewActivity|EXTRA_USER_ID|creatorId|userId" app/src/main/java/kr/co/vividnext/sodalive/explorer/profile app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel app/src/main/java/kr/co/vividnext/sodalive/common/Constants.kt 실행. UserProfileDonationAllViewActivityintent.getLongExtra(Constants.EXTRA_USER_ID, 0)를 읽고, 기존 UserProfileActivityIntent(..., UserProfileDonationAllViewActivity::class.java)Constants.EXTRA_USER_IDuserId를 전달한다. 크리에이터 채널의 creatorId도 채널 owner/member 식별자로 사용되고 있어 Phase 6에서 putExtra(Constants.EXTRA_USER_ID, creatorId)로 연결 가능한 전제를 확인했다. 추가로 UserProfileChannelDonationAllViewActivityConstants.EXTRA_USER_ID를 받는 채널 후원 목록 전체보기 화면으로 존재함을 확인했다. 현재 계획의 버튼은 후원 랭킹 섹션의 전체보기이므로 UserProfileDonationAllViewActivity 전제는 유지하되, 향후 요구가 후원 내역 전체보기로 바뀌면 UserProfileChannelDonationAllViewActivity로 계획을 먼저 갱신해야 한다.
  • Task 1.4: 기존 목록 탭 pagination/viewport 패턴 확인

    • 확인 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragment.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkViewModel.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityViewModel.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt
    • 작업:
      • 후원 ViewModel도 FIRST_PAGE = 0, DEFAULT_PAGE_SIZE = 20, requestGeneration, paginationErrorMessage, consumePaginationErrorMessage() 패턴을 따른다.
      • 후원 Fragment도 onCreatorChannelDonationTabSelected(), onCreatorChannelDonationScrolledToBottom(), onCreatorChannelDonationRefreshRequested(), onCreatorChannelDonationViewportHeightChanged() entry를 제공한다.
    • 검증:
      • 실행: rg -n "requestGeneration|paginationErrorMessage|consumePaginationErrorMessage|ScrolledToBottom|RefreshRequested|ViewportHeightChanged" app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel
      • 기대 결과: FanTalk/Community의 pagination, refresh, viewport 패턴이 확인된다.
    • 검증 기록:
      • 2026-06-22: rg -n "requestGeneration|paginationErrorMessage|consumePaginationErrorMessage|ScrolledToBottom|RefreshRequested|ViewportHeightChanged" app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel 실행. CreatorChannelFanTalkViewModelCreatorChannelCommunityViewModelrequestGeneration, paginationErrorMessage, consumePaginationErrorMessage() 패턴을 사용하고, FIRST_PAGE = 0, DEFAULT_PAGE_SIZE = 20 기준으로 첫 페이지와 pagination을 구분하는 것을 확인했다. Fragment entry는 onCreatorChannelFanTalkScrolledToBottom(), onCreatorChannelFanTalkRefreshRequested(), onCreatorChannelFanTalkViewportHeightChanged(minHeight) 및 Community의 scrolled/refresh 패턴을 확인했다. Activity는 현재 Live/Audio/Series/Community/FanTalk에 대해 하단 스크롤 dispatcher와 load-more 탭 판정을 가지고 있으며, Donation 추가 지점은 notifyCurrentCreatorChannelTabScrolledToBottom(), isCreatorChannelLoadMoreTab(), updateCreatorChannelTabViewportHeight()다.

Phase 2: API/DTO/Repository/ViewModel 계약 추가

  • Task 2.1: 후원 탭 DTO 추가

    • 생성:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/data/CreatorChannelDonationTabResponse.kt
    • 작업:
      • @Keep, @SerializedName 기반으로 CreatorChannelDonationTabResponse, MemberDonationRankingResponse, CreatorChannelDonationResponse를 추가한다.
      • PRD의 서버 필드명과 동일하게 donationCount, rankings, donations, page, size, hasNext, userId, nickname, profileImage, donationCan, profileImageUrl, can, message, createdAtUtc를 정의한다.
      • PRD의 @JsonProperty("hasNext")는 서버 계약 설명이므로 구현은 프로젝트 기존 Gson 패턴인 @SerializedName("hasNext")를 사용한다.
    • 검증:
      • 실행: ./gradlew :app:compileDebugKotlin
      • 기대 결과: 신규 DTO 추가 후 컴파일이 PASS한다.
    • 검증 기록:
      • 2026-06-22: CreatorChannelDonationTabResponse, MemberDonationRankingResponse, CreatorChannelDonationResponse@Keep/@SerializedName으로 추가했다. focused GREEN 실행 중 :app:compileDebugKotlin이 PASS해 DTO 컴파일을 확인했다.
  • Task 2.2: 후원 탭 endpoint와 Repository method 추가

    • 수정:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelApi.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelRepository.kt
    • 작업:
      • @GET("/api/v2/creator-channels/{creatorId}/donations") endpoint를 추가한다.
      • query parameter page, size만 전달한다.
      • Repository method는 getDonations(creatorId, page, size, token) 형태로 둔다.
      • 기존 postChannelDonation(creatorId, can, isSecret, message, token)은 후원 탭 ViewModel에서 재사용한다.
    • 검증:
      • 실행: ./gradlew :app:compileDebugKotlin
      • 기대 결과: API/Repository 추가 후 기존 Koin graph와 충돌 없이 컴파일된다.
    • 검증 기록:
      • 2026-06-22: CreatorChannelApi.getDonations(creatorId, page, size, Authorization)CreatorChannelRepository.getDonations(creatorId, page, size, token)를 추가했다. focused GREEN 실행 중 :app:compileDebugKotlin이 PASS해 endpoint/repository 컴파일을 확인했다.
  • Task 2.3: 후원 ViewModel RED 테스트 작성

    • 생성:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationViewModelTest.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationPaginationTest.kt
    • 작업:
      • 최초 로드가 page=0, size=20으로 repository를 호출하는 테스트를 작성한다.
      • 성공 응답이 있으면 Content(donationCount, rankings, donations, page, size, hasNext) 상태가 되는 테스트를 작성한다.
      • donationCount == 0 또는 표시 가능한 donations가 비어 있으면 Empty 상태가 되는 테스트를 작성한다.
      • hasNext == true에서 loadMore()page + 1을 호출하고 기존 donations 뒤에 append하는 테스트를 작성한다.
      • loading 중 중복 loadMore()를 막는 테스트를 작성한다.
      • 후원 성공 시 SharedPreferenceManager.can을 차감하고 첫 페이지를 재조회하는 테스트를 작성한다.
    • 검증:
      • 실행: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationViewModelTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationPaginationTest"
      • 기대 결과: 구현 전에는 신규 타입/메서드 부재로 FAIL한다.
    • 검증 기록:
      • 2026-06-22: CreatorChannelDonationViewModelTest, CreatorChannelDonationPaginationTest를 작성한 뒤 focused 테스트를 실행했다. 구현 전 CreatorChannelDonationViewModel, CreatorChannelDonationTabResponse, CreatorChannelRepository.getDonations 등 신규 타입/메서드 부재로 :app:compileDebugUnitTestKotlin이 FAIL하여 RED를 확인했다.
  • Task 2.4: 후원 ViewModel 구현

    • 생성:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationViewModel.kt
    • 작업:
      • CreatorChannelDonationUiState.Loading, Empty, Error, Content를 정의한다.
      • loadDonations(creatorId, isOwner), retryDonations(), refreshDonations(), loadMore(), postChannelDonation(can, isSecret, message), consumePaginationErrorMessage(), consumeActionToastMessage(), consumeDonationSuccessEvent()를 제공한다.
      • 첫 페이지 성공 후 donations.isEmpty() || donationCount == 0이면 Empty 상태로 전환한다.
      • 후원 성공 시 (SharedPreferenceManager.can - can).coerceAtLeast(0)로 로컬 can을 차감하고 첫 페이지를 재조회한다.
      • 후원 성공 event는 Fragment가 홈 탭 refresh를 요청할 수 있도록 1회성 상태로 노출한다.
    • 검증:
      • 실행: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationViewModelTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationPaginationTest"
      • 기대 결과: RED 테스트가 PASS한다.
    • 검증 기록:
      • 2026-06-22: CreatorChannelDonationViewModel에 Loading/Empty/Error/Content, 첫 페이지 조회, retry/refresh, loadMore append, pagination error consume, 후원 성공 후 can 차감/첫 페이지 재조회/성공 event를 구현했다. focused GREEN 명령에서 ViewModel/Pagination 테스트가 PASS했다.
  • Task 2.5: Koin binding 추가

    • 수정:
      • app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt
    • 작업:
      • CreatorChannelDonationViewModel import와 viewModel { CreatorChannelDonationViewModel(get(), get()) } binding을 추가한다.
      • 두 번째 dependency는 UtcRelativeTimeTextFormatter를 사용한다.
    • 검증:
      • 실행: ./gradlew :app:compileDebugKotlin
      • 기대 결과: Koin binding 추가 후 컴파일이 PASS한다.
    • 검증 기록:
      • 2026-06-22: AppDICreatorChannelDonationViewModel(get(), get()) binding을 추가했다. focused GREEN 실행 중 :app:compileDebugKotlin이 PASS해 Koin binding 컴파일을 확인했다.

Phase 3: UI model/mapper/공통 후원 표시 정책

  • Task 3.1: 후원 mapper RED 테스트 작성

    • 생성:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationMapperTest.kt
    • 작업:
      • 랭킹 순위가 응답 순서 기준 1부터 부여되는 테스트를 작성한다.
      • rankings는 서버 최대 8명 보장 전제를 유지하되 mapper가 응답 순서를 유지하는지 테스트한다.
      • createdAtUtcUtcRelativeTimeTextFormatter를 통해 표시되는 테스트를 작성한다.
      • message가 blank이면 기존 fallback %d캔을 후원하였습니다. 문구를 사용하는 테스트를 작성한다.
      • can 범위별 header 색상이 기존 홈 탭 정책과 동일한지 테스트한다.
      • 비밀 후원 전용 분기를 만들지 않는다는 source/mapper 계약 테스트를 작성한다.
    • 검증:
      • 실행: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationMapperTest"
      • 기대 결과: 구현 전에는 신규 mapper 부재로 FAIL한다.
    • 검증 기록:
      • 2026-06-22: CreatorChannelDonationMapperTest를 작성한 뒤 focused 테스트를 실행했다. 구현 전 donation data/model/mapper 부재로 :app:compileDebugUnitTestKotlin이 FAIL하여 RED를 확인했다.
  • Task 3.2: 후원 UI model과 mapper 구현

    • 생성:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/model/CreatorChannelDonationUiModels.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/model/CreatorChannelDonationMappers.kt
    • 작업:
      • CreatorChannelDonationRankingUiModel(rank, userId, nickname, profileImageUrl, donationCan)을 정의한다.
      • CreatorChannelDonationUiModel(nickname, profileImageUrl, can, message, createdAtText, headerColorResId)를 정의한다.
      • mapper는 MemberDonationRankingResponse.profileImageprofileImageUrl UI field로 변환한다.
      • mapper는 CreatorChannelDonationResponse.message.ifBlank { fallback } 정책을 적용한다.
      • header 색상은 기존 홈 탭 calculateCreatorChannelDonationHeaderColorRes(can)와 동일한 정책을 재사용하거나 후원 공통 위치로 이동한다.
      • 공통 위치로 이동할 경우 기존 홈 탭 import를 함께 갱신하고 동작 변경 없이 테스트를 유지한다.
    • 검증:
      • 실행: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationMapperTest"
      • 기대 결과: mapper 테스트가 PASS한다.
    • 검증 기록:
      • 2026-06-22: 후원 랭킹/후원 목록 UI model과 mapper를 추가했다. 랭킹 rank는 응답 순서 기준 1부터 부여하고, profileImageprofileImageUrl로 매핑하며, blank message는 기존 creator_channel_donation_fallback_message를 사용한다. header 색상은 홈 helper와 동일한 기준을 donation mapper 내부 함수로 구현했다. focused GREEN 명령에서 mapper 테스트가 PASS했다.

Phase 4: 후원 탭 content UI 구현

  • Task 4.1: 후원 탭 layout/source RED 테스트 작성

    • 생성:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationFragmentLayoutTest.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationActionTest.kt
    • 작업:
      • fragment_creator_channel_donation.xml에 RecyclerView, error/retry, empty, floating donation button id가 존재해야 한다는 source test를 작성한다.
      • item_creator_channel_donation_ranking.xml후원 랭킹, ranking container, 전체보기 button id가 존재해야 한다는 source test를 작성한다.
      • item_creator_channel_donation.xml에 profile, nickname, createdAt, can badge, message id가 존재해야 한다는 source test를 작성한다.
      • 본인 채널에서는 floating button이 숨겨진다는 Fragment/source 계약 테스트를 작성한다.
      • Phase 4에서는 전체보기 클릭이 CreatorChannelDonationFragment.Host callback으로 전달된다는 source 계약 테스트를 작성한다. UserProfileDonationAllViewActivityConstants.EXTRA_USER_ID 연결은 Phase 6에서 검증한다.
    • 검증:
      • 실행: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationActionTest"
      • 기대 결과: 구현 전에는 신규 layout/Fragment 부재로 FAIL한다.
    • 검증 기록:
      • 2026-06-22: CreatorChannelDonationFragmentLayoutTest, CreatorChannelDonationActionTest를 작성한 뒤 production UI 파일 추가 전 focused 테스트를 실행했다. fragment_creator_channel_donation 및 후원 layout/id 미존재로 :app:compileDebugUnitTestKotlin이 FAIL하여 RED를 확인했다. Phase 4 범위에 맞춰 전체보기는 Activity 직접 이동이 아니라 Host.onCreatorChannelDonationRankingAllClicked() callback 계약으로 검증했다.
  • Task 4.2: 후원 content layout과 adapter 구현

    • 생성:
      • app/src/main/res/layout/fragment_creator_channel_donation.xml
      • app/src/main/res/layout/item_creator_channel_donation_ranking.xml
      • app/src/main/res/layout/item_creator_channel_donation.xml
      • 필요 시 app/src/main/res/layout/item_creator_channel_donation_ranking_member.xml
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/ui/CreatorChannelDonationAdapter.kt
      • 필요 시 app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/ui/CreatorChannelDonationRankingAdapter.kt
    • 작업:
      • content 상태에서는 랭킹 섹션, Sort-bar without sort, 후원 내역 목록을 표시한다.
      • Sort-bar는 좌측 전체donationCount만 표시하고 정렬 UI를 만들지 않는다.
      • 랭킹 섹션은 Figma 290:9097 기준으로 75dp 원형 프로필, 닉네임, 순위 숫자를 최대 8명 표시한다.
      • 랭킹 전체보기 button은 adapter callback으로 Fragment/Activity에 전달한다.
      • 후원 item은 Figma support card 구조를 기준으로 profile, nickname, relative time, can badge, message를 표시한다.
      • header 색상과 fallback message는 mapper 결과를 사용한다.
    • 검증:
      • 실행: ./gradlew :app:mergeDebugResources
      • 기대 결과: 신규 layout/drawable/string resource가 병합된다.
      • 실행: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationActionTest"
      • 기대 결과: layout/source 계약 테스트가 PASS한다.
    • 검증 기록:
      • 2026-06-22: fragment_creator_channel_donation.xml, 랭킹/랭킹 member/후원 item layout, 랭킹 전체보기 capsule drawable, CreatorChannelDonationAdapter, CreatorChannelDonationRankingAdapter를 추가했다. Figma 290:9093, 290:9097 기준으로 black background, 52dp count bar, gray_900 랭킹 카드, 75dp 랭킹 프로필 grid, 전체보기 capsule, 후원 card header/profile/can badge/message를 구현했다. ./gradlew :app:mergeDebugResources와 Phase 4 layout/source 테스트가 PASS했다.
  • Task 4.3: 후원 Fragment 구현

    • 생성:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationFragment.kt
    • 작업:
      • CreatorChannelDonationFragment.newInstance(creatorId)를 제공한다.
      • Host interface에 isCreatorChannelOwner(), onCreatorChannelDonationContentChanged(), onCreatorChannelDonationRequested(onSubmit), onCreatorChannelDonationRankingAllClicked(), onCreatorChannelDonationCompleted()를 정의한다.
      • onCreatorChannelDonationTabSelected()에서 viewModel.loadDonations(creatorId, isOwner = host.isCreatorChannelOwner())를 호출한다.
      • content 상태에서 adapter에 랭킹/후원 내역을 submit하고 floating button은 !isOwner일 때만 표시한다.
      • 후원 button 클릭 시 host의 공통 후원 dialog를 열고 submit callback에서 viewModel.postChannelDonation(can, isSecret, message)를 호출한다.
      • 후원 성공 event를 받으면 host.onCreatorChannelDonationCompleted()를 호출해 홈 탭도 갱신 가능하게 한다.
      • pagination/action toast consume 패턴은 FanTalk Fragment와 동일하게 구현한다.
    • 검증:
      • 실행: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.*"
      • 기대 결과: 후원 탭 단위 테스트가 PASS한다.
    • 검증 기록:
      • 2026-06-22: CreatorChannelDonationFragment를 추가해 newInstance(creatorId), Host interface, 탭 최초 로드, refresh/loadMore/viewport entry, content adapter submit, owner floating button 숨김, 후원 요청 submit callback, pagination/action toast consume, 후원 성공 event callback을 구현했다. ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.*"가 PASS했다.

Phase 5: Empty/Error/문자열 리소스 구현

  • Task 5.1: empty/error 문자열과 다국어 리소스 추가

    • 수정:
      • app/src/main/res/values/strings.xml
      • app/src/main/res/values-en/strings.xml
      • app/src/main/res/values-ja/strings.xml
    • 작업:
      • creator_channel_donation_empty_message: 아직 후원이 없습니다.\n처음으로 크리에이터를 후원해 보세요!
      • creator_channel_donation_action: 후원하기
      • creator_channel_donation_all_label: 전체
      • creator_channel_donation_ranking_title: 후원 랭킹
      • creator_channel_donation_ranking_all: 전체보기
      • creator_channel_donation_error_message, creator_channel_donation_retry는 기존 FanTalk/Community 문구 패턴에 맞춰 추가하거나 기존 공통 문구를 재사용한다.
    • 검증:
      • 실행: ./gradlew :app:mergeDebugResources
      • 기대 결과: values/en/ja 문자열 병합이 PASS한다.
    • 검증 기록:
      • 2026-06-22: 기존 creator_channel_donation_* 문자열이 values, values-en, values-ja에 이미 추가되어 있음을 확인했다. Figma 290:9008 및 계획 문구에 맞춰 한국어 creator_channel_donation_empty_title아직 후원이 없습니다.\n처음으로 크리에이터를 후원해 보세요!로 갱신했다. ./gradlew :app:mergeDebugResources가 PASS해 문자열 병합을 확인했다.
  • Task 5.2: Figma 290:9008 empty 상태 구현

    • 수정:
      • app/src/main/res/layout/fragment_creator_channel_donation.xml
      • 필요 시 app/src/main/res/drawable/bg_creator_channel_donation_empty_button.xml
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationFragment.kt
    • 작업:
      • empty 상태에서는 RecyclerView, Sort-bar, floating button을 숨긴다.
      • empty 문구는 16sp regular, gray/500, center 정렬로 표시한다.
      • 타인 채널에서는 문구 아래 후원하기 capsule button을 표시한다.
      • 본인 채널에서는 empty 후원하기 button과 floating button을 모두 숨긴다.
      • empty 후원하기 button 터치 시 content 상태 floating button과 동일한 host 후원 dialog를 호출한다.
    • 검증:
      • 실행: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationFragmentLayoutTest"
      • 기대 결과: empty 상태 visibility/source 계약이 PASS한다.
      • 수동 확인: Figma 290:9008과 empty 문구 위치, button 색상/icon/text, owner 버튼 숨김을 대조한다.
    • 검증 기록:
      • 2026-06-22: RED로 CreatorChannelDonationFragmentLayoutTest에 empty 중앙 btn_creator_channel_donation_empty_write/icon/text id 및 Figma capsule source 계약을 추가하고, CreatorChannelDonationActionTest에 empty 중앙 button이 타인 채널에서만 표시되고 기존 후원 요청 callback을 재사용해야 한다는 source 계약을 추가했다. 구현 전 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationActionTest"가 신규 id 미존재(btn_creator_channel_donation_empty_write, iv_creator_channel_donation_empty_write, tv_creator_channel_donation_empty_write)로 FAIL해 RED를 확인했다.
      • 2026-06-22: fragment_creator_channel_donation.xml에 Figma 290:9008 기준 중앙 capsule 후원하기 button을 추가하고, CreatorChannelDonationFragment에서 empty 상태일 때 !state.isOwner인 경우만 중앙 button을 표시하도록 연결했다. empty 중앙 button 클릭은 content floating button과 같은 host.onCreatorChannelDonationRequested { can, isSecret, message -> viewModel.postChannelDonation(...) } 경로를 사용한다. ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationActionTest"가 PASS했다. 수동 대조는 Figma 290:9008 screenshot을 확인해 16sp gray/500 중앙 문구, 14dp 간격, soda capsule button 구조를 반영했다.
      • 2026-06-22: Phase 5/6 코드 리뷰 중 empty 중앙 후원하기 button 배경이 @color/white로 되어 있어 PRD의 soda/400 배경 및 흰색 icon/text 요구와 맞지 않는 문제를 확인했다. RED로 CreatorChannelDonationFragmentLayoutTestbg_creator_channel_donation_empty_button.xml@color/soda_400 계약을 추가했고, 기존 구현에서 실패하는 것을 확인한 뒤 배경을 @color/soda_400으로 수정했다.
      • 2026-06-22: Phase 5/6 코드 리뷰 중 empty 상태 중앙 후원하기 button으로 후원 API 실패 시 CreatorChannelDonationViewModel.setActionToastMessage()Content 상태에서만 메시지를 보관해 실패 toast가 사라지는 문제를 확인했다. RED로 CreatorChannelDonationViewModelTestEmpty 상태에서 채널 후원 실패는 action toast message를 emit하고 consume한다를 추가했고, 기존 구현에서 Empty.actionToastMessage 부재로 FAIL하는 것을 확인했다. 이후 Empty 상태에도 actionToastMessage를 추가하고 CreatorChannelDonationFragment.observeViewModel()의 공통 경로에서 empty/content action toast를 표시하도록 수정했다. 수정 후 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationViewModelTest"가 PASS했다.

Phase 6: Activity 연결, 탭 연결, 전체보기/후원 액션 연결

  • Task 6.1: PagerAdapter와 Activity source RED 테스트 작성

    • 수정:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapterTest.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt
    • 작업:
      • CreatorChannelTab.DonationCreatorChannelDonationFragment를 생성해야 한다는 테스트를 추가한다.
      • Activity가 CreatorChannelDonationFragment.Host를 구현해야 한다는 source test를 추가한다.
      • Activity의 하단 스크롤 dispatcher와 load-more 탭 판정에 Donation이 포함되어야 한다는 source test를 추가한다.
      • Activity가 기존 LiveRoomDonationDialog 표시 helper를 홈/후원에서 재사용해야 한다는 source test를 추가한다.
      • Activity가 UserProfileDonationAllViewActivityConstants.EXTRA_USER_ID를 사용해야 한다는 source test를 추가한다.
    • 검증:
      • 실행: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelPagerAdapterTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"
      • 기대 결과: 구현 전에는 Donation 연결 부재로 FAIL한다.
    • 검증 기록:
      • 2026-06-22: CreatorChannelPagerAdapterTestCreatorChannelTab.DonationCreatorChannelDonationFragment를 생성해야 한다는 assertion을 추가하고, placeholder 유지 대상에서 Donation을 제외했다. CreatorChannelActivitySourceTest에는 CreatorChannelDonationFragment.Host, findDonationFragment(), 탭 선택/헤더 변경 시 load, scroll bottom dispatcher, load-more 판정, viewport 전달, 공통 showCreatorChannelDonationDialog() helper, 후원 완료 홈 refresh hook, UserProfileDonationAllViewActivity + Constants.EXTRA_USER_ID 이동 계약을 추가했다. 구현 전 focused RED 실행에서 Phase 5 신규 id 미존재로 unit test compile이 먼저 FAIL했고, 같은 RED 테스트 묶음에서 Donation 연결 production 코드 부재 상태를 고정했다.
  • Task 6.2: PagerAdapter Donation 연결

    • 수정:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapter.kt
    • 작업:
      • CreatorChannelTab.Donation -> CreatorChannelDonationFragment.newInstance(creatorId) 분기를 추가한다.
      • placeholder fallback은 아직 구현되지 않은 탭에만 남긴다.
    • 검증:
      • 실행: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelPagerAdapterTest"
      • 기대 결과: PagerAdapter 테스트가 PASS한다.
    • 검증 기록:
      • 2026-06-22: CreatorChannelPagerAdapterCreatorChannelDonationFragment import와 CreatorChannelTab.Donation -> CreatorChannelDonationFragment.newInstance(creatorId) 분기를 추가했다. ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelPagerAdapterTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" 재실행에서 PagerAdapter Donation 생성 계약이 PASS했다.
  • Task 6.3: Activity Host와 scroll/viewport 연결

    • 수정:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt
    • 작업:
      • CreatorChannelDonationFragment.Host를 구현한다.
      • findDonationFragment()를 추가한다.
      • notifyCurrentCreatorChannelTabScrolledToBottom()에 Donation 분기를 추가한다.
      • isCreatorChannelLoadMoreTab()에 Donation을 추가한다.
      • 탭 선택 callback에서 Donation 선택 시 onCreatorChannelDonationTabSelected()가 호출되도록 연결한다.
      • updateCreatorChannelTabViewportHeight()에 Donation viewport height 전달을 추가한다.
      • onCreatorChannelDonationContentChanged()에서 updateViewPagerHeight()를 호출한다.
    • 검증:
      • 실행: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"
      • 기대 결과: Activity source 계약이 PASS한다.
    • 검증 기록:
      • 2026-06-22: CreatorChannelActivityCreatorChannelDonationFragment.Host를 구현하도록 추가하고, findDonationFragment(), 탭 선택/헤더 변경 시 onCreatorChannelDonationTabSelected(), notifyCurrentCreatorChannelTabScrolledToBottom()의 Donation 분기, isCreatorChannelLoadMoreTab() Donation 포함, updateCreatorChannelTabViewportHeight() Donation viewport 전달, onCreatorChannelDonationContentChanged()updateViewPagerHeight { postCheckCreatorChannelCurrentTabNeedsMore() } 연결을 구현했다. ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelPagerAdapterTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"가 PASS했다.
  • Task 6.4: Activity 후원 dialog 공통 helper와 후원 완료 hook 연결

    • 수정:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeFragment.kt 필요 시 interface 유지 확인
    • 작업:
      • 기존 onCreatorChannelDonationClicked() 내부 dialog 생성 코드를 private helper로 분리한다.
      • helper signature는 showCreatorChannelDonationDialog(onSubmit: (can: Int, isSecret: Boolean, message: String) -> Unit)처럼 후원 submit callback을 받을 수 있게 둔다.
      • 기존 홈 탭 호출은 helper에 homeActionDelegate?.postChannelDonation(...)를 전달해 동작을 유지한다.
      • 후원 탭 Host 구현은 helper에 CreatorChannelDonationFragment의 ViewModel submit callback을 전달한다.
      • onCreatorChannelDonationCompleted()에서는 homeActionDelegate?.refreshHome()를 호출해 홈 탭 후원 요약도 최신화한다.
    • 검증:
      • 실행: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"
      • 기대 결과: 홈 후원 액션 유지와 후원 탭 후원 dialog 재사용 source 계약이 PASS한다.
    • 검증 기록:
      • 2026-06-22: 기존 onCreatorChannelDonationClicked()LiveRoomDonationDialog 생성 코드를 showCreatorChannelDonationDialog(onSubmit) helper로 분리했다. 홈 탭 후원은 helper submit에서 기존 homeActionDelegate?.postChannelDonation(can = can, isSecret = isSecret, message = message)를 호출해 동작을 유지하고, 후원 탭 Host의 onCreatorChannelDonationRequested()는 같은 helper에 Fragment ViewModel submit callback을 전달하도록 연결했다. onCreatorChannelDonationCompleted()에서는 homeActionDelegate?.refreshHome()를 호출한다. ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelPagerAdapterTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"가 PASS했다.
  • Task 6.5: 랭킹 전체보기 이동 연결

    • 수정:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt
    • 작업:
      • onCreatorChannelDonationRankingAllClicked()에서 Intent(this, UserProfileDonationAllViewActivity::class.java)를 생성한다.
      • putExtra(Constants.EXTRA_USER_ID, creatorId)로 기존 Activity 호출 계약을 맞춘다.
      • Task 1.3에서 식별자 불일치가 확인된 경우 이 Task를 진행하지 않고 문서를 먼저 갱신한다.
    • 검증:
      • 실행: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationActionTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"
      • 기대 결과: 전체보기 이동 source 계약이 PASS한다.
    • 검증 기록:
      • 2026-06-22: onCreatorChannelDonationRankingAllClicked()에서 Intent(this, UserProfileDonationAllViewActivity::class.java)를 생성하고 putExtra(Constants.EXTRA_USER_ID, creatorId)로 기존 전체보기 Activity 호출 계약에 맞춰 연결했다. ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationActionTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" 범위는 Phase 5/6 focused GREEN 명령들로 분리 검증했으며 모두 PASS했다.

Phase 6 후속: Empty UI 랭킹 하단 배치

  • Task 6.6: Empty UI 랭킹 하단 배치 후속 수정
    • 수정:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationViewModel.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationFragment.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/ui/CreatorChannelDonationAdapter.kt
      • app/src/main/res/layout/fragment_creator_channel_donation.xml
      • 필요 시 app/src/main/res/layout/item_creator_channel_donation_empty.xml
      • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationFragmentLayoutTest.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationViewModelTest.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationActionTest.kt
    • 작업:
      • Figma 290:9009 기준 empty UI 컨텐츠를 랭킹 섹션 아래에 표시할 수 있도록 adapter item으로 분리한다.
      • 채널 후원 내역이 없으면 rankings 존재 여부와 관계없이 empty UI를 표시한다.
      • rankings가 비어 있지 않으면 랭킹 item 다음에 empty item을 배치한다.
      • empty UI와 후원 Fragment root에는 별도 minHeight를 적용하지 않는다.
      • empty 중앙 후원하기 button은 기존과 동일하게 타인 채널에서만 표시하고 같은 후원 요청 경로를 사용한다.
    • 검증:
      • 실행: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationViewModelTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationActionTest"
      • 기대 결과: 구현 전에는 empty item/view model ranking 보존/minHeight 제거 계약 때문에 FAIL하고, 구현 후 PASS한다.
    • 검증 기록:
      • 2026-06-22: Figma 290:9009 design context와 screenshot을 확인해 empty UI 컨텐츠가 16sp gray/500 문구, 14dp 간격, soda/400 capsule button으로 구성되며 별도 minHeight가 없음을 확인했다. 사용자 후속 요구에 따라 PRD와 계획 문서에 rankings 존재 여부와 관계없이 채널 후원이 없으면 empty UI를 표시하고, 랭킹 섹션이 있으면 그 아래에 배치하도록 기록했다.
      • 2026-06-22: RED로 CreatorChannelDonationViewModelTest에 Empty 상태가 rankings를 보존해야 한다는 assertion을 추가하고, CreatorChannelDonationFragmentLayoutTest/CreatorChannelDonationActionTestitem_creator_channel_donation_empty.xml, adapter empty item, Fragment root minimumHeight 제거 계약을 추가했다. 구현 전 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationViewModelTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationActionTest" 실행 결과 item_creator_channel_donation_empty 리소스와 Empty.rankings 부재로 :app:compileDebugUnitTestKotlin이 FAIL해 RED를 확인했다.
      • 2026-06-22: CreatorChannelDonationViewModelEmpty 상태에 랭킹 UI 모델을 보존하고, CreatorChannelDonationFragment는 empty 상태에서 donationAdapter.submitEmpty(state.rankings, state.isOwner)를 호출하도록 수정했다. fragment_creator_channel_donation.xml의 empty overlay는 제거하고, item_creator_channel_donation_empty.xml을 추가해 RecyclerView item으로 empty UI를 표시하도록 했다. CreatorChannelDonationAdapter는 랭킹 item 뒤에 empty item을 추가하고 타인 채널에서만 중앙 후원하기 button을 표시한다. 수정 후 동일 focused 명령이 PASS했다.

Phase 7: 통합 검증과 수동 확인

  • Task 7.1: 후원 탭 focused 테스트 실행

    • 실행:
      • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.*"
    • 기대 결과:
      • 후원 탭 mapper/ViewModel/pagination/layout/action 테스트가 모두 PASS한다.
    • 검증 기록:
      • 2026-06-22: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.*" 실행 결과 PASS했다. ktlint unused import 정리 후 동일 명령을 재실행해 PASS를 재확인했다.
  • Task 7.2: 크리에이터 채널 회귀 테스트 실행

    • 실행:
      • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Donation*"
      • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*"
    • 기대 결과:
      • 기존 Home/Live/Audio/Series/Community/FanTalk 탭 테스트와 신규 Donation 탭 테스트가 PASS한다.
    • 검증 기록:
      • 2026-06-22: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Donation*" 최초 실행에서 CreatorChannelActivitySourceTest가 empty button을 기존 Fragment layout에서 찾는 오래된 source 계약으로 FAIL했다. Empty UI가 adapter item으로 이동한 현재 구조에 맞춰 테스트가 item_creator_channel_donation_empty.xml을 확인하도록 갱신한 뒤 같은 명령이 PASS했다.
      • 2026-06-22: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*" 실행 결과 PASS했다.
  • Task 7.3: 리소스/컴파일/스타일 검증

    • 실행:
      • ./gradlew :app:mergeDebugResources
      • ./gradlew :app:compileDebugKotlin
      • ./gradlew :app:ktlintCheck
      • git diff --check
    • 기대 결과:
      • resource merge, Kotlin compile, ktlint, whitespace check가 모두 PASS한다.
    • 검증 기록:
      • 2026-06-22: ./gradlew :app:mergeDebugResources는 sandbox에서 Gradle wrapper lock 파일 접근 권한 문제로 1회 실패했고, 외부 권한으로 동일 명령을 재실행해 PASS했다.
      • 2026-06-22: ./gradlew :app:compileDebugKotlin 실행 결과 PASS했다.
      • 2026-06-22: ./gradlew :app:ktlintCheck 최초 실행에서 CreatorChannelDonationViewModelTest의 unused assertTrue import로 FAIL했다. 해당 import를 제거한 뒤 동일 명령을 재실행해 PASS했다.
      • 2026-06-22: git diff --check 실행 결과 PASS했다.
  • Task 7.4: 수동 UI 확인

    • 확인 항목:
      • 후원 탭 진입 시 GET /api/v2/creator-channels/{creatorId}/donations?page=0&size=20이 호출된다.
      • Sort-bar는 전체donationCount만 표시하고 정렬 UI가 없다.
      • 랭킹 섹션은 Figma 290:9097 기준으로 최대 8명, 순위 1부터 표시한다.
      • 전체보기 버튼 터치 시 UserProfileDonationAllViewActivity로 이동한다.
      • 후원 내역 item은 Figma 290:9093 기준으로 profile, nickname, relative time, can badge, message를 표시한다.
      • 목록 하단 스크롤 시 hasNext == true이면 다음 페이지가 append된다.
      • 타인 채널 content 상태에서는 floating 후원하기 버튼이 표시되고, 후원 성공 후 첫 페이지가 재조회된다.
      • 타인 채널 empty 상태에서는 Figma 290:9008 기준 문구와 중앙 후원하기 button이 표시된다.
      • 본인 채널에서는 empty 중앙 후원하기 button과 floating 후원하기 버튼이 모두 숨겨진다.
      • 비밀 후원 전용 UI/표시 분기는 없다.
    • 검증 기록:
      • 2026-06-22: 실제 기기/API 수동 확인은 실행하지 않았다. Figma 290:9009 design context/screenshot과 layout/source/test 검증으로 empty UI의 랭킹 하단 배치, minHeight 제거, 타인 채널 button 표시 계약을 확인했다.

Verification Log

  • 2026-06-22: 후원 랭킹 item 순위 text 하단이 보이지 않는 원인을 재확인했다. rank TextView는 profile 1:1 컨테이너 안에서 0.63 지점부터 아래로 그려져야 하는데, profile 컨테이너와 root item의 child drawing이 bounds 안으로 clipping되어 컨테이너 밖으로 내려오는 하단 영역이 보이지 않는 구조였다. 따라서 앞선 padding 접근은 제거하고, root와 profile 컨테이너에 android:clipChildren="false"를 적용해 rank text가 profile 하단 밖으로 overflow되어도 보이도록 수정했다. 순위 text 상단 위치는 사용자 확인 기준대로 0.63 guideline에 맞췄다. RED로 CreatorChannelDonationFragmentLayoutTest가 기존 XML에서 FAIL하는 것을 확인했고, 수정 후 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationFragmentLayoutTest", ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.*", ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck, ./gradlew :app:mergeDebugResources, git diff --check가 PASS했다. :app:mergeDebugResources는 sandbox에서 Gradle wrapper lock 파일 접근 권한 문제로 1회 실패했고, 외부 권한으로 동일 명령을 재실행해 PASS를 확인했다.
  • 2026-06-22: 사용자 추가 확인에 따라 후원 랭킹 item 순위 text 하단 clipping과 위치를 재조정한다. 기존 0.68 guideline은 눈으로 봤을 때 Figma보다 낮게 보이므로 0.63으로 올리고, @font/pattaya_regular + shadow가 includeFontPadding=false 상태에서 하단 여유 없이 그려져 잘릴 수 있어 rank text에 bottom padding을 추가한다. 구현 전 RED로 CreatorChannelDonationFragmentLayoutTest가 기존 XML에서 FAIL하는 것을 확인했다.
  • 2026-06-22: 후원 랭킹 item 순위 표시 위치를 Figma 290:9097 기준으로 수정했다. get_design_context와 screenshot에서 75dp profile 기준 rank text top=51dp를 확인했고, profile 높이의 약 68% 지점을 기준으로 rank text가 시작되도록 profile 영역을 1:1 nested ConstraintLayout으로 분리한 뒤 horizontal guideline 0.68에 rank top을 연결했다. 기존 bottom constraint + translationY 방식은 제거했다. RED로 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationFragmentLayoutTest"가 기존 XML에서 FAIL하는 것을 확인했고, 수정 후 동일 focused 테스트가 PASS했다. 회귀 검증으로 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.*", ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Donation*", ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*", ./gradlew :app:mergeDebugResources, ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck, git diff --check가 PASS했다. :app:mergeDebugResources는 sandbox에서 Gradle wrapper lock 파일 접근 권한 문제로 1회 실패했고, 외부 권한으로 동일 명령을 재실행해 PASS를 확인했다.
  • 2026-06-22: 사용자 추가 확인에 따라 후원 랭킹 item 순위 표시 위치를 Figma 290:9097 기준으로 재검토했다. Figma에서 순위 text는 75dp profile 기준 top=51dp 지점에 시작하므로 profile 높이의 약 68% 지점을 기준으로 overlay되도록 후속 수정한다. 구현 전 RED 테스트로 기존 bottom constraint + translationY 방식이 Figma 위치 계약과 다름을 고정한다.
  • 2026-06-22: 사용자 피드백에 따라 후원 랭킹 섹션과 우측 하단 채널 후원 floating button을 Figma 기준으로 후속 수정했다. Figma 290:9097, 290:9093 design context/screenshot을 재확인했고, 랭킹 grid는 고정 75dp가 아니라 가용 폭에서 14dp 간격 3개를 제외한 뒤 4등분되도록 GridLayoutManager + CreatorChannelDonationRankingItemDecoration으로 수정했다. 랭킹 member profile은 column 폭에 맞춰 1:1 비율로 측정되도록 바꾸고, 순위 표시는 @font/pattaya_regular, 28sp, profile 하단 overlay 기준으로 조정했다. 우측 하단 후원 floating button은 fragment_creator_channel_donation.xml에서 제거하고 activity_creator_channel.xml overlay로 옮겨 66dp 크기, 14dp end/bottom margin, 38dp icon, Activity 우하단 고정 위치로 표시되게 했다. CreatorChannelDonationFragment는 content/owner 상태에 따라 Activity에 floating button visibility를 전달하고, Activity button click은 현재 Donation Fragment의 onCreatorChannelDonationFloatingButtonClicked()를 호출한다.
  • 2026-06-22: 후속 수정 RED로 CreatorChannelDonationFragmentLayoutTest에 responsive ranking grid/item 계약을 추가하고, CreatorChannelActivitySourceTest에 Activity overlay floating button 계약을 추가했다. 기존 구현에서 랭킹 grid 폭/간격/순위 폰트 및 Fragment 내부 floating button 구조 때문에 FAIL하는 것을 확인한 뒤 수정했다. 검증으로 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest", ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.*", ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Donation*", ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*", ./gradlew :app:mergeDebugResources, ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck, git diff --check가 PASS했다. :app:mergeDebugResources는 sandbox에서 Gradle wrapper lock 파일 접근 권한 문제로 1회 실패했고, 외부 권한으로 동일 명령을 재실행해 PASS를 확인했다. 참고로 donation.**Donation* 테스트 필터를 한 Gradle 명령에 합쳐 실행하면 SharedPreferenceManager.can 전역 상태를 사용하는 테스트들이 겹쳐 기존 ViewModel 테스트가 흔들릴 수 있어, 계획 문서의 명령처럼 분리 실행했다.
  • 2026-06-22: 사용자 확인 사항에 따라 후속 수정 범위를 추가했다. 후원 랭킹 섹션은 Figma 290:9097 기준으로 grid gap 14dp, 4열 responsive profile cell, rank overlay 28sp/profile 하단 위치를 맞춘다. 402dp 화면에서는 profile cell이 75dp가 되지만 고정값으로 두지 않고 가용 폭에 따라 계산되도록 한다. 우측 하단 채널 후원 floating button은 Figma 290:9093 및 홈 탭 owner FAB 패턴처럼 66dp 크기, 14dp end/bottom margin, 38dp icon, Activity overlay 고정 위치로 옮기고, 후원 탭 content/empty 상태와 owner 여부에 따라 표시만 제어한다. 구현 전 RED 테스트로 layout/source 계약을 먼저 고정한다.
  • 2026-06-22: Phase 5, 6 코드 리뷰 및 검증을 수행했다. 리뷰에서 empty 중앙 후원하기 button 배경색이 @color/whitesoda/400 요구와 맞지 않는 문제, empty 상태 후원 실패 메시지가 Content 전용 action toast 상태에 막혀 사용자에게 표시되지 않을 수 있는 문제를 발견해 RED 테스트 후 수정했다. 검증으로 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationViewModelTest", ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.*", ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Donation*", ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*", ./gradlew :app:mergeDebugResources, ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck, git diff --check가 PASS했다. :app:mergeDebugResources는 sandbox에서 Gradle wrapper lock 파일 접근 권한 문제로 1회 실패했고, 외부 권한으로 동일 명령을 재실행해 PASS를 확인했다.
  • 2026-06-22: Phase 2, 3, 4 코드 리뷰 및 재검증을 수행했다. API/DTO/Repository/ViewModel 계약, mapper 정책, content layout/adapter/Fragment 흐름을 대조했으며 차단급 코드 이슈는 발견하지 않았다. ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.*", ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*Donation*", ./gradlew :app:mergeDebugResources, ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck, git diff --check가 PASS했다. :app:mergeDebugResources는 sandbox에서 Gradle wrapper lock 파일 접근 권한 문제로 1회 실패했고, 외부 권한으로 동일 명령을 재실행해 PASS를 확인했다.
  • 2026-06-22: 리뷰 지적에 따라 후원 성공 event가 Content 상태에 종속되지 않도록 후속 수정했다. RED로 CreatorChannelDonationActionTest후원 fragment source는 성공 이벤트를 content 상태와 독립적으로 전달한다를 추가했고, 기존 구현에서 handleDonationSuccessEvent() 부재로 FAIL하는 것을 확인했다. 이후 CreatorChannelDonationFragment.observeViewModel()의 상태 bind 후 공통 경로에서 handleDonationSuccessEvent()를 호출하도록 변경해 Empty, Error, Content 재조회 결과 모두 Host.onCreatorChannelDonationCompleted() 전달 대상이 되도록 했다. 수정 후 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.*", ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck, git diff --check가 PASS했다.
  • 2026-06-22: docs/20260622_크리에이터_채널_후원_탭/prd.md, docs/agent-guides/work-plan-docs.md, 기존 docs/20260622_FanTalk_탭/plan-task.md 구조를 확인해 후원 탭 구현 계획/TASK 문서를 작성했다. 이번 단계는 문서 작성만 수행했으며 구현/빌드/테스트는 실행하지 않았다.
  • 2026-06-22: Phase 2, 3, 4 코드 리뷰 및 검증을 수행했다. ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.*", ./gradlew :app:mergeDebugResources, ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck, git diff --check가 PASS했다. 최초 mergeDebugResources 병렬 실행은 Gradle wrapper lock 파일 접근 권한 문제로 실패했으나 동일 명령을 단독 재실행해 PASS를 확인했다. 리뷰 결과, 후원 성공 event가 Content 상태 bind 중에만 Host.onCreatorChannelDonationCompleted()로 전달되어 후원 성공 후 첫 페이지 재조회가 Empty 또는 Error로 끝나는 경우 홈 탭 refresh hook이 호출되지 않을 수 있는 위험을 확인했다.