diff --git a/docs/20260708_FanTalk_글_입력/plan-task.md b/docs/20260708_FanTalk_글_입력/plan-task.md new file mode 100644 index 00000000..a6dfa3bd --- /dev/null +++ b/docs/20260708_FanTalk_글_입력/plan-task.md @@ -0,0 +1,385 @@ +# FanTalk 글 입력 구현 계획/TASK + +> **For agentic workers:** 각 단계는 체크박스(`- [ ]`)로 추적하고, 완료 즉시 `- [x]`로 갱신한다. 구현 범위 변경이 생기면 이 문서를 먼저 수정한 뒤 코드에 반영한다. + +**Goal:** 크리에이터 채널 `FanTalk` 탭에서 고정형 plus button 또는 empty `응원 남기기` 버튼으로 글 입력 화면에 진입하고, 기존 FanTalk 등록 API로 응원글을 등록한다. + +**Architecture:** 기존 `CreatorChannelFanTalkFragment`의 빈 글쓰기 click callback을 Host로 올리고, `CreatorChannelActivity`에서 신규 `CreatorChannelFanTalkWriteActivity`를 ActivityResult로 실행한다. 기존 Fragment 내부 floating button은 Activity overlay fixed button으로 대체해 스크롤과 분리하고, 등록 API는 `CreatorChannelRepository.writeFanTalk()` wrapper가 레거시 `ExplorerRepository.writeCheers()`를 호출한다. + +**Tech Stack:** Kotlin, Android XML Views, ViewBinding, Activity Result API, RxJava3, Koin, JUnit4/Robolectric. + +--- + +## 전제와 성공 기준 + +- PRD: `docs/20260708_FanTalk_글_입력/prd.md` +- 기존 FanTalk 탭 문서: + - `docs/20260622_FanTalk_탭/prd.md` + - `docs/20260622_FanTalk_탭/plan-task.md` +- 기존 FanTalk 탭 구현: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragment.kt` + - `app/src/main/res/layout/fragment_creator_channel_fantalk.xml` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt` +- 기존 FanTalk 등록 API: + - `app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerRepository.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerApi.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/cheers/PostWriteCheersRequest.kt` +- 기존 v2 repository: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelRepository.kt` +- fixed plus button은 `CreatorChannelActivity` layout overlay에 배치한다. +- fixed plus button icon은 `ic_plus_no_bg`를 사용하고 white tint를 적용한다. +- empty 상태의 `응원 남기기` 버튼은 Fragment 내부에 유지하고 글 입력 화면으로 연결한다. +- 글 등록 성공 후 `refreshCreatorChannelFanTalk()`와 `homeActionDelegate?.refreshHome()`를 호출한다. +- 구현 완료 판단 시 Gradle 검증뿐 아니라 Figma `290:9235`, `290:9282`, `290:9290`, `308:19046`의 주요 layout 구조, 버튼 상태, popup 문구가 실제 구현 화면에 반영되었는지 확인한다. +- 자동 검증에서도 Figma에서 확정된 UI 계약을 확인한다. + - 입력값 없음: 보내기 버튼 disabled + - 입력값 있음: 보내기 버튼 enabled + - 입력 종료 popup: title/desc/cancel/confirm 문구 일치 + - fixed plus button: `ic_plus_no_bg`, white tint, Activity overlay 고정 배치 +- Figma 대조 결과는 문서 최하단 `Verification Log`에 한국어로 누적 기록한다. +- 구현 완료 후 최소 다음 명령을 실행한다. + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.write.*"` + - `./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 참조 + +- 입력 값 없음 + keyboard 있음: `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=290-9235&m=dev` +- 입력 값 있음 + keyboard 있음: `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=290-9282&m=dev` +- 입력 값 있음 + keyboard 없음: `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=290-9290&m=dev` +- 입력 종료 modal: `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=308-19046&m=dev` +- 입력 종료 modal 문구: + - title: `입력 종료` + - desc: `입력을 종료할까요?\n지금 나가면 입력한 내용이 저장되지 않아요.` + - cancel button: `계속 작성` + - confirm button: `종료` + +--- + +## 파일 구조 + +- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelRepository.kt` + - `writeFanTalk(creatorId, content, token)` wrapper를 추가한다. +- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragment.kt` + - 기존 빈 글쓰기 click listener를 Host callback으로 교체한다. + - content 상태에서 Activity fixed plus button 표시 여부를 Host에 알린다. + - Fragment 내부 `btn_creator_channel_fantalk_write`는 더 이상 floating 진입점으로 사용하지 않는다. +- 수정: `app/src/main/res/layout/fragment_creator_channel_fantalk.xml` + - Fragment 내부 `btn_creator_channel_fantalk_write`를 제거하거나 항상 `gone` 처리한다. + - empty `응원 남기기` 버튼은 유지한다. +- 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt` + - `CreatorChannelFanTalkWriteActivity` ActivityResult launcher를 추가한다. + - FanTalk fixed plus button 표시/숨김과 click 처리를 담당한다. + - 등록 성공 시 FanTalk 목록과 홈 요약을 refresh한다. +- 수정: `app/src/main/res/layout/activity_creator_channel.xml` + - FanTalk fixed plus button을 root overlay 우측 하단에 추가한다. + - icon은 `@drawable/ic_plus_no_bg`, tint는 `@color/white`를 사용한다. +- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/write/CreatorChannelFanTalkWriteActivity.kt` + - Figma 기반 입력 화면, keyboard show/hide, 취소 popup, 등록 성공 result를 담당한다. +- 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/write/CreatorChannelFanTalkWriteViewModel.kt` + - 입력값 상태, 보내기 버튼 enabled 상태, 등록 loading/result/error 상태를 관리한다. +- 생성: `app/src/main/res/layout/activity_creator_channel_fantalk_write.xml` + - 입력 화면 layout을 구현한다. +- 생성 가능: `app/src/main/res/drawable/bg_creator_channel_fantalk_write_send_enabled.xml` + - 기존 drawable로 대응되지 않을 때만 enabled 보내기 버튼 배경을 추가한다. +- 생성 가능: `app/src/main/res/drawable/bg_creator_channel_fantalk_write_send_disabled.xml` + - 기존 drawable로 대응되지 않을 때만 disabled 보내기 버튼 배경을 추가한다. +- 수정: `app/src/main/res/values/strings.xml` + - FanTalk 글 입력 title/hint/send/cancel/popup/오류 문구를 추가하거나 기존 문자열을 재사용한다. +- 수정: `app/src/main/res/values-en/strings.xml`, `app/src/main/res/values-ja/strings.xml` + - 신규 문자열의 다국어 값을 추가한다. +- 수정: `app/src/main/AndroidManifest.xml` + - 신규 `CreatorChannelFanTalkWriteActivity`를 등록한다. +- 수정: `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt` + - `CreatorChannelFanTalkWriteViewModel` binding을 추가한다. +- 테스트 생성: + - `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/write/CreatorChannelFanTalkWriteViewModelTest.kt` + - `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/write/CreatorChannelFanTalkWriteSourceTest.kt` +- 테스트 수정: + - `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt` + - `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragmentLayoutTest.kt` + +--- + +### Phase 1: 기존 구조 확인과 fixed button 경계 고정 + +- [x] **Task 1.1: 기존 FanTalk 글쓰기 진입점 상태 확인** + - 확인: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragment.kt` + - `app/src/main/res/layout/fragment_creator_channel_fantalk.xml` + - 작업: + - `btnCreatorChannelFantalkWrite.setOnClickListener { }`와 `layoutCreatorChannelFantalkEmptyWriteButton.setOnClickListener { }`가 빈 동작임을 확인한다. + - Fragment 내부 `btn_creator_channel_fantalk_write`가 Fragment root bottom에 묶여 있어 긴 콘텐츠에서 화면 우측 하단 고정 요구사항을 만족하지 못하는지 확인한다. + - 검증: + - `rg -n "btnCreatorChannelFantalkWrite|layoutCreatorChannelFantalkEmptyWriteButton|btn_creator_channel_fantalk_write" app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk app/src/main/res/layout/fragment_creator_channel_fantalk.xml` + - 기대 결과: 빈 click listener와 Fragment 내부 floating view 위치가 확인된다. + +- [x] **Task 1.2: Activity overlay fixed button 배치 지점 확인** + - 확인: + - `app/src/main/res/layout/activity_creator_channel.xml` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt` + - 작업: + - donation floating button, owner FAB와 겹치지 않는 FanTalk fixed plus button 배치 지점을 정한다. + - current tab이 `CreatorChannelTab.FanTalk`이고 FanTalk content 상태일 때만 표시되도록 Activity 상태 변수를 둔다. + - 검증: + - `rg -n "btnCreatorChannelDonationWrite|owner_fab_button|updateDonationFloatingButtonVisibility|CreatorChannelTab.FanTalk" app/src/main/res/layout/activity_creator_channel.xml app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt` + - 기대 결과: Activity overlay에 fixed button을 추가할 위치와 visibility 갱신 지점이 확인된다. + +--- + +### Phase 2: FanTalk 등록 API wrapper와 ViewModel 계약 + +- [x] **Task 2.1: Repository wrapper 추가** + - 수정: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelRepository.kt` + - 작업: + - `writeFanTalk(creatorId: Long, content: String, token: String)` method를 추가한다. + - 내부에서는 `explorerRepository.writeCheers(parentCheersId = null, creatorId = creatorId, content = content, token = token)`를 호출한다. + - 검증 명령: + - `./gradlew :app:compileDebugKotlin` + - 기대 결과: + - 기존 레거시 파일 수정 없이 v2 repository에서 FanTalk 등록 API를 호출할 수 있다. + +- [x] **Task 2.2: FanTalk 글 입력 ViewModel RED 테스트 작성** + - 생성: + - `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/write/CreatorChannelFanTalkWriteViewModelTest.kt` + - 테스트 케이스: + - 초기 입력값은 빈 값이고 보내기 버튼은 disabled다. + - 공백과 줄바꿈만 입력하면 보내기 버튼은 disabled다. + - trim 결과가 비어 있지 않으면 보내기 버튼은 enabled다. + - disabled 상태에서 submit하면 repository를 호출하지 않는다. + - enabled 상태에서 submit하면 `CreatorChannelRepository.writeFanTalk(creatorId, trimmedContent, "Bearer $token")`을 호출한다. + - 등록 중에는 중복 submit을 무시한다. + - 등록 성공 시 완료 event를 emit한다. + - 등록 실패 시 입력값을 유지하고 toast message를 emit한다. + - 검증 명령: + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.write.CreatorChannelFanTalkWriteViewModelTest"` + - 기대 결과: + - ViewModel 구현 전 실패한다. + +- [x] **Task 2.3: FanTalk 글 입력 ViewModel 구현** + - 생성: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/write/CreatorChannelFanTalkWriteViewModel.kt` + - 작업: + - `contentLiveData`, `isSendEnabledLiveData`, `isLoadingLiveData`, 완료 event, toast event를 제공한다. + - `onContentChanged(content)`에서 `content.trim().isNotEmpty()` 기준으로 보내기 enabled 상태를 갱신한다. + - `submit(creatorId)`에서 disabled/loading/invalid creatorId 상태를 방어한다. + - 성공 시 완료 event를 emit하고, 실패 시 toast event를 emit한다. + - 검증 명령: + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.write.CreatorChannelFanTalkWriteViewModelTest"` + - 기대 결과: + - 신규 ViewModel 테스트가 PASS한다. + +--- + +### Phase 3: 글 입력 Activity와 Figma UI 구현 + +- [x] **Task 3.1: 글 입력 Activity layout 추가** + - 생성: + - `app/src/main/res/layout/activity_creator_channel_fantalk_write.xml` + - 작업: + - Figma `290:9235`, `290:9282`, `290:9290` 기준으로 title/cancel/input/send 영역을 구성한다. + - 입력값 없음 상태의 보내기 버튼 disabled style과 입력값 있음 상태의 enabled style을 분리한다. + - keyboard가 내려간 상태에서도 입력 영역과 하단 action이 겹치지 않게 구성한다. + - 검증 명령: + - `./gradlew :app:mergeDebugResources` + - 기대 결과: + - 신규 layout과 drawable/string resource가 resource merge를 통과한다. + +- [x] **Task 3.2: 글 입력 Activity 구현** + - 생성: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/write/CreatorChannelFanTalkWriteActivity.kt` + - 수정: + - `app/src/main/AndroidManifest.xml` + - `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt` + - 작업: + - `newIntent(context, creatorId)`를 제공한다. + - 진입 시 `creatorId`가 없으면 `finish()`한다. + - `EditText`에 focus를 주고 keyboard를 표시한다. + - text change를 ViewModel에 전달한다. + - 보내기 버튼 enabled/loading 상태를 ViewModel state로 반영한다. + - 등록 성공 event 수신 시 `setResult(RESULT_OK)` 후 `finish()`한다. + - 검증 명령: + - `./gradlew :app:compileDebugKotlin` + - 기대 결과: + - 신규 Activity, manifest, DI 구성이 컴파일된다. + +- [x] **Task 3.3: 취소 popup과 back 동작 구현** + - 수정: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/write/CreatorChannelFanTalkWriteActivity.kt` + - `app/src/main/res/values/strings.xml` + - `app/src/main/res/values-en/strings.xml` + - `app/src/main/res/values-ja/strings.xml` + - 작업: + - 입력값이 없으면 좌측 상단 `취소` 버튼 터치 시 즉시 종료한다. + - 입력값이 있으면 좌측 상단 `취소` 버튼 터치 시 Figma `308:19046` 기준 `SodaDialog`를 표시한다. + - popup title은 `입력 종료`로 추가한다. + - popup desc는 `입력을 종료할까요?\n지금 나가면 입력한 내용이 저장되지 않아요.`로 추가한다. + - popup cancel button label은 `계속 작성`으로 추가한다. + - popup confirm button label은 `종료`로 추가한다. + - popup 종료 확정 시 `RESULT_OK`를 설정하지 않고 `finish()`한다. + - popup 유지 선택 시 dialog만 닫는다. + - Android back 동작도 좌측 상단 `취소` 버튼과 동일한 입력값 기준을 따른다. + - 입력값이 있으면 Android back 실행 시 입력 종료 popup을 표시한다. + - 입력값이 없으면 Android back 실행 시 popup 없이 현재 화면을 종료한다. + - 검증 명령: + - `./gradlew :app:mergeDebugResources` + - `./gradlew :app:compileDebugKotlin` + - 기대 결과: + - 취소 popup 문자열, 좌측 상단 `취소` 버튼 조건, Android back popup 조건이 resource merge 및 Kotlin compile을 통과한다. + +--- + +### Phase 4: FanTalk 탭 진입점 연결과 fixed plus button 수정 + +- [x] **Task 4.1: Fragment Host callback 추가** + - 수정: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragment.kt` + - `app/src/main/res/layout/fragment_creator_channel_fantalk.xml` + - 작업: + - Host에 `onCreatorChannelFanTalkWriteClicked()`를 추가한다. + - Host에 `onCreatorChannelFanTalkFloatingButtonVisibilityChanged(isVisible: Boolean)`를 추가한다. + - empty `응원 남기기` 버튼 click은 `onCreatorChannelFanTalkWriteClicked()`를 호출한다. + - content 상태에서는 Activity fixed plus button이 보이도록 `true`를 전달한다. + - loading/empty/error 상태와 `onDestroyView()`에서는 `false`를 전달한다. + - Fragment 내부 `btn_creator_channel_fantalk_write`는 제거하거나 `gone`으로 고정한다. + - 검증 명령: + - `./gradlew :app:compileDebugKotlin` + - 기대 결과: + - Fragment가 직접 floating button을 소유하지 않고 Host에 표시 상태와 진입 action을 위임한다. + +- [x] **Task 4.2: Activity fixed plus button 추가** + - 수정: + - `app/src/main/res/layout/activity_creator_channel.xml` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt` + - 작업: + - root overlay 우측 하단에 FanTalk fixed plus button을 추가한다. + - button icon은 `@drawable/ic_plus_no_bg`를 사용한다. + - icon tint는 `@color/white`로 지정한다. + - current tab이 FanTalk이고 Fragment가 content 상태 표시를 요청한 경우에만 visible 처리한다. + - bottom inset 또는 기존 bottom margin 정책을 적용해 navigation bar와 겹치지 않게 한다. + - 검증 명령: + - `./gradlew :app:mergeDebugResources` + - `./gradlew :app:compileDebugKotlin` + - 기대 결과: + - plus button이 목록 스크롤과 분리된 Activity overlay에 존재하고, icon이 white로 표시되도록 resource가 구성된다. + +- [x] **Task 4.3: ActivityResult 기반 글 입력 화면 연결** + - 수정: + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt` + - 작업: + - `CreatorChannelFanTalkWriteActivity` launcher를 추가한다. + - FanTalk fixed plus button click과 empty `응원 남기기` Host callback에서 동일한 `openFanTalkWrite()`를 호출한다. + - 로그인 토큰이 비어 있으면 기존 `showLoginActivity()`를 호출하고 글 입력 화면을 열지 않는다. + - 등록 성공 result 수신 시 `refreshCreatorChannelFanTalk()`와 `homeActionDelegate?.refreshHome()`를 호출한다. + - 검증 명령: + - `./gradlew :app:compileDebugKotlin` + - 기대 결과: + - 두 진입점 모두 동일한 글 입력 화면으로 이동하고 성공 후 목록/홈 요약 refresh가 연결된다. + +--- + +### Phase 5: Source/단위 테스트와 회귀 검증 + +- [x] **Task 5.1: fixed plus button/진입점 source test 추가** + - 수정: + - `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt` + - `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragmentLayoutTest.kt` + - 작업: + - `activity_creator_channel.xml`에 FanTalk fixed plus button과 `@drawable/ic_plus_no_bg`, `@color/white` tint가 있는지 확인한다. + - FanTalk fixed plus button이 `activity_creator_channel.xml` root overlay에 있고 `fragment_creator_channel_fantalk.xml`의 RecyclerView/content height에 묶이지 않는지 확인한다. + - `fragment_creator_channel_fantalk.xml`의 empty `응원 남기기` 버튼은 유지되고 Fragment 내부 floating button은 사용되지 않는지 확인한다. + - `CreatorChannelActivity`에 ActivityResult launcher와 등록 성공 refresh 호출이 있는지 확인한다. + - `CreatorChannelFanTalkFragment.Host`에 글쓰기 click/visibility callback이 있는지 확인한다. + - 검증 명령: + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*FanTalk*"` + - 기대 결과: + - Activity overlay fixed plus button과 글 입력 진입점 연결 source test가 PASS한다. + +- [x] **Task 5.2: 글 입력 Activity source test 추가** + - 생성: + - `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/write/CreatorChannelFanTalkWriteSourceTest.kt` + - 작업: + - `CreatorChannelFanTalkWriteActivity`가 `newIntent(context, creatorId)`를 제공하는지 확인한다. + - 좌측 상단 `취소` 버튼은 입력값이 있을 때 popup을 표시하고, 입력값이 없을 때 즉시 종료하는 경로가 있는지 확인한다. + - Android back 동작도 입력값이 있을 때 popup을 표시하고, 입력값이 없을 때 즉시 종료하는 경로가 있는지 확인한다. + - 입력 종료 popup 문자열 resource가 title `입력 종료`, desc `입력을 종료할까요?\n지금 나가면 입력한 내용이 저장되지 않아요.`, cancel `계속 작성`, confirm `종료` 값을 갖는지 확인한다. + - 글 입력 layout에 입력 영역, 좌측 상단 `취소`, 보내기 버튼이 있고 ViewModel의 enabled/disabled 상태를 반영하는 binding 경로가 있는지 확인한다. + - 등록 성공 시 `setResult(RESULT_OK)` 후 종료하는지 확인한다. + - `AndroidManifest.xml`에 신규 Activity가 등록되어 있는지 확인한다. + - 검증 명령: + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.write.*"` + - 기대 결과: + - 글 입력 Activity의 핵심 연결 계약 source test가 PASS한다. + +- [x] **Task 5.3: 전체 검증 실행** + - 실행: + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.write.*"` + - `./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 기반 자동 확인: + - `CreatorChannelFanTalkWriteViewModelTest`에서 입력값 없음/공백 입력은 보내기 disabled, 입력값 있음은 보내기 enabled임을 확인한다. + - `CreatorChannelFanTalkWriteSourceTest`에서 입력 종료 popup 문구가 Figma `308:19046` 확정 문구와 일치하는지 확인한다. + - `CreatorChannelActivitySourceTest`에서 fixed plus button이 Activity overlay에 있고 `ic_plus_no_bg`와 white tint를 사용하는지 확인한다. + - `CreatorChannelFanTalkFragmentLayoutTest`에서 Fragment 내부 floating button이 스크롤 콘텐츠에 묶인 진입점으로 남아 있지 않은지 확인한다. + - Figma 수동 확인: + - 입력값 없음 + keyboard 있음 화면이 Figma `290:9235`의 주요 layout 구조와 버튼 disabled 상태를 반영하는지 확인한다. + - 입력값 있음 + keyboard 있음 화면이 Figma `290:9282`의 주요 layout 구조와 버튼 enabled 상태를 반영하는지 확인한다. + - 입력값 있음 + keyboard 없음 화면이 Figma `290:9290`의 주요 layout 구조와 버튼 enabled 상태를 반영하는지 확인한다. + - 입력 종료 popup이 Figma `308:19046`의 문구와 버튼 구성을 반영하는지 확인한다. + - FanTalk 탭 fixed plus button이 스크롤과 관계없이 viewport 우측 하단에 고정되고, `ic_plus_no_bg` icon이 white로 보이는지 확인한다. + - 기대 결과: + - 모든 명령이 PASS하고, Figma 기반 자동 확인과 Figma 4개 상태 화면 및 fixed plus button 수동 대조 결과가 PASS한다. + - 검증 기록: + - Figma 기반 자동 확인 결과와 수동 대조를 수행한 화면 상태, 기기/해상도, PASS/FAIL 결과를 `Verification Log`에 누적한다. + +### Phase 6: Figma 290:9290 글 입력 UI 보정 + +- [x] **Task 6.1: title 제거와 하단 글자수 UI 추가** + - 수정: + - `app/src/main/res/layout/activity_creator_channel_fantalk_write.xml` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/write/CreatorChannelFanTalkWriteActivity.kt` + - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/write/CreatorChannelFanTalkWriteViewModel.kt` + - 작업: + - Figma `290:9290` 기준으로 `tv_creator_channel_fantalk_write_title`을 제거한다. + - 보내기 버튼 enabled 상태는 `soda_400` 배경과 white 글자색을 사용한다. + - 하단 fixed info bar에 현재 글자수와 `/500자`를 표시한다. + - keyboard가 올라오면 info bar가 함께 올라오도록 Activity layout bottom에 고정한다. + - 입력값은 500자를 넘을 수 없게 제한한다. + - 검증 명령: + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.write.*"` + - `./gradlew :app:mergeDebugResources` + - `./gradlew :app:compileDebugKotlin` + - 기대 결과: + - title 없는 Figma 구조, 글자수 UI, 500자 제한 계약이 테스트/리소스/컴파일을 통과한다. + +--- + +## Verification Log + +- 구현 전 문서 작성 단계에서는 Gradle 검증을 실행하지 않았다. + +- 2026-07-08 구현 중 TDD 기록: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.write.CreatorChannelFanTalkWriteViewModelTest"`를 RED 테스트 추가 직후 실행했으나 첫 실행은 120초 제한, 두 번째 실행은 300초 제한에서 `compileDebugKotlin` 단계 중 시간 초과되어 실패 출력을 끝까지 확보하지 못했다. 이후 구현 후 동일 계약을 포함한 write 테스트 묶음에서 GREEN을 확인했다. +- 2026-07-08 구현 검증: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.write.*"` 최초 실행은 테스트 문자열 escape와 `ApiResponse` 제네릭 불일치로 실패했고, 수정 후 재실행 결과 PASS했다. 이 테스트에서 입력값 없음/공백 disabled, 입력값 있음 enabled, trim submit, 중복 submit 무시, 성공 finish, 실패 시 입력 유지 및 서버 message toast, popup 문구, 신규 Activity/Manifest 계약을 확인했다. +- 2026-07-08 구현 검증: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.*"` PASS. FanTalk Fragment layout/source 계약과 기존 FanTalk 단위 테스트 회귀를 확인했다. +- 2026-07-08 구현 검증: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*FanTalk*"` PASS. Activity overlay fixed plus, `ic_plus_no_bg`, white tint, ActivityResult refresh, Fragment Host callback source 계약을 확인했다. +- 2026-07-08 구현 검증: `./gradlew :app:mergeDebugResources` PASS. 신규 write layout/drawable/string/manifest resource 병합을 확인했다. +- 2026-07-08 구현 검증: `./gradlew :app:compileDebugKotlin` PASS. 기존 deprecation/unchecked warning만 출력되었고 신규 컴파일 오류는 없었다. +- 2026-07-08 구현 검증: `./gradlew :app:ktlintCheck` PASS. 기존 `.editorconfig`의 `disabled_rules` deprecation 경고만 출력되었다. +- 2026-07-08 구현 검증: `git diff --check` PASS. trailing whitespace 및 patch whitespace 오류가 없음을 확인했다. +- 2026-07-08 Figma 계약 대조: 자동 테스트와 소스 확인으로 fixed plus가 Activity root overlay에 있고 `ic_plus_no_bg` + `@color/white` tint를 사용함을 확인했다. write 화면은 입력값 없음/있음에 따른 send disabled/enabled와 입력 종료 popup의 PRD 확정 문구를 테스트로 확인했다. 실제 기기/에뮬레이터 수동 실행은 이번 환경에서 수행하지 못했다. + +- 2026-07-08 후속 수정: Figma `290:9290` 대조 결과 상단 title이 없어야 하고 하단 `현재/500자` info bar가 필요함을 확인했다. 실제 기기/에뮬레이터 수동 실행은 환경 제약으로 별도 수행이 필요하다. +- 2026-07-08 후속 수정 검증: write 테스트 첫 실행은 source test 문자열 escape 오류로 `compileDebugUnitTestKotlin` 단계에서 실패했다. escape와 enabled 색상 계약을 수정 후 재검증한다. +- 2026-07-08 후속 수정 검증: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.write.*"` PASS. title 제거, 하단 글자수 UI, 500자 제한, enabled 버튼 `soda_400` 배경/white 글자색 계약을 테스트로 확인했다. +- 2026-07-08 후속 수정 검증: `./gradlew :app:mergeDebugResources` PASS, `./gradlew :app:compileDebugKotlin` PASS, `git diff --check` PASS.