From 8c3cd0c96549936f36e4cb29ef053fd788e86f28 Mon Sep 17 00:00:00 2001 From: Ole-Morten Duesund Date: Thu, 11 Jun 2026 18:26:46 +0200 Subject: [PATCH] Don't nag for future doses: time-guard escalation resume MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 'Ta nå' + 'Angre' on a not-yet-due dose leaves a PENDING log with a future scheduledAt; armAll() (every app start/update) then resumed 'escalation' for it as if it were overdue — nagging hours early. Three layers: armAll only resumes escalation for past-due logs, the escalation receiver drops anything scheduled in the future (defense in depth), and undo resets nagCount + cancels any escalation so stale counts can't eat the real quota when the dose actually falls due. Emulator-verified with both cases side by side: past-due PENDING nags within the minute, future PENDING stays silent. Co-Authored-By: Claude Fable 5 --- .../java/no/naiv/meddetsamme/alarm/AlarmScheduler.kt | 10 ++++++++-- .../no/naiv/meddetsamme/alarm/DoseAlarmReceiver.kt | 3 +++ .../java/no/naiv/meddetsamme/domain/DoseActions.kt | 4 ++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/no/naiv/meddetsamme/alarm/AlarmScheduler.kt b/app/src/main/java/no/naiv/meddetsamme/alarm/AlarmScheduler.kt index 9d1982c..bc485bc 100644 --- a/app/src/main/java/no/naiv/meddetsamme/alarm/AlarmScheduler.kt +++ b/app/src/main/java/no/naiv/meddetsamme/alarm/AlarmScheduler.kt @@ -87,9 +87,15 @@ class AlarmScheduler(private val context: Context) { val db = MedDatabase.get(context) db.doseTimeDao().getAllForActiveMeds().forEach { armDoseTime(it.id) } armSupplyCheck() - // Resume nagging for doses that were unresolved when the device died. + // Resume nagging for doses that were unresolved when the device died — + // but only PAST-DUE ones. An unresolved log can also be a future dose + // (e.g. "Ta nå" + "Angre" before its time); its own occurrence alarm + // handles it when it actually falls due. + val now = System.currentTimeMillis() db.doseLogDao().getUnresolved().forEach { log -> - if (log.nagCount < NAG_CAP) armEscalation(log.id, delayMinutes = 1) + if (log.nagCount < NAG_CAP && log.scheduledAtMillis <= now) { + armEscalation(log.id, delayMinutes = 1) + } } } diff --git a/app/src/main/java/no/naiv/meddetsamme/alarm/DoseAlarmReceiver.kt b/app/src/main/java/no/naiv/meddetsamme/alarm/DoseAlarmReceiver.kt index 74d1428..9c0e79a 100644 --- a/app/src/main/java/no/naiv/meddetsamme/alarm/DoseAlarmReceiver.kt +++ b/app/src/main/java/no/naiv/meddetsamme/alarm/DoseAlarmReceiver.kt @@ -94,6 +94,9 @@ class DoseAlarmReceiver : BroadcastReceiver() { // Only unresolved doses nag. TAKEN/SKIPPED here means the cancel raced us. if (log.status != DoseStatus.PENDING && log.status != DoseStatus.SNOOZED) return if (log.nagCount >= AlarmScheduler.NAG_CAP) return + // A future dose can't be overdue — drop stray escalations (defense in + // depth against any path that arms one too early). + if (log.scheduledAtMillis > System.currentTimeMillis()) return val med = db.medicationDao().getById(log.medId) ?: return val nagged = log.copy(nagCount = log.nagCount + 1) diff --git a/app/src/main/java/no/naiv/meddetsamme/domain/DoseActions.kt b/app/src/main/java/no/naiv/meddetsamme/domain/DoseActions.kt index 58f79b8..7afdc3d 100644 --- a/app/src/main/java/no/naiv/meddetsamme/domain/DoseActions.kt +++ b/app/src/main/java/no/naiv/meddetsamme/domain/DoseActions.kt @@ -67,5 +67,9 @@ class DoseActions(private val context: Context) { } } db.doseLogDao().setStatus(logId, DoseStatus.PENDING, null) + // Fresh start: stale nag counts would otherwise eat into the real + // escalation quota when the dose actually falls due. + db.doseLogDao().setNagCount(logId, 0) + scheduler.cancelEscalation(logId) } }