diff --git a/echolot-app/app/src/main/kotlin/app/echo_lot/app/SettingsScreen.kt b/echolot-app/app/src/main/kotlin/app/echo_lot/app/SettingsScreen.kt index 5d59bac..f82bb00 100644 --- a/echolot-app/app/src/main/kotlin/app/echo_lot/app/SettingsScreen.kt +++ b/echolot-app/app/src/main/kotlin/app/echo_lot/app/SettingsScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.FilterChip +import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Switch @@ -130,12 +131,21 @@ fun SettingsScreen( } Text(privacyExplanation(privacy), style = MaterialTheme.typography.bodySmall) + // At FULL nothing is pseudonymized, so a salt has nothing to act on. Shown + // disabled rather than hidden: the setting is still stored and still applies the + // moment the level changes, and a control that vanishes hides that fact. Toggle( label = "Stable pseudonyms across runs", - detail = "Lets you compare uploaded runs over time (same SSID reads the same " + - "each time). It also links your uploads together, so leave it off on a " + - "server you don't run yourself.", - checked = stableSalt, + detail = if (privacy == PrivacyLevel.FULL) { + "Not used at this level — nothing is pseudonymized, so there is nothing " + + "to keep stable. Choose balanced or strict to use this." + } else { + "Lets you compare uploaded runs over time (same SSID reads the same " + + "each time). It also links your uploads together, so leave it off on " + + "a server you don't run yourself." + }, + checked = stableSalt && privacy != PrivacyLevel.FULL, + enabled = privacy != PrivacyLevel.FULL, ) { stableSalt = it; settings.stableSalt = it } TextButton(onClick = onPreviewUpload) { Text("Preview what an upload would send") } @@ -237,13 +247,22 @@ private fun privacyExplanation(level: PrivacyLevel): String = when (level) { } @Composable -private fun Toggle(label: String, detail: String, checked: Boolean, onChange: (Boolean) -> Unit) { +private fun Toggle( + label: String, + detail: String, + checked: Boolean, + enabled: Boolean = true, + onChange: (Boolean) -> Unit, +) { Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.Top) { Column(Modifier.weight(1f)) { - Text(label, style = MaterialTheme.typography.bodyMedium) - Text(detail, style = MaterialTheme.typography.bodySmall) + // Dimmed together with the switch, so "this does nothing right now" reads at a glance + // instead of only on close inspection. + val alpha = if (enabled) 1f else 0.5f + Text(label, style = MaterialTheme.typography.bodyMedium, color = LocalContentColor.current.copy(alpha = alpha)) + Text(detail, style = MaterialTheme.typography.bodySmall, color = LocalContentColor.current.copy(alpha = alpha)) } - Switch(checked = checked, onCheckedChange = onChange) + Switch(checked = checked, onCheckedChange = onChange, enabled = enabled) } }