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

21 KiB

PRD: 크리에이터 채널 후원 탭

1. Overview

크리에이터 채널의 후원 탭에서 후원 랭킹, 전체 후원 개수, 후원 내역 목록을 조회하고, 우측 하단 후원하기 버튼으로 홈 탭과 동일한 채널 후원 플로우를 제공한다.


2. Problem

  • 크리에이터 채널에는 후원 탭이 존재하지만 현재 상세 탭 구현은 placeholder 상태다.
  • 사용자는 크리에이터 채널 안에서 누가 많이 후원했는지와 최근 후원 내역을 한 화면에서 확인할 수 있어야 한다.
  • 후원 내역은 길어질 수 있으므로 hasNext == true일 때 다음 페이지를 이어서 조회해야 한다.
  • 후원 탭에서 후원하기를 완료하면 방금 반영된 후원 내역과 랭킹이 화면에 갱신되어야 한다.
  • 홈 탭에 이미 연결된 채널 후원 플로우와 동일한 UX/API 동작을 재사용해야 한다.

3. Goals

  • Figma 전체 화면 290:9093 기준으로 크리에이터 채널 후원 탭 UI 요구사항을 정의한다.
  • Figma 후원 랭킹 섹션 290:9097 기준으로 랭킹 카드 요구사항을 정의한다.
  • API endpoint GET /api/v2/creator-channels/{creatorId}/donations를 기준으로 최초 조회와 pagination 요구사항을 정의한다.
  • 최초 조회 query parameter 기본값은 page=0, size=20으로 둔다.
  • Sort-bar에는 정렬 UI를 표시하지 않고 전체 label과 donationCount만 표시한다.
  • 후원 랭킹 섹션에는 API rankings를 응답 순서 기준으로 최대 8명까지 표시한다.
  • 후원 내역 item에는 후원자 프로필 이미지, 닉네임, 작성 시간, 후원 캔 수량, 메시지를 표시한다.
  • createdAtUtc는 크리에이터 채널 v2 공통 상대 시간 포맷을 따른다.
  • 후원 내역 item의 header 색상과 캔 수량 badge는 기존 홈 탭 후원 카드 정책을 우선 재사용한다.
  • 랭킹 섹션의 전체보기 버튼은 기존 UserProfileDonationAllViewActivity로 이동한다.
  • 우측 하단 floating 후원하기 버튼은 홈 탭 후원하기 버튼 터치 액션과 동일하게 LiveRoomDonationDialog 기반 채널 후원 플로우를 호출한다.
  • 후원 성공 후에는 후원 탭의 첫 페이지를 다시 조회해 donationCount, rankings, donations가 갱신되어야 한다.
  • 응답의 hasNexttrue이면 현재 page + 1 페이지를 추가 로딩한다.

4. Non-Goals

  • 크리에이터 채널 상단 header, title bar, 공통 tab-bar 구조 자체를 재설계하지 않는다.
  • , 라이브, 오디오, 시리즈, 커뮤니티, 팬Talk 탭의 동작은 이번 범위에서 변경하지 않는다.
  • 후원 결제/충전 dialog 내부 UX, validation, 충전 화면 진입 동작은 변경하지 않고 홈 탭 후원하기 액션을 재사용한다.
  • 후원 랭킹 전체보기용 신규 화면, bottom sheet, 랭킹 확장 UI는 만들지 않는다.
  • 후원 내역 item에서 프로필 이동, 신고, 삭제, 차단 등 추가 액션은 이번 범위에서 제외한다.
  • 후원 내역의 비밀 후원 전용 표시 정책은 이번 범위에서 제외한다. 현재 API가 비밀 후원 여부를 별도 필드로 내려주지 않으므로 클라이언트에서 비밀 후원 UI를 분기하지 않는다.
  • API schema를 임의 변경하거나 서버 응답 필드명을 클라이언트에서 새로 정의하지 않는다.
  • Sort-bar에 정렬 label, 정렬 icon, 정렬 popup을 추가하지 않는다.
  • Figma asset을 localhost URL 그대로 앱 코드에 직접 의존하지 않는다.
  • 레거시 후원 구현 파일을 직접 수정하지 않는다. 필요한 경우 기존 후원 dialog/repository 흐름을 호출하거나 신규 wrapper/adapter를 추가해 사용한다.

