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 97fd21b..de43558 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/HomeScreen.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/HomeScreen.kt @@ -97,6 +97,8 @@ fun HomeScreen( ) Spacer(Modifier.height(16.dp)) StatusCard(status) + Spacer(Modifier.height(16.dp)) + UsageNotesHomeCard(onOpen = onOpenUsageNotes) // The first drive that worked asks once to share it, right under the result. val car = workedCar if (car != null && !live) { diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/UsageStatsChoices.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/UsageStatsChoices.kt index 7818aee..66e6180 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/UsageStatsChoices.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/UsageStatsChoices.kt @@ -256,3 +256,44 @@ 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) }, + ) + } + } +} diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/wireless/WirelessLink.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/wireless/WirelessLink.kt index 6647ab3..5742c74 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/wireless/WirelessLink.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/wireless/WirelessLink.kt @@ -84,6 +84,8 @@ object WirelessLink { @Volatile private var worker: Thread? = null /** Set when Android drops the car's network mid-try, so the rest don't bind to a dead one. */ @Volatile private var lostNetwork: Network? = null + /** Bumped on each Wi-Fi join and on stop, so an older run of TCP tries ends instead of racing the new one. */ + @Volatile private var tcpRun = 0 private val writeLock = Any() /** Paired Bluetooth devices, cars or not: the driver picks theirs. Needs BLUETOOTH_CONNECT. */ @@ -112,6 +114,7 @@ object WirelessLink { /** Closes the Bluetooth channel, the socket and the Wi-Fi request. Safe to call any time. */ fun stop(context: Context) { + tcpRun++ worker?.interrupt() worker = null runCatching { listener?.close() } @@ -303,7 +306,8 @@ object WirelessLink { send(output, MSG_CONNECTION_STATUS, Protobuf.varintField(1, STATUS_SUCCESS)) log.i("-> StartResponse (phone at $phoneIp), ConnectionStatus: success") }.onFailure { log.w("Bluetooth: couldn't report the Wi-Fi join (${it.message})") } - Thread({ connectTcp(context, output, name, net, ip, port) }, "gearslip-wireless-tcp").start() + val run = ++tcpRun + Thread({ connectTcp(context, output, name, net, ip, port, run) }, "gearslip-wireless-tcp").start() } override fun onUnavailable() { @@ -394,11 +398,15 @@ object WirelessLink { } } - private fun connectTcp(context: Context, output: OutputStream, name: String, net: Network, ip: String, port: Int) { + private fun connectTcp(context: Context, output: OutputStream, name: String, net: Network, ip: String, port: Int, run: Int) { SessionStatus.connecting("Wi-Fi: reaching $name") lostNetwork = null var last: Exception? = null repeat(TCP_TRIES) { attempt -> + if (run != tcpRun) { + log.i("TCP: stopping the tries on the old network - the car started over") + return + } try { // A network Android has dropped refuses every bind (EPERM). If the driver joined the // car's Wi-Fi by hand, the phone's ordinary route reaches it, so use that instead.