diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 29c99c3..6181354 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -51,11 +51,8 @@ # obfuscated name and orphan the stored mapping. -keep class * extends androidx.glance.appwidget.GlanceAppWidget -# Belt and braces one level up: MonthWidgetReceiver and AgendaWidgetReceiver are -# nearly as alike (same supertype, same overrides, only a differing property -# initializer), and Glance's provider map is keyed off the receiver component -# too. AGP's manifest-derived keep rules already cover them, and the rule above -# keeps the two widgets distinct enough that the receivers' constructors differ — -# so this is redundant today. It is here because #89 cost a release to diagnose -# and the guarantee should not rest on a component staying in the manifest. +# Belt and braces one level up: the two receivers are nearly as alike, and the +# provider map is keyed off the receiver component too. Redundant today (AGP's +# manifest-derived rules cover them), but #89 cost a release to diagnose and the +# guarantee should not rest on a component staying in the manifest. -keep class * extends androidx.glance.appwidget.GlanceAppWidgetReceiver diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a5a1c4a..a7e97b6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -330,13 +330,12 @@ val cur = prefs[MONTH_INDEX_KEY] ?: currentMonthIndex(systemZone()) val next = cur + delta - // Landing back on the current month clears the key rather than - // storing today's index, so the widget goes back to *following* the - // date instead of being pinned to the month that happened to be - // current when it was tapped. Paging forward and back is the very - // workaround #228's reporter used to force a redraw; storing the - // index there would have left them stuck on that month for good once - // it stopped being the current one. + // Landing back on the current month clears the key, so the widget + // goes back to *following* the date rather than being pinned to + // whichever month was current at the tap. Paging out and back is the + // workaround #228's reporter used, and pinning it there would have + // stuck them on that month once it stopped being the current one. if (next == currentMonthIndex(systemZone())) { prefs.remove(MONTH_INDEX_KEY) } else { diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/widget/month/MonthWidgetReceiver.kt b/app/src/main/java/de/jeanlucmakiola/calendula/widget/month/MonthWidgetReceiver.kt index 7a56afa..08e4a3e 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/widget/month/MonthWidgetReceiver.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/widget/month/MonthWidgetReceiver.kt @@ -20,9 +20,8 @@ class MonthWidgetReceiver : GlanceAppWidgetReceiver() { } /** - * Last month widget removed. [WidgetRolloverScheduler.sync] only cancels the - * alarm if no agenda widget is left either, so removing one kind never stops - * the other from rolling over. + * Last month widget removed. [WidgetRolloverScheduler.sync] cancels only if + * no agenda widget is left either. */ override fun onDisabled(context: Context) { super.onDisabled(context) @@ -31,11 +30,10 @@ class MonthWidgetReceiver : GlanceAppWidgetReceiver() { /** * The `updatePeriodMillis` backstop is the one wake-up the *system* still - * owns, so it doubles as the rollover alarm's self-heal: anything that drops - * a pending alarm without a broadcast — a force-stop, a battery-restricted - * transition, an OEM freeze — is repaired here rather than waiting for the - * app to be opened. Re-arming closer to midnight also narrows the inexact - * alarm's delivery window, which scales with how far out it was set. + * owns, so it doubles as the alarm's self-heal: anything that drops a + * pending alarm without a broadcast (force-stop, battery restriction, an OEM + * freeze) is repaired here rather than on the next app open. Re-arming + * closer to midnight also narrows the inexact delivery window. */ override fun onUpdate( context: Context, diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/widget/WidgetRolloverSchedulerTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/widget/WidgetRolloverSchedulerTest.kt index 19370f1..da9edee 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/widget/WidgetRolloverSchedulerTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/widget/WidgetRolloverSchedulerTest.kt @@ -44,8 +44,8 @@ class WidgetRolloverSchedulerTest { @Test fun `at midnight exactly the target is the next day, never the current instant`() { - // The alarm has just fired and is re-arming: it must move a whole day on, - // otherwise the widget would wake itself in a tight loop. + // Re-arming after a firing must move a whole day on, or the widget wakes + // itself in a tight loop. val now = at("2026-08-28T00:00:00", berlin) val next = WidgetRolloverScheduler.nextRolloverAt(now, berlin) assertThat(next).isEqualTo(at("2026-08-29T00:00:05", berlin)) @@ -63,8 +63,8 @@ class WidgetRolloverSchedulerTest { @Test fun `the result is always in the future for every minute of a day`() { val zone = berlin - // Spans Berlin's 2024 spring-forward, the case most likely to produce a - // target in the past and so an alarm that fires immediately, forever. + // Spans Berlin's 2024 spring-forward: the likeliest source of a target + // in the past, i.e. an alarm that fires immediately, forever. var probe = LocalDateTime.parse("2024-03-29T00:00:00").toInstant(zone) val end = LocalDateTime.parse("2024-04-01T00:00:00").toInstant(zone) while (probe < end) { @@ -87,9 +87,8 @@ class WidgetRolloverSchedulerTest { @Test fun `fall back does not overshoot into the repeated hour`() { - // Berlin repeated 02:00-03:00 on 27 October 2024: midnight itself is - // unambiguous, but the day is 25h long, so "now + 24h" would land at - // 23:00 on the 26th and never roll the date over at all. + // Berlin repeated 02:00-03:00 on 27 October 2024: a 25h day, so + // "now + 24h" would land at 23:00 on the 26th and never roll over. val now = at("2024-10-26T12:00:00", berlin) val next = WidgetRolloverScheduler.nextRolloverAt(now, berlin) assertThat(next).isEqualTo(at("2024-10-27T00:00:05", berlin)) @@ -99,9 +98,8 @@ class WidgetRolloverSchedulerTest { @Test fun `a zone that repeats midnight takes the first start of day`() { // Sao Paulo used to end DST by moving 00:00 back to 23:00, so the day - // began twice. Pinned deliberately: the widget then runs an hour ahead - // of the clock until the next redraw, which is the accepted trade - // (Brazil dropped DST in 2019, so no live zone does this). + // began twice. Pinned as the accepted trade: the widget runs an hour + // ahead until the next redraw. No live zone does this since 2019. val saoPaulo = TimeZone.of("America/Sao_Paulo") val next = WidgetRolloverScheduler.nextRolloverAt( at("2018-02-16T12:00:00", saoPaulo), saoPaulo, @@ -132,8 +130,8 @@ class WidgetRolloverSchedulerTest { @Test fun `the same instant rolls over at different times in different zones`() { - // Flying east and getting TIMEZONE_CHANGED must re-arm to the new local - // midnight — the arithmetic follows the zone, not a cached offset. + // TIMEZONE_CHANGED must re-arm to the new local midnight: the arithmetic + // follows the zone, not a cached offset. val instant = at("2026-08-27T12:00:00", berlin) val tokyo = TimeZone.of("Asia/Tokyo") val berlinNext = WidgetRolloverScheduler.nextRolloverAt(instant, berlin)