feat: reactive watch state updates
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../utils/watch_state_notifier.dart';
|
||||
|
||||
/// Mixin for screens that need to react to watch state changes.
|
||||
///
|
||||
/// Provides automatic subscription management and filtering based on
|
||||
/// which items the screen cares about.
|
||||
///
|
||||
/// Example usage:
|
||||
/// ```dart
|
||||
/// class _MyScreenState extends State<MyScreen> with WatchStateAware {
|
||||
/// List<PlexMetadata> _items = [];
|
||||
///
|
||||
/// @override
|
||||
/// Set<String>? get watchedRatingKeys =>
|
||||
/// _items.map((e) => e.ratingKey).toSet();
|
||||
///
|
||||
/// @override
|
||||
/// void onWatchStateChanged(WatchStateEvent event) {
|
||||
/// // Refresh affected item
|
||||
/// _refreshItem(event.ratingKey);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
mixin WatchStateAware<T extends StatefulWidget> on State<T> {
|
||||
StreamSubscription<WatchStateEvent>? _watchStateSubscription;
|
||||
|
||||
/// Override to specify which ratingKeys this screen cares about.
|
||||
///
|
||||
/// Return null to receive ALL events (not recommended for performance).
|
||||
/// Return an empty set to receive no events.
|
||||
///
|
||||
/// The set should include:
|
||||
/// - Direct items displayed (e.g., episode ratingKeys in a season view)
|
||||
/// - Parent items that affect display (e.g., show ratingKey for on-deck)
|
||||
Set<String>? get watchedRatingKeys;
|
||||
|
||||
/// Called when a relevant watch state change occurs.
|
||||
///
|
||||
/// Only called if [watchedRatingKeys] is null or contains an affected key.
|
||||
void onWatchStateChanged(WatchStateEvent event);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_subscribeToWatchState();
|
||||
}
|
||||
|
||||
void _subscribeToWatchState() {
|
||||
_watchStateSubscription = WatchStateNotifier().stream.listen((event) {
|
||||
if (!mounted) return;
|
||||
|
||||
final keys = watchedRatingKeys;
|
||||
// If keys is null, receive all events
|
||||
// Otherwise, filter to events that affect our keys
|
||||
if (keys == null || event.affectsAnyOf(keys)) {
|
||||
onWatchStateChanged(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_watchStateSubscription?.cancel();
|
||||
_watchStateSubscription = null;
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart';
|
||||
import '../models/plex_metadata.dart';
|
||||
import '../services/offline_watch_sync_service.dart';
|
||||
import '../services/plex_api_cache.dart';
|
||||
import '../utils/watch_state_notifier.dart';
|
||||
import 'download_provider.dart';
|
||||
|
||||
/// Provider for offline watch status UI state.
|
||||
@@ -168,17 +169,57 @@ class OfflineWatchProvider extends ChangeNotifier {
|
||||
|
||||
/// Mark an item as watched while offline.
|
||||
///
|
||||
/// This queues the action for sync when online.
|
||||
/// This queues the action for sync when online and emits a [WatchStateEvent].
|
||||
Future<void> markAsWatched({required String serverId, required String ratingKey}) async {
|
||||
await _syncService.queueMarkWatched(serverId: serverId, ratingKey: ratingKey);
|
||||
|
||||
// Emit event for immediate UI update
|
||||
final globalKey = '$serverId:$ratingKey';
|
||||
final metadata = _downloadProvider.getMetadata(globalKey);
|
||||
if (metadata != null) {
|
||||
WatchStateNotifier().notifyWatched(metadata: metadata, isNowWatched: true);
|
||||
} else {
|
||||
// Fallback: emit minimal event without parent chain
|
||||
WatchStateNotifier().notify(
|
||||
WatchStateEvent(
|
||||
ratingKey: ratingKey,
|
||||
serverId: serverId,
|
||||
changeType: WatchStateChangeType.watched,
|
||||
parentChain: [],
|
||||
mediaType: 'unknown',
|
||||
isNowWatched: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Mark an item as unwatched while offline.
|
||||
///
|
||||
/// This queues the action for sync when online.
|
||||
/// This queues the action for sync when online and emits a [WatchStateEvent].
|
||||
Future<void> markAsUnwatched({required String serverId, required String ratingKey}) async {
|
||||
await _syncService.queueMarkUnwatched(serverId: serverId, ratingKey: ratingKey);
|
||||
|
||||
// Emit event for immediate UI update
|
||||
final globalKey = '$serverId:$ratingKey';
|
||||
final metadata = _downloadProvider.getMetadata(globalKey);
|
||||
if (metadata != null) {
|
||||
WatchStateNotifier().notifyWatched(metadata: metadata, isNowWatched: false);
|
||||
} else {
|
||||
// Fallback: emit minimal event without parent chain
|
||||
WatchStateNotifier().notify(
|
||||
WatchStateEvent(
|
||||
ratingKey: ratingKey,
|
||||
serverId: serverId,
|
||||
changeType: WatchStateChangeType.unwatched,
|
||||
parentChain: [],
|
||||
mediaType: 'unknown',
|
||||
isNowWatched: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ import '../providers/settings_provider.dart';
|
||||
import '../mixins/refreshable.dart';
|
||||
import '../i18n/strings.g.dart';
|
||||
import '../mixins/item_updatable.dart';
|
||||
import '../mixins/watch_state_aware.dart';
|
||||
import '../utils/watch_state_notifier.dart';
|
||||
import '../utils/app_logger.dart';
|
||||
import '../utils/provider_extensions.dart';
|
||||
import '../utils/video_player_navigation.dart';
|
||||
@@ -42,7 +44,7 @@ class DiscoverScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DiscoverScreenState extends State<DiscoverScreen>
|
||||
with Refreshable, FullRefreshable, ItemUpdatable, SingleTickerProviderStateMixin {
|
||||
with Refreshable, FullRefreshable, ItemUpdatable, WatchStateAware, SingleTickerProviderStateMixin {
|
||||
static const Duration _heroAutoScrollDuration = Duration(seconds: 8);
|
||||
|
||||
@override
|
||||
@@ -66,6 +68,28 @@ class _DiscoverScreenState extends State<DiscoverScreen>
|
||||
late AnimationController _indicatorAnimationController;
|
||||
bool _isAutoScrollPaused = false;
|
||||
|
||||
// WatchStateAware: watch on-deck items and their parent shows/seasons
|
||||
@override
|
||||
Set<String>? get watchedRatingKeys {
|
||||
final keys = <String>{};
|
||||
for (final item in _onDeck) {
|
||||
keys.add(item.ratingKey);
|
||||
if (item.parentRatingKey != null) {
|
||||
keys.add(item.parentRatingKey!);
|
||||
}
|
||||
if (item.grandparentRatingKey != null) {
|
||||
keys.add(item.grandparentRatingKey!);
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
@override
|
||||
void onWatchStateChanged(WatchStateEvent event) {
|
||||
// Refresh continue watching when any relevant item changes
|
||||
_refreshContinueWatching();
|
||||
}
|
||||
|
||||
// Hub navigation keys
|
||||
GlobalKey<HubSectionState>? _continueWatchingHubKey;
|
||||
final List<GlobalKey<HubSectionState>> _hubKeys = [];
|
||||
|
||||
@@ -30,6 +30,8 @@ import '../widgets/horizontal_scroll_with_arrows.dart';
|
||||
import '../widgets/focusable_media_card.dart';
|
||||
import '../widgets/media_context_menu.dart';
|
||||
import '../widgets/placeholder_container.dart';
|
||||
import '../mixins/watch_state_aware.dart';
|
||||
import '../utils/watch_state_notifier.dart';
|
||||
import 'season_detail_screen.dart';
|
||||
|
||||
class MediaDetailScreen extends StatefulWidget {
|
||||
@@ -42,7 +44,7 @@ class MediaDetailScreen extends StatefulWidget {
|
||||
State<MediaDetailScreen> createState() => _MediaDetailScreenState();
|
||||
}
|
||||
|
||||
class _MediaDetailScreenState extends State<MediaDetailScreen> {
|
||||
class _MediaDetailScreenState extends State<MediaDetailScreen> with WatchStateAware {
|
||||
List<PlexMetadata> _seasons = [];
|
||||
bool _isLoadingSeasons = false;
|
||||
PlexMetadata? _fullMetadata;
|
||||
@@ -53,6 +55,67 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
|
||||
bool _watchStateChanged = false;
|
||||
double _scrollOffset = 0;
|
||||
|
||||
// WatchStateAware: watch the show/movie and all season ratingKeys
|
||||
@override
|
||||
Set<String>? get watchedRatingKeys {
|
||||
final keys = <String>{widget.metadata.ratingKey};
|
||||
for (final season in _seasons) {
|
||||
keys.add(season.ratingKey);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
@override
|
||||
void onWatchStateChanged(WatchStateEvent event) {
|
||||
// Lightweight refresh - no loader, preserves scroll position
|
||||
if (!widget.isOffline) {
|
||||
_refreshWatchState();
|
||||
}
|
||||
}
|
||||
|
||||
/// Lightweight refresh for watch state changes - no loader, preserves scroll
|
||||
Future<void> _refreshWatchState() async {
|
||||
final client = _getClientForMetadata(context);
|
||||
if (client == null) return;
|
||||
|
||||
try {
|
||||
// Fetch updated metadata + on-deck without showing loader
|
||||
final result = await client.getMetadataWithImagesAndOnDeck(widget.metadata.ratingKey);
|
||||
final metadata = result['metadata'] as PlexMetadata?;
|
||||
final onDeckEpisode = result['onDeckEpisode'] as PlexMetadata?;
|
||||
|
||||
if (metadata != null && mounted) {
|
||||
setState(() {
|
||||
_fullMetadata = metadata.copyWith(
|
||||
serverId: widget.metadata.serverId,
|
||||
serverName: widget.metadata.serverName,
|
||||
);
|
||||
_onDeckEpisode = onDeckEpisode?.copyWith(
|
||||
serverId: widget.metadata.serverId,
|
||||
serverName: widget.metadata.serverName,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Refresh seasons for updated watched counts (also without loader)
|
||||
if (widget.metadata.isShow) {
|
||||
final seasons = await client.getChildren(widget.metadata.ratingKey);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_seasons = seasons
|
||||
.map((s) => s.copyWith(
|
||||
serverId: widget.metadata.serverId,
|
||||
serverName: widget.metadata.serverName,
|
||||
))
|
||||
.toList();
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Silently fail - data will refresh on next navigation
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
@@ -19,6 +19,8 @@ import '../widgets/desktop_app_bar.dart';
|
||||
import '../widgets/media_context_menu.dart';
|
||||
import '../widgets/placeholder_container.dart';
|
||||
import '../mixins/item_updatable.dart';
|
||||
import '../mixins/watch_state_aware.dart';
|
||||
import '../utils/watch_state_notifier.dart';
|
||||
import '../theme/mono_tokens.dart';
|
||||
import '../i18n/strings.g.dart';
|
||||
|
||||
@@ -32,7 +34,7 @@ class SeasonDetailScreen extends StatefulWidget {
|
||||
State<SeasonDetailScreen> createState() => _SeasonDetailScreenState();
|
||||
}
|
||||
|
||||
class _SeasonDetailScreenState extends State<SeasonDetailScreen> with ItemUpdatable {
|
||||
class _SeasonDetailScreenState extends State<SeasonDetailScreen> with ItemUpdatable, WatchStateAware {
|
||||
PlexClient? _client;
|
||||
|
||||
@override
|
||||
@@ -44,6 +46,18 @@ class _SeasonDetailScreenState extends State<SeasonDetailScreen> with ItemUpdata
|
||||
// Capture keyboard mode once at init to avoid rebuild dependency
|
||||
bool _initialKeyboardMode = false;
|
||||
|
||||
// WatchStateAware: watch all episode ratingKeys
|
||||
@override
|
||||
Set<String>? get watchedRatingKeys => _episodes.map((e) => e.ratingKey).toSet();
|
||||
|
||||
@override
|
||||
void onWatchStateChanged(WatchStateEvent event) {
|
||||
// Update the affected episode
|
||||
if (!widget.isOffline && _client != null) {
|
||||
updateItem(event.ratingKey);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the correct PlexClient for this season's server
|
||||
PlexClient? _getClientForSeason(BuildContext context) {
|
||||
if (widget.isOffline || widget.season.serverId == null) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'plex_client.dart';
|
||||
import 'offline_watch_sync_service.dart';
|
||||
import '../models/plex_metadata.dart';
|
||||
import '../utils/app_logger.dart';
|
||||
import '../utils/watch_state_notifier.dart';
|
||||
|
||||
/// Tracks playback progress and reports it to the Plex server.
|
||||
///
|
||||
@@ -103,6 +104,15 @@ class PlaybackProgressTracker {
|
||||
// Send progress to server immediately
|
||||
await _sendOnlineProgress(state, position, duration);
|
||||
}
|
||||
|
||||
// Emit watch state event on stop for UI updates across screens
|
||||
if (state == 'stopped' && position.inMilliseconds > 0) {
|
||||
WatchStateNotifier().notifyProgress(
|
||||
metadata: metadata,
|
||||
viewOffset: position.inMilliseconds,
|
||||
duration: duration.inMilliseconds,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
appLogger.d('Failed to send progress update (non-critical)', error: e);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import '../utils/app_logger.dart';
|
||||
import '../utils/log_redaction_manager.dart';
|
||||
import '../utils/plex_cache_parser.dart';
|
||||
import '../utils/plex_url_helper.dart';
|
||||
import '../utils/watch_state_notifier.dart';
|
||||
import 'plex_api_cache.dart';
|
||||
|
||||
/// Constants for Plex stream types
|
||||
@@ -1123,13 +1124,23 @@ class PlexClient {
|
||||
}
|
||||
|
||||
/// Mark media as watched
|
||||
Future<void> markAsWatched(String ratingKey) async {
|
||||
///
|
||||
/// If [metadata] is provided, emits a [WatchStateEvent] for UI updates.
|
||||
Future<void> markAsWatched(String ratingKey, {PlexMetadata? metadata}) async {
|
||||
await _dio.get('/:/scrobble', queryParameters: {'key': ratingKey, 'identifier': 'com.plexapp.plugins.library'});
|
||||
if (metadata != null) {
|
||||
WatchStateNotifier().notifyWatched(metadata: metadata, isNowWatched: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark media as unwatched
|
||||
Future<void> markAsUnwatched(String ratingKey) async {
|
||||
///
|
||||
/// If [metadata] is provided, emits a [WatchStateEvent] for UI updates.
|
||||
Future<void> markAsUnwatched(String ratingKey, {PlexMetadata? metadata}) async {
|
||||
await _dio.get('/:/unscrobble', queryParameters: {'key': ratingKey, 'identifier': 'com.plexapp.plugins.library'});
|
||||
if (metadata != null) {
|
||||
WatchStateNotifier().notifyWatched(metadata: metadata, isNowWatched: false);
|
||||
}
|
||||
}
|
||||
|
||||
/// Update playback progress
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
import 'dart:async';
|
||||
import '../models/plex_metadata.dart';
|
||||
import 'app_logger.dart';
|
||||
|
||||
/// Types of watch state changes
|
||||
enum WatchStateChangeType { watched, unwatched, progressUpdate }
|
||||
|
||||
/// Event representing a watch state change with parent chain for hierarchical invalidation
|
||||
class WatchStateEvent {
|
||||
/// The item that changed
|
||||
final String ratingKey;
|
||||
|
||||
/// Composite key: serverId:ratingKey
|
||||
final String globalKey;
|
||||
|
||||
/// Server this item belongs to
|
||||
final String serverId;
|
||||
|
||||
/// Type of change
|
||||
final WatchStateChangeType changeType;
|
||||
|
||||
/// Parent chain for hierarchical invalidation
|
||||
/// For an episode: [seasonRatingKey, showRatingKey]
|
||||
/// For a season: [showRatingKey]
|
||||
/// For a movie: []
|
||||
final List<String> parentChain;
|
||||
|
||||
/// Media type that changed
|
||||
final String mediaType;
|
||||
|
||||
/// New progress value (for progressUpdate)
|
||||
final int? viewOffset;
|
||||
|
||||
/// Whether item is now considered watched (>90% progress or marked)
|
||||
final bool? isNowWatched;
|
||||
|
||||
WatchStateEvent({
|
||||
required this.ratingKey,
|
||||
required this.serverId,
|
||||
required this.changeType,
|
||||
required this.parentChain,
|
||||
required this.mediaType,
|
||||
this.viewOffset,
|
||||
this.isNowWatched,
|
||||
}) : globalKey = '$serverId:$ratingKey';
|
||||
|
||||
/// Check if this event affects a specific item by ratingKey
|
||||
bool affectsItem(String ratingKey) => this.ratingKey == ratingKey || parentChain.contains(ratingKey);
|
||||
|
||||
/// Check if this event affects a specific globalKey
|
||||
bool affectsGlobalKey(String globalKey) =>
|
||||
this.globalKey == globalKey || parentChain.any((pk) => '$serverId:$pk' == globalKey);
|
||||
|
||||
/// Check if this event affects any item in a collection
|
||||
bool affectsAnyOf(Iterable<String> ratingKeys) => ratingKeys.any(affectsItem);
|
||||
|
||||
@override
|
||||
String toString() => 'WatchStateEvent($changeType, $globalKey, parents: $parentChain)';
|
||||
}
|
||||
|
||||
/// Notifier for watch state changes across the app.
|
||||
///
|
||||
/// Singleton pattern following [LibraryRefreshNotifier]. Screens subscribe
|
||||
/// to receive events when items are marked watched/unwatched or progress updates.
|
||||
class WatchStateNotifier {
|
||||
static final WatchStateNotifier _instance = WatchStateNotifier._internal();
|
||||
|
||||
factory WatchStateNotifier() => _instance;
|
||||
|
||||
WatchStateNotifier._internal();
|
||||
|
||||
StreamController<WatchStateEvent>? _controller;
|
||||
|
||||
StreamController<WatchStateEvent> get _ensureController {
|
||||
if (_controller == null || _controller!.isClosed) {
|
||||
_controller = StreamController<WatchStateEvent>.broadcast();
|
||||
}
|
||||
return _controller!;
|
||||
}
|
||||
|
||||
/// Stream of all watch state events
|
||||
Stream<WatchStateEvent> get stream => _ensureController.stream;
|
||||
|
||||
/// Filter for events affecting a specific server
|
||||
Stream<WatchStateEvent> forServer(String serverId) => stream.where((e) => e.serverId == serverId);
|
||||
|
||||
/// Filter for events affecting a specific item or its children
|
||||
Stream<WatchStateEvent> forItem(String ratingKey) => stream.where((e) => e.affectsItem(ratingKey));
|
||||
|
||||
/// Emit a watch state event
|
||||
void notify(WatchStateEvent event) {
|
||||
appLogger.d('WatchStateNotifier: $event');
|
||||
_ensureController.add(event);
|
||||
}
|
||||
|
||||
/// Helper to emit a watched/unwatched event from metadata
|
||||
void notifyWatched({required PlexMetadata metadata, bool isNowWatched = true}) {
|
||||
notify(
|
||||
WatchStateEvent(
|
||||
ratingKey: metadata.ratingKey,
|
||||
serverId: metadata.serverId ?? '',
|
||||
changeType: isNowWatched ? WatchStateChangeType.watched : WatchStateChangeType.unwatched,
|
||||
parentChain: _buildParentChain(metadata),
|
||||
mediaType: metadata.type,
|
||||
isNowWatched: isNowWatched,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Helper to emit a progress update event
|
||||
void notifyProgress({required PlexMetadata metadata, required int viewOffset, required int duration}) {
|
||||
const threshold = 0.90;
|
||||
final isNowWatched = duration > 0 && (viewOffset / duration) >= threshold;
|
||||
|
||||
notify(
|
||||
WatchStateEvent(
|
||||
ratingKey: metadata.ratingKey,
|
||||
serverId: metadata.serverId ?? '',
|
||||
changeType: WatchStateChangeType.progressUpdate,
|
||||
parentChain: _buildParentChain(metadata),
|
||||
mediaType: metadata.type,
|
||||
viewOffset: viewOffset,
|
||||
isNowWatched: isNowWatched,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build parent chain from metadata's parent keys
|
||||
List<String> _buildParentChain(PlexMetadata metadata) {
|
||||
final chain = <String>[];
|
||||
if (metadata.parentRatingKey != null) {
|
||||
chain.add(metadata.parentRatingKey!);
|
||||
}
|
||||
if (metadata.grandparentRatingKey != null) {
|
||||
chain.add(metadata.grandparentRatingKey!);
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_controller?.close();
|
||||
_controller = null;
|
||||
}
|
||||
}
|
||||
@@ -289,7 +289,7 @@ class MediaContextMenuState extends State<MediaContextMenu> {
|
||||
switch (selected) {
|
||||
case 'watch':
|
||||
if (isOffline && metadata?.serverId != null) {
|
||||
// Offline mode: queue action for later sync
|
||||
// Offline mode: queue action for later sync (emits WatchStateEvent)
|
||||
final offlineWatch = context.read<OfflineWatchProvider>();
|
||||
await offlineWatch.markAsWatched(serverId: metadata!.serverId!, ratingKey: metadata.ratingKey);
|
||||
if (context.mounted) {
|
||||
@@ -297,13 +297,18 @@ class MediaContextMenuState extends State<MediaContextMenu> {
|
||||
widget.onRefresh?.call(metadata.ratingKey);
|
||||
}
|
||||
} else {
|
||||
await _executeAction(context, () => client.markAsWatched(metadata!.ratingKey), t.messages.markedAsWatched);
|
||||
// Pass metadata to emit WatchStateEvent for cross-screen updates
|
||||
await _executeAction(
|
||||
context,
|
||||
() => client.markAsWatched(metadata!.ratingKey, metadata: metadata),
|
||||
t.messages.markedAsWatched,
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'unwatch':
|
||||
if (isOffline && metadata?.serverId != null) {
|
||||
// Offline mode: queue action for later sync
|
||||
// Offline mode: queue action for later sync (emits WatchStateEvent)
|
||||
final offlineWatch = context.read<OfflineWatchProvider>();
|
||||
await offlineWatch.markAsUnwatched(serverId: metadata!.serverId!, ratingKey: metadata.ratingKey);
|
||||
if (context.mounted) {
|
||||
@@ -311,9 +316,10 @@ class MediaContextMenuState extends State<MediaContextMenu> {
|
||||
widget.onRefresh?.call(metadata.ratingKey);
|
||||
}
|
||||
} else {
|
||||
// Pass metadata to emit WatchStateEvent for cross-screen updates
|
||||
await _executeAction(
|
||||
context,
|
||||
() => client.markAsUnwatched(metadata!.ratingKey),
|
||||
() => client.markAsUnwatched(metadata!.ratingKey, metadata: metadata),
|
||||
t.messages.markedAsUnwatched,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user