diff --git a/docs/20260626_현재_진행_중인_라이브_리스트_페이지/plan-task.md b/docs/20260626_현재_진행_중인_라이브_리스트_페이지/plan-task.md new file mode 100644 index 00000000..29046b2e --- /dev/null +++ b/docs/20260626_현재_진행_중인_라이브_리스트_페이지/plan-task.md @@ -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 +- 아직 구현 전이다. diff --git a/docs/20260626_현재_진행_중인_라이브_리스트_페이지/prd.md b/docs/20260626_현재_진행_중인_라이브_리스트_페이지/prd.md new file mode 100644 index 00000000..2cb8fe4c --- /dev/null +++ b/docs/20260626_현재_진행_중인_라이브_리스트_페이지/prd.md @@ -0,0 +1,149 @@ +# PRD: 현재 진행 중인 라이브 리스트 페이지 + +## 1. Overview +메인 홈 추천 탭 최상단의 라이브 섹션에서 전체 라이브 리스트 화면으로 이동하고, `GET /api/v2/home/on-air-lives` 응답을 Figma `live_detail_001` 디자인 기준으로 표시한다. + +--- + +## 2. Problem +- 메인 홈 추천 탭 최상단 라이브 아이템은 현재 클릭 콜백이 비어 있어 터치해도 아무 동작을 하지 않는다. +- 기존 레거시 `LiveNowAllActivity`는 별도 `/live/room` API와 그리드 UI를 사용하므로, v2 홈 추천의 Figma 리스트 디자인과 API 계약에 맞지 않는다. +- 라이브 입장은 비공개방, 유료방, 이미 결제한 방, 방장 입장, 성인 인증/콘텐츠 설정 등 기존 정책을 따라야 한다. +- 신규 목록 API는 리스트 표시용 정보만 제공하므로, 아이템 터치 시 기존 라이브 상세 조회 후 입장 정책을 결정해야 한다. + +--- + +## 3. Goals +- 홈 추천 최상단 라이브 섹션의 전체보기/더보기 항목 또는 진입 지점에서 현재 진행 중인 라이브 리스트 페이지를 연다. +- `GET /api/v2/home/on-air-lives?page={page}`를 호출해 0부터 시작하는 페이지 단위로 현재 진행 중인 라이브 목록을 표시한다. +- 서버 `size`는 20으로 고정되므로 앱은 `size` query parameter를 보내지 않는다. +- 리스트 아이템은 Figma 기준으로 프로필 이미지, `LIVE ${라이브 시작시간}`, 제목, 크리에이터 닉네임, 가격/무료 정보를 표시한다. +- `beginDateTimeUtc`는 디바이스 Timezone으로 변환한 뒤 `HH:mm` 형태로 표시한다. +- 유료 라이브는 `ic_bar_cash`와 가격, `입장 캔` 문구를 표시한다. +- 무료 라이브는 cash 아이콘을 숨기고 `무료`라고 표시한다. +- 아이템 터치 시 기존 `getRoomDetail(roomId)` 조회 후 상세 응답 기준으로 입장/비밀번호/결제/성인 인증 흐름을 결정한다. +- 기존 레거시 라이브 입장 정책은 직접 수정하지 않고, v2 신규 화면에서 호출/재사용한다. + +--- + +## 4. Non-Goals +- 이번 작업에서 서버 API 구현은 포함하지 않는다. +- 기존 레거시 `LiveNowAllActivity` 그리드 UI를 리팩터링하지 않는다. +- 기존 `LiveRoomActivity`, `LiveViewModel`, `LiveRepository`, `LiveApi`의 입장 정책 자체를 변경하지 않는다. +- 검색, 정렬, 필터, pull-to-refresh, skeleton loading은 이번 범위에 포함하지 않는다. +- Figma에 없는 별도 마케팅/빈 상태 화면을 새로 설계하지 않는다. +- 라이브 종료/삭제/네트워크 오류에 대한 신규 정책을 만들지 않고 기존 unknown error toast/loading 패턴을 따른다. + +--- + +## 5. Target Users +- 홈 추천 탭에서 현재 진행 중인 라이브를 더 많이 보고 바로 입장하려는 사용자. +- 무료/유료 라이브 여부와 시작 시각을 리스트에서 빠르게 확인하려는 사용자. +- 기존 라이브 입장 정책을 유지하면서 v2 홈 UI를 확장해야 하는 Android 개발자. + +--- + +## 6. User Stories +- 사용자는 홈 추천 탭의 라이브 영역에서 전체 현재 라이브 리스트를 열고 싶다. +- 사용자는 리스트에서 라이브 제목, 크리에이터, 시작 시각, 가격을 확인하고 싶다. +- 사용자는 유료 라이브와 무료 라이브를 명확히 구분하고 싶다. +- 사용자는 라이브 아이템을 터치했을 때 기존과 동일하게 비밀번호 입력, 결제 확인, 성인 인증 안내를 거쳐 입장하고 싶다. +- 개발자는 새 목록 API와 화면은 v2 하위에 두되, 기존 입장 플로우는 중복 구현하지 않고 재사용하고 싶다. + +--- + +## 7. Core Features + +### 현재 진행 중인 라이브 리스트 API +#### Endpoint Contract +- Method: `GET` +- Path: `/api/v2/home/on-air-lives` +- Header: `Authorization: Bearer {accessToken}` optional +- Query: + - `page`: optional, default `0`, 0부터 시작하는 page index + - `size`: 앱에서 보내지 않음. 서버에서 20으로 고정 + +#### Android Response Contract +```kotlin +data class HomeOnAirLivePageResponse( + val items: List, + val page: Int, + val size: Int, + val hasNext: Boolean +) + +data class HomeOnAirLiveResponse( + val roomId: Long, + val creatorNickname: String, + val creatorProfileImage: String, + val title: String, + val price: Int, + val beginDateTimeUtc: String +) +``` + +#### Requirements +- DTO는 `@Keep`, `@SerializedName`을 사용해 기존 v2 data layer 관례를 따른다. +- Retrofit API는 `@GET("/api/v2/home/on-air-lives")`와 nullable `Authorization` header를 사용한다. +- 토큰이 비어 있으면 Authorization header를 보내지 않는다. +- Repository/ViewModel은 기존 `Api -> Repository -> ViewModel -> Activity` 흐름을 따른다. +- 첫 진입 시 `page = 0`을 요청한다. +- `hasNext = true`이고 리스트 하단에 도달하면 다음 page를 요청한다. +- 중복 로딩 방지를 위해 loading 중 추가 page 요청은 무시한다. + +### 리스트 UI +#### Requirements +- 화면 배경은 검은색을 유지한다. +- 상단 title bar는 Figma처럼 뒤로가기 + `On Air` 제목을 표시한다. +- 리스트 좌우 여백은 Figma 기준 14dp를 따른다. +- 아이템은 세로 리스트로 표시한다. +- 프로필 이미지는 75dp 원형으로 표시한다. +- 상단 메타 텍스트는 `LIVE HH:mm` 형태로 표시한다. +- `HH:mm`은 `beginDateTimeUtc`를 UTC로 파싱한 뒤 디바이스 Timezone으로 변환한 시작 시각이다. +- 제목은 18sp bold, 한 줄 말줄임으로 표시한다. +- 크리에이터 닉네임은 14sp regular, 회색으로 표시한다. +- 유료 라이브는 `ic_bar_cash`, 가격, `입장 캔`을 표시한다. +- 무료 라이브는 cash 아이콘을 숨기고 `무료` 텍스트만 표시한다. +- 이미지 로드는 기존 `loadUrl` 또는 기존 Glide 확장 관례를 따른다. + +### 라이브 입장 +#### Requirements +- 아이템 터치 시 바로 `LiveRoomActivity`를 열지 않는다. +- 터치 시 `LiveViewModel.getRoomDetail(roomId)`를 호출한다. +- 상세 응답을 받은 뒤 기존 `LiveNowAllActivity.enterLiveRoom`과 동일한 정책을 적용한다. +- 방장(`manager.id == SharedPreferenceManager.userId`)이면 `enterRoom` 후 `LiveRoomActivity`를 연다. +- 이미 결제했거나 무료인 방은 비공개 여부에 따라 비밀번호 다이얼로그 또는 직접 입장을 수행한다. +- 유료 미결제 방은 비공개 여부에 따라 비밀번호 다이얼로그 또는 `LivePaymentDialog`를 표시한다. +- 성인방은 기존 `ensureLoginAndAdultAuth` 정책을 v2 화면에 맞춰 재사용한다. +- 입장 성공 시 오디오 재생 서비스를 중지한 뒤 `Constants.EXTRA_ROOM_ID`와 함께 `LiveRoomActivity`를 실행한다. + +--- + +## 8. UX / UI Expectations +- 첫 진입 시 기존 `LoadingDialog` 패턴으로 로딩 상태를 표시한다. +- 추가 페이지 로딩은 기존 화면 스타일을 해치지 않는 최소 처리로 구현한다. +- API 실패 또는 상세 조회 실패는 기존 unknown error toast 패턴을 따른다. +- 빈 목록이면 리스트를 비우고 과도한 신규 빈 화면 디자인은 추가하지 않는다. +- 뒤로가기는 현재 리스트 화면을 종료한다. + +--- + +## 9. Technical Constraints +- 신규 `Activity`, `ViewModel`, DTO, Repository, adapter/helper는 `kr.co.vividnext.sodalive.v2` 하위에 작성한다. +- 현재 진행 중인 라이브 리스트 화면의 신규 화면/API/Repository/ViewModel/adapter/model은 `kr.co.vividnext.sodalive.v2.live.onair` 패키지 하위에 작성한다. +- 메인 홈 추천 탭 코드는 신규 화면으로 이동하는 진입점 연결만 담당한다. +- 레거시 파일은 직접 수정하지 않는다. 필요한 기능은 기존 public API를 호출해 사용한다. +- `LiveViewModel`은 기존 Koin 등록을 재사용한다. +- 기존 `BuildConfig` 값, token, URL을 로그/Toast에 노출하지 않는다. +- Compose로 전환하지 않고 기존 XML View/ViewBinding/RecyclerView 패턴을 따른다. + +--- + +## 10. Metrics +- 별도 분석 이벤트는 이번 범위에 추가하지 않는다. +- 기능 검증 기준은 API 호출, 페이지네이션, 가격 표시, 시작 시각 표시, 상세 조회 후 입장 분기 동작이다. + +--- + +## 11. Open Questions +- 없음. `LIVE ${라이브 시작시간}`은 `beginDateTimeUtc`를 디바이스 Timezone으로 변환한 `HH:mm` 표시로 확정한다.