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 66c510b..c33ca80 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarHomeScreen.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarHomeScreen.kt @@ -27,7 +27,6 @@ import app.seb3thehacker.gearslip.R import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.gestures.draggable import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.mutableLongStateOf @@ -106,19 +105,11 @@ fun CarHome() { // The map runs edge to edge underneath; the player floats over one side of it as a card, with // 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. - val savedWidth by CarSettings.playerWidth.collectAsStateWithLifecycle() - // The width while the grip is being dragged; null otherwise. - var dragging by remember { androidx.compose.runtime.mutableStateOf(null) } BoxWithConstraints(Modifier.fillMaxSize()) { - val widest = maxWidth * PLAYER_MAX_SHARE - val settled = (if (savedWidth > 0f) savedWidth.dp else (maxWidth / PLAYER_SHARE).coerceAtMost(PLAYER_DEFAULT_MAX)) - .coerceIn(PLAYER_MIN_WIDTH, widest) - val column = dragging ?: settled + val column = (maxWidth / PLAYER_SHARE).coerceIn(PLAYER_MIN_WIDTH, PLAYER_MAX_WIDTH) androidx.compose.runtime.CompositionLocalProvider( LocalCardsAtEnd provides (splitScreen && playerLeft), - // The settled width, not the one under the finger: the map app re-centres once, when - // the drag ends, rather than on every frame of it. - LocalSidePanelInset provides if (splitScreen) settled else 0.dp, + LocalSidePanelInset provides if (splitScreen) column else 0.dp, ) { MapPane(navStatus, frame) } @@ -139,63 +130,17 @@ fun CarHome() { top = CARD_GAP, bottom = LocalNavBarInset.current + CARD_GAP, ) - Box(card) { - val fill = Modifier.fillMaxSize() - if (shown.value == SidePanel.LYRICS) SideLyrics(now, fill) else DockedPlayer(now, fill) - ResizeGrip( - onDrag = { delta -> - val grown = if (playerLeft) delta else -delta - dragging = ((dragging ?: settled) + grown).coerceIn(PLAYER_MIN_WIDTH, widest) - }, - onDone = { - dragging?.let { CarSettings.setPlayerWidth(it.value) } - dragging = null - }, - modifier = Modifier.align(if (playerLeft) Alignment.CenterEnd else Alignment.CenterStart), - ) - } + if (shown.value == SidePanel.LYRICS) SideLyrics(now, card) else DockedPlayer(now, card) } } } -/** Until it's dragged, the player takes one part in this many of the screen's width, up to [PLAYER_DEFAULT_MAX]. */ +/** The player takes one part in this many of the screen's width. */ private const val PLAYER_SHARE = 3.6f -/** On a wide screen the default card stops growing, so the map gets the extra room. */ -private val PLAYER_DEFAULT_MAX = 340.dp - -/** How far the grip can take the card: narrow enough for the controls, wide enough to leave a map. */ +/** Narrow enough to leave the map most of a small screen; on a wide one the map gets the extra room. */ private val PLAYER_MIN_WIDTH = 240.dp -private const val PLAYER_MAX_SHARE = 0.5f - -/** - * The handle along the card's edge facing the map: a short bar to show it's there, in a strip wide - * enough for a finger. [onDrag] gets each move in dp, positive to the right. - */ -@Composable -private fun ResizeGrip(onDrag: (androidx.compose.ui.unit.Dp) -> Unit, onDone: () -> Unit, modifier: Modifier) { - val density = androidx.compose.ui.platform.LocalDensity.current - val state = androidx.compose.foundation.gestures.rememberDraggableState { px -> onDrag(with(density) { px.toDp() }) } - Box( - // Only the middle of the edge, so it stays clear of the close button in the corner. - modifier - .height(140.dp) - .width(28.dp) - .draggable( - state, - androidx.compose.foundation.gestures.Orientation.Horizontal, - onDragStopped = { onDone() }, - ), - contentAlignment = Alignment.Center, - ) { - Box( - Modifier - .size(width = 5.dp, height = 44.dp) - .clip(CircleShape) - .background(MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.45f)), - ) - } -} +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 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 6a353bb..00ff92a 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.gsPanelShadow +import app.seb3thehacker.gearslip.car.theme.gsBarShadow import app.seb3thehacker.gearslip.car.theme.GsButton import app.seb3thehacker.gearslip.car.theme.GsTone import app.seb3thehacker.gearslip.car.theme.gsColors @@ -165,13 +165,9 @@ internal fun CarNavBar(navigator: CarNavigator) { val frame by CarEnvironment.frame.collectAsState() val running = rememberRunningApps() - // 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, - ) { + // A full-width strip along the bottom, casting 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) { 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/CarSettings.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarSettings.kt index 60e3bee..c4ca6e2 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarSettings.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarSettings.kt @@ -64,7 +64,6 @@ private const val KEY_LOCATION_WARNING = "car_location_warning" private const val KEY_AUTOPLAY = "car_autoplay" private const val KEY_PLAYER_TITLE = "car_player_title" private const val KEY_PLAYER_LEFT = "car_player_left" - private const val KEY_PLAYER_WIDTH = "car_player_width" private const val KEY_MORE_LYRICS = "car_more_lyrics" private const val KEY_THEME = "car_app_theme" private const val KEY_UI_THEME = "car_ui_theme" @@ -106,7 +105,6 @@ private const val KEY_LOCATION_WARNING = "car_location_warning" private val autoplayFlow = MutableStateFlow(false) private val playerTitleFlow = MutableStateFlow(false) private val playerLeftFlow = MutableStateFlow(false) - private val playerWidthFlow = MutableStateFlow(0f) private val moreLyricsFlow = MutableStateFlow(true) private val themeFlow = MutableStateFlow(AppTheme.PHONE) val appTheme: StateFlow = themeFlow @@ -228,9 +226,6 @@ private const val KEY_LOCATION_WARNING = "car_location_warning" /** The home screen's player sits left of the map instead of right of it. */ val playerLeft: StateFlow = playerLeftFlow - /** The docked player's width in dp, as last dragged; 0 until it has been, for the default. */ - val playerWidth: StateFlow = playerWidthFlow - /** Lyrics from BetterLyrics and KuGou as well as LRCLIB; off sends song details to LRCLIB only. */ val moreLyrics: StateFlow = moreLyricsFlow @@ -326,7 +321,6 @@ private const val KEY_LOCATION_WARNING = "car_location_warning" autoplayFlow.value = AppSettings.getString(app, KEY_AUTOPLAY, "false") == "true" playerTitleFlow.value = AppSettings.getString(app, KEY_PLAYER_TITLE, "false") == "true" playerLeftFlow.value = AppSettings.getString(app, KEY_PLAYER_LEFT, "false") == "true" - playerWidthFlow.value = AppSettings.getString(app, KEY_PLAYER_WIDTH, "0").toFloatOrNull() ?: 0f moreLyricsFlow.value = AppSettings.getString(app, KEY_MORE_LYRICS, "true") == "true" pipeAudioFlow.value = AppSettings.getString(app, KEY_PIPE_AUDIO, "true") == "true" // Until the driver sets it, on for USB audio and off for Bluetooth, where the map app's @@ -405,11 +399,6 @@ private const val KEY_LOCATION_WARNING = "car_location_warning" AppSettings.putString(app, KEY_PLAYER_LEFT, on.toString()) } - fun setPlayerWidth(dp: Float) { - playerWidthFlow.value = dp - AppSettings.putString(app, KEY_PLAYER_WIDTH, dp.toString()) - } - fun setMoreLyrics(on: Boolean) { moreLyricsFlow.value = on AppSettings.putString(app, KEY_MORE_LYRICS, on.toString()) 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 da2507b..3b8f670 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarUi.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarUi.kt @@ -234,8 +234,8 @@ fun CarUi() { val screen = navigator.current val safetyWarningSeen by CarSettings.hasSeenSafetyWarning.collectAsState() val keyboardVisible by CarKeyboardVisibility.state - // 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. + // The bar always lies 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() @@ -250,7 +250,7 @@ fun CarUi() { val onCall = CarCalls.state.collectAsState().value is CallUiState.Active val barShown = !keyboardVisible && (!barMayHide || controlsWake.navBarAwake || onCall) // 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 + val homeInset = if (barShown) NAV_BAR_HEIGHT 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)) { @@ -281,9 +281,8 @@ fun CarUi() { Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.background) - // 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) + // The bar never hides here, so keep the screen clear of it. + .padding(bottom = if (!keyboardVisible && !fullscreen) NAV_BAR_HEIGHT else 0.dp) // Keeps touches that miss this screen's controls off the map. .pointerInput(Unit) { detectTapGestures { } }, ) { @@ -359,9 +358,6 @@ 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 495756a..0fe3ba5 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,6 +183,26 @@ 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