Today screen: one-time nudge while backup is unconfigured

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 <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2026-06-10 15:43:18 +02:00
commit 909d03fcb3
2 changed files with 41 additions and 0 deletions

View file

@ -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()

View file

@ -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() {