22 KiB
22 KiB
PRD: 크리에이터 채널 FanTalk 탭
1. Overview
크리에이터 채널의 FanTalk 탭에서 팬이 남긴 응원글과 크리에이터 답글을 조회하고, 작성자/채널 소유 여부에 따라 신고 또는 더보기 메뉴를 제공한다.
2. Problem
- 크리에이터 채널에는
FanTalk탭이 존재하지만, 탭 전용 API 계약과 Figma 기반 UI 요구사항이 별도 문서로 정의되어야 한다. - 사용자는 크리에이터 채널 안에서 전체 FanTalk 수와 팬 응원글, 크리에이터 답글을 한 화면에서 확인할 수 있어야 한다.
- FanTalk 목록은 길어질 수 있으므로
hasNext == true일 때 다음 페이지를 이어서 조회해야 한다. - 내가 쓴 글 또는 내 채널의 글에는 수정/삭제 등 소유자용 액션을 열 수 있는 더보기 버튼이 필요하다.
- 그 외 유저가 작성한 글에는 더보기 버튼 대신 신고 버튼만 표시되어야 한다.
- 신고 기능은 기존 크리에이터 페이지(
UserProfileActivity)의 FanTalk 신고 흐름과 동일하게 처리되어야 한다.
3. Goals
- Figma 전체 화면
290:9139기준으로 크리에이터 채널FanTalk탭 UI 요구사항을 정의한다. - API endpoint
GET /api/v2/creator-channels/{creatorId}/fan-talks를 기준으로 최초 조회와 pagination 요구사항을 정의한다. - 최초 조회 query parameter 기본값은
page=0,size=20으로 둔다. - Sort-bar에는 정렬 UI를 표시하지 않고
전체label과fanTalkCount만 표시한다. - FanTalk item에는 작성자 프로필 이미지, 닉네임, 작성 시간, 본문을 표시한다.
createdAtUtc는 크리에이터 채널 v2 공통 날짜/시간 포맷을 따른다.- FanTalk item에 크리에이터 답글이 있으면 원글 아래에 크리에이터 답글을 최대 1개 표시한다.
- 내가 쓴 글일 때 원글 우측 액션은
ic_new_more더보기 버튼으로 표시하고, popup에는수정하기,삭제하기를 표시한다. - 내 채널에서 타인이 작성한 글일 때 원글 우측 액션은
ic_new_more더보기 버튼으로 표시하고, popup에는삭제하기만 표시한다. - 내가 쓴 글도 아니고 내 채널도 아닌 경우 원글 우측 액션은
신고버튼으로 표시한다. - 더보기 popup의
수정하기는 이번 범위에서 표시만 하고 실제 수정 화면/API는 연결하지 않는다. - 더보기 popup의
삭제하기는 기존 FanTalk 삭제 API 흐름에 연결한다. - 신고 버튼 터치 시 기존
UserProfileFantalkAllViewActivity의 FanTalk 신고 기능과 동일하게CheersReportDialog를 열고ReportType.CHEERS신고를 처리한다. - 우측 하단 floating 글쓰기 버튼은 Figma처럼 표시하되, 터치 액션은 후속 작업 범위로 남긴다.
- FanTalk가 없는 경우 Figma empty 화면
290:9000기준으로 empty 문구와응원 남기기button을 표시하되, button 터치 액션은 후속 작업 범위로 남긴다. - 응답의
hasNext가true이면 현재page + 1페이지를 추가 로딩한다.
4. Non-Goals
- 크리에이터 채널 상단 header, title bar, 공통 main tab-bar 구조 자체를 재설계하지 않는다.
홈,라이브,오디오,시리즈,화보,커뮤니티,후원탭의 상세 구현은 이번 범위에서 제외한다.- FanTalk 글쓰기 페이지 구현과 floating 글쓰기 버튼의 실제 화면 연결은 이번 범위에서 제외한다.
- empty 상태의
응원 남기기button 실제 화면 연결은 이번 범위에서 제외한다. - FanTalk 상세 화면이 별도로 필요하더라도 이번 범위에서는 정의하지 않는다.
- API schema를 임의 변경하거나 서버 응답 필드명을 클라이언트에서 새로 정의하지 않는다.
- Sort-bar에 정렬 label, 정렬 icon, 정렬 popup을 추가하지 않는다.
- 더보기 메뉴의
수정하기실제 수정 화면/API 연결은 이번 범위에서 제외한다. - 크리에이터 답글 작성, 수정, 삭제 기능은 이번 범위에서 제외한다.
- Figma asset을 localhost URL 그대로 앱 코드에 직접 의존하지 않는다.
- 레거시 FanTalk 구현 파일을 직접 수정하지 않는다. 필요한 경우 기존 신고 dialog/repository 흐름을 호출하거나 신규 wrapper/adapter를 추가해 사용한다.
5. Target Users
- 크리에이터 채널에서 팬 응원글을 읽는 앱 사용자.
- 특정 크리에이터에게 남겨진 팬 반응과 크리에이터 답글을 확인하려는 사용자.
- 본인이 작성한 FanTalk 글을 관리하려는 사용자.
- 본인 채널의 FanTalk 글을 관리하려는 크리에이터.
- 부적절한 FanTalk 글을 신고하려는 사용자.
kr.co.vividnext.sodalive.v2하위 크리에이터 채널 탭을 구현/유지보수하는 Android 개발자.
6. User Stories
- 사용자는 크리에이터 채널의
FanTalk탭에서 전체 FanTalk 수를 확인하고 싶다. - 사용자는 팬이 남긴 응원글의 작성자, 작성 시간, 본문을 확인하고 싶다.
- 사용자는 FanTalk 원글에 달린 크리에이터 답글을 원글 아래에서 확인하고 싶다.
- 사용자는 내가 작성하지 않은 FanTalk 글을 부적절하다고 판단하면 신고하고 싶다.
- 사용자는 내가 작성한 FanTalk 글에서 더보기 메뉴를 열고 수정/삭제 액션을 확인하고 싶다.
- 사용자는 내가 작성한 FanTalk 글을 삭제하고 싶다.
- 크리에이터는 내 채널에 타인이 작성한 FanTalk 글에서 더보기 메뉴를 열고 삭제 액션을 확인하고 싶다.
- 사용자는 목록 하단까지 스크롤하면 다음 페이지가 자연스럽게 이어서 로딩되길 기대한다.
- 사용자는 우측 하단 글쓰기 버튼 또는 empty 상태의
응원 남기기button을 보고 FanTalk 작성 진입점이 있음을 인지하고 싶다.
7. Core Features
Creator Channel FanTalk Tab API
FanTalk 탭 진입과 추가 로딩 시 크리에이터별 FanTalk 목록 데이터를 조회한다.
Requirements
- API endpoint는
GET /api/v2/creator-channels/{creatorId}/fan-talks이다. creatorId는 path variable로 전달한다.- Query parameters는
page,size를 사용한다. - 최초 조회 기본값은
page=0,size=20이다. - 정렬 query parameter는 사용하지 않는다.
hasNext == true일 때 다음 페이지 요청은 현재 응답의page + 1값을 사용한다.- 중복 pagination 요청이 발생하지 않도록 loading 중 추가 요청을 막아야 한다.
- 다음 페이지 성공 시 기존
fanTalks뒤에 append한다.
Response Contract
data class CreatorChannelFanTalkTabResponse(
val fanTalkCount: Int,
val fanTalks: List<CreatorChannelFanTalkResponse>,
val page: Int,
val size: Int,
@JsonProperty("hasNext")
val hasNext: Boolean
)
data class CreatorChannelFanTalkResponse(
val fanTalkId: Long,
val writerId: Long,
val writerNickname: String,
val writerProfileImageUrl: String,
val content: String,
val createdAtUtc: String,
val creatorReplies: List<CreatorChannelFanTalkReplyResponse>
)
data class CreatorChannelFanTalkReplyResponse(
val fanTalkId: Long,
val writerId: Long,
val writerNickname: String,
val writerProfileImageUrl: String,
val content: String,
val createdAtUtc: String
)
Edge Cases
- 최초 조회 실패 시 기존 크리에이터 채널 탭의 error/retry 패턴을 따른다.
- 다음 페이지 로딩 실패 시 기존 목록은 유지하고 기존 pagination 실패 표시 정책을 따른다.
- 다음 페이지 응답의
fanTalks가 비어 있어도hasNext값 기준으로 이후 로딩 가능 여부를 갱신한다. - 서버 응답의
page,size가 요청 상태와 다를 경우 서버 응답 값을 기준으로 ViewModel page 상태를 동기화한다. writerProfileImageUrl이 비어 있거나 이미지 로딩에 실패하면 기존 프로필 이미지 placeholder 정책을 따른다.creatorReplies가 비어 있으면 답글 영역과 답글 연결선을 표시하지 않는다.creatorReplies가 2개 이상이어도 응답 순서 기준 첫 번째 1개만 표시한다.
Sort Bar without Sort
Sort-bar는 전체 FanTalk 수만 표시한다.
Requirements
- Figma 전체 화면 기준 Sort-bar는
290:9139내sort-bar이다. - 좌측에는
전체와fanTalkCount를 표시한다. - 우측 정렬 label, 정렬 icon, 정렬 popup 진입 영역은 표시하지 않는다.
- FanTalk 탭에서는 정렬 상태를 보관하거나 API query로 전달하지 않는다.
Edge Cases
fanTalkCount == 0이고 전체 empty 상태이면 Figma empty 화면290:9000을 우선하며 Sort-bar를 표시하지 않는다.- 다국어 label 길이가 길어져도 전체 개수와 겹치지 않아야 한다.
FanTalk List Item
FanTalk 목록은 Figma의 feed item 구조를 기준으로 원글과 크리에이터 답글을 표시한다.
Requirements
- Figma 전체 화면 기준 FanTalk item은
290:9139의feed-list내feed구조를 따른다. - 각 원글에는
writerProfileImageUrl,writerNickname,createdAtUtc,content를 표시한다. createdAtUtc는 크리에이터 채널 v2 공통 날짜/시간 포맷을 따른다.- 원글 본문은 Figma처럼 16sp regular text로 표시하고 긴 내용은 자연스럽게 여러 줄로 확장한다.
creatorReplies가 있으면 원글 아래에 회색 배경의 답글 card를 표시한다.- 답글 card에는 답글 작성자 프로필 이미지, 닉네임, 작성 시간, 본문을 표시한다.
- 답글 card의 작성자는 API 응답의
writerNickname,writerProfileImageUrl을 사용한다. - 크리에이터 답글은 현재 1개만 추가되는 서버 정책을 기준으로 최대 1개만 표시한다.
creatorReplies가 여러 개 내려오더라도 응답 순서 기준 첫 번째 1개만 표시하고 나머지는 표시하지 않는다.- 원글과 답글 사이에는 Figma처럼 연결선을 표시한다.
Edge Cases
- 원글
content가 비어 있으면 작성자/작성 시간/우측 액션은 유지하고 본문 텍스트 영역은 빈 문자열로 표시한다. - 답글
content가 비어 있으면 답글 card는 표시하되 본문 텍스트 영역은 빈 문자열로 표시한다. - 닉네임이 긴 경우 프로필 영역에서 말줄임 처리한다.
createdAtUtc파싱 실패 시 기존 날짜 표시 fallback 정책을 따른다.
Item Right Action
FanTalk 원글 우측 액션은 사용자 권한에 따라 더보기 또는 신고로 분기한다.
Requirements
- 현재 로그인 사용자의 id와
writerId가 같으면 내가 쓴 글로 판단한다. - 현재 크리에이터 채널이 내 채널이면 내 채널로 판단한다.
- 내가 쓴 글이거나 내 채널에서 타인이 작성한 글인 경우 우측 액션은 더보기 버튼으로 표시한다.
- 더보기 버튼 icon은
ic_new_more를 사용한다. - 더보기 버튼을 누르면 Figma popup 메뉴처럼 권한별 메뉴를 표시한다.
- 내가 쓴 글의 popup 메뉴 label은
수정하기,삭제하기이다. - 내 채널에서 타인이 작성한 글의 popup 메뉴 label은
삭제하기만 표시한다. - 내가 쓴 글이면서 내 채널의 글인 경우 내가 쓴 글 기준으로
수정하기,삭제하기를 표시한다. 수정하기는 표시만 하고 이번 범위에서는 실제 수정 화면/API를 연결하지 않는다.삭제하기는 기존 FanTalk 삭제 API 흐름에 연결한다.- 기존 삭제 API 흐름은 레거시 FanTalk 삭제와 동일하게
PutModifyCheersRequest(cheersId = fanTalkId, isActive = false)를ExplorerRepository.modifyCheers()/PUT /explorer/profile/cheers로 요청하는 방식이다. - 삭제 전에는 기존 FanTalk 삭제와 동일하게
SodaDialog확인 dialog를 표시한다. - 삭제 성공 후에는 목록을 새로고침하거나 해당 item을 제거해 삭제 결과가 화면에 반영되어야 한다.
- 내가 쓴 글도 아니고 내 채널도 아닌 경우 우측 액션은
신고버튼으로 표시한다. - 신고 버튼은 Figma 기준 14sp medium gray text button으로 표시한다.
Edge Cases
- 로그인 사용자 id를 확인할 수 없는 경우 내가 쓴 글로 판단하지 않는다.
- 내 채널 여부를 확인할 수 없는 경우 내 채널로 판단하지 않는다.
수정하기터치 시에는 API를 호출하지 않고 수정 화면으로 이동하지 않는다.- 삭제 확인 dialog에서 취소하면 삭제 API를 호출하지 않는다.
- 삭제 API 실패 시 기존 FanTalk 삭제 흐름과 동일한 toast/error 정책을 따른다.
- 더보기 popup은 화면 우측 또는 하단을 벗어나지 않아야 한다.
- 더보기 popup이 열린 상태에서 다른 item의 액션을 누르면 기존 popup을 닫고 새 액션을 처리한다.
FanTalk Report
신고 버튼 터치 시 기존 FanTalk 신고 기능과 동일한 신고 dialog와 신고 API 계약을 사용한다.
Requirements
- 신고 버튼 터치 시 기존
UserProfileFantalkAllViewActivity.showCheersReportPopup()흐름과 동일하게CheersReportDialog를 표시한다. - 신고 사유를 선택하지 않고 신고하면
screen_user_profile_fantalk_report_reason_required문구를 사용한다. - 신고 요청은 기존 FanTalk 신고와 동일하게
ReportRequest(type = ReportType.CHEERS, reason = reason, cheersId = fanTalkId)계약을 사용한다. - 신고 성공 toast는 기존 신고 흐름의 응답 message 또는
character_comment_report_submitted문구를 따른다. - 신고 API 호출은 기존
ReportRepository.report()흐름을 재사용한다.
Edge Cases
- 신고 dialog를 취소하면 API를 호출하지 않는다.
- 신고 API 실패 시 기존 FanTalk 신고 흐름과 동일한 toast/error 정책을 따른다.
- 이미 신고한 글에 대한 서버 오류 또는 중복 신고 메시지는 서버 응답 message를 우선 표시한다.
Floating Write Button
우측 하단 floating 글쓰기 버튼은 표시만 하고 실제 연결은 후속 작업으로 남긴다.
Requirements
- Figma 전체 화면 기준 우측 하단
button-floating을 표시한다. - 버튼은 목록 위에 floating 형태로 배치한다.
- 버튼 icon과 색상은 Figma와 대응되는 기존 프로젝트 asset/token을 우선 사용한다.
- 버튼 터치 이벤트는 후속 FanTalk 글쓰기 페이지 구현 후 연결한다.
- 이번 범위에서는 버튼 터치 시 실제 글쓰기 화면을 열지 않고 별도 API도 호출하지 않는다.
Edge Cases
- 목록 하단 item, 시스템 navigation bar, keyboard와 버튼이 겹치지 않아야 한다.
- 빈 목록 상태에서는 Figma empty 화면
290:9000을 기준으로 우측 하단 floating 글쓰기 버튼 대신 중앙응원 남기기capsule button을 표시한다.
Pagination
FanTalk 목록은 스크롤 하단 접근 시 다음 페이지를 로딩한다.
Requirements
CreatorChannelFanTalkTabResponse.hasNext == true일 때만 다음 페이지를 요청한다.- 다음 페이지는 마지막 성공 응답의
page + 1로 요청한다. - 다음 페이지 요청에는
size=20을 유지한다. - 다음 페이지 로딩 중에는 추가 page 요청을 중복으로 보내지 않는다.
- 다음 페이지 성공 시 기존
fanTalks뒤에 append한다.
Edge Cases
- 빠른 스크롤로 load-more trigger가 반복 발생해도 page가 중복 append되지 않아야 한다.
- Fragment/View 재생성 후 현재 목록과 page 상태는 ViewModel 상태 보존 정책에 따라 유지되어야 한다.
- 마지막 페이지 응답 이후
hasNext == false이면 이후 load-more trigger를 무시한다.
Empty State
FanTalk가 없으면 목록 대신 empty 상태를 표시한다.
Requirements
fanTalkCount == 0또는 표시 가능한fanTalks가 없는 전체 empty 상태이면 empty 상태를 표시한다.- empty 상태에서는 FanTalk 목록을 표시하지 않는다.
- empty 상태에서는 Figma empty 화면
290:9000기준으로 Sort-bar를 표시하지 않는다. - empty 문구는 Figma node
290:9001기준으로 16sp regular, line-height 1.45,gray/500(#959595), center 정렬로 표시한다. - empty 문구 한국어는
아직 응원이 없습니다.\n처음으로 크리에이터를 응원해 보세요!이다. - empty 문구 영어는
No cheers yet.\nBe the first to cheer for the creator!이다. - empty 문구 일본어는
まだ応援がありません。\n最初にクリエイターを応援してみましょう!이다. - empty 문구는 한국어/영어/일본어 다국어 문자열 리소스로 관리한다.
- empty 상태에서는 Figma node
290:9001처럼 문구 아래에 14dp 간격으로응원 남기기capsule button을 표시한다. - empty 상태의
응원 남기기button은gray/800(#343434) 배경, 100dp 수준 radius, 20dp icon, icon-text 6dp gap, 16sp medium white text를 기준으로 한다. - empty 상태의
응원 남기기button 터치 액션은 후속 FanTalk 글쓰기 페이지 구현 후 연결한다. - 이번 범위에서는 empty 상태의
응원 남기기button 터치 시 실제 글쓰기 화면을 열지 않고 별도 API도 호출하지 않는다.
Edge Cases
- API 최초 조회 실패 상태는 empty 상태로 취급하지 않고 기존 error/retry 패턴을 따른다.
fanTalkCount > 0이지만 첫 페이지fanTalks가 비어 있고 표시 가능한 item이 없으면 empty 상태를 표시하되, 전체 개수는 서버 응답의fanTalkCount값을 유지한다.
8. UX / UI Expectations
- 전체 화면은 기존 크리에이터 채널 컨테이너의 black background, sticky tab-bar, title-bar 동작을 유지한다.
FanTalkmain tab은 선택 상태로 표시하고, 선택 underline과 텍스트 색상은 기존 tab-bar 정책을 따른다.- Sort-bar 높이와 배치는 Figma
290:9139를 기준으로 하되 정렬 UI는 제거한다. - FanTalk item은 black background 위에 white/gray text hierarchy를 유지한다.
- 답글 card는 Figma처럼
gray/900계열 배경과 radius를 적용해 원글과 구분한다. - 원글 작성자 프로필 이미지는 42dp 원형, 답글 작성자 프로필 이미지는 20dp 원형 수준을 기준으로 한다.
- item 간 divider 또는 spacing은 Figma 구조를 기준으로 하되 기존 RecyclerView item 간격 정책과 충돌하지 않게 맞춘다.
- 우측 액션 영역은 더보기 icon 또는 신고 text 중 하나만 표시한다.
- popup 메뉴는 Figma처럼 원글 우측 액션 근처에 표시하고
수정하기,삭제하기항목을 세로로 배치한다. - floating 글쓰기 버튼은 우측 하단 14dp margin 수준으로 표시하고 목록 스크롤과 독립적으로 유지한다.
- empty 상태는 오디오/시리즈/커뮤니티 탭과 같은 상단 가시 영역 배치 패턴을 따른다. 별도
minHeight로 부모 높이를 늘리지 않고, tab content 상단 기준 48dp padding 아래에 empty 문구와응원 남기기capsule button을 세로로 배치한다.
9. Technical Constraints
- Android Gradle 프로젝트의
:app모듈에서 구현한다. - 신규
Fragment,ViewModel, adapter, mapper, DTO 등은kr.co.vividnext.sodalive.v2하위에 작성한다. - 레거시 파일은 직접 수정하지 않고, 기존 기능이 필요하면 기존 class를 호출하거나 신규 wrapper/adapter를 추가한다.
- 기존
CreatorChannelActivity의ViewPager2/CreatorChannelPagerAdapter구조를 유지한다. - API/Repository는 기존 크리에이터 채널 공통 data 계층 패턴을 우선 따른다.
- 신고 dialog/API는 기존
CheersReportDialog,ReportRepository,ReportType.CHEERS계약을 재사용한다. fanTalkId는 신고 요청의cheersId로 전달한다.- FanTalk 삭제는 기존
ExplorerRepository.modifyCheers()와PutModifyCheersRequest(cheersId = fanTalkId, isActive = false)계약을 재사용한다. - 정렬 관련
ContentSort,CreatorChannelSortPopup은 FanTalk 탭에서 사용하지 않는다. - 이미지 로딩, 날짜 포맷, toast/error/retry, pagination trigger는 기존 크리에이터 채널 탭 구현 패턴을 따른다.
- 문자열은 다국어 리소스로 관리하고 hardcoded user-facing text를 최소화한다.
- Figma localhost asset URL을 앱 코드에 직접 사용하지 않는다.
10. Metrics
- FanTalk 탭 최초 조회 성공률.
- FanTalk 탭 최초 조회 API latency.
- FanTalk pagination 성공률과 중복 요청 발생 여부.
- 신고 버튼 노출 조건 정확도.
- 더보기 버튼 노출 조건 정확도.
- 삭제 action 성공률과 삭제 후 목록 반영 정확도.
- FanTalk 신고 성공률.
- FanTalk 탭 empty/error 상태 노출 빈도.
- floating 글쓰기 버튼 클릭 수는 후속 글쓰기 페이지 연결 시 측정한다.
11. Open Questions
- 현재 확정 대기 중인 항목 없음.
12. Resolved Decisions
- 더보기 popup에서
수정하기는 표시만 하고 실제 수정 화면/API는 연결하지 않는다. - 더보기 popup에서
삭제하기는 기존 FanTalk 삭제 API 흐름에 연결한다. - 내 채널에서 타인이 작성한 FanTalk 글의 더보기 popup에는
삭제하기만 표시한다. createdAtUtc는 크리에이터 채널 v2 공통 날짜/시간 포맷을 따른다.- empty 문구는 한국어/영어/일본어 문자열 리소스로 관리한다.
- floating 글쓰기 버튼과 empty 상태의
응원 남기기button은 이번 범위에서 표시만 하고 터치 액션은 후속 범위에서 연결한다. creatorReplies는 최대 1개만 표시한다.