Skip to content

Rebuild the timer from scratch (kill me) - #568

Open
RisPNG wants to merge 48 commits into
you-apps:mainfrom
RisPNG:timer-rework-and-alarm-card-row
Open

Rebuild the timer from scratch (kill me)#568
RisPNG wants to merge 48 commits into
you-apps:mainfrom
RisPNG:timer-rework-and-alarm-card-row

Conversation

@RisPNG

@RisPNG RisPNG commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

also solves #524, #491, #392.

this is quite another big one, sorry.

it reworks the timer from top to bottom; the screen, the notification, multiple timer behaviour rework, some smaller tweak over the alarm card, and the stopwatch along the way.

take your time auditing and testing things, as I am also doing the same.

the changes kind of work together as a whole since it touches the core, so splitting them into possibly 3 smaller ones (each for timer, alarm, and stopwatch respectively) can introduce some complication during PR merges. bare with me, haha.

Timer Screen Rework: A single screen experience

image image

setting a timer, starting it, keeping it and editing it all happen in one place now, instead of the screen swapping between a picker layout and a list layout depending on whether anything is running. the page carries the picker, the start button, and the active and saved timer sections. the picker is a row of the list like the cards, so it scrolls away with everything else and arrives and leaves with the same movement the cards have, and the list is held anchored on that row so it grows back into view rather than above the top of the screen.

once something is running, the list is what the page is about, so the picker steps aside by default. whether it stays is a setting (keep open / hide), alongside other new added setting which is allowing user on how much the add button adds to a running timer, from a second up to five minutes, which is also what the notification offers.

every timer is named by the duration it was set to (2m 1s) until it's given a label, and clearing the label returns that name rather than a blank card. the whole card opens the editor now, which is a sheet shaped like the alarm editor: the picker it was set with, a label field, and sound and vibration rows, so a timer is configured the way an alarm is. editing the duration of a running timer changes what restart returns to and leaves the current run alone. saved timers keep a label, a sound and a vibration pattern rather than only a duration, and a long press picks them out with the same selection the alarm list uses. a new install starts with 5, 10, 15 and 30 minute timers, since the short end of the range is where a saved timer earns its place.

image

extra: the number keypads the timer and alarm editors kept were near-copies, so they're one shared component now.

Timer Notification Rework

image

timer serves an actual useful purpose for the timer now, and with the buttons in the notification for:

  • running is; pause, add, stop,
  • and paused is; resume, reset, stop.

so a timer can be answered from the notification rather than being a silent notification before, with all the buttons now reaching for the shared timer broadcast, instead of copy pasting the same thing over and over again.

there was a bug when tapping the notification opens a second copy of the app on top of the first. that is fixed too.

Full-screen Ringing Timer

timer-ringing-screen

a finished timer now takes the screen the way a ringing alarm does: the same full-screen treatment, under the same rules, carrying the timer's name, with the three answers a finished timer has; dismiss, give it the increment (kind of like snooze), or set it running again from the duration it was made with (reset).

since this is a new introduced behaviour, I made it a setting instead, so whoever would rather have the notification alone can say so in the timer settings.

Tweak on Alarm Card on Alarm Screen

image

the repeat rule moves onto the name row since the rule describes the alarm rather than the switch it used to sit beside, and a repeat on all seven days reads "Every day" the way a daily repeat already does. the name row lines up with the time under it, and an advanced alarm that rings for several days at each repetition says so ("for 2 days every 6") instead of reading like a one-day alarm.

Animations!

there's now a quick fade and slide animation depending on the action in the timer screen, alarm screen, and stopwatch screen. it is centralized and the timer, alarm, and stopwatch screens share them. some of what is now animated is;

  • the stopwatch's lap and stop buttons travel out from under its main button rather than appearing in place,
  • the lap table grows into each new lap rather than snapping to fit it,
  • the stopwatch's own button stays centred whatever is beside it,
  • and the alarm list and timer list when deletion or addition happens.

Settings

