diff --git a/CHANGELOG.md b/CHANGELOG.md index c887e30..e278004 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,9 +38,11 @@ The first release of Clockula: alarms, timers, a stopwatch and a world clock. timer turns the screen on and opens over the lock screen with Stop and +1 min. Timers keep counting through a reboot and aren't affected by changes to the clock. -- Timers have their own vibration switch, off by default, and an optional - gradual volume, both apart from the alarms'. A kitchen timer can just beep - while alarms still vibrate; a silent timer still vibrates. +- Timers have their own vibration switch and an optional gradual volume, both + apart from the alarms'. A kitchen timer can just beep while alarms still + vibrate; a silent timer still vibrates. New installs start with timer + vibration off; an updated install keeps vibrating its timers if it did + before. - **Stopwatch** with laps, the fastest and slowest marked, and Lap and Pause in the notification. It keeps counting while the app is closed. - **World clock.** An analog face for your own time, plus cities from the diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/ClockulaApp.kt b/app/src/main/java/de/jeanlucmakiola/clockula/ClockulaApp.kt index 9953f26..5945f12 100644 --- a/app/src/main/java/de/jeanlucmakiola/clockula/ClockulaApp.kt +++ b/app/src/main/java/de/jeanlucmakiola/clockula/ClockulaApp.kt @@ -105,7 +105,9 @@ class ClockulaApp : Application() { } catch (cancelled: CancellationException) { throw cancelled } catch (_: Exception) { - // Unsettled is safe; the next launch tries again. + // Unsettled reads as the old behaviour. The next launch tries again, + // and by then the store is no longer empty, so even a fresh install + // settles on the alarm default: vibrating, the safe side to err on. } } diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/data/prefs/SettingsPrefs.kt b/app/src/main/java/de/jeanlucmakiola/clockula/data/prefs/SettingsPrefs.kt index 977db31..bed61f8 100644 --- a/app/src/main/java/de/jeanlucmakiola/clockula/data/prefs/SettingsPrefs.kt +++ b/app/src/main/java/de/jeanlucmakiola/clockula/data/prefs/SettingsPrefs.kt @@ -1,5 +1,7 @@ package de.jeanlucmakiola.clockula.data.prefs +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.stringPreferencesKey import de.jeanlucmakiola.clockula.domain.ClockDefaults import de.jeanlucmakiola.clockula.domain.DismissChallenge import de.jeanlucmakiola.clockula.domain.backup.BackupSettings @@ -135,10 +137,16 @@ class SettingsPrefs @Inject constructor(private val store: PrefStore) { * side's fallback keeps the old behaviour, so a failed write costs nothing. */ suspend fun settleTimerVibrate(freshInstall: Boolean) { - val untouched = store.data.first().asMap().isEmpty() - val alarmVibrate = store.get(ClockPrefs.vibrate) - store.update(ClockPrefs.timerVibrate) { stored -> - stored ?: if (untouched && freshInstall) false else alarmVibrate + // One transaction, so nothing written meanwhile can make the snapshot + // it decides on stale. Raw keys for the reads: the kit reads a Pref + // only from a store, not from a transaction's preferences. + store.edit { prefs -> + if (stringPreferencesKey(ClockPrefs.timerVibrate.name) in prefs) return@edit + prefs[ClockPrefs.timerVibrate] = if (prefs.asMap().isEmpty() && freshInstall) { + false + } else { + prefs[booleanPreferencesKey(ClockPrefs.vibrate.name)] ?: ClockPrefs.vibrate.default + } } } diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/TimerDefaultsScreen.kt b/app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/TimerDefaultsScreen.kt index be6aca0..2d7c659 100644 --- a/app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/TimerDefaultsScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/TimerDefaultsScreen.kt @@ -10,6 +10,11 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.toggleableState +import androidx.compose.ui.state.ToggleableState import androidx.compose.ui.unit.dp import de.jeanlucmakiola.clockula.R import de.jeanlucmakiola.clockula.ui.alarms.VolumeRampPicker @@ -49,6 +54,11 @@ internal fun TimerDefaultsScreen( // what the ring does with Silent and vibration both off: vibrates anyway. GroupedRow( title = stringResource(R.string.editor_vibrate), + // The switch below is display-only, so the row carries its role and state. + modifier = Modifier.semantics { + role = Role.Switch + toggleableState = ToggleableState(defaults.timerVibrate) + }, summary = if (defaults.timerVibrate) { vibrateText(on = true) } else { @@ -57,7 +67,7 @@ internal fun TimerDefaultsScreen( position = Position.Middle, trailing = { // Display only: the row is the one control, so a screen reader - // stops on it once rather than on the row and then the switch. + // stops once, on a row announced as a switch. Switch(checked = defaults.timerVibrate, onCheckedChange = null) }, onClick = { viewModel.onTimerVibrateChanged(!defaults.timerVibrate) },