Count passphrase-only Google backup as 'configured' in the UI

Split the overloaded isBackupConfigured into isS3Configured (gates the
S3 upload path — unchanged behaviour) and isAnyBackupConfigured (S3 OR
a passphrase, which drives the always-on Google blob). The settings
status card and the Today nudge now use the latter, so a passphrase-only
setup reads as protected instead of 'ikke satt opp'. Status line
distinguishes the three states: Google-only, Google+S3, and nothing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2026-06-11 21:24:22 +02:00
commit 0cae8a8ef9
5 changed files with 25 additions and 14 deletions

View file

@ -116,11 +116,11 @@ class BackupManager(private val context: Context) {
suspend fun runConfiguredBackups(): BackupOutcome { suspend fun runConfiguredBackups(): BackupOutcome {
val settings = SettingsStore(context) val settings = SettingsStore(context)
val hasPassphrase = !settings.autoBackupPassphrase.isNullOrEmpty() val hasPassphrase = !settings.autoBackupPassphrase.isNullOrEmpty()
require(hasPassphrase || settings.isBackupConfigured) { "Ingen sikkerhetskopi er satt opp" } require(settings.isAnyBackupConfigured) { "Ingen sikkerhetskopi er satt opp" }
// Google Auto Backup blob: written whenever a passphrase exists. // Google Auto Backup blob: written whenever a passphrase exists.
writeCloudBlob() writeCloudBlob()
val s3Key = if (settings.isBackupConfigured) runAutoBackup() else null val s3Key = if (settings.isS3Configured) runAutoBackup() else null
return BackupOutcome(cloudBlobWritten = hasPassphrase, s3Key = s3Key) return BackupOutcome(cloudBlobWritten = hasPassphrase, s3Key = s3Key)
} }

View file

@ -28,7 +28,7 @@ class BackupWorker(context: Context, params: WorkerParameters) : CoroutineWorker
} catch (e: Exception) { } catch (e: Exception) {
return if (runAttemptCount < 3) Result.retry() else Result.failure() return if (runAttemptCount < 3) Result.retry() else Result.failure()
} }
if (!SettingsStore(applicationContext).isBackupConfigured) return Result.success() if (!SettingsStore(applicationContext).isS3Configured) return Result.success()
return try { return try {
manager.runAutoBackup() manager.runAutoBackup()
Result.success() Result.success()

View file

@ -55,12 +55,21 @@ class SettingsStore(context: Context) {
get() = prefs.getInt("supply_check_minute", 600) get() = prefs.getInt("supply_check_minute", 600)
set(v) = prefs.edit { putInt("supply_check_minute", v.coerceIn(0, 1439)) } set(v) = prefs.edit { putInt("supply_check_minute", v.coerceIn(0, 1439)) }
val isBackupConfigured: Boolean /** S3/Garage credentials complete — gates the S3 upload path only. */
val isS3Configured: Boolean
get() = !s3Endpoint.isNullOrBlank() && !s3Bucket.isNullOrBlank() && get() = !s3Endpoint.isNullOrBlank() && !s3Bucket.isNullOrBlank() &&
!s3AccessKey.isNullOrBlank() && !s3SecretKey.isNullOrBlank() !s3AccessKey.isNullOrBlank() && !s3SecretKey.isNullOrBlank()
/**
* Any backup mechanism active: S3, OR a passphrase alone (which drives the
* always-on Google Auto Backup blob). What the UI "configured?" status and
* the Today nudge key off so a passphrase-only setup reads as protected.
*/
val isAnyBackupConfigured: Boolean
get() = isS3Configured || !autoBackupPassphrase.isNullOrEmpty()
fun s3Client(): no.naiv.meddetsamme.backup.S3Client? { fun s3Client(): no.naiv.meddetsamme.backup.S3Client? {
if (!isBackupConfigured) return null if (!isS3Configured) return null
return no.naiv.meddetsamme.backup.S3Client( return no.naiv.meddetsamme.backup.S3Client(
endpoint = s3Endpoint!!.trimEnd('/'), endpoint = s3Endpoint!!.trimEnd('/'),
region = s3Region?.takeIf { it.isNotBlank() } ?: "garage", region = s3Region?.takeIf { it.isNotBlank() } ?: "garage",

View file

@ -80,13 +80,19 @@ fun SettingsScreen(nav: Nav) {
verticalArrangement = Arrangement.spacedBy(10.dp), verticalArrangement = Arrangement.spacedBy(10.dp),
) { ) {
// Backup got its own page — seven fields drowned everything else here. // Backup got its own page — seven fields drowned everything else here.
val configured = settings.isAnyBackupConfigured
val statusLine = when {
settings.isS3Configured -> "Google + S3/Garage konfigurert"
configured -> "Google-sikkerhetskopi aktiv (kryptert)"
else -> "Ikke satt opp — helsedata finnes bare på denne enheten"
}
Card( Card(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clickable { nav.push(Screen.BackupSettings) } .clickable { nav.push(Screen.BackupSettings) }
.semantics(mergeDescendants = true) { .semantics(mergeDescendants = true) {
contentDescription = if (settings.isBackupConfigured) { contentDescription = if (configured) {
"Sikkerhetskopi, konfigurert. Trykk for å åpne." "Sikkerhetskopi: $statusLine. Trykk for å åpne."
} else { } else {
"Sikkerhetskopi, ikke satt opp. Trykk for å sette opp." "Sikkerhetskopi, ikke satt opp. Trykk for å sette opp."
} }
@ -96,13 +102,9 @@ fun SettingsScreen(nav: Nav) {
Column(Modifier.weight(1f)) { Column(Modifier.weight(1f)) {
Text("Sikkerhetskopi", style = MaterialTheme.typography.titleMedium) Text("Sikkerhetskopi", style = MaterialTheme.typography.titleMedium)
Text( Text(
if (settings.isBackupConfigured) { statusLine,
"S3/Garage konfigurert · eksport og import"
} else {
"Ikke satt opp — helsedata finnes bare på denne enheten"
},
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = if (settings.isBackupConfigured) { color = if (configured) {
MaterialTheme.colorScheme.onSurfaceVariant MaterialTheme.colorScheme.onSurfaceVariant
} else { } else {
MaterialTheme.colorScheme.error MaterialTheme.colorScheme.error

View file

@ -382,7 +382,7 @@ private fun BackupNudge(onSetup: () -> Unit) {
val context = LocalContext.current val context = LocalContext.current
val settings = remember { SettingsStore(context) } val settings = remember { SettingsStore(context) }
var show by remember { var show by remember {
mutableStateOf(!settings.isBackupConfigured && !settings.backupNudgeDismissed) mutableStateOf(!settings.isAnyBackupConfigured && !settings.backupNudgeDismissed)
} }
if (!show) return if (!show) return
Card(modifier = Modifier.fillMaxWidth()) { Card(modifier = Modifier.fillMaxWidth()) {