With the player beside the map, like, shuffle, repeat and lyrics move into the nav bar

The nav bar's pill carries them while the player is docked, with its dock button in the
same place to close it, so the player itself is just the cover, the song and play/skip.
The player's own close button, which sat over the corner of the cover, is gone.
This commit is contained in:
Seb3thehacker committed 2026-11-11 12:21:55 -08:00
1 parent fa33e7c650
commit 4aeaaee875
3 files changed
+50 -24

No files matched your search

@@ -275,31 +275,15 @@ private fun DockedPlayer(now: NowPlaying, modifier: Modifier) {
// Sized from the column's width rather than fixed: fixed sizes filled a narrow // Sized from the column's width rather than fixed: fixed sizes filled a narrow
// column edge to edge, so the buttons sat squeezed against each other. Each row // column edge to edge, so the buttons sat squeezed against each other. Each row
// keeps a clear gap between buttons, and none grows past a comfortable size. // keeps a clear gap between buttons, and none grows past a comfortable size.
// Like, shuffle, repeat and lyrics are in the nav bar's pill while the player is
// here (NavNowPlaying), which leaves the cover more room.
BoxWithConstraints(Modifier.fillMaxWidth()) { BoxWithConstraints(Modifier.fillMaxWidth()) {
val w = maxWidth
// Two skips, play at 1.25 times a skip, and two gaps of at least 20dp. // Two skips, play at 1.25 times a skip, and two gaps of at least 20dp.
val skip = ((w - 40.dp) / 3.25f).coerceIn(36.dp, 48.dp) val skip = ((maxWidth - 40.dp) / 3.25f).coerceIn(36.dp, 48.dp)
// Four buttons and three gaps of at least 12dp. TransportRow(now, media, skip = skip, modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp))
val mode = ((w - 36.dp) / 4).coerceIn(34.dp, 42.dp)
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
TransportRow(now, media, skip = skip, modifier = Modifier.fillMaxWidth())
Spacer(Modifier.height(10.dp))
PlayModeRow(now, media, size = mode, modifier = Modifier.fillMaxWidth()) {
GsIconButton(
ImageVector.vectorResource(R.drawable.lyrics_24), stringResource(R.string.storage_lyrics), navigator::showSideLyrics,
size = mode, iconSize = mode * 0.52f,
)
}
}
} }
} }
// Round like the player's other buttons, and tucked into the card's corner so it // No close button here: the nav bar's dock button closes the player (NavNowPlaying).
// covers as little of the cover as it can.
GsIconButton(
Icons.Filled.Close, stringResource(R.string.home_back_nav), navigator::closeSidePanel,
Modifier.align(Alignment.TopEnd).offset(x = 6.dp, y = (-6).dp), size = 32.dp, iconSize = 18.dp,
shape = CircleShape,
)
} }
} }
} }
@@ -556,8 +556,13 @@ private fun NavNowPlaying(navigator: CarNavigator, selected: Boolean, modifier:
val phase by media.phase.collectAsState() val phase by media.phase.collectAsState()
val now by media.now.collectAsState() val now by media.now.collectAsState()
if (phase != CarMedia.Phase.READY || !now.isActive) return if (phase != CarMedia.Phase.READY || !now.isActive) return
// Docked beside the map, the player is already on screen - the pill steps aside for it. // Docked beside the map, the player is already on screen, so the pill carries what the
if (navigator.sidePanel == SidePanel.CONTROLS && navigator.current == CarScreen.Home) return // player leaves out - like, shuffle, repeat and lyrics - and its dock button, filled in, in
// the same place: tapped again, it closes the player.
if (navigator.sidePanel == SidePanel.CONTROLS && navigator.current == CarScreen.Home) {
DockedPill(navigator, now, selected, modifier)
return
}
// The media screen is the full player already; the pill would only repeat its controls. // The media screen is the full player already; the pill would only repeat its controls.
if (navigator.current == CarScreen.Media) return if (navigator.current == CarScreen.Media) return
val context = LocalContext.current val context = LocalContext.current
@@ -670,6 +675,42 @@ private fun NavNowPlaying(navigator: CarNavigator, selected: Boolean, modifier:
} }
} }
/**
* The pill while the player is docked: like, shuffle, repeat and lyrics, then the dock button,
* shown open, which closes it. Sits on the player's side of the bar, under the player.
*/
@Composable
private fun DockedPill(navigator: CarNavigator, now: app.seb3thehacker.gearslip.media.NowPlaying, selected: Boolean, modifier: Modifier) {
val scheme = MaterialTheme.colorScheme
val playerLeft by CarSettings.playerLeft.collectAsState()
Box(modifier.fillMaxWidth(), contentAlignment = if (playerLeft) Alignment.CenterStart else Alignment.CenterEnd) {
Surface(
modifier = Modifier.height(44.dp),
shape = RoundedCornerShape(14.dp),
color = if (selected) scheme.primaryContainer else scheme.secondaryContainer,
contentColor = if (selected) scheme.onPrimaryContainer else scheme.onSecondaryContainer,
) {
PlayModeRow(
now, CarServices.media, size = 36.dp,
modifier = Modifier.fillMaxHeight().padding(horizontal = 4.dp),
arrangement = Arrangement.spacedBy(6.dp),
) {
GsIconButton(
androidx.compose.ui.graphics.vector.ImageVector.vectorResource(R.drawable.lyrics_24),
stringResource(R.string.storage_lyrics), navigator::showSideLyrics,
size = 36.dp, iconSize = 20.dp,
)
GsIconButton(
if (playerLeft) MediaIcons.DockLeftOpen else MediaIcons.DockRightOpen,
stringResource(R.string.navbar_close_panel),
navigator::togglePlayerDock,
size = 36.dp, iconSize = 22.dp,
)
}
}
}
}
/** /**
* A filled lightning bolt, next to the battery percentage instead of the word "charging" - the * A filled lightning bolt, next to the battery percentage instead of the word "charging" - the
* core Material icon set this project ships (no extended pack, to keep the app small) has no * core Material icon set this project ships (no extended pack, to keep the app small) has no
@@ -130,6 +130,7 @@ internal fun PlayModeRow(
size: Dp, size: Dp,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
maxOther: Int = 0, maxOther: Int = 0,
arrangement: Arrangement.Horizontal = Arrangement.SpaceEvenly,
trailing: @Composable RowScope.() -> Unit = {}, trailing: @Composable RowScope.() -> Unit = {},
) { ) {
val modes by media.modes.collectAsState() val modes by media.modes.collectAsState()
@@ -139,7 +140,7 @@ internal fun PlayModeRow(
val shuffleOn = shuffleKnown && modes.shuffle != PlaybackStateCompat.SHUFFLE_MODE_NONE val shuffleOn = shuffleKnown && modes.shuffle != PlaybackStateCompat.SHUFFLE_MODE_NONE
val repeatOn = repeatKnown && modes.repeat != PlaybackStateCompat.REPEAT_MODE_NONE val repeatOn = repeatKnown && modes.repeat != PlaybackStateCompat.REPEAT_MODE_NONE
val icon = size * 0.52f val icon = size * 0.52f
Row(modifier, horizontalArrangement = Arrangement.SpaceEvenly, verticalAlignment = Alignment.CenterVertically) { Row(modifier, horizontalArrangement = arrangement, verticalAlignment = Alignment.CenterVertically) {
// An app's own like, shuffle or repeat button shows whether it's on only through its icon, // An app's own like, shuffle or repeat button shows whether it's on only through its icon,
// so that icon is drawn, as Android Auto does, and lit when its name says it's on. // so that icon is drawn, as Android Auto does, and lit when its name says it's on.
// Gearslip's own stand in for apps that report a heart rating or shuffle and repeat modes. // Gearslip's own stand in for apps that report a heart rating or shuffle and repeat modes.