docs(main): 오디오 미니 플레이어 작업을 기록한다
This commit is contained in:
448
docs/20260706_메인_V2_미니_플레이어와_오디오_알림_이동/plan-task.md
Normal file
448
docs/20260706_메인_V2_미니_플레이어와_오디오_알림_이동/plan-task.md
Normal file
@@ -0,0 +1,448 @@
|
||||
# 메인 V2 미니 플레이어와 오디오 알림 이동 구현 계획/TASK
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: 구현 시 `superpowers:subagent-driven-development` 또는 `superpowers:executing-plans`를 사용해 task 단위로 진행한다. 각 단계는 체크박스(`- [ ]`)로 추적하고, 완료 즉시 `- [x]`로 갱신한다. 구현 범위 변경이 생기면 이 문서를 먼저 수정한 뒤 코드에 반영한다.
|
||||
|
||||
**Goal:** `MainV2Activity`에서 오디오 재생 중 미니 플레이어를 표시하고, Notification Shade의 오디오 알림 터치 시 새 플레이어 또는 오디오 상세페이지로 이동한다.
|
||||
|
||||
**Architecture:** `MainV2Activity`는 기존 `MainActivity`의 `MediaController` 기반 `clMiniPlayer` 흐름을 기준으로 미니 플레이어 상태를 표시한다. 알림 터치는 서비스별 `PendingIntent`에 명시 route extra를 담고, `MainV2Activity`가 해당 extra를 소비해 `AudioContentPlayerFragment` 또는 `AudioContentDetailActivity`로 분기한다.
|
||||
|
||||
**Tech Stack:** Kotlin, Android XML Views, ViewBinding, Media3 `MediaController`/`MediaSession`, Android `PendingIntent`, JUnit4 source-level test.
|
||||
|
||||
---
|
||||
|
||||
## 전제와 성공 기준
|
||||
- PRD: `docs/20260706_메인_V2_미니_플레이어와_오디오_알림_이동/prd.md`
|
||||
- 이번 문서 작성 턴에서는 구현하지 않는다.
|
||||
- 구현 전 레거시 파일 수정 승인 필요:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/audio_content/player/AudioContentPlayerService.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentPlayService.kt`
|
||||
- 구현 대상:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt`
|
||||
- 조건부 구현 대상:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/audio_content/player/AudioContentPlayerService.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentPlayService.kt`
|
||||
- 확인 대상:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/main/MainActivity.kt`
|
||||
- `app/src/main/res/layout/activity_main_v2.xml`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/audio_content/detail/AudioContentDetailActivity.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/audio_content/player/AudioContentPlayerFragment.kt`
|
||||
- 성공 기준:
|
||||
- `MainV2Activity`가 `SharedPreferenceManager.isPlayerServiceRunningFlow`를 통해 미니 플레이어를 표시/숨김 처리한다.
|
||||
- `MainV2Activity` 미니 플레이어가 title, artist, artwork, play/pause icon을 `MediaController` 상태와 동기화한다.
|
||||
- `MainV2Activity` 미니 플레이어 터치 시 `AudioContentPlayerFragment`가 표시된다.
|
||||
- `AudioContentPlayerService` 알림 터치 route는 `MainV2Activity`에서 player fragment 표시로 이어진다.
|
||||
- `AudioContentPlayService` 알림 터치 route는 `MainV2Activity`에서 `AudioContentDetailActivity` 이동으로 이어진다.
|
||||
- 처리한 알림 route extra는 중복 실행되지 않도록 제거된다.
|
||||
- 채팅 필터 진입과 일반 딥링크 처리가 회귀하지 않는다.
|
||||
- 구현 완료 후 최소 다음 명령을 실행한다.
|
||||
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.MainV2AudioNotificationRouteSourceTest"`
|
||||
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerServiceNotificationRouteSourceTest"`
|
||||
- `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.audio_content.AudioContentPlayServiceNotificationRouteSourceTest"`
|
||||
- `./gradlew :app:compileDebugKotlin`
|
||||
- `./gradlew :app:ktlintCheck`
|
||||
- `git diff --check`
|
||||
|
||||
---
|
||||
|
||||
## 검토한 접근
|
||||
|
||||
### 접근 A: `MainV2Activity`에서만 알림 진입 추정
|
||||
- 내용: `onNewIntent()`에서 extra가 없는 `MainV2Activity` 진입을 보고 player service 상태를 추정한다.
|
||||
- 장점: 레거시 서비스 파일을 수정하지 않는다.
|
||||
- 단점: 일반 앱 진입과 알림 터치를 구분할 수 없어 의도치 않게 player fragment가 열릴 수 있다.
|
||||
- 결론: 채택하지 않는다.
|
||||
|
||||
### 접근 B: 서비스 알림 `PendingIntent`에 route extra 추가
|
||||
- 내용: 새 player service는 `audio_player`, 기존 play service는 `audio_detail` route extra를 전달한다.
|
||||
- 장점: 알림 본문 터치와 일반 진입을 명확히 구분하고, `MainActivity`의 두 오디오 흐름과 동일한 목적지 결정을 유지한다.
|
||||
- 단점: 레거시 경로의 service 파일 2개를 최소 수정해야 한다.
|
||||
- 결론: 권장안. 구현 전 사용자 승인 후 진행한다.
|
||||
|
||||
---
|
||||
|
||||
## 파일 구조
|
||||
- Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt`
|
||||
- 서비스에서 참조 가능한 public 알림 route `const val`을 추가한다.
|
||||
- `onCreate()`와 `onNewIntent()`에서 알림 route를 소비한다.
|
||||
- player route는 `showPlayerFragment()`로 연결한다.
|
||||
- detail route는 `AudioContentDetailActivity` explicit intent로 연결한다.
|
||||
- route 처리 후 intent extra를 제거한다.
|
||||
- Modify after approval: `app/src/main/java/kr/co/vividnext/sodalive/audio_content/player/AudioContentPlayerService.kt`
|
||||
- `initMediaSession()`의 session activity pending intent에 player route extra를 추가한다.
|
||||
- Modify after approval: `app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentPlayService.kt`
|
||||
- `updateNotification()`의 content pending intent에 detail route extra와 `Constants.EXTRA_AUDIO_CONTENT_ID`를 추가한다.
|
||||
- Create: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2AudioNotificationRouteSourceTest.kt`
|
||||
- `MainV2Activity`의 route 소비 계약을 source-level로 검증한다.
|
||||
- Create after approval: `app/src/test/java/kr/co/vividnext/sodalive/audio_content/player/AudioContentPlayerServiceNotificationRouteSourceTest.kt`
|
||||
- `AudioContentPlayerService`의 player route pending intent 계약을 검증한다.
|
||||
- Create after approval: `app/src/test/java/kr/co/vividnext/sodalive/audio_content/AudioContentPlayServiceNotificationRouteSourceTest.kt`
|
||||
- `AudioContentPlayService`의 detail route pending intent 계약을 검증한다.
|
||||
- No Modify:
|
||||
- `app/src/main/res/layout/activity_main_v2.xml`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/audio_content/detail/AudioContentDetailActivity.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/audio_content/player/AudioContentPlayerFragment.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/main/MainActivity.kt`
|
||||
|
||||
---
|
||||
|
||||
### Phase 1: 기존 흐름 확인
|
||||
|
||||
- [x] **Task 1.1: `MainActivity` 미니 플레이어 흐름 확인**
|
||||
- 확인:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/main/MainActivity.kt`
|
||||
- `app/src/main/res/layout/activity_main.xml`
|
||||
- 결과:
|
||||
- `clMiniPlayer`는 `SharedPreferenceManager.isPlayerServiceRunningFlow`가 `true`일 때 1500ms 후 표시된다.
|
||||
- `clMiniPlayer` 터치 시 `AudioContentPlayerFragment`를 표시한다.
|
||||
- `ivPlayerStop`은 `AudioContentPlayerService`에 `STOP_SERVICE` action을 전달한다.
|
||||
- `MediaController` listener가 metadata와 play/pause icon을 갱신한다.
|
||||
- 별도 `rlMiniPlayer`는 `AudioContentPlayService` broadcast를 받아 표시되고, 터치 시 `AudioContentDetailActivity`로 이동한다.
|
||||
- 검증 기록:
|
||||
- 2026-07-06: `rg -n "clMiniPlayer|rlMiniPlayer|AudioContentPlayerService|AudioContentPlayService|showPlayerFragment|AudioContentDetailActivity" app/src/main/java/kr/co/vividnext/sodalive/main/MainActivity.kt`와 source 확인으로 두 오디오 흐름을 확인했다.
|
||||
|
||||
- [x] **Task 1.2: `MainV2Activity` 현재 미니 플레이어 흐름 확인**
|
||||
- 확인:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt`
|
||||
- `app/src/main/res/layout/activity_main_v2.xml`
|
||||
- 결과:
|
||||
- `MainV2Activity`에는 `observePlayerState()`, `initAndVisibleMiniPlayer()`, `connectPlayerService()`, `setupMediaController()`, `deInitMiniPlayer()`, `showPlayerFragment()`가 이미 존재한다.
|
||||
- `activity_main_v2.xml`에는 `cl_mini_player`가 bottom navigation 위에 정의되어 있다.
|
||||
- 구현 시 신규 레이아웃 추가 없이 기존 binding을 사용하면 된다.
|
||||
- 검증 기록:
|
||||
- 2026-07-06: `rg -n "observePlayerState|initAndVisibleMiniPlayer|connectPlayerService|showPlayerFragment|clMiniPlayer" app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt app/src/main/res/layout/activity_main_v2.xml`로 확인했다.
|
||||
|
||||
- [x] **Task 1.3: 오디오 알림 PendingIntent 흐름 확인**
|
||||
- 확인:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/audio_content/player/AudioContentPlayerService.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentPlayService.kt`
|
||||
- 결과:
|
||||
- `AudioContentPlayerService.initMediaSession()`은 `MainV2Activity` pending intent를 `setSessionActivity()`에 설정한다.
|
||||
- `AudioContentPlayService.updateNotification()`은 `MainV2Activity` pending intent를 `setContentIntent()`에 설정한다.
|
||||
- 두 intent 모두 현재 알림 터치 route를 구분할 extra가 없다.
|
||||
- 검증 기록:
|
||||
- 2026-07-06: `rg -n "setSessionActivity|setContentIntent|PendingIntent|getActivity|MainV2Activity" app/src/main/java/kr/co/vividnext/sodalive/audio_content/player/AudioContentPlayerService.kt app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentPlayService.kt`로 확인했다.
|
||||
|
||||
### Phase 2: `MainV2Activity` 알림 route 소비 계약 추가
|
||||
|
||||
- [x] **Task 2.1: `MainV2Activity` source test 작성**
|
||||
- 생성:
|
||||
- `app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2AudioNotificationRouteSourceTest.kt`
|
||||
- 검증 기준:
|
||||
- `MainV2Activity.kt`가 `EXTRA_AUDIO_NOTIFICATION_ROUTE`를 정의한다.
|
||||
- `ROUTE_AUDIO_PLAYER`와 `ROUTE_AUDIO_DETAIL`을 정의한다.
|
||||
- 세 route 상수는 service 파일에서 참조할 수 있도록 `private` 없이 `const val`로 정의한다.
|
||||
- `handleAudioNotificationRoute` 또는 동등한 메서드가 존재한다.
|
||||
- `ROUTE_AUDIO_PLAYER` 처리 시 `showPlayerFragment()`를 호출한다.
|
||||
- `ROUTE_AUDIO_DETAIL` 처리 시 `AudioContentDetailActivity::class.java`와 `Constants.EXTRA_AUDIO_CONTENT_ID`를 사용한다.
|
||||
- 처리 후 `intent.removeExtra(EXTRA_AUDIO_NOTIFICATION_ROUTE)`를 호출한다.
|
||||
- `onNewIntent()`에서 `EXTRA_CHAT_FILTER` 처리 이후 알림 route 처리가 실행된다.
|
||||
- 테스트 예시:
|
||||
```kotlin
|
||||
package kr.co.vividnext.sodalive.v2.main
|
||||
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
|
||||
class MainV2AudioNotificationRouteSourceTest {
|
||||
private val source = File(
|
||||
"src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt"
|
||||
).readText()
|
||||
|
||||
@Test
|
||||
fun `오디오 알림 route는 플레이어와 상세페이지로 분기한다`() {
|
||||
assertTrue(source.contains("EXTRA_AUDIO_NOTIFICATION_ROUTE"))
|
||||
assertTrue(source.contains("const val EXTRA_AUDIO_NOTIFICATION_ROUTE"))
|
||||
assertTrue(source.contains("ROUTE_AUDIO_PLAYER"))
|
||||
assertTrue(source.contains("ROUTE_AUDIO_DETAIL"))
|
||||
assertTrue(source.contains("handleAudioNotificationRoute"))
|
||||
assertTrue(source.contains("showPlayerFragment()"))
|
||||
assertTrue(source.contains("AudioContentDetailActivity::class.java"))
|
||||
assertTrue(source.contains("Constants.EXTRA_AUDIO_CONTENT_ID"))
|
||||
assertTrue(source.contains("removeExtra(EXTRA_AUDIO_NOTIFICATION_ROUTE)"))
|
||||
}
|
||||
}
|
||||
```
|
||||
- 검증:
|
||||
- Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.MainV2AudioNotificationRouteSourceTest"`
|
||||
- Expected: 구현 전에는 `AssertionError`로 실패한다.
|
||||
- 검증 기록:
|
||||
- 2026-07-06: `MainV2AudioNotificationRouteSourceTest.kt`를 추가했다. 초기 병렬 Gradle 실행 중 `processDebugManifest` 임시 산출물 누락으로 테스트 assertion 전 빌드 실패가 발생해, 구현 후 순차 재실행으로 테스트 PASS를 확인했다.
|
||||
|
||||
- [x] **Task 2.2: `MainV2Activity`에 알림 route 처리 추가**
|
||||
- 수정:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt`
|
||||
- 구현 방향:
|
||||
```kotlin
|
||||
companion object {
|
||||
private const val EXTRA_CHAT_FILTER: String = "extra_chat_filter"
|
||||
const val EXTRA_AUDIO_NOTIFICATION_ROUTE: String = "extra_audio_notification_route"
|
||||
const val ROUTE_AUDIO_PLAYER: String = "audio_player"
|
||||
const val ROUTE_AUDIO_DETAIL: String = "audio_detail"
|
||||
}
|
||||
|
||||
private fun handleAudioNotificationRoute(intent: Intent): Boolean {
|
||||
val route = intent.getStringExtra(EXTRA_AUDIO_NOTIFICATION_ROUTE) ?: return false
|
||||
intent.removeExtra(EXTRA_AUDIO_NOTIFICATION_ROUTE)
|
||||
|
||||
return when (route) {
|
||||
ROUTE_AUDIO_PLAYER -> {
|
||||
if (isLoggedIn()) {
|
||||
handler.post { showPlayerFragment() }
|
||||
} else {
|
||||
showLoginActivity()
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
ROUTE_AUDIO_DETAIL -> {
|
||||
val contentId = intent.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_ID, 0)
|
||||
intent.removeExtra(Constants.EXTRA_AUDIO_CONTENT_ID)
|
||||
if (contentId > 0 && isLoggedIn()) {
|
||||
startActivity(
|
||||
Intent(applicationContext, AudioContentDetailActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, contentId)
|
||||
}
|
||||
)
|
||||
} else if (!isLoggedIn()) {
|
||||
showLoginActivity()
|
||||
}
|
||||
contentId > 0
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
```
|
||||
- 적용 위치:
|
||||
- `onCreate()`: `isLoggedIn()` 블록의 `executeDeeplink(intent)` 예약 전에 알림 route를 먼저 확인한다.
|
||||
- `onNewIntent()`: `EXTRA_CHAT_FILTER` 처리 이후, `executeDeeplink(intent)` 전에 알림 route를 확인한다.
|
||||
- 주의:
|
||||
- `EXTRA_CHAT_FILTER`는 기존처럼 가장 먼저 처리한다.
|
||||
- 알림 route가 처리되면 같은 intent의 일반 딥링크 실행을 생략한다.
|
||||
- `showPlayerFragment()`가 activity state 저장 이후 실행되지 않도록 필요 시 lifecycle state를 확인하거나 `handler.post` 수준의 최소 지연만 사용한다.
|
||||
- 검증:
|
||||
- Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.MainV2AudioNotificationRouteSourceTest"`
|
||||
- Expected: PASS
|
||||
- 검증 기록:
|
||||
- 2026-07-06: `MainV2Activity.kt`에 public route 상수와 `handleAudioNotificationRoute()`를 추가하고, `onCreate()`/`onNewIntent()`에서 딥링크 전에 route를 소비하도록 연결했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.MainV2AudioNotificationRouteSourceTest"` PASS.
|
||||
|
||||
### Phase 3: 오디오 서비스 알림 PendingIntent route 추가
|
||||
|
||||
- [x] **Task 3.1: 레거시 서비스 파일 수정 승인 확인**
|
||||
- 확인 필요:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/audio_content/player/AudioContentPlayerService.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentPlayService.kt`
|
||||
- 작업:
|
||||
- 두 파일은 `kr.co.vividnext.sodalive.v2` 하위가 아니므로 구현 전에 사용자에게 레거시 파일 최소 수정 승인을 받는다.
|
||||
- 검증 기준:
|
||||
- 승인 없이는 Task 3.2~3.5를 진행하지 않는다.
|
||||
- 검증 기록:
|
||||
- 2026-07-06: 사용자가 `승인하고 진행`을 선택해 `AudioContentPlayerService.kt`, `AudioContentPlayService.kt` 최소 수정 승인을 확인했다.
|
||||
|
||||
- [x] **Task 3.2: `AudioContentPlayerService` notification route source test 작성**
|
||||
- 생성:
|
||||
- `app/src/test/java/kr/co/vividnext/sodalive/audio_content/player/AudioContentPlayerServiceNotificationRouteSourceTest.kt`
|
||||
- 검증 기준:
|
||||
- service source가 `MainV2Activity.EXTRA_AUDIO_NOTIFICATION_ROUTE`를 사용한다.
|
||||
- service source가 `MainV2Activity.ROUTE_AUDIO_PLAYER`를 사용한다.
|
||||
- `PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE`는 유지된다.
|
||||
- 테스트 예시:
|
||||
```kotlin
|
||||
package kr.co.vividnext.sodalive.audio_content.player
|
||||
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
|
||||
class AudioContentPlayerServiceNotificationRouteSourceTest {
|
||||
private val source = File(
|
||||
"src/main/java/kr/co/vividnext/sodalive/audio_content/player/AudioContentPlayerService.kt"
|
||||
).readText()
|
||||
|
||||
@Test
|
||||
fun `MediaSession session activity는 플레이어 route를 전달한다`() {
|
||||
assertTrue(source.contains("MainV2Activity.EXTRA_AUDIO_NOTIFICATION_ROUTE"))
|
||||
assertTrue(source.contains("MainV2Activity.ROUTE_AUDIO_PLAYER"))
|
||||
assertTrue(source.contains("PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE"))
|
||||
}
|
||||
}
|
||||
```
|
||||
- 검증:
|
||||
- Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerServiceNotificationRouteSourceTest"`
|
||||
- Expected: 구현 전에는 `AssertionError`로 실패한다.
|
||||
- 검증 기록:
|
||||
- 2026-07-06: `AudioContentPlayerServiceNotificationRouteSourceTest.kt`를 추가했다. 구현 전 실행에서 `AssertionError`로 실패해 RED를 확인했다.
|
||||
|
||||
- [x] **Task 3.3: `AudioContentPlayerService` session activity pending intent에 player route 추가**
|
||||
- 수정:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/audio_content/player/AudioContentPlayerService.kt`
|
||||
- 구현 방향:
|
||||
```kotlin
|
||||
val contextIntent = Intent(applicationContext, MainV2Activity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
putExtra(
|
||||
MainV2Activity.EXTRA_AUDIO_NOTIFICATION_ROUTE,
|
||||
MainV2Activity.ROUTE_AUDIO_PLAYER
|
||||
)
|
||||
}
|
||||
```
|
||||
- 검증:
|
||||
- Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerServiceNotificationRouteSourceTest"`
|
||||
- Expected: PASS
|
||||
- 검증 기록:
|
||||
- 2026-07-06: `AudioContentPlayerService.kt`의 session activity intent에 `ROUTE_AUDIO_PLAYER` extra를 추가했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerServiceNotificationRouteSourceTest"` PASS.
|
||||
|
||||
- [x] **Task 3.4: `AudioContentPlayService` notification route source test 작성**
|
||||
- 생성:
|
||||
- `app/src/test/java/kr/co/vividnext/sodalive/audio_content/AudioContentPlayServiceNotificationRouteSourceTest.kt`
|
||||
- 검증 기준:
|
||||
- service source가 `MainV2Activity.EXTRA_AUDIO_NOTIFICATION_ROUTE`를 사용한다.
|
||||
- service source가 `MainV2Activity.ROUTE_AUDIO_DETAIL`을 사용한다.
|
||||
- content pending intent가 `Constants.EXTRA_AUDIO_CONTENT_ID`를 전달한다.
|
||||
- `PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_CANCEL_CURRENT`는 유지된다.
|
||||
- 테스트 예시:
|
||||
```kotlin
|
||||
package kr.co.vividnext.sodalive.audio_content
|
||||
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
|
||||
class AudioContentPlayServiceNotificationRouteSourceTest {
|
||||
private val source = File(
|
||||
"src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentPlayService.kt"
|
||||
).readText()
|
||||
|
||||
@Test
|
||||
fun `기존 오디오 알림은 상세 route와 content id를 전달한다`() {
|
||||
assertTrue(source.contains("MainV2Activity.EXTRA_AUDIO_NOTIFICATION_ROUTE"))
|
||||
assertTrue(source.contains("MainV2Activity.ROUTE_AUDIO_DETAIL"))
|
||||
assertTrue(source.contains("Constants.EXTRA_AUDIO_CONTENT_ID"))
|
||||
assertTrue(source.contains("PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_CANCEL_CURRENT"))
|
||||
}
|
||||
}
|
||||
```
|
||||
- 검증:
|
||||
- Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.audio_content.AudioContentPlayServiceNotificationRouteSourceTest"`
|
||||
- Expected: 구현 전에는 `AssertionError`로 실패한다.
|
||||
- 검증 기록:
|
||||
- 2026-07-06: `AudioContentPlayServiceNotificationRouteSourceTest.kt`를 추가했다. 초기 병렬 Gradle 실행 중 KSP 임시 산출물 EOF로 테스트 assertion 전 빌드 실패가 발생해, 구현 후 순차 재실행으로 테스트 PASS를 확인했다.
|
||||
|
||||
- [x] **Task 3.5: `AudioContentPlayService` content pending intent에 detail route와 content id 추가**
|
||||
- 수정:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentPlayService.kt`
|
||||
- 구현 방향:
|
||||
```kotlin
|
||||
val intent = Intent(this, MainV2Activity::class.java).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||
putExtra(
|
||||
MainV2Activity.EXTRA_AUDIO_NOTIFICATION_ROUTE,
|
||||
MainV2Activity.ROUTE_AUDIO_DETAIL
|
||||
)
|
||||
contentId?.let {
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it)
|
||||
}
|
||||
}
|
||||
```
|
||||
- 검증:
|
||||
- Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.audio_content.AudioContentPlayServiceNotificationRouteSourceTest"`
|
||||
- Expected: PASS
|
||||
- 검증 기록:
|
||||
- 2026-07-06: `AudioContentPlayService.kt`의 notification content intent에 `ROUTE_AUDIO_DETAIL`과 현재 `contentId` extra를 추가했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.audio_content.AudioContentPlayServiceNotificationRouteSourceTest"` PASS.
|
||||
|
||||
### Phase 4: 통합 검증
|
||||
|
||||
- [x] **Task 4.1: source-level test 실행**
|
||||
- 실행:
|
||||
```bash
|
||||
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.MainV2AudioNotificationRouteSourceTest"
|
||||
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerServiceNotificationRouteSourceTest"
|
||||
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.audio_content.AudioContentPlayServiceNotificationRouteSourceTest"
|
||||
```
|
||||
- 기대 결과:
|
||||
- 모두 PASS한다.
|
||||
- 검증 기록:
|
||||
- 2026-07-06: 세 source-level test를 순차 실행해 모두 PASS를 확인했다. 병렬 실행 시 Gradle manifest/KSP 임시 산출물 경합으로 빌드 단계 실패가 있었으나, 순차 실행에서는 재현되지 않았다.
|
||||
|
||||
- [x] **Task 4.2: 컴파일과 ktlint 확인**
|
||||
- 실행:
|
||||
```bash
|
||||
./gradlew :app:compileDebugKotlin
|
||||
./gradlew :app:ktlintCheck
|
||||
git diff --check
|
||||
```
|
||||
- 기대 결과:
|
||||
- 모두 PASS한다.
|
||||
- 검증 기록:
|
||||
- 2026-07-06: `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check` 모두 PASS를 확인했다. `ktlintCheck` 과정에서 이번 변경으로 검사 대상이 된 `audio_content` 패키지 파일의 기존 underscore package-name 규칙 위반은 저장소 내 기존 suppress 관례와 동일하게 file-level suppress로 처리했다.
|
||||
|
||||
- [x] **Task 4.3: 수동 확인**
|
||||
- 확인:
|
||||
- 로그인 상태에서 `AudioContentPlayerService` 기반 오디오를 재생하면 `MainV2Activity` 하단에 미니 플레이어가 표시된다.
|
||||
- 미니 플레이어의 title, artist, artwork, play/pause icon이 현재 재생 상태와 맞다.
|
||||
- 미니 플레이어 터치 시 `AudioContentPlayerFragment`가 표시된다.
|
||||
- 미니 플레이어 stop 터치 시 재생 서비스가 종료되고 미니 플레이어가 숨겨진다.
|
||||
- Notification Shade에서 `AudioContentPlayerService` 알림 본문 터치 시 `MainV2Activity`가 열리고 player fragment가 표시된다.
|
||||
- Notification Shade에서 `AudioContentPlayService` 알림 본문 터치 시 `AudioContentDetailActivity`가 열리고 현재 content id가 전달된다.
|
||||
- `MainV2Activity.newChatDmIntent()`로 진입한 채팅 필터 흐름이 기존처럼 동작한다.
|
||||
- 콘텐츠 딥링크 bundle로 진입하면 기존처럼 `AudioContentDetailActivity`로 이동한다.
|
||||
- 검증 기록:
|
||||
- 2026-07-06: 연결된 디바이스(`adb devices`)에서 debug 앱 설치를 확인했고, `adb shell monkey -p kr.co.vividnext.sodalive.debug -c android.intent.category.LAUNCHER 1`로 일반 앱 런처 진입이 crash 없이 실행됨을 확인했다.
|
||||
- 2026-07-06: `adb shell am start -W -n kr.co.vividnext.sodalive.debug/kr.co.vividnext.sodalive.v2.main.MainV2Activity --es extra_audio_notification_route audio_player` 직접 실행은 `MainV2Activity`가 exported가 아니어서 OS `SecurityException`으로 차단됐다. 실제 알림 PendingIntent는 앱 내부에서 생성되므로 route 계약은 source-level test와 compile로 검증했다.
|
||||
- 2026-07-06: 로그인 세션과 실제 오디오 재생 상태를 요구하는 미니 플레이어 UI/Notification Shade end-to-end 확인은 현재 자동 실행 환경에서 완료하지 못했다.
|
||||
|
||||
---
|
||||
|
||||
## Verification Log
|
||||
- 2026-07-06: 문서 작성 단계에서는 코드 구현과 Gradle 검증을 수행하지 않았다. `MainActivity`, `MainV2Activity`, `activity_main_v2.xml`, `AudioContentPlayerService`, `AudioContentPlayService`를 정적으로 확인해 PRD와 계획/TASK 문서만 작성했다.
|
||||
|
||||
- 2026-07-06: 구현 완료 후 route source-level test 3개, `compileDebugKotlin`, `ktlintCheck`, `git diff --check`를 실행했다. 일반 앱 런처 표면은 연결 디바이스에서 확인했고, non-exported Activity 직접 route 실행은 OS 권한으로 차단되어 source-level test로 계약을 검증했다.
|
||||
- 2026-07-06: 리뷰 게이트에서 비로그인 알림 진입 시 route가 로그인 가드 밖에서 소비되지 않는 차단 이슈가 발견되어 `MainV2Activity`의 route 처리 순서를 수정했다. `MainV2AudioNotificationRouteSourceTest`에 route handler가 `isLoggedIn()`보다 먼저 평가되는 회귀 검증을 추가했고, route source-level test 3개, `compileDebugKotlin`, `ktlintCheck`, `git diff --check`를 재실행해 모두 PASS를 확인했다.
|
||||
|
||||
### Phase 5: `AudioContentPlayService` 기반 MainV2 미니 플레이어 표시 보완
|
||||
|
||||
- [x] **Task 5.1: `MainV2Activity` legacy 오디오 미니 플레이어 source test 작성**
|
||||
- 생성/수정:
|
||||
- `app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2ActivitySourceTest.kt`
|
||||
- 검증 기준:
|
||||
- `MainV2Activity`가 `Constants.ACTION_MAIN_AUDIO_CONTENT_RECEIVER` receiver를 등록/해제한다.
|
||||
- receiver가 `Constants.EXTRA_AUDIO_CONTENT_SHOWING`, `Constants.EXTRA_AUDIO_CONTENT_ID`, title, nickname, cover image, playing 상태를 읽는다.
|
||||
- 표시 상태에서는 `binding.clMiniPlayer.visibility = View.VISIBLE`을 설정한다.
|
||||
- 미니 플레이어 터치 시 `AudioContentDetailActivity`로 `Constants.EXTRA_AUDIO_CONTENT_ID`를 전달한다.
|
||||
- play/pause/stop 버튼은 `AudioContentPlayService.MusicAction`을 전달한다.
|
||||
- 검증 기록:
|
||||
- 2026-07-06: `MainV2ActivitySourceTest`에 `ACTION_MAIN_AUDIO_CONTENT_RECEIVER` receiver 계약을 추가했다. 구현 전 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.MainV2ActivitySourceTest"` 실행 시 `AssertionError`로 실패해 RED를 확인했다.
|
||||
|
||||
- [x] **Task 5.2: `MainV2Activity`에 `AudioContentPlayService` receiver 연결**
|
||||
- 수정:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt`
|
||||
- 구현 방향:
|
||||
- `MainActivity.AudioContentReceiver`의 동작을 기준으로 신규 UI를 만들지 않고 `activity_main_v2.xml`의 기존 `clMiniPlayer` 뷰를 재사용한다.
|
||||
- `onResume()`에서 `Constants.ACTION_MAIN_AUDIO_CONTENT_RECEIVER` receiver를 등록하고, 기존 `AudioContentPlayService.MusicAction.INIT` 호출은 유지한다.
|
||||
- `onPause()`에서 receiver를 해제한다.
|
||||
- `isShowing == true`이면 `clMiniPlayer`를 하단 탭 위에 표시하고 title, nickname, cover, play/pause, stop을 반영한다.
|
||||
- `isShowing != true`이면 `clMiniPlayer`를 숨기고 UI를 초기화한다.
|
||||
- 주의:
|
||||
- 레거시 서비스 파일은 추가 수정하지 않는다.
|
||||
- XML 레이아웃과 디자인은 변경하지 않는다.
|
||||
- `AudioContentPlayerService` 기반 MediaController 미니 플레이어 흐름은 유지한다.
|
||||
- 검증 기록:
|
||||
- 2026-07-06: `MainV2Activity`에 `AudioContentReceiver`를 추가하고 `onResume()`/`onPause()`에서 `Constants.ACTION_MAIN_AUDIO_CONTENT_RECEIVER`를 등록/해제하도록 연결했다. receiver는 기존 `clMiniPlayer`에 title, nickname, cover, play/pause, stop, 상세 이동을 반영한다. 레거시 서비스 파일과 XML은 추가 수정하지 않았다.
|
||||
|
||||
- [x] **Task 5.3: 후속 검증**
|
||||
- 실행:
|
||||
```bash
|
||||
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.MainV2ActivitySourceTest"
|
||||
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.MainV2AudioNotificationRouteSourceTest"
|
||||
./gradlew :app:compileDebugKotlin
|
||||
./gradlew :app:ktlintCheck
|
||||
git diff --check
|
||||
```
|
||||
- 수동 확인:
|
||||
- `AudioContentPlayService` 기반 오디오 재생 중 `MainV2Activity` 하단 탭 바로 위에 미니 플레이어가 표시된다.
|
||||
- Notification Shade의 기존 오디오 알림 본문 터치로 상세 진입 후, MainV2로 돌아왔을 때 미니 플레이어가 표시된다.
|
||||
- 미니 플레이어 터치 시 현재 `contentId`의 `AudioContentDetailActivity`로 이동한다.
|
||||
- 검증 기록:
|
||||
- 2026-07-06: `MainV2ActivitySourceTest`, `MainV2AudioNotificationRouteSourceTest`, `compileDebugKotlin`, `ktlintCheck`, `git diff --check` 모두 PASS를 확인했다. 연결 디바이스에서 `adb shell monkey -p kr.co.vividnext.sodalive.debug -c android.intent.category.LAUNCHER 1`로 debug 앱 launcher smoke 실행도 완료했다. 실제 오디오 재생 상태의 미니 플레이어 육안 확인은 사용자 환경에서 재확인이 필요하다.
|
||||
148
docs/20260706_메인_V2_미니_플레이어와_오디오_알림_이동/prd.md
Normal file
148
docs/20260706_메인_V2_미니_플레이어와_오디오_알림_이동/prd.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# PRD: 메인 V2 미니 플레이어와 오디오 알림 이동
|
||||
|
||||
## 1. Overview
|
||||
`MainV2Activity`에서 오디오 재생 중 미니 플레이어를 안정적으로 표시하고, Notification Shade의 오디오 재생 알림 터치 시 현재 재생 방식에 맞는 플레이어 또는 오디오 상세페이지로 이동한다.
|
||||
|
||||
---
|
||||
|
||||
## 2. Problem
|
||||
- `MainActivity`는 오디오 재생 상태를 관찰해 하단 미니 플레이어를 표시하고, 재생/일시정지/정지/상세 이동 동작을 제공한다.
|
||||
- `MainV2Activity`에는 `AudioContentPlayerService` 기반 `clMiniPlayer` 표시 코드가 이미 들어와 있으나, Notification Shade에서 알림을 터치했을 때 플레이어 또는 상세페이지로 분기하는 명시적인 진입 계약이 없다.
|
||||
- `AudioContentPlayerService`의 `MediaSession.setSessionActivity`와 `AudioContentPlayService.updateNotification()` 모두 현재 `MainV2Activity`를 열지만, 알림에서 온 진입인지 구분할 extra가 없어 `MainV2Activity`가 목적지를 결정할 수 없다.
|
||||
- 기존 `MainActivity`에는 두 종류의 오디오 흐름이 공존한다.
|
||||
- `AudioContentPlayerService` 기반 `clMiniPlayer`: 미니 플레이어 터치 시 `AudioContentPlayerFragment`를 표시한다.
|
||||
- `AudioContentPlayService` 기반 `rlMiniPlayer`: 미니 플레이어 터치 시 `AudioContentDetailActivity`로 이동한다.
|
||||
|
||||
---
|
||||
|
||||
## 3. Goals
|
||||
- `MainV2Activity`에서 오디오 재생 중 하단 미니 플레이어가 `MainActivity`의 `clMiniPlayer` 흐름과 동일하게 표시된다.
|
||||
- `SharedPreferenceManager.isPlayerServiceRunningFlow`가 `true`이면 지연 후 `clMiniPlayer`를 표시하고, `false`이면 숨긴다.
|
||||
- 미니 플레이어에는 현재 미디어의 title, artist, artwork, 재생/일시정지 상태가 반영된다.
|
||||
- 미니 플레이어 터치 시 `AudioContentPlayerFragment`를 표시한다.
|
||||
- 미니 플레이어 정지 버튼 터치 시 `AudioContentPlayerService`에 `STOP_SERVICE` action을 전달한다.
|
||||
- Notification Shade에서 `AudioContentPlayerService` 알림을 터치하면 `MainV2Activity`가 열리고 `AudioContentPlayerFragment`를 표시한다.
|
||||
- Notification Shade에서 `AudioContentPlayService` 알림을 터치하면 `MainV2Activity`가 열리고 현재 `contentId`의 `AudioContentDetailActivity`로 이동한다.
|
||||
- 알림 터치 분기는 explicit extra로 구분해 일반 앱 진입, 딥링크, 채팅 필터 진입과 충돌하지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 4. Non-Goals
|
||||
- 이번 요청에서는 코드, 리소스, 레이아웃을 구현하지 않는다. 문서만 생성한다.
|
||||
- 미니 플레이어의 디자인, 높이, 색상, 아이콘, XML 구조를 변경하지 않는다.
|
||||
- `AudioContentPlayerFragment`의 UI/재생 기능을 변경하지 않는다.
|
||||
- `AudioContentDetailActivity` 내부 동작을 변경하지 않는다.
|
||||
- 알림 액션 버튼의 play/pause/stop 동작을 변경하지 않는다.
|
||||
- 신규 오디오 플레이어 화면을 만들지 않는다.
|
||||
- 딥링크 라우팅 전체 구조를 리팩터링하지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 5. Target Users
|
||||
- v2 메인 화면을 사용하면서 오디오 재생 상태를 하단에서 확인하고 제어하려는 사용자.
|
||||
- Notification Shade의 재생 알림을 터치해 현재 듣는 콘텐츠 또는 플레이어로 즉시 복귀하려는 사용자.
|
||||
- 기존 `MainActivity` 동작을 기준으로 `MainV2Activity`의 오디오 UX를 맞춰야 하는 Android 개발자.
|
||||
|
||||
---
|
||||
|
||||
## 6. User Stories
|
||||
- 사용자는 v2 메인 화면에서 오디오가 재생 중이면 하단 미니 플레이어를 보고 싶다.
|
||||
- 사용자는 미니 플레이어에서 재생/일시정지와 정지를 바로 제어하고 싶다.
|
||||
- 사용자는 미니 플레이어를 터치하면 현재 재생 목록/플레이어 화면을 보고 싶다.
|
||||
- 사용자는 새 플레이어 알림을 터치하면 전체 플레이어로 돌아가고 싶다.
|
||||
- 사용자는 기존 오디오 콘텐츠 알림을 터치하면 해당 오디오 상세페이지로 이동하고 싶다.
|
||||
|
||||
---
|
||||
|
||||
## 7. Core Features
|
||||
|
||||
### Feature A. MainV2Activity 미니 플레이어 표시
|
||||
|
||||
#### Requirements
|
||||
- 구현 기준은 `MainActivity`의 다음 흐름이다.
|
||||
- `SharedPreferenceManager.isPlayerServiceRunningFlow.collect`
|
||||
- `handler.postDelayed(showMiniPlayerRunnable, 1500)`
|
||||
- `initAndVisibleMiniPlayer()`
|
||||
- `connectPlayerService()`
|
||||
- `MediaController`의 metadata/playWhenReady listener
|
||||
- `deInitMiniPlayer()`
|
||||
- `MainV2Activity`는 이미 같은 이름과 유사한 흐름의 메서드를 보유하므로, 구현 시 기존 코드를 유지하고 누락/불일치만 보완한다.
|
||||
- `activity_main_v2.xml`의 `cl_mini_player`, `iv_player_cover`, `tv_player_title`, `tv_player_nickname`, `iv_player_play_or_pause`, `iv_player_stop`을 그대로 사용한다.
|
||||
- 미니 플레이어 터치 동작은 `showPlayerFragment()`를 호출해 `AudioContentPlayerFragment(screenWidth, arrayListOf())`를 표시한다.
|
||||
- 정지 버튼은 `AudioContentPlayerService`에 `action = "STOP_SERVICE"`를 전달한다.
|
||||
- `MediaController` 연결 실패 시 crash 없이 로그만 남기고, controller future와 controller 참조를 정리한다.
|
||||
|
||||
#### Edge Cases
|
||||
- 로그인하지 않은 상태에서는 기존 `MainV2Activity.isLoggedIn()` 조건상 player state 관찰을 시작하지 않는다.
|
||||
- 서비스가 종료되면 pending runnable을 제거하고 미니 플레이어를 숨기며 controller를 release한다.
|
||||
- `MediaController`가 이미 연결되어 있거나 연결 중이면 중복 연결하지 않는다.
|
||||
- metadata가 null이면 title/artist/artwork를 임의 값으로 덮어쓰지 않는다.
|
||||
|
||||
### Feature B. Notification Shade 알림 터치 이동
|
||||
|
||||
#### Requirements
|
||||
- 알림 터치 진입은 일반 `MainV2Activity` 실행과 구분되는 explicit extra로 표시한다.
|
||||
- 권장 extra 계약:
|
||||
- `EXTRA_AUDIO_NOTIFICATION_ROUTE = "extra_audio_notification_route"`
|
||||
- `ROUTE_AUDIO_PLAYER = "audio_player"`
|
||||
- `ROUTE_AUDIO_DETAIL = "audio_detail"`
|
||||
- 상세 이동 시 `Constants.EXTRA_AUDIO_CONTENT_ID`를 함께 전달한다.
|
||||
- `AudioContentPlayerService.initMediaSession()`의 `setSessionActivity` pending intent는 `ROUTE_AUDIO_PLAYER`를 전달한다.
|
||||
- `AudioContentPlayService.updateNotification()`의 content pending intent는 `ROUTE_AUDIO_DETAIL`과 현재 `contentId`를 전달한다.
|
||||
- `MainV2Activity.onCreate()`와 `onNewIntent()`는 채팅 필터 처리 이후, 일반 딥링크 처리 전 또는 별도 우선순위로 알림 route를 소비한다.
|
||||
- `ROUTE_AUDIO_PLAYER`를 받으면 로그인 상태에서 `AudioContentPlayerFragment`를 표시한다.
|
||||
- `ROUTE_AUDIO_DETAIL`과 유효한 `contentId > 0`을 받으면 `AudioContentDetailActivity`로 이동한다.
|
||||
- 알림 route를 처리한 뒤에는 intent extra를 제거해 회전, 재전달, `setIntent()` 후 중복 처리를 방지한다.
|
||||
|
||||
#### Edge Cases
|
||||
- `ROUTE_AUDIO_DETAIL`인데 `contentId`가 없거나 0이면 상세페이지 이동을 하지 않고 일반 메인 진입으로 둔다.
|
||||
- `ROUTE_AUDIO_PLAYER`인데 서비스가 이미 종료되어 미니 플레이어/controller를 연결할 수 없으면 player fragment 표시를 생략하고 일반 메인 진입으로 둔다.
|
||||
- `EXTRA_CHAT_FILTER`가 있는 `MainV2Activity` intent는 기존 채팅 탭 초기 필터 동작을 우선 유지한다.
|
||||
- `Constants.EXTRA_DATA` 딥링크 bundle이 있는 경우에는 알림 route와 충돌하지 않도록 알림 route 처리 후에만 딥링크를 실행한다.
|
||||
- 비로그인 상태에서 알림을 터치하면 기존 로그인 유도 정책을 따른다. 상세페이지 직접 이동은 로그인 이후 처리 대상이며, 이번 범위에서 신규 deferred route 저장소는 만들지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 8. UX / UI Expectations
|
||||
- 미니 플레이어는 `activity_main_v2.xml`에서 이미 정의된 위치처럼 bottom navigation 바로 위에 표시된다.
|
||||
- 미니 플레이어가 보이면 `fl_container`는 미니 플레이어 위쪽까지만 차지해 하단 UI와 겹치지 않는다.
|
||||
- Notification Shade에서 알림을 터치하면 사용자가 기대하는 현재 오디오 맥락으로 즉시 복귀한다.
|
||||
- 새 `AudioContentPlayerService` 알림: 플레이어 바텀시트.
|
||||
- 기존 `AudioContentPlayService` 알림: 오디오 상세페이지.
|
||||
- 알림 액션 버튼 터치와 알림 본문 터치의 동작은 구분된다.
|
||||
|
||||
---
|
||||
|
||||
## 9. Technical Constraints
|
||||
- Android Kotlin, XML View, ViewBinding, Media3 `MediaController`/`MediaSession` 구조를 유지한다.
|
||||
- `MainV2Activity`의 기존 bottom navigation, chat filter, deeplink 처리 순서를 깨지 않는다.
|
||||
- 신규 `Activity`, `Fragment`, `ViewModel`은 만들지 않는다.
|
||||
- 신규 route 상수는 구현 시 `MainV2Activity` companion object 또는 v2 main 하위의 좁은 범위에 둔다.
|
||||
- 서비스 pending intent에서 참조해야 하므로 `MainV2Activity`에 둘 경우 `private`이 아닌 `const val`로 정의한다.
|
||||
- `AudioContentPlayerService`, `AudioContentPlayService`는 `kr.co.vividnext.sodalive.v2` 하위가 아니므로 구현 전에 레거시 파일 수정 필요성을 사용자에게 확인한다.
|
||||
- 레거시 파일 수정을 승인받지 못하면 알림 터치 진입을 안정적으로 구분할 수 없으므로 Feature B는 보류한다.
|
||||
- 관련 근거 파일:
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/main/MainActivity.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt`
|
||||
- `app/src/main/res/layout/activity_main.xml`
|
||||
- `app/src/main/res/layout/activity_main_v2.xml`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/audio_content/player/AudioContentPlayerService.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentPlayService.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/audio_content/detail/AudioContentDetailActivity.kt`
|
||||
- `app/src/main/java/kr/co/vividnext/sodalive/audio_content/player/AudioContentPlayerFragment.kt`
|
||||
|
||||
---
|
||||
|
||||
## 10. Metrics
|
||||
- 정량 지표는 이번 범위에 추가하지 않는다.
|
||||
- 검증 기준은 다음 동작의 수동/정적 확인이다.
|
||||
- `MainV2Activity`에서 오디오 재생 중 미니 플레이어가 표시된다.
|
||||
- 미니 플레이어 터치 시 player fragment가 표시된다.
|
||||
- 새 플레이어 알림 터치 시 player fragment가 표시된다.
|
||||
- 기존 오디오 알림 터치 시 `AudioContentDetailActivity`로 이동한다.
|
||||
- 일반 딥링크, 채팅 필터 진입이 회귀하지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 11. Open Questions
|
||||
- 없음. 다만 실제 구현 시 `AudioContentPlayerService`, `AudioContentPlayService` 수정은 레거시 파일 수정에 해당하므로 구현 착수 전에 사용자 승인을 받아야 한다.
|
||||
Reference in New Issue
Block a user