Automatically manages downloaded episodes per series — keeps 5 unwatched
episodes downloaded, removing watched ones and fetching new ones on
HomeScreen open or pull-to-refresh. A single download button on the
Series screen opens a dialog to choose between downloading all episodes
or enabling smart download.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire up the existing download button on the Series screen to download
all episodes, and add a per-season download button next to the season
tabs. Episode metadata is fetched in parallel for faster queuing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of just popping the EpisodeRoute (which could land on Home if
opened from ContinueWatching), pop and push a SeriesRoute so playback
exit consistently returns to the SeriesScreen.
Pop the EpisodeRoute from the navigation stack when playback starts,
so finishing PlayerActivity returns to the SeriesScreen instead of
the EpisodeScreen.
Wrap HomeContent with PullToRefreshBox to allow refreshing library,
continue watching, and next up sections by pulling down. Also fix
refreshHomeData to suspend until loading completes so the refresh
indicator dismisses properly.
Adds a MediaResumeButton to the Series screen action bar that directly
launches playback for the next unwatched episode. Shows "Resume" with
progress fill if partially watched, or "Play" for unwatched episodes.
EpisodeScreen called viewModel.onDownloadClick() directly without first
requesting the POST_NOTIFICATIONS runtime permission. On Android 13+
(API 33+), foreground service notifications are suppressed without this
permission, so downloads ran silently with no notification shown.
Mirrors the existing permission pattern from MovieScreen: on Android 13+
the system dialog is shown before the first download; on denial the
download still proceeds (notification is nice-to-have).
- Add ActiveDownloadItem data class to represent a download in progress
- Add observeActiveDownloads() to MediaDownloadManager, polling the Media3
download index every 500ms on Dispatchers.IO for reliable real-time progress
(listener callbacks alone do not fire on every progress update)
- DownloadsViewModel exposes activeDownloads (StateFlow) and cancelDownload();
the completed downloads flow filters out items currently in progress
- DownloadsContent shows a "Downloading" section with thumbnail, title,
progress bar + percentage, and a cancel button above the completed grid
Capture the player position when the horizontal drag starts and compute
an absolute seek target instead of using relative seekBy, which could
drift or snap back due to playback advancing during the gesture.
The detectTapGestures and drag awaitEachGesture handlers competed for
pointer events, causing drag() to sometimes end prematurely and the
seek to not fire. A shared dragActive flag now gates tap/double-tap
callbacks so they are suppressed during active drags.
Vertical drag gestures (brightness/volume) were not consuming pointer
events, causing detectTapGestures to fire onTap and toggle the player
overlay when the gesture ended.
ensureReady() had a race condition where concurrent callers from init
block and ViewModels would all run the loading logic simultaneously.
Added Mutex to ensure only the first caller loads; others await.
Also added a cooldown to refreshHomeData() since LifecycleResumeEffect
fires immediately on first composition, triggering a redundant reload
right after the initial load completes.
Switch seek parameters from CLOSEST_SYNC to PREVIOUS_SYNC so seeks always
land at or before the requested position, preventing subtitle cues from
being skipped. On backward seek discontinuity, briefly disable and re-enable
the text track to flush TextRenderer state so the current cue is re-delivered.
Removing the item key disables Compose's scroll anchoring, which was
shifting the scroll offset rightward whenever a refresh prepended new
items, making the list appear to start in the middle.
Implement media downloading with foreground notification showing progress
percentage and speed. Uses Media3's DownloadManager + SimpleCache with
OkHttp datasource for authenticated Jellyfin downloads. Downloaded movies
are saved to the offline database and the player reads from cache
automatically via CacheDataSource.
Replaced competing pointerInput modifiers with a single unified gesture handler that determines drag direction early. This eliminates conflicts between horizontal seeking and vertical brightness/volume gestures, making swipe interactions more reliable and predictable.