From 702e0ea04984f203048d9a440bc6e1c8ba8b6d4e Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Thu, 6 Aug 2026 09:31:03 +0000 Subject: Widget: ship Material You theming, past-events restructuring, legibility fixes, new settings This lands the color-theming work that had sat uncommitted since a prior session (2026-07-28) -- every build published in between stripped it out deliberately to avoid shipping unreviewed work -- plus a full round of fixes and new features layered on top since it finally shipped: Theming (WidgetPalette.kt, new): - 5 themes now: NEUTRAL/ACCENT/TONAL (wallpaper-derived via Material You), VIVID (new -- all three text roles pull from a different accent slot instead of anchoring primary to neutral, for real hue variety), CLASSIC (fixed, wallpaper-independent). - Settings picker redesigned to match Android's native wallpaper "Basic colors" circular swatches (bottom half + two top quadrants, filled with each theme's actual buildWidgetPalette() output, not an approximation). - Per-source accent colors (colored checkboxes/bars) fully removed from the grid. Past events (DootWidget.kt, WidgetRows.kt): - Already-ended-today events pulled out of the hourly grid, shown as list rows above it instead (matching how past tasks already float) -- fixes the grid's start hour getting stretched backward by stale events. Legibility (WidgetRows.kt): - ShadowedText upgraded from a single-corner drop shadow to a 4-corner halo/outline (protects all sides of a glyph, not just one). - Halo color now tracks each palette's text luminance (dark halo for light text, light halo for dark text) -- a hardcoded black halo behind already-dark light-mode text was doing essentially nothing. Bumped opacity 0.45/0.55 -> 0.65/0.7 as the cheap, low-risk strength dial. New settings (SettingsActivity.kt, DataStore.kt): - Background transparency slider (0-85%, default 0% unchanged) -- exposed for testing per explicit request, not a default change. - Hide-checkboxes toggle: drops the leading checkbox/dot element entirely (not just hides the icon) so task titles land flush with event titles; tap-to-complete-from-widget trades off for TaskDetailActivity's Complete button. Also: today's moon phase in the TODAY header (moonPhaseEmoji, pure date computation, no network) -- verified against direct calculation before writing test assertions, not hand-computed. Removed the unused glance-material3 dependency (verified zero usages before removing; turned out to save ~2KB, not the ~280KB expected, since material3 itself already pulls the same transitive deps -- noted honestly rather than oversold). Every new pure-logic piece has unit tests (isPastEvent, moonPhaseEmoji, theme construction) -- 59 total, all green. Verified installable and crash-free via a real API 36 emulator launch before each publish, not assumed. Deployed as doot-widget.apk. --- .../org/terst/doot/widget/ui/DootWidgetGridTest.kt | 62 ++++++++++++++++++++++ 1 file changed, 62 insertions(+) (limited to 'android/app/src/test/java/org') diff --git a/android/app/src/test/java/org/terst/doot/widget/ui/DootWidgetGridTest.kt b/android/app/src/test/java/org/terst/doot/widget/ui/DootWidgetGridTest.kt index e3d76b1..550c533 100644 --- a/android/app/src/test/java/org/terst/doot/widget/ui/DootWidgetGridTest.kt +++ b/android/app/src/test/java/org/terst/doot/widget/ui/DootWidgetGridTest.kt @@ -1,10 +1,13 @@ package org.terst.doot.widget.ui import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.After import org.junit.Test import org.terst.doot.widget.data.WidgetItem +import java.time.Instant import java.util.TimeZone class DootWidgetGridTest { @@ -47,4 +50,63 @@ class DootWidgetGridTest { val gridEnd = calcGridEnd(emptyList(), nowHour = 22) assertEquals(23, gridEnd) } + + // isPastEvent: 2026-08-05 change pulling already-ended events out of the grid to + // stack above it as list rows, same treatment past tasks already got. + + private fun event(endIso: String?) = WidgetItem( + id = "e", title = "Event", source = "calendar", type = "event", + start = "2026-07-12T09:00:00Z", end = endIso + ) + + private fun task(startIso: String) = WidgetItem( + id = "t", title = "Task", source = "doot", type = "task", start = startIso + ) + + @Test + fun `isPastEvent is true once end has passed`() { + val now = Instant.parse("2026-07-12T10:00:00Z") + assertTrue(isPastEvent(event(endIso = "2026-07-12T09:30:00Z"), now)) + } + + @Test + fun `isPastEvent is false while still ongoing`() { + val now = Instant.parse("2026-07-12T10:00:00Z") + assertFalse(isPastEvent(event(endIso = "2026-07-12T10:30:00Z"), now)) + } + + @Test + fun `isPastEvent is false with no end time -- never considered past`() { + val now = Instant.parse("2026-07-12T23:00:00Z") + assertFalse(isPastEvent(event(endIso = null), now)) + } + + @Test + fun `isPastEvent is always false for tasks -- SlotPacker already handles those`() { + val now = Instant.parse("2026-07-12T10:00:00Z") + assertFalse(isPastEvent(task(startIso = "2026-07-12T08:00:00Z"), now)) + } + + // moonPhaseEmoji: values verified against direct computation before writing these + // assertions (days-since-anchor / synodic-month arithmetic), not hand-computed. + + @Test + fun `moonPhaseEmoji on the reference new moon date is new moon`() { + assertEquals("🌑", moonPhaseEmoji(java.time.LocalDate.of(2000, 1, 6))) + } + + @Test + fun `moonPhaseEmoji about half a synodic month later is full moon`() { + assertEquals("🌕", moonPhaseEmoji(java.time.LocalDate.of(2000, 1, 21))) + } + + @Test + fun `moonPhaseEmoji never indexes out of bounds across a full cycle`() { + val anchor = java.time.LocalDate.of(2000, 1, 6) + for (offset in 0..365) { + // Must not throw for any date in a full year -- the real assertion here is + // "doesn't crash," the emoji values themselves are checked in the two tests above. + moonPhaseEmoji(anchor.plusDays(offset.toLong())) + } + } } -- cgit v1.2.3