feat(ui): integrate MiniPlayer into Scaffold with real player state

This commit is contained in:
nk
2026-06-01 13:12:51 +03:00
parent 9e3ce0f1e4
commit aa287f7588
2 changed files with 57 additions and 13 deletions

View File

@@ -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() }
) )
} }
} }

View File

@@ -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 {