Skip to content

Continue Watching home-screen widget - #17

Merged
Ivorisnoob merged 1 commit into
roadmap/refreshfrom
feature/continue-watching-widget
Oct 7, 2026
Merged

Ivorisnoob merged 1 commit into
roadmap/refreshfrom
feature/continue-watching-widget

Conversation

@Ivorisnoob

Copy link
Copy Markdown
Owner

Adds a 4x3 home-screen card showing the newest unfinished title: still, badge, progress bar, title and episode name. With nothing to continue it shows an empty state and opens Home.

Stacked on #16 (both branches touch \docs/ROADMAP.md\ and \CLAUDE.md). *Base is
oadmap/refresh, not \main*
— merge #16 first and this retargets automatically.

No new dependencies

Two deliberate rejections, both to keep \libs.versions.toml\ untouched:

  • RemoteViews, not Glance. Glance's \Image\ cannot fetch anything either, so both routes decode the bitmap in the app; all Glance adds here is declarative syntax. A RemoteViews widget also cannot scroll without a \RemoteViewsService\ and an R8 keep rule, and the existing launcher shortcut already jumps straight to the newest item (\AppNavigation.kt:185), so one card matches what the app already does.
  • No WorkManager, no snapshot. Reading and drawing both happen in the app's process; only inflation happens in the launcher's. So \onUpdate\ reads Room inside \goAsync(), and \ContinueWatchingWidgetRefresher\ repaints from a flow subscription on the Application scope. A periodic job would be inexact, Doze-deferred, and would leave the first render showing nothing for up to its interval.

Behaviour worth knowing

  • The tap reuses the TMDB deep link the app already handles, so no navigation code was needed. \MainActivity\ is \singleTask\ with \onNewIntent, its VIEW filter covers \ hemoviedb.org\ movie and TV paths, and \DeepLinks\ turns that into a Details route. Details and Home use different request codes and data URIs so the two PendingIntents stay distinct.
  • \continueWatching\ follows the active profile, so switching profiles repaints the widget too. Every mutation already flows through \WatchProgressRepository\ (playback, Home remove, Details mark watched, profile switch), so \PlaybackSession\ is untouched.
  • Offline, the card is complete. Title, episode, badge and progress all come from Room; only the poster needs the network, and on failure the placeholder colour shows. This matches the in-app rail rather than improving on it, so it is not a new lie — but a Continue Watching title with no download will still show and then fail to play, same as the rail.
  • *\WatchProgress.badge()\ moved to \presentation/components* so the rail and the widget cannot drift on the up-next case, where \ raction\ is 0 and an unconditional progress bar would read as broken.

Reduced fidelity, deliberately

  • Dynamic colour follows the wallpaper, not \AppSettings.dynamicColor. \�alues-v31\ aliases @android:color/system_accent1_*. A resource alias cannot be conditional, so a user who turned dynamic colour off still gets a dynamic widget.
  • Dark mode follows the system setting, not \AppSettings.themeMode. The launcher re-inflates the layout in its own configuration when the system night mode flips. A user who forces Light while the system is dark gets a dark widget.

Both are noted in the code and the manifest comments so they are not mistaken for bugs.

Verified

  • :app:compileDebugKotlin, :app:assembleDebug, :app:testDebugUnitTest\ all pass; no new warnings.
  • The receiver is present in the merged manifest (checked \�pp/build/intermediates/merged_manifest), which is the usual silent widget failure.

Not verified

Not tested on a device. Per \CLAUDE.md\ the maintainer tests. Widgets genuinely cannot be JVM-tested, so please place it on the home screen and check the forward/back round trip and the empty state before merging. Release-build R8 behaviour was also not exercised.

Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com

A 4x3 card showing the newest unfinished title: still, badge, progress bar,
title and episode name. With nothing to continue it shows an empty state and
opens Home. Everything on the card comes from Room, so it is complete offline;
only the poster needs the network.

RemoteViews, not Glance, so there is no new dependency. Glance's Image cannot
fetch anything either, so both routes decode the bitmap in the app; all Glance
adds here is declarative syntax. A RemoteViews widget also cannot scroll without
a RemoteViewsService and an R8 keep rule, and the launcher shortcut already
jumps straight to the newest item, so one card matches what the app does.

Reading and drawing both happen in the app's process; only inflation happens in
the launcher's. So onUpdate reads Room inside goAsync() and a flow subscription
from the Application scope repaints on every progress change. No WorkManager and
no snapshot: a periodic job would be inexact, Doze-deferred, and would leave the
first render showing nothing for up to its interval. continueWatching follows the
active profile, so switching profiles repaints too. PlaybackSession is untouched
- every mutation already flows through WatchProgressRepository.

The tap is a TMDB VIEW intent, which MainActivity's existing deep link path
already turns into Details, so no navigation code is needed. Details and Home
use different request codes and data, so the two PendingIntents stay distinct.

The layout uses plain colour resources rather than theme attributes, because a
RemoteViews layout is inflated in the launcher and ?attr/... would resolve
against the launcher's theme. values-v31 aliases the system Material You roles
for dynamic colour at reduced fidelity: it follows the wallpaper, not the app's
dynamicColor setting, because a resource alias cannot be conditional. Dark mode
follows the system setting, not AppSettings.themeMode - a user who forces Light
while the system is dark gets a dark widget.

WatchProgress.badge() moves to presentation/components so the rail and the
widget cannot drift on the up-next case, where the fraction is 0 and an
unconditional progress bar would read as broken.

Not verified on a device; CLAUDE.md says the maintainer tests. Compiled and
assembled, and the receiver was checked in the merged manifest. The release
build's R8 behaviour was not exercised.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Ivorisnoob
Ivorisnoob merged commit 177db4c into roadmap/refresh Oct 7, 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