image

the timer section is rebuilt around what a timer can do now. the settings for timer is now (including old and new);

  • picker behaviour,
  • the big start button,
  • full-screen alert,
  • increment,
  • and ring timeout.

there's also an added setting which is a gradual volume rise for both alarm and timer. the alarm's ramp used to be hardcoded to eighteen seconds; it's the same setting now and "Never" is the default, so an alarm starts where it means to stay unless asked to arrive gently.

RisPNG added 30 commits August 27, 2026 23:28
Keep the name row present and hide its label icon when the alarm has no name. Describe seven selected weekdays as every day.
The timer screen switched between picker and list layouts depending on active timers. Show the picker, start button, active timers, and saved timers together, with duration-based default names and vibration enabled for new timers.

Open editing from the entire card and use a sheet containing the selected picker, label, sound, and vibration controls. Editing a running timer's duration changes its restart duration without changing the current countdown.

Store saved timer labels, sounds, and vibration patterns as JSON under a new preference key and pass them to the service on start. Preserve paused state on reset and restart finished timers.

Reuse the alarm number keypad and let each shared wheel consume its own drags. Remove the quick selection and large start button settings because saved timers are always visible and the screen uses the stopwatch-style start button.
Assign stable in-memory identifiers when reading saved timers so list animations, editing, and deletion remain attached to the correct item after positions change. Keep the stored timer format unchanged.
Move card animations into the theme and reuse them for alarms, timers, and stopwatch laps. Key alarm rows by alarm identity so toggling an alarm does not animate it as a replacement. Scroll to a new lap only after the table exceeds its visible capacity.

Support long-press saved timer selection with copy, delete, and dismiss actions. Share the lowercase selection count string and clip card press ripples to their corners.
Animate the table height as laps are appended. Reserve both side-button slots so showing or hiding lap and stop controls does not shift the main button.
Translate each side button by its slot width and draw the main button above them so overlapping portions remain hidden during entry and exit.
Show each timer's label and paused state with an hourglass icon. Offer pause, add time, and stop while running, and resume, reset, and stop while paused.

Add one minute to the remaining countdown without changing the restart duration and reschedule the expiry alarm. Expose the same increment on the timer card using an available clock-plus icon.
Move the countdown into the notification title and refresh it once per second, with the timer name and paused state beneath it. Include the timer identifier and action in intent data to prevent pending intent collisions.

Reuse the existing app activity when opening timer and stopwatch notifications and select the corresponding tab.

Allow the timer picker to hide while timers run and expose preferences for its visibility, the start button size, and the increment amount. Apply the same increment to notifications and initialize the picker to one minute.
Hide the start button with its picker and animate the row height so restoring the picker remains visible. Reserve space for the widest wheel label even below three characters.

Include the ringing duration of each advanced alarm recurrence in its card summary so multi-day runs are distinguishable.
A collapsed picker lost its lazy-list anchor and expanded above the visible area. Place it above the list so its expansion moves the list down visibly.
Make automatic picker hiding the default and first option. Use the standard start button size unless the large button preference is enabled.
Return the picker to the first list row so it scrolls with the page. Anchor the list to that row when restoring it and delay collapse until the first timer card finishes its entry animation.
Lazy lists drop rows that shrink to zero, preventing the picker from expanding smoothly. Use the same entry and exit animations as timer cards and scroll to the top when the picker returns.

Use one visibility state for preferences and running timers so delayed collapse also applies when the first timer starts automatically.
Flooring the remaining duration displayed zero throughout the final second. Share the rounded-up count between cards, notification titles, and refresh scheduling so zero coincides with expiry.

