docs(creator): FanTalk 탭 문서를 추가한다
This commit is contained in:
527
docs/20260622_FanTalk_탭/plan-task.md
Normal file
527
docs/20260622_FanTalk_탭/plan-task.md
Normal file
@@ -0,0 +1,527 @@
|
||||
# 크리에이터 채널 FanTalk 탭 구현 계획/TASK
|
||||
|
||||
> **For agentic workers:** 각 단계는 체크박스(`- [ ]`)로 추적하고, 완료 즉시 `- [x]`로 갱신한다. 구현 범위 변경이 생기면 이 문서를 먼저 수정한 뒤 코드에 반영한다.
|
||||
|
||||
**Goal:** `GET /api/v2/creator-channels/{creatorId}/fan-talks` 응답을 기반으로 크리에이터 채널의 `FanTalk` 탭에 전체 개수, FanTalk 목록, 크리에이터 답글 1개, 권한별 신고/더보기/삭제, empty 상태, 표시 전용 글쓰기 버튼과 pagination을 표시한다.
|
||||
|
||||
**Architecture:** 기존 `CreatorChannelActivity`의 `ViewPager2`/`CreatorChannelPagerAdapter` 구조를 유지하고, `CreatorChannelTab.FanTalk`의 placeholder를 신규 `CreatorChannelFanTalkFragment`로 교체한다. FanTalk 탭 전용 Fragment/ViewModel/DTO/mapper/adapter/popup은 `kr.co.vividnext.sodalive.v2.creator.channel.fantalk` 하위에 두되, API/Repository는 기존 채널 공통 `CreatorChannelApi`/`CreatorChannelRepository`에 endpoint와 FanTalk 삭제/신고 위임만 추가한다. 날짜 표시는 v2 공통 `UtcRelativeTimeTextFormatter`를 사용하고, 신고/삭제는 기존 레거시 `CheersReportDialog`, `ReportRepository`, `ExplorerRepository.modifyCheers()` 계약을 재사용한다.
|
||||
|
||||
**Tech Stack:** Kotlin, Android XML Views, ViewBinding, RecyclerView, Retrofit, Gson, RxJava3, Koin, JUnit4/Robolectric local unit test.
|
||||
|
||||
---
|
||||
|
||||
## 전제와 성공 기준
|
||||
|
||||
- PRD: `docs/20260622_FanTalk_탭/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`
|
||||
- 기존 v2 날짜 포맷:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/common/RelativeTimeFormatter.kt`
|
||||
- `UtcRelativeTimeTextFormatter`
|
||||
- `AndroidUtcRelativeTimeTextFormatter`
|
||||
- 기존 FanTalk 홈 카드 참조:
|
||||
- `app/src/main/res/layout/item_creator_channel_home_fantalk.xml`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelHomeSectionAdapter.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelFanTalkCardView.kt`
|
||||
- 기존 신고/삭제 흐름 참조:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/fantalk/UserProfileFantalkAllViewActivity.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/fantalk/UserProfileFantalkAllViewModel.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/report/CheersReportDialog.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/report/ReportRequest.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/cheers/PutModifyCheersRequest.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerRepository.kt`
|
||||
- Figma:
|
||||
- 전체 목록: `290:9139`
|
||||
- empty 상태: `290:9000`
|
||||
- API endpoint는 `GET /api/v2/creator-channels/{creatorId}/fan-talks`이다.
|
||||
- 첫 페이지 `page`는 `0`, 기본 `size`는 `20`이다.
|
||||
- query parameter는 `page`, `size`만 전달한다.
|
||||
- 정렬 query parameter와 sort popup은 FanTalk 탭에서 사용하지 않는다.
|
||||
- `creatorReplies`는 응답 순서 기준 첫 번째 1개만 표시한다.
|
||||
- 내가 쓴 글이면 더보기 popup에 `수정하기`, `삭제하기`를 표시한다.
|
||||
- 내 채널에서 타인이 작성한 글이면 더보기 popup에 `삭제하기`만 표시한다.
|
||||
- `수정하기`는 표시만 하고 화면/API를 연결하지 않는다.
|
||||
- `삭제하기`는 `PutModifyCheersRequest(cheersId = fanTalkId, isActive = false)`를 `ExplorerRepository.modifyCheers()`로 요청한다.
|
||||
- 신고는 `ReportRequest(type = ReportType.CHEERS, reason = reason, cheersId = fanTalkId)`를 `ReportRepository.report()`로 요청한다.
|
||||
- 목록 content 상태에서는 우측 하단 floating 글쓰기 버튼을 표시하되 클릭 시 아무 화면/API도 연결하지 않는다.
|
||||
- empty 상태에서는 Sort-bar와 우측 하단 floating 버튼을 숨기고 중앙 empty 문구와 `응원 남기기` capsule button을 표시한다.
|
||||
- empty 상태의 `응원 남기기` button도 클릭 시 아무 화면/API도 연결하지 않는다.
|
||||
- empty 문구는 다음 다국어 문자열 리소스로 제공한다.
|
||||
- 한국어: `아직 응원이 없습니다.\n처음으로 크리에이터를 응원해 보세요!`
|
||||
- 영어: `No cheers yet.\nBe the first to cheer for the creator!`
|
||||
- 일본어: `まだ応援がありません。\n最初にクリエイターを応援してみましょう!`
|
||||
- 구현 완료 후 최소 다음 명령을 실행한다.
|
||||
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.*"`
|
||||
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*FanTalk*"`
|
||||
- `./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, 신고/삭제 재사용 경계 확인이 중심이며 Figma는 PRD 기준만 확인한다.
|
||||
- Phase 2: Figma 참조 불필요
|
||||
- API/DTO/Repository/ViewModel 상태 모델은 서버 계약과 기존 Community/Series 탭 패턴을 따른다.
|
||||
- Phase 3: 제한 참조
|
||||
- mapper와 action state는 PRD, 기존 FanTalk adapter, v2 날짜 formatter 정책을 함께 확인한다.
|
||||
- Phase 4: 필수 참조
|
||||
- Sort-bar, list item, 답글 card, 권한별 우측 action, popup, floating button은 Figma `290:9139` 기준으로 구현한다.
|
||||
- Phase 5: 필수 참조
|
||||
- empty 상태는 Figma `290:9000` 기준으로 구현한다.
|
||||
- Phase 6: 제한 참조
|
||||
- 탭 연결, pagination, 신고/삭제 dialog/API 연결은 기존 코드 패턴 중심으로 검증한다.
|
||||
- Phase 7: 필수 참조
|
||||
- 최종 수동 화면 검증은 PRD의 Figma 노드와 실제 화면을 대조한다.
|
||||
|
||||
---
|
||||
|
||||
## 파일 구조
|
||||
|
||||
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapter.kt`
|
||||
- `CreatorChannelTab.FanTalk`을 신규 `CreatorChannelFanTalkFragment`로 연결한다.
|
||||
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt`
|
||||
- `CreatorChannelFanTalkFragment.Host` 구현, FanTalk 탭 선택 시 최초 로드, pagination trigger, ViewPager 높이 갱신, 신고/삭제 확인 dialog 표시, 삭제 성공 후 refresh 연결을 추가한다.
|
||||
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelApi.kt`
|
||||
- FanTalk 탭 endpoint를 추가한다.
|
||||
- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelRepository.kt`
|
||||
- FanTalk 목록 조회, 신고, 삭제 repository method를 추가한다.
|
||||
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/data/CreatorChannelFanTalkTabResponse.kt`
|
||||
- `CreatorChannelFanTalkTabResponse`, `CreatorChannelFanTalkResponse`, `CreatorChannelFanTalkReplyResponse`를 정의한다.
|
||||
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkViewModel.kt`
|
||||
- 최초 조회, retry, refresh, pagination, 신고, 삭제, loading/error/empty/content 상태를 관리한다.
|
||||
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/model/CreatorChannelFanTalkUiModels.kt`
|
||||
- FanTalk item, reply, 권한별 action, 화면 상태 UI model을 정의한다.
|
||||
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/model/CreatorChannelFanTalkMappers.kt`
|
||||
- DTO를 UI model로 변환하고 `creatorReplies.firstOrNull()`, 날짜 포맷, 권한별 action 표시를 결정한다.
|
||||
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragment.kt`
|
||||
- FanTalk 탭 UI, adapter, popup, retry, pagination error toast, report/delete callback, host callback 연결을 담당한다.
|
||||
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/ui/CreatorChannelFanTalkAdapter.kt`
|
||||
- FanTalk 목록 RecyclerView adapter를 담당한다.
|
||||
- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/ui/CreatorChannelFanTalkMorePopup.kt`
|
||||
- `수정하기`, `삭제하기` popup 표시와 click callback을 담당한다.
|
||||
- 생성: `app/src/main/res/layout/fragment_creator_channel_fantalk.xml`
|
||||
- Sort-bar 없는 count bar, RecyclerView, empty, error/retry, floating write button 영역을 포함한다.
|
||||
- 생성: `app/src/main/res/layout/item_creator_channel_fantalk.xml`
|
||||
- 원글, 권한별 우측 action, 크리에이터 답글 card 1개를 구현한다.
|
||||
- 생성: `app/src/main/res/layout/view_creator_channel_fantalk_more_popup.xml`
|
||||
- 권한별 `수정하기`/`삭제하기` popup menu layout을 구현한다.
|
||||
- 생성 가능: `app/src/main/res/drawable/bg_creator_channel_fantalk_reply.xml`
|
||||
- 답글 card 배경이 기존 drawable로 대응되지 않을 때만 추가한다.
|
||||
- 생성 가능: `app/src/main/res/drawable/bg_creator_channel_fantalk_empty_button.xml`
|
||||
- empty capsule button 배경이 기존 home FanTalk/donation drawable로 대응되지 않을 때만 추가한다.
|
||||
- 수정: `app/src/main/res/values/strings.xml`
|
||||
- FanTalk 탭 empty/error/retry/action/report/delete/count 문구를 추가하거나 기존 문자열을 재사용한다.
|
||||
- 수정: `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`
|
||||
- `CreatorChannelFanTalkViewModel` binding을 추가한다.
|
||||
- 테스트 생성:
|
||||
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkMapperTest.kt`
|
||||
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkViewModelTest.kt`
|
||||
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkPaginationTest.kt`
|
||||
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkActionTest.kt`
|
||||
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragmentLayoutTest.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: FanTalk 탭 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.FanTalk`이 현재 `CreatorChannelPlaceholderFragment`로 연결되는지 확인한다.
|
||||
- 신규 `CreatorChannelFanTalkFragment.newInstance(creatorId)`로 교체할 위치를 고정한다.
|
||||
- 검증:
|
||||
- `rg -n "CreatorChannelTab\\.FanTalk|CreatorChannelPlaceholderFragment|createFragment" app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel`
|
||||
- 기대 결과: FanTalk placeholder와 관련 테스트 갱신 지점이 확인된다.
|
||||
- 검증 기록:
|
||||
|
||||
- [ ] **Task 1.2: 기존 v2 목록 탭 패턴 확인**
|
||||
- 확인:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityFragment.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/series/CreatorChannelSeriesViewModel.kt`
|
||||
- 작업:
|
||||
- FanTalk ViewModel도 `FIRST_PAGE = 0`, `DEFAULT_PAGE_SIZE = 20`, `requestGeneration`, `paginationErrorMessage`, `consumePaginationErrorMessage()` 패턴을 따른다.
|
||||
- Fragment도 `onCreatorChannelFanTalkTabSelected()`, `onCreatorChannelFanTalkScrolledToBottom()`, `onCreatorChannelFanTalkRefreshRequested()` entry를 제공한다.
|
||||
- 검증:
|
||||
- `rg -n "requestGeneration|paginationErrorMessage|consumePaginationErrorMessage|ScrolledToBottom|RefreshRequested" app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel`
|
||||
- 기대 결과: Community/Series의 pagination과 refresh 패턴이 확인된다.
|
||||
- 검증 기록:
|
||||
|
||||
- [ ] **Task 1.3: 기존 신고/삭제 재사용 경계 확인**
|
||||
- 확인:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/fantalk/UserProfileFantalkAllViewActivity.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/fantalk/UserProfileFantalkAllViewModel.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/report/CheersReportDialog.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/cheers/PutModifyCheersRequest.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerRepository.kt`
|
||||
- 작업:
|
||||
- 신고 dialog는 `CheersReportDialog(requireActivity(), layoutInflater)`를 사용한다.
|
||||
- 신고 사유가 비어 있으면 `screen_user_profile_fantalk_report_reason_required` toast를 표시한다.
|
||||
- 신고 요청은 `ReportType.CHEERS`, `cheersId = fanTalkId`로 고정한다.
|
||||
- 삭제 요청은 `PutModifyCheersRequest(cheersId = fanTalkId, isActive = false)`로 고정한다.
|
||||
- 레거시 파일은 직접 수정하지 않는다.
|
||||
- 검증:
|
||||
- `rg -n "CheersReportDialog|ReportType\\.CHEERS|PutModifyCheersRequest|isActive = false|modifyCheers" app/src/main/java/kr/co/vividnext/sodalive/explorer app/src/main/java/kr/co/vividnext/sodalive/report`
|
||||
- 기대 결과: 기존 신고/삭제 계약이 확인된다.
|
||||
- 검증 기록:
|
||||
|
||||
- [ ] **Task 1.4: FanTalk 문자열과 리소스 재사용 경계 확인**
|
||||
- 확인:
|
||||
- `app/src/main/res/values/strings.xml`
|
||||
- `app/src/main/res/values-en/strings.xml`
|
||||
- `app/src/main/res/values-ja/strings.xml`
|
||||
- `app/src/main/res/drawable`
|
||||
- `app/src/main/res/layout/item_creator_channel_home_fantalk.xml`
|
||||
- 작업:
|
||||
- `ic_new_more`, `ic_new_fantalk_plus`, `ic_placeholder_profile` 재사용 가능 여부를 확인한다.
|
||||
- empty 문구는 신규 `creator_channel_fantalk_empty_message`로 추가한다.
|
||||
- button label은 기존 `creator_channel_fantalk_support_action`을 재사용한다.
|
||||
- `수정하기`, `삭제하기`, `신고`, `전체` 문구는 기존 문자열이 PRD 문구와 다르면 FanTalk 탭 전용 문자열을 추가한다.
|
||||
- 검증:
|
||||
- `rg -n "ic_new_more|ic_new_fantalk_plus|creator_channel_fantalk_support_action|screen_user_profile_cheer_edit|confirm_delete_title|report|creator_channel_.*all" app/src/main/res`
|
||||
- 기대 결과: 재사용 가능한 리소스와 신규 문자열 추가 대상이 구분된다.
|
||||
- 검증 기록:
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: API/DTO/Repository/ViewModel 계약 추가
|
||||
|
||||
- [ ] **Task 2.1: FanTalk 탭 DTO 추가**
|
||||
- 생성:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/data/CreatorChannelFanTalkTabResponse.kt`
|
||||
- 작업:
|
||||
- `@Keep`, `@SerializedName` 기반으로 `CreatorChannelFanTalkTabResponse`, `CreatorChannelFanTalkResponse`, `CreatorChannelFanTalkReplyResponse`를 추가한다.
|
||||
- PRD의 서버 필드명과 동일하게 `fanTalkCount`, `fanTalks`, `page`, `size`, `hasNext`, `fanTalkId`, `writerId`, `writerNickname`, `writerProfileImageUrl`, `content`, `createdAtUtc`, `creatorReplies`를 정의한다.
|
||||
- `@JsonProperty("hasNext")` 대신 프로젝트 기존 Gson 패턴인 `@SerializedName("hasNext")`를 사용한다.
|
||||
- 검증 명령:
|
||||
- `./gradlew :app:compileDebugKotlin`
|
||||
- 기대 결과:
|
||||
- 신규 DTO 추가 후 컴파일이 PASS한다.
|
||||
- 검증 기록:
|
||||
|
||||
- [ ] **Task 2.2: FanTalk 탭 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}/fan-talks")` endpoint를 추가한다.
|
||||
- query parameter `page`, `size`만 전달한다.
|
||||
- Repository method는 `getFanTalks(creatorId, page, size, token)` 형태로 둔다.
|
||||
- `CreatorChannelRepository.reportFanTalk(fanTalkId, reason, token)`는 `ReportRequest(ReportType.CHEERS, reason, cheersId = fanTalkId)`를 `reportRepository.report()`로 위임한다.
|
||||
- `CreatorChannelRepository.deleteFanTalk(fanTalkId, token)`는 `PutModifyCheersRequest(cheersId = fanTalkId, isActive = false)`를 `explorerRepository.modifyCheers()`로 위임한다.
|
||||
- 검증 명령:
|
||||
- `./gradlew :app:compileDebugKotlin`
|
||||
- 기대 결과:
|
||||
- API/Repository 추가 후 기존 Koin graph와 충돌 없이 컴파일된다.
|
||||
- 검증 기록:
|
||||
|
||||
- [ ] **Task 2.3: ViewModel RED 테스트 작성**
|
||||
- 생성:
|
||||
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkViewModelTest.kt`
|
||||
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkPaginationTest.kt`
|
||||
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkActionTest.kt`
|
||||
- 테스트 케이스:
|
||||
- 최초 로딩이 `page=0`, `size=20`으로 호출된다.
|
||||
- `fanTalkCount == 0`이면 `Empty` 상태가 된다.
|
||||
- 표시 가능한 `fanTalks`가 없으면 `Empty` 상태가 된다.
|
||||
- `fanTalkCount > 0`이지만 첫 페이지 `fanTalks`가 비어 있으면 `Empty` 상태가 되고 count 값은 유지된다.
|
||||
- `hasNext == true`일 때 다음 페이지는 마지막 응답의 `page + 1`로 요청한다.
|
||||
- load-more 요청에는 `size=20`을 유지한다.
|
||||
- loading 중 중복 load-more 요청은 무시된다.
|
||||
- 다음 페이지 성공 시 기존 FanTalk 뒤에 append한다.
|
||||
- 다음 페이지 실패 시 기존 목록은 유지하고 pagination error message만 설정한다.
|
||||
- `consumePaginationErrorMessage()` 호출 후 pagination error message가 null이 된다.
|
||||
- 신고 성공 시 toast message가 설정된다.
|
||||
- 신고 실패 시 기존 content 상태는 유지하고 toast/error message가 설정된다.
|
||||
- 삭제 성공 시 첫 페이지 refresh가 호출된다.
|
||||
- 삭제 실패 시 기존 content 상태는 유지하고 toast/error message가 설정된다.
|
||||
- 검증 명령:
|
||||
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkViewModelTest"`
|
||||
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkPaginationTest"`
|
||||
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkActionTest"`
|
||||
- 기대 결과:
|
||||
- production 구현 전 `CreatorChannelFanTalkViewModel` 미구현으로 RED 실패한다.
|
||||
- 검증 기록:
|
||||
|
||||
- [ ] **Task 2.4: `CreatorChannelFanTalkViewModel` 구현**
|
||||
- 생성:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkViewModel.kt`
|
||||
- 수정:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt`
|
||||
- 작업:
|
||||
- `DEFAULT_PAGE_SIZE = 20`, `FIRST_PAGE = 0`으로 둔다.
|
||||
- `loadFanTalks(creatorId: Long, isOwner: Boolean)`는 같은 `creatorId`와 `isOwner`로 기존 state가 있으면 중복 최초 조회를 막는다.
|
||||
- `retryFanTalks()`와 `refreshFanTalks()`는 첫 페이지를 다시 조회한다.
|
||||
- `loadMore()`는 content 상태, `hasNext`, `isLoadingMore`, `creatorId`를 확인해 중복 요청을 막는다.
|
||||
- `requestGeneration`으로 오래된 응답이 최신 상태를 덮어쓰지 않게 한다.
|
||||
- 첫 페이지 성공 후 `fanTalkCount == 0` 또는 표시 가능한 item이 0개이면 `Empty(fanTalkCount)` 상태로 전환한다.
|
||||
- pagination 실패는 기존 content를 유지하고 `paginationErrorMessage`에만 반영한다.
|
||||
- `reportFanTalk(fanTalkId, reason)`는 blank reason을 ViewModel에서 호출하지 않는 전제로 두고 repository 결과 message를 toast state로 전달한다.
|
||||
- `deleteFanTalk(fanTalkId)`는 성공 시 `refreshFanTalks()`를 호출한다.
|
||||
- 검증 명령:
|
||||
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.*"`
|
||||
- 기대 결과:
|
||||
- ViewModel 관련 테스트가 GREEN이다.
|
||||
- 검증 기록:
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: Mapper와 권한별 UI model 추가
|
||||
|
||||
- [ ] **Task 3.1: FanTalk UI model 추가**
|
||||
- 생성:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/model/CreatorChannelFanTalkUiModels.kt`
|
||||
- 작업:
|
||||
- `CreatorChannelFanTalkUiModel`에는 `fanTalkId`, `writerId`, `writerNickname`, `writerProfileImageUrl`, `content`, `createdAtText`, `reply`, `rightAction`을 포함한다.
|
||||
- `CreatorChannelFanTalkReplyUiModel`에는 `fanTalkId`, `writerId`, `writerNickname`, `writerProfileImageUrl`, `content`, `createdAtText`를 포함한다.
|
||||
- `CreatorChannelFanTalkRightAction`은 `Report`, `OwnerMore(showEdit: Boolean, showDelete: Boolean)`로 정의한다.
|
||||
- 내가 쓴 글이면 `OwnerMore(showEdit = true, showDelete = true)`이다.
|
||||
- 내 채널의 타인 글이면 `OwnerMore(showEdit = false, showDelete = true)`이다.
|
||||
- 내가 쓴 글도 아니고 내 채널도 아니면 `Report`이다.
|
||||
- 검증 명령:
|
||||
- `./gradlew :app:compileDebugKotlin`
|
||||
- 기대 결과:
|
||||
- UI model 추가 후 컴파일이 PASS한다.
|
||||
- 검증 기록:
|
||||
|
||||
- [ ] **Task 3.2: Mapper RED/GREEN 테스트 작성**
|
||||
- 생성:
|
||||
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkMapperTest.kt`
|
||||
- 테스트 케이스:
|
||||
- `createdAtUtc`는 `UtcRelativeTimeTextFormatter.format()` 결과로 매핑된다.
|
||||
- `creatorReplies`가 비어 있으면 `reply == null`이다.
|
||||
- `creatorReplies`가 2개 이상이면 첫 번째 reply만 매핑된다.
|
||||
- 내가 쓴 글이면 `OwnerMore(showEdit = true, showDelete = true)`이다.
|
||||
- 내 채널의 타인 글이면 `OwnerMore(showEdit = false, showDelete = true)`이다.
|
||||
- 내가 쓴 글도 아니고 내 채널도 아니면 `Report`이다.
|
||||
- 원글/답글 content가 빈 문자열이어도 item은 유지된다.
|
||||
- 구현:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/model/CreatorChannelFanTalkMappers.kt`
|
||||
- `List<CreatorChannelFanTalkResponse>.toFanTalkUiModels(relativeTimeTextFormatter, isOwner, currentUserId)`를 추가한다.
|
||||
- 검증 명령:
|
||||
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkMapperTest"`
|
||||
- 기대 결과:
|
||||
- Mapper 테스트가 GREEN이다.
|
||||
- 검증 기록:
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: FanTalk 목록 UI와 popup 구현
|
||||
|
||||
- [ ] **Task 4.1: Fragment layout과 문자열 추가**
|
||||
- 생성:
|
||||
- `app/src/main/res/layout/fragment_creator_channel_fantalk.xml`
|
||||
- 수정:
|
||||
- `app/src/main/res/values/strings.xml`
|
||||
- `app/src/main/res/values-en/strings.xml`
|
||||
- `app/src/main/res/values-ja/strings.xml`
|
||||
- 작업:
|
||||
- content 상태에서는 `전체` label과 `fanTalkCount`만 있는 count bar를 배치한다.
|
||||
- sort label/icon/popup 진입 영역은 layout에 넣지 않는다.
|
||||
- RecyclerView, error message, retry button, empty container, floating write button을 포함한다.
|
||||
- empty container에는 PRD의 empty 문구와 `creator_channel_fantalk_support_action` button을 배치한다.
|
||||
- empty 상태에서는 count bar, RecyclerView, floating write button을 숨기는 구조로 둔다.
|
||||
- 신규 문자열은 `creator_channel_fantalk_empty_message`, `creator_channel_fantalk_error_message`, `creator_channel_fantalk_retry`, `creator_channel_fantalk_all_label`, `creator_channel_fantalk_report`, `creator_channel_fantalk_edit`, `creator_channel_fantalk_delete` 이름으로 추가한다.
|
||||
- 검증 명령:
|
||||
- `./gradlew :app:mergeDebugResources`
|
||||
- 기대 결과:
|
||||
- 신규 layout/string resource merge가 PASS한다.
|
||||
- 검증 기록:
|
||||
|
||||
- [ ] **Task 4.2: FanTalk item layout과 adapter 추가**
|
||||
- 생성:
|
||||
- `app/src/main/res/layout/item_creator_channel_fantalk.xml`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/ui/CreatorChannelFanTalkAdapter.kt`
|
||||
- 작업:
|
||||
- 원글 profile image는 42dp 원형 수준으로 표시한다.
|
||||
- 원글 writer nickname, createdAtText, content를 표시한다.
|
||||
- 우측 action은 `OwnerMore`이면 `ic_new_more` ImageView, `Report`이면 `신고` TextView만 표시한다.
|
||||
- reply가 있으면 원글 아래 회색 배경 답글 card를 표시한다.
|
||||
- reply profile image는 20dp 원형 수준으로 표시한다.
|
||||
- reply writer nickname, createdAtText, content를 표시한다.
|
||||
- reply가 없으면 답글 card와 연결선을 숨긴다.
|
||||
- profile image url이 비어 있거나 실패하면 `ic_placeholder_profile`을 사용한다.
|
||||
- item click 자체는 동작하지 않는다.
|
||||
- 검증 명령:
|
||||
- `./gradlew :app:mergeDebugResources`
|
||||
- `./gradlew :app:compileDebugKotlin`
|
||||
- 기대 결과:
|
||||
- item layout과 adapter 추가 후 resource merge와 Kotlin compile이 PASS한다.
|
||||
- 검증 기록:
|
||||
|
||||
- [ ] **Task 4.3: 더보기 popup 추가**
|
||||
- 생성:
|
||||
- `app/src/main/res/layout/view_creator_channel_fantalk_more_popup.xml`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/ui/CreatorChannelFanTalkMorePopup.kt`
|
||||
- 작업:
|
||||
- `showEdit == true`이면 `수정하기`를 표시한다.
|
||||
- `showDelete == true`이면 `삭제하기`를 표시한다.
|
||||
- `수정하기` 터치 시 popup만 닫고 API/화면 이동은 호출하지 않는다.
|
||||
- `삭제하기` 터치 시 popup을 닫고 adapter callback으로 `fanTalkId`를 전달한다.
|
||||
- popup은 anchor view 기준 우측 영역에 표시하되 화면 밖으로 벗어나지 않도록 `PopupWindow` 기본 clipping을 유지한다.
|
||||
- 검증 명령:
|
||||
- `./gradlew :app:compileDebugKotlin`
|
||||
- 기대 결과:
|
||||
- popup class 추가 후 컴파일이 PASS한다.
|
||||
- 검증 기록:
|
||||
|
||||
- [ ] **Task 4.4: Fragment layout source 테스트 추가**
|
||||
- 생성:
|
||||
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragmentLayoutTest.kt`
|
||||
- 테스트 케이스:
|
||||
- fragment layout에 sort icon/정렬 label이 없다.
|
||||
- fragment layout에 `전체` count TextView가 있다.
|
||||
- fragment layout에 empty 문구와 `응원 남기기` button이 있다.
|
||||
- fragment layout에 content floating write button이 있다.
|
||||
- item layout에 `ic_new_more`와 `신고` TextView가 있다.
|
||||
- item layout에 reply container가 있다.
|
||||
- popup layout에 `수정하기`, `삭제하기` TextView가 있다.
|
||||
- 검증 명령:
|
||||
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkFragmentLayoutTest"`
|
||||
- 기대 결과:
|
||||
- Layout source 테스트가 GREEN이다.
|
||||
- 검증 기록:
|
||||
|
||||
---
|
||||
|
||||
### Phase 5: Fragment 동작, 신고/삭제, empty 상태 연결
|
||||
|
||||
- [ ] **Task 5.1: `CreatorChannelFanTalkFragment` 구현**
|
||||
- 생성:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragment.kt`
|
||||
- 작업:
|
||||
- `CreatorChannelFanTalkViewModel`을 Koin `by viewModel()`로 주입한다.
|
||||
- `RecyclerView`는 `LinearLayoutManager`와 `CreatorChannelFanTalkAdapter`를 사용한다.
|
||||
- `onCreatorChannelFanTalkTabSelected()`에서 `viewModel.loadFanTalks(creatorId, isOwner = host.isCreatorChannelOwner())`를 호출한다.
|
||||
- `onCreatorChannelFanTalkScrolledToBottom()`에서 `viewModel.loadMore()`를 호출한다.
|
||||
- `onCreatorChannelFanTalkRefreshRequested()`에서 `viewModel.refreshFanTalks()`를 호출한다.
|
||||
- Loading/Error/Empty/Content 상태별 visibility를 명확히 분기한다.
|
||||
- Content 상태에서는 count bar, RecyclerView, floating write button을 표시한다.
|
||||
- Empty 상태에서는 empty container만 표시하고 count bar, RecyclerView, floating write button을 숨긴다.
|
||||
- floating write button과 empty `응원 남기기` button click listener는 비워 두거나 no-op으로 둔다.
|
||||
- 신고 action click 시 `CheersReportDialog`를 열고 blank reason이면 기존 reason required toast를 표시한다.
|
||||
- 삭제 action click 시 host에 `fanTalkId`를 전달해 삭제 확인 dialog를 열도록 한다.
|
||||
- pagination error와 action toast message는 Toast로 표시 후 consume한다.
|
||||
- 검증 명령:
|
||||
- `./gradlew :app:compileDebugKotlin`
|
||||
- 기대 결과:
|
||||
- Fragment 추가 후 컴파일이 PASS한다.
|
||||
- 검증 기록:
|
||||
|
||||
- [ ] **Task 5.2: `CreatorChannelActivity`에 FanTalk Host 연결**
|
||||
- 수정:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt`
|
||||
- 작업:
|
||||
- Activity가 `CreatorChannelFanTalkFragment.Host`를 구현한다.
|
||||
- `isCreatorChannelOwner()` 기존 method를 FanTalk Host에서도 재사용한다.
|
||||
- FanTalk 탭 선택 시 `findFanTalkFragment()?.onCreatorChannelFanTalkTabSelected()`를 호출한다.
|
||||
- 스크롤 하단 도달 시 `findFanTalkFragment()?.onCreatorChannelFanTalkScrolledToBottom()`를 호출한다.
|
||||
- 새로고침이 필요한 공통 경로에서 `findFanTalkFragment()?.onCreatorChannelFanTalkRefreshRequested()`를 호출한다.
|
||||
- `onCreatorChannelFanTalkContentChanged()`에서 ViewPager 높이 갱신을 호출한다.
|
||||
- `onCreatorChannelFanTalkDeleteClicked(fanTalkId)`에서 `SodaDialog`를 표시한다.
|
||||
- 삭제 확인 dialog의 confirm에서 Fragment 또는 ViewModel으로 삭제 요청을 다시 전달한다.
|
||||
- 검증 명령:
|
||||
- `./gradlew :app:compileDebugKotlin`
|
||||
- 기대 결과:
|
||||
- Activity Host 연결 후 컴파일이 PASS한다.
|
||||
- 검증 기록:
|
||||
|
||||
- [ ] **Task 5.3: `CreatorChannelPagerAdapter`에 FanTalk 탭 연결**
|
||||
- 수정:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapter.kt`
|
||||
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapterTest.kt`
|
||||
- 작업:
|
||||
- `CreatorChannelTab.FanTalk -> CreatorChannelFanTalkFragment.newInstance(creatorId)` 분기를 추가한다.
|
||||
- 기존 테스트를 Home/Live/Audio/Series/Community/FanTalk 실제 Fragment 생성 검증으로 갱신한다.
|
||||
- FanTalk 외 placeholder 유지 기대값은 유지한다.
|
||||
- 검증 명령:
|
||||
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelPagerAdapterTest"`
|
||||
- 기대 결과:
|
||||
- PagerAdapter 테스트가 GREEN이다.
|
||||
- 검증 기록:
|
||||
|
||||
- [ ] **Task 5.4: Activity source 테스트 갱신**
|
||||
- 수정:
|
||||
- `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt`
|
||||
- 작업:
|
||||
- 기존 `assertFalse(source.contains("CreatorChannelTab.FanTalk ->"))` 기대값을 제거하거나 FanTalk 연결 검증으로 변경한다.
|
||||
- `CreatorChannelFanTalkFragment.Host` 구현을 검증한다.
|
||||
- `findFanTalkFragment()?.onCreatorChannelFanTalkTabSelected()` 호출을 검증한다.
|
||||
- `findFanTalkFragment()?.onCreatorChannelFanTalkScrolledToBottom()` 호출을 검증한다.
|
||||
- `onCreatorChannelFanTalkContentChanged()` 구현을 검증한다.
|
||||
- 신고/삭제 dialog 관련 source 검증은 과도한 문자열 고정 대신 주요 class/method 호출만 검증한다.
|
||||
- 검증 명령:
|
||||
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"`
|
||||
- 기대 결과:
|
||||
- Activity source 테스트가 GREEN이다.
|
||||
- 검증 기록:
|
||||
|
||||
---
|
||||
|
||||
### Phase 6: 통합 검증과 회귀 확인
|
||||
|
||||
- [ ] **Task 6.1: FanTalk 단위 테스트 실행**
|
||||
- 실행:
|
||||
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.*"`
|
||||
- 기대 결과:
|
||||
- FanTalk mapper/ViewModel/pagination/action/layout 테스트가 모두 PASS한다.
|
||||
- 검증 기록:
|
||||
|
||||
- [ ] **Task 6.2: Creator Channel 관련 회귀 테스트 실행**
|
||||
- 실행:
|
||||
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*FanTalk*"`
|
||||
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*"`
|
||||
- 기대 결과:
|
||||
- FanTalk 연결로 인해 Home/Live/Audio/Series/Community 기존 테스트가 깨지지 않는다.
|
||||
- 검증 기록:
|
||||
|
||||
- [ ] **Task 6.3: Resource/Kotlin/Lint 검증 실행**
|
||||
- 실행:
|
||||
- `./gradlew :app:mergeDebugResources`
|
||||
- `./gradlew :app:compileDebugKotlin`
|
||||
- `./gradlew :app:ktlintCheck`
|
||||
- `git diff --check`
|
||||
- 기대 결과:
|
||||
- resource merge, Kotlin compile, ktlint, whitespace check가 모두 PASS한다.
|
||||
- 검증 기록:
|
||||
|
||||
- [ ] **Task 6.4: 수동 화면 검증**
|
||||
- 확인:
|
||||
- content 상태에서 Sort-bar에는 `전체`와 count만 보이고 정렬 UI가 없다.
|
||||
- 내가 쓴 글에는 `ic_new_more`가 보이고 popup에 `수정하기`, `삭제하기`가 보인다.
|
||||
- 내 채널의 타인 글에는 `ic_new_more`가 보이고 popup에 `삭제하기`만 보인다.
|
||||
- 타인 채널의 타인 글에는 `신고`만 보인다.
|
||||
- `수정하기`를 눌러도 화면 이동/API 호출이 없다.
|
||||
- 삭제 confirm을 취소하면 API 호출이 없다.
|
||||
- 삭제 confirm을 확인하면 삭제 API 호출 후 목록이 갱신된다.
|
||||
- 신고 reason 미선택 시 `screen_user_profile_fantalk_report_reason_required` toast가 표시된다.
|
||||
- 신고 reason 선택 후 report API가 호출된다.
|
||||
- `creatorReplies`가 여러 개 내려와도 첫 번째 1개만 표시된다.
|
||||
- empty 상태에서 Sort-bar와 우측 하단 floating 버튼이 숨겨지고 중앙 empty 문구와 `응원 남기기` button이 보인다.
|
||||
- content 상태에서 우측 하단 floating 버튼이 보이지만 클릭해도 화면 이동/API 호출이 없다.
|
||||
- 목록 하단 스크롤 시 `hasNext == true`일 때만 다음 page가 로딩된다.
|
||||
- 검증 기록:
|
||||
|
||||
---
|
||||
|
||||
## Verification Log
|
||||
|
||||
- 문서 작성 시점에는 구현을 수행하지 않았으므로 빌드/테스트 검증 기록이 없다.
|
||||
317
docs/20260622_FanTalk_탭/prd.md
Normal file
317
docs/20260622_FanTalk_탭/prd.md
Normal file
@@ -0,0 +1,317 @@
|
||||
# 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
|
||||
```kotlin
|
||||
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 문구는 화면 중앙에 16sp regular, `gray/500` 계열, center 정렬로 표시한다.
|
||||
- empty 문구 한국어는 `아직 응원이 없습니다.\n처음으로 크리에이터를 응원해 보세요!`이다.
|
||||
- empty 문구 영어는 `No cheers yet.\nBe the first to cheer for the creator!`이다.
|
||||
- empty 문구 일본어는 `まだ応援がありません。\n最初にクリエイターを応援してみましょう!`이다.
|
||||
- empty 문구는 한국어/영어/일본어 다국어 문자열 리소스로 관리한다.
|
||||
- empty 상태에서는 Figma `290:9000`처럼 문구 아래에 `응원 남기기` capsule button을 표시한다.
|
||||
- empty 상태의 `응원 남기기` button은 `gray/800` 배경, 100dp 수준 radius, 20dp icon, 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 동작을 유지한다.
|
||||
- `FanTalk` main 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 상태는 Figma `290:9000`처럼 tab-bar 아래 black background 중앙에 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개만 표시한다.
|
||||
Reference in New Issue
Block a user