From c265d137c898f07e3f05ae99a8853d50d75a78bb Mon Sep 17 00:00:00 2001 From: Seb3thehacker Date: Wed, 11 Nov 2026 12:10:51 -0800 Subject: [PATCH] The trip gets its own card at the bottom again, and the side player's buttons fit with room between them The player's buttons are sized from the column's width instead of fixed, its close button is round and tucked into the corner, and the trip's figures shrink to stay whole when the player narrows the column. --- .../gearslip/car/CarHomeScreen.kt | 32 +++++++--- .../gearslip/car/TemplateChrome.kt | 58 ++++++++++++------- 2 files changed, 61 insertions(+), 29 deletions(-) diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarHomeScreen.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarHomeScreen.kt index 1047e65..16efc54 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarHomeScreen.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarHomeScreen.kt @@ -1,5 +1,6 @@ package app.seb3thehacker.gearslip.car +import androidx.compose.foundation.layout.offset import androidx.compose.ui.res.stringResource import app.seb3thehacker.gearslip.car.theme.* import androidx.compose.foundation.layout.Arrangement @@ -271,18 +272,33 @@ private fun DockedPlayer(now: NowPlaying, modifier: Modifier) { } else { Spacer(Modifier.height(10.dp)) } - TransportRow(now, media, skip = 56.dp, modifier = Modifier.fillMaxWidth()) - Spacer(Modifier.height(10.dp)) - PlayModeRow(now, media, size = 50.dp, modifier = Modifier.fillMaxWidth()) { - GsIconButton( - ImageVector.vectorResource(R.drawable.lyrics_24), stringResource(R.string.storage_lyrics), navigator::showSideLyrics, - size = 50.dp, iconSize = 26.dp, - ) + // 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 + // keeps a clear gap between buttons, and none grows past a comfortable size. + BoxWithConstraints(Modifier.fillMaxWidth()) { + val w = maxWidth + // 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) + // Four buttons and three gaps of at least 12dp. + 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 + // 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), size = 36.dp, iconSize = 20.dp, + Modifier.align(Alignment.TopEnd).offset(x = 6.dp, y = (-6).dp), size = 32.dp, iconSize = 18.dp, + shape = CircleShape, ) } } diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/TemplateChrome.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/TemplateChrome.kt index 6a39263..e9f8925 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/TemplateChrome.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/TemplateChrome.kt @@ -223,14 +223,21 @@ private fun NavigationBars( val column = cardColumnWidth(maxWidth) val cards = @Composable { if (routing != null || estimate != null) { - Column(Modifier.width(column).fillMaxHeight().covers("nav-cards", cardEdge).padding(12.dp)) { - // As tall as what it holds, up to the column's height. - NavigationCard( - routing, - estimate, - showStop = !template.actionStrip.hasStop(), - Modifier.fillMaxWidth().weight(1f, fill = false), - ) + // The turn at the top and the trip in a card of its own at the bottom, with the + // map showing between them. The trip is measured first, so the turn card shrinks + // (dropping parts, see NavigationCard) rather than run into it on a short screen. + Column( + Modifier.width(column).fillMaxHeight().covers("nav-cards", cardEdge).padding(12.dp), + verticalArrangement = Arrangement.SpaceBetween, + ) { + if (routing != null) { + NavigationCard(routing, Modifier.fillMaxWidth().weight(1f, fill = false)) + } + estimate?.let { + ChromeSurface(Modifier.fillMaxWidth().padding(top = if (routing != null) 12.dp else 0.dp)) { + TripRow(it, showStop = !template.actionStrip.hasStop(), Modifier.fillMaxWidth()) + } + } } } } @@ -252,23 +259,23 @@ private fun NavigationBars( } /** - * The drive in one card, top to bottom: the manoeuvre big enough to read at a glance, the lanes - * to be in, the turn after it, and the trip. On a short screen parts drop out whole, the lanes' - * words first, then the next turn, then the lanes; the manoeuvre and the trip always stay. + * The turn, top to bottom: the manoeuvre big enough to read at a glance, the lanes to be in, and + * the turn after it. On a short screen parts drop out whole, the lanes' words first, then the + * next turn, then the lanes; the manoeuvre always stays. The trip has its own card (TripRow). */ @Composable -private fun NavigationCard(info: RoutingInfo?, estimate: TravelEstimate?, showStop: Boolean, modifier: Modifier) { - val step = info?.currentStep?.takeUnless { info.isLoading } +private fun NavigationCard(info: RoutingInfo, modifier: Modifier) { + val step = info.currentStep?.takeUnless { info.isLoading } ChromeSurface(modifier) { FitColumn { - if (info?.isLoading == true) { + if (info.isLoading) { Text( stringResource(R.string.nav_finding_route), Modifier.layoutId(0).padding(horizontal = 18.dp, vertical = 16.dp), style = ChromeType.title, ) } - if (info != null && step != null) { + if (step != null) { ManeuverHeader(info, step, Modifier.layoutId(0).fillMaxWidth()) // Drawn from the lane data whenever there is some, even beside the app's own picture. step.lanes.orEmpty().takeIf { it.isNotEmpty() }?.let { @@ -277,7 +284,6 @@ private fun NavigationCard(info: RoutingInfo?, estimate: TravelEstimate?, showSt } info.nextStep?.let { NextTurn(it, Modifier.layoutId(3).fillMaxWidth()) } } - estimate?.let { TripRow(it, showStop, Modifier.layoutId(1).fillMaxWidth()) } } } } @@ -439,8 +445,8 @@ private fun TripRow(estimate: TravelEstimate, showStop: Boolean, modifier: Modif stringResource(R.string.nav_stop), { connection.stopNavigating() }, colors = GsColors(scheme.surfaceContainerHighest, scheme.onSurface), - size = 52.dp, - iconSize = 26.dp, + size = 46.dp, + iconSize = 24.dp, shape = CircleShape, ) } @@ -461,7 +467,7 @@ private fun TripRow(estimate: TravelEstimate, showStop: Boolean, modifier: Modif @Composable private fun TripRule() { Box( - Modifier.padding(horizontal = 12.dp, vertical = 4.dp).width(1.dp).fillMaxHeight() + Modifier.padding(horizontal = 8.dp, vertical = 4.dp).width(1.dp).fillMaxHeight() .background(MaterialTheme.colorScheme.outlineVariant), ) } @@ -478,10 +484,20 @@ private fun TripFigure(value: String, label: String?, color: Color, style: andro color = color, maxLines = 1, softWrap = false, - onTextLayout = { if (it.hasVisualOverflow && size.value > 12f) size *= 0.9f }, + onTextLayout = { if (it.hasVisualOverflow && size.value > 10f) size *= 0.9f }, ) if (label != null) { - Text(label, style = ChromeType.label, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis) + // Shrinks like the figure, so "arrival" stays whole when the player narrows the column. + val labelStyle = ChromeType.label + var labelSize by androidx.compose.runtime.remember(label, labelStyle) { androidx.compose.runtime.mutableStateOf(labelStyle.fontSize) } + Text( + label, + style = labelStyle.copy(fontSize = labelSize), + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 1, + softWrap = false, + onTextLayout = { if (it.hasVisualOverflow && labelSize.value > 10f) labelSize *= 0.9f }, + ) } } }