Use a consistent clock for the initial tick and subsequent readings to avoid an invalid initial delta if a timer is queued before the first tick.
Marking the notification silent prevented heads-up display despite its high-importance channel. Remove that flag and the ineffective insistent flag; the timer service remains responsible for ringtone playback until dismissal.
The service could show both the ringing activity and its heads-up notification. Track activity visibility and post silently without a full-screen intent while it is visible, retaining the foreground service entry. Restore alerting when the activity leaves the screen.
Alarm and timer services already play the selected ringtone, so channel sounds added an unwanted chime on each notification update. Create silent channels under new identifiers because existing channel sound settings cannot be changed, and remove obsolete channels.
The default pattern inserted a one-second delay before each buzz sequence. Begin with zero delay for newly created alarms and timers. Preserve stored patterns and the existing database column default in this change.
The default pattern delayed vibration for a second after expiry. Start it immediately for newly created timers while leaving saved patterns unchanged.
Enable colorization for the foreground service notification and use the theme accent already used by widgets.
Request Android 16 notification promotion so the system may show the remaining time in a status bar chip. Older versions ignore the request. Use the same countdown value for the chip and notification title.
The running timer channel's minimum importance prevented status bar promotion. Create a replacement channel at the required higher importance, with sound disabled and alerting limited to the initial notification.
Extract lock-screen visibility, display wake handling, face-down actions, and volume key behavior so other ringing events can reuse them. Keep alarm-specific dismissal and snooze behavior in the alarm activity.

Share the dark layout and landscape arrangement while accepting the displayed time, label, and ringing icon from each activity.
Finished timers previously relied on notifications alone. Use the shared ringing screen with dismiss, increment, and restart actions, and provide a timer setting to retain notification-only behavior.

Restart an expired timer when time is added so the added duration counts down. Cancel its scheduled alarm at expiry rather than during notification posting, preventing later notification updates from cancelling a restarted timer.
Continue counting elapsed time after expiry, add ringing timeout and volume ramp settings, and let a later timer replace the current ringtone while retaining the earlier timer's ringing duration.
# Conflicts:
#	app/src/main/java/com/bnyro/clock/presentation/screens/timer/components/TimerItem.kt
# Conflicts:
#	app/src/main/java/com/bnyro/clock/presentation/screens/alarm/AlarmActivity.kt
#	app/src/main/java/com/bnyro/clock/util/NotificationHelper.kt
#	app/src/main/java/com/bnyro/clock/util/services/AlarmService.kt
Add the increment picker below vibration in the timer sheet and use the global setting as the initial amount for started and saved timers.
Keep the global increment active until a timer has an explicit override. Add a Default choice to remove the override and resume following the setting.
Keep the top time fixed at expiry because the overrun display already tracks the subsequent wait.
Keep the ringing screen's main time fixed to the scheduled alarm instead of the current clock time, including after snoozing. The status bar continues to show the current time.
# Conflicts:
#	app/src/main/java/com/bnyro/clock/presentation/screens/alarm/components/AlarmCard.kt
#	app/src/main/java/com/bnyro/clock/presentation/screens/settings/model/SettingsModel.kt
Startup cleanup deleted the pre-alarm channel, cancelling visible upcoming alarm notifications and resetting user channel preferences. Include it in the retained channel list.
Saved timers changed from comma-separated seconds to JSON and the large start button key was renamed, causing upgrades to lose both preferences. Read the old keys once and store their values in the new format.
Use timer-owned broadcast naming in base Clock You code. Measure countdown deltas with elapsed time so wall-clock changes cannot alter running timers, and rescale remaining time when the running duration changes so countdown and progress remain consistent.
The constructor used an immediate vibration pattern while the SQL default and existing alarms retained the initial delay. Rebuild the alarms table in a schema migration, update alarms using the old default, and verify the migration by opening a version 12 database through Room.
@RisPNG
RisPNG force-pushed the timer-rework-and-alarm-card-row branch 2 times, most recently from 95ae36d to 2ae77de Compare September 5, 2026 15:59
Snoozing a ringing timer only buys it another increment, which rarely matches what a volume key press while reaching for a ringing phone should do there; the keys now end the timer instead.

Alarms keep the snooze default, and a volume button action chosen in the settings still applies to both, since only the unset preference falls back to the new default.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant