Files
sodalive-android/docs/20260709_FanTalk_상세_답글쓰기/plan-task.md

37 KiB

FanTalk 상세 / 답글쓰기 구현 계획/TASK

For agentic workers: 각 단계는 체크박스(- [ ])로 추적하고, 완료 즉시 - [x]로 갱신한다. 구현 범위 변경이 생기면 이 문서를 먼저 수정한 뒤 코드에 반영한다.

Goal: 본인 채널의 FanTalk item을 터치하면 커뮤니티 답글 화면과 동일한 UI의 FanTalk 상세 화면으로 진입하고, 기존 FanTalk 답글 쓰기 API로 크리에이터 답글을 작성한다.

Architecture: 상세 화면은 kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail 하위에 신규 Activity/ViewModel로 둔다. FanTalk는 별도 상세 조회 API가 없으므로 item 터치 시 목록의 FanTalk data model을 통째로 상세 화면에 전달한다. 답글이 없으면 CreatorChannelRepository.writeFanTalkReply(...) wrapper가 기존 ExplorerRepository.writeCheers(parentCheersId = fanTalkId, ...)를 호출하고 ApiResponse<CreatorChannelFanTalkReplyResponse>의 반환 data를 현재 상세 답글로 즉시 바인딩한다. 답글이 있으면 대체 안내 모달 확인 후 modifyCheers wrapper로 기존 답글을 수정한다.

Tech Stack: Kotlin, Android XML Views, ViewBinding, Activity Result API, RxJava3, Koin, JUnit4/Robolectric.


전제와 성공 기준

  • PRD: docs/20260709_FanTalk_상세_답글쓰기/prd.md
  • 기존 FanTalk 탭 문서:
    • docs/20260622_FanTalk_탭/prd.md
    • docs/20260622_FanTalk_탭/plan-task.md
  • 기존 FanTalk 글 입력 문서:
    • docs/20260708_FanTalk_글_입력/prd.md
    • docs/20260708_FanTalk_글_입력/plan-task.md
  • UI 참조 구현:
    • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/reply/CreatorChannelCommunityReplyActivity.kt
    • app/src/main/res/layout/activity_creator_channel_community_reply.xml
    • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/reply/CreatorChannelCommunityReplyViewModel.kt
    • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/reply/ui/CreatorChannelCommunityReplyAdapter.kt
  • 기존 FanTalk 목록 구현:
    • 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/fantalk/ui/CreatorChannelFanTalkAdapter.kt
    • app/src/main/res/layout/item_creator_channel_fantalk.xml
  • 기존 FanTalk API wrapper:
    • CreatorChannelRepository.getFanTalks(...)
    • CreatorChannelRepository.writeFanTalk(creatorId, content, token)
    • CreatorChannelRepository.deleteFanTalk(fanTalkId, token)
  • 기존 레거시 답글 쓰기 API:
    • app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerRepository.kt
    • writeCheers(parentCheersId, creatorId, content, token)
  • 본인 채널 gate:
    • CreatorChannelActivity.currentHeader?.isOwner == true
  • FanTalk 상세 데이터 전달:
    • 별도 상세 조회 API를 호출하지 않는다.
    • FanTalk item data model을 통째로 전달한다.
    • 개별 primitive extra 분해 전달은 사용하지 않는다.
  • FanTalk 상세 답글 정책:
    • 크리에이터 답글은 1개만 가능하다.
    • 별도 답글 pagination/API는 사용하지 않는다.
    • 기존 답글이 있는 상태에서 send하면 안내 모달을 표시하고, 확인 시 수정 API로 기존 답글을 신규 입력값으로 대체한다.
  • 구현 완료 후 최소 다음 명령을 실행한다.
    • ./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:mergeDebugResources
    • ./gradlew :app:compileDebugKotlin
    • ./gradlew :app:ktlintCheck
    • git diff --check

Figma 참조

  • 답글 없음: https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=669-40462&m=dev
  • 답글 있음: https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=669-40468&m=dev
  • Figma MCP 확인 기록:
    • 669:40462: 뒤로가기만 있는 title-bar, 부모 FanTalk 원문, 하단 input bar, disabled send button, 답글 card 없음.
    • 669:40468: 부모 FanTalk 원문, 답글 card, 답글 더보기 popup(수정하기, 삭제하기), 하단 input bar.

