mirror of
https://github.com/twinksoftware/Gearslip.git
synced 2026-11-11 08:12:26 +01:00
car: the nav bar floats on every screen, and the docked player is narrower with bigger buttons
This commit is contained in:
1 parent
8a2dae4173
commit
93a1ff7485
4 files changed
+31
-44
No files matched your search
@@ -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
|
// 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.
|
// only re-centres its map instead of drawing onto a new surface.
|
||||||
BoxWithConstraints(Modifier.fillMaxSize()) {
|
BoxWithConstraints(Modifier.fillMaxSize()) {
|
||||||
val column = maxWidth / PLAYER_SHARE
|
val column = (maxWidth / PLAYER_SHARE).coerceAtMost(PLAYER_MAX_WIDTH)
|
||||||
androidx.compose.runtime.CompositionLocalProvider(
|
androidx.compose.runtime.CompositionLocalProvider(
|
||||||
LocalCardsAtEnd provides (splitScreen && playerLeft),
|
LocalCardsAtEnd provides (splitScreen && playerLeft),
|
||||||
LocalSidePanelInset provides if (splitScreen) column else 0.dp,
|
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. */
|
/** The player takes one part in this many of the screen's width, up to [PLAYER_MAX_WIDTH]. */
|
||||||
private const val PLAYER_SHARE = 3.1f
|
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. */
|
/** Between the player card and the screen's edges, and the map's own cards beside it. */
|
||||||
private val CARD_GAP = 12.dp
|
private val CARD_GAP = 12.dp
|
||||||
@@ -214,7 +217,7 @@ private fun DockedPlayer(now: NowPlaying, modifier: Modifier) {
|
|||||||
BoxWithConstraints(Modifier.fillMaxSize().padding(12.dp)) {
|
BoxWithConstraints(Modifier.fillMaxSize().padding(12.dp)) {
|
||||||
// Whatever height the controls below leave over, so the art grows on a taller
|
// 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.
|
// 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) {
|
Column(Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
Box(Modifier.fillMaxWidth()) {
|
Box(Modifier.fillMaxWidth()) {
|
||||||
// Art opens the full media screen, same as on the pill.
|
// Art opens the full media screen, same as on the pill.
|
||||||
@@ -252,12 +255,12 @@ private fun DockedPlayer(now: NowPlaying, modifier: Modifier) {
|
|||||||
} else {
|
} else {
|
||||||
Spacer(Modifier.height(10.dp))
|
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))
|
Spacer(Modifier.height(10.dp))
|
||||||
PlayModeRow(now, media, size = 42.dp, modifier = Modifier.fillMaxWidth()) {
|
PlayModeRow(now, media, size = 50.dp, modifier = Modifier.fillMaxWidth()) {
|
||||||
GsIconButton(
|
GsIconButton(
|
||||||
ImageVector.vectorResource(R.drawable.lyrics_24), "Lyrics", navigator::showSideLyrics,
|
ImageVector.vectorResource(R.drawable.lyrics_24), "Lyrics", navigator::showSideLyrics,
|
||||||
size = 42.dp, iconSize = 22.dp,
|
size = 50.dp, iconSize = 26.dp,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ import app.seb3thehacker.gearslip.car.theme.GsColors
|
|||||||
import app.seb3thehacker.gearslip.car.theme.withTintedSurfaces
|
import app.seb3thehacker.gearslip.car.theme.withTintedSurfaces
|
||||||
import app.seb3thehacker.gearslip.car.theme.GsIconBox
|
import app.seb3thehacker.gearslip.car.theme.GsIconBox
|
||||||
import app.seb3thehacker.gearslip.car.theme.GsIconButton
|
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.GsButton
|
||||||
import app.seb3thehacker.gearslip.car.theme.GsTone
|
import app.seb3thehacker.gearslip.car.theme.GsTone
|
||||||
import app.seb3thehacker.gearslip.car.theme.gsColors
|
import app.seb3thehacker.gearslip.car.theme.gsColors
|
||||||
@@ -165,8 +165,13 @@ internal fun CarNavBar(navigator: CarNavigator) {
|
|||||||
val frame by CarEnvironment.frame.collectAsState()
|
val frame by CarEnvironment.frame.collectAsState()
|
||||||
val running = rememberRunningApps()
|
val running = rememberRunningApps()
|
||||||
|
|
||||||
// Casts the theme's panel shadow up onto the map, so the bar sits above it rather than beside it.
|
// Floats like the player card: lifted off the screen's edges, rounded, with the theme's shadow.
|
||||||
Surface(Modifier.gsBarShadow(), color = MaterialTheme.colorScheme.surfaceContainer) {
|
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)) {
|
BoxWithConstraints(Modifier.fillMaxWidth().height(NAV_BAR_HEIGHT).padding(horizontal = 10.dp)) {
|
||||||
Row(Modifier.fillMaxSize(), verticalAlignment = Alignment.CenterVertically) {
|
Row(Modifier.fillMaxSize(), verticalAlignment = Alignment.CenterVertically) {
|
||||||
// Home and Apps are two different places now, not a toggle on one button - the
|
// Home and Apps are two different places now, not a toggle on one button - the
|
||||||
|
|||||||
@@ -234,12 +234,11 @@ fun CarUi() {
|
|||||||
val screen = navigator.current
|
val screen = navigator.current
|
||||||
val safetyWarningSeen by CarSettings.hasSeenSafetyWarning.collectAsState()
|
val safetyWarningSeen by CarSettings.hasSeenSafetyWarning.collectAsState()
|
||||||
val keyboardVisible by CarKeyboardVisibility.state
|
val keyboardVisible by CarKeyboardVisibility.state
|
||||||
// Off "Always show", the bar floats over the map rather than taking a row of its
|
// The bar always floats, over the map and every other screen, rather than taking a
|
||||||
// own: hiding it then leaves the map's surface the size it was. Resizing it would
|
// row of its own: hiding it then leaves the map's surface the size it was.
|
||||||
// hand the map app a new surface each time, and some go black on that.
|
// 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 navBarMode by CarSettings.navBarMode.collectAsState()
|
||||||
val appTemplate by CarServices.nav.template.collectAsState()
|
val appTemplate by CarServices.nav.template.collectAsState()
|
||||||
val floatingBar = navBarMode != NavBarMode.SHOW
|
|
||||||
val barMayHide = screen == CarScreen.Home && when (navBarMode) {
|
val barMayHide = screen == CarScreen.Home && when (navBarMode) {
|
||||||
NavBarMode.SHOW -> false
|
NavBarMode.SHOW -> false
|
||||||
NavBarMode.HIDE_NAVIGATING -> appTemplate is androidx.car.app.navigation.model.NavigationTemplate
|
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.
|
// 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 onCall = CarCalls.state.collectAsState().value is CallUiState.Active
|
||||||
val barShown = !keyboardVisible && (!barMayHide || controlsWake.navBarAwake || onCall)
|
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.
|
// Only the screen that asked: going Home or anywhere else ends it.
|
||||||
val fullscreen = CarFullscreen.active && screen != CarScreen.Home
|
val fullscreen = CarFullscreen.active && screen != CarScreen.Home
|
||||||
Box(Modifier.fillMaxSize().padding(pad)) {
|
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 -
|
// 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
|
// nothing on the nav bar is useful mid-type, and the screen is too small to
|
||||||
// show both.
|
// show both.
|
||||||
if (!floatingBar && !keyboardVisible) CarNavBar(navigator)
|
|
||||||
}
|
}
|
||||||
// Every other screen, and everything that pops up, in a layer over Home rather
|
// 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
|
// 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.
|
// Home, and the map's surface in it, keeps its size.
|
||||||
if (safetyWarningSeen) {
|
if (safetyWarningSeen) {
|
||||||
Box(
|
Box(Modifier.fillMaxSize()) {
|
||||||
Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(bottom = if (!floatingBar && !keyboardVisible && !fullscreen) NAV_BAR_HEIGHT else 0.dp),
|
|
||||||
) {
|
|
||||||
if (screen != CarScreen.Home) {
|
if (screen != CarScreen.Home) {
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(MaterialTheme.colorScheme.background)
|
.background(MaterialTheme.colorScheme.background)
|
||||||
// The floating bar never hides here, so keep the screen clear of it.
|
// The bar never hides here, so keep the screen clear of it, with
|
||||||
.padding(bottom = if (floatingBar && !keyboardVisible && !fullscreen) NAV_BAR_HEIGHT else 0.dp)
|
// 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.
|
// Keeps touches that miss this screen's controls off the map.
|
||||||
.pointerInput(Unit) { detectTapGestures { } },
|
.pointerInput(Unit) { detectTapGestures { } },
|
||||||
) {
|
) {
|
||||||
@@ -327,7 +323,7 @@ fun CarUi() {
|
|||||||
// whatever the screen drew there.
|
// whatever the screen drew there.
|
||||||
DebugBuildBadge(Modifier.align(Alignment.BottomEnd).padding(bottom = homeInset))
|
DebugBuildBadge(Modifier.align(Alignment.BottomEnd).padding(bottom = homeInset))
|
||||||
}
|
}
|
||||||
if (floatingBar || fullscreen) {
|
run {
|
||||||
androidx.compose.animation.AnimatedVisibility(
|
androidx.compose.animation.AnimatedVisibility(
|
||||||
if (fullscreen) CarFullscreen.revealed || onCall else barShown,
|
if (fullscreen) CarFullscreen.revealed || onCall else barShown,
|
||||||
Modifier.align(Alignment.BottomCenter),
|
Modifier.align(Alignment.BottomCenter),
|
||||||
@@ -363,6 +359,9 @@ fun CarUi() {
|
|||||||
|
|
||||||
internal val NAV_BAR_HEIGHT = 56.dp
|
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,
|
* 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.
|
* zero while it's hidden or when it has a row of its own.
|
||||||
|
|||||||
@@ -183,26 +183,6 @@ fun Modifier.gsPanelShadow(shape: Shape): Modifier {
|
|||||||
return drawBehind { drawSoftShadow(shape, look.panelShadow.toPx(), look.panelShadowAlpha, 1f) }
|
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) {
|
private fun DrawScope.drawSoftShadow(shape: Shape, elevation: Float, alpha: Float, lift: Float) {
|
||||||
if (alpha <= 0f) return
|
if (alpha <= 0f) return
|
||||||
val blur = elevation * 2f
|
val blur = elevation * 2f
|
||||||
|
|||||||
Reference in new issue
Block a user