fix(propagation): scope events by server
This commit is contained in:
@@ -27,6 +27,17 @@ import '../utils/deletion_notifier.dart';
|
||||
mixin DeletionAware<T extends StatefulWidget> on State<T> {
|
||||
StreamSubscription<DeletionEvent>? _deletionSubscription;
|
||||
|
||||
/// Override to scope events to a specific server.
|
||||
///
|
||||
/// Return null to receive events from all servers.
|
||||
String? get deletionServerId => null;
|
||||
|
||||
/// Override to specify which global keys this screen cares about.
|
||||
///
|
||||
/// Use format `serverId:ratingKey`.
|
||||
/// Return null to fall back to [deletionRatingKeys] matching.
|
||||
Set<String>? get deletionGlobalKeys => null;
|
||||
|
||||
/// Override to specify which ratingKeys this screen cares about.
|
||||
///
|
||||
/// Return null to receive ALL events (not recommended for performance).
|
||||
@@ -52,10 +63,21 @@ mixin DeletionAware<T extends StatefulWidget> on State<T> {
|
||||
_deletionSubscription = DeletionNotifier().stream.listen((event) {
|
||||
if (!mounted) return;
|
||||
|
||||
final keys = deletionRatingKeys;
|
||||
final serverId = deletionServerId;
|
||||
if (serverId != null && event.serverId != serverId) return;
|
||||
|
||||
final globalKeys = deletionGlobalKeys;
|
||||
if (globalKeys != null) {
|
||||
if (event.affectsAnyGlobalKey(globalKeys)) {
|
||||
onDeletionEvent(event);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final ratingKeys = deletionRatingKeys;
|
||||
// If keys is null, receive all events
|
||||
// Otherwise, filter to events that affect our keys
|
||||
if (keys == null || event.affectsAnyOf(keys)) {
|
||||
if (ratingKeys == null || event.affectsAnyOf(ratingKeys)) {
|
||||
onDeletionEvent(event);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -26,6 +26,17 @@ import '../utils/watch_state_notifier.dart';
|
||||
mixin WatchStateAware<T extends StatefulWidget> on State<T> {
|
||||
StreamSubscription<WatchStateEvent>? _watchStateSubscription;
|
||||
|
||||
/// Override to scope events to a specific server.
|
||||
///
|
||||
/// Return null to receive events from all servers.
|
||||
String? get watchStateServerId => null;
|
||||
|
||||
/// Override to specify which global keys this screen cares about.
|
||||
///
|
||||
/// Use format `serverId:ratingKey`.
|
||||
/// Return null to fall back to [watchedRatingKeys] matching.
|
||||
Set<String>? get watchedGlobalKeys => null;
|
||||
|
||||
/// Override to specify which ratingKeys this screen cares about.
|
||||
///
|
||||
/// Return null to receive ALL events (not recommended for performance).
|
||||
@@ -51,6 +62,17 @@ mixin WatchStateAware<T extends StatefulWidget> on State<T> {
|
||||
_watchStateSubscription = WatchStateNotifier().stream.listen((event) {
|
||||
if (!mounted) return;
|
||||
|
||||
final serverId = watchStateServerId;
|
||||
if (serverId != null && event.serverId != serverId) return;
|
||||
|
||||
final globalKeys = watchedGlobalKeys;
|
||||
if (globalKeys != null) {
|
||||
if (event.affectsAnyGlobalKey(globalKeys)) {
|
||||
onWatchStateChanged(event);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final keys = watchedRatingKeys;
|
||||
// If keys is null, receive all events
|
||||
// Otherwise, filter to events that affect our keys
|
||||
|
||||
@@ -75,6 +75,8 @@ class _DiscoverScreenState extends State<DiscoverScreen>
|
||||
final ValueNotifier<double> _indicatorProgress = ValueNotifier(0.0);
|
||||
bool _isAutoScrollPaused = false;
|
||||
|
||||
String _toGlobalKey(String ratingKey, String serverId) => '$serverId:$ratingKey';
|
||||
|
||||
// WatchStateAware: watch on-deck items and their parent shows/seasons
|
||||
@override
|
||||
Set<String>? get watchedRatingKeys {
|
||||
@@ -91,6 +93,24 @@ class _DiscoverScreenState extends State<DiscoverScreen>
|
||||
return keys;
|
||||
}
|
||||
|
||||
@override
|
||||
Set<String>? get watchedGlobalKeys {
|
||||
final keys = <String>{};
|
||||
for (final item in _onDeck) {
|
||||
final serverId = item.serverId;
|
||||
if (serverId == null) return null;
|
||||
|
||||
keys.add(_toGlobalKey(item.ratingKey, serverId));
|
||||
if (item.parentRatingKey != null) {
|
||||
keys.add(_toGlobalKey(item.parentRatingKey!, serverId));
|
||||
}
|
||||
if (item.grandparentRatingKey != null) {
|
||||
keys.add(_toGlobalKey(item.grandparentRatingKey!, serverId));
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
@override
|
||||
void onWatchStateChanged(WatchStateEvent event) {
|
||||
// Refresh continue watching when any relevant item changes
|
||||
|
||||
@@ -59,9 +59,28 @@ class _LibraryBrowseTabState extends BaseLibraryTabState<PlexMetadata, LibraryBr
|
||||
@override
|
||||
PlexClient get client => getClientForLibrary();
|
||||
|
||||
String _toGlobalKey(String ratingKey, {String? serverId}) =>
|
||||
'${serverId ?? widget.library.serverId ?? ''}:$ratingKey';
|
||||
|
||||
@override
|
||||
String? get deletionServerId => widget.library.serverId;
|
||||
|
||||
@override
|
||||
Set<String>? get deletionRatingKeys => items.map((e) => e.ratingKey).toSet();
|
||||
|
||||
@override
|
||||
Set<String>? get deletionGlobalKeys {
|
||||
if (items.isEmpty) return <String>{};
|
||||
|
||||
final keys = <String>{};
|
||||
for (final item in items) {
|
||||
final serverId = item.serverId ?? widget.library.serverId;
|
||||
if (serverId == null) return null;
|
||||
keys.add(_toGlobalKey(item.ratingKey, serverId: serverId));
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
@override
|
||||
void onDeletionEvent(DeletionEvent event) {
|
||||
// If we have an item that matches the rating key exactly, then remove it from our list
|
||||
|
||||
@@ -76,6 +76,9 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> with WatchStateAw
|
||||
// GlobalKeys for season cards to access their context menu
|
||||
final Map<int, GlobalKey<MediaCardState>> _seasonCardKeys = {};
|
||||
|
||||
String _toGlobalKey(String ratingKey, {String? serverId}) =>
|
||||
'${serverId ?? widget.metadata.serverId ?? ''}:$ratingKey';
|
||||
|
||||
// WatchStateAware: watch the show/movie and all season ratingKeys
|
||||
@override
|
||||
Set<String>? get watchedRatingKeys {
|
||||
@@ -86,6 +89,21 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> with WatchStateAw
|
||||
return keys;
|
||||
}
|
||||
|
||||
@override
|
||||
String? get watchStateServerId => widget.metadata.serverId;
|
||||
|
||||
@override
|
||||
Set<String>? get watchedGlobalKeys {
|
||||
final serverId = widget.metadata.serverId;
|
||||
if (serverId == null) return null;
|
||||
|
||||
final keys = <String>{_toGlobalKey(widget.metadata.ratingKey, serverId: serverId)};
|
||||
for (final season in _seasons) {
|
||||
keys.add(_toGlobalKey(season.ratingKey, serverId: season.serverId ?? serverId));
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
@override
|
||||
void onWatchStateChanged(WatchStateEvent event) {
|
||||
// Lightweight refresh - no loader, preserves scroll position
|
||||
@@ -103,6 +121,21 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> with WatchStateAw
|
||||
return keys;
|
||||
}
|
||||
|
||||
@override
|
||||
String? get deletionServerId => widget.metadata.serverId;
|
||||
|
||||
@override
|
||||
Set<String>? get deletionGlobalKeys {
|
||||
final serverId = widget.metadata.serverId;
|
||||
if (serverId == null) return null;
|
||||
|
||||
final keys = <String>{_toGlobalKey(widget.metadata.ratingKey, serverId: serverId)};
|
||||
for (final season in _seasons) {
|
||||
keys.add(_toGlobalKey(season.ratingKey, serverId: season.serverId ?? serverId));
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
@override
|
||||
void onDeletionEvent(DeletionEvent event) {
|
||||
if (widget.isOffline) return;
|
||||
|
||||
@@ -55,10 +55,23 @@ class _SeasonDetailScreenState extends State<SeasonDetailScreen>
|
||||
bool _suppressNextBackKeyUp = false;
|
||||
bool _routeSubscribed = false;
|
||||
|
||||
String _toGlobalKey(String ratingKey, {String? serverId}) => '${serverId ?? widget.season.serverId ?? ''}:$ratingKey';
|
||||
|
||||
// WatchStateAware: watch all episode ratingKeys
|
||||
@override
|
||||
Set<String>? get watchedRatingKeys => _episodes.map((e) => e.ratingKey).toSet();
|
||||
|
||||
@override
|
||||
String? get watchStateServerId => widget.season.serverId;
|
||||
|
||||
@override
|
||||
Set<String>? get watchedGlobalKeys {
|
||||
final serverId = widget.season.serverId;
|
||||
if (serverId == null) return null;
|
||||
|
||||
return _episodes.map((e) => _toGlobalKey(e.ratingKey, serverId: e.serverId ?? serverId)).toSet();
|
||||
}
|
||||
|
||||
@override
|
||||
void onWatchStateChanged(WatchStateEvent event) {
|
||||
// Update the affected episode
|
||||
@@ -74,6 +87,19 @@ class _SeasonDetailScreenState extends State<SeasonDetailScreen>
|
||||
return keys;
|
||||
}
|
||||
|
||||
@override
|
||||
String? get deletionServerId => widget.season.serverId;
|
||||
|
||||
@override
|
||||
Set<String>? get deletionGlobalKeys {
|
||||
final serverId = widget.season.serverId;
|
||||
if (serverId == null) return null;
|
||||
|
||||
final keys = _episodes.map((e) => _toGlobalKey(e.ratingKey, serverId: e.serverId ?? serverId)).toSet();
|
||||
keys.add(_toGlobalKey(widget.season.ratingKey, serverId: serverId));
|
||||
return keys;
|
||||
}
|
||||
|
||||
@override
|
||||
void onDeletionEvent(DeletionEvent event) {
|
||||
// If we have an episode that matches the rating key exactly, then remove it from our list
|
||||
|
||||
@@ -44,6 +44,9 @@ class DeletionEvent {
|
||||
/// Check if this event affects any item in a collection
|
||||
bool affectsAnyOf(Iterable<String> ratingKeys) => ratingKeys.any(affectsItem);
|
||||
|
||||
/// Check if this event affects any item in a global-key collection
|
||||
bool affectsAnyGlobalKey(Iterable<String> globalKeys) => globalKeys.any(affectsGlobalKey);
|
||||
|
||||
@override
|
||||
String toString() => 'DeletionEvent(deleted: $globalKey, type: $mediaType, parents: $parentChain)';
|
||||
}
|
||||
|
||||
@@ -54,6 +54,9 @@ class WatchStateEvent {
|
||||
/// Check if this event affects any item in a collection
|
||||
bool affectsAnyOf(Iterable<String> ratingKeys) => ratingKeys.any(affectsItem);
|
||||
|
||||
/// Check if this event affects any item in a global-key collection
|
||||
bool affectsAnyGlobalKey(Iterable<String> globalKeys) => globalKeys.any(affectsGlobalKey);
|
||||
|
||||
@override
|
||||
String toString() => 'WatchStateEvent($changeType, $globalKey, parents: $parentChain)';
|
||||
}
|
||||
|
||||
@@ -1073,6 +1073,8 @@ class MediaContextMenuState extends State<MediaContextMenu> {
|
||||
showSuccessSnackBar(context, t.mediaMenu.mediaDeletedSuccessfully);
|
||||
// Broadcast deletion event for cross-screen propagation
|
||||
DeletionNotifier().notifyDeleted(metadata: metadata);
|
||||
// Backward-compatible list refresh for screens that are not DeletionAware yet
|
||||
widget.onListRefresh?.call();
|
||||
} else {
|
||||
showErrorSnackBar(context, t.mediaMenu.mediaFailedToDelete);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user