Commit Graph

611 Commits

Author SHA1 Message Date
f6c465656e perf(home): reduce redundant requests on home refresh
Drop a duplicate getLibraries call from loadLatestLibraryContent by
reusing the libraries already loaded earlier in the same refresh.

Add a 30s debounce to HomeRefreshCoordinator.onResumed so rapid
resume/tab-switch events coalesce into a single refresh; pull-to-refresh
bypasses the gate via onRefresh.

Rate-limit SyncPlaybackPositionsHomeRefreshSideEffect to once every
6h with a 25-item cap and a rotating window cursor, so users with many
downloads no longer fire one getItemInfo call per item on every resume.
2026-06-19 14:38:57 +02:00
54c895ff0f feat(player): track skippable segment type to gate intro and outro UI
Expose the active skippable segment type from the player state so the UI
can show the skip intro button only for intro segments and trigger the
next episode overlay when an outro segment is active.
2026-06-19 14:25:29 +02:00
a4267d85ce refactor(player): replace manual hidden UI visibility with ValueChangeTimedVisibility counters
- Replace hiddenSeekPreviewPositionMs with hiddenSeek counter for ValueChangeTimedVisibility
- Add resumeStopFeedbackCounter to decouple feedback visibility from isPlaying state
- Consolidate onPausePlayback/onResumePlayback into single onTogglePlayback
- Increase right D-pad seek from 10s to 30s
- Remove obsolete LaunchedEffect resetting hidden seek state
2026-06-19 11:42:16 +00:00
a07f9dd6e4 refactor(player): replace manual stop feedback visibility with ValueChangeTimedVisibility 2026-06-19 11:14:48 +00:00
50bbf5b81f fix(player): let focused overlay handle D-pad center instead of pausing
The root Box's onPreviewKeyEvent consumed KeyDown for D-pad
center/Enter in handleTvPlayerRootKeyEvent, pausing or resuming
playback and returning true before the event reached the focused
next-episode overlay. Pressing center on the overlay therefore
paused playback instead of starting the next episode.

Add a popupVisible flag so the root handler returns false for
center/Enter when a focusable overlay (skip intro / next episode) is
shown, letting the event propagate to the overlay's clickable. The
skip-intro action is preserved because the visible skip button now
handles activation through its own clickable.

Drop the now-unused onSkipSegment/hasSkippableSegment parameters and
the redundant focusable modifier on TvNextEpisodeOverlay (clickable
already makes it focusable).
2026-06-19 11:04:18 +00:00
1bd51d8a9f refactor(player): move controls auto-hide from ViewModel to composable layer 2026-06-18 20:05:35 +00:00
615e95db5c feat(player): add next episode overlay that appears 20s before episode end
Show an "Up Next" card with the next episode thumbnail, play overlay,
and title when within 20 seconds of the current episode ending.
Tapping it immediately starts the next episode from the playlist queue.

- Add nextEpisode field to PlayerUiState derived from queue
- Create NextEpisodeOverlay for mobile with artwork + title
- Create TvNextEpisodeOverlay for TV with D-pad focus support
- Integrate into both PlayerScreen and TvPlayerScreen
- Auto-focus overlay on TV when it appears
2026-06-18 16:34:39 +00:00
ccd5a7f50e refactor(series): remove unused Add button from series detail screen 2026-06-18 15:48:26 +00:00
d0149bf647 fix(search): auto-focus search field to open keyboard on screen open 2026-06-18 15:39:51 +00:00
9bea9a81ff chore(build): increment app versionCode to 1000027 2026-06-12 20:45:20 +02:00
8d74d0c5df refactor(media): remove LocalMediaUpdater interface in favor of MediaMetadataUpdater
Make LocalMediaRepository extend MediaMetadataUpdater directly instead of the
now-removed LocalMediaUpdater, eliminating the redundant intermediate interface.
Add no-op updatePlaybackPosition stubs to all LocalMediaRepository implementations
since server-side position tracking belongs in the remote layer of the composite.

