diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/detail/AudioContentDetailActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/detail/AudioContentDetailActivity.kt index 354a0571..d1a6e3a9 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/detail/AudioContentDetailActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/detail/AudioContentDetailActivity.kt @@ -1,3 +1,5 @@ +@file:Suppress("ktlint:package-name", "ktlint:standard:package-name") + package kr.co.vividnext.sodalive.audio_content.detail import android.annotation.SuppressLint @@ -10,6 +12,7 @@ import android.os.Build import android.os.Bundle import android.os.Handler import android.os.Looper +import android.text.TextUtils import android.view.LayoutInflater import android.view.View import android.view.inputmethod.InputMethodManager @@ -463,6 +466,11 @@ class AudioContentDetailActivity : BaseActivity **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 콘텐츠 상세의 긴 작품 설명을 펼칠 때 불필요한 말줄임 처리를 제거하고 기존 접힘 동작을 유지한다. + +**Architecture:** 기존 `AudioContentDetailActivity`의 `isExpandDetail` observer 안에서 `TextView.maxLines`와 `TextView.ellipsize`를 함께 갱신한다. ViewModel, XML, API와 화면 구조는 변경하지 않는다. + +**Tech Stack:** Kotlin, Android TextView, LiveData, JUnit 4, Gradle + +--- + +| 문서 항목 | 내용 | +|---|---| +| 상태 | 구현 완료 | +| 작성일 | 2026-08-03 | +| 요구사항 기준 | `docs/20260803_콘텐츠_상세_긴_내용_펼침_성능_개선/prd.md` | +| API 기준 | 해당 없음: API 변경 없음 | +| 현재 Phase | Phase 1 긴 내용 펼침 처리 | +| 현재 활성 Goal | 없음 | + +## 범위 + +### 포함 + +- `AudioContentDetailActivity`의 설명 접기/펼치기 상태 처리 +- 접힘, 펼침, 반복 토글 회귀 source test +- focused test, debug build, 연결 기기 수동 검증 + +### 제외 + +- XML 및 스크롤/추천 목록 구조 변경 +- 신규 helper, dependency, benchmark framework +- 관련 없는 레거시 코드 정리 + +## 기술적 제약 + +- 사용자 승인에 따라 해당 레거시 Activity의 observer만 최소 수정한다. +- 기존 `HomeBusinessInfoBinder`와 `CharacterDetailFragment`의 `ellipsize = null`/`END` 패턴을 따른다. +- 테스트를 먼저 추가하고 RED 확인 후 production 코드를 수정한다. +- 커밋은 사용자가 명시적으로 요청하지 않았으므로 수행하지 않는다. + +## 시나리오 계약 + +| 시나리오 | 이진 통과 조건 | 자동 검증 | 실제 표면 | +|---|---|---|---| +| 펼침 | observer가 `Int.MAX_VALUE`와 `ellipsize = null`을 함께 설정한다. | `AudioContentDetailActivitySourceTest.긴 설명을 펼치면 말줄임을 제거한다` | 연결 기기 콘텐츠 상세 화면 터치 | +| 접힘 | observer가 `2`와 `TextUtils.TruncateAt.END`를 함께 설정한다. | `AudioContentDetailActivitySourceTest.긴 설명을 접으면 두 줄 말줄임을 복원한다` | 연결 기기 콘텐츠 상세 화면 재터치 | +| 반복 토글 회귀 | 하나의 observer 분기에 펼침/접힘 값이 모두 존재한다. | `AudioContentDetailActivitySourceTest.긴 설명의 반복 토글 상태를 한 observer에서 처리한다` | 연결 기기에서 펼침 후 접힘 | + +중단 조건: 세 시나리오, focused test, debug build, 진단과 리뷰가 통과하고 가능한 실제 기기 검증 및 정리 기록이 남으면 즉시 중단한다. + +## Phase 1 + +**Phase 결과:** 긴 작품 설명이 펼침 상태에서 말줄임 없이 표시되고 접힘 상태가 보존된다. + +**선행조건:** `DETAIL-001~003`, `DEC-001` 확정. + +**Phase 완료 조건:** `P1-T1`과 `P1-GATE` 완료 및 검증 기록 누적. + +### 구현 항목 + +#### Task 1.1 설명 접기/펼치기 상태 최적화 + +**Goal 실행 `P1-T1`:** 펼침 상태의 불필요한 ellipsize를 제거하고 접힘 상태에서 복원한다. + +- **시작 조건:** PRD와 이 계획 문서 작성 완료. +- **완료 증거:** source test RED/GREEN, debug build, 변경 파일 진단. +- **범위 밖:** 레이아웃 구조, ViewModel, API 수정. + +**Files:** + +- Create: `app/src/test/java/kr/co/vividnext/sodalive/audio_content/detail/AudioContentDetailActivitySourceTest.kt` +- Modify: `app/src/main/java/kr/co/vividnext/sodalive/audio_content/detail/AudioContentDetailActivity.kt` + +**Interfaces:** + +- Consumes: `AudioContentDetailViewModel.isExpandDetail: LiveData` +- Produces: 펼침 시 `ellipsize == null`, 접힘 시 `ellipsize == TextUtils.TruncateAt.END` + +- [x] **RED:** 세 시나리오를 검증하는 source test를 작성한다. +- [x] **RED 확인:** `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivitySourceTest"` 실행 시 ellipsize 상태 미구현 assertion 실패를 확인한다. +- [x] **GREEN:** `isExpandDetail` observer에서 `ellipsize`를 펼침/접힘 상태에 맞게 설정한다. +- [x] **GREEN 확인:** 같은 focused test가 모두 통과하는지 확인한다. +- [x] **REFACTOR:** 추가 abstraction 없이 import와 변경 블록만 정리하고 focused test 및 build를 다시 확인한다. + +계획된 최소 구현: + +```kotlin +binding.tvDetail.ellipsize = if (it) { + null +} else { + TextUtils.TruncateAt.END +} +``` + +### 완료 조건 + +- [x] `DETAIL-001~003`이 source test와 구현으로 추적된다. +- [x] 요청 밖 파일과 동작을 변경하지 않았다. + +### 검증 방법 + +#### Phase 1 Gate + +**Goal 실행 `P1-GATE`:** 접기/펼치기 동작과 회귀 여부를 최종 판정한다. + +```bash +./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivitySourceTest" +./gradlew :app:assembleDebug +adb devices +``` + +**Expected:** focused test 3건 통과, debug build exit code 0, 연결 기기에서 접힘/펼침 상태 확인 또는 접근 불가 사유 기록. + +- [x] focused test 3건이 통과했다. +- [x] ktlint main/test와 debug build가 성공했다. +- [x] 연결 기기에서 접힘, 펼침, 재접힘 상태를 확인했다. +- [x] 독립 코드 및 화면 리뷰가 PASS로 종료됐다. + +## 실행 순서와 의존성 + +| 순서 | Goal | 선행조건 | 병행 가능 | +|---:|---|---|---| +| 1 | `P1-T1` RED | 문서 작성 완료 | 아니요 | +| 2 | `P1-T1` GREEN | RED 확인 | 아니요 | +| 3 | `P1-GATE` | GREEN 확인 | 아니요 | + +## 변경 금지 항목 + +- 테스트 삭제, skip, assertion 완화 +- 신규 dependency 또는 공통 abstraction 추가 +- 레이아웃, ViewModel, API와 관련 없는 코드 수정 +- 사용자 요청 없는 커밋 및 Git 이력 변경 + +## Progress + +### `P1-T1` 1차 실행 — 2026-08-03 + +- 상태: 구현 및 Gate 검증 완료, 리뷰 진행 중 +- 무엇을: PRD와 구현 계획을 작성하고 변경 범위를 고정했다. +- 왜: 코드 변경 전 문서 준비와 최소 변경 범위를 확정하기 위해서다. +- 어떻게: + - `codegraph` 및 `rg` 조사 — 기존 동일 패턴과 source test 관례 확인. + - `adb devices` — 기기 `2cec640c34017ece` 연결 확인. + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivitySourceTest"` — 의도한 assertion 실패 3건으로 RED 확인. + - 같은 focused test 재실행 — 3건 통과, `BUILD SUCCESSFUL`. + - `./gradlew :app:ktlintMainSourceSetCheck :app:ktlintTestSourceSetCheck :app:assembleDebug` — `BUILD SUCCESSFUL`. + - 연결 기기 debug APK 설치 및 콘텐츠 상세 토글 — 접힘 2줄 말줄임, 펼침 전체 표시, 재접힘 복원 확인. + - `adb shell dumpsys gfxinfo kr.co.vividnext.sodalive.debug` — 펼침 렌더링 프레임 150ms, 접힘 렌더링 프레임 117ms. +- 남은 항목: 독립 리뷰와 QA 임시 파일 정리. +- 다음 행동: 변경 diff와 최신 화면 캡처를 독립 검토한다. + +## Decision Log + +| 날짜 | ID | 상태 | 결정 | 근거 | 영향 Goal/문서 | +|---|---|---|---|---|---| +| 2026-08-03 | `DEC-001` | 확정 | observer에 ellipsize 상태 복원만 추가한다. | 사용자 최소 변경 요청 | `P1-T1`, PRD `DETAIL-001~003` | + +## Verification Log + +- 2026-08-03: 구현 전 `git status --short` 출력 없음으로 clean worktree를 확인했다. +- 2026-08-03: Kotlin LSP는 사용자가 이전에 설치를 거부해 실행할 수 없었으며, Kotlin compiler, focused test와 ktlint로 대체 검증했다. +- 2026-08-03: 실기기에서 접힘/펼침/재접힘 화면과 UI hierarchy를 캡처해 세 시나리오를 확인했다. +- 2026-08-03: 독립 코드 리뷰와 CJK 표시 리뷰가 PASS였다. 기능 QA의 초기 프레임 예산 후보는 명시된 수용 기준에 없는 조건으로 재판정되어 철회됐고 최종 PASS를 받았다. +- 2026-08-03: `./gradlew :app:testDebugUnitTest :app:ktlintMainSourceSetCheck :app:ktlintTestSourceSetCheck :app:assembleDebug` 최종 실행이 `BUILD SUCCESSFUL`로 완료됐다. +- 2026-08-03: 기기와 로컬의 QA 임시 XML, screenshot 및 notepad 파일을 삭제했다. diff --git a/docs/20260803_콘텐츠_상세_긴_내용_펼침_성능_개선/prd.md b/docs/20260803_콘텐츠_상세_긴_내용_펼침_성능_개선/prd.md new file mode 100644 index 00000000..f9d9e2ee --- /dev/null +++ b/docs/20260803_콘텐츠_상세_긴_내용_펼침_성능_개선/prd.md @@ -0,0 +1,73 @@ +# 콘텐츠 상세 긴 내용 펼침 성능 개선 PRD + +## 문서 정보 + +| 항목 | 내용 | +|---|---| +| 문서 상태 | 구현 완료 | +| 작성일 | 2026-08-03 | +| 최종 수정일 | 2026-08-03 | +| 대상 제품 | SodaLive Android 콘텐츠 상세 | +| 관련 API Contract | 해당 없음: API 변경 없음 | +| 관련 구현 계획 | `docs/20260803_콘텐츠_상세_긴_내용_펼침_성능_개선/plan-task.md` | +| 관련 review | 구현 후 `reviews/phase1-content-detail-expansion-review.md` | + +## 1. Overview + +콘텐츠 상세 화면에서 긴 작품 설명을 터치해 전체 내용을 펼칠 때 발생하는 지연을 최소 변경으로 줄인다. 기존 접기/펼치기 UX와 데이터 흐름은 유지한다. + +## 2. Problem Statement + +- 약 1,000자의 작품 설명을 펼칠 때 3초 이상 지연되는 사례가 있다. +- 현재 `TextView`는 접힘용 `ellipsize=end`를 유지한 채 `maxLines`만 `Int.MAX_VALUE`로 변경한다. +- 긴 본문을 펼친 상태에서는 말줄임 계산이 필요하지 않다. + +## 3. Goals + +- 펼친 상태에서 `TextView`의 말줄임 처리를 제거한다. +- 접힌 상태에서는 기존 2줄 및 끝 말줄임 표시를 유지한다. +- 반복해서 접고 펼쳐도 각 상태가 정확히 복원된다. +- 기존 상세 화면의 API, ViewModel, 레이아웃 구조는 변경하지 않는다. + +## 4. Non-Goals + +- `NestedScrollView` 또는 하위 `RecyclerView` 구조 변경 +- 애니메이션, 별도 더보기 버튼 또는 신규 UI 추가 +- API, 번역 데이터, 콘텐츠 본문 가공 변경 +- 정량 성능 기준을 검증하기 위한 benchmark framework 추가 + +## 5. 핵심 사용자 흐름 + +1. 사용자가 콘텐츠 상세 화면에서 2줄로 접힌 작품 설명을 본다. +2. 작품 설명을 터치하면 전체 본문이 말줄임 없이 표시된다. +3. 다시 터치하면 2줄 끝 말줄임 상태로 복원된다. + +## 6. 기능 요구사항 + +| ID | 상태 | 요구사항 | 수용 기준 | Goal 연결 | +|---|---|---|---|---| +| `DETAIL-001` | 확정 | 펼침 상태에서는 말줄임을 사용하지 않는다. | `maxLines == Int.MAX_VALUE`, `ellipsize == null` | `P1-T1` | +| `DETAIL-002` | 확정 | 접힘 상태에서는 기존 표시를 유지한다. | `maxLines == 2`, `ellipsize == TextUtils.TruncateAt.END` | `P1-T1` | +| `DETAIL-003` | 확정 | 반복 토글 시 상태가 복원된다. | 펼침 후 접힘 처리에 두 상태가 모두 명시된다. | `P1-T1`, `P1-GATE` | + +## 7. 성능과 품질 요구사항 + +- 신규 dependency와 abstraction을 추가하지 않는다. +- 기존 저장소의 `TextView` 접기/펼치기 패턴을 재사용한다. +- source regression test, debug unit test, debug build를 통과해야 한다. +- 연결된 Android 기기에서 가능한 범위의 실제 화면 검증을 수행한다. + +## 8. 성공 기준 + +- [x] 펼침 시 `ellipsize`가 `null`로 설정된다. +- [x] 접힘 시 `ellipsize`가 `END`로 복원된다. +- [x] 접힘/펼침의 기존 `maxLines` 값이 유지된다. +- [x] focused test와 debug build가 성공한다. +- [x] 실제 기기 검증 결과가 기록된다. + +## 9. Decision Log + +| 날짜 | ID | 상태 | 결정 | 근거 | 영향 요구사항·Goal | +|---|---|---|---|---|---| +| 2026-08-03 | `DEC-001` | 확정 | 펼침 상태의 `ellipsize`만 제거하고 접힘 시 복원한다. | 사용자 최소 변경 요청과 저장소 내 동일 패턴 | `DETAIL-001~003`, `P1-T1` | +| 2026-08-03 | `DEC-002` | 확정 | 레이아웃 구조와 추천 목록 측정 최적화는 제외한다. | 검증되지 않은 범위 확장을 피한다. | Non-Goals, `P1-GATE` | diff --git a/docs/20260803_콘텐츠_상세_긴_내용_펼침_성능_개선/reviews/phase1-content-detail-expansion-review.md b/docs/20260803_콘텐츠_상세_긴_내용_펼침_성능_개선/reviews/phase1-content-detail-expansion-review.md new file mode 100644 index 00000000..67527a65 --- /dev/null +++ b/docs/20260803_콘텐츠_상세_긴_내용_펼침_성능_개선/reviews/phase1-content-detail-expansion-review.md @@ -0,0 +1,84 @@ +# Phase 1 콘텐츠 상세 긴 내용 펼침 리뷰 + +## 1. 리뷰 정보 + +| 항목 | 내용 | +|---|---| +| 리뷰 대상 | Phase 1 / Task 1.1 | +| 기준 commit 또는 working tree | working tree | +| 리뷰 일자 | 2026-08-03 | +| 리뷰어 | Oracle 코드 리뷰, 기능 QA, CJK 표시 QA | +| 기준 문서 | `prd.md`, `plan-task.md` | +| 리뷰 상태 | 판정 완료 | + +## 2. 리뷰 목적과 범위 + +### 목적 + +- `DETAIL-001~003`과 최소 변경 요구사항 충족 여부를 확인한다. +- source test, 빌드 및 실기기 검증 기록과 실제 구현이 일치하는지 확인한다. + +### 포함 범위 + +- 코드: `app/src/main/java/kr/co/vividnext/sodalive/audio_content/detail/AudioContentDetailActivity.kt` +- 테스트: `app/src/test/java/kr/co/vividnext/sodalive/audio_content/detail/AudioContentDetailActivitySourceTest.kt` +- 수동 검증: 연결 기기의 접힘, 펼침, 재접힘 상태와 `gfxinfo` + +### 제외 범위 + +- 스크롤 및 추천 목록 레이아웃 구조 +- 신규 benchmark framework와 60Hz 단일 프레임 목표 + +## 3. 검토한 근거 + +| 명령 또는 수동 검증 | 결과 | 핵심 증거 | +|---|---|---| +| focused test RED | 성공 | 구현 전 assertion 실패 3건 | +| focused test GREEN | 성공 | 테스트 3건 통과 | +| 전체 debug unit test | 성공 | `:app:testDebugUnitTest` 통과 | +| ktlint main/test | 성공 | 두 source set 검사 통과 | +| `:app:assembleDebug` | 성공 | debug APK 생성 | +| 실기기 접힘 → 펼침 → 재접힘 | 성공 | 2줄 말줄임 → 전체 표시 → 2줄 말줄임 복원 | +| 실기기 `gfxinfo` | 성공 | 펼침 150ms, 접힘 117ms 렌더링 프레임 | + +## 4. 발견 사항 요약 + +확정 발견 사항 없음. + +| ID | 심각도 | 상태 | 제목 | 후속 goal | +|---|---|---|---|---| +| `REV-P1-001` | Low | 오탐 | 16ms 프레임 예산 미충족 후보 | 없음 | + +## 5. 발견 사항 상세 + +### REV-P1-001 — 16ms 프레임 예산 미충족 후보 + +- **심각도:** Low +- **상태:** 오탐 +- **관련 요구사항:** `DETAIL-001~003` +- **소유 Task:** `P1-T1` + +**관찰 내용** + +초기 기능 QA에서 펼침 150ms와 접힘 117ms가 60Hz 프레임 예산을 초과한다는 후보가 제기됐다. + +**판정 기록** + +- 2026-08-03 — PRD에는 16ms 목표가 없고 사용자 요청은 기존 3초 이상 지연을 최소 변경으로 줄이는 것이다. 현재 결과는 세 기능 요구사항을 충족하고 기존 관찰값보다 크게 개선됐으므로 차단 기준이 아니며 리뷰어 재판정에서 PASS로 철회됐다. + +## 6. 확정 항목의 plan·goal 전환 + +전환 항목 없음. + +## 7. 리뷰 종료 판정 + +| 판정 항목 | 결과 | 근거 | +|---|---|---| +| 리뷰 범위 전체 확인 | 충족 | 코드, 테스트, 문서 및 최신 실기기 캡처 확인 | +| 후보 항목 판정 완료 | 충족 | `REV-P1-001` 오탐 판정 | +| 확정 항목 plan 반영 | 해당 없음 | 확정 발견 사항 없음 | +| 검증 명령과 결과 기록 | 충족 | `plan-task.md` Progress와 Verification Log | + +**최종 결론:** 확정 발견 사항 없음, 독립 리뷰 PASS. + +**남은 항목:** 없음.