79 Commits

Author SHA1 Message Date
28b647e209 build(deps): upgrade gradle, agp, kotlin, compose and sdk to 37
- Bump compileSdk/targetSdk from 36 to 37 across all modules
- Upgrade gradle wrapper to 9.6.1
- Upgrade agp to 9.3.0, kotlin to 2.4.10, ksp to 2.3.10
- Upgrade composeBom to 2026.06.01 and compose libs
- Upgrade hilt to 2.60.1, room to 2.8.4, media3 to 1.10.1
- Upgrade okhttp to 5.4.0, coil to 3.5.0, datastore to 1.2.1
- Upgrade lifecycle to 2.11.0, activity-compose to 1.13.0
- Upgrade firebase crashlytics to 20.1.0, gms services to 4.5.0
2026-07-18 15:11:38 +00:00
79066b2ce5 feat(player): load external subtitles including ASS/SSA via SubtitleConfiguration 2026-07-11 15:49:56 +02:00
c2cc4e9e65 revert(data): remove ETag caching and head-fetch home refresh
Reverts 5491c38 (ETag-based conditional HTTP caching for home refresh) and 1ec9ff9 (head-fetch rows and count-only libraries). Restores the pre-ETag home refresh path that fully loads library content per library and fetches each home row in a single request.

Preserves unrelated later improvements: SingleFlight deduplication (86f44ac), nullable toLibrary skip for unsupported library types (80b1bd8), and upsert-before-publish ordering to prevent empty intermediate states (f0a7828).
2026-06-26 15:44:05 +00:00
59315b701d fix(repository): merge existing seasons when updating series with empty seasons 2026-06-25 21:09:43 +02:00
3c46650a63 fix(repository): enhance series upsert logic to preserve existing seasons 2026-06-25 20:47:38 +02:00
f0a7828613 refactor(repository): reorder state updates to prevent empty intermediate states 2026-06-22 20:30:31 +02:00
9872f401f8 refactor(data): remove redundant dedup guards and improve loading robustness 2026-06-22 18:09:50 +00:00
86f44ac9ce refactor(data): deduplicate concurrent data fetches with SingleFlight 2026-06-22 17:26:50 +00:00
d80b283ded fix(repository): streamline season loading and update series state 2026-06-22 19:20:20 +02:00
80b1bd8547 fix(data): gracefully skip unsupported library types during refresh
Previously, toLibrary() threw UnsupportedOperationException for
library types the app does not surface (e.g. boxsets, music, photos,
live TV), which aborted the entire home screen refresh. Now these
unsupported types are logged and skipped, preventing a single
unexpected view from leaving the user with an empty home screen.
2026-06-20 21:50:15 +02:00
6e48b35741 refactor(data/jellyfin): reload media items on user data change events 2026-06-20 16:01:22 +00:00
8d5948438e feat(data/jellyfin): sync watch progress in real time via WebSocket
Add JellyfinWebSocketService that subscribes to UserDataChangedMessage events and propagates watch progress changes to the local media repository.

The WebSocket subscription is gated by login status and network connectivity (matching other background work), runs with automatic retry on transient failures, and follows the lifecycle of the Jellyfin SDK's ApiClient for automatic reconnection.

Inject the service into JellyfinSessionBootstrapper to ensure Hilt instantiates the singleton at app startup.
2026-06-20 13:27:10 +00:00
8a2702a7cb refactor(core/series): improve data loading reliability with concurrent initialization 2026-06-20 12:18:29 +00:00
5a98f3d2e7 refactor(core): separate data loading from repository reads
Move implicit loading side effects out of getX methods into explicit loadX methods, making data flow predictable. Remove seriesTitle StateFlow in favor of direct seriesName access. Add proper error handling to repository load operations.
2026-06-20 09:27:25 +00:00
1ec9ff9c95 perf(home): head-fetch rows and count-only libraries
Add a head-only fetch path for the home rows (Suggestions, Continue
Watching, Next Up, Latest per library). The first HEAD_LIMIT items
are fetched first; if the head matches the cached head, the full
request is skipped. Most home refreshes now only pay for the small
head request, not the full payload.