JellyfinMediaMetadataUpdaterImpl continues to act as the composite that combines
LocalMediaRepository (local cache) with JellyfinApiClient (remote server) under
the single MediaMetadataUpdater contract.
2026-06-12 18:40:22 +00:00
e78a3700a6 refactor(media): consolidate metadata update functions into MediaMetadataUpdater
Replace the disparate JellyfinMediaMetadataUpdater, LocalMediaUpdater,
and direct API/repository calls for watched status and progress updates
with a single unified MediaMetadataUpdater interface and implementation.

The new implementation coordinates both local cache (in-memory/Room)
and remote server updates, so callers have a single point of entry
for all metadata mutations. markAsWatched now updates both server
and local state, fixing a gap where the UI would not reflect
watched changes until a full refresh.
2026-06-12 18:08:32 +00:00
def92ad111 feat(ui): add watched toggle to detail screens and home/library cards
Add mark-as-watched/unwatched bottom sheet actions to home screen
NextUp cards and library browse cards, and to library screen grid items.
Add a single-button watched toggle (with dual icon/color states) to
Movie, Episode, and Series detail screens. Inject
JellyfinMediaMetadataUpdater into all four affected ViewModels.
2026-06-12 17:47:14 +00:00
c92a8831a5 chore(build): increment app versionCode to 1000026 2026-06-12 18:35:45 +02:00
fe66926335 fix(playback): add offline fallback for next-up episode resolution
When offline, getNextUpPlayableMedias() only tried the Jellyfin API which silently returns an empty list on failure. This broke the queue population for offline playback, meaning no playlist was built and autoplay-next could never fire.

Add an offline fallback path that queries the Room database for the current episode's series, finds the next episodes ordered by (seasonIndex, index), and resolves them as downloaded PlayableMedia via getPlayableMedia().
2026-06-12 16:20:17 +00:00
4727cdcab1 refactor(offline): rename OfflineCatalogStore to OfflineMediaManager and consolidate into repository
- Rename interface OfflineCatalogStore → OfflineMediaManager for clarity

- Merge RoomOfflineCatalogStore functionality into OfflineLocalMediaRepository

- Remove RoomOfflineCatalogStore; update DI binding and consumers
2026-06-12 16:11:34 +00:00
2853609cbb chore: remove unused interface 2026-06-12 17:53:15 +02:00
327e68af19 fix(playback): resume offline playback from saved position instead of start
Offline downloaded media always started playback from the beginning because getOfflineDownloadedPlayableMedia() hardcoded resumePositionMs to 0L, ignoring the progress percentage stored in the Room database.

Added calculateOfflineResumePosition() that parses the runtime string and converts the stored progress percentage (0-100) to milliseconds, applying the same 5%-95% threshold as the online path.
2026-06-12 12:30:23 +00:00
8dcb6aeea9 chore(build): increment app versionCode to 1000025 2026-06-07 13:35:49 +02:00
df2a468e89 feat(playback): mark items fully watched when progress exceeds 80%
When playback position reaches 80% of runtime, send the full runtimeTicks to the server so items appear completely watched, matching the played flag already being set at this threshold.
2026-06-07 11:30:33 +00:00
4a6977eefb feat(connectivity): add offline resilience with server reachability checks 2026-06-07 11:25:59 +00:00
3f9fd6e36e feat(card): replace dropdown menu with modal bottom sheet and add long-press
Replace the DropdownMenu popup in MediaImageCard with a ModalBottomSheet
that appears when the three-dot icon is pressed on ContinueWatchingCard
items. Also trigger the bottom sheet on long-press of the card.
Follows the existing ModalBottomSheet pattern from SeriesComponents.
2026-06-07 09:40:20 +00:00
10e7b450f3 fix(series): make season selector bottom sheet scrollable
Replace the non-scrollable Column with a LazyColumn in the season selector ModalBottomSheet so the sheet content scrolls when there are many seasons.
2026-06-06 17:37:47 +00:00
c81b228bb2 refactor(media): centralize detail scaffold content
Move duplicated media detail title, metadata, actions, synopsis, playback, and cast rendering behind MediaDetailScaffoldUiModel so screens only provide model data and optional trailing content.
2026-06-06 12:17:34 +00:00
f94bdc5bce feat(downloads): add DefaultTopBar to DownloadsScreen 2026-06-06 14:01:52 +02:00
4ade70a5de refactor(media): merge heroContent and content into single content lambda 2026-06-06 11:47:48 +00:00
ce907cee0c chore(build): increment app versionCode to 1000024 2026-06-06 13:28:35 +02:00
dd66302da0 feat(series): replace season tabs with BottomSheet selector
Convert SeasonTabs from horizontal scrollable tabs to a clickable label that opens a ModalBottomSheet listing all seasons for selection.

