Skip to content

Home screen widgets - #11

Merged
sayed0am merged 23 commits into
mainfrom
widgets
Oct 25, 2026
Merged

sayed0am merged 23 commits into
mainfrom
widgets

Conversation

@sayed0am

Copy link
Copy Markdown
Owner

Home screen widgets for OpenStrap, built to docs/design/widgets/spec.md steps 1 to 7.

What's in it

  • Five widgets, each in three sizes: Small 2×2, Wide 4×2, Large 4×3.
    • Moment: follows the day (Waking, Midday, Workout window, During, After, Evening, Night). It leads with something else when a widget for that metric is already placed.
    • Recovery, Steps and Sleep.
    • Start a workout: your usual sport, the next four and this week. While recording it shows a live timer, and Paused when paused.
  • Strap status: a round sync button beside the title. It shows not paired, low battery, a stale sync or syncing. Tapping a stale sync syncs without opening the app.
  • Picker: previews and descriptions. Android 15 and later use generated previews from the real layouts.
  • Refreshes: sync end, workout start and end, strap state, steps (at most every 15 min), midnight, and alarms at each moment change. Moment's minute counts tick every 5 min without waking the phone.
  • Rendering off the main thread: WorkManager starts from the app's own setup, so Glance's render job gets its own thread instead of the UI thread. This removes the hitch on each sync's refresh. It uses a Glance internal (SessionWorker's dispatcher), so a Glance update that changes it fails the build rather than the app.
  • Shared drawing: light, dots and charts are drawn by shared code in ui/glow/draw/, which the app's Sleep stage strip now uses too.
  • Debug widget gallery: included but turned off in the debug manifest. CLAUDE.md explains how to turn it back on.

Testing

  • 904 unit tests pass. The debug and release builds both pass.
  • Checked on a Galaxy Z Fold (SM-F966B):
    • all five widgets at One UI's cell sizes and at the minimum sizes, at font scales 1.0, 1.08 and 1.3
    • every moment and every strap status
    • the pin dialog preview, and the taps
    • the recording and paused states with a real workout
  • Not yet checked:
    • the release build on the phone
    • the generated picker previews on Android 15 and later
    • battery use over a full day

Open

  • Moment and Recovery sentences can wrap to two lines and leave one word alone on the second.
  • One widget fills an 11th spot in a column, and Glance drops it. Which widget and which item hasn't been found yet.

🤖 Generated with Claude Code

sayed0am and others added 23 commits October 24, 2026 21:01
Moment, Sleep, Steps, Recovery and Start a workout, each at Small 2x2,
Wide 4x2 and Large 4x3 so they work in stacks. Covers tiers and light,
per size content, taps, the strap status line, Glance rendering limits,
refresh triggers, copy, open questions and the build order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New ui/glow/draw package holds the glow and edge light, the dot font
and layout, and the line, ghost line, hypnogram, stage bar and pill
chart geometry as pure functions, with a DrawScope renderer and an
android.graphics.Canvas renderer (plus bitmap helpers) over them.
GlowCard, EdgeCard, DotMatrixText and the charts now call it, so a
widget's bitmap and its card come from the same code.

No visual change: before and after screenshots of Today, Recovery,
Sleep and Activity on the phone differ only in live data and the
sync line. 28 new unit tests, 750 in total pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Glance 1.2.0
- MomentResolver.nextChange, the instant the moment next changes,
  checked against resolve at every returned instant, DST included
- momentLead and WidgetKind for Moment's duplicate rule
- strapStatus and its copy for the status line, strap battery,
  charging and the last finished sync kept in settings
- WidgetUpdater: updates only placed widgets, wired to sync end,
  workout start and end, strap state, live steps (15 min throttle),
  midnight and moment alarms set only once a widget is placed
- SyncNowAction for the status line, not wired yet
- CopyPunctuationTest scans widget/

No visible change. 35 new tests, 785 pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s refresh)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RecoveryWidget (Glance, SizeMode.Responsive) with Small, Wide and Large
layouts per spec 3.4: an edge card lit from the left in the moment's
mood, drawn with the shared edge light, and the score in shared dots.
Data and copy come from the app's own functions. Tapping opens the
Recovery screen for the day shown. Waiting states: Yesterday, then
the app's own reason.

Sizes found on the phone: One UI's cells are smaller than the mockups
and it places by the old cell formula, so the layouts are picked from
minimums (Small any, Wide 300x160, Large 300x260 dp) and the provider's
minimum is 110 dp. It now places as 2x2. The Large shows the meaning
sentence, which fits at the minimum width.

