1646b02ca2
The home screen renders solely from the per-item cached_home_index path (proven on devices for many releases); the legacy snapshot stack was dead weight. - library_providers: remove homeProvider + _encodeHomeData + _albumToJson/_artistToJson/_trackToJson; drop now-unused dart:convert and models/home_data.dart imports - metadata_prefetcher: re-point off homeIndexProvider/HomeIndex — pre-warm artistProvider from the rediscover/last-played artist sections (album/track tiles hydrate their own artist on render) - live_events_dispatcher: homeProvider -> homeIndexProvider so live events still refresh the home screen - db.dart: drop CachedHomeSnapshot (table class + @DriftDatabase entry); schemaVersion 10->11; from<3 createTable -> raw customStatement so the historical step compiles without the generated symbol; from<11 DROP TABLE cached_home_snapshot No test references the removed symbols. db.g.dart regenerated by CI. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
105 lines
4.1 KiB
Dart
105 lines
4.1 KiB
Dart
// Maps incoming LiveEvent kinds to provider invalidations.
|
|
//
|
|
// Activated by ref.read(liveEventsDispatcherProvider) in app.dart; once
|
|
// activated, the dispatcher listens to liveEventsProvider for the
|
|
// lifetime of the ProviderScope and invalidates the small set of
|
|
// public-scoped providers we know about.
|
|
//
|
|
// Screen-scoped providers (file-private providers in library_screen.dart,
|
|
// admin handlers, etc.) opt in to live-refresh by themselves listening
|
|
// to liveEventsProvider — the dispatcher only handles cross-screen,
|
|
// publicly-importable providers. This keeps the dispatcher small and
|
|
// avoids a back-edge dependency from /shared onto every feature folder.
|
|
//
|
|
// Includes an AppLifecycleState resume handler that defensively
|
|
// invalidates the same set on app foreground. SSE will catch up on its
|
|
// own, but on cold-start or after a long background period the
|
|
// re-invalidate is fast and avoids stale data flashing before the
|
|
// stream reconnects.
|
|
|
|
import 'package:flutter/widgets.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../library/library_providers.dart' show homeIndexProvider;
|
|
import '../quarantine/quarantine_provider.dart';
|
|
import 'live_events_provider.dart';
|
|
|
|
/// Activates the SSE → invalidation dispatcher for the lifetime of the
|
|
/// containing ProviderScope. Read this once at startup (from app.dart);
|
|
/// the provider's body runs once, sets up listeners, and returns a
|
|
/// sentinel value.
|
|
final liveEventsDispatcherProvider = Provider<_LiveEventsDispatcher>((ref) {
|
|
final d = _LiveEventsDispatcher(ref);
|
|
ref.onDispose(d.dispose);
|
|
return d;
|
|
});
|
|
|
|
class _LiveEventsDispatcher with WidgetsBindingObserver {
|
|
_LiveEventsDispatcher(this._ref) {
|
|
// Subscribe to the event stream. Each emitted event invokes
|
|
// _handle. Errors / disconnects auto-rebuild the underlying
|
|
// provider; we don't need to react to them here.
|
|
_sub = _ref.listen<AsyncValue<LiveEvent>>(
|
|
liveEventsProvider,
|
|
(_, next) => next.whenData(_handle),
|
|
fireImmediately: false,
|
|
);
|
|
WidgetsBinding.instance.addObserver(this);
|
|
}
|
|
|
|
final Ref _ref;
|
|
late final ProviderSubscription<AsyncValue<LiveEvent>> _sub;
|
|
|
|
void _handle(LiveEvent e) {
|
|
// Map event kinds to provider invalidations. Keep this list short:
|
|
// only providers reachable from /shared. Screen-private providers
|
|
// listen to liveEventsProvider themselves.
|
|
switch (e.kind) {
|
|
case 'quarantine.flagged':
|
|
case 'quarantine.unflagged':
|
|
case 'quarantine.resolved':
|
|
case 'quarantine.file_deleted':
|
|
case 'quarantine.deleted_via_lidarr':
|
|
_ref.invalidate(myQuarantineProvider);
|
|
// Hidden / liked / album rows referencing a deleted track may
|
|
// now be stale — homeIndexProvider re-fetch refreshes the cards.
|
|
_ref.invalidate(homeIndexProvider);
|
|
case 'playlist.created':
|
|
case 'playlist.updated':
|
|
case 'playlist.deleted':
|
|
case 'playlist.tracks_changed':
|
|
// Home renders a Playlists row; refresh it so a delete or add
|
|
// is reflected immediately. Detail screens that need the
|
|
// mutated row will get a separate invalidate from their own
|
|
// listener (filed as a follow-up).
|
|
_ref.invalidate(homeIndexProvider);
|
|
case 'scan.run_started':
|
|
case 'scan.run_finished':
|
|
// Admin scan card provider lives in the admin feature folder
|
|
// and is file-private today; will be invalidated by the admin
|
|
// dashboard's own listener once that exists.
|
|
break;
|
|
default:
|
|
// track.liked / track.unliked / request.status_changed reach
|
|
// screen-private providers; their screens listen directly.
|
|
break;
|
|
}
|
|
}
|
|
|
|
@override
|
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
if (state == AppLifecycleState.resumed) {
|
|
// Defensive cold-start invalidation. SSE will catch up on its
|
|
// own, but the re-invalidate flushes any stale data that
|
|
// landed while the app was backgrounded.
|
|
_ref.invalidate(myQuarantineProvider);
|
|
_ref.invalidate(homeIndexProvider);
|
|
}
|
|
}
|
|
|
|
void dispose() {
|
|
WidgetsBinding.instance.removeObserver(this);
|
|
_sub.close();
|
|
}
|
|
}
|