fix(timers): settle timer vibration in one transaction and announce its row as a switch
The settle read the store, then wrote it in a second step; a write in between could leave it deciding on a stale snapshot. It is one edit now, which also skips the write once the value exists. The Vibrate row carries the switch role and its state, since the switch itself is display-only. The changelog says what an updated install gets, not just a fresh one. Refs #22
This commit is contained in:
1 parent
84d31fe7f8
commit
cc2099c40a
4 files changed
+31
-9
No files matched your search
+5
-3
@@ -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
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) },
|
||||
|
||||
Reference in new issue
Block a user