mirror of
https://github.com/twinksoftware/Gearslip.git
synced 2026-11-10 16:02:25 +01:00
home: show only the car's status; help, preview, logs and the dev test message move to Settings
This commit is contained in:
1 parent
7d9eb20ff0
commit
3869258e0e
5 files changed
+86
-125
No files matched your search
@@ -60,10 +60,13 @@ fun GearslipApp(onDisconnect: () -> Unit, onRequestCallScreening: () -> Unit) {
|
||||
var screen by rememberSaveable { mutableStateOf(if (Intro.due(context)) Screen.INTRO else afterIntro) }
|
||||
// A replay from About goes back to Settings; the launch one carries on into setup.
|
||||
var introReturn by rememberSaveable { mutableStateOf(afterIntro) }
|
||||
// Home's usage notes row opens Settings at that section rather than at the top.
|
||||
// Home's "it worked" card opens Settings at usage notes rather than at the top.
|
||||
var settingsAtUsageNotes by rememberSaveable { mutableStateOf(false) }
|
||||
BackHandler(enabled = screen != Screen.HOME && screen != Screen.INTRO) {
|
||||
screen = if (screen == Screen.RECOMMENDED_APPS) Screen.SETTINGS else Screen.HOME
|
||||
screen = when (screen) {
|
||||
Screen.RECOMMENDED_APPS, Screen.LOGS, Screen.CAR_PREVIEW, Screen.HELP -> Screen.SETTINGS
|
||||
else -> Screen.HOME
|
||||
}
|
||||
}
|
||||
|
||||
// Home stays upright; every other screen turns with the phone.
|
||||
@@ -101,27 +104,27 @@ fun GearslipApp(onDisconnect: () -> Unit, onRequestCallScreening: () -> Unit) {
|
||||
when (screen) {
|
||||
Screen.INTRO -> IntroScreen(onDone = { screen = introReturn })
|
||||
Screen.HOME -> HomeScreen(
|
||||
onOpenLogs = { screen = Screen.LOGS },
|
||||
onOpenSettings = { settingsAtUsageNotes = false; screen = Screen.SETTINGS },
|
||||
onOpenCarPreview = { screen = Screen.CAR_PREVIEW },
|
||||
onOpenHelp = { screen = Screen.HELP },
|
||||
onOpenUsageNotes = { settingsAtUsageNotes = true; screen = Screen.SETTINGS },
|
||||
onDisconnect = onDisconnect,
|
||||
)
|
||||
Screen.WHATS_NEW -> WhatsNewScreen(onBack = { screen = Screen.SETTINGS })
|
||||
Screen.LOGS -> LogsScreen(onBack = { screen = Screen.HOME })
|
||||
Screen.LOGS -> LogsScreen(onBack = { screen = Screen.SETTINGS })
|
||||
Screen.SETTINGS -> SettingsScreen(
|
||||
onBack = { screen = Screen.HOME },
|
||||
onOpenRecommendedApps = { settingsAtUsageNotes = false; screen = Screen.RECOMMENDED_APPS },
|
||||
onOpenWhatsNew = { settingsAtUsageNotes = false; screen = Screen.WHATS_NEW },
|
||||
onOpenHelp = { settingsAtUsageNotes = false; screen = Screen.HELP },
|
||||
onOpenCarPreview = { settingsAtUsageNotes = false; screen = Screen.CAR_PREVIEW },
|
||||
onOpenLogs = { settingsAtUsageNotes = false; screen = Screen.LOGS },
|
||||
onReplayTutorial = { screen = Screen.SETUP_GUIDE },
|
||||
onReplayIntro = { introReturn = Screen.SETTINGS; screen = Screen.INTRO },
|
||||
atUsageNotes = settingsAtUsageNotes,
|
||||
)
|
||||
Screen.RECOMMENDED_APPS -> RecommendedAppsScreen(onBack = { screen = Screen.SETTINGS })
|
||||
Screen.CAR_PREVIEW -> CarPreviewScreen(onBack = { screen = Screen.HOME })
|
||||
Screen.CAR_PREVIEW -> CarPreviewScreen(onBack = { screen = Screen.SETTINGS })
|
||||
Screen.HELP -> HelpScreen(
|
||||
onBack = { screen = Screen.HOME },
|
||||
onBack = { screen = Screen.SETTINGS },
|
||||
onRequestCallScreening = onRequestCallScreening,
|
||||
)
|
||||
Screen.CERT_SETUP -> CertSetupScreen(
|
||||
|
||||
@@ -47,10 +47,7 @@ import app.seb3thehacker.gearslip.stats.WorkedPrompt
|
||||
|
||||
@Composable
|
||||
fun HomeScreen(
|
||||
onOpenLogs: () -> Unit,
|
||||
onOpenSettings: () -> Unit,
|
||||
onOpenCarPreview: () -> Unit,
|
||||
onOpenHelp: () -> Unit,
|
||||
onOpenUsageNotes: () -> Unit,
|
||||
onDisconnect: () -> Unit,
|
||||
) {
|
||||
@@ -108,10 +105,6 @@ fun HomeScreen(
|
||||
}
|
||||
Spacer(Modifier.height(16.dp))
|
||||
UpdateCard()
|
||||
if (status.phase != SessionStatus.Phase.PROJECTING) {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
WirelessCard()
|
||||
}
|
||||
}
|
||||
|
||||
// Sits at the bottom, where a thumb reaches it.
|
||||
@@ -120,29 +113,8 @@ fun HomeScreen(
|
||||
Button(onClick = onDisconnect, modifier = Modifier.fillMaxWidth().height(56.dp)) {
|
||||
Text("Disconnect")
|
||||
}
|
||||
Spacer(Modifier.height(16.dp))
|
||||
}
|
||||
|
||||
// Not while the card above is asking the same thing.
|
||||
if (workedCar == null || live) {
|
||||
UsageNotesHomeCard(onOpen = onOpenUsageNotes)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
}
|
||||
|
||||
Surface(color = MaterialTheme.colorScheme.surfaceContainerHigh, shape = MaterialTheme.shapes.extraLarge) {
|
||||
Column {
|
||||
if (!live) {
|
||||
ActionRow(Icons.Filled.PlayArrow, "Car preview", onOpenCarPreview)
|
||||
RowDivider()
|
||||
}
|
||||
ActionRow(Icons.Filled.List, "Live logs", onOpenLogs)
|
||||
RowDivider()
|
||||
ActionRow(Icons.Filled.Info, "Connection help", onOpenHelp)
|
||||
if (BuildConfig.DEBUG) {
|
||||
RowDivider()
|
||||
ActionRow(Icons.Filled.Send, "Send a test message") { TestMessage.post(context) }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
WirelessButton()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,34 +124,6 @@ fun HomeScreen(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ActionRow(icon: ImageVector, label: String, onClick: () -> Unit) {
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 20.dp, vertical = 18.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
Icon(icon, contentDescription = null, tint = MaterialTheme.colorScheme.primary)
|
||||
Text(label, style = MaterialTheme.typography.titleMedium, modifier = Modifier.weight(1f))
|
||||
Icon(
|
||||
Icons.AutoMirrored.Filled.KeyboardArrowRight,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RowDivider() {
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(horizontal = 20.dp),
|
||||
color = MaterialTheme.colorScheme.outlineVariant,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StatusCard(status: SessionStatus.Snapshot) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
|
||||
@@ -19,7 +19,11 @@ import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
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.Add
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||
@@ -60,6 +64,8 @@ import androidx.compose.ui.unit.dp
|
||||
import app.seb3thehacker.gearslip.AppSettings
|
||||
import app.seb3thehacker.gearslip.BuildConfig
|
||||
import app.seb3thehacker.gearslip.CertProvider
|
||||
import app.seb3thehacker.gearslip.SessionStatus
|
||||
import app.seb3thehacker.gearslip.notify.TestMessage
|
||||
import app.seb3thehacker.gearslip.car.CarSettings
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -74,12 +80,15 @@ fun SettingsScreen(
|
||||
onBack: () -> Unit,
|
||||
onOpenRecommendedApps: () -> Unit,
|
||||
onOpenWhatsNew: () -> Unit,
|
||||
onOpenHelp: () -> Unit,
|
||||
onOpenCarPreview: () -> Unit,
|
||||
onOpenLogs: () -> Unit,
|
||||
onReplayTutorial: () -> Unit,
|
||||
onReplayIntro: () -> Unit,
|
||||
atUsageNotes: Boolean = false,
|
||||
) {
|
||||
val scroll = rememberScrollState()
|
||||
// Opened from Home's usage notes row: start at that section.
|
||||
// Opened from Home's "it worked" card: start at usage notes.
|
||||
var usageNotesY by remember { mutableIntStateOf(-1) }
|
||||
if (atUsageNotes) {
|
||||
LaunchedEffect(Unit) {
|
||||
@@ -111,6 +120,7 @@ fun SettingsScreen(
|
||||
) {
|
||||
Spacer(Modifier.height(0.dp))
|
||||
ConnectionSection()
|
||||
HelpSection(onOpenHelp, onOpenCarPreview, onOpenLogs)
|
||||
CarAudioSection()
|
||||
VoiceModelSection()
|
||||
if (SHOW_RECOMMENDED_APPS) AppsSection(onOpenRecommendedApps)
|
||||
@@ -185,6 +195,51 @@ private fun ConnectionSection() {
|
||||
}
|
||||
}
|
||||
|
||||
/** Connection help, and the tools for checking what Gearslip does. */
|
||||
@Composable
|
||||
private fun HelpSection(onOpenHelp: () -> Unit, onOpenCarPreview: () -> Unit, onOpenLogs: () -> Unit) {
|
||||
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") {
|
||||
SettingsRow(
|
||||
"Connection help",
|
||||
Modifier.clickable(onClick = onOpenHelp),
|
||||
subtitle = "Which cars work, and what to set up first",
|
||||
icon = Icons.Filled.Info,
|
||||
trailing = { Arrow() },
|
||||
)
|
||||
// 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 = "Dev builds only. Posts a message notification to read on the car",
|
||||
icon = Icons.AutoMirrored.Filled.Send,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** How music reaches the car: by Bluetooth as usual, or through Gearslip over USB. Same choice as setup. */
|
||||
@Composable
|
||||
private fun CarAudioSection() {
|
||||
|
||||
@@ -256,44 +256,3 @@ internal fun WorkedCard(car: String, onSeeWhatsSent: () -> Unit, modifier: Modif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Usage notes on Home, one row with one action either way. While they're off, the row is the
|
||||
* switch, so opting in is a single tap. Once they're on, it shrinks to a quiet line that opens
|
||||
* Settings at its usage notes section, so a driver already sharing isn't pitched again.
|
||||
*/
|
||||
@Composable
|
||||
internal fun UsageNotesHomeCard(onOpen: () -> Unit, modifier: Modifier = Modifier) {
|
||||
val context = LocalContext.current
|
||||
var on by remember { mutableStateOf(UsageStats.enabled(context)) }
|
||||
|
||||
SettingsCard(modifier) {
|
||||
if (on) {
|
||||
SettingsRow(
|
||||
"Sharing usage notes",
|
||||
Modifier.clickable(onClick = onOpen),
|
||||
subtitle = buildNote(),
|
||||
icon = Icons.Filled.Check,
|
||||
trailing = {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Filled.KeyboardArrowRight,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
SettingsRow(
|
||||
USAGE_STATS_TITLE,
|
||||
Modifier.toggleable(false, role = Role.Switch) {
|
||||
UsageStats.set(context, true, UsageStats.choices(context))
|
||||
on = true
|
||||
},
|
||||
subtitle = buildNote() ?: ("A short note after each drive tells the next driver " +
|
||||
"whether their car works. No name, account, or location"),
|
||||
icon = Icons.Filled.Send,
|
||||
trailing = { Switch(checked = false, onCheckedChange = null) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
-17
@@ -8,15 +8,13 @@ import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
@@ -41,11 +39,11 @@ private val WIRELESS_PERMISSIONS = buildList {
|
||||
}.toTypedArray()
|
||||
|
||||
/**
|
||||
* Test entry point for wireless Android Auto: pick the paired car, and Gearslip connects to it
|
||||
* over Bluetooth and Wi-Fi. Automatic start comes later.
|
||||
* Test entry point for wireless Android Auto, at the bottom of Home: pick the paired car, and
|
||||
* Gearslip connects to it over Bluetooth and Wi-Fi. Automatic start comes later.
|
||||
*/
|
||||
@Composable
|
||||
internal fun WirelessCard() {
|
||||
internal fun WirelessButton() {
|
||||
val context = LocalContext.current
|
||||
var devices by remember { mutableStateOf<List<BluetoothDevice>?>(null) }
|
||||
var denied by remember { mutableStateOf(false) }
|
||||
@@ -56,18 +54,21 @@ internal fun WirelessCard() {
|
||||
if (granted()) devices = WirelessLink.pairedDevices(context) else denied = true
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
SettingsRow(
|
||||
"Connect wirelessly (test)",
|
||||
Modifier.clickable {
|
||||
denied = false
|
||||
if (granted()) devices = WirelessLink.pairedDevices(context) else ask.launch(WIRELESS_PERMISSIONS)
|
||||
},
|
||||
subtitle = if (denied) "Gearslip needs Bluetooth and nearby Wi-Fi access for this"
|
||||
else "Pick your car from the phone's paired devices",
|
||||
icon = Icons.Filled.Info,
|
||||
if (denied) {
|
||||
Text(
|
||||
"Gearslip needs Bluetooth and nearby Wi-Fi access for this.",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(bottom = 8.dp),
|
||||
)
|
||||
}
|
||||
FilledTonalButton(
|
||||
onClick = {
|
||||
denied = false
|
||||
if (granted()) devices = WirelessLink.pairedDevices(context) else ask.launch(WIRELESS_PERMISSIONS)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth().height(56.dp),
|
||||
) { Text("Connect wirelessly (test)") }
|
||||
|
||||
devices?.let { list ->
|
||||
AlertDialog(
|
||||
@@ -96,5 +97,4 @@ internal fun WirelessCard() {
|
||||
confirmButton = { TextButton(onClick = { devices = null }) { Text("Cancel") } },
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.height(16.dp))
|
||||
}
|
||||
Reference in new issue
Block a user