fix(ui): refresh stale watch state

This commit is contained in:
edde746
2026-05-09 06:00:36 +02:00
parent 3e6ebcdb6a
commit 7a3f63683e
21 changed files with 572 additions and 195 deletions
@@ -46,11 +46,13 @@ import '../../../services/storage_service.dart';
import '../../../services/settings_service.dart';
import '../../../mixins/grid_focus_node_mixin.dart';
import '../../../mixins/item_updatable.dart';
import '../../../mixins/watch_state_aware.dart';
import '../../../mixins/deletion_aware.dart';
import '../../../mixins/paginated_item_loader.dart';
import '../../../widgets/skeleton_media_card.dart';
import '../../../utils/deletion_notifier.dart';
import '../../../utils/global_key_utils.dart';
import '../../../utils/watch_state_notifier.dart';
import '../../../utils/platform_detector.dart';
import '../../../i18n/strings.g.dart';
import '../../main_screen.dart';
@@ -81,7 +83,13 @@ class LibraryBrowseTab extends BaseLibraryTab<MediaItem> {
}
class _LibraryBrowseTabState extends BaseLibraryTabState<MediaItem, LibraryBrowseTab>
with ItemUpdatable, LibraryTabFocusMixin, GridFocusNodeMixin, DeletionAware, PaginatedItemLoader<LibraryBrowseTab> {
with
ItemUpdatable,
LibraryTabFocusMixin,
GridFocusNodeMixin,
WatchStateAware,
DeletionAware,
PaginatedItemLoader<LibraryBrowseTab> {
@override
String? get itemServerId => widget.library.serverId;
@@ -91,6 +99,25 @@ class _LibraryBrowseTabState extends BaseLibraryTabState<MediaItem, LibraryBrows
@override
String? get deletionServerId => widget.library.serverId;
@override
String? get watchStateServerId => widget.library.serverId;
@override
Set<String>? get watchedIds => loadedItems.values.map((e) => e.id).toSet();
@override
Set<String>? get watchedGlobalKeys {
if (loadedItems.isEmpty) return <String>{};
final keys = <String>{};
for (final item in loadedItems.values) {
final serverId = item.serverId ?? widget.library.serverId;
if (serverId == null) return null;
keys.add(_toGlobalKey(item.id, serverId: serverId));
}
return keys;
}
@override
Set<String>? get deletionIds => loadedItems.values.map((e) => e.id).toSet();
@@ -107,6 +134,21 @@ class _LibraryBrowseTabState extends BaseLibraryTabState<MediaItem, LibraryBrows
return keys;
}
@override
void onWatchStateChanged(WatchStateEvent event) {
if (event.changeType == WatchStateChangeType.progressUpdate ||
event.changeType == WatchStateChangeType.removedFromContinueWatching) {
return;
}
final affectedIds = {event.itemId, ...event.parentChain};
for (final item in loadedItems.values) {
if (affectedIds.contains(item.id)) {
unawaited(updateItem(item.id));
}
}
}
@override
void onDeletionEvent(DeletionEvent event) {
// If we have an item that matches the rating key exactly, remove it and rebuild indices
@@ -154,14 +196,12 @@ class _LibraryBrowseTabState extends BaseLibraryTabState<MediaItem, LibraryBrows
@override
void updateItemInLists(String itemId, MediaItem updatedMetadata) {
setState(() {
for (final entry in loadedItems.entries) {
if (entry.value.id == itemId) {
loadedItems[entry.key] = updatedMetadata;
break;
}
for (final entry in loadedItems.entries) {
if (entry.value.id == itemId) {
loadedItems[entry.key] = updatedMetadata;
break;
}
});
}
}
// Browse-specific state (not in base class)
@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
@@ -5,7 +7,10 @@ import '../../../i18n/strings.g.dart';
import '../../../media/media_hub.dart';
import '../../../media/media_item.dart';
import '../../../mixins/item_updatable.dart';
import '../../../mixins/watch_state_aware.dart';
import '../../../utils/global_key_utils.dart';
import '../../../utils/provider_extensions.dart';
import '../../../utils/watch_state_notifier.dart';
import '../../../widgets/hub_section.dart';
import '../../main_screen.dart';
import 'base_library_tab.dart';
@@ -26,13 +31,45 @@ class LibraryRecommendedTab extends BaseLibraryTab<MediaHub> {
State<LibraryRecommendedTab> createState() => _LibraryRecommendedTabState();
}
class _LibraryRecommendedTabState extends BaseLibraryTabState<MediaHub, LibraryRecommendedTab> with ItemUpdatable {
class _LibraryRecommendedTabState extends BaseLibraryTabState<MediaHub, LibraryRecommendedTab>
with ItemUpdatable, WatchStateAware {
/// GlobalKeys for each hub section to enable vertical navigation
final List<GlobalKey<HubSectionState>> _hubKeys = [];
@override
String? get itemServerId => widget.library.serverId;
@override
String? get watchStateServerId => widget.library.serverId;
@override
Set<String>? get watchedIds {
final keys = <String>{};
for (final hub in items) {
for (final item in hub.items) {
keys.add(item.id);
if (item.parentId != null) keys.add(item.parentId!);
if (item.grandparentId != null) keys.add(item.grandparentId!);
}
}
return keys;
}
@override
Set<String>? get watchedGlobalKeys {
final keys = <String>{};
for (final hub in items) {
for (final item in hub.items) {
final serverId = item.serverId ?? widget.library.serverId;
if (serverId == null) return null;
keys.add(buildGlobalKey(serverId, item.id));
if (item.parentId != null) keys.add(buildGlobalKey(serverId, item.parentId!));
if (item.grandparentId != null) keys.add(buildGlobalKey(serverId, item.grandparentId!));
}
}
return keys;
}
@override
void updateItemInLists(String itemId, MediaItem updatedItem) {
// Update the item in any hub that contains it. MediaHub items are
@@ -48,6 +85,43 @@ class _LibraryRecommendedTabState extends BaseLibraryTabState<MediaHub, LibraryR
}
}
@override
void onWatchStateChanged(WatchStateEvent event) {
if (event.changeType == WatchStateChangeType.progressUpdate) return;
if (event.changeType == WatchStateChangeType.removedFromContinueWatching) {
_removeContinueWatchingItem(event.itemId);
unawaited(loadItems());
return;
}
final affectedIds = {event.itemId, ...event.parentChain};
final refreshIds = <String>{};
for (final hub in items) {
for (final item in hub.items) {
if (affectedIds.contains(item.id)) {
refreshIds.add(item.id);
}
}
}
for (final itemId in refreshIds) {
unawaited(updateItem(itemId));
}
}
void _removeContinueWatchingItem(String itemId) {
setState(() {
for (var i = 0; i < items.length; i++) {
final hub = items[i];
if (!_isContinueWatchingHub(hub)) continue;
final newItems = hub.items.where((item) => item.id != itemId).toList();
if (newItems.length != hub.items.length) {
items[i] = hub.copyWith(items: newItems, size: newItems.length);
}
}
});
}
@override
IconData get emptyIcon => Symbols.recommend_rounded;