Commit Graph
22 Commits
Author SHA1 Message Date
makiolaj f570bdefb7 fix(alarms): keep the upcoming notice off a locked screen below Android 12
The Dismiss action's unlock requirement is honoured only from Android 12, so on 10 and 11 the notice is now secret on the secure lock screen rather than dismissible by whoever holds the phone.

Fixes #16
2026-10-22 07:41:05 +02:00
makiolaj 1253c0fc55 fix(alarms): no fire for an occurrence already accounted for
A natural fire whose instant equals the skip or the handled-occurrence watermark is ignored, so a dismissal racing a reschedule cannot ring. Dismissing a repeating alarm persists its resolution before arming the skip, so an unspent old skip cannot absorb it. The deferred notice sync runs even if starting the ring throws, the swipe record is one synchronised read-modify-write, and it stays out of backups.

Fixes #16
2026-10-21 20:26:50 +02:00
makiolaj dedcbd5dfd fix(alarms): a consumed skip stays handled, and only a swipe hides a notice
A skip spent on its occurrence now becomes the handled-occurrence watermark, so a second resolution inside the fire grace cannot ring the skipped alarm after all. The upcoming notice records a swipe through its delete intent rather than inferring one, so a reboot or a blocked channel no longer hides it for good; its Dismiss asks for an unlock, and a Dismiss for a switched-off alarm clears its notice.

Fixes #16
2026-10-21 20:18:05 +02:00
makiolaj 5b35ef65d4 fix(alarms): keep the upcoming Dismiss away from the fire
A fire broadcast already in flight when a skip was armed on its occurrence is now ignored, and a Dismiss that lands once the occurrence is due is stale. The notices are synced after the ring starts on the fire path, and a swiped-away notice stays gone across a process restart. A switched-off alarm answers NotFound.

