feat: jellyfin

This commit is contained in:
edde746
2026-05-01 01:20:36 +02:00
parent 4080c812d2
commit 31d2d9dc98
408 changed files with 56022 additions and 13747 deletions
@@ -1,18 +1,18 @@
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
import '../../../../services/plex_client.dart';
import '../../../i18n/strings.g.dart';
import '../../../media/media_hub.dart';
import '../../../media/media_item.dart';
import '../../../mixins/item_updatable.dart';
import '../../../models/plex_hub.dart';
import '../../../models/plex_metadata.dart';
import '../../../utils/provider_extensions.dart';
import '../../../widgets/hub_section.dart';
import '../../main_screen.dart';
import 'base_library_tab.dart';
/// Recommended tab for library screen
/// Shows library-specific hubs and recommendations, including dedicated Continue Watching
class LibraryRecommendedTab extends BaseLibraryTab<PlexHub> {
class LibraryRecommendedTab extends BaseLibraryTab<MediaHub> {
const LibraryRecommendedTab({
super.key,
required super.library,
@@ -26,20 +26,24 @@ class LibraryRecommendedTab extends BaseLibraryTab<PlexHub> {
State<LibraryRecommendedTab> createState() => _LibraryRecommendedTabState();
}
class _LibraryRecommendedTabState extends BaseLibraryTabState<PlexHub, LibraryRecommendedTab> with ItemUpdatable {
class _LibraryRecommendedTabState extends BaseLibraryTabState<MediaHub, LibraryRecommendedTab> with ItemUpdatable {
/// GlobalKeys for each hub section to enable vertical navigation
final List<GlobalKey<HubSectionState>> _hubKeys = [];
@override
PlexClient get client => getClientForLibrary();
String? get itemServerId => widget.library.serverId;
@override
void updateItemInLists(String ratingKey, PlexMetadata updatedMetadata) {
// Update the item in any hub that contains it
for (final hub in items) {
final itemIndex = hub.items.indexWhere((item) => item.ratingKey == ratingKey);
void updateItemInLists(String itemId, MediaItem updatedItem) {
// Update the item in any hub that contains it. MediaHub items are
// immutable lists; rebuild the affected hub in-place.
for (var i = 0; i < items.length; i++) {
final hub = items[i];
final itemIndex = hub.items.indexWhere((item) => item.id == itemId);
if (itemIndex != -1) {
hub.items[itemIndex] = updatedMetadata;
final newItems = List<MediaItem>.from(hub.items);
newItems[itemIndex] = updatedItem;
items[i] = hub.copyWith(items: newItems);
}
}
}
@@ -53,20 +57,22 @@ class _LibraryRecommendedTabState extends BaseLibraryTabState<PlexHub, LibraryRe
@override
String get errorContext => t.libraries.tabs.recommended;
/// Detects Continue Watching hubs by hubIdentifier.
/// Detects Continue Watching hubs by hub identifier.
/// Section-specific CW hubs use identifiers like "movie.inprogress.1".
static bool _isContinueWatchingHub(PlexHub hub) {
final hubId = hub.hubIdentifier?.toLowerCase() ?? '';
static bool _isContinueWatchingHub(MediaHub hub) {
final hubId = hub.identifier?.toLowerCase() ?? '';
return hubId.contains('inprogress');
}
@override
Future<List<PlexHub>> loadData() async {
Future<List<MediaHub>> loadData() async {
// Clear hub keys before loading new hubs to prevent stale references
_hubKeys.clear();
final client = getClientForLibrary();
final hubs = await client.getLibraryHubs(widget.library.key, limit: 12);
// Backend-aware fetch: Plex hits /hubs/sections, Jellyfin synthesises
// Continue Watching + Next Up + Recently Added.
final client = context.tryGetMediaClientForServer(widget.library.serverId);
final hubs = client == null ? <MediaHub>[] : List.of(await client.fetchLibraryHubs(widget.library.id, limit: 12));
// Move Continue Watching hub to the front if present
final cwIndex = hubs.indexWhere(_isContinueWatchingHub);
@@ -127,7 +133,7 @@ class _LibraryRecommendedTabState extends BaseLibraryTabState<PlexHub, LibraryRe
static const double _focusDecorationPadding = 8.0;
@override
Widget buildContent(List<PlexHub> items) {
Widget buildContent(List<MediaHub> items) {
_ensureHubKeys(items.length);
return ListView.builder(
@@ -161,7 +167,7 @@ class _LibraryRecommendedTabState extends BaseLibraryTabState<PlexHub, LibraryRe
loadItems();
}
IconData _getHubIcon(PlexHub hub) {
IconData _getHubIcon(MediaHub hub) {
final title = hub.title.toLowerCase();
if (title.contains('continue watching') || title.contains('on deck')) {
return Symbols.play_circle_rounded;