From Android 14 large font scales are non-linear, so scaling the sp
size by the width ratio still overflowed the cap; the size is now
worked out in px and converted back. Without seconds the row's time
wraps as it did before instead of ellipsizing.
The row's time is fitted only with seconds on, against both day periods
since an AM marker can be the wider one, and the style is built once by
the screen rather than per row. With seconds off the row is exactly as
before. The home readout stays on one line with seconds, shrinking to
fit instead of wrapping. SCREEN_ON is dropped: the tab cannot be seen
again without a resume, which already refreshes. §5 no longer claims a
clock change heals within one tick for free.
With the tick a minute long, a language change left the city names in
the old language until the next minute; the screen now sends a refresh
on every resume. The row's time is sized once per format against a
wide sample instead of searched for every second, and ellipsizes as a
last resort rather than clipping. BackupSettings takes the setting
without a default, so a snapshot cannot drop it silently.
The home readout gave TalkBack its minute-precision time as a
description, which replaced "Home", the city and the zone name; it is
the node's text now. The clock-change receiver swaps TIME_TICK, which
doubled every minute's work, for SCREEN_ON, which covers a delay
stretched by deep sleep, and resets the cached default zone on
TIMEZONE_CHANGED so the rebuild cannot read the old one. Doc and KDoc
drift fixed.
The time each row and the home readout give TalkBack stays at minute
precision, and the ticking text is kept out of the tree, so a focused
row is not re-read every second. The row's time is capped in width and
shrinks to fit, so a large font scale cannot crowd out the city name.
The setting is its own SettingsPrefs flow rather than a ClockDefaults
field, so flipping it re-emits nothing alarms and timers watch. The
clock-change receiver sits outside the flatMapLatest and restarts the
tick, registering once per subscription and re-measuring the wait
against the clock as it now reads.
Rows that tick every second re-seeded the reorderable column mid-drag,
so it is handed ids now. A zone or clock change no longer waits for the
next minute: TIME_SET, TIMEZONE_CHANGED and TIME_TICK are merged into
the tick through a new ClockChanges seam. With seconds on, the row's
time drops a size so the city name keeps its room. The truncation unit
and the tick come from one place, and stale 200 ms comments are gone.
One switch in Settings > Clock adds seconds to the home readout and
every city row, in the same 12/24-hour and locale form as before, and
gives the analog face its second hand. Off by default.
The tab now ticks on the wall-clock boundary through a new
Ticker.alignedTicks: once a minute with seconds off, once a second only
with them on, replacing the free-running 200 ms tick. The setting is in
backups as world_clock_show_seconds; older files import with it off.
Fixes#23
The snackbar appended the codec's or the exception's own English text,
class names and all. Failures now carry a category (not a backup, from a
newer version, invalid data, too large, unreadable) that the screen maps
to a string resource; the detail stays in the result for tests.
A backup without the timer_presets field decoded it as an empty list,
which means "the user removed them all", so restoring it wiped the
built-in presets. A missing field now keeps them. BACKUP.md describes
this and the per-alarm clamps.
A timer whose ring was held back by a ringing alarm still popped a
heads-up over the alarm's screen, then alerted again once the alarm
ended. The heads-up now waits with the ring.
The widget worked out "Today" when it was drawn, and nothing redrew it
when the date changed, so a widget drawn on Monday night still named
Tuesday on Tuesday morning. It now shares the analog faces' midnight
redraw, renamed to MidnightRefresh, and redraws on a clock or zone change.
The hub read the self-check once, so after fixing something in the
self-check screen it kept showing the old number of problems. It now
re-reads on every resume, as the self-check screen does.
A sticky restart of the timer or stopwatch service has no user action
behind it, so Android 12+ can refuse its foreground start, and the crash
repeated with every restart. The same could happen to the alarm ring
after an inexact fire, after its state was written. Refused starts now
stop the service, or post the ring screen's notification, instead of
crashing. The ring service is stopped with stopService, which cannot
throw from the background.
Granting "Alarms & reminders" again left the next alarm on the inexact
fallback until something else re-registered it. The system's permission
broadcast now reschedules alarms and timers.
A MediaPlayer that errored after it started left the alarm or timer
silent, with no fallback and no forced vibration. The ring now moves on
to the next sound in the chain, and vibrates when none is left.
From Android 14 a foreground service's notification can be swiped away on
an unlocked phone, which left a sounding alarm with no controls. The ring
notification now reposts itself when it is deleted, and tapping a ringing
alarm in the list opens its ring screen.
The system reports only the earliest alarm clock of any app, so an older
alarm left in the stock clock showed up as a problem the offered fix
couldn't solve. Another app's alarm at or before Clockula's next fire is
now fine; a missing registration, or anything later than ours, still is
a problem.
Backup import clamped the app-wide defaults but passed an alarm's own
snooze length, snooze limit and volume ramp through unchanged, so a
snooze of 0 re-rang at once and a negative limit turned snoozing off.
Both import and the database read now clamp them to the ranges the
pickers offer, and imported timer lengths to the setup range.
The fire broadcast's wake lock ended with the receiver, and the ring
services only took their own after a database read, so a dozing device
could suspend in between and ring late. Both receivers now hold a short
timed wake lock across the hand-off, and the alarm service takes its own
before the read.
The ringing notification's Dismiss ended the alarm directly, so a heads-up
or the lock screen got round a sum or hold challenge. With a challenge set,
Dismiss now opens the ring screen. Snooze is shown only while snoozes are
left, since a refused snooze dismisses.
A snooze stays pending for the ring window after it comes due, but the
fire was rejected after the two-minute grace and re-registered at the
same past instant, so the alarm looped on wakeups and never rang after a
reboot or a late delivery.
Adds the screen-on and timer ring screen fixes, the "Timer expired" sound, the hardened backup restore and the faster fallback when a music app refuses to play an alarm.
The start waited for PLAYING and nothing else, so an app that answered
the play request with an error (YouTube without Premium) left the alarm
vibrating in silence for the full 15 s START_TIMEOUT before the default
sound took over. An error stamped at or after the request, or a lost
connection, now ends the wait straight away. An error left over from
the user's own listening still doesn't count.
The asset uploads and the release PATCH only printed their HTTP status,
so a failed upload still ended in "Published" and left the release
Obtainium follows without its APK or checksum. Each now retries with
backoff and fails the job if it never succeeds.
applyBackup re-derived every key name, type and clamp by hand because
Pref.write was internal to floret-kit. floret-kit now has a typed
MutablePreferences[pref] = value, so a restored value is stored exactly
as its setter would, and a changed range or encoding reaches restore
too.
A backup restore stops the ring while its row still exists and deletes
the row afterwards, so the screen's one check came too early and left a
silent ring screen up for good. It now closes once the coordinator and
storage both say nothing rings, which still keeps it up if the import
fails and the ring resumes.
The sound Google Clock and BlackyHawky's Clock use for timers is
timer_expire.ogg from AOSP DeskClock, bundled with those apps rather
than installed as a system ringtone, so RingtoneManager never lists it.
It is now bundled here too and listed first in every sound picker.
The choice is stored as clockula://tone/timer_expired and resolved to
the raw resource when played, so it survives a restore into another
build variant. The file is Apache-2.0, recorded in docs/PROVENANCE.md
with the licence text in licenses/.
Fixes#14
SystemUI launches a full-screen intent only when a notification is first
posted, never on an update. The alarm service went foreground with a
placeholder that had none and added it in the update, so a locked phone
could ring behind a black screen. The placeholder now carries it.
Timers had no full-screen screen at all. A finished timer now opens
TimerRingActivity over the lock screen with Stop and +1 min, posted
under a second notification id so the expiry is a fresh post. It only
does so while the timer is sounding, so it never covers a ringing
alarm's screen or wakes the display for an already auto-silenced timer.
Fixes#15
floret-kit 0.7.0 pulls in material3 1.5.0-alpha29 on the runtime
classpath while the app still compiled against alpha21. alpha29 removed
the composable-content ButtonGroup overload, so the stopwatch and timer
controls threw NoSuchMethodError as soon as a tab opened.
Align the Compose BOM, material3, core-ktx and appcompat with
floret-kit 0.7.0, and make each variant's compile classpath resolve
consistently with its runtime classpath, so a future skew fails the
build instead of a user's app. The two ButtonGroups never used
animateWidth, so they become a Row with the group's spacing; the
removed ToggleButtonDefaults.shapes() was the default anyway.
The first public build. CHANGELOG's Unreleased section is rewritten as
user-facing notes, since it becomes the beta's release notes. The README
gains an install section, and the store short description fits Play's
80 characters.
Removes AI-writing patterns across the docs, store listing and
templates: dash-heavy rhythm, not-X-but-Y contrasts, one-line kickers
and decorative bold. Facts, code, links and headings are unchanged.
Also fixes leftovers copied from the sibling apps: the crash report
template promised "no calendar content", and CONTRIBUTING described
Agendula's task-provider layout, test seams and commit style.
Brings the workflows, scripts and fastlane setup up to Agendula's
v1.1.0-beta.1 state:
- beta.yaml: a release/** branch carrying X.Y.Z-beta.N publishes a
Gitea + Codeberg pre-release with the signed APK. Never F-Droid or Play.
- scripts/version_info.sh derives versionCode from versionName for every
workflow and CI step. Clockula has no shipped 1.0.x codes, so it uses one
scheme: X*1000000 + Y*10000 + Z*100 + (beta N, or 99 for stable).
- CI checks the committed versionCode, blocks a beta from reaching main,
enforces the 500-char What's New and validates the store listing.
- Shared publish_gitea_release.sh / publish_codeberg_release.sh /
release_notes.sh replace the inline steps in release.yaml.
- Renovate also runs on every push to main to rebase conflicted PRs.
- jniLibs keepDebugSymbols, which the reproducibility guard now requires.
Fixes a bug present in Agendula too: the Codeberg tag lookups used
/git/refs/tags/{tag}, which matches by prefix, so v1.0.0 would have
"existed" as soon as v1.0.0-beta.1 did. That would have refused every
later beta and skipped the stable release. Both now use /tags/{tag}.
Every widget instance can now follow the system or be pinned to Light or
Dark, and on Android 12+ turn the wallpaper's colours off in favour of
the app's own palette. Both are set in the widget's config screen and
stored in its own Glance state; an instance without them keeps today's
look.
Glance content takes them through WidgetTheme. The plain-RemoteViews
parts (the analog and digital faces, readouts and chronometers) go
through a new WidgetPalette, which resolves the widget_* palette under a
forced day/night and sets concrete colours, or day/night pairs, on the
views. The app's own tones are now widget_app_*, so they stay reachable
on API 31+. The analog and digital clocks are re-coloured on API 31+
only: below that AnalogClock's hands and a shape's background tint can't
be set through RemoteViews, so their config screens don't offer it.
The next-alarm and timer widgets gain a config screen for this
(configuration_optional, so placing one doesn't stop there). The analog,
stopwatch and quick-timer config screens become settings screens with an
OK, like the digital clock's, since they now hold more than one choice.
Background opacity is left out: most faces are a shape with text in its
container colour rather than a card, so there is no background to fade
without losing contrast against the wallpaper.
Closes#6
The quick-timer config screen now ends its list of presets with a
"Custom…" row that opens the timer keypad. Anything from 1 second to
59:59 is accepted; past that the keypad shows a range hint and OK stays
disabled, since the widget's readout is sized for MM:SS. A stored value
that isn't a preset marks the Custom row, with the value as its summary.
The keypad dialog is lifted out of the timer editor into a shared
TimerDurationDialog, so both use the same one; the editor's now also
shows the range hint the setup screen shows.
Closes#7
Tapping the digital or analog clock widget now opens Alarms instead of
World clock, and both config screens gain an "On tap" setting (Alarms,
Timers, Stopwatch, World clock) stored per instance next to the style.
Instances placed before the setting existed open Alarms. The analog
config becomes a settings screen like the digital one, with the style
behind a row, to make room for it.
A widget tap sometimes showed whatever the app was last on instead of
its tab, for two reasons. The shell replayed a deep link as a tab tap
with restoreState, so it restored the tab's saved back stack, nested
screen and all (fixed by the pager shell, which pops to the tab). And
the widget intents carried only NEW_TASK, so with a picker or settings
page left on top of MainActivity the task was merely brought forward and
onNewIntent never ran. Every widget that opens the app now builds its
intent in one place with CLEAR_TOP and SINGLE_TOP as well.
Closes#8
The four tabs are now pages of one HorizontalPager on a single NavHost
route, so a sideways swipe moves between Alarms, Timers, Stopwatch and
World clock as well as tapping the bar. The pager is the source of truth
for the selected tab; editors, the timer setup page and Settings stay
NavHost destinations pushed over it. A tab tap from one of them pops
back to the tabs, and a deep link always lands on its tab rather than on
whatever nested screen was last open there.
Swiping is on only under a navigation bar: beside a rail the tabs are
stacked vertically, and a horizontal swipe has no counterpart there.
Closes#2
The World clock tab now prints the home time under the hero face in the
app's 12/24-hour format, reversing the earlier call to leave it out.
Reading the exact minute off a dial is slow, and that minute is what
most visits to the tab are for.
The face no longer has a content description, so TalkBack does not read
the time twice. The readout, "Home", the city and the zone name are one
merged node instead.
Closes#4