Add landscape layout support for main player screen

Refactors PlayerContent into reusable components and adds orientation
detection. In landscape mode, artwork appears on the left with station
info and controls on the right for better horizontal space usage.
This commit is contained in:
vesp 2025-11-23 00:00:15 +01:00
parent 074ed30b8e
commit 6279c2412e

View File

@ -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,10 +271,106 @@ private fun PlayerContent(
) {
Spacer(modifier = Modifier.height(16.dp))
// Station artwork
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(200.dp)
.size(size)
.clickable { onShowStationPicker() },
shape = RoundedCornerShape(16.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
@ -248,9 +379,9 @@ private fun PlayerContent(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
if (!radioBrowserInfo?.favicon.isNullOrBlank()) {
if (!favicon.isNullOrBlank()) {
AsyncImage(
model = radioBrowserInfo?.favicon,
model = favicon,
contentDescription = "Station logo",
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
@ -265,16 +396,24 @@ private fun PlayerContent(
Icon(
imageVector = Icons.Default.Radio,
contentDescription = null,
modifier = Modifier.size(80.dp),
modifier = Modifier.size(size / 2.5f),
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
}
}
}
}
}
Spacer(modifier = Modifier.height(24.dp))
@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,14 +480,19 @@ private fun PlayerContent(
}
}
}
}
}
Spacer(modifier = Modifier.weight(1f))
// Playback controls
@Composable
private fun PlaybackControls(
isPlaying: Boolean,
onTogglePlayPause: () -> Unit,
onNextStation: () -> Unit,
onPreviousStation: () -> Unit
) {
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
verticalAlignment = Alignment.CenterVertically
) {
FilledIconButton(
onClick = onPreviousStation,
@ -371,8 +515,8 @@ private fun PlayerContent(
modifier = Modifier.size(72.dp)
) {
Icon(
imageVector = if (status.isPlaying) Icons.Default.Pause else Icons.Default.PlayArrow,
contentDescription = if (status.isPlaying) "Pause" else "Play",
imageVector = if (isPlaying) Icons.Default.Pause else Icons.Default.PlayArrow,
contentDescription = if (isPlaying) "Pause" else "Play",
modifier = Modifier.size(40.dp)
)
}
@ -393,10 +537,13 @@ private fun PlayerContent(
)
}
}
}
Spacer(modifier = Modifier.height(32.dp))
// Volume control
@Composable
private fun VolumeControl(
volume: Int,
onVolumeChange: (Int) -> Unit
) {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
@ -422,23 +569,20 @@ private fun PlayerContent(
)
Spacer(modifier = Modifier.weight(1f))
Text(
text = "${(status.volume * 100 / 254)}%",
text = "${(volume * 100 / 254)}%",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.primary
)
}
Spacer(modifier = Modifier.height(8.dp))
Slider(
value = status.volume.toFloat(),
value = volume.toFloat(),
onValueChange = { onVolumeChange(it.toInt()) },
valueRange = 0f..254f,
modifier = Modifier.fillMaxWidth()
)
}
}
Spacer(modifier = Modifier.height(16.dp))
}
}
@OptIn(ExperimentalMaterial3Api::class)