docs(plan): 채팅 쿼터 충전 확장 작업 기록을 추가한다

This commit is contained in:
2026-04-30 12:47:14 +09:00
parent b565a0eb02
commit 5dd58d0092
2 changed files with 184 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="#FEF8E3" />
<corners android:radius="30dp" />
<stroke android:width="1dp" android:color="#F7CB50" />
</shape>

View File

@@ -0,0 +1,178 @@
# 20260430 채팅 쿼터 충전 확장 계획
## 작업 체크리스트
- [x] 기존 채팅 쿼터 UI, 응답 DTO, 구매 API, Yandex 광고 사용 패턴을 근거 파일 기준으로 조사한다.
QA: `ChatRoomActivity`, `ChatMessageAdapter`, `ChatQuotaPurchaseRequest`, `TalkApi`, `ChatRepository`, `AudioContentDetailActivity`, `app/build.gradle`를 근거로 현재 구조를 설명할 수 있어야 한다.
- [x] 쿼터 안내 노출 조건을 `nextRechargeAtEpoch != null`에서 `totalRemaining <= 0` 기준으로 전환한다.
QA: 채팅방 입장, 메시지 전송 응답, 쿼터 상태 조회, 쿼터 구매 응답 모두에서 `updateQuotaUi``totalRemaining` 기준으로만 노출 여부를 결정해야 한다.
- [x] 무료 충전 제거에 맞춰 카운트다운 및 무료 대기 문구 관련 로직을 제거한다.
QA: `CountDownTimer`, `tv_time`, `nextRechargeAtEpoch` 기반 갱신, `checkQuotaStatus()`의 무료 충전 재조회 목적 로직이 더 이상 남지 않아야 한다.
- [x] `item_chat_quota_notice`를 2단 구성의 신규 구매 UI로 교체한다.
QA: 상단에는 광고 버튼 1개, 하단에는 캔 구매 버튼 2개가 가로 배치되어야 하며, 기존 시간/무료 안내 문구 영역은 제거되어야 한다.
- [x] 광고 버튼과 캔 구매 버튼의 텍스트/스타일을 요청 사양대로 반영한다.
QA: 광고 버튼은 `광고 / 5채팅`, 하단 버튼은 `10 / 15채팅`, `20 / 40채팅` 구조를 가지며, 각 캔 숫자 앞에 `ic_can` 아이콘이 표시되고 캔 숫자는 bold, 채팅 개수는 medium이어야 한다.
- [x] 쿼터 구매 요청 DTO를 광고/캔 구매 구분이 가능하도록 확장한다.
QA: 요청 본문에 `container`, `chargeType`, `canOption`이 포함되고, 캔 구매 시 `CAN_10`, `CAN_20`, 광고 구매 시 `AD` 타입을 표현할 수 있어야 한다.
- [x] Yandex 채팅 쿼터 전용 rewarded ad unit id를 buildType별 `BuildConfig`로 추가한다.
QA: `debug`/`release`에서 채팅 쿼터 광고용 별도 `BuildConfig` 키가 생성되어야 하며, 기존 오디오 콘텐츠 전면 광고 id와 분리되어야 한다.
- [x] `totalRemaining == 1`일 때 광고를 미리 준비하고, 광고 버튼 터치 시 rewarded 광고를 표시한 뒤 `onRewarded()` 시점에 쿼터 충전 API를 호출하도록 흐름을 추가한다.
QA: 광고 미로드/표시 실패 시에는 API가 호출되지 않아야 하고, 사용자가 reward 조건을 충족했을 때만 `AD` 타입 구매 요청이 한 번만 전송되어야 한다.
- [x] 캔 구매 버튼 2종이 각각 올바른 구매 옵션으로 API를 호출하고, 성공 시 헤더 캔 수와 쿼터 UI를 갱신하도록 정리한다.
QA: 10캔 버튼은 15채팅, 20캔 버튼은 40채팅과 연결되고, 성공 후 `SharedPreferenceManager.can``tvCanBadge`가 실제 차감값에 맞게 갱신되어야 한다.
- [x] 변경 결과를 문서 하단 검증 기록에 누적한다.
QA: 최소 빌드, 테스트, 수동 확인 계획과 실제 실행 결과가 한국어로 누적되어야 한다.
## 범위 메모
- 이번 요청은 `ChatRoomActivity`의 채팅 쿼터 부족 안내와 구매 흐름을 광고/캔 3가지 선택지로 확장하는 작업으로 한정한다.
- 무료 충전은 제거되므로, 기존 `nextRechargeAtEpoch` 기반 표시 판단과 카운트다운 UI/로직은 제거 대상으로 본다.
- 안내 노출 여부는 `totalRemaining <= 0`일 때만 표시하는 방향으로 고정한다.
- 광고 버튼은 Yandex **rewarded ad**를 사용하고, reward 지급 콜백인 `onRewarded()` 시점에 쿼터 충전 API를 호출한다.
- reward 기준으로 처리하므로, 광고가 단순히 표시되거나 닫힌 것만으로는 보상을 지급하지 않는다.
- 광고 unit id는 기존 `app/build.gradle``buildConfigField` 패턴을 유지하되, 채팅 쿼터 지면은 별도 키로 분리한다.
- 캔 구매 옵션은 요청에 맞춰 `10캔→15채팅`, `20캔→40채팅` 두 가지만 제공한다.
- `AD` 타입 쿼터 지급은 서버가 광고 보상 완료를 검증할 수 있는 구조(예: SSV, 검증 토큰, nonce/idempotency)인지 별도 확인이 필요하다.
- 요청 범위를 넘는 별도 결제 화면 추가, 무료 충전 대체 UX 확장, 다른 화면 공통화 리팩터링은 제외한다.
## 조사 근거
- 현재 채팅 쿼터 흐름
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRoomActivity.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatMessageAdapter.kt`
- `app/src/main/res/layout/item_chat_quota_notice.xml`
- 쿼터 응답/구매 DTO
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRoomEnterResponse.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/SendChatMessageResponse.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/quota/ChatQuotaStatusResponse.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/quota/ChatQuotaPurchaseRequest.kt`
- API/Repository
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/TalkApi.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRepository.kt`
- 기존 Yandex 광고/전면광고 패턴
- `app/src/main/java/kr/co/vividnext/sodalive/audio_content/detail/AudioContentDetailActivity.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/app/SodaLiveApp.kt`
- `app/build.gradle`
- 공식 문서
- `https://ads.yandex.com/helpcenter/en/dev/android/interstitial`
- `https://ads.yandex.com/helpcenter/en/dev/android/rewarded`
## 구현 계획
### 1. 쿼터 상태 판단 기준 전환
- 수정 대상:
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRoomActivity.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRoomEnterResponse.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/SendChatMessageResponse.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/quota/ChatQuotaStatusResponse.kt`
- 계획:
- `updateQuotaUi` 시그니처와 호출부를 `nextRechargeAtEpoch` 대신 `totalRemaining` 중심으로 재구성한다.
- 입장 응답, 메시지 전송 응답, 쿼터 조회/구매 응답에서 모두 동일한 판단식을 사용한다.
- `inputContainer` 표시 여부도 `totalRemaining > 0` 기준으로 정리한다.
### 2. 무료 충전/카운트다운 제거
- 수정 대상:
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRoomActivity.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatMessageAdapter.kt`
- `app/src/main/res/layout/item_chat_quota_notice.xml`
- 계획:
- `quotaTimer`, `startQuotaCountdown`, `stopQuotaCountdown`, `formatEpochToHms`, `formatMillisToHms`, `checkQuotaStatus`의 역할을 재평가하고, 무료 충전용 로직은 제거한다.
- `ChatListItem.QuotaNotice`가 시간 텍스트를 들고 다니는 구조를 단순화한다.
- `QuotaNoticeViewHolder``tv_time` 의존성을 제거하고, 버튼 클릭만 처리하는 형태로 바꾼다.
### 3. QuotaNotice 레이아웃 교체
- 수정 대상:
- `app/src/main/res/layout/item_chat_quota_notice.xml`
- 필요 시 관련 drawable / string 리소스
- 계획:
- 상단 시간/무료 안내 블록을 제거하고 광고 버튼 1개를 `match_parent`로 배치한다.
- 하단에는 좌우 2개 버튼을 동일 행에 둔다.
- 광고 버튼 배경색 `RGB(254, 248, 227)`, border `RGB(247, 203, 80)`를 기존 코드 스타일에 맞춰 hex로 정의한다.
- 버튼 텍스트는 국제화 리소스로 분리하되, 요청 문구를 그대로 반영한다.
### 4. 캔 구매 DTO 및 API 호출 확장
- 수정 대상:
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/quota/ChatQuotaPurchaseRequest.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/TalkApi.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRepository.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRoomActivity.kt`
- 계획:
- 기존 `ChatQuotaPurchaseRequest`를 요청 사양에 맞게 확장하고, 필요하면 내부 클래스명도 API 계약과 맞는 방향으로 정리한다.
- `ChatRoomQuotaChargeType`, `ChatRoomQuotaCanOption` enum을 추가한다.
- `purchaseChatQuota(...)`가 광고/캔 버튼별로 다른 요청 본문을 받을 수 있게 repository 시그니처를 확장한다.
### 5. 채팅 쿼터 전용 Yandex rewarded 흐름 추가
- 수정 대상:
- `app/build.gradle`
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRoomActivity.kt`
- 계획:
- `debug`/`release` 각각에 채팅 쿼터 rewarded ad unit id `BuildConfig` 키를 추가한다.
- 기존 Yandex 광고 초기화 구조는 유지하고, rewarded ad의 loader / load listener / event listener / `show(activity)` 패턴을 채팅방에 맞게 적용한다.
- `totalRemaining == 1` 시점에 preload를 시도하고, 광고 버튼 터치 시 로드된 광고가 있으면 표시한다.
- `onRewarded()` 콜백에서 `AD` 타입 쿼터 구매 API를 호출한다.
- `onAdFailedToShow`, `onAdDismissed`, `onDestroy`에서 listener와 ad 참조를 정리한다.
### 6. 버튼별 구매 액션과 로컬 상태 갱신 정리
- 수정 대상:
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatMessageAdapter.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRoomActivity.kt`
- 계획:
- 어댑터 콜백을 광고/10캔/20캔으로 구분 가능한 형태로 확장하거나, 단일 콜백에 옵션 인자를 전달하도록 바꾼다.
- 캔 구매 성공 시에는 선택한 `needCan`만큼 `SharedPreferenceManager.can`을 차감하고 `tvCanBadge`를 갱신한다.
- 광고 구매 성공 시에는 캔 차감 없이 쿼터 UI만 갱신한다.
- 응답의 `totalRemaining` 기준으로 입력창 복구 여부와 QuotaNotice 제거 여부를 결정한다.
## 예상 수정 파일
- `docs/20260430_채팅쿼터충전확장계획.md`
- `app/build.gradle`
- `app/src/main/res/layout/item_chat_quota_notice.xml`
- `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/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRoomActivity.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatMessageAdapter.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/quota/ChatQuotaPurchaseRequest.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/quota/ChatQuotaStatusResponse.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRoomEnterResponse.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/SendChatMessageResponse.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/TalkApi.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRepository.kt`
## 검증 계획
- `./gradlew :app:assembleDebug`
- `./gradlew :app:testDebugUnitTest`
- 필요 시 `./gradlew :app:ktlintCheck`
- 수동 확인:
- `totalRemaining > 0` 상태에서 입력창이 보이고 QuotaNotice가 사라지는지 확인한다.
- `totalRemaining <= 0` 상태에서 신규 QuotaNotice UI가 노출되는지 확인한다.
- `totalRemaining == 1`일 때 rewarded 광고 preload가 준비되는지 로그 또는 디버그 포인트로 확인한다.
- 광고 버튼 탭 시 rewarded 광고가 뜨고 `onRewarded()` 직후 `AD` 타입 구매 요청이 1회 호출되는지 확인한다.
- 10캔/20캔 버튼 각각이 15채팅/40채팅 구매와 연결되고, 성공 후 헤더 캔 수와 입력창 상태가 갱신되는지 확인한다.
## 검증 기록
- 2026-04-30
- 무엇: 채팅 쿼터 충전 확장 작업의 계획 문서를 생성했다.
- 왜: 저장소 규칙에 따라 구현 전에 `docs` 아래 계획 문서를 먼저 만들고, 그 문서를 기준으로 범위·근거·검증 기준을 고정해야 하기 때문이다.
- 어떻게:
- 생성 파일: `docs/20260430_채팅쿼터충전확장계획.md`
- 근거 파일: `ChatRoomActivity.kt`, `ChatMessageAdapter.kt`, `item_chat_quota_notice.xml`, `ChatQuotaPurchaseRequest.kt`, `ChatQuotaStatusResponse.kt`, `TalkApi.kt`, `ChatRepository.kt`, `ChatRoomEnterResponse.kt`, `SendChatMessageResponse.kt`, `AudioContentDetailActivity.kt`, `app/build.gradle`
- 근거 문서: `https://ads.yandex.com/helpcenter/en/dev/android/interstitial`, `https://ads.yandex.com/helpcenter/en/dev/android/rewarded`
- 결과: 무료 충전 제거, `totalRemaining` 기준 노출 전환, 광고/캔 3가지 구매 UI, DTO 확장, Yandex rewarded 연동 범위, 예상 수정 파일과 검증 계획을 구현 전에 먼저 확정했다.
- 2026-04-30
- 무엇: 채팅 쿼터 충전 확장 구현과 검증을 완료했다.
- 왜: 무료 충전 제거 이후에도 채팅 쿼터 부족 상태에서 rewarded 광고와 2종의 캔 구매 옵션으로 다시 충전할 수 있어야 했기 때문이다.
- 어떻게:
- 수정 파일: `app/build.gradle`, `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRoomActivity.kt`, `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatMessageAdapter.kt`, `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/quota/ChatQuotaPurchaseRequest.kt`, `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRepository.kt`, `app/src/main/res/layout/item_chat_quota_notice.xml`, `app/src/main/res/drawable/bg_chat_quota_rewarded_ad_button.xml`, `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/chat/talk/room/ChatMessageAdapterTest.kt`
- 구현 내용: `totalRemaining` 기준 QuotaNotice 노출 전환, countdown/free-wait 로직 제거, rewarded 광고 preload/show/onRewarded 구매 호출, 10캔/20캔 구매 분기, 채팅 쿼터 전용 rewarded ad unit id 추가, QuotaNotice 3버튼 UI 적용
- 실행 명령: `./gradlew :app:testDebugUnitTest :app:assembleDebug`
- 실행 결과: `BUILD SUCCESSFUL`
- 실행 명령: `./gradlew :app:ktlintCheck`
- 실행 결과: `BUILD SUCCESSFUL`
- 진단 도구: `lsp_diagnostics`
- 진단 결과: `.kt` LSP 서버 미설정으로 `No LSP server configured for extension: .kt`
- 수동 확인 시도: `adb devices`
- 수동 확인 결과: 연결된 Android 기기가 없어 앱 실행 기반의 광고 노출/버튼 동작 수동 검증은 이번 세션에서 진행하지 못했다.
- 2026-04-30
- 무엇: 리뷰 피드백을 반영해 중복 요청 방지와 rewarded preload 조건을 보강하고, 서버 검증 리스크를 문서에 기록했다.
- 왜: 빠른 연속 탭으로 인한 중복 구매/중복 광고 시도와, 이미 quota가 소진된 상태에서 첫 광고 탭이 항상 실패하는 UX를 줄여야 했기 때문이다.
- 어떻게:
- 수정 파일: `app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRoomActivity.kt`, `docs/20260430_채팅쿼터충전확장계획.md`
- 로직 보강: `isQuotaPurchaseInFlight`, `isChatQuotaRewardedAdShowing` 가드 추가, rewarded preload 조건을 `totalRemaining <= 1`로 확장
- 문서 반영: `AD` 타입 쿼터 지급의 서버 검증/SSV/idempotency 확인 필요성을 범위 메모에 기록