Merge branch 'feat/25-screen-saver' into release/v1.0.0
# Conflicts: # app/src/main/AndroidManifest.xml # app/src/main/java/de/jeanlucmakiola/clockula/backup/BackupEngine.kt # app/src/main/java/de/jeanlucmakiola/clockula/data/backup/BackupCodec.kt # app/src/main/java/de/jeanlucmakiola/clockula/data/backup/BackupDto.kt # app/src/main/java/de/jeanlucmakiola/clockula/data/di/TimeModule.kt # app/src/main/java/de/jeanlucmakiola/clockula/data/prefs/SettingsPrefs.kt # app/src/main/java/de/jeanlucmakiola/clockula/data/time/AndroidClockChanges.kt # app/src/main/java/de/jeanlucmakiola/clockula/domain/backup/BackupContent.kt # app/src/main/java/de/jeanlucmakiola/clockula/domain/widget/NextAlarmWidgetModel.kt # app/src/main/java/de/jeanlucmakiola/clockula/ui/alarms/AlarmsScreen.kt # app/src/main/java/de/jeanlucmakiola/clockula/ui/common/AlarmTimeFormatter.kt # app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/ClockSettingsScreen.kt # app/src/main/java/de/jeanlucmakiola/clockula/ui/worldclock/WorldClockFace.kt # app/src/main/res/values/strings.xml # app/src/test/java/de/jeanlucmakiola/clockula/data/backup/BackupCodecTest.kt # app/src/test/java/de/jeanlucmakiola/clockula/data/prefs/SettingsPrefsTest.kt # docs/ARCHITECTURE.md # docs/BACKUP.md
This commit is contained in:
commit
a0a1daa920
46 files changed
+1662
-57
No files matched your search
+16
-4
@@ -90,7 +90,7 @@ kit module: the kit's roadmap keeps schedulers app-local.
|
||||
| `domain/ring/` | what both ring paths share: `RingAudio` (the ramp tick, the volume floor), `VolumeRamp`, `AudioSourcePolicy`/`RingtoneSourceKind`, `RingFallbackPolicy`, `VibrationPattern` |
|
||||
| `domain/stopwatch/` | the stopwatch path's pure half: `StopwatchReadings` (the one reading the pill, the shade and the tab share), `LapStats` (best and worst), `StopwatchLaps` (the lap cap), `StopwatchNotificationPolicy` |
|
||||
| `domain/timer/` | the timer path's pure half: `TimerReadings` (the app's one timer precedence), `TimerExpiry` (the sweep and the slot's value), `TimerRingPolicy`, `TimerNotificationPolicy`, `TimerPresets`, `TimerDurationEntry`, `TimerSettings`, `TimerRing` |
|
||||
| `domain/time/` | `WallClock`, `ElapsedRealtimeClock`, `ZoneProvider` (`current()` and, since M8, `available()`, the device's tzdata), `BootId`, `Ticker` |
|
||||
| `domain/time/` | `WallClock`, `ElapsedRealtimeClock`, `ZoneProvider` (`current()` and, since M8, `available()`, the device's tzdata), `BootId`, `Ticker`, and for the screen saver `ClockChanges` (a time, zone or 12/24-hour change while collected), `HourFormat` and `MinuteBoundary` |
|
||||
| `domain/worldclock/` | the world clock's pure half (M8): `ZoneCatalog` (`ZoneEntry`, the pickable-id filter, the city fallback, the sort), `ZoneSearch` (folding and word-prefix matching), `ZoneComparison` (the offset and day arithmetic, `OffsetLabel`/`DayLabel`, `ZoneOffsetFormat`), `WorldClocks` (`MAX`, the move arithmetic) + `HomeZone` + `LocalClock` (when the device's zone gets a temporary row), `AnalogFace` (hand and tick angles) + `DayNight` (the day fraction) |
|
||||
| `domain/interop/` | the `android.provider.AlarmClock` boundary's pure half (M9): `AlarmClockContract` (the platform's vocabulary as literals; the only file in `src/main` that may hold them), `IntentExtras` (a Bundle modelled honestly), `InteropLimits`, `InteropText` (the label and ringtone sanitisers), `InteropDeepLinks`, `AlarmClockRequest`/`AlarmSpec`/`AlarmSearch`/`InteropOutcome`, `AlarmClockRequests` (the validator) and `AlarmMatching` (which alarms a search names) |
|
||||
| `domain/text/` | `TextFolding` (M9): NFD-fold, strip combining marks, lowercase through `Locale.ROOT`, `containsFolded`. One copy, shared by the zone search and the label search |
|
||||
@@ -99,6 +99,7 @@ kit module: the kit's roadmap keeps schedulers app-local.
|
||||
| `domain/backup/` | `BackupContent` (M10): the plain Kotlin model between `data/backup`'s JSON codec and the repository/engine; decoupled from the wire DTOs the same way the data layer is decoupled from Room |
|
||||
| `domain/selfcheck/` | `SelfCheck` (M10): the "why might my alarm not ring?" rule table: pure functions over a `DeviceState` snapshot, each producing a `CheckResult` with a status and an optional deep-link `SelfCheckFix` |
|
||||
| `domain/bedtime/` | the bedtime schedule's pure half (issue #1): `BedtimeSettings`/`BedtimeEffects`/`BedtimeLimits` and `BedtimeSchedule` (the window a night resolves to, the active window, the due reminder, the next event). §18 |
|
||||
| `domain/screensaver/` | the screen saver's pure half (issue #25): `ScreenSaverSettings` (style and night mode), `ScreenSaverDrift`, the once-a-minute burn-in position as fractions of the free space, derived from the minute alone, and `ScreenSaverAlarmDay`, which dates a next alarm a week or more away rather than naming its weekday alone |
|
||||
| `domain/widget/` | the three widget presenters (M10), `NextAlarmWidgetModel`/`TimerWidgetModel`/`ClockWidgetModel`: selection and formatting only, so a widget's content is unit-tested without Glance |
|
||||
| `data/db/` | `ClockulaDatabase` (`@Database` v4, `exportSchema = true`) and `Migrations` |
|
||||
| `data/alarms/` | the alarm and ring-state entities, DAOs, mappers and repositories |
|
||||
@@ -106,9 +107,9 @@ kit module: the kit's roadmap keeps schedulers app-local.
|
||||
| `data/worldclocks/` | `WorldClockEntity`, `WorldClockDao`, `WorldClockMapper`, `WorldClockRepository(+Impl)`, unchanged since M2; M8 is the milestone that finally calls all of it |
|
||||
| `data/zones/` | the ICU seam (M8): `ZoneNames`, `IcuZoneNames` (the only `android.icu` file in the app) and `ZoneDirectory`, the locale-keyed catalog cache and the DST-keyed display-name memo |
|
||||
| `data/stopwatch/` | `LapEntity`, `LapDao` (one `COUNT(*)` added in M7), `LapMapper`, `StopwatchStateStore`, `StopwatchRepository(+Impl)` |
|
||||
| `data/prefs/` | `SettingsPrefs`, `ClockPrefs`, `StopwatchPrefs`, `TimerPrefs`, `UiPrefs`, `BedtimePrefs` (issue #1) |
|
||||
| `data/prefs/` | `SettingsPrefs`, `ClockPrefs`, `StopwatchPrefs`, `TimerPrefs`, `UiPrefs`, `BedtimePrefs` (issue #1), `ScreenSaverPrefs` (issue #25) |
|
||||
| `data/ringtones/` | the two ringtone seams, `RingtoneCatalog` (the bundled and device alarm sounds, titles, playability, a SAF grant) and `RingtonePreviewer`, plus their `System*` implementations, and `MediaLibrary`/`SystemMediaLibrary`, the picker's view of other apps' `MediaBrowserService` libraries |
|
||||
| `data/time/` | `SystemWallClock`, `SystemElapsedRealtimeClock`, `SystemZoneProvider`, `AndroidBootIdProvider`, `RealTicker` |
|
||||
| `data/time/` | `SystemWallClock`, `SystemElapsedRealtimeClock`, `SystemZoneProvider`, `AndroidBootIdProvider`, `RealTicker`, `AndroidClockChanges` (a receiver for `TIME_SET`/`TIMEZONE_CHANGED` registered only while collected), `AndroidHourFormat` |
|
||||
| `data/di/` | `DataModule`, `DatabaseModule`, `RepositoryModule`, `TimeModule`, `RingtoneModule`, `ZoneModule` (M8; one `@Binds`, `ZoneNames → IcuZoneNames`), `BackupModule` (M10) |
|
||||
| `data/backup/` | the backup's data-layer half (M10): `BackupDto`/`BackupCodec` (the pure JSON wire format, `ignoreUnknownKeys`, strict on known fields), `BackupDao` (one `@Transaction` replace-all over alarms/timers/world clocks; no schema or version change), `BackupRepository(+Impl)`, mapping through the existing `AlarmMapper`/`TimerMapper`/`WorldClockMapper`, never a second copy |
|
||||
| `alarm/` | `AlarmEngine` and the four seams it talks to (`AlarmScheduler`, `AlarmCapabilities`, `RingCoordinator`, `AlarmNotifier`), plus `AlarmIntents` |
|
||||
@@ -140,8 +141,9 @@ kit module: the kit's roadmap keeps schedulers app-local.
|
||||
| `ui/stopwatch/` | the real Stopwatch tab (M7): the pure row builder (`StopwatchRows`) and its source, the ViewModel, the readout panel with its controls, the lap table and its column header, `StopwatchDefaults` |
|
||||
| `ui/worldclock/` | the real World clock tab (M8): the pure row builders (`WorldClockRows`, `ZonePickerRows`) and their state, `WorldClockSource`, the ViewModel, the tab, the analog face, the row and the zone picker, `WorldClockDefaults` |
|
||||
| `ui/ring/` | `AlarmRingActivity`, its ViewModel, `RingScreen` and the dismiss-challenge controls |
|
||||
| `ui/settings/` | the settings hub (M10): `SettingsScreen`/`SettingsViewModel` (appearance, alarm/timer/clock defaults, reliability, data, help), `SelfCheckScreen`/`SelfCheckViewModel`, `BackupScreen`/`BackupViewModel`, all reachable from a gear icon on every tab, with no owning tab of their own |
|
||||
| `ui/settings/` | the settings hub (M10): `SettingsScreen`/`SettingsViewModel` (appearance, alarm/timer/clock defaults, reliability, data, help), `SelfCheckScreen`/`SelfCheckViewModel`, `BackupScreen`/`BackupViewModel`, all reachable from every tab's overflow menu (`ui/shell/ShellOverflowMenu`, which also opens the screen saver), with no owning tab of their own |
|
||||
| `ui/bedtime/` | `BedtimeScreen`/`BedtimeViewModel` (issue #1), a nested destination reached from the settings hub, the bedtime reminder and the mode's page in system Settings, never a tab |
|
||||
| `ui/screensaver/` | the screen saver (issue #25), here rather than beside the widgets because both of its hosts host Compose: `ClockulaDreamService` (the system's dream; `DreamViewOwner` puts the lifecycle, view-model-store and saved-state owners a `ComposeView` needs on the dream window's decor view), `ScreenSaverActivity` (the same content full-screen, from the tabs' overflow menu, since an app cannot start a dream without a system permission), `ScreenSaverSource` (prefs + `AlarmEngine.upcoming()` + the clocks, waking once a minute on the minute and on a `ClockChanges`), the content (numerals or the world clock's `WorldClockFace` without its second hand, the date and the next alarm through `NextAlarmWidgetPresenter`), and the settings rows shared by Settings → Clock and `ScreenSaverSettingsActivity`, the system's gear. `ui/theme/ScreenSaverTheme.kt` is the dark scheme over black, dimmed towards black in night mode |
|
||||
| `ui/widget/` | the three widgets' `@Composable` Glance content (M10), the only Compose this milestone adds outside a tab; the `GlanceAppWidget`/`GlanceAppWidgetReceiver` classes themselves live in `widget/`, since they are manifest components, not UI. `ui/widget/config/` holds each widget's config activity; every widget has one, if only for its per-instance colours (`domain/widget/WidgetColors.kt`: System/Light/Dark and, on API 31+, wallpaper colours on/off). Glance content takes those through `WidgetTheme`; the plain-`RemoteViews` faces, readouts and chronometers through `WidgetPalette.kt`, which resolves the `widget_*` palette under a forced day/night (the app's own tones are `widget_app_*`) and sets concrete colours, or day/night pairs, on the views. The analog and digital clocks are re-coloured on API 31+ only, since below that `AnalogClock`'s hands and a shape's background tint can't be set through `RemoteViews` |
|
||||
|
||||
---
|
||||
@@ -618,6 +620,7 @@ Six slices:
|
||||
| System facts (M3) | `last_boot_id` | `SystemPrefs`, behind `BootStateStore`, the persisted half of the reboot gate |
|
||||
| Timers (M6) | `timer_presets`, `timer_ring_sounding_since_millis` | `TimerPrefs`: the presets surfaced as `SettingsPrefs.timerPresets`, the ring session's anchor behind `TimerRingStateStore` |
|
||||
| Bedtime (issue #1) | `bedtime_enabled`, `bedtime_start_minutes`, `bedtime_wake_minutes`, `bedtime_days`, `bedtime_reminder_minutes`, `bedtime_effect_grayscale`, `bedtime_effect_dim_wallpaper`, `bedtime_effect_dark_theme`, `bedtime_effect_hide_aod`, `bedtime_applied_active`, `bedtime_rule_stale`, `bedtime_state_owed` | `BedtimePrefs`, surfaced as `SettingsPrefs.bedtime: Flow<BedtimeSettings>` and written as one record by `BedtimeEngine` only. Times clamp into a day, the reminder into 0–120 minutes, the days through `RepeatDays.fromMask`. The system rule's id is **not** stored (§18) |
|
||||
| Screen saver (issue #25) | `screen_saver_style`, `screen_saver_night_mode` | `ScreenSaverPrefs`, surfaced as `SettingsPrefs.screenSaver: Flow<ScreenSaverSettings>` and carried in the backup |
|
||||
|
||||
The stopwatch's *run* is a single record, so it lives in DataStore rather than
|
||||
as a one-row table (`PLAN.md` §5); its *laps* are a list, so they live in Room.
|
||||
@@ -1064,6 +1067,15 @@ three widget receivers, each with **only** the platform-required
|
||||
platform-delivered broadcast no other app can trigger by naming the action.
|
||||
M10 adds no `<uses-permission>`.
|
||||
|
||||
The screen saver (issue #25) adds `ui.screensaver.ClockulaDreamService`,
|
||||
exported with only the `android.service.dreams.DreamService` filter and
|
||||
guarded by `BIND_DREAM_SERVICE`, which only the system holds; its
|
||||
`android.service.dream` meta-data (`res/xml/screen_saver_info.xml`) names
|
||||
`ScreenSaverSettingsActivity`, exported with no intent filter so the Settings
|
||||
app can open it by name; and the unexported `ScreenSaverActivity`. No
|
||||
`<uses-permission>` either: keeping the screen on is a window flag, not a
|
||||
wake lock.
|
||||
|
||||
---
|
||||
|
||||
## 10. What is not built yet
|
||||
|
||||
+7
-2
@@ -172,7 +172,9 @@ device or after a reboot (`docs/PLAN.md` §5). Every imported timer lands
|
||||
"dim_wallpaper": true,
|
||||
"dark_theme": true,
|
||||
"hide_always_on_display": false
|
||||
}
|
||||
},
|
||||
"screen_saver_style": "DIGITAL",
|
||||
"screen_saver_night_mode": false
|
||||
}
|
||||
```
|
||||
|
||||
@@ -186,7 +188,10 @@ is clamped rather than rejected: times into a day, the reminder into 0–120
|
||||
minutes, and a `days` of 0 (which the screen never allows) into every night. The id of the Do Not Disturb rule a device created is not part of it;
|
||||
the importing device creates its own (`docs/ARCHITECTURE.md` §18).
|
||||
|
||||
Field names mirror `ClockPrefs`'/`SettingsPrefs`' own on-disk keys. Older
|
||||
Field names mirror `ClockPrefs`'/`SettingsPrefs`'/`ScreenSaverPrefs`' own
|
||||
on-disk keys. A file from before the screen saver has neither
|
||||
`screen_saver_*` field and restores its defaults (digital, night mode off);
|
||||
an unknown style degrades to `DIGITAL` rather than rejecting the file. Older
|
||||
files may also carry `default_timer_duration_millis`, from a default timer
|
||||
length setting that has since been removed; it is ignored on import like any
|
||||
other unknown field. `volume_buttons_while_ringing` is one of
|
||||
|
||||
+5
-2
@@ -611,8 +611,11 @@ reliability soak across a few real nights. Then 1.0.0.
|
||||
|
||||
Deferred deliberately, in rough priority order:
|
||||
|
||||
- **Dock / screensaver mode**: a `DreamService` full-screen clock. Cheapest big
|
||||
win, and the surface people look at most.
|
||||
- ~~**Dock / screensaver mode**: a `DreamService` full-screen clock. Cheapest big
|
||||
win, and the surface people look at most.~~ Done (issue #25):
|
||||
`ClockulaDreamService` plus `ScreenSaverActivity` for the overflow menu's
|
||||
"Screen saver", digital or analog, with a dim night mode and a once-a-minute
|
||||
drift against burn-in (`ARCHITECTURE.md` §3, §6).
|
||||
- **Quick Settings tile**: timer control and next alarm from the shade.
|
||||
- 🚧 **Bedtime / sleep schedule** (issue #1): a first version exists as an
|
||||
opt-in screen under Settings, not a fifth tab. Clockula's own Do Not Disturb
|
||||
|
||||
Reference in new issue
Block a user