From ef006dc695d0699fe014450d84cd962cb4993076 Mon Sep 17 00:00:00 2001 From: Seb3thehacker Date: Sat, 24 Oct 2026 00:00:55 -0700 Subject: [PATCH] home: Help, Preview and Logs in one row at the bottom, and What's new tops Settings Small labelled buttons pinned at the bottom of Home, so cards above can't push them out of sight. Help and tools leaves Settings, Connection help (the same page as Help) goes, What's new moves to the top of Settings, the intro replay goes, and the debug build's test message gets its own section there. --- .../seb3thehacker/gearslip/ui/GearslipApp.kt | 11 +-- .../seb3thehacker/gearslip/ui/HomeScreen.kt | 47 +++++++---- .../gearslip/ui/SettingsScreen.kt | 82 +++++-------------- 3 files changed, 55 insertions(+), 85 deletions(-) diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/GearslipApp.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/GearslipApp.kt index f31fb5f..b130b68 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/GearslipApp.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/GearslipApp.kt @@ -108,26 +108,23 @@ fun GearslipApp(onDisconnect: () -> Unit, onRequestCallScreening: () -> Unit) { Screen.INTRO -> IntroScreen(onDone = { screen = introReturn }) Screen.HOME -> HomeScreen( onOpenSettings = { settingsAtUsageNotes = false; screen = Screen.SETTINGS }, - onOpenWhatsNew = { backTo = Screen.HOME; screen = Screen.WHATS_NEW }, onOpenHelp = { backTo = Screen.HOME; screen = Screen.HELP }, onOpenUsageNotes = { settingsAtUsageNotes = true; screen = Screen.SETTINGS }, + onOpenCarPreview = { backTo = Screen.HOME; screen = Screen.CAR_PREVIEW }, + onOpenLogs = { backTo = Screen.HOME; screen = Screen.LOGS }, onDisconnect = onDisconnect, ) Screen.WHATS_NEW -> WhatsNewScreen(onBack = { screen = backTo }) - Screen.LOGS -> LogsScreen(onBack = { screen = Screen.SETTINGS }) + Screen.LOGS -> LogsScreen(onBack = { screen = backTo }) Screen.SETTINGS -> SettingsScreen( onBack = { screen = Screen.HOME }, onOpenRecommendedApps = { settingsAtUsageNotes = false; screen = Screen.RECOMMENDED_APPS }, onOpenWhatsNew = { settingsAtUsageNotes = false; backTo = Screen.SETTINGS; screen = Screen.WHATS_NEW }, - onOpenHelp = { settingsAtUsageNotes = false; backTo = Screen.SETTINGS; screen = Screen.HELP }, - onOpenCarPreview = { settingsAtUsageNotes = false; screen = Screen.CAR_PREVIEW }, - onOpenLogs = { settingsAtUsageNotes = false; screen = Screen.LOGS }, onReplayTutorial = { screen = Screen.SETUP_GUIDE }, - onReplayIntro = { Intro.replay(context); introReturn = Screen.SETTINGS; screen = Screen.INTRO }, atUsageNotes = settingsAtUsageNotes, ) Screen.RECOMMENDED_APPS -> RecommendedAppsScreen(onBack = { screen = Screen.SETTINGS }) - Screen.CAR_PREVIEW -> CarPreviewScreen(onBack = { screen = Screen.SETTINGS }) + Screen.CAR_PREVIEW -> CarPreviewScreen(onBack = { screen = backTo }) Screen.HELP -> HelpScreen( onBack = { screen = backTo }, onRequestCallScreening = onRequestCallScreening, diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/HomeScreen.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/HomeScreen.kt index 4abde51..aab9325 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/HomeScreen.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/HomeScreen.kt @@ -5,6 +5,7 @@ import app.seb3thehacker.gearslip.speech.SpeechModels import androidx.compose.material3.LinearProgressIndicator import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -27,6 +28,8 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight +import androidx.compose.material.icons.automirrored.filled.List +import androidx.compose.material.icons.automirrored.filled.Send import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.Send import androidx.compose.material.icons.filled.List @@ -60,9 +63,10 @@ import app.seb3thehacker.gearslip.stats.WorkedPrompt @Composable fun HomeScreen( onOpenSettings: () -> Unit, - onOpenWhatsNew: () -> Unit, onOpenHelp: () -> Unit, onOpenUsageNotes: () -> Unit, + onOpenCarPreview: () -> Unit, + onOpenLogs: () -> Unit, onDisconnect: () -> Unit, ) { val status by SessionStatus.state.collectAsStateWithLifecycle() @@ -125,21 +129,11 @@ fun HomeScreen( // Sits at the bottom, where a thumb reaches it. Column(Modifier.padding(vertical = 16.dp)) { - SettingsCard { - SettingsRow( - "What's new in ${Intro.release}", - Modifier.clickable(onClick = onOpenWhatsNew), - icon = Icons.Filled.Star, - trailing = { Arrow() }, - ) - SettingsDivider() - SettingsRow( - "Which cars work", - Modifier.clickable(onClick = onOpenHelp), - subtitle = "Most do. See which ones, and what to set up first", - icon = Icons.Filled.Info, - trailing = { Arrow() }, - ) + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + HomeShortcut(Icons.Filled.Info, "Help", onOpenHelp, Modifier.weight(1f)) + // The preview draws the car screen on the phone, so it waits until no car is using it. + if (!live) HomeShortcut(Icons.Filled.PlayArrow, "Preview", onOpenCarPreview, Modifier.weight(1f)) + HomeShortcut(Icons.AutoMirrored.Filled.List, "Logs", onOpenLogs, Modifier.weight(1f)) } if (live) { Spacer(Modifier.height(16.dp)) @@ -157,6 +151,27 @@ fun HomeScreen( } } +/** One of Home's shortcuts at the bottom: an icon over a one-word label, small enough that all fit in a row. */ +@Composable +private fun HomeShortcut(icon: androidx.compose.ui.graphics.vector.ImageVector, label: String, onClick: () -> Unit, modifier: Modifier = Modifier) { + Surface( + onClick = onClick, + modifier = modifier.heightIn(min = 64.dp), + shape = MaterialTheme.shapes.large, + color = MaterialTheme.colorScheme.secondaryContainer, + contentColor = MaterialTheme.colorScheme.onSecondaryContainer, + ) { + Column( + Modifier.padding(vertical = 10.dp, horizontal = 4.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + Icon(icon, contentDescription = null, modifier = Modifier.size(22.dp)) + Text(label, style = MaterialTheme.typography.labelMedium, maxLines = 1) + } + } +} + /** Bluetooth audio is chosen, but the phone isn't connected to the car. See [BluetoothAudioCheck]. */ @Composable private fun BluetoothMissingCard() { diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/SettingsScreen.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/SettingsScreen.kt index db61df6..30a0574 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/SettingsScreen.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/SettingsScreen.kt @@ -80,11 +80,7 @@ fun SettingsScreen( onBack: () -> Unit, onOpenRecommendedApps: () -> Unit, onOpenWhatsNew: () -> Unit, - onOpenHelp: () -> Unit, - onOpenCarPreview: () -> Unit, - onOpenLogs: () -> Unit, onReplayTutorial: () -> Unit, - onReplayIntro: () -> Unit, atUsageNotes: Boolean = false, ) { val scroll = rememberScrollState() @@ -119,12 +115,20 @@ fun SettingsScreen( verticalArrangement = Arrangement.spacedBy(24.dp), ) { Spacer(Modifier.height(0.dp)) + SettingsSection { + SettingsRow( + "What's new", + Modifier.clickable(onClick = onOpenWhatsNew), + subtitle = "Version ${BuildConfig.VERSION_NAME}", + icon = Icons.Filled.Star, + trailing = { Arrow() }, + ) + } SettingsSection( "Support Gearslip", footer = "Gearslip is free, with no ads and no tracking. Your support keeps it going.", ) { DonateRows() } ConnectionSection() - HelpSection(onOpenHelp, onOpenCarPreview, onOpenLogs) CarAudioSection() CarScreenSection() VoiceModelSection() @@ -136,7 +140,8 @@ fun SettingsScreen( ) { UsageStatsRows() } SettingsSection("Updates") { UpdateRow() } CertificateSection() - AboutSection(onOpenWhatsNew, onReplayTutorial, onReplayIntro) + AboutSection(onReplayTutorial) + if (BuildConfig.DEBUG) DebugSection() Spacer(Modifier.height(16.dp)) } } @@ -149,26 +154,10 @@ private fun Arrow() = Icon( tint = MaterialTheme.colorScheme.onSurfaceVariant, ) -/** What's new, the replays, and where Gearslip lives outside the app. */ +/** The setup guide replay, and where Gearslip lives outside the app. */ @Composable -private fun AboutSection(onOpenWhatsNew: () -> Unit, onReplayTutorial: () -> Unit, onReplayIntro: () -> Unit) { +private fun AboutSection(onReplayTutorial: () -> Unit) { SettingsSection("About") { - SettingsRow( - "What's new", - Modifier.clickable(onClick = onOpenWhatsNew), - subtitle = "Version ${BuildConfig.VERSION_NAME}", - icon = Icons.Filled.Star, - trailing = { Arrow() }, - ) - SettingsDivider() - SettingsRow( - "Replay the ${Intro.release} intro", - Modifier.clickable(onClick = onReplayIntro), - subtitle = "What's in this release, in a few taps", - icon = Icons.Filled.Star, - trailing = { Arrow() }, - ) - SettingsDivider() SettingsRow( "Replay the setup guide", Modifier.clickable(onClick = onReplayTutorial), @@ -200,48 +189,17 @@ private fun ConnectionSection() { } } -/** Connection help, and the tools for checking what Gearslip does. */ +/** Tools for testing a debug build; release builds never show this section. */ @Composable -private fun HelpSection(onOpenHelp: () -> Unit, onOpenCarPreview: () -> Unit, onOpenLogs: () -> Unit) { +private fun DebugSection() { val context = LocalContext.current - val status by SessionStatus.state.collectAsState() - val live = status.phase == SessionStatus.Phase.CONNECTING || status.phase == SessionStatus.Phase.PROJECTING - SettingsSection("Help and tools") { + SettingsSection("Debug build") { SettingsRow( - "Connection help", - Modifier.clickable(onClick = onOpenHelp), - subtitle = "Which cars work, and what to set up first", - icon = Icons.Filled.Info, - trailing = { Arrow() }, + "Send a test message", + Modifier.clickable { TestMessage.post(context) }, + subtitle = "Posts a message notification to read on the car", + icon = Icons.AutoMirrored.Filled.Send, ) - // The preview draws the car screen on the phone, so it waits until no car is using it. - if (!live) { - SettingsDivider() - SettingsRow( - "Car preview", - Modifier.clickable(onClick = onOpenCarPreview), - subtitle = "See the car screen on your phone", - icon = Icons.Filled.PlayArrow, - trailing = { Arrow() }, - ) - } - SettingsDivider() - SettingsRow( - "Live logs", - Modifier.clickable(onClick = onOpenLogs), - subtitle = "What Gearslip and the car are saying to each other", - icon = Icons.AutoMirrored.Filled.List, - trailing = { Arrow() }, - ) - if (BuildConfig.DEBUG) { - SettingsDivider() - SettingsRow( - "Send a test message", - Modifier.clickable { TestMessage.post(context) }, - subtitle = "Debug builds only. Posts a message notification to read on the car", - icon = Icons.AutoMirrored.Filled.Send, - ) - } } }