5. Target Users

  • 크리에이터 채널에서 후원 랭킹과 후원 내역을 확인하려는 앱 사용자.
  • 특정 크리에이터에게 후원하고 후원 결과가 화면에 반영되길 기대하는 앱 사용자.
  • 본인 채널의 후원 현황을 확인하려는 크리에이터.
  • kr.co.vividnext.sodalive.v2 하위 크리에이터 채널 탭을 구현/유지보수하는 Android 개발자.

6. User Stories

  • 사용자는 크리에이터 채널의 후원 탭에서 전체 후원 수를 확인하고 싶다.
  • 사용자는 후원 랭킹 상위 사용자의 프로필, 닉네임, 순위를 빠르게 확인하고 싶다.
  • 사용자는 각 후원 내역의 후원자, 후원 시간, 후원 캔 수량, 메시지를 확인하고 싶다.
  • 사용자는 목록 하단까지 스크롤하면 다음 페이지가 자연스럽게 이어서 로딩되길 기대한다.
  • 사용자는 후원 탭 우측 하단 버튼으로 바로 후원하고 싶다.
  • 사용자는 후원 완료 직후 목록과 랭킹이 최신 상태로 갱신되길 기대한다.

7. Core Features

Creator Channel Donation Tab API

후원 탭 진입과 추가 로딩 시 크리에이터별 후원 랭킹과 후원 내역 데이터를 조회한다.

Requirements

  • API endpoint는 GET /api/v2/creator-channels/{creatorId}/donations이다.
  • creatorId는 path variable로 전달한다.
  • Query parameters는 page, size를 사용한다.
  • 최초 조회 기본값은 page=0, size=20이다.
  • 정렬 query parameter는 사용하지 않는다.
  • hasNext == true일 때 다음 페이지 요청은 현재 응답의 page + 1 값을 사용한다.
  • 중복 pagination 요청이 발생하지 않도록 loading 중 추가 요청을 막아야 한다.
  • 다음 페이지 성공 시 기존 donations 뒤에 append한다.
  • 후원 성공 후 갱신은 append가 아니라 page=0, size=20 최초 조회를 다시 수행한다.

Response Contract

data class CreatorChannelDonationTabResponse(
    val donationCount: Int,
    val rankings: List<MemberDonationRankingResponse>,
    val donations: List<CreatorChannelDonationResponse>,
    val page: Int,
    val size: Int,
    @JsonProperty("hasNext")
    val hasNext: Boolean
)

data class MemberDonationRankingResponse(
    @JsonProperty("userId") val userId: Long,
    @JsonProperty("nickname") val nickname: String,
    @JsonProperty("profileImage") val profileImage: String,
    @JsonProperty("donationCan") val donationCan: Int
)

data class CreatorChannelDonationResponse(
    val nickname: String,
    val profileImageUrl: String,
    val can: Int,
    val message: String,
    val createdAtUtc: String
)

Edge Cases

  • creatorId <= 0이면 API를 호출하지 않고 기존 크리에이터 채널 탭의 종료/placeholder 정책을 따른다.
  • 최초 조회 실패 시 기존 크리에이터 채널 탭의 error/retry 패턴을 따른다.
  • 다음 페이지 로딩 실패 시 기존 목록은 유지하고 기존 pagination 실패 표시 정책을 따른다.
  • 다음 페이지 응답의 donations가 비어 있어도 hasNext 값 기준으로 이후 로딩 가능 여부를 갱신한다.
  • 서버 응답의 page, size가 요청 상태와 다를 경우 서버 응답 값을 기준으로 ViewModel page 상태를 동기화한다.
  • profileImage 또는 profileImageUrl이 비어 있거나 이미지 로딩에 실패하면 기존 프로필 이미지 placeholder 정책을 따른다.
  • message가 빈 문자열이면 기존 홈 탭 후원 카드와 동일하게 %1$d캔을 후원하였습니다. 형식의 fallback 문구를 표시한다.

Donation Ranking Section

후원 랭킹 섹션은 Figma 290:9097 기준의 카드 UI로 상위 후원자를 표시한다.

