show usage notes in dev builds too, but never send from them

This commit is contained in:
Seb3thehacker committed 2026-10-05 01:31:15 -07:00
1 parent 62fb8babad
commit 302dd6c344
4 files changed
+26 -30

No files matched your search

@@ -51,15 +51,15 @@ object UsageStats {
private fun prefs(context: Context) = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE) private fun prefs(context: Context) = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
/** /**
* Dev builds never send anything and never ask: no switch in Settings, no page in setup, so * Dev builds show every switch and screen, so they can be tested, but never send anything:
* testing doesn't muddy the numbers. Only release builds offer the choice. * testing mustn't muddy the numbers.
*/ */
val choosable: Boolean get() = !BuildConfig.DEBUG val sends: Boolean get() = !BuildConfig.DEBUG && BuildConfig.STATS_URL.isNotBlank()
fun enabled(context: Context): Boolean = choosable && prefs(context).getBoolean(KEY_ENABLED, false) fun enabled(context: Context): Boolean = prefs(context).getBoolean(KEY_ENABLED, false)
/** Whether the driver has answered the question in setup, either way. */ /** Whether the driver has answered the question in setup, either way. */
fun asked(context: Context): Boolean = !choosable || prefs(context).getBoolean(KEY_ASKED, false) fun asked(context: Context): Boolean = prefs(context).getBoolean(KEY_ASKED, false)
fun choices(context: Context): Choices { fun choices(context: Context): Choices {
val p = prefs(context) val p = prefs(context)
@@ -98,7 +98,7 @@ object UsageStats {
* a car recorded mid-send still goes out. * a car recorded mid-send still goes out.
*/ */
private fun sendSoon(context: Context) { private fun sendSoon(context: Context) {
if (!enabled(context)) return if (!sends || !enabled(context)) return
if (!sending.compareAndSet(false, true)) { sendAgain = true; return } if (!sending.compareAndSet(false, true)) { sendAgain = true; return }
val app = context.applicationContext val app = context.applicationContext
Thread({ Thread({
@@ -192,9 +192,8 @@ object UsageStats {
/** Sends the note now; false when it didn't get through. */ /** Sends the note now; false when it didn't get through. */
private fun send(context: Context): Boolean { private fun send(context: Context): Boolean {
if (!enabled(context)) return true if (!sends || !enabled(context)) return true
val endpoint = BuildConfig.STATS_URL val endpoint = BuildConfig.STATS_URL
if (endpoint.isBlank()) return true
val p = prefs(context) val p = prefs(context)
val note = synchronized(carsLock) { preview(context) } val note = synchronized(carsLock) { preview(context) }
val sentCars = note.optJSONArray("cars")?.toString() val sentCars = note.optJSONArray("cars")?.toString()
@@ -55,7 +55,6 @@ import androidx.compose.ui.unit.dp
import app.seb3thehacker.gearslip.AppSettings import app.seb3thehacker.gearslip.AppSettings
import app.seb3thehacker.gearslip.BuildConfig import app.seb3thehacker.gearslip.BuildConfig
import app.seb3thehacker.gearslip.CertProvider import app.seb3thehacker.gearslip.CertProvider
import app.seb3thehacker.gearslip.stats.UsageStats
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -94,9 +93,7 @@ fun SettingsScreen(
ConnectionSection() ConnectionSection()
if (SHOW_RECOMMENDED_APPS) AppsSection(onOpenRecommendedApps) if (SHOW_RECOMMENDED_APPS) AppsSection(onOpenRecommendedApps)
CertificateSection() CertificateSection()
if (UsageStats.choosable) { SettingsSection("Usage notes", header = USAGE_STATS_INTRO) { UsageStatsRows(showNote = true) }
SettingsSection("Usage notes", header = USAGE_STATS_INTRO) { UsageStatsRows(showNote = true) }
}
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
} }
} }
@@ -287,21 +287,18 @@ private fun pages(context: Context, onRequestCallScreening: () -> Unit): List<Gu
// --- The very end, every time: usage notes. The first time through, leaving the page // --- The very end, every time: usage notes. The first time through, leaving the page
// without turning them on is a no; on a replay, the switch shows what was chosen before. // without turning them on is a no; on a replay, the switch shows what was chosen before.
// Dev builds never send, so they skip the page. add(
if (UsageStats.choosable) { GuidePage(
add( title = USAGE_STATS_TITLE,
GuidePage( explanation = USAGE_STATS_INTRO,
title = USAGE_STATS_TITLE, content = {
explanation = USAGE_STATS_INTRO, UsageStatsChoices()
content = { DisposableEffect(Unit) {
UsageStatsChoices() onDispose { if (!UsageStats.asked(context)) UsageStats.set(context, false) }
DisposableEffect(Unit) { }
onDispose { if (!UsageStats.asked(context)) UsageStats.set(context, false) } },
} ),
}, )
),
)
}
} }
/** /**
@@ -84,7 +84,11 @@ internal fun ColumnScope.UsageStatsRows(showNote: Boolean) {
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 = if (BuildConfig.STATS_URL.isBlank()) "This build has no stats server, so it sends nothing" else null, subtitle = 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
},
icon = Icons.Filled.Send, icon = Icons.Filled.Send,
trailing = { Switch(checked = on, onCheckedChange = null) }, trailing = { Switch(checked = on, onCheckedChange = null) },
) )
@@ -154,11 +158,10 @@ private fun Choice(label: String, checked: Boolean, onChange: (Boolean) -> Unit)
/** /**
* Asks once, for people who set Gearslip up before usage notes existed and so never saw the * Asks once, for people who set Gearslip up before usage notes existed and so never saw the
* setup page. Either answer is final; Settings changes it later. Release builds only. * setup page. Either answer is final; Settings changes it later.
*/ */
@Composable @Composable
internal fun UsageStatsPrompt() { internal fun UsageStatsPrompt() {
if (!UsageStats.choosable) return
val context = LocalContext.current val context = LocalContext.current
var show by remember { mutableStateOf(!UsageStats.asked(context)) } var show by remember { mutableStateOf(!UsageStats.asked(context)) }
if (!show) return if (!show) return