- Removes unused LazyRow, itemsIndexed, rememberLazyListState imports
- Removes unused SeasonTab private composable and SeasonTabTagPrefix constant
- Adds ArrowDropDown and Check icon imports
- Adds OptIn(ExperimentalMaterial3Api) for ModalBottomSheet
2026-06-06 10:51:12 +00:00
753b461453 refactor(series): remove unused preview functions and imports from SeriesScreen 2026-06-06 12:40:07 +02:00
0bf968a42b feat(ui): make top bars scroll aware 2026-06-06 10:38:40 +00:00
005b3249ff refactor(series): remove unused PlayCircle icon from SeriesComponents 2026-06-06 11:40:21 +02:00
84157868d0 feat(series): show episodes as vertical list 2026-06-06 09:38:15 +00:00
25b65b9048 refactor(app/ui): refactor MediaDetailScaffold
Move everything into the Scaffold for simpler scrolling behavior

pass Modifier to heroContent and content composables
2026-06-06 11:30:03 +02:00
ed418a8e44 chore(build): increment app-tv versionCode to 2000013 2026-06-05 18:41:34 +02:00
fecc962950 chore: update update manifest URL to secure HTTPS 2026-06-05 18:41:16 +02:00
1ae2541773 chore(build): increment app versionCode to 1000023 2026-06-05 18:28:08 +02:00
88e8b3a41f refactor(download): share download UI handling 2026-06-05 15:29:06 +00:00
85c53f8704 feat(playback): sync offline positions with Jellyfin
Add a home refresh side effect that compares offline progress with Jellyfin user data and syncs the furthest playback position.

Use Jellyfin playbackPositionTicks for server updates while keeping percent-based writes for local offline and in-memory repositories.
2026-06-05 14:57:20 +00:00
448550b5ab chore: remove unused code 2026-06-05 14:01:50 +02:00
9f8292b04d refactor(home): extract refresh side effects
Centralize home refresh orchestration so future refresh side effects can be added through Hilt multibindings instead of expanding AppViewModel.
2026-06-04 19:26:47 +00:00
fa87005d01 chore: update Gradle and AGP versions 2026-06-04 20:59:54 +02:00
1ad7bc4e23 feat: add exit confirmation dialog on back navigation 2026-06-04 20:57:38 +02:00
e72c421294 feat(playback): pause media playback when screen is in the background 2026-06-04 17:39:59 +02:00
9aec271aa4 fix(playback): manage playback state on activity lifecycle events 2026-06-04 17:39:59 +02:00
717bf3c8e1 fix(playback): Add clamp effect to brightness sliders 2026-06-03 20:59:47 +02:00
0011f3842a refactor(playback): simplify transcoding fallback state 2026-06-01 19:37:04 +00:00
670e937566 chore(build): increment app-tv versionCode to 2000012 2026-06-01 21:27:34 +02:00
38f1f94956 fix(playback): fall back to transcoding on direct play errors 2026-06-01 21:25:21 +02:00
72cb574753 chore(build): increment app versionCode to 1000022 2026-06-01 21:24:22 +02:00
80ab591662 Simplify Jellyfin playback url retrieval 2026-06-01 20:54:20 +02:00