Requirements

  • 섹션 title은 후원 랭킹이다.
  • 카드 배경은 gray/900, radius 14dp, 내부 padding 14dp를 기준으로 한다.
  • 랭킹 영역은 75dp 원형 프로필, 닉네임, 순위 숫자를 하나의 profile cell로 표시한다.
  • profile cell은 Figma처럼 4열 x 2행 기준으로 최대 8개까지 표시한다.
  • 순위 숫자는 응답 순서 기준으로 1부터 부여한다.
  • 닉네임은 한 줄로 표시하고 긴 경우 말줄임 처리한다.
  • 프로필 이미지는 MemberDonationRankingResponse.profileImage를 사용한다.
  • donationCan은 현재 Figma 랭킹 cell에 직접 노출되지 않으므로 이번 범위에서는 UI model에 보관하되 화면에는 표시하지 않는다.
  • 랭킹 섹션 하단에는 Figma 기준 전체보기 capsule button을 표시한다.
  • 서버는 rankings를 항상 최대 8명까지만 내려준다.
  • 전체보기 버튼을 터치하면 기존 UserProfileDonationAllViewActivity로 이동한다.
  • UserProfileDonationAllViewActivity는 기존 호출 계약처럼 Constants.EXTRA_USER_ID를 전달해 실행한다.

Edge Cases

  • rankings가 비어 있으면 랭킹 grid는 표시하지 않는다.
  • rankings가 1~7개이면 내려온 개수만 표시하고 빈 cell을 만들지 않는다.
  • profileImage가 비어 있거나 이미지 로딩에 실패하면 기존 프로필 이미지 placeholder를 표시한다.
  • 동일 userId가 중복으로 내려오면 API 응답 순서를 유지해 그대로 표시한다.
  • UserProfileDonationAllViewActivity 진입에 필요한 id와 크리에이터 채널의 creatorId가 다른 식별자인 경우, 구현 계획에서 기존 Activity 호출 계약에 맞는 식별자 확보 방식을 먼저 확정한다.

Sort Bar without Sort

Sort-bar는 전체 후원 수만 표시한다.

Requirements

  • Figma 전체 화면 기준 Sort-bar는 290:9093sort-bar이다.
  • 좌측에는 전체donationCount를 표시한다.
  • 우측 정렬 label, 정렬 icon, 정렬 popup 진입 영역은 표시하지 않는다.
  • 후원 탭에서는 정렬 상태를 보관하거나 API query로 전달하지 않는다.

Edge Cases

  • donationCount == 0이고 표시 가능한 후원 내역이 없으면 empty 상태 정책을 우선한다.
  • 다국어 label 길이가 길어져도 전체 개수와 겹치지 않아야 한다.

Donation List Item

후원 내역 목록은 Figma의 support item 구조를 기준으로 표시한다.

Requirements

  • 각 item은 gray/900 배경, radius 14dp 카드로 표시한다.
  • item header에는 후원자 프로필 이미지, 닉네임, 작성 시간, 후원 캔 수량 badge를 표시한다.
  • header 배경색은 기존 홈 탭 후원 카드의 calculateCreatorChannelDonationHeaderColorRes(can) 정책을 우선 재사용한다.
  • 후원 캔 수량은 can icon과 %d캔 텍스트로 표시한다.
  • createdAtUtc는 크리에이터 채널 v2 공통 상대 시간 포맷을 따른다.
  • 메시지는 header 아래에 16sp regular white text로 표시하고 긴 내용은 여러 줄로 확장한다.
  • message가 빈 문자열이면 기존 홈 탭 후원 카드 fallback 문구를 표시한다.
  • 현재 후원 탭 API는 비밀 후원 여부를 별도로 내려주지 않으므로 비밀 후원 전용 아이콘, 안내 문구, 익명 처리 분기를 구현하지 않는다.

Edge Cases

  • 닉네임이 긴 경우 header의 텍스트 영역에서 말줄임 처리한다.
  • createdAtUtc 파싱 실패 시 기존 날짜 표시 fallback 정책을 따른다.
  • can <= 0 값이 내려와도 앱에서 크래시가 발생하지 않아야 하며, 서버 응답 값을 그대로 표시한다.
  • 메시지가 매우 긴 경우 item 높이는 자연스럽게 확장하되 인접 item과 겹치지 않아야 한다.

Floating Donation Button

우측 하단 floating 후원하기 버튼은 홈 탭 후원하기 액션과 동일하게 동작한다.

Requirements

  • Figma 전체 화면 기준 우측 하단 button-floating을 표시한다.
  • 버튼은 목록 위에 floating 형태로 배치한다.
  • 버튼 icon과 색상은 Figma와 대응되는 기존 프로젝트 asset/token을 우선 사용한다.
  • 타인 채널에서 버튼을 누르면 홈 탭의 onCreatorChannelDonationClicked()와 동일한 후원 dialog를 연다.
  • 후원 dialog는 기존 홈 탭과 동일하게 LiveRoomDonationDialog를 사용한다.
  • 후원 dialog 옵션과 동작은 홈 탭 후원하기 액션과 동일하게 유지한다.
  • 후원 API는 기존 CreatorChannelRepository.postChannelDonation() 흐름을 재사용한다.
  • 후원 성공 시 SharedPreferenceManager.can 차감 정책은 홈 탭과 동일하게 적용한다.
  • 후원 성공 후 후원 탭은 GET /api/v2/creator-channels/{creatorId}/donations?page=0&size=20을 다시 호출해 화면을 갱신한다.
  • 같은 채널의 홈 탭 데이터가 이미 로드되어 있다면 홈 탭도 기존 delegate를 통해 갱신할 수 있다.
  • 본인 채널에서는 floating 후원하기 버튼을 숨긴다.

