fix(trackers): preserve Plex library context

This commit is contained in:
edde746
2026-05-12 22:46:38 +02:00
parent 3a01fa25a7
commit de129fbb0a
30 changed files with 845 additions and 119 deletions
+15 -5
View File
@@ -17,6 +17,7 @@ import '../i18n/strings.g.dart';
import 'base_media_list_detail_screen.dart';
import 'focusable_detail_screen_mixin.dart';
import '../mixins/grid_focus_node_mixin.dart';
import '../services/playlist_items_loader.dart';
/// Screen to display the contents of a collection
class CollectionDetailScreen extends StatefulWidget {
@@ -59,7 +60,14 @@ class _CollectionDetailScreenState extends BaseMediaListDetailScreen<CollectionD
@override
Future<LibraryPage<MediaItem>> fetchPage(int start, int size, AbortController? abort) {
return mediaClient.fetchCollectionPage(widget.collection.id, start: start, size: size, abort: abort);
return mediaClient.fetchCollectionPage(
widget.collection.id,
start: start,
size: size,
abort: abort,
libraryId: widget.collection.libraryId,
libraryTitle: widget.collection.libraryTitle,
);
}
@override
@@ -147,10 +155,12 @@ class _CollectionDetailScreenState extends BaseMediaListDetailScreen<CollectionD
final downloadProvider = context.read<DownloadProvider>();
try {
// [fetchChildren] is the neutral equivalent of the Plex-only
// `fetchAllCollectionItemsAsMediaItems` — both backends return the
// collection's full contents.
final allItems = await mediaClient.fetchChildren(widget.collection.id);
final allItems = await fetchAllCollectionItemsPaged(
mediaClient,
widget.collection.id,
libraryId: widget.collection.libraryId,
libraryTitle: widget.collection.libraryTitle,
);
if (!mounted) return;
final result = await showCollectionDownloadOptionsAndQueue(
context,
+17 -3
View File
@@ -116,7 +116,11 @@ class FolderTreeViewState extends State<FolderTreeView> {
final client = context.getPlexClientForServer(widget.serverId!);
// Items are automatically tagged with server info by PlexClient.
final children = await client.fetchFolderChildren(folderKey);
final children = await client.fetchFolderChildren(
folderKey,
libraryId: folder.libraryId,
libraryTitle: folder.libraryTitle,
);
if (!mounted) return;
@@ -162,7 +166,12 @@ class FolderTreeViewState extends State<FolderTreeView> {
if (folderKey == null) return;
final client = context.getPlexClientForServer(widget.serverId!);
final launcher = PlexPlayQueueLauncher(context: context, client: client, serverId: widget.serverId);
await launcher.launchFromFolder(folderKey: folderKey, shuffle: false);
await launcher.launchFromFolder(
folderKey: folderKey,
shuffle: false,
libraryId: folder.libraryId,
libraryTitle: folder.libraryTitle,
);
}
Future<void> _handleFolderShuffle(MediaItem folder) async {
@@ -170,7 +179,12 @@ class FolderTreeViewState extends State<FolderTreeView> {
if (folderKey == null) return;
final client = context.getPlexClientForServer(widget.serverId!);
final launcher = PlexPlayQueueLauncher(context: context, client: client, serverId: widget.serverId);
await launcher.launchFromFolder(folderKey: folderKey, shuffle: true);
await launcher.launchFromFolder(
folderKey: folderKey,
shuffle: true,
libraryId: folder.libraryId,
libraryTitle: folder.libraryTitle,
);
}
bool _isFolder(MediaItem item) {
+65 -23
View File
@@ -117,6 +117,14 @@ class _MediaDetailScreenState extends State<MediaDetailScreen>
final Map<String, List<MediaItem>> _episodeCache = {};
bool _isLoadingSeasonEpisodes = false;
List<FocusNode> _seasonTabFocusNodes = [];
MediaItem _withFallbackLibrary(MediaItem item, MediaItem fallback) {
return item.copyWith(
libraryId: item.libraryId ?? fallback.libraryId,
libraryTitle: item.libraryTitle ?? fallback.libraryTitle,
);
}
final Map<int, GlobalKey<MediaContextMenuState>> _seasonContextMenuKeys = {};
final ScrollController _seasonTabsScrollController = ScrollController();
final FocusNode _firstEpisodeFocusNode = FocusNode(debugLabel: 'first_episode');
@@ -400,15 +408,23 @@ class _MediaDetailScreenState extends State<MediaDetailScreen>
final metadata = result.item;
final onDeckEpisode = result.onDeckEpisode;
if (metadata != null) {
setStateIfMounted(() {
_fullMetadata = _applyLocalProgress(
final refreshedMetadata = _applyLocalProgress(
_withFallbackLibrary(
metadata.copyWith(serverId: serverId, serverName: serverName ?? metadata.serverName),
);
_onDeckEpisode = onDeckEpisode == null
? null
: _applyLocalProgress(
_metadata,
),
);
final refreshedOnDeck = onDeckEpisode == null
? null
: _applyLocalProgress(
_withFallbackLibrary(
onDeckEpisode.copyWith(serverId: serverId, serverName: serverName ?? onDeckEpisode.serverName),
);
refreshedMetadata,
),
);
setStateIfMounted(() {
_fullMetadata = refreshedMetadata;
_onDeckEpisode = refreshedOnDeck;
});
}
@@ -417,7 +433,12 @@ class _MediaDetailScreenState extends State<MediaDetailScreen>
_episodeCache.clear();
setStateIfMounted(() {
_seasons = seasons
.map((s) => s.copyWith(serverId: serverId, serverName: serverName ?? s.serverName))
.map(
(s) => _withFallbackLibrary(
s.copyWith(serverId: serverId, serverName: serverName ?? s.serverName),
_metadata,
),
)
.toList();
});
if (_showEpisodesDirectly) {
@@ -1002,14 +1023,20 @@ class _MediaDetailScreenState extends State<MediaDetailScreen>
final serverName = _metadata.serverName;
final source = metadata ?? _metadata;
final base = _applyLocalProgress(
source.copyWith(serverId: serverId ?? source.serverId, serverName: serverName ?? source.serverName),
_withFallbackLibrary(
source.copyWith(serverId: serverId ?? source.serverId, serverName: serverName ?? source.serverName),
_metadata,
),
);
final onDeckWithServerId = onDeckEpisode == null
? null
: _applyLocalProgress(
onDeckEpisode.copyWith(
serverId: serverId ?? onDeckEpisode.serverId,
serverName: serverName ?? onDeckEpisode.serverName,
_withFallbackLibrary(
onDeckEpisode.copyWith(
serverId: serverId ?? onDeckEpisode.serverId,
serverName: serverName ?? onDeckEpisode.serverName,
),
base,
),
);
@@ -1081,7 +1108,12 @@ class _MediaDetailScreenState extends State<MediaDetailScreen>
// Preserve serverId for each season.
final seasonsWithServerId = seasons
.map((season) => season.copyWith(serverId: serverId, serverName: _metadata.serverName ?? season.serverName))
.map(
(season) => _withFallbackLibrary(
season.copyWith(serverId: serverId, serverName: _metadata.serverName ?? season.serverName),
_metadata,
),
)
.toList();
// Plex can override the library season mode per show; Jellyfin falls
@@ -1157,6 +1189,8 @@ class _MediaDetailScreenState extends State<MediaDetailScreen>
leafCount: entry.value.length,
thumbPath: firstEp.parentThumbPath,
parentId: firstEp.grandparentId,
libraryId: firstEp.libraryId ?? _metadata.libraryId,
libraryTitle: firstEp.libraryTitle ?? _metadata.libraryTitle,
serverId: _metadata.serverId,
serverName: _metadata.serverName,
);
@@ -1270,11 +1304,14 @@ class _MediaDetailScreenState extends State<MediaDetailScreen>
final episodes = await mediaClient.fetchChildren(season.id);
final episodesWithServerId = episodes
.map(
(e) => e.copyWith(
serverId: _metadata.serverId ?? e.serverId,
serverName: _metadata.serverName ?? e.serverName,
grandparentId: _metadata.id,
grandparentTitle: _metadata.title ?? e.grandparentTitle,
(e) => _withFallbackLibrary(
e.copyWith(
serverId: _metadata.serverId ?? e.serverId,
serverName: _metadata.serverName ?? e.serverName,
grandparentId: _metadata.id,
grandparentTitle: _metadata.title ?? e.grandparentTitle,
),
season.libraryId != null ? season : _metadata,
),
)
.map(_applyLocalProgress)
@@ -2002,11 +2039,14 @@ class _MediaDetailScreenState extends State<MediaDetailScreen>
: _metadata.title;
final enriched = episodes
.map(
(e) => e.copyWith(
serverId: serverId,
serverName: _metadata.serverName ?? e.serverName,
grandparentId: e.grandparentId ?? fallbackGrandparentId,
grandparentTitle: e.grandparentTitle ?? fallbackGrandparentTitle,
(e) => _withFallbackLibrary(
e.copyWith(
serverId: serverId,
serverName: _metadata.serverName ?? e.serverName,
grandparentId: e.grandparentId ?? fallbackGrandparentId,
grandparentTitle: e.grandparentTitle ?? fallbackGrandparentTitle,
),
_metadata,
),
)
.map(_applyLocalProgress)
@@ -2092,6 +2132,8 @@ class _MediaDetailScreenState extends State<MediaDetailScreen>
final episodeWithServerId = firstEpisode.copyWith(
serverId: _metadata.serverId ?? firstEpisode.serverId,
serverName: _metadata.serverName ?? firstEpisode.serverName,
libraryId: firstEpisode.libraryId ?? _metadata.libraryId,
libraryTitle: firstEpisode.libraryTitle ?? _metadata.libraryTitle,
);
if (mounted) {
appLogger.d('Playing first episode: ${episodeWithServerId.title}');
@@ -53,11 +53,21 @@ extension _VideoPlayerEpisodeQueueMethods on VideoPlayerScreenState {
showRatingKey: showRatingKey,
shuffle: 0,
startingEpisodeKey: _currentMetadata.id,
librarySectionID: _currentMetadata.libraryId,
librarySectionTitle: _currentMetadata.libraryTitle,
);
if (playQueue != null && playQueue.items != null && playQueue.items!.isNotEmpty) {
await playbackState.setPlaybackFromPlayQueue(playQueue, showRatingKey);
playbackState.setPlayQueueWindowFetcher(client.getPlayQueue);
playbackState.setPlayQueueWindowFetcher(
(id, {center, window = 50}) => client.getPlayQueue(
id,
center: center,
window: window,
librarySectionID: _currentMetadata.libraryId,
librarySectionTitle: _currentMetadata.libraryTitle,
),
);
appLogger.d('Sequential play queue created with ${playQueue.items!.length} items');
}