V2 재사용 후보 사전 조사

  • CreatorChannelCommunityReplyActivity
    • 후보 이유: 요청 UI와 동일한 화면 구조, input bar, send button 상태, 키보드 제어, 수정 모드, V2ModalDialog 삭제 확인 흐름이 이미 구현되어 있다.
    • 사용 방침: Activity 자체를 직접 재사용하지 않고 FanTalk 전용 Activity를 신규 생성하되, 구조와 동작을 기준으로 맞춘다. 커뮤니티 전용 CreatorChannelCommunityCommentUiModel 의존을 FanTalk로 끌고 오지 않는다.
  • activity_creator_channel_community_reply.xml
    • 후보 이유: Figma 669:40462/669:40468와 동일한 title-bar/input/list 구조다.
    • 사용 방침: FanTalk 전용 layout으로 복제/축소하거나 include 가능한 공통 input bar를 분리한다. 단일 사용처라면 과도한 공통화는 피한다.
  • CreatorChannelCommunityMorePopup + view_creator_channel_community_more_popup.xml
    • 후보 이유: 수정하기, 삭제하기, @font/regular, bg_creator_channel_sort_popup이 Figma popup과 맞는다.
    • 사용 방침: FanTalk popup이 이미 있으므로 CreatorChannelFanTalkMorePopup에 edit callback/font 보강을 우선 검토한다. 커뮤니티 popup 직접 재사용은 model/API 차이 때문에 신중히 판단한다.
  • CreatorChannelFanTalkAdapter + item_creator_channel_fantalk.xml
    • 후보 이유: 부모 FanTalk와 최신 답글 card의 프로필/닉네임/시간/본문 UI가 이미 있다.
    • 사용 방침: 상세 화면의 부모 item/답글 item binding 로직 참고 또는 adapter callback 확장에 사용한다.
  • CreatorChannelFanTalkMorePopup
    • 후보 이유: FanTalk item의 owner menu로 이미 사용 중이다.
    • 현재 한계: edit click이 dismiss만 하고 실제 callback이 없다.
    • 사용 방침: 상세 수정이 범위에 포함되면 onEditClick을 추가하고 source/layout 테스트를 보강한다.
  • V2ModalDialog
    • 후보 이유: v2 내부 삭제 확인 dialog 표준이다.
    • 사용 방침: FanTalk 답글 삭제 확인에 재사용한다.
  • UtcRelativeTimeTextFormatter
    • 후보 이유: FanTalk/커뮤니티 모두 상대시간 표시를 사용한다.
    • 사용 방침: intent로 원본 createdAtUtc를 넘길 수 있으면 상세에서도 동일 formatter로 포맷한다.
  • bg_creator_channel_community_input_field, bg_creator_channel_community_send_enabled, bg_creator_channel_community_send_disabled
    • 후보 이유: 커뮤니티 답글 input/send button 상태와 동일하다.
    • 사용 방침: 디자인 토큰이 동일하면 재사용한다. resource naming이 커뮤니티에 묶이는 점이 문제이면 FanTalk 전용 alias drawable을 추가한다.
  • ic_new_arrow_up_white, ic_new_arrow_up_gray
    • 후보 이유: 현재 저장소에 존재하며 커뮤니티 답글 화면에서 사용 중이다.
    • 사용 방침: FanTalk 상세 send button 활성/비활성 icon으로 사용한다.
  • v2/widget/*
    • 검토 결과: AudioContentCardView, SeriesContentCardView, ranking/feed/live thumbnail 계열은 FanTalk 상세/답글쓰기 UI와 직접 맞지 않는다. 이번 범위에서는 재사용 후보에서 제외한다.

API / 데이터 계약

  • 목록 조회:
    • 기존 GET /api/v2/creator-channels/{creatorId}/fan-talks?page=0&size=20
    • 응답 모델: CreatorChannelFanTalkTabResponse, CreatorChannelFanTalkResponse, CreatorChannelFanTalkReplyResponse
  • 상세 초기 데이터:
    • 신규 상세 조회 API를 가정하지 않는다.
    • FanTalk item data model을 통째로 전달한다.
    • CreatorChannelFanTalkUiModel 또는 FanTalk 상세 전용 payload model을 Parcelable로 전달하는 방식을 우선한다.
    • 개별 primitive extra로 필드를 나누어 전달하지 않는다.
  • 답글 작성:
    • 기존 답글 작성: CreatorChannelRepository.writeFanTalkReply(fanTalkId, creatorId, content, token)
    • 내부 호출: ExplorerRepository.writeCheers(parentCheersId = fanTalkId, creatorId = creatorId, content = content, token = token)
    • 응답: ApiResponse<CreatorChannelFanTalkReplyResponse>data를 상세 화면 답글로 즉시 바인딩한다.
    • success=true, data=null도 성공으로 처리하되, 이 경우 임의 답글 UI model을 만들지 않는다.
  • 답글 대체/수정/삭제:
    • 답글은 1개만 가능하므로 별도 pagination/API를 사용하지 않는다.
    • 기존 답글이 없을 때 send: writeFanTalkReply(...).
    • 기존 답글이 있을 때 send: 대체 안내 모달 확인 후 modifyFanTalkReply(...).
    • 수정: ExplorerRepository.modifyCheers(PutModifyCheersRequest(cheersId = replyFanTalkId, content = ...), token)
    • 삭제: ExplorerRepository.modifyCheers(PutModifyCheersRequest(cheersId = replyFanTalkId, isActive = false), token)
  • 대체 안내 모달:
    • title: 답글 수정
    • desc: 기존 답글이 신규 답글로 대체됩니다.\n수정하시겠어요?
    • cancel button: 취소
    • confirm button: 확인

파일 구조 (예정)

  • 생성: app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailActivity.kt
  • 생성: app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailViewModel.kt
  • 생성 가능: app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailUiModels.kt
  • 생성: app/src/main/res/layout/activity_creator_channel_fantalk_detail.xml
  • 생성 가능: app/src/main/res/layout/item_creator_channel_fantalk_detail_reply.xml
  • 수정: app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/ui/CreatorChannelFanTalkAdapter.kt
  • 수정: 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/CreatorChannelActivity.kt
  • 수정: app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelRepository.kt
  • 수정 가능: app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/ui/CreatorChannelFanTalkMorePopup.kt
  • 수정 가능: app/src/main/res/layout/view_creator_channel_fantalk_more_popup.xml
  • 수정: app/src/main/AndroidManifest.xml
  • 수정: app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt
  • 수정: 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/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailViewModelTest.kt
    • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailUiContractTest.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/CreatorChannelActivitySourceTest.kt

Phase 1: 기존 구조 확인과 상세 진입 계약

  • Task 1.1: FanTalk item 터치 가능 지점 확인

    • 확인:
      • CreatorChannelFanTalkAdapter.kt
      • item_creator_channel_fantalk.xml
    • 작업:
      • item root 또는 본문 영역에 click callback을 추가할 수 있는지 확인한다.
      • report/more button click과 item click이 충돌하지 않게 event 경계를 정한다.
    • 검증:
      • source test로 adapter constructor에 detail click callback이 있는지 확인한다.
  • Task 1.2: 본인 채널 gate 정의

    • 확인:
      • CreatorChannelActivity.kt
      • currentHeader?.isOwner
    • 작업:
      • currentHeader?.isOwner == true일 때만 상세 진입을 허용한다.
      • 본인 채널이 아닌 경우 adapter callback 연결을 막거나 Activity callback에서 return한다.
    • 검증:
      • source test로 owner gate와 showLoginActivity() 정책을 확인한다.

Phase 2: API wrapper와 ViewModel 계약

  • Task 2.1: FanTalk 답글 쓰기 wrapper 추가

    • 수정:
      • CreatorChannelRepository.kt
    • 작업:
      • writeFanTalkReply(fanTalkId: Long, creatorId: Long, content: String, token: String)를 추가한다.
      • 내부에서 기존 explorerRepository.writeCheers(parentCheersId = fanTalkId, creatorId = creatorId, content = content, token = token)를 호출하고 ApiResponse<CreatorChannelFanTalkReplyResponse>를 반환한다.
      • modifyFanTalkReply(replyFanTalkId: Long, content: String, token: String)를 추가한다.
      • 내부에서 explorerRepository.modifyCheers(PutModifyCheersRequest(cheersId = replyFanTalkId, content = content), token)를 호출한다.
    • 검증:
      • repository source test 또는 ViewModel 테스트로 parentCheersId = fanTalkIdcheersId = replyFanTalkId 계약을 확인한다.
  • Task 2.2: 상세 ViewModel RED 테스트 작성

    • 생성:
      • CreatorChannelFanTalkDetailViewModelTest.kt
    • 테스트 케이스:
      • 통째로 전달받은 FanTalk data model 기반으로 부모 FanTalk와 기존 답글 1개를 Content state로 노출한다.
      • 입력값 없음/공백만 입력 시 send disabled 상태다.
      • 입력값 있음이면 send enabled 상태다.
      • 기존 답글이 없으면 submit 시 writeFanTalkReply(fanTalkId, creatorId, trimmedContent, token)를 호출한다.
      • 기존 답글이 있으면 submit 시 바로 API를 호출하지 않고 대체 안내 모달 event를 emit한다.
      • 대체 안내 모달 확인 시 modifyFanTalkReply(replyFanTalkId, trimmedContent, token)를 호출한다.
      • 대체 안내 모달 취소 시 API를 호출하지 않고 입력값을 유지한다.
      • submit 중 중복 전송을 막는다.
      • 성공 시 입력값을 비우고 완료 event를 emit한다.
      • 실패 시 입력값을 유지하고 toast event를 emit한다.
    • 검증:
      • 구현 전 실패(RED)를 확인한다.
  • Task 2.3: 상세 ViewModel 구현

    • 생성:
      • CreatorChannelFanTalkDetailViewModel.kt
      • CreatorChannelFanTalkDetailUiModels.kt
    • 작업:
      • 부모 FanTalk, 답글 1개, input, sending, replacement modal event, toast/event state를 관리한다.
      • 작성/수정 성공 시 목록 refresh가 필요한 event를 emit한다.
    • 검증:
      • CreatorChannelFanTalkDetailViewModelTest PASS.

Phase 3: 상세 Activity/layout 구현

  • Task 3.1: FanTalk 상세 layout 추가

    • 생성:
      • activity_creator_channel_fantalk_detail.xml
      • 필요 시 item_creator_channel_fantalk_detail_reply.xml
    • 작업:
      • activity_creator_channel_community_reply.xml과 동일한 title-bar/input/list 구조를 만든다.
      • 답글 없음/있음 Figma 상태를 모두 표현한다.
      • 답글 card는 최대 1개만 표시한다.
      • input field는 maxLines=3, vertical scroll, 하단 고정을 적용한다.
      • disabled send: gray_900 + ic_new_arrow_up_gray.
      • enabled send: soda_400 + ic_new_arrow_up_white.
      • 기존 답글 대체 안내는 V2ModalDialog로 표시한다.
    • 검증:
      • ./gradlew :app:mergeDebugResources
      • layout inflate/source 계약 테스트.
  • Task 3.2: FanTalk 상세 Activity 구현

    • 생성:
      • CreatorChannelFanTalkDetailActivity.kt
    • 수정:
      • AndroidManifest.xml
      • AppDI.kt
    • 작업:
      • newIntent(context, creatorId, item) 성격의 factory를 제공하고 FanTalk data model을 통째로 전달한다.
      • 개별 primitive extra 분해 전달은 사용하지 않는다.
      • 유효하지 않은 creatorId/fanTalkId면 toast 후 종료한다.
      • 뒤로가기, input text change, send click, keyboard show/hide, event observe를 연결한다.
      • 기존 답글이 있는 상태의 send event에서 대체 안내 모달을 표시하고, 확인 시 ViewModel의 수정 confirm action을 호출한다.
      • 작성 성공 시 setResult(Activity.RESULT_OK) 후 현재 화면 state를 갱신하거나 목록 refresh를 요청한다.
    • 검증:
      • ./gradlew :app:compileDebugKotlin
      • source test로 manifest/AppDI/newIntent 계약 확인.

Phase 4: FanTalk 목록 진입점 연결

  • Task 4.1: Adapter/Fragment callback 추가

    • 수정:
      • CreatorChannelFanTalkAdapter.kt
      • CreatorChannelFanTalkFragment.kt
    • 작업:
      • item click callback을 adapter에 추가한다.
      • Fragment Host에 onCreatorChannelFanTalkItemClicked(item) 성격의 callback을 추가한다.
      • 본인 채널이 아닌 경우 item click callback을 연결하지 않거나 no-op 처리한다.
    • 검증:
      • source test로 report/more click과 item click callback이 분리되어 있는지 확인한다.
  • Task 4.2: ActivityResult 기반 상세 화면 연결

    • 수정:
      • CreatorChannelActivity.kt
    • 작업:
      • FanTalk 상세 ActivityResult launcher를 추가한다.
      • 상세에서 답글 작성/수정/삭제 성공 result를 받으면 refreshCreatorChannelFanTalk()homeActionDelegate?.refreshHome()를 호출한다.
      • 로그인 토큰이 없으면 기존 showLoginActivity()를 호출하고 상세를 열지 않는다.
    • 검증:
      • CreatorChannelActivitySourceTest에 owner gate, launcher, refresh 계약 추가.

Phase 5: 기존 답글 대체/수정/삭제 흐름

  • Task 5.1: 기존 답글 대체 모달 구현

    • 수정:
      • CreatorChannelFanTalkDetailActivity.kt
      • CreatorChannelFanTalkDetailViewModel.kt
      • strings.xml, values-en/strings.xml, values-ja/strings.xml
    • 작업:
      • 기존 답글이 있는 상태에서 send하면 V2ModalDialog를 표시한다.
      • 모달 desc는 기존 답글이 신규 답글로 대체된다는 내용을 담는다.
      • 확인 시 modifyFanTalkReply(replyFanTalkId, trimmedContent, token)를 호출한다.
      • 취소 시 API를 호출하지 않고 입력값을 유지한다.
    • 검증:
      • ViewModel replacement event/confirm/cancel 테스트.
      • Activity source 계약 테스트.
  • Task 5.2: 더보기 popup edit/delete 동작 정리

    • 수정 가능:
      • CreatorChannelFanTalkMorePopup.kt
      • view_creator_channel_fantalk_more_popup.xml
    • 작업:
      • 수정하기 click callback을 실제로 연결한다.
      • font는 커뮤니티 popup처럼 @font/regular 적용을 검토한다.
      • 삭제는 V2ModalDialog 확인 후 modifyCheers(isActive = false) wrapper로 연결한다.
    • 검증:
      • popup source/layout 계약 테스트.
  • Task 5.3: 더보기 수정 모드 구현 여부 확정

    • 작업:
      • 커뮤니티 답글 화면과 동일하게 하단 input 수정 모드를 사용할지 확정한다.
      • 확정 시 ViewModel edit target, X 취소 button, 변경 없음 toast를 구현 계획에 반영한다.
    • 검증:
      • 확정된 범위에 맞춰 ViewModel 테스트를 추가한다.

Phase 6: 통합 검증

  • Task 6.1: targeted 테스트 실행

    • 실행:
      • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.*"
      • ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*FanTalk*"
    • 기대 결과:
      • FanTalk 상세 ViewModel/source/layout 계약 테스트가 PASS한다.
  • Task 6.2: build/resource/lint 검증

    • 실행:
      • ./gradlew :app:mergeDebugResources
      • ./gradlew :app:compileDebugKotlin
      • ./gradlew :app:ktlintCheck
      • git diff --check
    • 기대 결과:
      • resource merge, Kotlin compile, ktlint, whitespace 검증이 모두 통과한다.
  • Task 6.3: 수동 확인

    • 확인:
      • 본인 채널에서 FanTalk item 터치 시 상세 진입.
      • 타인 채널에서 FanTalk item 터치 시 상세 미진입.
      • 답글 없음 UI가 Figma 669:40462와 주요 구조 일치.
      • 답글 있음 UI가 Figma 669:40468와 주요 구조 일치.
      • 입력값 없음 send disabled: gray_900 + ic_new_arrow_up_gray.
      • 입력값 있음 send enabled: soda_400 + ic_new_arrow_up_white.
      • 기존 답글이 없으면 send 시 답글 작성 API 호출.
      • 기존 답글이 있으면 send 시 대체 안내 모달 표시 후 확인 시 답글 수정 API 호출.
      • 답글 작성/수정 성공 후 FanTalk 목록과 홈 요약 refresh.

Verification Log

  • 2026-07-09: 문서 작성만 수행했다. sample-prd.md, docs/agent-guides/work-plan-docs.md, 기존 FanTalk 글 입력 문서, 기존 커뮤니티 게시물 상세 / 댓글 / 답글 문서, CreatorChannelCommunityReplyActivity, activity_creator_channel_community_reply.xml, FanTalk Fragment/Adapter/ViewModel/API wrapper를 확인했다.

  • 2026-07-09: Figma MCP로 669:40462, 669:40468 design context와 screenshot을 확인했다. 답글 없음/있음 구조, 하단 input bar, popup 문구를 문서에 반영했다.

  • 2026-07-09: 이번 요청은 “문서만 작성”이므로 Gradle build/test/ktlint는 실행하지 않았다.

  • 2026-07-09: 사용자 보완 요청을 반영했다. FanTalk 상세 조회 API 없음, item data model 통째 전달, 답글 1개 제한, 기존 답글 대체 안내 모달, 확인 시 답글 수정 API 호출, ic_new_arrow_up_* 리소스 사용, 답글 pagination/API 불필요 정책을 PRD와 계획/TASK에 반영했다. 문서 보완만 수행했으므로 Gradle build/test/ktlint는 실행하지 않았다.

  • 2026-07-09: 비-UI 기반 TDD 작업을 수행했다. RED 단계에서 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail.CreatorChannelFanTalkDetailViewModelTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkFragmentLayoutTest"를 실행했고, CreatorChannelFanTalkDetailViewModel, CreatorChannelFanTalkDetailPayload, writeFanTalkReply/modifyFanTalkReply wrapper 등 누락 계약으로 compileDebugUnitTestKotlin이 실패하는 것을 확인했다.

  • 2026-07-09: GREEN 단계에서 CreatorChannelRepository FanTalk reply wrapper, CreatorChannelFanTalkDetailPayload/reply payload, CreatorChannelFanTalkDetailViewModel, FanTalk Adapter item click callback, Fragment Host callback을 구현했다. @Parcelize payload의 factory는 Parcelize 대상 class 밖 top-level 함수로 두어 컴파일 안정성을 확인했다.

  • 2026-07-09: 검증으로 ./gradlew --no-daemon -Dkotlin.compiler.execution.strategy=in-process :app:compileDebugKotlin, ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail.CreatorChannelFanTalkDetailViewModelTest", ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.*FanTalk*"를 실행했고 모두 성공했다. lsp_diagnostics는 환경에 kotlin-lsp가 설치되어 있지 않아 실행할 수 없었다.

  • 2026-07-09: 상세 Activity/layout 및 목록 진입 연결을 구현했다. CreatorChannelFanTalkDetailActivity, activity_creator_channel_fantalk_detail.xml, Manifest/AppDI 등록, CreatorChannelActivity owner gate와 ActivityResult refresh 계약을 추가했다. 당시 writeCheers 응답은 ApiResponse<Any>라 신규 답글 id를 받을 수 없어, 작성 성공 직후 로컬 임시 답글(fanTalkId=0)에는 수정/삭제 더보기 메뉴를 숨기도록 보정했다. 이후 후속 요청으로 writeCheers/modifyCheers 응답은 ApiResponse<CreatorChannelFanTalkReplyResponse>로 변경되었다.

  • 2026-07-09: 최종 검증으로 ./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.CreatorChannelActivitySourceTest", ./gradlew :app:mergeDebugResources, ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck, git diff --check를 실행했다. 모두 성공했고, 병렬 Gradle 실행 중 Kotlin daemon incremental cache 충돌 로그가 있어 이후 검증은 순차 실행으로 재확인했다. 실제 기기/에뮬레이터 수동 조작은 이 환경에서 수행하지 못해 source/layout/unit/resource/compile 검증으로 대체했다.

  • 2026-07-09: UI/통합 단계 TDD를 수행했다. RED 단계에서 CreatorChannelFanTalkDetailUiContractTestCreatorChannelActivitySourceTest 계약을 먼저 추가하고 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail.*" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"를 실행했다. 최초 테스트 문자열 escaping 오류를 수정한 뒤 production 변경 전 재실행에서 신규 상세 Activity 파일 없음, 상세 layout 파일 없음, Manifest/DI/ViewModel 계약 누락으로 3개 테스트 실패를 확인했다.

  • 2026-07-09: GREEN 단계에서 CreatorChannelFanTalkDetailActivity, activity_creator_channel_fantalk_detail.xml, Manifest activity 등록, Koin ViewModel 등록, CreatorChannelFanTalkDetailViewModel.consumeReplyChangedEvent()/cancelReplyEdit(), CreatorChannelActivity의 owner-gated detail ActivityResult launcher를 구현했다. 상세 화면은 기존 커뮤니티 답글 input/send token과 FanTalk reply background를 재사용했고 별도 FanTalk 상세 API는 추가하지 않았다.

  • 2026-07-09: 검증으로 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail.*", ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*FanTalk*", ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest", ./gradlew :app:mergeDebugResources, ./gradlew :app:compileDebugKotlin를 실행했고 모두 성공했다. lsp_diagnosticskotlin-lsp 미설치 및 XML LSP 미구성으로 실행할 수 없었다.

  • 2026-07-09: 리뷰 게이트에서 지적된 차단 이슈 2건을 수정했다. FanTalk 상세 원글 more icon은 원글 수정/삭제를 이번 범위에서 지원하지 않으므로 표시하지 않게 변경했고, 답글 작성 직후 API 응답에 실제 답글 id가 없어 로컬 임시 답글(fanTalkId=0) 상태에서 다시 submit할 때 수정 API가 조용히 무시되는 문제는 오류 toast를 표시하고 대체 모달/수정 API로 진행하지 않도록 guard를 추가했다. 회귀 테스트 작성 직후 임시 답글은 대체 submit 시 수정 API를 호출하지 않고 오류를 표시한다를 추가했다.

  • 2026-07-09: 차단 이슈 수정 후 ./gradlew --no-daemon -Dkotlin.compiler.execution.strategy=in-process :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail.CreatorChannelFanTalkDetailViewModelTest", ./gradlew --no-daemon -Dkotlin.compiler.execution.strategy=in-process :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.*", ./gradlew --no-daemon -Dkotlin.compiler.execution.strategy=in-process :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*FanTalk*", ./gradlew --no-daemon -Dkotlin.compiler.execution.strategy=in-process :app:mergeDebugResources :app:compileDebugKotlin :app:ktlintCheck, git diff --check를 재실행했고 모두 성공했다. 첫 ViewModel 테스트 실행에서는 Kotlin daemon incremental cache 경고 후 fallback compile로 성공했으며, 이후 순차 재검증은 성공했다. lsp_diagnosticskotlin-lsp 미설치로 실행할 수 없었다.

  • 2026-07-09: Figma 669:40470 후속 확인에 따라 FanTalk 상세 답글 card의 small profile row nickname/date 타이포그래피를 Pretendard Variable Regular 12px 대응 토큰인 @style/Typography.Caption3로 맞춘다. 부모 FanTalk nickname/time은 변경하지 않고, CreatorChannelFanTalkDetailUiContractTest에서 tv_creator_channel_fantalk_detail_reply_nicknametv_creator_channel_fantalk_detail_reply_timeTypography.Caption3를 사용하는 계약을 검증한다.

  • 2026-07-09: 답글 card nickname/date 타이포그래피 후속 수정 검증으로 ./gradlew --no-daemon -Dkotlin.compiler.execution.strategy=in-process :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail.CreatorChannelFanTalkDetailUiContractTest" :app:mergeDebugResourcesgit diff --check를 실행했고 모두 성공했다. Gradle 실행 중 Kotlin daemon incremental cache 충돌 로그가 있었지만 fallback compile 후 BUILD SUCCESSFUL을 확인했다. lsp_diagnosticskotlin-lsp 미설치, XML/Markdown LSP 미구성으로 실행할 수 없었다.

  • 2026-07-09: 답글 card 시간 위치 후속 수정으로 닉네임 TextView의 layout_weight를 제거하고 wrap_content 폭을 사용하게 변경했으며, 시간 TextView의 layout_marginStart@dimen/spacing_4로 조정해 닉네임 오른쪽 4dp 간격에 붙도록 했다. CreatorChannelFanTalkDetailUiContractTest에 닉네임 폭과 시간 간격 계약을 추가했다.

  • 2026-07-09: 답글 card 시간 위치 후속 수정 검증으로 ./gradlew --no-daemon -Dkotlin.compiler.execution.strategy=in-process :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail.CreatorChannelFanTalkDetailUiContractTest" :app:mergeDebugResourcesgit diff --check를 실행했고 모두 성공했다. Gradle 실행 중 Kotlin daemon incremental cache 충돌 로그가 있었지만 fallback compile 후 BUILD SUCCESSFUL을 확인했다.

  • 2026-07-09: 답글 card header에서 닉네임과 시간은 4dp 간격으로 붙이고, 더보기 button은 기존처럼 오른쪽 끝에 유지하기 위해 시간과 더보기 사이에 0dp/layout_weight=1 spacer를 추가했다. CreatorChannelFanTalkDetailUiContractTest에 spacer 계약을 추가했다.

  • 2026-07-09: 답글 card 더보기 button 오른쪽 정렬 후속 수정 검증으로 ./gradlew --no-daemon -Dkotlin.compiler.execution.strategy=in-process :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail.CreatorChannelFanTalkDetailUiContractTest" :app:mergeDebugResourcesgit diff --check를 실행했고 모두 성공했다. Gradle 실행 중 Kotlin daemon incremental cache 충돌 로그가 있었지만 fallback compile 후 BUILD SUCCESSFUL을 확인했다.

  • 2026-07-09: FanTalk 상세 답글 작성 API 응답이 ApiResponse<CreatorChannelFanTalkReplyResponse>로 변경된 후속 요청을 반영한다. 기존 로컬 임시 답글(fanTalkId=0) 생성 대신 서버가 내려준 답글 data를 현재 상세 화면에 즉시 바인딩해 닉네임, 프로필 이미지, 시간을 실제 응답 기준으로 표시하도록 수정하고 검증한다.

  • 2026-07-09: FanTalk 상세 답글 작성 응답 바인딩 후속 수정 검증으로 ./gradlew --no-daemon -Dkotlin.compiler.execution.strategy=in-process :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail.CreatorChannelFanTalkDetailViewModelTest", ./gradlew --no-daemon -Dkotlin.compiler.execution.strategy=in-process :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.*", ./gradlew --no-daemon -Dkotlin.compiler.execution.strategy=in-process :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*FanTalk*", ./gradlew --no-daemon -Dkotlin.compiler.execution.strategy=in-process :app:mergeDebugResources :app:compileDebugKotlin :app:ktlintCheck, git diff --check를 실행했고 모두 성공했다. 첫 ViewModel 테스트 재실행에서는 Kotlin daemon incremental cache 충돌 후 fallback compile로 BUILD SUCCESSFUL을 확인했다. lsp_diagnosticskotlin-lsp 미설치로 실행할 수 없었다.

  • 2026-07-09: FanTalk 상세 답글 작성 성공 후 toast만 표시되고 반환 데이터가 바인딩되지 않는 문제를 확인했다. 원인은 handleWriteReplyResponseresponse.data.creatorReplies.firstOrNull()만 답글로 인정해, write API가 생성된 답글 자체를 data로 내려주거나 creatorReplies 없이 내려주는 성공 응답을 실패로 처리한 것이다. creatorReplies가 없으면 response.data 자체를 답글 UI 모델로 바인딩하도록 보정하고, 외부 응답의 creatorReplies는 nullable로 받아 mapper에서 orEmpty()로 처리한다.

  • 2026-07-09: FanTalk 상세 답글 작성 성공 응답 shape 보정 검증으로 ./gradlew --no-daemon -Dkotlin.compiler.execution.strategy=in-process :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail.CreatorChannelFanTalkDetailViewModelTest", ./gradlew --no-daemon -Dkotlin.compiler.execution.strategy=in-process :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.*", ./gradlew --no-daemon -Dkotlin.compiler.execution.strategy=in-process :app:mergeDebugResources :app:compileDebugKotlin :app:ktlintCheck, git diff --check를 실행했고 모두 성공했다. 첫 ViewModel 테스트 실행에서는 Kotlin daemon incremental cache 충돌 후 fallback compile로 BUILD SUCCESSFUL을 확인했고, lsp_diagnosticskotlin-lsp 미설치로 실행할 수 없었다.

  • 2026-07-09: 코드 리뷰 후속 수정으로 FanTalk 답글 작성 wrapper를 V2 CreatorChannelApi.writeFanTalkReply가 아니라 요구사항의 기존 ExplorerRepository.writeCheers(parentCheersId = fanTalkId, ...) 경로로 되돌렸다. 이후 사용자 후속 요청에 따라 writeCheers/modifyCheers 반환 타입을 ApiResponse<CreatorChannelFanTalkReplyResponse>로 변경했고, 크리에이터 답글 작성 성공 시 반환된 data를 현재 상세 답글에 즉시 바인딩한다. success=true, data=null이면 성공 처리는 유지하되 임의 답글 데이터는 만들지 않는다.

  • 2026-07-09: 사용자 후속 요청으로 일반 FanTalk 글 작성은 기존처럼 success 기준 처리와 목록 새로고침 흐름을 유지하고, 유저가 쓴 FanTalk에 크리에이터가 답글을 추가하는 상세 화면만 반환 CreatorChannelFanTalkReplyResponse data를 즉시 UI에 반영하도록 수정한다. CreatorChannelFanTalkDetailViewModelTest답글이 없으면 submit 시 반환된 답글 데이터를 현재 state에 바인딩한다가 해당 계약을 검증한다.

  • 2026-07-09: 코드 리뷰 후속 문자열 수정으로 CreatorChannelFanTalkReplacementModalUiModel의 하드코딩 한국어 문구를 string resource id 기반으로 변경했다. title/cancel/confirm은 기존 screen_user_profile_reply_edit, cancel, confirm을 재사용하고, description은 creator_channel_fantalk_reply_replace_descriptionvalues, values-en, values-ja에 추가했다. CreatorChannelFanTalkDetailUiContractTest에 Activity의 getString(modal.*ResId) 사용과 다국어 resource 존재, UI model 내 하드코딩 문구 부재 계약을 추가했다.

  • 2026-07-09: 후속 수정 검증으로 KSP incremental cache 충돌을 피하기 위해 app/build/kspCaches/debug, app/build/kspCaches/debugUnitTest를 삭제한 뒤 순차 실행했다. ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail.CreatorChannelFanTalkDetailViewModelTest"./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail.CreatorChannelFanTalkDetailUiContractTest"가 모두 BUILD SUCCESSFUL로 통과했다. 최초 병렬 테스트 실행은 KSP cache 동시 접근으로 실패했으나 코드 실패가 아니라 검증 방식 문제로 확인했다.

  • 2026-07-09: writeCheers/modifyCheers 반환 타입 변경 후 검증으로 ./gradlew --no-daemon -Dkotlin.compiler.execution.strategy=in-process :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail.CreatorChannelFanTalkDetailViewModelTest", ./gradlew --no-daemon -Dkotlin.compiler.execution.strategy=in-process :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail.CreatorChannelFanTalkDetailUiContractTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.write.CreatorChannelFanTalkWriteViewModelTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkActionTest", ./gradlew --no-daemon -Dkotlin.compiler.execution.strategy=in-process :app:mergeDebugResources :app:compileDebugKotlin, git diff --check를 실행했고 모두 성공했다. 일반 FanTalk 글 작성/삭제 테스트는 반환 data를 사용하지 않고 기존 success 기준 동작이 유지됨을 확인했다.