fix(bedtime): switch a recreated rule, keep manual overrides, finish edits
A rule recreated by a pass (deleted in Settings, access re-granted) now gets its state set even when no boundary was crossed, and the applied state is recorded only once the system accepted it. Edits that can't move a boundary (reminder, effects) no longer re-assert the mode over a manual override, and the screen's writes run on the application scope so leaving right after a change can't cut it short. A late reminder counts the minutes actually left, and one slot fire reads the zone once. An equal bedtime and wake time is now no window instead of a day-long one, which on every night meant Do Not Disturb never ended, and an empty days mask in prefs reads as every night, as a backup's does. The rule-id extras are translated to the bedtime action in MainActivity, keeping the routing table keyed on the action alone. Refs #1
This commit is contained in:
1 parent
119197106b
commit
2dce78281a
14 files changed
+148
-67
No files matched your search
@@ -8,6 +8,9 @@ import de.jeanlucmakiola.clockula.domain.time.ZoneProvider
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import java.time.ZoneId
|
||||
import kotlin.math.ceil
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.Instant
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@@ -42,15 +45,21 @@ class BedtimeEngine @Inject constructor(
|
||||
|
||||
/**
|
||||
* A change made on the bedtime screen: stored, then applied at once. The
|
||||
* rule is rewritten when a field it carries changed, and the mode is
|
||||
* switched to what the new schedule says now, so turning bedtime on at
|
||||
* 23:30 for a 23:00 bedtime starts it straight away.
|
||||
* rule is rewritten when a field it carries changed. When the schedule
|
||||
* itself changed, the mode is switched to what it says now, so turning
|
||||
* bedtime on at 23:30 for a 23:00 bedtime starts it straight away; an
|
||||
* edit that cannot move a boundary (the reminder, an effect) leaves a
|
||||
* mode switched by hand alone.
|
||||
*/
|
||||
suspend fun update(transform: (BedtimeSettings) -> BedtimeSettings) = lock.withLock {
|
||||
val previous = settings.currentBedtime()
|
||||
val updated = transform(previous)
|
||||
settings.setBedtime(updated)
|
||||
apply(updated, refreshRule = previous.ruleFields() != updated.ruleFields(), force = true)
|
||||
apply(
|
||||
updated,
|
||||
refreshRule = previous.ruleFields() != updated.ruleFields(),
|
||||
force = previous.scheduleFields() != updated.scheduleFields(),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,9 +83,12 @@ class BedtimeEngine @Inject constructor(
|
||||
suspend fun onSlotFired() = lock.withLock {
|
||||
val current = settings.currentBedtime()
|
||||
val now = wallClock.now()
|
||||
apply(current, refreshRule = false, force = false, now = now)
|
||||
BedtimeSchedule.dueReminder(current, now, zones.current())?.let { window ->
|
||||
notifier.showReminder(window.start, current.reminderMinutes)
|
||||
val zone = zones.current()
|
||||
apply(current, refreshRule = false, force = false, now = now, zone = zone)
|
||||
BedtimeSchedule.dueReminder(current, now, zone)?.let { window ->
|
||||
// What is actually left: a late fire must not promise the full lead.
|
||||
val minutesLeft = ceil((window.start - now) / 1.minutes).toInt().coerceAtLeast(1)
|
||||
notifier.showReminder(window.start, minutesLeft)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,8 +108,8 @@ class BedtimeEngine @Inject constructor(
|
||||
refreshRule: Boolean,
|
||||
force: Boolean,
|
||||
now: Instant = wallClock.now(),
|
||||
zone: ZoneId = zones.current(),
|
||||
) {
|
||||
val zone = zones.current()
|
||||
if (!current.enabled) {
|
||||
scheduler.cancel()
|
||||
notifier.cancelReminder()
|
||||
@@ -113,10 +125,13 @@ class BedtimeEngine @Inject constructor(
|
||||
// or the schedule changed under it) is stale.
|
||||
if (BedtimeSchedule.dueReminder(current, now, zone) == null) notifier.cancelReminder()
|
||||
if (mode.hasAccess()) {
|
||||
mode.install(current, refresh = refreshRule)
|
||||
if (force || active != settings.bedtimeAppliedActive()) {
|
||||
mode.setActive(active)
|
||||
settings.setBedtimeAppliedActive(active)
|
||||
// A rule that was just (re)created starts off, whatever was last
|
||||
// applied to its predecessor, so it is switched regardless.
|
||||
val created = mode.install(current, refresh = refreshRule)
|
||||
if (force || created || active != settings.bedtimeAppliedActive()) {
|
||||
// Recorded only once the system took it, so a refused call is
|
||||
// retried by the next pass rather than forgotten.
|
||||
if (mode.setActive(active)) settings.setBedtimeAppliedActive(active)
|
||||
}
|
||||
}
|
||||
val next = BedtimeSchedule.nextEvent(current, now, zone)
|
||||
@@ -125,4 +140,7 @@ class BedtimeEngine @Inject constructor(
|
||||
|
||||
/** What the system rule is built from; the reminder is not part of it. */
|
||||
private fun BedtimeSettings.ruleFields() = listOf(enabled, bedtime, wakeTime, effects)
|
||||
|
||||
/** What decides whether the mode should be on right now. */
|
||||
private fun BedtimeSettings.scheduleFields() = listOf(enabled, bedtime, wakeTime, days)
|
||||
}
|
||||
Reference in new issue
Block a user