Edge Cases

  • 후원 dialog를 닫거나 취소하면 후원 API를 호출하지 않는다.
  • 후원 API 실패 시 기존 홈 탭 후원 실패 toast/error 정책을 따른다.
  • 후원 요청 중 중복 터치로 API가 중복 호출되지 않아야 한다.
  • 후원 성공 후 재조회가 실패하면 기존 후원 성공 처리 자체는 유지하고, 후원 탭은 error/retry 또는 기존 목록 유지 정책 중 구현 계획에서 결정한다.
  • floating button은 목록 하단 item, 시스템 navigation bar, keyboard와 겹치지 않아야 한다.

Pagination

후원 내역 목록은 스크롤 하단 접근 시 다음 페이지를 로딩한다.

Requirements

  • CreatorChannelDonationTabResponse.hasNext == true일 때만 다음 페이지를 요청한다.
  • 다음 페이지는 마지막 성공 응답의 page + 1로 요청한다.
  • 다음 페이지 요청에는 size=20을 유지한다.
  • 다음 페이지 로딩 중에는 추가 page 요청을 중복으로 보내지 않는다.
  • 다음 페이지 성공 시 기존 donations 뒤에 append한다.
  • 첫 페이지 새로고침 또는 후원 성공 갱신 시 기존 pagination 상태는 초기화한다.

Edge Cases

  • 빠른 스크롤로 load-more trigger가 반복 발생해도 page가 중복 append되지 않아야 한다.
  • Fragment/View 재생성 후 현재 목록과 page 상태는 ViewModel 상태 보존 정책에 따라 유지되어야 한다.
  • 마지막 페이지 응답 이후 hasNext == false이면 이후 load-more trigger를 무시한다.

Empty State

후원 내역이 없으면 목록 대신 empty 상태를 표시한다.

Requirements

  • donationCount == 0 또는 표시 가능한 donations가 없는 전체 empty 상태이면 empty 상태를 표시한다.
  • empty 상태에서는 후원 내역 목록을 표시하지 않는다.
  • empty 상태에서는 Sort-bar를 숨긴다.
  • 후원 랭킹 섹션은 rankings가 비어 있으면 숨긴다.
  • empty 상태는 Figma 290:9008을 기준으로 표시한다.
  • empty 문구는 아직 후원이 없습니다.\n처음으로 크리에이터를 후원해 보세요!이다.
  • empty 문구는 16sp regular, gray/500, center 정렬로 표시한다.
  • 타인 채널 empty 상태에서는 문구 아래에 후원하기 capsule button을 표시한다.
  • empty 후원하기 button은 Figma처럼 soda/400 배경, gift icon, 16sp medium white text를 기준으로 한다.
  • empty 후원하기 button 터치 시 floating 후원하기 버튼과 동일하게 홈 탭 후원하기 액션을 호출한다.
  • 본인 채널 empty 상태에서는 문구 아래의 후원하기 button을 숨긴다.
  • 본인 채널 empty 상태에서는 floating 후원하기 버튼도 숨긴다.

Edge Cases

  • API 최초 조회 실패 상태는 empty 상태로 취급하지 않고 기존 error/retry 패턴을 따른다.
  • donationCount > 0이지만 첫 페이지 donations가 비어 있고 표시 가능한 item이 없으면 empty 상태를 표시하되, 전체 개수는 서버 응답의 donationCount 값을 유지한다.

