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.
This commit is contained in:
Seb3thehacker committed 2026-10-24 00:17:12 -07:00
1 parent 55c25fe967
commit ef006dc695
3 files changed
+55 -85

No files matched your search

@@ -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,
@@ -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() {
@@ -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,
)
}
}
}