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 75b2cca..fd6aa6b 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarHomeScreen.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarHomeScreen.kt @@ -106,7 +106,7 @@ fun CarHome() { // a gap all round. The map's surface keeps its size when the card comes and goes, so the app // only re-centres its map instead of drawing onto a new surface. BoxWithConstraints(Modifier.fillMaxSize()) { - val column = maxWidth / PLAYER_SHARE + val column = (maxWidth / PLAYER_SHARE).coerceAtMost(PLAYER_MAX_WIDTH) androidx.compose.runtime.CompositionLocalProvider( LocalCardsAtEnd provides (splitScreen && playerLeft), LocalSidePanelInset provides if (splitScreen) column else 0.dp, @@ -135,8 +135,11 @@ fun CarHome() { } } -/** The player takes one part in this many of the screen's width, as it did beside the map. */ -private const val PLAYER_SHARE = 3.1f +/** The player takes one part in this many of the screen's width, up to [PLAYER_MAX_WIDTH]. */ +private const val PLAYER_SHARE = 3.6f + +/** On a wide screen the card stops growing, so the map gets the extra room. */ +private val PLAYER_MAX_WIDTH = 340.dp /** Between the player card and the screen's edges, and the map's own cards beside it. */ private val CARD_GAP = 12.dp @@ -214,7 +217,7 @@ private fun DockedPlayer(now: NowPlaying, modifier: Modifier) { BoxWithConstraints(Modifier.fillMaxSize().padding(12.dp)) { // Whatever height the controls below leave over, so the art grows on a taller // screen and shrinks rather than pushing the buttons off a short one. - val artSize = minOf(maxWidth - 44.dp, maxHeight - 250.dp).coerceIn(56.dp, 200.dp) + val artSize = minOf(maxWidth - 44.dp, maxHeight - 290.dp).coerceIn(56.dp, 200.dp) Column(Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) { Box(Modifier.fillMaxWidth()) { // Art opens the full media screen, same as on the pill. @@ -252,12 +255,12 @@ private fun DockedPlayer(now: NowPlaying, modifier: Modifier) { } else { Spacer(Modifier.height(10.dp)) } - TransportRow(now, media, skip = 48.dp, modifier = Modifier.fillMaxWidth()) + TransportRow(now, media, skip = 56.dp, modifier = Modifier.fillMaxWidth()) Spacer(Modifier.height(10.dp)) - PlayModeRow(now, media, size = 42.dp, modifier = Modifier.fillMaxWidth()) { + PlayModeRow(now, media, size = 50.dp, modifier = Modifier.fillMaxWidth()) { GsIconButton( ImageVector.vectorResource(R.drawable.lyrics_24), "Lyrics", navigator::showSideLyrics, - size = 42.dp, iconSize = 22.dp, + size = 50.dp, iconSize = 26.dp, ) } } diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarNavBar.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarNavBar.kt index efb093e..6a353bb 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarNavBar.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarNavBar.kt @@ -78,7 +78,7 @@ import app.seb3thehacker.gearslip.car.theme.GsColors import app.seb3thehacker.gearslip.car.theme.withTintedSurfaces import app.seb3thehacker.gearslip.car.theme.GsIconBox import app.seb3thehacker.gearslip.car.theme.GsIconButton -import app.seb3thehacker.gearslip.car.theme.gsBarShadow +import app.seb3thehacker.gearslip.car.theme.gsPanelShadow import app.seb3thehacker.gearslip.car.theme.GsButton import app.seb3thehacker.gearslip.car.theme.GsTone import app.seb3thehacker.gearslip.car.theme.gsColors @@ -165,8 +165,13 @@ internal fun CarNavBar(navigator: CarNavigator) { val frame by CarEnvironment.frame.collectAsState() val running = rememberRunningApps() - // Casts the theme's panel shadow up onto the map, so the bar sits above it rather than beside it. - Surface(Modifier.gsBarShadow(), color = MaterialTheme.colorScheme.surfaceContainer) { + // Floats like the player card: lifted off the screen's edges, rounded, with the theme's shadow. + val shape = RoundedCornerShape(22.dp) + Surface( + Modifier.padding(start = NAV_BAR_GAP, end = NAV_BAR_GAP, bottom = NAV_BAR_GAP).gsPanelShadow(shape), + shape = shape, + color = MaterialTheme.colorScheme.surfaceContainer, + ) { BoxWithConstraints(Modifier.fillMaxWidth().height(NAV_BAR_HEIGHT).padding(horizontal = 10.dp)) { Row(Modifier.fillMaxSize(), verticalAlignment = Alignment.CenterVertically) { // Home and Apps are two different places now, not a toggle on one button - the diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarUi.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarUi.kt index cf0bddb..da2507b 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarUi.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarUi.kt @@ -234,12 +234,11 @@ fun CarUi() { val screen = navigator.current val safetyWarningSeen by CarSettings.hasSeenSafetyWarning.collectAsState() val keyboardVisible by CarKeyboardVisibility.state - // Off "Always show", the bar floats over the map rather than taking a row of its - // own: hiding it then leaves the map's surface the size it was. Resizing it would - // hand the map app a new surface each time, and some go black on that. + // The bar always floats, over the map and every other screen, rather than taking a + // row of its own: hiding it then leaves the map's surface the size it was. + // Resizing it would hand the map app a new surface each time, and some go black on that. val navBarMode by CarSettings.navBarMode.collectAsState() val appTemplate by CarServices.nav.template.collectAsState() - val floatingBar = navBarMode != NavBarMode.SHOW val barMayHide = screen == CarScreen.Home && when (navBarMode) { NavBarMode.SHOW -> false NavBarMode.HIDE_NAVIGATING -> appTemplate is androidx.car.app.navigation.model.NavigationTemplate @@ -250,7 +249,8 @@ fun CarUi() { // A call keeps the bar up whatever the setting: End and Mute live on it. val onCall = CarCalls.state.collectAsState().value is CallUiState.Active val barShown = !keyboardVisible && (!barMayHide || controlsWake.navBarAwake || onCall) - val homeInset = if (floatingBar && barShown) NAV_BAR_HEIGHT else 0.dp + // Up to the bar's top edge, so the map's cards and the player end above it. + val homeInset = if (barShown) NAV_BAR_HEIGHT + NAV_BAR_GAP else 0.dp // Only the screen that asked: going Home or anywhere else ends it. val fullscreen = CarFullscreen.active && screen != CarScreen.Home Box(Modifier.fillMaxSize().padding(pad)) { @@ -270,24 +270,20 @@ fun CarUi() { // The keyboard takes the nav bar's row instead of squeezing in above it - // nothing on the nav bar is useful mid-type, and the screen is too small to // show both. - if (!floatingBar && !keyboardVisible) CarNavBar(navigator) } // Every other screen, and everything that pops up, in a layer over Home rather // than inside its area: full screen grows this layer over the nav bar's row while // Home, and the map's surface in it, keeps its size. if (safetyWarningSeen) { - Box( - Modifier - .fillMaxSize() - .padding(bottom = if (!floatingBar && !keyboardVisible && !fullscreen) NAV_BAR_HEIGHT else 0.dp), - ) { + Box(Modifier.fillMaxSize()) { if (screen != CarScreen.Home) { Box( Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.background) - // The floating bar never hides here, so keep the screen clear of it. - .padding(bottom = if (floatingBar && !keyboardVisible && !fullscreen) NAV_BAR_HEIGHT else 0.dp) + // The bar never hides here, so keep the screen clear of it, with + // the same gap above it as below. + .padding(bottom = if (!keyboardVisible && !fullscreen) NAV_BAR_HEIGHT + NAV_BAR_GAP * 2 else 0.dp) // Keeps touches that miss this screen's controls off the map. .pointerInput(Unit) { detectTapGestures { } }, ) { @@ -327,7 +323,7 @@ fun CarUi() { // whatever the screen drew there. DebugBuildBadge(Modifier.align(Alignment.BottomEnd).padding(bottom = homeInset)) } - if (floatingBar || fullscreen) { + run { androidx.compose.animation.AnimatedVisibility( if (fullscreen) CarFullscreen.revealed || onCall else barShown, Modifier.align(Alignment.BottomCenter), @@ -363,6 +359,9 @@ fun CarUi() { internal val NAV_BAR_HEIGHT = 56.dp +/** Between the floating nav bar and the screen's edges. */ +internal val NAV_BAR_GAP = 12.dp + /** * How much of the bottom of Home the floating nav bar covers right now: its height while it shows, * zero while it's hidden or when it has a row of its own. diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/theme/GsControls.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/theme/GsControls.kt index 0fe3ba5..495756a 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/theme/GsControls.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/theme/GsControls.kt @@ -183,26 +183,6 @@ fun Modifier.gsPanelShadow(shape: Shape): Modifier { return drawBehind { drawSoftShadow(shape, look.panelShadow.toPx(), look.panelShadowAlpha, 1f) } } -/** The same shadow cast upwards from the top edge of a bar along the bottom of the screen. */ -@Composable -fun Modifier.gsBarShadow(): Modifier { - val look = gsLook() - if (look.panelShadow <= 0.dp || look.panelShadowAlpha <= 0f) return this - return drawBehind { - val reach = look.panelShadow.toPx() * 1.5f - drawRect( - Brush.verticalGradient( - 0f to Color.Transparent, - 1f to Color.Black.copy(alpha = look.panelShadowAlpha * 0.6f), - startY = -reach, - endY = 0f, - ), - topLeft = androidx.compose.ui.geometry.Offset(0f, -reach), - size = Size(size.width, reach), - ) - } -} - private fun DrawScope.drawSoftShadow(shape: Shape, elevation: Float, alpha: Float, lift: Float) { if (alpha <= 0f) return val blur = elevation * 2f