feat: jellyfin

This commit is contained in:
edde746
2026-05-01 01:20:36 +02:00
parent 4080c812d2
commit 31d2d9dc98
408 changed files with 56022 additions and 13747 deletions
+13 -13
View File
@@ -2,10 +2,10 @@ import 'dart:async';
import 'package:flutter/widgets.dart';
import '../models/plex_metadata.dart';
import '../services/plex_client.dart';
import '../utils/plex_http_client.dart';
import '../utils/plex_http_exception.dart';
import '../media/library_query.dart';
import '../media/media_item.dart';
import '../utils/media_server_http_client.dart';
import '../exceptions/media_server_exceptions.dart';
/// Sparse-loading state + fetch orchestration for paginated item grids/lists.
///
@@ -22,7 +22,7 @@ import '../utils/plex_http_exception.dart';
/// 3. On dispose, subclass calls [disposePagination].
mixin PaginatedItemLoader<W extends StatefulWidget> on State<W> {
/// Sparse map of loaded items, keyed by position.
final Map<int, PlexMetadata> loadedItems = {};
final Map<int, MediaItem> loadedItems = {};
/// Total items on the server. 0 until the first page completes.
int totalSize = 0;
@@ -43,12 +43,12 @@ mixin PaginatedItemLoader<W extends StatefulWidget> on State<W> {
VoidCallback? _scheduledRetry;
/// Fetch a page of items. Subclass implements this — typically delegating
/// to a paginated `PlexClient` method that returns a [LibraryContentResult].
Future<LibraryContentResult> fetchPage(int start, int size, AbortController? abort);
/// to a paginated client method that returns a [LibraryPage] of [MediaItem].
Future<LibraryPage<MediaItem>> fetchPage(int start, int size, AbortController? abort);
/// Hook fired after each successful page merge. Default: no-op.
/// Override for image prefetch, syncing a base-class `items` list, etc.
void onPageLoaded(int start, List<PlexMetadata> items) {}
void onPageLoaded(int start, List<MediaItem> items) {}
/// Synchronously clear pagination state and bump the generation counter.
/// Call from inside the subclass's `setState` before awaiting
@@ -69,7 +69,7 @@ mixin PaginatedItemLoader<W extends StatefulWidget> on State<W> {
/// Fetch the first page. Await from outside `setState`. Mutates
/// [loadedItems] and [totalSize] on success; throws on failure.
Future<LibraryContentResult> loadInitialPage(int pageSize) async {
Future<LibraryPage<MediaItem>> loadInitialPage(int pageSize) async {
final generation = _requestId;
final result = await fetchPage(0, pageSize, _cancelToken);
if (generation != _requestId || !mounted) return result;
@@ -77,7 +77,7 @@ mixin PaginatedItemLoader<W extends StatefulWidget> on State<W> {
for (var i = 0; i < result.items.length; i++) {
loadedItems[i] = result.items[i];
}
totalSize = result.totalSize;
totalSize = result.totalCount;
onPageLoaded(0, result.items);
return result;
}
@@ -160,7 +160,7 @@ mixin PaginatedItemLoader<W extends StatefulWidget> on State<W> {
/// [totalSize] even if [index] wasn't in the sparse map (evicted).
void removeLoadedItemAndShift(int index) {
loadedItems.remove(index);
final shifted = <int, PlexMetadata>{};
final shifted = <int, MediaItem>{};
for (final entry in loadedItems.entries) {
if (entry.key > index) {
shifted[entry.key - 1] = entry.value;
@@ -229,14 +229,14 @@ mixin PaginatedItemLoader<W extends StatefulWidget> on State<W> {
for (var i = 0; i < result.items.length; i++) {
loadedItems[start + i] = result.items[i];
}
if (result.totalSize != totalSize) totalSize = result.totalSize;
if (result.totalCount != totalSize) totalSize = result.totalCount;
});
_retryCount = 0;
onPageLoaded(start, result.items);
return true;
} catch (e) {
if (e is PlexHttpException && e.type == PlexHttpErrorType.cancelled) return false;
if (e is MediaServerHttpException && e.type == MediaServerHttpErrorType.cancelled) return false;
_retryCount++;
final delay = Duration(milliseconds: 500 * (1 << _retryCount.clamp(0, 4)));
_retryTimer?.cancel();