WidgetSizes and the provider XML test are shared for the next widgets.
A debug-only gallery hosts the real widget at each size and mood, and
can request a pin. 19 new tests, 804 pass, release build with R8 works.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
StepsWidget in Small, Wide and Large per spec 3.3: an edge card lit from
the bottom in Steps lime, steps in shared dots, today's line on Wide and
Large with the goal at its top (one line, no goal bar, the user's call),
and This week on one line over a bar a day. Data and copy come from the
app's own functions. Tapping opens the Steps detail.

The parts Recovery and Steps share move to WidgetParts (loading, frame,
edge background, title row, dots, week chart, tap, receiver callbacks),
with Recovery's output unchanged on the phone.

Found on the phone:
- Glance drops a Row's or Column's children past ten: the week chart's
  letters and the Large's week were cut. Grouped, padding not Spacers.
- What fits was decided at the bucket's minimum, so a 2x2 Small dropped
  its share line. It is now decided at the smallest real cell reported
  for that layout (WidgetSizes.fitSize).

818 tests pass, release build with R8 works.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Steps card's share line and the active minutes word said "Goal
reached" while the Steps widget said "Goal met". One phrase now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SleepWidget in Small, Wide and Large per spec 3.2: an edge card lit from
the top in Sleep's ink, always the night mood, with the score in shared
dots, time asleep and the stage bar, and the legend on the Wide. The
Large shows the time in each stage as rows with bars, as the Sleep score
detail does, in place of the hypnogram (the user's call). Data and copy
come from the Sleep tab's own functions, pulled out where they were
inline (main sleep, the week's nights, the score and stage lines).
Tapping a night opens its Sleep score detail.

Checked on the phone: the Sleep tab looks as before, the widget renders
at every size, places as 2x2 beside Steps and opens the right night.
832 tests pass, release build with R8 works.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Small, Wide and Large, lit from the right in heart ink. The usual sport with
play, the next sports in order of use plus All sports, and on the Large this
week and the last of the usual sport. While recording, the workout and Open.
Nothing wraps: captions shrink, tiles shrink, then columns go. Captions are
centred under their tiles. All sports opens Ready on the picker (ready_pick).
The gallery can host at One UI's real cells (--es cells oneui).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The recording state was one row in an empty card. A RemoteViews Chronometer
now counts active time on its own, stopped while paused, sized to each card,
with the sport, a recording dot and Open above it and, on the Large, this week
below. Start also refreshes on pause and resume.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only the Wide and Large had a status line. The Small's last line now reads
Paused in place of "Since 11:58", the timer giving way to keep it, and at the
very smallest Paused takes the name's line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A glow card that follows the day the way Today does: waking, last night, the
workout window, midday energy, during and after a workout, evening, night.
A lead with nothing to show gives way to the next. Counts (minutes since the
run, to bedtime) refresh on 5-minute alarms within the moment. Recovery's
number gives way on Moment when a Recovery widget is placed. Text stays on
one line except the sentence (two at most), and spare height grows the parts
before it fills the gaps. The tap opens Today at its root.

Shared: WidgetText.fitWidth with a 15% margin, GlowWidget beside EdgeWidget,
the stage strip geometry for Compose and Canvas. The gallery takes --es moment
and --es placed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The status line (not paired, low battery, a stale sync, syncing) takes the
title's place on every widget, fitted into each layout's sums with shorter
wordings before it drops; tapping a stale one syncs. A stale alarm at last
sync plus 3 hours refreshes the widgets when nothing else would. Previews:
generated ones on Android 15+, a previewLayout per widget and PNG
previewImages, so the pin dialog no longer says "Couldn't add widget".
Recovery, Steps and Sleep grow their parts on tall cells as Moment does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pill took the title's place and read long and thin. The title now stays,
and the status is a 32 dp round button at the top right (44 dp to tap) with
its state's icon: refresh, spinner, strap, a new low battery. On Wide and
Large its words ("Synced Thursday", "Pair your strap", "Strap at 12%") sit
beside it in amber, in place of the note. Start gains a title row only while
a status shows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
As the other Smalls: "Start" (the full title from about 200 dp) with the
sync button at the top right, then the usual's tile with play beside it,
then the name and "Your usual". Without a status it is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 5 minute count alarms (min since a workout, to bedtime) now use a
non-wakeup RTC alarm, so they wait for the screen to come on. Only the
moment's end still wakes the phone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An install kills the process before onDestroy, so the gallery's bound ids
stayed and counted as placed: 78 of them had piled up, and every widget
update rendered them all on the main thread, lagging the app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Glance runs every widget session on the main thread, about 90 ms a
widget on the Fold (bitmaps were only 20 to 30 ms of it), so each sync's
refresh stuttered the app. WorkManager now starts from the app's own
configuration with a worker factory that gives Glance's SessionWorker a
single background thread. SessionWorker's dispatcher argument is a Glance
internal, so a Glance update that changes it fails the build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its activity is commented out in the debug manifest; the code stays and
still compiles. While open its widgets count as placed and refresh along
with the real ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sayed0am
sayed0am merged commit 50f9d99 into main Oct 25, 2026
1 check passed
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