Fixes #16
2026-10-21 20:09:47 +02:00
makiolaj e43339c386 feat(alarms): notify before an alarm rings, with Dismiss
Two hours before a natural occurrence (AOSP DeskClock's lead), a silent
"Upcoming alarm" notification offers Dismiss, and the alarm's row shows
the same Dismiss as a chip. Dismissing is dismissUpcoming's own branches:
a repeating alarm skips that occurrence, a one-shot is switched off.

The notices are synced at the end of every reschedule, after the real
registration, and a third AlarmManager slot on its own request code
wakes when the next window opens. The sync is wrapped so a notification
failure can never stop a ring. Dismiss names the occurrence and is
refused once the alarm is ringing or has moved, so it can't get round
the dismiss challenge. No challenge is asked before the ring, as in
Google Clock.

Fixes #16
2026-10-21 20:01:21 +02:00
makiolaj e870918a03 docs: plain-language pass over every doc
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.
2026-10-06 19:14:11 +02:00
makiolaj 19ee678a1b feat(widgets): let each widget pick its theme and wallpaper colours
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
2026-10-05 19:48:59 +02:00
makiolaj 462f9f8c9f feat(widgets): open Alarms from the clock widgets, or a tab of your choice
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
2026-10-05 19:42:14 +02:00
makiolaj be526711d7 feat(shell): swipe between tabs
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
2026-10-05 19:42:14 +02:00
makiolaj 45e5a51c98 feat(worldclock): show home time as text under the analog face
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
2026-10-05 19:36:31 +02:00
makiolaj cac5b2dfca refactor(settings): remove the unused default timer duration
Settings > Timers offered a "Default duration", but nothing that creates
a timer ever read it: the keypad starts empty. The row, its preference,
ClockDefaults.timerDuration and the backup field are gone, and the
screen's hint and the hub's subtitle now only speak of the sound.

Backups no longer write default_timer_duration_millis. Older files that
carry it still import, since the codec ignores unknown keys.

Closes #5
2026-10-05 19:28:31 +02:00
makiolaj f37c5f2f84 feat(ringtones): let a music app play the alarm
"Choose from a music app…" in the sound picker browses any app that shares
its library through Android's MediaBrowserService and stores the choice as a
clockula://media URI in the existing ringtone field — no schema change, no
new permission, no internet. The music app streams with its own.

At ring time the app is asked to play through its media session. Every wait
is bounded and any failure falls back to the ordinary alarm sound: the app
won't connect or start, stalls or buffers too long, or the media stream
might not be heard (headphones or a classic Bluetooth device connected, Do
Not Disturb muting media). The ramp drives the media stream and the user's
media volume is restored when the last media ring ends.

RingAudioPlayer.start no longer suspends: vibration starts up front, the
timer collector is not held up, and a generation token keeps late work from
reviving a stopped ring.
2026-10-02 20:44:58 +02:00
makiolaj 5d03c654bf docs: mark M10 done
ARCHITECTURE.md's package table gets the new domain/backup,
domain/selfcheck, domain/widget, data/backup, backup/, widget/,
ui/settings/ and ui/widget/ entries, and §9/§10 are brought up to date
with what M10 actually built (the exported-component count, the
permission list unchanged, which 'not built yet' rows are now done).
CHANGELOG and ROADMAP's current-state line follow.
2026-10-01 22:34:07 +02:00
makiolaj 9ab3927efe docs: the system boundary, and what it refuses
`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.
2026-09-23 11:02:20 +02:00
makiolaj 36f6b356d0 docs: the world clock, its seam and its caches
`ARCHITECTURE.md` gains §16 for the tab — where the zone list comes from, why
ICU sits behind a seam, what `ZoneDirectory` caches and on what key, and why
every comparison is read at the instant rather than against a stored offset.
The module, package and rule tables count the new arrivals.
2026-09-22 13:05:24 +02:00
makiolaj cb35cc9c95 docs: the stopwatch, and why its reading is floored and banked
`ARCHITECTURE.md` gains §15 for the stopwatch and records the one correctness
find this slice turned up: a lap taken inside a segment a reboot later
discarded would have dragged the readout, the lap totals and the shade
backwards. The reading is floored at the last lap's total — and the floor is
banked, not merely displayed, or the shade's free-running chronometer walks
away from a tab that is standing still.

The package, module, receiver, service and permission tables all count one
more; §10 loses the two rows M7 closed.
2026-09-22 10:24:08 +02:00
makiolaj 319d176ae8 docs: the timers, and the counts checked against the gate
ARCHITECTURE gains §14 for timers and the ring package's new shape. The test
counts were written before the review's fixes landed; they now match what the
gate actually runs.
2026-09-12 16:49:05 +02:00
makiolajandClaude Opus 5 176626b33a docs: the alarms screen, and the numbers put right
ARCHITECTURE gains §13 for the alarms screen. The test counts in §8 were
written before the review's fixes landed, and M4's instrumentation count was
one short; both now match what the gate actually runs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wmy1BpCKi8KeSjaWhYuCPV
2026-09-12 14:44:23 +02:00
makiolaj d4120d8e06 docs: the app shell, and what it earned
ARCHITECTURE gains §12 for the shell and loses the two rows M4 paid off; the
permissions row now says what M4 actually asks for and leaves the rest to
M10's self-check.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wmy1BpCKi8KeSjaWhYuCPV
2026-09-12 13:07:53 +02:00
makiolajandClaude Opus 5 a522d68098 docs: the alarm engine, and the blind spot it closes
ARCHITECTURE gains a section 11 on the engine itself — the state machine, the
two AlarmManager slots, the DST table, and the chain that keeps a denied
permission from turning into a silent morning. Section 5's known blind spot is
amended rather than deleted: the boot id exists now, and the stopwatch repair
moved from M7 to here.

Section 9's "no permissions are declared yet, deliberately" is finally untrue,
so it is replaced with the real set and why each one is there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 16:05:31 +02:00
makiolajandClaude Opus 5 971ee4f7a3 docs: point the reboot repair at the milestone that actually owns it
The roadmap puts the BOOT_COMPLETED and TIME_SET receivers, the ringing
service and the full-screen intent in M3; ARCHITECTURE.md credited them
to M6 and M7, which are Timers and Stopwatch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 13:53:11 +02:00
makiolajandClaude Opus 5 1e6443c907 docs: the first ARCHITECTURE.md
How Clockula is built today: the layers and the data seam, the package
layout, the four tables column by column, dependency injection, and the
JVM-first testing posture.

The section on the two clocks is the one a future contributor will need
most — which aggregate resolves against which clock and why, the timer's
three anchors, and the reboot window where a dead anchor can still read
as live, stated as it actually behaves rather than as it was first
assumed to. Closing that window needs a persisted boot id; M3's
BOOT_COMPLETED receiver is where it belongs.

Also says plainly what is not built yet, and points each at its milestone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 13:52:33 +02:00