From 909d03fcb3742e5e0a61bad16df64828a978c787 Mon Sep 17 00:00:00 2001 From: Ole-Morten Duesund Date: Wed, 10 Jun 2026 15:43:18 +0200 Subject: [PATCH] Today screen: one-time nudge while backup is unconfigured MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Post-incident follow-up: the gap between 'app supports encrypted backups' and 'backups actually configured' cost real data on 2026-06-10. A quiet card on the Today screen now points at settings until S3 is configured; 'Ikke nå' dismisses it permanently. Co-Authored-By: Claude Fable 5 --- .../meddetsamme/settings/SettingsStore.kt | 5 +++ .../no/naiv/meddetsamme/ui/TodayScreen.kt | 36 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/app/src/main/java/no/naiv/meddetsamme/settings/SettingsStore.kt b/app/src/main/java/no/naiv/meddetsamme/settings/SettingsStore.kt index 94dad0b..3159bc4 100644 --- a/app/src/main/java/no/naiv/meddetsamme/settings/SettingsStore.kt +++ b/app/src/main/java/no/naiv/meddetsamme/settings/SettingsStore.kt @@ -44,6 +44,11 @@ class SettingsStore(context: Context) { get() = prefs.getString("fest_url", null) set(v) = prefs.edit().putString("fest_url", v).apply() + /** One-time "backup isn't set up" card on the Today screen. */ + var backupNudgeDismissed: Boolean + get() = prefs.getBoolean("backup_nudge_dismissed", false) + set(v) = prefs.edit().putBoolean("backup_nudge_dismissed", v).apply() + val isBackupConfigured: Boolean get() = !s3Endpoint.isNullOrBlank() && !s3Bucket.isNullOrBlank() && !s3AccessKey.isNullOrBlank() && !s3SecretKey.isNullOrBlank() diff --git a/app/src/main/java/no/naiv/meddetsamme/ui/TodayScreen.kt b/app/src/main/java/no/naiv/meddetsamme/ui/TodayScreen.kt index f75b894..d813457 100644 --- a/app/src/main/java/no/naiv/meddetsamme/ui/TodayScreen.kt +++ b/app/src/main/java/no/naiv/meddetsamme/ui/TodayScreen.kt @@ -145,6 +145,7 @@ fun TodayScreen(nav: Nav) { modifier = Modifier.fillMaxSize().padding(padding).padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(8.dp), ) { + item { BackupNudge(onSetup = { nav.push(Screen.Settings) }) } if (warnings.isNotEmpty()) { item { Card(modifier = Modifier.fillMaxWidth().semantics { contentDescription = "Varsler om lager og resept" }) { @@ -245,6 +246,41 @@ private fun DoseCard( } } +/** + * Quiet one-time card while backup is unconfigured: health data existing only + * on this device is a real risk (proven the hard way on 2026-06-10). Goes + * away on its own once S3 is configured; "Ikke nå" dismisses it for good. + */ +@Composable +private fun BackupNudge(onSetup: () -> Unit) { + val context = LocalContext.current + val settings = remember { SettingsStore(context) } + var show by remember { + mutableStateOf(!settings.isBackupConfigured && !settings.backupNudgeDismissed) + } + if (!show) return + Card(modifier = Modifier.fillMaxWidth()) { + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { + Text( + "Sikkerhetskopi er ikke satt opp", + style = MaterialTheme.typography.titleSmall, + ) + Text( + "Helsedataene finnes bare på denne enheten. Sett opp kryptert " + + "sikkerhetskopi i innstillingene.", + style = MaterialTheme.typography.bodyMedium, + ) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + TextButton(onClick = onSetup) { Text("Sett opp") } + TextButton(onClick = { + settings.backupNudgeDismissed = true + show = false + }) { Text("Ikke nå") } + } + } + } +} + /** API 33+ runtime notification permission; asked once per app start if missing. */ @Composable private fun NotificationPermissionRequest() {