From e23f472d787be1fa2adbdcd18fab5be457afb211 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:49:13 +0100 Subject: [PATCH] refactor: centralize globalKey construction via buildGlobalKey() --- lib/database/app_database.dart | 5 +++-- lib/models/plex_library.dart | 3 ++- lib/models/plex_metadata.dart | 3 ++- lib/models/plex_playlist.dart | 3 ++- lib/providers/download_provider.dart | 22 +++++++++---------- lib/providers/offline_watch_provider.dart | 3 ++- lib/screens/discover_screen.dart | 9 ++++---- .../libraries/tabs/library_browse_tab.dart | 3 ++- lib/screens/media_detail_screen.dart | 5 +++-- lib/screens/season_detail_screen.dart | 7 +++--- lib/screens/video_player_screen.dart | 4 ++-- lib/services/data_aggregation_service.dart | 5 +++-- lib/services/download_manager_service.dart | 15 +++++++------ lib/services/plex_api_cache.dart | 3 ++- lib/utils/deletion_notifier.dart | 3 ++- lib/utils/global_key_utils.dart | 3 +++ lib/utils/hierarchical_event_mixin.dart | 4 +++- lib/utils/video_player_navigation.dart | 2 +- lib/utils/watch_state_notifier.dart | 3 ++- lib/widgets/media_card.dart | 2 +- lib/widgets/media_context_menu.dart | 6 ++--- 21 files changed, 65 insertions(+), 48 deletions(-) diff --git a/lib/database/app_database.dart b/lib/database/app_database.dart index 9d573b5e..69c31464 100644 --- a/lib/database/app_database.dart +++ b/lib/database/app_database.dart @@ -7,6 +7,7 @@ import 'package:path/path.dart' as p; import 'tables.dart'; import '../models/download_models.dart'; import '../utils/app_logger.dart'; +import '../utils/global_key_utils.dart'; part 'app_database.g.dart'; @@ -99,7 +100,7 @@ class AppDatabase extends _$AppDatabase { required int duration, required bool shouldMarkWatched, }) async { - final globalKey = '$serverId:$ratingKey'; + final globalKey = buildGlobalKey(serverId, ratingKey); final now = DateTime.now().millisecondsSinceEpoch; // Check for existing progress entry @@ -144,7 +145,7 @@ class AppDatabase extends _$AppDatabase { required String ratingKey, required String actionType, // 'watched' or 'unwatched' }) async { - final globalKey = '$serverId:$ratingKey'; + final globalKey = buildGlobalKey(serverId, ratingKey); final now = DateTime.now().millisecondsSinceEpoch; // Remove conflicting actions (opposite action type and progress) diff --git a/lib/models/plex_library.dart b/lib/models/plex_library.dart index 1b84f868..b1ab5ae1 100644 --- a/lib/models/plex_library.dart +++ b/lib/models/plex_library.dart @@ -1,6 +1,7 @@ import 'package:json_annotation/json_annotation.dart'; import 'mixins/multi_server_fields.dart'; +import '../utils/global_key_utils.dart'; part 'plex_library.g.dart'; @@ -26,7 +27,7 @@ class PlexLibrary with MultiServerFields { final String? serverName; /// Global unique identifier across all servers (serverId:key) - String get globalKey => serverId != null ? '$serverId:$key' : key; + String get globalKey => serverId != null ? buildGlobalKey(serverId!, key) : key; PlexLibrary({ required this.key, diff --git a/lib/models/plex_metadata.dart b/lib/models/plex_metadata.dart index 2828828f..a6801b6c 100644 --- a/lib/models/plex_metadata.dart +++ b/lib/models/plex_metadata.dart @@ -4,6 +4,7 @@ import '../services/settings_service.dart' show EpisodePosterMode; import '../widgets/plex_optimized_image.dart' show kBlurArtwork, obfuscateText; import 'mixins/multi_server_fields.dart'; import 'plex_role.dart'; +import '../utils/global_key_utils.dart'; part 'plex_metadata.g.dart'; @@ -111,7 +112,7 @@ class PlexMetadata with MultiServerFields { final String? clearLogo; /// Global unique identifier across all servers (serverId:ratingKey) - String get globalKey => serverId != null ? '$serverId:$ratingKey' : ratingKey; + String get globalKey => serverId != null ? buildGlobalKey(serverId!, ratingKey) : ratingKey; /// Parsed media type enum for type-safe comparisons PlexMediaType get mediaType { diff --git a/lib/models/plex_playlist.dart b/lib/models/plex_playlist.dart index 8f72e874..e90ab255 100644 --- a/lib/models/plex_playlist.dart +++ b/lib/models/plex_playlist.dart @@ -2,6 +2,7 @@ import 'package:json_annotation/json_annotation.dart'; import '../widgets/plex_optimized_image.dart' show kBlurArtwork, obfuscateText; import 'mixins/multi_server_fields.dart'; +import '../utils/global_key_utils.dart'; part 'plex_playlist.g.dart'; @@ -65,7 +66,7 @@ class PlexPlaylist with MultiServerFields { bool get isEditable => !smart; /// Get globally unique key across all servers - String get globalKey => serverId != null ? '$serverId:$ratingKey' : ratingKey; + String get globalKey => serverId != null ? buildGlobalKey(serverId!, ratingKey) : ratingKey; // Properties for MediaCard compatibility with PlexMetadata interface diff --git a/lib/providers/download_provider.dart b/lib/providers/download_provider.dart index 7a4185c2..577d0f23 100644 --- a/lib/providers/download_provider.dart +++ b/lib/providers/download_provider.dart @@ -172,7 +172,7 @@ class DownloadProvider extends ChangeNotifier { // Load show metadata final showRatingKey = episode.grandparentRatingKey; if (showRatingKey != null) { - final showGlobalKey = '$serverId:$showRatingKey'; + final showGlobalKey = buildGlobalKey(serverId, showRatingKey); if (!_metadata.containsKey(showGlobalKey)) { final showMetadata = allMetadata[showGlobalKey]; if (showMetadata != null) { @@ -187,7 +187,7 @@ class DownloadProvider extends ChangeNotifier { // Load season metadata final seasonRatingKey = episode.parentRatingKey; if (seasonRatingKey != null) { - final seasonGlobalKey = '$serverId:$seasonRatingKey'; + final seasonGlobalKey = buildGlobalKey(serverId, seasonRatingKey); if (!_metadata.containsKey(seasonGlobalKey)) { final seasonMetadata = allMetadata[seasonGlobalKey]; if (seasonMetadata != null) { @@ -270,7 +270,7 @@ class DownloadProvider extends ChangeNotifier { final showRatingKey = meta.grandparentRatingKey; if (showRatingKey != null && !shows.containsKey(showRatingKey)) { // Try to get stored show metadata first - final showGlobalKey = '${meta.serverId}:$showRatingKey'; + final showGlobalKey = buildGlobalKey(meta.serverId!, showRatingKey); final storedShow = _metadata[showGlobalKey]; if (storedShow != null && storedShow.type == 'show') { @@ -384,7 +384,7 @@ class DownloadProvider extends ChangeNotifier { required List episodes, required String entityType, }) { - final globalKey = '$serverId:$ratingKey'; + final globalKey = buildGlobalKey(serverId, ratingKey); // DIAGNOSTIC: Check all sources of episode count final meta = _metadata[globalKey]; @@ -604,7 +604,7 @@ class DownloadProvider extends ChangeNotifier { /// For shows and seasons, fetches all child episodes and queues them. /// Returns the number of items queued. Future queueDownload(PlexMetadata metadata, PlexClient client) async { - final globalKey = '${metadata.serverId}:${metadata.ratingKey}'; + final globalKey = metadata.globalKey; // Check if downloads are blocked on cellular if (await DownloadManagerService.shouldBlockDownloadOnCellular()) { @@ -646,7 +646,7 @@ class DownloadProvider extends ChangeNotifier { /// Queue a single movie or episode for download Future _queueSingleDownload(PlexMetadata metadata, PlexClient client) async { - final globalKey = '${metadata.serverId}:${metadata.ratingKey}'; + final globalKey = metadata.globalKey; // Don't re-queue if already downloading or completed if (_downloads.containsKey(globalKey)) { @@ -697,7 +697,7 @@ class DownloadProvider extends ChangeNotifier { // Fetch and store show metadata if not already stored final showRatingKey = episode.grandparentRatingKey; if (showRatingKey != null) { - final showGlobalKey = '$serverId:$showRatingKey'; + final showGlobalKey = buildGlobalKey(serverId, showRatingKey); // Try to use existing metadata (set when queueing an entire show) PlexMetadata? showMetadata = _metadata[showGlobalKey]; @@ -734,7 +734,7 @@ class DownloadProvider extends ChangeNotifier { // Fetch and store season metadata if not already stored final seasonRatingKey = episode.parentRatingKey; if (seasonRatingKey != null) { - final seasonGlobalKey = '$serverId:$seasonRatingKey'; + final seasonGlobalKey = buildGlobalKey(serverId, seasonRatingKey); PlexMetadata? seasonMetadata = _metadata[seasonGlobalKey]; if (seasonMetadata == null) { @@ -768,7 +768,7 @@ class DownloadProvider extends ChangeNotifier { /// Queue all episodes from a TV show for download Future _queueShowDownload(PlexMetadata show, PlexClient client) async { - final globalKey = '${show.serverId}:${show.ratingKey}'; + final globalKey = show.globalKey; int count = 0; final seasons = await client.getChildren(show.ratingKey); @@ -804,7 +804,7 @@ class DownloadProvider extends ChangeNotifier { /// Queue all episodes from a season for download Future _queueSeasonDownload(PlexMetadata season, PlexClient client) async { - final globalKey = '${season.serverId}:${season.ratingKey}'; + final globalKey = season.globalKey; int count = 0; final episodes = await client.getChildren(season.ratingKey); @@ -883,7 +883,7 @@ class DownloadProvider extends ChangeNotifier { if (episode.type == 'episode') { final episodeWithServer = episode.serverId != null ? episode : episode.copyWith(serverId: season.serverId); - final episodeGlobalKey = '${episodeWithServer.serverId}:${episodeWithServer.ratingKey}'; + final episodeGlobalKey = episodeWithServer.globalKey; // Only queue if NOT already downloaded or in progress final progress = _downloads[episodeGlobalKey]; diff --git a/lib/providers/offline_watch_provider.dart b/lib/providers/offline_watch_provider.dart index 0b056e74..da5f7a5a 100644 --- a/lib/providers/offline_watch_provider.dart +++ b/lib/providers/offline_watch_provider.dart @@ -5,6 +5,7 @@ import '../services/offline_watch_sync_service.dart'; import '../services/plex_api_cache.dart'; import '../utils/watch_state_notifier.dart'; import 'download_provider.dart'; +import '../utils/global_key_utils.dart'; /// Provider for offline watch status UI state. /// @@ -174,7 +175,7 @@ class OfflineWatchProvider extends ChangeNotifier { required bool isNowWatched, required WatchStateChangeType changeType, }) { - final globalKey = '$serverId:$ratingKey'; + final globalKey = buildGlobalKey(serverId, ratingKey); final metadata = _downloadProvider.getMetadata(globalKey); if (metadata != null) { WatchStateNotifier().notifyWatched(metadata: metadata, isNowWatched: isNowWatched); diff --git a/lib/screens/discover_screen.dart b/lib/screens/discover_screen.dart index 1d54a293..e242f155 100644 --- a/lib/screens/discover_screen.dart +++ b/lib/screens/discover_screen.dart @@ -6,6 +6,7 @@ import 'package:plezy/widgets/app_icon.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import '../focus/key_event_utils.dart'; +import '../utils/global_key_utils.dart'; import 'package:cached_network_image/cached_network_image.dart'; import '../../services/plex_client.dart'; import '../utils/plex_image_helper.dart'; @@ -81,8 +82,6 @@ class _DiscoverScreenState extends State bool _isAutoScrollPaused = false; HiddenLibrariesProvider? _hiddenLibrariesProvider; - String _toGlobalKey(String ratingKey, String serverId) => '$serverId:$ratingKey'; - // WatchStateAware: watch on-deck items and their parent shows/seasons @override Set? get watchedRatingKeys { @@ -106,12 +105,12 @@ class _DiscoverScreenState extends State final serverId = item.serverId; if (serverId == null) return null; - keys.add(_toGlobalKey(item.ratingKey, serverId)); + keys.add(buildGlobalKey(serverId, item.ratingKey)); if (item.parentRatingKey != null) { - keys.add(_toGlobalKey(item.parentRatingKey!, serverId)); + keys.add(buildGlobalKey(serverId, item.parentRatingKey!)); } if (item.grandparentRatingKey != null) { - keys.add(_toGlobalKey(item.grandparentRatingKey!, serverId)); + keys.add(buildGlobalKey(serverId, item.grandparentRatingKey!)); } } return keys; diff --git a/lib/screens/libraries/tabs/library_browse_tab.dart b/lib/screens/libraries/tabs/library_browse_tab.dart index c0916f2b..4f6a3388 100644 --- a/lib/screens/libraries/tabs/library_browse_tab.dart +++ b/lib/screens/libraries/tabs/library_browse_tab.dart @@ -32,6 +32,7 @@ import '../../../mixins/grid_focus_node_mixin.dart'; import '../../../mixins/item_updatable.dart'; import '../../../mixins/deletion_aware.dart'; import '../../../utils/deletion_notifier.dart'; +import '../../../utils/global_key_utils.dart'; import '../../../utils/platform_detector.dart'; import '../../../i18n/strings.g.dart'; import '../../main_screen.dart'; @@ -61,7 +62,7 @@ class _LibraryBrowseTabState extends BaseLibraryTabState getClientForLibrary(); String _toGlobalKey(String ratingKey, {String? serverId}) => - '${serverId ?? widget.library.serverId ?? ''}:$ratingKey'; + buildGlobalKey(serverId ?? widget.library.serverId ?? '', ratingKey); @override String? get deletionServerId => widget.library.serverId; diff --git a/lib/screens/media_detail_screen.dart b/lib/screens/media_detail_screen.dart index 81131b35..928b7edf 100644 --- a/lib/screens/media_detail_screen.dart +++ b/lib/screens/media_detail_screen.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; +import '../utils/global_key_utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter/services.dart'; @@ -106,7 +107,7 @@ class _MediaDetailScreenState extends State with WatchStateAw final _seasonsSectionKey = GlobalKey(); String _toGlobalKey(String ratingKey, {String? serverId}) => - '${serverId ?? widget.metadata.serverId ?? ''}:$ratingKey'; + buildGlobalKey(serverId ?? widget.metadata.serverId ?? '', ratingKey); // WatchStateAware: watch the show/movie and all season ratingKeys @override @@ -424,7 +425,7 @@ class _MediaDetailScreenState extends State with WatchStateAw if (!widget.isOffline) Consumer( builder: (context, downloadProvider, _) { - final globalKey = '${metadata.serverId}:${metadata.ratingKey}'; + final globalKey = metadata.globalKey; final progress = downloadProvider.getProgress(globalKey); final isQueueing = downloadProvider.isQueueing(globalKey); diff --git a/lib/screens/season_detail_screen.dart b/lib/screens/season_detail_screen.dart index e52bcd8a..507fabb7 100644 --- a/lib/screens/season_detail_screen.dart +++ b/lib/screens/season_detail_screen.dart @@ -8,6 +8,7 @@ import 'package:provider/provider.dart'; import '../../services/plex_client.dart'; import '../main.dart'; import '../focus/focusable_wrapper.dart'; +import '../utils/global_key_utils.dart'; import '../focus/key_event_utils.dart'; import '../focus/dpad_navigator.dart'; import '../focus/input_mode_tracker.dart'; @@ -55,7 +56,7 @@ class _SeasonDetailScreenState extends State bool _suppressNextBackKeyUp = false; bool _routeSubscribed = false; - String _toGlobalKey(String ratingKey, {String? serverId}) => '${serverId ?? widget.season.serverId ?? ''}:$ratingKey'; + String _toGlobalKey(String ratingKey, {String? serverId}) => buildGlobalKey(serverId ?? widget.season.serverId ?? '', ratingKey); // WatchStateAware: watch all episode ratingKeys @override @@ -270,7 +271,7 @@ class _SeasonDetailScreenState extends State String? localPosterPath; if (widget.isOffline && episode.serverId != null) { final downloadProvider = context.read(); - final globalKey = '${episode.serverId}:${episode.ratingKey}'; + final globalKey = episode.globalKey; // Get the artwork reference and convert to local file path final artworkRef = downloadProvider.getArtworkPaths(globalKey); localPosterPath = artworkRef?.getLocalPath(DownloadStorageService.instance, episode.serverId!); @@ -499,7 +500,7 @@ class _EpisodeCardState extends State<_EpisodeCard> { // Only show download status in online mode if (!widget.isOffline && widget.episode.serverId != null) { - final globalKey = '${widget.episode.serverId}:${widget.episode.ratingKey}'; + final globalKey = widget.episode.globalKey; final progress = downloadProvider.getProgress(globalKey); final isQueueing = downloadProvider.isQueueing(globalKey); diff --git a/lib/screens/video_player_screen.dart b/lib/screens/video_player_screen.dart index da587c65..e18738a4 100644 --- a/lib/screens/video_player_screen.dart +++ b/lib/screens/video_player_screen.dart @@ -951,7 +951,7 @@ class VideoPlayerScreenState extends State with WidgetsBindin // since the user may have watched further since downloading. Duration? resumePosition; if (widget.isOffline) { - final globalKey = '${widget.metadata.serverId}:${widget.metadata.ratingKey}'; + final globalKey = widget.metadata.globalKey; final localOffset = await offlineWatchService!.getLocalViewOffset(globalKey); if (localOffset != null && localOffset > 0) { resumePosition = Duration(milliseconds: localOffset); @@ -1103,7 +1103,7 @@ class VideoPlayerScreenState extends State with WidgetsBindin // Debug: log metadata info appLogger.d('Offline playback - serverId: ${widget.metadata.serverId}, ratingKey: ${widget.metadata.ratingKey}'); - final globalKey = '${widget.metadata.serverId}:${widget.metadata.ratingKey}'; + final globalKey = widget.metadata.globalKey; appLogger.d('Looking up video with globalKey: $globalKey'); final videoPath = await downloadProvider.getVideoFilePath(globalKey); diff --git a/lib/services/data_aggregation_service.dart b/lib/services/data_aggregation_service.dart index e4ef23fa..b0963025 100644 --- a/lib/services/data_aggregation_service.dart +++ b/lib/services/data_aggregation_service.dart @@ -5,6 +5,7 @@ import '../models/plex_hub.dart'; import '../models/plex_library.dart'; import '../models/plex_metadata.dart'; import '../utils/app_logger.dart'; +import '../utils/global_key_utils.dart'; import 'multi_server_manager.dart'; import 'plex_auth_service.dart'; @@ -45,7 +46,7 @@ class DataAggregationService { filteredOnDeck = allOnDeck.where((item) { final librarySectionId = item.librarySectionID; if (librarySectionId == null) return true; // Keep if no section ID - final globalKey = '${item.serverId}:$librarySectionId'; + final globalKey = buildGlobalKey(item.serverId!, librarySectionId.toString()); return !hiddenLibraryKeys.contains(globalKey); }).toList(); } @@ -118,7 +119,7 @@ class DataAggregationService { // Build the global key for the item's library section final librarySectionId = item.librarySectionID; if (librarySectionId == null) return true; // Keep if no section ID - final globalKey = '$serverId:$librarySectionId'; + final globalKey = buildGlobalKey(serverId, librarySectionId.toString()); return !hiddenLibraryKeys.contains(globalKey); }).toList(); diff --git a/lib/services/download_manager_service.dart b/lib/services/download_manager_service.dart index 0bd98cf7..ab37a94a 100644 --- a/lib/services/download_manager_service.dart +++ b/lib/services/download_manager_service.dart @@ -430,7 +430,7 @@ class DownloadManagerService { bool downloadSubtitles = true, bool downloadArtwork = true, }) async { - final globalKey = '${metadata.serverId}:${metadata.ratingKey}'; + final globalKey = metadata.globalKey; // Check if already downloading or completed final existing = await _database.getDownloadedMedia(globalKey); @@ -1252,12 +1252,13 @@ class DownloadManagerService { if (metadata == null) { // Fallback: Try database record - final downloadRecord = await _database.getDownloadedMedia('$serverId:$ratingKey'); + final gk = buildGlobalKey(serverId, ratingKey); + final downloadRecord = await _database.getDownloadedMedia(gk); if (downloadRecord?.videoFilePath != null) { await _deleteByFilePath(downloadRecord!); return; } - appLogger.w('Cannot delete - no metadata for $serverId:$ratingKey'); + appLogger.w('Cannot delete - no metadata for $gk'); return; } @@ -1433,12 +1434,12 @@ class DownloadManagerService { }) async { for (int i = 0; i < episodes.length; i++) { final episode = episodes[i]; - final episodeGlobalKey = '$serverId:${episode.ratingKey}'; + final episodeGlobalKey = buildGlobalKey(serverId, episode.ratingKey); // Emit progress update _emitDeletionProgress( DeletionProgress( - globalKey: '$serverId:$parentKey', + globalKey: buildGlobalKey(serverId, parentKey), itemTitle: parentTitle, currentItem: i + 1, totalItems: episodes.length, @@ -1551,7 +1552,7 @@ class DownloadManagerService { final otherEpisodes = await _database.getEpisodesBySeason(seasonKey); // Check if any episodes besides this one - return otherEpisodes.any((e) => e.globalKey != '${episode.serverId}:${episode.ratingKey}'); + return otherEpisodes.any((e) => e.globalKey != episode.globalKey); } /// Check if show artwork is in use @@ -1562,7 +1563,7 @@ class DownloadManagerService { final showEpisodes = await _database.getEpisodesByShow(showKey); // Check if any episodes belong to this show besides the current item - return showEpisodes.any((item) => item.globalKey != '${metadata.serverId}:${metadata.ratingKey}'); + return showEpisodes.any((item) => item.globalKey != metadata.globalKey); } /// Find file with any extension diff --git a/lib/services/plex_api_cache.dart b/lib/services/plex_api_cache.dart index 2f19d128..25a89677 100644 --- a/lib/services/plex_api_cache.dart +++ b/lib/services/plex_api_cache.dart @@ -6,6 +6,7 @@ import 'package:drift/drift.dart'; import '../database/app_database.dart'; import '../models/plex_metadata.dart'; import '../utils/plex_cache_parser.dart'; +import '../utils/global_key_utils.dart'; /// Key-value cache for Plex API responses using Drift/SQLite. /// Stores raw JSON responses keyed by serverId:endpoint format. @@ -152,7 +153,7 @@ class PlexApiCache { final json = PlexCacheParser.extractFirstMetadata(data); if (json == null) continue; final metadata = PlexMetadata.fromJsonWithImages(json).copyWith(serverId: serverId); - result['$serverId:$ratingKey'] = metadata; + result[buildGlobalKey(serverId, ratingKey)] = metadata; } catch (_) { // Skip malformed entries } diff --git a/lib/utils/deletion_notifier.dart b/lib/utils/deletion_notifier.dart index d68a7672..cd54bb9c 100644 --- a/lib/utils/deletion_notifier.dart +++ b/lib/utils/deletion_notifier.dart @@ -1,6 +1,7 @@ import '../models/plex_metadata.dart'; import 'app_logger.dart'; import 'base_notifier.dart'; +import 'global_key_utils.dart'; import 'hierarchical_event_mixin.dart'; /// Event representing a media item deletion with parent chain for hierarchical invalidation @@ -42,7 +43,7 @@ class DeletionEvent with HierarchicalEventMixin { required this.mediaType, this.leafCount = 1, this.isDownloadOnly = false, - }) : globalKey = '$serverId:$ratingKey'; + }) : globalKey = buildGlobalKey(serverId, ratingKey); @override String toString() => 'DeletionEvent(deleted: $globalKey, type: $mediaType, parents: $parentChain)'; diff --git a/lib/utils/global_key_utils.dart b/lib/utils/global_key_utils.dart index 2010fc45..cf696eaf 100644 --- a/lib/utils/global_key_utils.dart +++ b/lib/utils/global_key_utils.dart @@ -1,3 +1,6 @@ +/// Builds a globalKey string from [serverId] and [ratingKey]. +String buildGlobalKey(String serverId, String ratingKey) => '$serverId:$ratingKey'; + /// Parses a globalKey string (format: "serverId:ratingKey") into its components. /// /// Returns `null` if the key does not contain a colon separator. diff --git a/lib/utils/hierarchical_event_mixin.dart b/lib/utils/hierarchical_event_mixin.dart index a1599e55..80f1f357 100644 --- a/lib/utils/hierarchical_event_mixin.dart +++ b/lib/utils/hierarchical_event_mixin.dart @@ -1,3 +1,5 @@ +import 'global_key_utils.dart'; + /// Mixin providing hierarchical event matching methods. /// /// Events that represent changes to media items often need to check if they @@ -24,7 +26,7 @@ mixin HierarchicalEventMixin { /// Check if this event affects a specific globalKey. bool affectsGlobalKey(String globalKey) => - this.globalKey == globalKey || parentChain.any((pk) => '$serverId:$pk' == globalKey); + this.globalKey == globalKey || parentChain.any((pk) => buildGlobalKey(serverId, pk) == globalKey); /// Check if this event affects any item in a collection. bool affectsAnyOf(Iterable ratingKeys) => ratingKeys.any(affectsItem); diff --git a/lib/utils/video_player_navigation.dart b/lib/utils/video_player_navigation.dart index 098f7472..eefdc835 100644 --- a/lib/utils/video_player_navigation.dart +++ b/lib/utils/video_player_navigation.dart @@ -77,7 +77,7 @@ Future navigateToVideoPlayer( if (isOffline) { // Offline mode: resolve local file path for the external player - final globalKey = '${metadata.serverId}:${metadata.ratingKey}'; + final globalKey = metadata.globalKey; final videoPath = await downloadProvider.getVideoFilePath(globalKey); if (videoPath != null && context.mounted) { final videoUrl = videoPath.contains('://') ? videoPath : 'file://$videoPath'; diff --git a/lib/utils/watch_state_notifier.dart b/lib/utils/watch_state_notifier.dart index dcb908c4..207179ac 100644 --- a/lib/utils/watch_state_notifier.dart +++ b/lib/utils/watch_state_notifier.dart @@ -1,6 +1,7 @@ import '../models/plex_metadata.dart'; import 'app_logger.dart'; import 'base_notifier.dart'; +import 'global_key_utils.dart'; import 'hierarchical_event_mixin.dart'; /// Types of watch state changes @@ -47,7 +48,7 @@ class WatchStateEvent with HierarchicalEventMixin { required this.mediaType, this.viewOffset, this.isNowWatched, - }) : globalKey = '$serverId:$ratingKey'; + }) : globalKey = buildGlobalKey(serverId, ratingKey); @override String toString() => 'WatchStateEvent($changeType, $globalKey, parents: $parentChain)'; diff --git a/lib/widgets/media_card.dart b/lib/widgets/media_card.dart index fecbc650..cbce9926 100644 --- a/lib/widgets/media_card.dart +++ b/lib/widgets/media_card.dart @@ -149,7 +149,7 @@ class MediaCardState extends State { if (metadata.serverId == null) return null; final downloadProvider = context.read(); - final globalKey = '${metadata.serverId}:${metadata.ratingKey}'; + final globalKey = metadata.globalKey; // Get artwork reference and resolve to local path using hash (includes serverId) final artwork = downloadProvider.getArtworkPaths(globalKey); diff --git a/lib/widgets/media_context_menu.dart b/lib/widgets/media_context_menu.dart index 5b4bdb34..65500961 100644 --- a/lib/widgets/media_context_menu.dart +++ b/lib/widgets/media_context_menu.dart @@ -263,7 +263,7 @@ class MediaContextMenuState extends State { mediaType == PlexMediaType.show || mediaType == PlexMediaType.season) { final downloadProvider = Provider.of(context, listen: false); - final globalKey = '${metadata.serverId}:${metadata.ratingKey}'; + final globalKey = metadata.globalKey; final isDownloaded = downloadProvider.isDownloaded(globalKey); if (isDownloaded) { @@ -1063,7 +1063,7 @@ class MediaContextMenuState extends State { // Check if the item is downloaded and use local file path if available final downloadProvider = Provider.of(context, listen: false); - final globalKey = '${metadata.serverId}:${metadata.ratingKey}'; + final globalKey = metadata.globalKey; if (downloadProvider.isDownloaded(globalKey)) { final videoPath = await downloadProvider.getVideoFilePath(globalKey); if (videoPath != null && context.mounted) { @@ -1107,7 +1107,7 @@ class MediaContextMenuState extends State { Future _handleDeleteDownload(BuildContext context) async { final downloadProvider = Provider.of(context, listen: false); final metadata = widget.item as PlexMetadata; - final globalKey = '${metadata.serverId}:${metadata.ratingKey}'; + final globalKey = metadata.globalKey; // Show confirmation dialog final confirmed = await showDeleteConfirmation(