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:
parent
f9adf3d3db
commit
44c84ab57f
@ -1,5 +1,6 @@
|
|||||||
package cz.bugsy.karemote.ui.screens.main
|
package cz.bugsy.karemote.ui.screens.main
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@ -45,6 +46,7 @@ import androidx.compose.material3.TopAppBarDefaults
|
|||||||
import androidx.compose.material3.rememberModalBottomSheetState
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
@ -226,6 +228,39 @@ private fun PlayerContent(
|
|||||||
onNextStation: () -> Unit,
|
onNextStation: () -> Unit,
|
||||||
onPreviousStation: () -> Unit,
|
onPreviousStation: () -> Unit,
|
||||||
onShowStationPicker: () -> 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 status = uiState.karadioStatus
|
||||||
val radioBrowserInfo = uiState.radioBrowserInfo
|
val radioBrowserInfo = uiState.radioBrowserInfo
|
||||||
@ -236,10 +271,106 @@ private fun PlayerContent(
|
|||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
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(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(200.dp)
|
.size(size)
|
||||||
.clickable { onShowStationPicker() },
|
.clickable { onShowStationPicker() },
|
||||||
shape = RoundedCornerShape(16.dp),
|
shape = RoundedCornerShape(16.dp),
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
|
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
|
||||||
@ -248,9 +379,9 @@ private fun PlayerContent(
|
|||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
if (!radioBrowserInfo?.favicon.isNullOrBlank()) {
|
if (!favicon.isNullOrBlank()) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = radioBrowserInfo?.favicon,
|
model = favicon,
|
||||||
contentDescription = "Station logo",
|
contentDescription = "Station logo",
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentScale = ContentScale.Crop
|
contentScale = ContentScale.Crop
|
||||||
@ -265,16 +396,24 @@ private fun PlayerContent(
|
|||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Radio,
|
imageVector = Icons.Default.Radio,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(80.dp),
|
modifier = Modifier.size(size / 2.5f),
|
||||||
tint = MaterialTheme.colorScheme.onPrimaryContainer
|
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
|
// Station name
|
||||||
Text(
|
Text(
|
||||||
text = status.stationName.ifBlank { "No Station" },
|
text = status.stationName.ifBlank { "No Station" },
|
||||||
@ -341,14 +480,19 @@ private fun PlayerContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
@Composable
|
||||||
|
private fun PlaybackControls(
|
||||||
// Playback controls
|
isPlaying: Boolean,
|
||||||
|
onTogglePlayPause: () -> Unit,
|
||||||
|
onNextStation: () -> Unit,
|
||||||
|
onPreviousStation: () -> Unit
|
||||||
|
) {
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.Center,
|
horizontalArrangement = Arrangement.Center,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
) {
|
||||||
FilledIconButton(
|
FilledIconButton(
|
||||||
onClick = onPreviousStation,
|
onClick = onPreviousStation,
|
||||||
@ -371,8 +515,8 @@ private fun PlayerContent(
|
|||||||
modifier = Modifier.size(72.dp)
|
modifier = Modifier.size(72.dp)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = if (status.isPlaying) Icons.Default.Pause else Icons.Default.PlayArrow,
|
imageVector = if (isPlaying) Icons.Default.Pause else Icons.Default.PlayArrow,
|
||||||
contentDescription = if (status.isPlaying) "Pause" else "Play",
|
contentDescription = if (isPlaying) "Pause" else "Play",
|
||||||
modifier = Modifier.size(40.dp)
|
modifier = Modifier.size(40.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -393,10 +537,13 @@ private fun PlayerContent(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(32.dp))
|
@Composable
|
||||||
|
private fun VolumeControl(
|
||||||
// Volume control
|
volume: Int,
|
||||||
|
onVolumeChange: (Int) -> Unit
|
||||||
|
) {
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = CardDefaults.cardColors(
|
colors = CardDefaults.cardColors(
|
||||||
@ -422,23 +569,20 @@ private fun PlayerContent(
|
|||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
Text(
|
Text(
|
||||||
text = "${(status.volume * 100 / 254)}%",
|
text = "${(volume * 100 / 254)}%",
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.primary
|
color = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Slider(
|
Slider(
|
||||||
value = status.volume.toFloat(),
|
value = volume.toFloat(),
|
||||||
onValueChange = { onVolumeChange(it.toInt()) },
|
onValueChange = { onVolumeChange(it.toInt()) },
|
||||||
valueRange = 0f..254f,
|
valueRange = 0f..254f,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user