feat(ui): integrate MiniPlayer into Scaffold with real player state
This commit is contained in:
@@ -8,14 +8,17 @@ import androidx.compose.foundation.layout.*
|
|||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import com.radiola.ui.components.MiniPlayer
|
||||||
import com.radiola.ui.favorites.FavoritesScreen
|
import com.radiola.ui.favorites.FavoritesScreen
|
||||||
import com.radiola.ui.history.HistoryScreen
|
import com.radiola.ui.history.HistoryScreen
|
||||||
import com.radiola.ui.navigation.BottomNavBar
|
import com.radiola.ui.navigation.BottomNavBar
|
||||||
import com.radiola.ui.navigation.NavDestinations
|
import com.radiola.ui.navigation.NavDestinations
|
||||||
import com.radiola.ui.player.PlayerBottomSheet
|
import com.radiola.ui.player.PlayerBottomSheet
|
||||||
|
import com.radiola.ui.player.PlayerViewModel
|
||||||
import com.radiola.ui.settings.SettingsScreen
|
import com.radiola.ui.settings.SettingsScreen
|
||||||
import com.radiola.ui.stations.StationsScreen
|
import com.radiola.ui.stations.StationsScreen
|
||||||
import com.radiola.ui.theme.RadiolaTheme
|
import com.radiola.ui.theme.RadiolaTheme
|
||||||
@@ -31,9 +34,26 @@ class MainActivity : ComponentActivity() {
|
|||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||||
var showPlayer by remember { mutableStateOf(false) }
|
var showPlayer by remember { mutableStateOf(false) }
|
||||||
|
val playerViewModel: PlayerViewModel = hiltViewModel()
|
||||||
|
val isPlaying by playerViewModel.isPlaying.collectAsState()
|
||||||
|
val currentStation by playerViewModel.currentStation.collectAsState()
|
||||||
|
val currentTrack by playerViewModel.currentTrack.collectAsState()
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
bottomBar = { BottomNavBar(navController) }
|
bottomBar = {
|
||||||
|
Column {
|
||||||
|
if (currentStation != null) {
|
||||||
|
MiniPlayer(
|
||||||
|
stationName = currentStation!!.name,
|
||||||
|
track = currentTrack,
|
||||||
|
isPlaying = isPlaying,
|
||||||
|
onClick = { showPlayer = true },
|
||||||
|
onPlayPause = { playerViewModel.togglePlayPause() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
BottomNavBar(navController)
|
||||||
|
}
|
||||||
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
NavHost(
|
NavHost(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
@@ -41,10 +61,19 @@ class MainActivity : ComponentActivity() {
|
|||||||
modifier = Modifier.padding(paddingValues)
|
modifier = Modifier.padding(paddingValues)
|
||||||
) {
|
) {
|
||||||
composable(NavDestinations.Stations.route) {
|
composable(NavDestinations.Stations.route) {
|
||||||
StationsScreen(onStationClick = { showPlayer = true })
|
StationsScreen(
|
||||||
|
onStationClick = { stationId ->
|
||||||
|
// TODO: lookup station and play
|
||||||
|
showPlayer = true
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
composable(NavDestinations.Favorites.route) {
|
composable(NavDestinations.Favorites.route) {
|
||||||
FavoritesScreen(onStationClick = { showPlayer = true })
|
FavoritesScreen(
|
||||||
|
onStationClick = { stationId ->
|
||||||
|
showPlayer = true
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
composable(NavDestinations.History.route) {
|
composable(NavDestinations.History.route) {
|
||||||
HistoryScreen()
|
HistoryScreen()
|
||||||
@@ -62,10 +91,10 @@ class MainActivity : ComponentActivity() {
|
|||||||
containerColor = MaterialTheme.colorScheme.background
|
containerColor = MaterialTheme.colorScheme.background
|
||||||
) {
|
) {
|
||||||
PlayerBottomSheet(
|
PlayerBottomSheet(
|
||||||
station = null,
|
station = currentStation,
|
||||||
track = null,
|
track = currentTrack,
|
||||||
isPlaying = false,
|
isPlaying = isPlaying,
|
||||||
onPlayPause = { }
|
onPlayPause = { playerViewModel.togglePlayPause() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.radiola.domain.model.DeeplinkService
|
|||||||
import com.radiola.domain.model.Station
|
import com.radiola.domain.model.Station
|
||||||
import com.radiola.domain.model.Track
|
import com.radiola.domain.model.Track
|
||||||
import com.radiola.domain.repository.SettingsRepository
|
import com.radiola.domain.repository.SettingsRepository
|
||||||
|
import com.radiola.domain.repository.StationRepository
|
||||||
import com.radiola.domain.usecase.GetNowPlayingUseCase
|
import com.radiola.domain.usecase.GetNowPlayingUseCase
|
||||||
import com.radiola.domain.usecase.SearchTrackInServiceUseCase
|
import com.radiola.domain.usecase.SearchTrackInServiceUseCase
|
||||||
import com.radiola.service.PlayerController
|
import com.radiola.service.PlayerController
|
||||||
@@ -17,6 +18,7 @@ import javax.inject.Inject
|
|||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class PlayerViewModel @Inject constructor(
|
class PlayerViewModel @Inject constructor(
|
||||||
private val playerController: PlayerController,
|
private val playerController: PlayerController,
|
||||||
|
private val stationRepository: StationRepository,
|
||||||
private val getNowPlayingUseCase: GetNowPlayingUseCase,
|
private val getNowPlayingUseCase: GetNowPlayingUseCase,
|
||||||
private val searchTrackInServiceUseCase: SearchTrackInServiceUseCase,
|
private val searchTrackInServiceUseCase: SearchTrackInServiceUseCase,
|
||||||
private val settingsRepository: SettingsRepository
|
private val settingsRepository: SettingsRepository
|
||||||
@@ -25,6 +27,9 @@ class PlayerViewModel @Inject constructor(
|
|||||||
val isPlaying: StateFlow<Boolean> = playerController.isPlaying
|
val isPlaying: StateFlow<Boolean> = playerController.isPlaying
|
||||||
val currentStationPrefix: StateFlow<String?> = playerController.currentStationPrefix
|
val currentStationPrefix: StateFlow<String?> = playerController.currentStationPrefix
|
||||||
|
|
||||||
|
private val _currentStation = MutableStateFlow<Station?>(null)
|
||||||
|
val currentStation: StateFlow<Station?> = _currentStation.asStateFlow()
|
||||||
|
|
||||||
private val _currentTrack = MutableStateFlow<Track?>(null)
|
private val _currentTrack = MutableStateFlow<Track?>(null)
|
||||||
val currentTrack: StateFlow<Track?> = _currentTrack.asStateFlow()
|
val currentTrack: StateFlow<Track?> = _currentTrack.asStateFlow()
|
||||||
|
|
||||||
@@ -37,9 +42,19 @@ class PlayerViewModel @Inject constructor(
|
|||||||
_enabledServices.value = DeeplinkService.entries.filter { it.serviceId in ids }
|
_enabledServices.value = DeeplinkService.entries.filter { it.serviceId in ids }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
viewModelScope.launch {
|
||||||
|
currentStationPrefix.collect { prefix ->
|
||||||
|
prefix?.let { p ->
|
||||||
|
// Find station by prefix from repository
|
||||||
|
// Note: repository only has getStationById; we use a workaround
|
||||||
|
// In real implementation, add getStationByPrefix to repository
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun play(station: Station) {
|
fun play(station: Station) {
|
||||||
|
_currentStation.value = station
|
||||||
playerController.play(station.streamUrl, station.prefix)
|
playerController.play(station.streamUrl, station.prefix)
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
getNowPlayingUseCase(station.prefix).collect { track ->
|
getNowPlayingUseCase(station.prefix).collect { track ->
|
||||||
@@ -52,12 +67,12 @@ class PlayerViewModel @Inject constructor(
|
|||||||
playerController.pause()
|
playerController.pause()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resume(station: Station) {
|
fun resume() {
|
||||||
if (playerController.currentStationPrefix.value == station.prefix) {
|
playerController.exoPlayer.play()
|
||||||
playerController.exoPlayer.play()
|
}
|
||||||
} else {
|
|
||||||
play(station)
|
fun togglePlayPause() {
|
||||||
}
|
if (isPlaying.value) pause() else resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDeeplinkUrl(track: Track, service: DeeplinkService): String {
|
fun getDeeplinkUrl(track: Track, service: DeeplinkService): String {
|
||||||
|
|||||||
Reference in New Issue
Block a user