Replace per-library getLibraryContent calls in the home page with
getLibraryItemCount (limit=0, enableTotalRecordCount=true). The home
page only uses Library.size; the full movies/series lists are fetched
on demand by LibraryViewModel via the new loadLibraryContent method.
The per-row loaders now upsert full movie/series details into
onlineMediaRepository so the home viewmodel can still resolve them.
2026-06-19 13:46:21 +00:00
5491c381a3 feat(jellyfin): add ETag-based conditional HTTP caching for home refresh
Introduce an OkHttp interceptor that caches ETags from home-refresh
endpoints and throws NotModifiedException on 304, allowing callers to
reuse cached data. Wire it into the Jellyfin SDK via a dedicated
@JellyfinSdkClient OkHttpClient. Update API methods to return null on
304, and update InMemoryAppContentRepository to keep existing state
when null is returned. Persist dateLastMediaAdded from /UserViews in
HomeCache to short-circuit unchanged /Items/Latest calls.
2026-06-19 13:16:07 +00:00
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
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
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
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
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
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
0011f3842a refactor(playback): simplify transcoding fallback state 2026-06-01 19:37:04 +00:00
38f1f94956 fix(playback): fall back to transcoding on direct play errors 2026-06-01 21:25:21 +02:00
80ab591662 Simplify Jellyfin playback url retrieval 2026-06-01 20:54:20 +02:00
0eea3165de PlaybackProfilePolicy should not require server version 2026-06-01 20:06:55 +02:00
5c1ddffe7a refactor(playback): simplify getVideoStreamUrl usage and improve direct play handling 2026-05-29 18:09:12 +02:00
09a1bb2288 fix(profile): enable ASS direct play by default in mobile profile config 2026-05-27 20:36:04 +02:00
194052517b feat(api): enable user data retrieval and enhance logging for result sizes 2026-05-25 20:54:43 +02:00
b0ed32ef7c feat(series): improve season and episode loading handling 2026-05-25 20:40:10 +02:00
1dedc7ff0a Revert "feat(network): enhance connectivity monitoring with request reporting"
This reverts commit 56a2b42434.
2026-05-25 17:59:01 +02:00
56a2b42434 feat(network): enhance connectivity monitoring with request reporting 2026-05-20 17:07:32 +02:00
7ae0c2eb27 feat(api): increase item limit for library queries from 10 to 15 2026-05-14 21:45:06 +02:00
e09b63f47e refactor(logging): standardize logRequest function naming and remove redundant logging 2026-05-14 18:29:58 +02:00
c70657e627 feat(logging): enhance log entry handling with truncation and improve upload file naming 2026-05-14 18:23:21 +02:00
4b87f93dee Add Timber file logging and Jellyfin log upload 2026-05-14 18:06:54 +02:00
6cff6be238 feat: enhance media playback handling with offline support and download metadata integration 2026-05-14 14:38:24 +02:00
f60aba5a72 feat: update media selection logic to handle offline state in view models and DTOs 2026-05-14 12:56:48 +02:00
cc643ae34f feat: add preferenceMediaId to PlayableMedia and update related logic to make more prone to mistakes 2026-05-13 22:53:27 +02:00
7ec9a0f41f feat: implement logout functionality and settings provider 2026-05-13 22:17:50 +02:00
d3b66d4fc5 feat: redesign login screen on both app and app-tv 2026-05-13 21:59:23 +02:00
3ee4b558f5 Make media detail flows reactive 2026-05-13 16:43:42 +02:00
af2e8b0dc9 feat: enhance settings screen with dropdown and void setting options for improved user experience 2026-05-08 20:19:05 +02:00
4788eec102 feat: add SeriesIdLookUpUseCase for episode to series ID mapping and update track preference saving logic 2026-05-08 19:30:44 +02:00
5444d3c8b1 feat: refactor logging in JellyfinApiClient to improve request tracking and error handling 2026-05-08 18:53:18 +02:00
9d7c166bc8 feat: refactor SeriesScreen and ViewModel for improved season episode loading and download handling 2026-05-08 18:47:15 +02:00