usage notes changes, audio changes, video quality increase.

This commit is contained in:
Seb3thehacker committed 2026-10-07 23:50:47 -07:00
1 parent 55320c4d98
commit c212c2e2d1
4 files changed
+83 -35

No files matched your search

@@ -60,6 +60,8 @@ fun GearslipApp(onDisconnect: () -> Unit, onRequestCallScreening: () -> Unit) {
}, },
) )
} }
// Home's usage notes row opens Settings at that section rather than at the top.
var settingsAtUsageNotes by rememberSaveable { mutableStateOf(false) }
BackHandler(enabled = screen != Screen.HOME) { BackHandler(enabled = screen != Screen.HOME) {
screen = if (screen == Screen.RECOMMENDED_APPS) Screen.SETTINGS else Screen.HOME screen = if (screen == Screen.RECOMMENDED_APPS) Screen.SETTINGS else Screen.HOME
} }
@@ -95,18 +97,20 @@ fun GearslipApp(onDisconnect: () -> Unit, onRequestCallScreening: () -> Unit) {
when (screen) { when (screen) {
Screen.HOME -> HomeScreen( Screen.HOME -> HomeScreen(
onOpenLogs = { screen = Screen.LOGS }, onOpenLogs = { screen = Screen.LOGS },
onOpenSettings = { screen = Screen.SETTINGS }, onOpenSettings = { settingsAtUsageNotes = false; screen = Screen.SETTINGS },
onOpenCarPreview = { screen = Screen.CAR_PREVIEW }, onOpenCarPreview = { screen = Screen.CAR_PREVIEW },
onOpenHelp = { screen = Screen.HELP }, onOpenHelp = { screen = Screen.HELP },
onOpenUsageNotes = { settingsAtUsageNotes = true; screen = Screen.SETTINGS },
onDisconnect = onDisconnect, onDisconnect = onDisconnect,
) )
Screen.WHATS_NEW -> WhatsNewScreen(onBack = { screen = Screen.SETTINGS }) Screen.WHATS_NEW -> WhatsNewScreen(onBack = { screen = Screen.SETTINGS })
Screen.LOGS -> LogsScreen(onBack = { screen = Screen.HOME }) Screen.LOGS -> LogsScreen(onBack = { screen = Screen.HOME })
Screen.SETTINGS -> SettingsScreen( Screen.SETTINGS -> SettingsScreen(
onBack = { screen = Screen.HOME }, onBack = { screen = Screen.HOME },
onOpenRecommendedApps = { screen = Screen.RECOMMENDED_APPS }, onOpenRecommendedApps = { settingsAtUsageNotes = false; screen = Screen.RECOMMENDED_APPS },
onOpenWhatsNew = { screen = Screen.WHATS_NEW }, onOpenWhatsNew = { settingsAtUsageNotes = false; screen = Screen.WHATS_NEW },
onReplayTutorial = { screen = Screen.SETUP_GUIDE }, onReplayTutorial = { screen = Screen.SETUP_GUIDE },
atUsageNotes = settingsAtUsageNotes,
) )
Screen.RECOMMENDED_APPS -> RecommendedAppsScreen(onBack = { screen = Screen.SETTINGS }) Screen.RECOMMENDED_APPS -> RecommendedAppsScreen(onBack = { screen = Screen.SETTINGS })
Screen.CAR_PREVIEW -> CarPreviewScreen(onBack = { screen = Screen.HOME }) Screen.CAR_PREVIEW -> CarPreviewScreen(onBack = { screen = Screen.HOME })
@@ -48,6 +48,7 @@ fun HomeScreen(
onOpenSettings: () -> Unit, onOpenSettings: () -> Unit,
onOpenCarPreview: () -> Unit, onOpenCarPreview: () -> Unit,
onOpenHelp: () -> Unit, onOpenHelp: () -> Unit,
onOpenUsageNotes: () -> Unit,
onDisconnect: () -> Unit, onDisconnect: () -> Unit,
) { ) {
val status by SessionStatus.state.collectAsStateWithLifecycle() val status by SessionStatus.state.collectAsStateWithLifecycle()
@@ -92,7 +93,6 @@ fun HomeScreen(
StatusCard(status) StatusCard(status)
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
UpdateCard() UpdateCard()
UsageNotesHomeCard()
} }
// Sits at the bottom, where a thumb reaches it. // Sits at the bottom, where a thumb reaches it.
@@ -106,6 +106,9 @@ fun HomeScreen(
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
} }
UsageNotesHomeCard(onOpen = onOpenUsageNotes)
Spacer(Modifier.height(16.dp))
Surface(color = MaterialTheme.colorScheme.surfaceContainerHigh, shape = MaterialTheme.shapes.extraLarge) { Surface(color = MaterialTheme.colorScheme.surfaceContainerHigh, shape = MaterialTheme.shapes.extraLarge) {
Column { Column {
if (!live) { if (!live) {
@@ -57,6 +57,9 @@ import app.seb3thehacker.gearslip.BuildConfig
import app.seb3thehacker.gearslip.CertProvider import app.seb3thehacker.gearslip.CertProvider
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.snapshotFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -66,7 +69,16 @@ fun SettingsScreen(
onOpenRecommendedApps: () -> Unit, onOpenRecommendedApps: () -> Unit,
onOpenWhatsNew: () -> Unit, onOpenWhatsNew: () -> Unit,
onReplayTutorial: () -> Unit, onReplayTutorial: () -> Unit,
atUsageNotes: Boolean = false,
) { ) {
val scroll = rememberScrollState()
// Opened from Home's usage notes row: usage notes is the last section, so start at the end.
if (atUsageNotes) {
LaunchedEffect(Unit) {
snapshotFlow { scroll.maxValue }.first { it > 0 && it < Int.MAX_VALUE }
scroll.scrollTo(scroll.maxValue)
}
}
Scaffold( Scaffold(
containerColor = MaterialTheme.colorScheme.background, containerColor = MaterialTheme.colorScheme.background,
topBar = { topBar = {
@@ -85,7 +97,7 @@ fun SettingsScreen(
.fillMaxSize() .fillMaxSize()
.padding(padding) .padding(padding)
.padding(horizontal = 24.dp) .padding(horizontal = 24.dp)
.verticalScroll(rememberScrollState()), .verticalScroll(scroll),
verticalArrangement = Arrangement.spacedBy(24.dp), verticalArrangement = Arrangement.spacedBy(24.dp),
) { ) {
Spacer(Modifier.height(0.dp)) Spacer(Modifier.height(0.dp))
@@ -1,6 +1,7 @@
package app.seb3thehacker.gearslip.ui package app.seb3thehacker.gearslip.ui
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import android.content.Context
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
@@ -9,6 +10,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.selection.toggleable import androidx.compose.foundation.selection.toggleable
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp import androidx.compose.material.icons.filled.KeyboardArrowUp
@@ -83,11 +86,7 @@ internal fun ColumnScope.UsageStatsRows() {
SettingsRow( SettingsRow(
"Send usage notes", "Send usage notes",
Modifier.toggleable(on, role = Role.Switch) { on = it; save() }, Modifier.toggleable(on, role = Role.Switch) { on = it; save() },
subtitle = when { subtitle = buildNote(),
BuildConfig.DEBUG -> "Dev build: nothing is ever sent"
BuildConfig.STATS_URL.isBlank() -> "This build has no stats server, so it sends nothing"
else -> null
},
icon = Icons.Filled.Send, icon = Icons.Filled.Send,
trailing = { Switch(checked = on, onCheckedChange = null) }, trailing = { Switch(checked = on, onCheckedChange = null) },
) )
@@ -126,14 +125,7 @@ internal fun ColumnScope.UsageStatsRows() {
) )
AnimatedVisibility(noteOpen) { AnimatedVisibility(noteOpen) {
// Rebuilt from the saved choices each time one changes, so it always matches. // Rebuilt from the saved choices each time one changes, so it always matches.
val note = remember(choices) { val note = remember(choices) { notePreview(context) }
val preview = UsageStats.preview(context, everything = true)
// Until the first drive the list is empty, so show what one entry looks like.
val example = if (preview.optJSONArray("sessions")?.length() == 0) {
"\n\nExample, not sent: one drive adds this to \"sessions\"\n" + UsageStats.exampleSession.toString(2)
} else ""
preview.toString(2) + example
}
Surface( Surface(
color = MaterialTheme.colorScheme.surfaceContainerLowest, color = MaterialTheme.colorScheme.surfaceContainerLowest,
shape = MaterialTheme.shapes.medium, shape = MaterialTheme.shapes.medium,
@@ -164,23 +156,60 @@ private fun Choice(label: String, checked: Boolean, onChange: (Boolean) -> Unit)
} }
} }
/** Usage notes on Home, always: the switch, and once it's on, what to share. */ /** The note as it would go out now, with an example drive until a real one is in it. */
private fun notePreview(context: Context): String {
val preview = UsageStats.preview(context, everything = true)
// Until the first drive the list is empty, so show what one entry looks like.
val example = if (preview.optJSONArray("sessions")?.length() == 0) {
"\n\nExample, not sent: one drive adds this to \"sessions\"\n" + UsageStats.exampleSession.toString(2)
} else ""
return preview.toString(2) + example
}
/** Why this build sends nothing, when it doesn't; null for a normal release. */
private fun buildNote(): String? = when {
BuildConfig.DEBUG -> "Dev build: nothing is ever sent"
BuildConfig.STATS_URL.isBlank() -> "This build has no stats server, so it sends nothing"
else -> null
}
/**
* 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 @Composable
internal fun UsageNotesHomeCard(modifier: Modifier = Modifier) { internal fun UsageNotesHomeCard(onOpen: () -> Unit, modifier: Modifier = Modifier) {
Column(modifier) { val context = LocalContext.current
Text( var on by remember { mutableStateOf(UsageStats.enabled(context)) }
USAGE_STATS_TITLE,
style = MaterialTheme.typography.labelLarge, SettingsCard(modifier) {
color = MaterialTheme.colorScheme.primary, if (on) {
modifier = Modifier.padding(start = 16.dp, bottom = 4.dp), SettingsRow(
) "Sharing usage notes",
Text( Modifier.clickable(onClick = onOpen),
"Share a short note after each drive, so the next driver knows whether Gearslip works " + subtitle = buildNote(),
"in their car. No name, account, or location.", icon = Icons.Filled.Check,
style = MaterialTheme.typography.bodyMedium, trailing = {
color = MaterialTheme.colorScheme.onSurfaceVariant, Icon(
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 12.dp), Icons.AutoMirrored.Filled.KeyboardArrowRight,
) contentDescription = null,
SettingsCard { UsageStatsRows() } 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) },
)
}
} }
} }