docs(live): 현재 진행 라이브 리스트 문서를 추가한다

This commit is contained in:
2026-06-26 22:54:34 +09:00
parent 66116351c2
commit 0943237b5f
2 changed files with 397 additions and 0 deletions

View File

@@ -0,0 +1,248 @@
# 현재 진행 중인 라이브 리스트 페이지 구현 계획/TASK
> **For agentic workers:** REQUIRED SUB-SKILL: 구현 시 `superpowers:executing-plans`를 사용해 task 단위로 진행한다. 각 단계는 체크박스(`- [ ]`)로 추적하고, 완료 즉시 `- [x]`로 갱신한다. 구현 범위 변경이 생기면 이 문서를 먼저 수정한 뒤 코드에 반영한다.
**Goal:** `GET /api/v2/home/on-air-lives` 응답을 기반으로 현재 진행 중인 라이브 리스트 화면을 만들고, 아이템 터치 시 기존 라이브 상세 조회 후 입장/비밀번호/결제 흐름을 재사용한다.
**Architecture:** 신규 화면/API/Repository/ViewModel/adapter/model은 `kr.co.vividnext.sodalive.v2.live.onair` 하위에 둔다. 기존 홈 추천 라이브 섹션의 더보기 진입점은 새 Activity를 여는 역할만 담당하고, 실제 라이브 입장 정책은 기존 `LiveViewModel.getRoomDetail()``enterRoom()`을 호출해 중복 구현을 최소화한다.
**Tech Stack:** Kotlin, Android XML Views, ViewBinding, RecyclerView, Retrofit, Gson, RxJava3, Koin, JUnit4 local unit test.
---
## 전제와 성공 기준
- PRD: `docs/20260626_현재_진행_중인_라이브_리스트_페이지/prd.md`
- Figma 전체: `185:4506`
- Figma 아이템: `185:4509`
- 신규 기능 본체의 Kotlin package는 `kr.co.vividnext.sodalive.v2.live.onair`로 고정한다.
- 메인 홈 추천 탭은 `HomeOnAirLiveActivity` 진입 연결만 추가하고, on-air live API/data/model 구현을 홈 패키지에 두지 않는다.
- API endpoint는 `GET /api/v2/home/on-air-lives`이다.
- 앱은 `size` query parameter를 보내지 않는다.
- `HomeOnAirLiveResponse`에는 `beginDateTimeUtc: String`이 포함된다.
- 목록 아이템은 `LIVE HH:mm`을 표시하며, `HH:mm``beginDateTimeUtc`를 디바이스 Timezone으로 변환한 라이브 시작 시각이다.
- 유료 라이브는 `ic_bar_cash`, 가격, `입장 캔`을 표시한다.
- 무료 라이브는 cash 아이콘 없이 `무료`를 표시한다.
- 아이템 터치 시 `getRoomDetail(roomId)` 호출 후 기존 입장 정책을 따른다.
- 구현 완료 후 최소 다음 명령을 실행한다.
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.live.onair.*"`
- `./gradlew :app:mergeDebugResources`
- `./gradlew :app:compileDebugKotlin`
- `./gradlew :app:ktlintCheck`
- `git diff --check`
---
## 파일 구조
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/data/HomeOnAirLiveApi.kt`
- `/api/v2/home/on-air-lives` Retrofit endpoint를 정의한다.
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/data/HomeOnAirLiveModels.kt`
- `HomeOnAirLivePageResponse`, `HomeOnAirLiveResponse` DTO를 정의한다.
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/data/HomeOnAirLiveRepository.kt`
- API 호출을 repository method로 감싼다.
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/model/HomeOnAirLiveUiModels.kt`
- 리스트 item UI model, page state, 시간/가격 표시 모델을 정의한다.
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/model/HomeOnAirLiveMappers.kt`
- DTO를 UI model로 변환하고 `beginDateTimeUtc``HH:mm`으로 변환한다.
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/model/HomeOnAirLiveAuthHeader.kt`
- blank token이면 `null`, 값이 있으면 `Bearer {token}`을 반환한다.
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/HomeOnAirLiveViewModel.kt`
- 첫 페이지/추가 페이지 로딩, loading/error/page 상태를 관리한다.
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/HomeOnAirLiveActivity.kt`
- 화면 구성, pagination, 상세 조회 후 입장 흐름을 연결한다.
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/ui/HomeOnAirLiveAdapter.kt`
- 리스트 아이템을 바인딩한다.
- Create: `app/src/main/res/layout/activity_home_on_air_live.xml`
- title bar와 RecyclerView 컨테이너를 정의한다.
- Create: `app/src/main/res/layout/item_home_on_air_live.xml`
- Figma 기준 라이브 리스트 아이템을 정의한다.
- Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeLiveAdapter.kt`
- 더보기 항목 클릭 콜백을 받을 수 있게 한다.
- Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt`
- 홈 추천 라이브 더보기에서 `HomeOnAirLiveActivity`를 연다.
- Modify: `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt`
- 신규 API/Repository/ViewModel을 Koin에 등록한다.
- Modify: `app/src/main/AndroidManifest.xml`
- `HomeOnAirLiveActivity`를 등록한다.
- Modify: `app/src/main/res/values/strings.xml`
- `On Air`, `입장 캔`, `무료` 등 필요한 문자열을 추가하거나 기존 문자열을 재사용한다.
- Modify: `app/src/main/res/values-en/strings.xml`
- 신규 문자열 번역을 추가한다.
- Modify: `app/src/main/res/values-ja/strings.xml`
- 신규 문자열 번역을 추가한다.
- Create: `app/src/test/java/kr/co/vividnext/sodalive/v2/live/onair/HomeOnAirLiveAuthHeaderTest.kt`
- optional auth header 생성 규칙을 검증한다.
- Create: `app/src/test/java/kr/co/vividnext/sodalive/v2/live/onair/HomeOnAirLiveMapperTest.kt`
- DTO to UI mapping, `LIVE HH:mm`, 가격/무료 표시를 검증한다.
---
### Phase 1: 문서와 기존 구조 확인
- [x] **Task 1.1: 기존 홈 라이브 클릭 동작 확인**
- 확인:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeLiveAdapter.kt`
- 결과:
- 홈 추천 최상단 라이브 아이템 클릭은 `onLiveClick()`으로 연결되어 있으나 현재 구현은 `Unit`이다.
- 더보기 항목은 `setOnClickListener(null)`로 클릭 동작이 없다.
- 검증:
- Run: `rg -n "onLiveClick|setOnLiveClick|MoreViewHolder|setOnClickListener\\(null\\)" app/src/main/java/kr/co/vividnext/sodalive/v2/main/home`
- Expected: 클릭 콜백과 미구현 지점이 확인된다.
- [x] **Task 1.2: 기존 라이브 입장 정책 확인**
- 확인:
- `app/src/main/java/kr/co/vividnext/sodalive/live/now/all/LiveNowAllActivity.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/live/LiveViewModel.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/live/room/detail/GetRoomDetailResponse.kt`
- 결과:
- 기존 입장은 `getRoomDetail(roomId)``manager.id`, `price`, `isPaid`, `isPrivateRoom`, `beginDateTimeUtc`를 기준으로 분기한다.
- `LiveViewModel.enterRoom(roomId, onSuccess, password)`를 재사용할 수 있다.
- 검증:
- Run: `rg -n "fun enterLiveRoom|fun getRoomDetail|fun enterRoom|data class GetRoomDetailResponse" app/src/main/java/kr/co/vividnext/sodalive/live`
- Expected: 상세 조회와 입장 API 호출 지점이 확인된다.
---
### Phase 2: API, DTO, mapper, ViewModel 추가
- [ ] **Task 2.1: optional auth header 테스트 작성**
- 생성:
- `app/src/test/java/kr/co/vividnext/sodalive/v2/live/onair/HomeOnAirLiveAuthHeaderTest.kt`
- 검증:
- Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.live.onair.HomeOnAirLiveAuthHeaderTest"`
- Expected: helper 구현 전 RED 실패.
- [ ] **Task 2.2: optional auth header helper 구현**
- 생성:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/model/HomeOnAirLiveAuthHeader.kt`
- 작업:
- `fun homeOnAirLiveAuthHeader(token: String): String?`를 추가한다.
- `token.trim().takeIf { it.isNotEmpty() }?.let { "Bearer $it" }` 규칙을 적용한다.
- 검증:
- Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.live.onair.HomeOnAirLiveAuthHeaderTest"`
- Expected: PASS.
- [ ] **Task 2.3: mapper 테스트 작성**
- 생성:
- `app/src/test/java/kr/co/vividnext/sodalive/v2/live/onair/HomeOnAirLiveMapperTest.kt`
- 테스트 케이스:
- `beginDateTimeUtc`를 디바이스 Timezone 기준 `HH:mm`으로 변환한다.
- `price > 0`이면 유료 가격 표시 모델로 매핑한다.
- `price == 0`이면 무료 표시 모델로 매핑한다.
- page 응답의 `page`, `hasNext`, `items`를 UI state로 유지한다.
- 검증:
- Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.live.onair.HomeOnAirLiveMapperTest"`
- Expected: mapper 구현 전 RED 실패.
- [ ] **Task 2.4: API/DTO/Repository/model/mapper 구현**
- 생성:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/data/HomeOnAirLiveApi.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/data/HomeOnAirLiveModels.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/data/HomeOnAirLiveRepository.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/model/HomeOnAirLiveUiModels.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/model/HomeOnAirLiveMappers.kt`
- 수정:
- `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt`
- 작업:
- Retrofit endpoint는 `@GET("/api/v2/home/on-air-lives")`로 정의한다.
- `@Header("Authorization") authHeader: String?`, `@Query("page") page: Int`만 사용한다.
- DTO는 `@Keep`, `@SerializedName`을 사용한다.
- mapper는 `java.time` 사용 가능성을 확인하고, 프로젝트 minSdk 제약상 문제가 있으면 기존 `SimpleDateFormat` 패턴을 사용한다.
- 검증:
- Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.live.onair.HomeOnAirLiveMapperTest"`
- Expected: PASS.
- [ ] **Task 2.5: ViewModel 구현**
- 생성:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/HomeOnAirLiveViewModel.kt`
- 수정:
- `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt`
- 작업:
- `loadFirstPage()`는 page 0부터 호출하고 기존 items를 교체한다.
- `loadNextPage()``hasNext = true`이고 loading 중이 아닐 때만 호출한다.
- success이면 mapper 결과를 state로 발행한다.
- failure이면 기존 unknown error toast 패턴을 따른다.
- 검증:
- Run: `./gradlew :app:compileDebugKotlin`
- Expected: 신규 data/model/ViewModel/DI 코드가 컴파일된다.
---
### Phase 3: 화면 UI와 홈 진입점 연결
- [ ] **Task 3.1: string/layout 추가**
- 생성:
- `app/src/main/res/layout/activity_home_on_air_live.xml`
- `app/src/main/res/layout/item_home_on_air_live.xml`
- 수정:
- `app/src/main/res/values/strings.xml`
- `app/src/main/res/values-en/strings.xml`
- `app/src/main/res/values-ja/strings.xml`
- 작업:
- `On Air` title, `입장 캔`, 무료 문자열은 기존 리소스가 있으면 재사용하고 부족한 값만 추가한다.
- `item_home_on_air_live.xml`은 75dp profile, `LIVE HH:mm`, title, creator, price/free 영역을 포함한다.
- 검증:
- Run: `./gradlew :app:mergeDebugResources`
- Expected: 신규 layout/string resource가 merge된다.
- [ ] **Task 3.2: adapter 구현**
- 생성:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/ui/HomeOnAirLiveAdapter.kt`
- 작업:
- `submitItems(items)``onClick` callback을 제공한다.
- 유료/무료 표시 모델에 따라 cash icon visibility와 텍스트를 바인딩한다.
- 이미지 로드는 기존 `loadUrl` 확장을 사용한다.
- 검증:
- Run: `./gradlew :app:compileDebugKotlin`
- Expected: adapter가 컴파일된다.
- [ ] **Task 3.3: Activity 구현과 Manifest 등록**
- 생성:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/HomeOnAirLiveActivity.kt`
- 수정:
- `app/src/main/AndroidManifest.xml`
- 작업:
- `newIntent(context)`를 제공한다.
- `RecyclerView`와 pagination scroll listener를 연결한다.
- item click 시 `getRoomDetail(roomId)` 후 기존 입장 정책을 적용한다.
- 오디오 재생 서비스 중지 후 `LiveRoomActivity`를 실행한다.
- 검증:
- Run: `./gradlew :app:compileDebugKotlin`
- Expected: Activity와 Manifest 등록이 컴파일된다.
- [ ] **Task 3.4: 홈 추천 라이브 더보기 진입점 연결**
- 수정:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeLiveAdapter.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt`
- 작업:
- `HomeLiveAdapter``setOnMoreClick()`을 추가한다.
- 더보기 항목 클릭 시 `HomeOnAirLiveActivity.newIntent(requireContext())`를 실행한다.
- 기존 라이브 아이템 클릭은 이번 범위에서 직접 입장 연결하지 않고 기존 `onLiveClick()` 미구현 상태를 유지한다.
- 검증:
- Run: `./gradlew :app:compileDebugKotlin`
- Expected: 홈 추천 더보기에서 신규 Activity 진입 코드가 컴파일된다.
---
### Phase 4: 최종 검증
- [ ] **Task 4.1: 단위 테스트와 컴파일 검증**
- 검증:
- Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.live.onair.*"`
- Expected: on-air live 관련 단위 테스트 PASS.
- Run: `./gradlew :app:mergeDebugResources`
- Expected: resource merge PASS.
- Run: `./gradlew :app:compileDebugKotlin`
- Expected: Kotlin compile PASS.
- [ ] **Task 4.2: 린트/차이 검증**
- 검증:
- Run: `./gradlew :app:ktlintCheck`
- Expected: ktlint PASS.
- Run: `git diff --check`
- Expected: whitespace error 없음.
---
## Verification Log
- 아직 구현 전이다.