570 lines
17 KiB
Dart
570 lines
17 KiB
Dart
|
|
import 'dart:async';
|
||
|
|
|
||
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
|
|
|
||
|
|
import '../core/contracts/cancellation.dart';
|
||
|
|
import '../core/contracts/library.dart';
|
||
|
|
import '../core/infra/storage/home_page_cache_store.dart';
|
||
|
|
import '../shared/utils/app_logger.dart';
|
||
|
|
import 'di_providers.dart';
|
||
|
|
import 'playback_active_provider.dart';
|
||
|
|
import 'session_provider.dart';
|
||
|
|
|
||
|
|
class LibraryOverviewState {
|
||
|
|
final List<EmbyRawLibrary> libraries;
|
||
|
|
final List<EmbyRawItem> resumeItems;
|
||
|
|
final Map<String, List<EmbyRawItem>> latestByLibrary;
|
||
|
|
final Map<String, int> totalCountByLibrary;
|
||
|
|
final Set<String> loadingLibraryIds;
|
||
|
|
final bool resumeLoading;
|
||
|
|
final Object? resumeError;
|
||
|
|
final Map<String, Object?> latestErrorByLibrary;
|
||
|
|
|
||
|
|
const LibraryOverviewState({
|
||
|
|
this.libraries = const [],
|
||
|
|
this.resumeItems = const [],
|
||
|
|
this.latestByLibrary = const {},
|
||
|
|
this.totalCountByLibrary = const {},
|
||
|
|
this.loadingLibraryIds = const {},
|
||
|
|
this.resumeLoading = false,
|
||
|
|
this.resumeError,
|
||
|
|
this.latestErrorByLibrary = const {},
|
||
|
|
});
|
||
|
|
|
||
|
|
LibraryOverviewState copyWith({
|
||
|
|
List<EmbyRawLibrary>? libraries,
|
||
|
|
List<EmbyRawItem>? resumeItems,
|
||
|
|
Map<String, List<EmbyRawItem>>? latestByLibrary,
|
||
|
|
Map<String, int>? totalCountByLibrary,
|
||
|
|
Set<String>? loadingLibraryIds,
|
||
|
|
bool? resumeLoading,
|
||
|
|
Object? resumeError,
|
||
|
|
bool clearResumeError = false,
|
||
|
|
Map<String, Object?>? latestErrorByLibrary,
|
||
|
|
}) => LibraryOverviewState(
|
||
|
|
libraries: libraries ?? this.libraries,
|
||
|
|
resumeItems: resumeItems ?? this.resumeItems,
|
||
|
|
latestByLibrary: latestByLibrary ?? this.latestByLibrary,
|
||
|
|
totalCountByLibrary: totalCountByLibrary ?? this.totalCountByLibrary,
|
||
|
|
loadingLibraryIds: loadingLibraryIds ?? this.loadingLibraryIds,
|
||
|
|
resumeLoading: resumeLoading ?? this.resumeLoading,
|
||
|
|
resumeError: clearResumeError ? null : (resumeError ?? this.resumeError),
|
||
|
|
latestErrorByLibrary: latestErrorByLibrary ?? this.latestErrorByLibrary,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
class LibraryOverviewNotifier extends AsyncNotifier<LibraryOverviewState> {
|
||
|
|
CancellationToken? _latestBatchToken;
|
||
|
|
int _latestRunId = 0;
|
||
|
|
|
||
|
|
@override
|
||
|
|
Future<LibraryOverviewState> build() async {
|
||
|
|
final session = ref.watch(activeSessionProvider);
|
||
|
|
if (session == null) return const LibraryOverviewState();
|
||
|
|
|
||
|
|
final store = await ref.read(homePageCacheStoreProvider.future);
|
||
|
|
final cache = await store.readOverview(session.serverId, session.userId);
|
||
|
|
|
||
|
|
if (cache != null) {
|
||
|
|
unawaited(_refreshFromNetwork(session.serverId, session.userId));
|
||
|
|
return LibraryOverviewState(
|
||
|
|
libraries: cache.libraries,
|
||
|
|
resumeItems: cache.resumeItems,
|
||
|
|
latestByLibrary: cache.latestByLibrary,
|
||
|
|
loadingLibraryIds: const {},
|
||
|
|
resumeLoading: false,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return _loadPrimaryAndCache(session.serverId, session.userId);
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<LibraryOverviewState> _loadPrimaryAndCache(
|
||
|
|
String serverId,
|
||
|
|
String userId,
|
||
|
|
) async {
|
||
|
|
final store = await ref.read(homePageCacheStoreProvider.future);
|
||
|
|
final viewsUc = await ref.read(libraryViewsUseCaseProvider.future);
|
||
|
|
|
||
|
|
final sw = Stopwatch()..start();
|
||
|
|
final views = await viewsUc.execute();
|
||
|
|
AppLogger.debug('prefetch', 'overview.views', {
|
||
|
|
'count': views.Items.length,
|
||
|
|
'ms': sw.elapsedMilliseconds,
|
||
|
|
});
|
||
|
|
|
||
|
|
unawaited(
|
||
|
|
store.updateOverviewFields(serverId, userId, libraries: views.Items),
|
||
|
|
);
|
||
|
|
|
||
|
|
final libraryIds = views.Items.map((lib) => lib.Id).toSet();
|
||
|
|
final initialState = LibraryOverviewState(
|
||
|
|
libraries: views.Items,
|
||
|
|
resumeItems: const [],
|
||
|
|
loadingLibraryIds: libraryIds,
|
||
|
|
resumeLoading: true,
|
||
|
|
);
|
||
|
|
|
||
|
|
unawaited(_loadResumeAndCache(serverId, userId, store));
|
||
|
|
unawaited(
|
||
|
|
_loadLatestProgressivelyAndCache(views.Items, serverId, userId, store),
|
||
|
|
);
|
||
|
|
|
||
|
|
return initialState;
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<void> _loadResumeAndCache(
|
||
|
|
String serverId,
|
||
|
|
String userId,
|
||
|
|
HomePageCacheStore store,
|
||
|
|
) async {
|
||
|
|
final sw = Stopwatch()..start();
|
||
|
|
try {
|
||
|
|
final resumeUc = await ref.read(libraryResumeUseCaseProvider.future);
|
||
|
|
final resume = await resumeUc.execute();
|
||
|
|
final current = state.value;
|
||
|
|
if (current == null) return;
|
||
|
|
AppLogger.debug('prefetch', 'overview.resume.ok', {
|
||
|
|
'count': resume.Items.length,
|
||
|
|
'ms': sw.elapsedMilliseconds,
|
||
|
|
});
|
||
|
|
state = AsyncValue.data(
|
||
|
|
current.copyWith(
|
||
|
|
resumeItems: resume.Items,
|
||
|
|
resumeLoading: false,
|
||
|
|
clearResumeError: true,
|
||
|
|
),
|
||
|
|
);
|
||
|
|
unawaited(
|
||
|
|
store.updateOverviewFields(serverId, userId, resumeItems: resume.Items),
|
||
|
|
);
|
||
|
|
} catch (e) {
|
||
|
|
final current = state.value;
|
||
|
|
if (current == null) return;
|
||
|
|
AppLogger.debug('prefetch', 'overview.resume.err', {
|
||
|
|
'error': e.toString(),
|
||
|
|
'ms': sw.elapsedMilliseconds,
|
||
|
|
});
|
||
|
|
state = AsyncValue.data(
|
||
|
|
current.copyWith(resumeLoading: false, resumeError: e),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static const _batchSize = 4;
|
||
|
|
|
||
|
|
Future<void> _loadLatestProgressivelyAndCache(
|
||
|
|
List<EmbyRawLibrary> libraries,
|
||
|
|
String serverId,
|
||
|
|
String userId,
|
||
|
|
HomePageCacheStore store,
|
||
|
|
) async {
|
||
|
|
await _runLatestBatches(
|
||
|
|
libraries,
|
||
|
|
serverId,
|
||
|
|
userId,
|
||
|
|
store,
|
||
|
|
mode: _LatestUpdateMode.progressive,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<void> _refreshFromNetwork(String serverId, String userId) async {
|
||
|
|
final store = await ref.read(homePageCacheStoreProvider.future);
|
||
|
|
|
||
|
|
try {
|
||
|
|
final viewsUc = await ref.read(libraryViewsUseCaseProvider.future);
|
||
|
|
final sw = Stopwatch()..start();
|
||
|
|
final views = await viewsUc.execute();
|
||
|
|
AppLogger.debug('prefetch', 'overview.views', {
|
||
|
|
'count': views.Items.length,
|
||
|
|
'ms': sw.elapsedMilliseconds,
|
||
|
|
});
|
||
|
|
if (!_sessionMatches(serverId, userId)) return;
|
||
|
|
final current = state.value;
|
||
|
|
if (current != null) {
|
||
|
|
state = AsyncValue.data(current.copyWith(libraries: views.Items));
|
||
|
|
}
|
||
|
|
unawaited(
|
||
|
|
store.updateOverviewFields(serverId, userId, libraries: views.Items),
|
||
|
|
);
|
||
|
|
|
||
|
|
unawaited(_refreshResumeFromNetwork(serverId, userId, store));
|
||
|
|
|
||
|
|
unawaited(
|
||
|
|
_refreshLatestFromNetwork(views.Items, serverId, userId, store),
|
||
|
|
);
|
||
|
|
} catch (_) {}
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<void> _refreshResumeFromNetwork(
|
||
|
|
String serverId,
|
||
|
|
String userId,
|
||
|
|
HomePageCacheStore store,
|
||
|
|
) async {
|
||
|
|
final sw = Stopwatch()..start();
|
||
|
|
try {
|
||
|
|
final resumeUc = await ref.read(libraryResumeUseCaseProvider.future);
|
||
|
|
final resume = await resumeUc.execute();
|
||
|
|
AppLogger.debug('prefetch', 'overview.resume.ok', {
|
||
|
|
'count': resume.Items.length,
|
||
|
|
'ms': sw.elapsedMilliseconds,
|
||
|
|
});
|
||
|
|
if (!_sessionMatches(serverId, userId)) return;
|
||
|
|
final current = state.value;
|
||
|
|
if (current == null) return;
|
||
|
|
state = AsyncValue.data(
|
||
|
|
current.copyWith(
|
||
|
|
resumeItems: resume.Items,
|
||
|
|
resumeLoading: false,
|
||
|
|
clearResumeError: true,
|
||
|
|
),
|
||
|
|
);
|
||
|
|
unawaited(
|
||
|
|
store.updateOverviewFields(serverId, userId, resumeItems: resume.Items),
|
||
|
|
);
|
||
|
|
} catch (_) {}
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<void> _refreshLatestFromNetwork(
|
||
|
|
List<EmbyRawLibrary> libraries,
|
||
|
|
String serverId,
|
||
|
|
String userId,
|
||
|
|
HomePageCacheStore store,
|
||
|
|
) async {
|
||
|
|
await _runLatestBatches(
|
||
|
|
libraries,
|
||
|
|
serverId,
|
||
|
|
userId,
|
||
|
|
store,
|
||
|
|
mode: _LatestUpdateMode.refresh,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<void> _runLatestBatches(
|
||
|
|
List<EmbyRawLibrary> libraries,
|
||
|
|
String serverId,
|
||
|
|
String userId,
|
||
|
|
HomePageCacheStore store, {
|
||
|
|
required _LatestUpdateMode mode,
|
||
|
|
}) async {
|
||
|
|
final runId = ++_latestRunId;
|
||
|
|
_latestBatchToken?.cancel('latest_replaced');
|
||
|
|
_latestBatchToken = null;
|
||
|
|
final latestUc = await ref.read(libraryLatestUseCaseProvider.future);
|
||
|
|
|
||
|
|
for (var i = 0; i < libraries.length;) {
|
||
|
|
if (runId != _latestRunId || !_sessionMatches(serverId, userId)) return;
|
||
|
|
|
||
|
|
await _waitWhileDetailActive();
|
||
|
|
if (runId != _latestRunId || !_sessionMatches(serverId, userId)) return;
|
||
|
|
|
||
|
|
final batch = libraries.sublist(
|
||
|
|
i,
|
||
|
|
(i + _batchSize).clamp(0, libraries.length),
|
||
|
|
);
|
||
|
|
final token = CancellationToken();
|
||
|
|
_latestBatchToken = token;
|
||
|
|
final sub = ref.listen<bool>(detailPageActiveProvider, (_, active) {
|
||
|
|
if (active) token.cancel('detail_active');
|
||
|
|
});
|
||
|
|
|
||
|
|
try {
|
||
|
|
if (ref.read(detailPageActiveProvider)) {
|
||
|
|
token.cancel('detail_active');
|
||
|
|
}
|
||
|
|
token.throwIfCancelled();
|
||
|
|
final results = await _fetchLatestBatch(batch, latestUc.execute, token);
|
||
|
|
token.throwIfCancelled();
|
||
|
|
|
||
|
|
if (runId != _latestRunId || !_sessionMatches(serverId, userId)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
_applyLatestResults(results, serverId, userId, store, mode: mode);
|
||
|
|
i += _batchSize;
|
||
|
|
} on CancelledException catch (e) {
|
||
|
|
if (runId != _latestRunId) return;
|
||
|
|
AppLogger.debug('prefetch', 'overview.latest.cancel', {
|
||
|
|
'index': i,
|
||
|
|
'reason': e.reason ?? token.reason ?? 'cancelled',
|
||
|
|
});
|
||
|
|
} finally {
|
||
|
|
sub.close();
|
||
|
|
if (identical(_latestBatchToken, token)) {
|
||
|
|
_latestBatchToken = null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<List<_LatestResult>> _fetchLatestBatch(
|
||
|
|
List<EmbyRawLibrary> batch,
|
||
|
|
Future<LibraryLatestRes> Function(
|
||
|
|
LibraryLatestReq input, {
|
||
|
|
CancellationToken? cancelToken,
|
||
|
|
})
|
||
|
|
execute,
|
||
|
|
CancellationToken cancelToken,
|
||
|
|
) async {
|
||
|
|
final results = <_LatestResult>[];
|
||
|
|
final futures = batch.map((lib) async {
|
||
|
|
cancelToken.throwIfCancelled();
|
||
|
|
final sw = Stopwatch()..start();
|
||
|
|
List<EmbyRawItem> items = const [];
|
||
|
|
int totalCount = 0;
|
||
|
|
Object? err;
|
||
|
|
try {
|
||
|
|
final res = await execute(
|
||
|
|
LibraryLatestReq(parentId: lib.Id, limit: 16),
|
||
|
|
cancelToken: cancelToken,
|
||
|
|
);
|
||
|
|
cancelToken.throwIfCancelled();
|
||
|
|
items = res.items;
|
||
|
|
totalCount = res.totalCount;
|
||
|
|
} on CancelledException {
|
||
|
|
rethrow;
|
||
|
|
} catch (e) {
|
||
|
|
err = e;
|
||
|
|
}
|
||
|
|
AppLogger.debug('prefetch',
|
||
|
|
err == null ? 'overview.latest.ok' : 'overview.latest.err',
|
||
|
|
{
|
||
|
|
'lib': lib.Name,
|
||
|
|
'count': items.length,
|
||
|
|
if (err != null) 'error': err.toString(),
|
||
|
|
'ms': sw.elapsedMilliseconds,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
results.add(
|
||
|
|
_LatestResult(
|
||
|
|
libraryId: lib.Id,
|
||
|
|
items: items,
|
||
|
|
totalCount: totalCount,
|
||
|
|
error: err,
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}).toList();
|
||
|
|
await Future.wait(futures);
|
||
|
|
return results;
|
||
|
|
}
|
||
|
|
|
||
|
|
void _applyLatestResults(
|
||
|
|
List<_LatestResult> results,
|
||
|
|
String serverId,
|
||
|
|
String userId,
|
||
|
|
HomePageCacheStore store, {
|
||
|
|
required _LatestUpdateMode mode,
|
||
|
|
}) {
|
||
|
|
final current = state.value;
|
||
|
|
if (current == null) return;
|
||
|
|
|
||
|
|
final newMap = Map<String, List<EmbyRawItem>>.from(current.latestByLibrary);
|
||
|
|
final newCounts = Map<String, int>.from(current.totalCountByLibrary);
|
||
|
|
var hasSuccess = false;
|
||
|
|
|
||
|
|
if (mode == _LatestUpdateMode.refresh) {
|
||
|
|
final newLoading = Set<String>.from(current.loadingLibraryIds);
|
||
|
|
for (final r in results) {
|
||
|
|
if (r.error != null) continue;
|
||
|
|
newMap[r.libraryId] = r.items;
|
||
|
|
if (r.totalCount > 0) newCounts[r.libraryId] = r.totalCount;
|
||
|
|
newLoading.remove(r.libraryId);
|
||
|
|
hasSuccess = true;
|
||
|
|
}
|
||
|
|
state = AsyncValue.data(
|
||
|
|
current.copyWith(
|
||
|
|
latestByLibrary: newMap,
|
||
|
|
totalCountByLibrary: newCounts,
|
||
|
|
loadingLibraryIds: newLoading,
|
||
|
|
),
|
||
|
|
);
|
||
|
|
if (hasSuccess) {
|
||
|
|
unawaited(
|
||
|
|
store.updateOverviewFields(
|
||
|
|
serverId,
|
||
|
|
userId,
|
||
|
|
latestByLibrary: Map<String, List<EmbyRawItem>>.from(newMap),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
final newLoading = Set<String>.from(current.loadingLibraryIds);
|
||
|
|
final newErrors = Map<String, Object?>.from(current.latestErrorByLibrary);
|
||
|
|
|
||
|
|
for (final r in results) {
|
||
|
|
newMap[r.libraryId] = r.items;
|
||
|
|
if (r.totalCount > 0) newCounts[r.libraryId] = r.totalCount;
|
||
|
|
newLoading.remove(r.libraryId);
|
||
|
|
if (r.error != null) {
|
||
|
|
newErrors[r.libraryId] = r.error;
|
||
|
|
} else {
|
||
|
|
newErrors.remove(r.libraryId);
|
||
|
|
hasSuccess = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
state = AsyncValue.data(
|
||
|
|
current.copyWith(
|
||
|
|
latestByLibrary: newMap,
|
||
|
|
totalCountByLibrary: newCounts,
|
||
|
|
loadingLibraryIds: newLoading,
|
||
|
|
latestErrorByLibrary: newErrors,
|
||
|
|
),
|
||
|
|
);
|
||
|
|
|
||
|
|
if (hasSuccess) {
|
||
|
|
unawaited(
|
||
|
|
store.updateOverviewFields(
|
||
|
|
serverId,
|
||
|
|
userId,
|
||
|
|
latestByLibrary: Map<String, List<EmbyRawItem>>.from(newMap),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<void> _waitWhileDetailActive() async {
|
||
|
|
if (!ref.read(detailPageActiveProvider)) return;
|
||
|
|
final completer = Completer<void>();
|
||
|
|
final sub = ref.listen<bool>(detailPageActiveProvider, (_, active) {
|
||
|
|
if (!active && !completer.isCompleted) {
|
||
|
|
completer.complete();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
await completer.future;
|
||
|
|
sub.close();
|
||
|
|
}
|
||
|
|
|
||
|
|
bool _sessionMatches(String serverId, String userId) {
|
||
|
|
final s = ref.read(activeSessionProvider);
|
||
|
|
return s != null && s.serverId == serverId && s.userId == userId;
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<void> refresh() async {
|
||
|
|
state = const AsyncValue.loading();
|
||
|
|
state = await AsyncValue.guard(() async {
|
||
|
|
final session = ref.read(activeSessionProvider);
|
||
|
|
if (session == null) return const LibraryOverviewState();
|
||
|
|
return _loadPrimaryAndCache(session.serverId, session.userId);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
Future<void> softRefresh() async {
|
||
|
|
final session = ref.read(activeSessionProvider);
|
||
|
|
if (session == null) return;
|
||
|
|
if (state.value == null) {
|
||
|
|
await refresh();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
await _refreshFromNetwork(session.serverId, session.userId);
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<void> retryResume() async {
|
||
|
|
final session = ref.read(activeSessionProvider);
|
||
|
|
if (session == null) return;
|
||
|
|
final store = await ref.read(homePageCacheStoreProvider.future);
|
||
|
|
final current = state.value;
|
||
|
|
if (current == null) return;
|
||
|
|
state = AsyncValue.data(
|
||
|
|
current.copyWith(resumeLoading: true, clearResumeError: true),
|
||
|
|
);
|
||
|
|
await _loadResumeAndCache(session.serverId, session.userId, store);
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<void> retryLatest(String libraryId) async {
|
||
|
|
final session = ref.read(activeSessionProvider);
|
||
|
|
if (session == null) return;
|
||
|
|
final store = await ref.read(homePageCacheStoreProvider.future);
|
||
|
|
final current = state.value;
|
||
|
|
if (current == null) return;
|
||
|
|
|
||
|
|
final newLoading = Set<String>.from(current.loadingLibraryIds)
|
||
|
|
..add(libraryId);
|
||
|
|
final clearedErrors = Map<String, Object?>.from(
|
||
|
|
current.latestErrorByLibrary,
|
||
|
|
)..remove(libraryId);
|
||
|
|
state = AsyncValue.data(
|
||
|
|
current.copyWith(
|
||
|
|
loadingLibraryIds: newLoading,
|
||
|
|
latestErrorByLibrary: clearedErrors,
|
||
|
|
),
|
||
|
|
);
|
||
|
|
|
||
|
|
final uc = await ref.read(libraryLatestUseCaseProvider.future);
|
||
|
|
final sw = Stopwatch()..start();
|
||
|
|
try {
|
||
|
|
final res = await uc.execute(
|
||
|
|
LibraryLatestReq(parentId: libraryId, limit: 16),
|
||
|
|
);
|
||
|
|
AppLogger.debug('prefetch', 'overview.latest.retry.ok', {
|
||
|
|
'lib': libraryId,
|
||
|
|
'count': res.items.length,
|
||
|
|
'ms': sw.elapsedMilliseconds,
|
||
|
|
});
|
||
|
|
final cur = state.value;
|
||
|
|
if (cur == null) return;
|
||
|
|
final newMap = Map<String, List<EmbyRawItem>>.from(cur.latestByLibrary);
|
||
|
|
newMap[libraryId] = res.items;
|
||
|
|
final newCounts = Map<String, int>.from(cur.totalCountByLibrary);
|
||
|
|
if (res.totalCount > 0) newCounts[libraryId] = res.totalCount;
|
||
|
|
final stillLoading = Set<String>.from(cur.loadingLibraryIds)
|
||
|
|
..remove(libraryId);
|
||
|
|
state = AsyncValue.data(
|
||
|
|
cur.copyWith(
|
||
|
|
latestByLibrary: newMap,
|
||
|
|
totalCountByLibrary: newCounts,
|
||
|
|
loadingLibraryIds: stillLoading,
|
||
|
|
),
|
||
|
|
);
|
||
|
|
unawaited(
|
||
|
|
store.updateOverviewFields(
|
||
|
|
session.serverId,
|
||
|
|
session.userId,
|
||
|
|
latestByLibrary: Map<String, List<EmbyRawItem>>.from(newMap),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
} catch (e) {
|
||
|
|
AppLogger.debug('prefetch', 'overview.latest.retry.err', {
|
||
|
|
'lib': libraryId,
|
||
|
|
'error': e.toString(),
|
||
|
|
'ms': sw.elapsedMilliseconds,
|
||
|
|
});
|
||
|
|
final cur = state.value;
|
||
|
|
if (cur == null) return;
|
||
|
|
final stillLoading = Set<String>.from(cur.loadingLibraryIds)
|
||
|
|
..remove(libraryId);
|
||
|
|
final errs = Map<String, Object?>.from(cur.latestErrorByLibrary);
|
||
|
|
errs[libraryId] = e;
|
||
|
|
state = AsyncValue.data(
|
||
|
|
cur.copyWith(
|
||
|
|
loadingLibraryIds: stillLoading,
|
||
|
|
latestErrorByLibrary: errs,
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
final libraryOverviewProvider =
|
||
|
|
AsyncNotifierProvider<LibraryOverviewNotifier, LibraryOverviewState>(
|
||
|
|
LibraryOverviewNotifier.new,
|
||
|
|
);
|
||
|
|
|
||
|
|
enum _LatestUpdateMode { progressive, refresh }
|
||
|
|
|
||
|
|
class _LatestResult {
|
||
|
|
final String libraryId;
|
||
|
|
final List<EmbyRawItem> items;
|
||
|
|
final int totalCount;
|
||
|
|
final Object? error;
|
||
|
|
|
||
|
|
const _LatestResult({
|
||
|
|
required this.libraryId,
|
||
|
|
required this.items,
|
||
|
|
required this.totalCount,
|
||
|
|
this.error,
|
||
|
|
});
|
||
|
|
}
|