feat(android): OutputPickerViewModel - sheet lifecycle + selection
android / Build + lint + test (push) Has been cancelled
android / Build + lint + test (push) Has been cancelled
Bluetooth slice (3/5). HiltViewModel projecting the controller's routesState Flow plus a sheetVisible MutableStateFlow that owns the sheet's open/close state. onChipTapped + onSheetDismissed forward to the controller's discovery toggle so active MediaRouter discovery only runs while the sheet is visible (battery cost). Compose UI + NowPlaying wiring land next.
This commit is contained in:
+60
@@ -0,0 +1,60 @@
|
|||||||
|
package com.fabledsword.minstrel.player.output
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NowPlaying-scoped projection over [OutputPickerController]. The
|
||||||
|
* controller is the long-lived Hilt singleton (its MediaRouter
|
||||||
|
* callback must not churn with screen lifecycle); this ViewModel is
|
||||||
|
* a thin lens over its [OutputPickerController.routesState] Flow
|
||||||
|
* plus the sheet's visibility state.
|
||||||
|
*
|
||||||
|
* Sheet open/close are forwarded to the controller's discovery
|
||||||
|
* toggle so active MediaRouter discovery only runs while the sheet
|
||||||
|
* is actually visible.
|
||||||
|
*/
|
||||||
|
@HiltViewModel
|
||||||
|
class OutputPickerViewModel @Inject constructor(
|
||||||
|
private val controller: OutputPickerController,
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
val routes: StateFlow<RouteSnapshot> = controller.routesState
|
||||||
|
.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.WhileSubscribed(STOP_TIMEOUT_MS),
|
||||||
|
initialValue = controller.routesState.value,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val sheetVisibleInternal = MutableStateFlow(false)
|
||||||
|
val sheetVisible: StateFlow<Boolean> = sheetVisibleInternal.asStateFlow()
|
||||||
|
|
||||||
|
fun onChipTapped() {
|
||||||
|
sheetVisibleInternal.value = true
|
||||||
|
controller.upgradeDiscovery()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onSheetDismissed() {
|
||||||
|
sheetVisibleInternal.value = false
|
||||||
|
controller.downgradeDiscovery()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onRouteSelected(route: OutputRoute) {
|
||||||
|
controller.select(route)
|
||||||
|
sheetVisibleInternal.value = false
|
||||||
|
controller.downgradeDiscovery()
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
// SharingStarted timeout so quick screen-orientation changes
|
||||||
|
// don't tear down + re-subscribe the controller's Flow.
|
||||||
|
const val STOP_TIMEOUT_MS = 5_000L
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user