8. UX / UI Expectations

  • 후원 탭 선택 시 공통 header와 tab-bar는 기존 크리에이터 채널 컨테이너 구조를 유지한다.
  • 후원 탭 컨텐츠는 Figma 290:9093처럼 black 배경 위에 구성한다.
  • Sort-bar는 52dp 높이 기준으로 전체와 개수만 표시한다.
  • 랭킹 카드와 후원 내역 카드는 좌우 14dp margin 기준으로 배치한다.
  • 카드 간 vertical spacing은 Figma 기준을 우선하되 기존 크리에이터 채널 탭 spacing token과 충돌하면 기존 token을 우선한다.
  • 모든 텍스트는 Pretendard 기반 기존 앱 폰트 스타일을 따른다.
  • 후원 목록 스크롤 중 floating button이 주요 텍스트를 과도하게 가리지 않아야 한다.
  • empty 상태의 후원하기 button과 floating 후원하기 버튼은 같은 후원 액션을 호출하지만, 본인 채널에서는 둘 다 노출하지 않는다.
  • 이미지 로딩 중/실패 시 기존 프로필 placeholder를 사용해 레이아웃이 흔들리지 않아야 한다.

9. Technical Constraints

  • 신규 후원 탭 전용 Fragment/ViewModel/DTO/mapper/adapter는 kr.co.vividnext.sodalive.v2.creator.channel.donation 하위에 작성한다.
  • API/Repository endpoint 추가는 기존 채널 공통 CreatorChannelApi/CreatorChannelRepository에 최소 변경으로 추가한다.
  • 기존 CreatorChannelPagerAdapterCreatorChannelTab.Donation placeholder를 신규 CreatorChannelDonationFragment로 교체한다.
  • 기존 홈 탭 후원 API 호출 흐름인 CreatorChannelRepository.postChannelDonation()을 재사용한다.
  • 기존 홈 탭 후원 item의 색상 계산, fallback message, 상대 시간 formatter는 가능한 범위에서 재사용한다.
  • 랭킹 전체보기 이동은 기존 UserProfileDonationAllViewActivity를 재사용하고 신규 전체보기 화면을 만들지 않는다.
  • 기존 UserProfileDonationAllViewActivityConstants.EXTRA_USER_ID를 요구하므로, 구현 계획에서 크리에이터 채널에서 전달할 식별자가 기존 Activity 계약과 맞는지 확인한다.
  • 레거시 후원 dialog 또는 repository 파일은 직접 수정하지 않는다.
  • DTO annotation은 프로젝트 Retrofit/Gson 관례에 맞춰 구현 계획에서 @SerializedName 사용 여부를 확인한다. PRD의 @JsonProperty는 서버 계약 설명으로 기록한다.
  • 구현 전 plan-task.md를 작성하고, 해당 문서의 Phase/Task별 검증 기준에 따라 최소 테스트를 작성한다.

10. Metrics

  • 후원 탭 최초 로딩 성공률.
  • 후원 탭 pagination 성공률과 중복 요청 방지 여부.
  • 후원하기 버튼 터치 후 dialog 노출률.
  • 후원 성공 후 후원 탭 재조회 성공률.
  • 후원 성공 후 donationCount, rankings, donations 최신화 여부.

11. Resolved Decisions

  • 랭킹 섹션의 전체보기 버튼은 기존 UserProfileDonationAllViewActivity로 이동한다.
  • rankings는 서버가 항상 최대 8명만 내려준다.
  • 후원 내역 empty 상태는 Figma 290:9008을 기준으로 구현한다.
  • 본인 채널에서는 empty 상태의 하단 후원하기 button과 floating 후원하기 버튼을 모두 숨긴다.
  • 현재 API는 비밀 후원 여부를 별도로 내려주지 않으므로 이번 범위에서는 비밀 후원 관련 UI/표시 분기를 구현하지 않는다.

12. References


13. Verification Log

  • 2026-06-22: docs/prd/sample-prd.mddocs/agent-guides/work-plan-docs.md를 확인해 신규 문서 경로와 PRD 섹션 구성을 맞췄다.
  • 2026-06-22: Figma 290:9093, 290:9097의 design context와 screenshot을 확인해 후원 랭킹 카드, Sort-bar, 후원 내역 item, floating button 구조를 PRD에 반영했다.
  • 2026-06-22: 기존 홈 탭 PRD와 CreatorChannelActivity.onCreatorChannelDonationClicked(), CreatorChannelHomeViewModel.postChannelDonation() 흐름을 확인해 후원 탭 후원하기 액션 재사용 및 성공 후 갱신 요구를 PRD에 반영했다.
  • 2026-06-22: 사용자 확인사항을 반영해 랭킹 전체보기는 기존 UserProfileDonationAllViewActivity 이동으로 확정하고, rankings 최대 8명 서버 보장, Figma 290:9008 empty 상태, 본인 채널 후원하기 버튼 숨김, 비밀 후원 표시 제외 정책을 PRD에 보강했다.