`ARCHITECTURE.md` gains §17: the door, the split between the Android-free validator and the reader that guards the unparcel, the range rule (clamp what is happening now, drop what would define a future ring), the candidate table for "which alarm did you mean", and the three deliberate narrowings — including why a link we cannot read dismisses nothing rather than everything. §4 records schema v3 and its migration; §13 records the reading of `PLAN.md` §6 this milestone builds, since a malformed intent lands the user in an editor over a row created from the valid extras, there being no "new alarm" sentinel.
13 KiB
13 KiB
Changelog
All notable changes to Clockula are documented here.
The format is based on Keep a Changelog,
and this project adheres to Semantic Versioning.
Tag sections feed the release notes — see docs/RELEASING.md.
[Unreleased]
Added
- Project skeleton: Gradle build, version catalog, Hilt, Compose and the Material 3
Expressive theme over floret-kit's
identitymodule, seeded#6B7A5C— the third 120° hue rotation of Calendula's slate, completing the family's palette. - floret-kit as a git submodule wired in as a Gradle composite build.
- Crash capture and the report surface via floret-kit's
core-crash; per-app language support viacore-locale. - Launcher and notification icons: a line-art clock face carrying the family's Calendula bloom badge.
- Full release pipeline, Codeberg-canonical from the first commit — CI, translations, release (F-Droid repo + Codeberg release + Google Play) and Renovate, with the F-Droid reproducibility rules intact.
- The locked specification:
docs/PLAN.mdanddocs/ROADMAP.md. - Preferences over floret-kit's new
core-prefs:SettingsPrefsholds the appearance slice (theme mode, dynamic colour) in a DataStore of Clockula's own, under the family's key names. The theme now follows the stored preference — light/dark override and dynamic colour persist across launches instead of always following the system — and an unrelated preference write no longer re-emits it. - floret-kit's
core-disupplies the@IoDispatcherthe preference store runs on, so Clockula no longer declares its own dispatcher qualifier. - Clockula's own storage, headless: a Room database with
alarms,timers,world_clocksandstopwatch_laps, its schema exported and committed at every version so each migration is reviewable and testable. - Plain-Kotlin alarms, timers, world clocks and stopwatch runs behind four repository interfaces exposing Flows — nothing above the data layer knows Room exists, and a test fails the build if anyone reaches through. A corrupt row degrades on read (a clamped time, an unknown ringtone, a rotted zone id) rather than crashing the list it appears in.
- Alarm and timer settings are inherited, not copied: a per-alarm ringtone, snooze, vibrate, volume ramp or dismiss challenge is an override, so changing an app default later reaches every alarm the user never customised.
- The clock defaults and the stopwatch's running state now live in DataStore alongside the appearance preferences, clamped on read as well as on write, so a hand-edited or restored file cannot produce a setting the user could never have chosen.
- The wall-clock / elapsed-realtime split, made explicit: alarm records follow the system clock, while running timers and the stopwatch are anchored to time since boot. Changing the device's time — forwards or backwards — cannot warp either, and a timer that survives a reboot falls back to its wall-clock estimate and says so instead of vanishing.
- Alarms that ring. A repeat schedule that is re-resolved against the device's zone every time anything moves, so the clock going forwards, backwards or through a daylight-saving transition cannot lose one: a 02:30 alarm on a spring-forward night rings at 03:30 rather than vanishing, and on a fall-back night rings once rather than twice.
- Skip-next-occurrence that skips exactly one occurrence, snooze with a per-alarm interval and limit, and a snooze that is still kept after a reboot.
- An alarm that keeps ringing through a reboot or a process kill — the ring is rebuilt from storage, not from memory — and one that still rings when the full-screen-intent or notification permission is denied. The chain of fallbacks ends in vibration, never in silence.
- The post-reboot repair: a running timer no longer counts down from an anchor the reboot killed, and the stopwatch no longer invents a segment it never ran.
- The app shell: four tabs — alarms, timers, stopwatch, world clock — under a navigation bar that becomes a rail on a tablet, chosen by the window's size rather than by hand. Back from a tab goes home to Alarms; back from Alarms leaves the app, and the predictive-back gesture previews whichever of the two is actually about to happen.
- The live pill: whatever is counting is visible from every tab, and pausable and stoppable without going to find the row that owns it. It stays up while paused — a control that vanishes under the thumb that pressed it is no control — shows how many other timers are running behind it, and flips a timer that reaches zero to "finished" the moment it does, rather than counting into negative time. Stop resets; it never deletes a timer the user configured.
- The real ring screen, over the lock screen with the display kept on for exactly as long as the window lives: the time, the label, snooze, and an optional dismiss challenge — a two-term sum, or a two-second hold — that can never strand anyone. Three wrong answers or sixty seconds of ringing, either one, opens a way out; snooze is never gated; solving the challenge dismisses on the spot instead of asking twice; and the hold completes from an accessibility click, so a screen reader is not locked out of its own alarm. Back cannot silence an alarm.
- The first Compose motion: fade-through between peer tabs, the kit's springs for the pill coming and going and for the challenge block opening, all of which already stand down when the system's "remove animations" setting is on.
- Notification permission is asked for once, on first launch, and the fact that it was asked is recorded rather than guessed at.
- The alarms tab, for real: a list of your alarms that says when each one will ring — "in 9h 12m" — and marks the next one to go off, with the repeat days and a switch on every row. A switched-off alarm stays where it is, faded rather than shuffled to the bottom, and the countdown keeps up on its own without anything being saved.
- Create, edit, delete, enable and disable an alarm, applied as you make them rather than behind a Save button: tapping + creates an alarm at the next whole hour and opens it, every control writes at once, and back is just back.
- The Material time picker, with a keypad to type a time and the dial behind one toggle, following the device's own 12- or 24-hour setting.
- A repeat-day selector that starts on your locale's first day of the week, not on a hardcoded Monday, and a "skip next alarm" switch for a repeating one — so you can sleep in on Thursday without switching the alarm off and forgetting to switch it back.
- A per-alarm sound, vibration, gradual volume, snooze length, snooze limit and dismiss challenge — each of which can say "follow the app default" rather than silently copying today's default and drifting away from it later. Every row shows what it currently resolves to.
- A ringtone picker with the device's own alarm sounds, a file you choose yourself, and a Silent option that still vibrates — because an alarm that does literally nothing is not an alarm. You can play a sound before taking it.
- A sound Clockula cannot read right now says so under the row, and your choice is kept rather than quietly replaced with the default: an unmounted card comes back. If a file you picked cannot be held onto permanently, that is disclosed too instead of being hidden.
- The timers tab, for real: several timers at once, each with its own label, length and sound, and a keypad that sets one in a couple of taps — with saved presets you can add to and remove from as you go. A timer starts the moment you press Start, so an accidental tap leaves nothing behind to delete.
- Pause, resume, reset and "+1 min" on every timer, from the row, from the live pill and from the notification — and "+1 min" on a timer that has just rung starts it again with a whole minute, rather than handing back a paused timer you have to press Start on.
- A notification that counts the timer down without waking the device every second: the system draws the ticking, so Clockula posts once when something actually changes. It carries the same two buttons the row does, and they work with the app closed.
- A timer keeps counting across a reboot and rings when you come back — the countdown is anchored on the device's uptime, so changing the system clock cannot move it in either direction. Change the clock while a timer runs and the notification's countdown follows the change instead of going wrong.
- Two timers finishing together share one sound and one ten-minute window, and each is stopped on its own, so you can still tell which one you have dealt with. A timer left ringing goes quiet after ten minutes but stays finished, so you can come back and add a minute to it — and the next timer to finish rings for itself rather than inheriting that silence.
- A timer that finishes while an alarm is ringing waits its turn instead of talking over it: it is still marked finished, its notification says so, and it starts sounding the moment the alarm is done.
- Progress on each timer card is drawn by the Expressive wavy indicator, and the wave itself is the state — it flattens when you pause and when the timer is done.
- The stopwatch tab, for real: start, pause and reset, with a readout down to hundredths of a second that stays legible because the fraction is drawn smaller than the seconds rather than shouting alongside them.
- Laps, each with its own split and its running total, newest at the top, and the fastest and slowest of them marked — in colour and in words, so a screen reader hears the distinction too. The lap you are in the middle of has its own row, counting up.
- An ongoing notification with Lap and Pause in the shade, which costs no battery to keep accurate because the system draws the ticking; Clockula posts once when something actually changes.
- A stopwatch that keeps counting while the app is closed, and that comes back after a restart paused — with every lap intact — at the time it had banked, rather than inventing the time it lost. Changing the system clock, in either direction, cannot move it at all.
- The app's big numbers now sit in fixed columns, so a readout no longer jitters as its digits change — on every screen that shows one, not only the stopwatch.
- A world clock tab, with an analog face for your own time zone whose dial changes shape with the hour — so a glance says whether it is the middle of the night there, without a line of text saying so.
- Cities added from the device's own time-zone database, searched by name in your language — accents and all, so typing "sao" finds São Paulo — with the country and the offset from GMT shown before you pick.
- Each city shows its time, how far ahead or behind your own it is, and whether it is a different day; all of it re-read from the real time-zone rules, so a daylight-saving change in either place is right on the day it happens.
- Cities reordered by dragging them, or by a "move up" / "move down" action a screen reader can reach — a list you can only rearrange by dragging is a list some people cannot rearrange at all.
- A home time zone you can set by hand or leave following the device, and which every city's offset and day difference are measured against.
- Other apps can now drive Clockula. The whole
android.provider.AlarmClockcontract is answered — setting an alarm, setting a timer, showing the alarms or the timers, dismissing an alarm, snoozing one and dismissing a timer — so an assistant, an automation app or a watch companion can ask for any of it. - Nothing another app sends can make Clockula write something you did not ask for: a nonsense hour opens the alarm in the editor instead of setting one at some other time, an unusable sound falls back to your default instead of blocking the alarm, and a request that matches two of your alarms asks which one you meant rather than guessing. A link to an alarm or a timer that cannot be read dismisses nothing at all, rather than falling back to "all of them".
- An alarm or timer set by voice without showing you a screen removes itself once it has been dismissed, so talking to your assistant every morning no longer leaves a list of dead 06:30 alarms behind.
Changed
- Tapping the alarm icon in the status bar, or the next-alarm line on the lock screen, now opens your alarms. It used to open the ringing screen for an alarm that was not ringing, which closed itself again straight away.
- The database moved to version 3, adding one column to the alarms and timers tables for the alarms and timers another app asked to be temporary. Existing alarms and timers are unaffected and stay exactly as they were.