feat(jellyfin): lazily page recently added hubs

close #1611
This commit is contained in:
edde746
2026-07-24 20:29:21 +02:00
parent d6b24c3e07
commit 102ef46d00
6 changed files with 225 additions and 48 deletions
+13
View File
@@ -28,6 +28,7 @@ mixin PaginatedItemLoader<T, W extends StatefulWidget> on State<W> {
final Set<int> _loadingRanges = {};
AbortController? _cancelToken;
Object? _paginationError;
/// Monotonic generation — bumped on reset/dispose so stale fetches are
/// discarded instead of mutating state from a prior load.
@@ -37,6 +38,8 @@ mixin PaginatedItemLoader<T, W extends StatefulWidget> on State<W> {
Timer? _retryTimer;
bool _visibleRangeLoading = false;
DateTime? _lastEagerPrefetch;
Object? get paginationError => _paginationError;
bool get isPaginationLoading => _loadingRanges.isNotEmpty;
/// Re-invoked by the retry timer. Most recent range-load args.
VoidCallback? _scheduledRetry;
@@ -49,6 +52,10 @@ mixin PaginatedItemLoader<T, W extends StatefulWidget> on State<W> {
/// Override for image prefetch, syncing a base-class `items` list, etc.
void onPageLoaded(int _, List<T> _) {}
/// Hook fired when a lazy page starts or finishes loading, or fails.
/// Override when the surrounding UI exposes loading or retry state.
void onPaginationStateChanged() {}
/// Synchronously clear pagination state and bump the generation counter.
/// Call from inside the subclass's `setState` before awaiting
/// [loadInitialPage]. Aborts any in-flight fetches from the previous load.
@@ -61,6 +68,7 @@ mixin PaginatedItemLoader<T, W extends StatefulWidget> on State<W> {
_visibleRangeLoading = false;
_lastEagerPrefetch = null;
_scheduledRetry = null;
_paginationError = null;
loadedItems.clear();
_loadingRanges.clear();
totalSize = 0;
@@ -265,6 +273,7 @@ mixin PaginatedItemLoader<T, W extends StatefulWidget> on State<W> {
_retryTimer?.cancel();
_retryTimer = null;
_loadingRanges.clear();
_paginationError = null;
_scheduledRetry = null;
}
@@ -276,6 +285,8 @@ mixin PaginatedItemLoader<T, W extends StatefulWidget> on State<W> {
final indices = List.generate(clampedSize, (i) => start + i);
if (indices.every((i) => _loadingRanges.contains(i) || loadedItems.containsKey(i))) return true;
_loadingRanges.addAll(indices);
_paginationError = null;
onPaginationStateChanged();
final generation = _requestId;
@@ -295,6 +306,7 @@ mixin PaginatedItemLoader<T, W extends StatefulWidget> on State<W> {
return true;
} catch (e) {
if (e is MediaServerHttpException && e.type == MediaServerHttpErrorType.cancelled) return false;
_paginationError = e;
_retryCount++;
final delay = Duration(milliseconds: 500 * (1 << _retryCount.clamp(0, 4)));
_retryTimer?.cancel();
@@ -304,6 +316,7 @@ mixin PaginatedItemLoader<T, W extends StatefulWidget> on State<W> {
return false;
} finally {
_loadingRanges.removeAll(indices);
onPaginationStateChanged();
}
}
}