fix: widget loading skeleton + first-add bitmap-memory crash, and edit-frequency carousel desync - #180
Merged
Conversation
…it-frequency carousel desync
Widget (Android):
- Add a loading skeleton for the cold/never-synced window (gated on
habits_updated_at) so a freshly added widget never renders as a blank card.
- Cap rasterized RemoteViews bitmaps at 512px in createRoundedBitmap (consumer
ImageViews are fitXY, so they upscale). The card + every habit-row background
were rendered at full device resolution; Android inlines the collection on
update and rejects views whose summed bitmap memory exceeds ~6x screen
("RemoteViews for widget update exceeds maximum bitmap memory usage"), which
surfaced as the launcher's white "Problem loading widget" on first add
(~31MB used vs ~20MB cap). Now ~5MB.
Edit habit frequency (web + mobile):
- The frequency carousel scrolled to the active card before its slides laid out
at full width, so it stayed on "one-time" while the form state (dots + detail
fields) was correctly Recurrent. Mobile re-positions on onContentSizeChange;
web retries via requestAnimationFrame until the track width is measured.
- Mobile card tap now selects the tapped card instead of re-applying the active
index (+ regression test); per-card accessibility state corrected.
Both bugs were pre-existing (carousel rework in #175/#177; full-resolution
widget bitmaps predate this branch).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
|
This was referenced Jun 19, 2026
thomasluizon
added a commit
that referenced
this pull request
Jun 19, 2026
…ne (#224) (#234) The widget refresh spinner could spin forever because widget_refresh_loading was only ever hidden inside OrbitWidgetFactory.onDataSetChanged, the ColorOS-unreliable RemoteViews binder callback. When that callback is dropped (or the synchronous binder-thread fetch stalls), no clear-site ran. Decouple the spinner-clear from the binder callback: when onReceive raises the spinner it now enqueues a one-time OrbitWidgetRefreshTimeoutWorker (12s, unique, REPLACE) that force-restores the refresh button even if onDataSetChanged never fires. The deadline (12s) sits above the native fetch budget (5s connect + 5s read) so a slow-but-succeeding fetch still wins the race; a normal resolve clears the spinner first and the worker becomes a harmless no-op. The worker recomputes the #180 cold-state skeleton gate from prefs (!isSignedOut && habits_updated_at <= 0) instead of blind-hiding it, and touches only setViewVisibility on the two spinner flags + the skeleton flag, so it neither re-rasterizes bitmaps nor stomps list content. The app-fed syncWidgetData feed is untouched. The pure skeleton predicate and the timeout constant are mirrored in a small TS module (lib/widget-refresh-timeout.ts) with Vitest coverage, since Kotlin has no unit runner in this repo. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Fixes two pre-existing bugs reported on-device (Android).
1. Android widget
Blank cold state. Before the first successful sync there is no cache and the binder-thread fetch is unreliable on ColorOS, so the card rendered blank. Added a loading skeleton (3 placeholder bars) shown while signed-in but never-synced (
habits_updated_at == 0), so a freshly added widget never paints blank. Distinct from the existing "All clear!" empty state and the signed-out state."Problema ao carregar o widget" on first add. Confirmed via logcat:
The card background and every habit-row background were rasterized at full device resolution. Android inlines the collection items into one update and caps total bitmap memory at ~6× screen px; with ~30 rows (~26 MB) plus the first-add card bg sized to half-screen (
AppWidgetOptionsisn't populated on the firstonUpdate, ~7 MB) it hit ~31 MB → the host rejected the update and showed the launcher's white error card. Re-add "worked" only because the bg was then smaller.Fix: cap rasterized bitmaps to 512 px in
createRoundedBitmap(radius/stroke scaled to match). All consumerImageViews arescaleType="fitXY", so the capped bitmap upscales to fill — identical look, but widget bitmap memory drops from ~31 MB to ~5 MB (headroom for 100+ rows).2. Edit habit frequency carousel (web + mobile)
Editing a Recurrent habit showed "Tarefa única" on the card even though the dots/detail fields were correctly on Recurrent — a scroll-position-vs-state desync: the carousel scrolled to the active card before its slides had laid out at full width, so
scrollToclamped back to index 0.onContentSizeChange(fires after slides reach full width); card tap now selects the tapped card instead of re-applying the active index (wasfrequencyHandlers[activeIndex]); per-card accessibility state corrected. Regression test added.requestAnimationFrameuntil the track width is measured (parity hardening).Verification
Notes
Both bugs predate this branch — the frequency carousel was reworked in #175/#177, and the full-resolution widget bitmaps are long-standing. The widget is Android-only (no web/iOS counterpart).
🤖 Generated with Claude Code