Merge branch 'feat/24-automatic-home-clock' into release/v1.0.0
# Conflicts: # CHANGELOG.md # 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/prefs/ClockPrefs.kt # app/src/main/java/de/jeanlucmakiola/clockula/data/prefs/SettingsPrefs.kt # app/src/main/java/de/jeanlucmakiola/clockula/domain/ClockDefaults.kt # app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/ClockSettingsScreen.kt # app/src/main/java/de/jeanlucmakiola/clockula/ui/worldclock/WorldClockRow.kt # app/src/main/java/de/jeanlucmakiola/clockula/ui/worldclock/WorldClockRowState.kt # app/src/main/java/de/jeanlucmakiola/clockula/ui/worldclock/WorldClockSource.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/ClockPrefsTest.kt # app/src/test/java/de/jeanlucmakiola/clockula/data/prefs/SettingsPrefsTest.kt # docs/ARCHITECTURE.md # docs/BACKUP.md
This commit is contained in:
commit
813bb52c03
24 files changed
+566
-57
No files matched your search
+31
-3
@@ -91,7 +91,7 @@ kit module: the kit's roadmap keeps schedulers app-local.
|
||||
| `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/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`, `AnalogFace` (hand and tick angles) + `DayNight` (the day fraction) |
|
||||
| `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 |
|
||||
| `domain/live/` | `LivePillSelector` and its state: which running thing the live pill is about, as a pure function |
|
||||
@@ -612,7 +612,7 @@ Six slices:
|
||||
| Slice | Keys | Owner |
|
||||
|---|---|---|
|
||||
| Appearance (M1) | `theme_mode`, `dynamic_color` | `AppearancePrefs` in `core-prefs`, the family's shared key names |
|
||||
| Clock defaults (M2) | `default_snooze_minutes`, `default_snooze_limit`, `default_vibrate`, `default_volume_ramp_seconds`, `default_alarm_ringtone_uri`, `default_timer_ringtone_uri`, `default_timer_vibrate`, `default_timer_volume_ramp_seconds` (#22), `default_dismiss_challenge`, `home_zone_id`, `volume_buttons_while_ringing` (#18; app-wide, no per-alarm override) | `ClockPrefs`, surfaced as `SettingsPrefs.defaults: Flow<ClockDefaults>` |
|
||||
| Clock defaults (M2) | `default_snooze_minutes`, `default_snooze_limit`, `default_vibrate`, `default_volume_ramp_seconds`, `default_alarm_ringtone_uri`, `default_timer_ringtone_uri`, `default_timer_vibrate`, `default_timer_volume_ramp_seconds` (#22), `default_dismiss_challenge`, `home_zone_id`, `volume_buttons_while_ringing` (#18; app-wide, no per-alarm override), `automatic_local_clock` (#24) | `ClockPrefs`, surfaced as `SettingsPrefs.defaults: Flow<ClockDefaults>` |
|
||||
| World clock display (#23) | `world_clock_show_seconds` | `ClockPrefs`, surfaced as its own `SettingsPrefs.worldClockSeconds`, not a clock default, so flipping it re-emits nothing alarms and timers watch |
|
||||
| Stopwatch run record (M2) | `stopwatch_state`, `stopwatch_started_elapsed_millis`, `stopwatch_accumulated_millis`, `stopwatch_last_lap_cumulative_millis` (M7 is the first caller of the last, for the in-progress lap's split) | `StopwatchPrefs`, behind `StopwatchStateStore` |
|
||||
| System facts (M3) | `last_boot_id` | `SystemPrefs`, behind `BootStateStore`, the persisted half of the reboot gate |
|
||||
@@ -1094,7 +1094,6 @@ M10 adds no `<uses-permission>`.
|
||||
| An analog stopwatch face or a `MaterialShapes` showpiece on the Stopwatch tab. `PLAN.md` §8 sanctions the showpiece for the analog world-clock face, the ring screen and timer progress; a stopwatch has no progress to be a fraction of | not planned for v1 |
|
||||
| Surfacing `StopwatchReading.anchorIsStale` in the UI, for the same reason the timers' is not: the repair makes it momentary, and the value displayed is already honest | not planned for v1 |
|
||||
| A rename UI for `world_clocks.label`. The column is honoured on **read** (a row shows the label when it has one), and M10's backup now carries it on export/import, but there is still no way to *write* a label in-app | not planned for v1 |
|
||||
| An automatic "home" row while travelling. Google Clock's automatic home clock is a separate feature with its own preference and its own edge cases; M8's answer to "where am I" is the hero face, which already follows the device when no home zone is stored | not planned for v1 |
|
||||
| A per-city detail screen, a map, sunrise/sunset times or a day-length bar. `DayNight` is a stated convention for a shape morph, not an ephemeris (§16) | not planned for v1 |
|
||||
| Live times or long zone names in the zone picker. 450 rows of ICU long names is real work for a list the user scrolls past, and a ticking picker is a ticking picker | not planned for v1 |
|
||||
| Reordering world clocks by anything but `sort_order`: no grouping by continent, no sort-by-offset, no pinning. The list is the order the user put it in | not planned for v1 |
|
||||
@@ -2639,6 +2638,35 @@ the user cannot reach is not handling; M10 may surface it a second time. **The
|
||||
home zone may also be added as a row**, which is not prevented: `add` is
|
||||
idempotent on `zone_id`, and that row simply reads "Same time as home".
|
||||
|
||||
### The automatic local clock (#24)
|
||||
|
||||
Google Clock's "automatic home clock" adds a home row while you travel,
|
||||
because its hero is *local* time. Clockula's hero is home, so on a trip home
|
||||
is already on screen and the clock that goes missing is the local one. The
|
||||
feature is therefore turned round: while a home zone is **stored** and the
|
||||
device's zone is on a different UTC offset **at this instant**, the tab shows
|
||||
the device's zone as a temporary row between the face and the saved cities,
|
||||
labelled "Local time" and read against home like any other row.
|
||||
|
||||
`LocalClock.zoneToShow` is the whole rule, pure and tested. The comparison is
|
||||
the offset now, as Google's is, not the zone id: Paris while home is Berlin
|
||||
shows nothing, and Phoenix while home is Denver shows a row only in summer.
|
||||
The row is hidden when a saved city is already on the device's offset, since
|
||||
that city already shows the local time. Home that follows the device can never
|
||||
differ from it, so with no stored home the rule is always null, and Settings ›
|
||||
Clock says so under the switch rather than hiding it. A device set by UTC
|
||||
offset rather than by city reports an id like `Etc/GMT-9`, whose last segment
|
||||
has its sign reversed, so that zone is named by its real offset ("GMT+9")
|
||||
instead, wherever a zone falls back to its id for a name (`ZoneCatalog.cityFallbackFor`).
|
||||
|
||||
The row is not stored. It is `WorldClockUiState.Loaded.local`, built per tick
|
||||
by the source from the same `zones.current()` read the face uses, so a
|
||||
`TIMEZONE_CHANGED` brings it in or takes it out within a tick with no receiver
|
||||
(D2). It sits outside the `ReorderableColumn`, carries no move or remove
|
||||
actions, has the reserved id `WorldClockRows.LOCAL_ROW_ID` (`-1`; Room ids
|
||||
start at 1), and does not count towards `WorldClocks.MAX`. The switch is
|
||||
`automatic_local_clock`, on by default and carried in the backup's settings.
|
||||
|
||||
### The face, and why its morph is information
|
||||
|
||||
`PLAN.md` §8 sanctions a `MaterialShapes` morph on the analog face and warns it
|
||||
|
||||
+3
-1
@@ -160,6 +160,7 @@ device or after a reboot (`docs/PLAN.md` §5). Every imported timer lands
|
||||
"default_timer_silence_after_minutes": 10,
|
||||
"volume_buttons_while_ringing": "CONTROL_VOLUME",
|
||||
"world_clock_show_seconds": false,
|
||||
"automatic_local_clock": true,
|
||||
"timer_presets": [60000, 120000, 180000, 300000, 600000, 900000, 1800000, 3600000],
|
||||
"bedtime": {
|
||||
"enabled": false,
|
||||
@@ -202,7 +203,8 @@ rings for that long anyway); a negative one reads as the default, 10.
|
||||
|
||||
Files from before `world_clock_show_seconds` existed import with it off, its
|
||||
default. It travels with the settings but is not a clock default: nothing an
|
||||
alarm or timer inherits depends on it.
|
||||
alarm or timer inherits depends on it. A file without `automatic_local_clock`
|
||||
(one written before it existed) restores it as `true`, its default.
|
||||
|
||||
Every numeric/duration value is clamped on *apply* using the same ranges and encoding
|
||||
as the existing preference setters, never on parse.
|
||||
|
||||
Reference in new issue
Block a user