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

41 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
  • 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도 숨긴다.
  • 후원 성공 후에는 후원 탭의 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한다.
    • 검증 기록:
      • 미실행. 구현 시 기록한다.
  • 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와 충돌 없이 컴파일된다.
    • 검증 기록:
      • 미실행. 구현 시 기록한다.
  • 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한다.
    • 검증 기록:
      • 미실행. 구현 시 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한다.
    • 검증 기록:
      • 미실행. 구현 시 기록한다.
  • 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한다.
    • 검증 기록:
      • 미실행. 구현 시 기록한다.

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한다.
    • 검증 기록:
      • 미실행. 구현 시 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한다.
    • 검증 기록:
      • 미실행. 구현 시 기록한다.

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 계약 테스트를 작성한다.
      • 전체보기 클릭이 UserProfileDonationAllViewActivityConstants.EXTRA_USER_ID를 사용한다는 source 계약 테스트를 작성한다.
    • 검증:
      • 실행: ./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한다.
    • 검증 기록:
      • 미실행. 구현 시 RED 결과를 기록한다.
  • 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한다.
    • 검증 기록:
      • 미실행. 구현 시 기록한다.
  • 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한다.
    • 검증 기록:
      • 미실행. 구현 시 기록한다.

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한다.
    • 검증 기록:
      • 미실행. 구현 시 기록한다.
  • 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 버튼 숨김을 대조한다.
    • 검증 기록:
      • 미실행. 구현 시 기록한다.

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한다.
    • 검증 기록:
      • 미실행. 구현 시 RED 결과를 기록한다.
  • 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한다.
    • 검증 기록:
      • 미실행. 구현 시 기록한다.
  • 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한다.
    • 검증 기록:
      • 미실행. 구현 시 기록한다.
  • 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한다.
    • 검증 기록:
      • 미실행. 구현 시 기록한다.
  • 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한다.
    • 검증 기록:
      • 미실행. 구현 시 기록한다.

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

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

    • 실행:
      • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.*"
    • 기대 결과:
      • 후원 탭 mapper/ViewModel/pagination/layout/action 테스트가 모두 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한다.
    • 검증 기록:
      • 미실행. 구현 시 기록한다.
  • Task 7.3: 리소스/컴파일/스타일 검증

    • 실행:
      • ./gradlew :app:mergeDebugResources
      • ./gradlew :app:compileDebugKotlin
      • ./gradlew :app:ktlintCheck
      • git diff --check
    • 기대 결과:
      • resource merge, Kotlin compile, ktlint, whitespace 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/표시 분기는 없다.
    • 검증 기록:
      • 미실행. 구현 시 기록한다.

Verification Log

  • 2026-06-22: docs/20260622_크리에이터_채널_후원_탭/prd.md, docs/agent-guides/work-plan-docs.md, 기존 docs/20260622_FanTalk_탭/plan-task.md 구조를 확인해 후원 탭 구현 계획/TASK 문서를 작성했다. 이번 단계는 문서 작성만 수행했으며 구현/빌드/테스트는 실행하지 않았다.