diff --git a/app/src/main/java/cz/bugsy/karemote/ui/screens/main/MainScreen.kt b/app/src/main/java/cz/bugsy/karemote/ui/screens/main/MainScreen.kt index 8caa2aa..290ef77 100644 --- a/app/src/main/java/cz/bugsy/karemote/ui/screens/main/MainScreen.kt +++ b/app/src/main/java/cz/bugsy/karemote/ui/screens/main/MainScreen.kt @@ -1,5 +1,6 @@ package cz.bugsy.karemote.ui.screens.main +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -45,6 +46,7 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -226,6 +228,39 @@ private fun PlayerContent( onNextStation: () -> Unit, onPreviousStation: () -> Unit, onShowStationPicker: () -> Unit +) { + val configuration = LocalConfiguration.current + val isLandscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE + + if (isLandscape) { + LandscapePlayerContent( + uiState = uiState, + onTogglePlayPause = onTogglePlayPause, + onVolumeChange = onVolumeChange, + onNextStation = onNextStation, + onPreviousStation = onPreviousStation, + onShowStationPicker = onShowStationPicker + ) + } else { + PortraitPlayerContent( + uiState = uiState, + onTogglePlayPause = onTogglePlayPause, + onVolumeChange = onVolumeChange, + onNextStation = onNextStation, + onPreviousStation = onPreviousStation, + onShowStationPicker = onShowStationPicker + ) + } +} + +@Composable +private fun PortraitPlayerContent( + uiState: MainUiState, + onTogglePlayPause: () -> Unit, + onVolumeChange: (Int) -> Unit, + onNextStation: () -> Unit, + onPreviousStation: () -> Unit, + onShowStationPicker: () -> Unit ) { val status = uiState.karadioStatus val radioBrowserInfo = uiState.radioBrowserInfo @@ -236,45 +271,149 @@ private fun PlayerContent( ) { Spacer(modifier = Modifier.height(16.dp)) - // Station artwork - Card( - modifier = Modifier - .size(200.dp) - .clickable { onShowStationPicker() }, - shape = RoundedCornerShape(16.dp), - elevation = CardDefaults.cardElevation(defaultElevation = 8.dp) - ) { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - if (!radioBrowserInfo?.favicon.isNullOrBlank()) { - AsyncImage( - model = radioBrowserInfo?.favicon, - contentDescription = "Station logo", - modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.Crop - ) - } else { - Box( - modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colorScheme.primaryContainer), - contentAlignment = Alignment.Center - ) { - Icon( - imageVector = Icons.Default.Radio, - contentDescription = null, - modifier = Modifier.size(80.dp), - tint = MaterialTheme.colorScheme.onPrimaryContainer - ) - } - } - } - } + StationArtwork( + favicon = radioBrowserInfo?.favicon, + size = 200.dp, + onShowStationPicker = onShowStationPicker + ) Spacer(modifier = Modifier.height(24.dp)) + StationInfo( + status = status, + radioBrowserInfo = radioBrowserInfo, + onShowStationPicker = onShowStationPicker + ) + + Spacer(modifier = Modifier.weight(1f)) + + PlaybackControls( + isPlaying = status.isPlaying, + onTogglePlayPause = onTogglePlayPause, + onNextStation = onNextStation, + onPreviousStation = onPreviousStation + ) + + Spacer(modifier = Modifier.height(32.dp)) + + VolumeControl( + volume = status.volume, + onVolumeChange = onVolumeChange + ) + + Spacer(modifier = Modifier.height(16.dp)) + } +} + +@Composable +private fun LandscapePlayerContent( + uiState: MainUiState, + onTogglePlayPause: () -> Unit, + onVolumeChange: (Int) -> Unit, + onNextStation: () -> Unit, + onPreviousStation: () -> Unit, + onShowStationPicker: () -> Unit +) { + val status = uiState.karadioStatus + val radioBrowserInfo = uiState.radioBrowserInfo + + Row( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + // Left side: Artwork + StationArtwork( + favicon = radioBrowserInfo?.favicon, + size = 180.dp, + onShowStationPicker = onShowStationPicker + ) + + Spacer(modifier = Modifier.width(24.dp)) + + // Right side: Info and controls + Column( + modifier = Modifier.weight(1f), + horizontalAlignment = Alignment.CenterHorizontally + ) { + StationInfo( + status = status, + radioBrowserInfo = radioBrowserInfo, + onShowStationPicker = onShowStationPicker + ) + + Spacer(modifier = Modifier.height(16.dp)) + + PlaybackControls( + isPlaying = status.isPlaying, + onTogglePlayPause = onTogglePlayPause, + onNextStation = onNextStation, + onPreviousStation = onPreviousStation + ) + + Spacer(modifier = Modifier.height(16.dp)) + + VolumeControl( + volume = status.volume, + onVolumeChange = onVolumeChange + ) + } + } +} + +@Composable +private fun StationArtwork( + favicon: String?, + size: androidx.compose.ui.unit.Dp, + onShowStationPicker: () -> Unit +) { + Card( + modifier = Modifier + .size(size) + .clickable { onShowStationPicker() }, + shape = RoundedCornerShape(16.dp), + elevation = CardDefaults.cardElevation(defaultElevation = 8.dp) + ) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + if (!favicon.isNullOrBlank()) { + AsyncImage( + model = favicon, + contentDescription = "Station logo", + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop + ) + } else { + Box( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.primaryContainer), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.Default.Radio, + contentDescription = null, + modifier = Modifier.size(size / 2.5f), + tint = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + } + } + } +} + +@Composable +private fun StationInfo( + status: cz.bugsy.karemote.data.model.KaradioStatus, + radioBrowserInfo: cz.bugsy.karemote.data.model.RadioBrowserStation?, + onShowStationPicker: () -> Unit +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { // Station name Text( text = status.stationName.ifBlank { "No Station" }, @@ -341,103 +480,108 @@ private fun PlayerContent( } } } + } +} - Spacer(modifier = Modifier.weight(1f)) - - // Playback controls - Row( - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { - FilledIconButton( - onClick = onPreviousStation, - modifier = Modifier.size(56.dp), - colors = IconButtonDefaults.filledIconButtonColors( - containerColor = MaterialTheme.colorScheme.secondaryContainer - ) - ) { - Icon( - imageVector = Icons.Default.SkipPrevious, - contentDescription = "Previous station", - modifier = Modifier.size(32.dp) - ) - } - - Spacer(modifier = Modifier.width(16.dp)) - - FilledIconButton( - onClick = onTogglePlayPause, - modifier = Modifier.size(72.dp) - ) { - Icon( - imageVector = if (status.isPlaying) Icons.Default.Pause else Icons.Default.PlayArrow, - contentDescription = if (status.isPlaying) "Pause" else "Play", - modifier = Modifier.size(40.dp) - ) - } - - Spacer(modifier = Modifier.width(16.dp)) - - FilledIconButton( - onClick = onNextStation, - modifier = Modifier.size(56.dp), - colors = IconButtonDefaults.filledIconButtonColors( - containerColor = MaterialTheme.colorScheme.secondaryContainer - ) - ) { - Icon( - imageVector = Icons.Default.SkipNext, - contentDescription = "Next station", - modifier = Modifier.size(32.dp) - ) - } - } - - Spacer(modifier = Modifier.height(32.dp)) - - // Volume control - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceVariant +@Composable +private fun PlaybackControls( + isPlaying: Boolean, + onTogglePlayPause: () -> Unit, + onNextStation: () -> Unit, + onPreviousStation: () -> Unit +) { + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + FilledIconButton( + onClick = onPreviousStation, + modifier = Modifier.size(56.dp), + colors = IconButtonDefaults.filledIconButtonColors( + containerColor = MaterialTheme.colorScheme.secondaryContainer ) ) { - Column( - modifier = Modifier.padding(16.dp) - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.VolumeUp, - contentDescription = null, - tint = MaterialTheme.colorScheme.primary - ) - Spacer(modifier = Modifier.width(8.dp)) - Text( - text = "Volume", - style = MaterialTheme.typography.titleMedium - ) - Spacer(modifier = Modifier.weight(1f)) - Text( - text = "${(status.volume * 100 / 254)}%", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.primary - ) - } - Spacer(modifier = Modifier.height(8.dp)) - Slider( - value = status.volume.toFloat(), - onValueChange = { onVolumeChange(it.toInt()) }, - valueRange = 0f..254f, - modifier = Modifier.fillMaxWidth() - ) - } + Icon( + imageVector = Icons.Default.SkipPrevious, + contentDescription = "Previous station", + modifier = Modifier.size(32.dp) + ) } - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.width(16.dp)) + + FilledIconButton( + onClick = onTogglePlayPause, + modifier = Modifier.size(72.dp) + ) { + Icon( + imageVector = if (isPlaying) Icons.Default.Pause else Icons.Default.PlayArrow, + contentDescription = if (isPlaying) "Pause" else "Play", + modifier = Modifier.size(40.dp) + ) + } + + Spacer(modifier = Modifier.width(16.dp)) + + FilledIconButton( + onClick = onNextStation, + modifier = Modifier.size(56.dp), + colors = IconButtonDefaults.filledIconButtonColors( + containerColor = MaterialTheme.colorScheme.secondaryContainer + ) + ) { + Icon( + imageVector = Icons.Default.SkipNext, + contentDescription = "Next station", + modifier = Modifier.size(32.dp) + ) + } + } +} + +@Composable +private fun VolumeControl( + volume: Int, + onVolumeChange: (Int) -> Unit +) { + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant + ) + ) { + Column( + modifier = Modifier.padding(16.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.VolumeUp, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = "Volume", + style = MaterialTheme.typography.titleMedium + ) + Spacer(modifier = Modifier.weight(1f)) + Text( + text = "${(volume * 100 / 254)}%", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.primary + ) + } + Spacer(modifier = Modifier.height(8.dp)) + Slider( + value = volume.toFloat(), + onValueChange = { onVolumeChange(it.toInt()) }, + valueRange = 0f..254f, + modifier = Modifier.fillMaxWidth() + ) + } } }