feat: global hubs with settings toggle

close #216 & #217
This commit is contained in:
edde746
2026-01-06 23:23:03 +01:00
parent ed98a65c60
commit 5ed2532bf1
14 changed files with 243 additions and 23 deletions
+30 -2
View File
@@ -4,9 +4,9 @@
/// To regenerate, run: `dart run slang`
///
/// Locales: 7
/// Strings: 3554 (507 per locale)
/// Strings: 3568 (509 per locale)
///
/// Built on 2025-12-31 at 06:54 UTC
/// Built on 2026-01-06 at 22:09 UTC
// coverage:ignore-file
// ignore_for_file: type=lint
@@ -313,6 +313,8 @@ class _StringsSettingsEn {
String get listViewDescription => 'Display items in a list layout';
String get useSeasonPosters => 'Use Season Posters';
String get showHeroSection => 'Show Hero Section';
String get useGlobalHubs => 'Use Plex Home Layout';
String get useGlobalHubsDescription => 'Show home page hubs like the official Plex client. When off, shows per-library recommendations instead.';
String get hardwareDecoding => 'Hardware Decoding';
String get hardwareDecodingDescription => 'Use hardware acceleration when available';
String get bufferSize => 'Buffer Size';
@@ -1186,6 +1188,8 @@ class _StringsSettingsDe implements _StringsSettingsEn {
@override String get listViewDescription => 'Elemente in Listenansicht anzeigen';
@override String get useSeasonPosters => 'Staffelposter verwenden';
@override String get showHeroSection => 'Hero-Bereich anzeigen';
@override String get useGlobalHubs => 'Plex-Startseiten-Layout verwenden';
@override String get useGlobalHubsDescription => 'Zeigt Startseiten-Hubs wie der offizielle Plex-Client. Wenn deaktiviert, werden stattdessen Empfehlungen pro Bibliothek angezeigt.';
@override String get hardwareDecoding => 'Hardware-Decodierung';
@override String get hardwareDecodingDescription => 'Hardwarebeschleunigung verwenden, sofern verfügbar';
@override String get bufferSize => 'Puffergröße';
@@ -2060,6 +2064,8 @@ class _StringsSettingsIt implements _StringsSettingsEn {
@override String get listViewDescription => 'Visualizza gli elementi in un layout a elenco';
@override String get useSeasonPosters => 'Usa poster delle stagioni';
@override String get showHeroSection => 'Mostra sezione principale';
@override String get useGlobalHubs => 'Usa layout Home di Plex';
@override String get useGlobalHubsDescription => 'Mostra gli hub della home page come il client Plex ufficiale. Se disattivato, mostra invece i suggerimenti per libreria.';
@override String get hardwareDecoding => 'Decodifica Hardware';
@override String get hardwareDecodingDescription => 'Utilizza l\'accelerazione hardware quando disponibile';
@override String get bufferSize => 'Dimensione buffer';
@@ -2934,6 +2940,8 @@ class _StringsSettingsKo implements _StringsSettingsEn {
@override String get listViewDescription => '항목을 목록 레이아웃으로 표시합니다';
@override String get useSeasonPosters => '시즌 포스터 사용';
@override String get showHeroSection => '주요 추천 영역 표시';
@override String get useGlobalHubs => 'Plex 홈 레이아웃 사용';
@override String get useGlobalHubsDescription => '공식 Plex 클라이언트처럼 홈 페이지 허브를 표시합니다. 끄면 라이브러리별 추천이 대신 표시됩니다.';
@override String get hardwareDecoding => '하드웨어 디코딩';
@override String get hardwareDecodingDescription => '가능한 경우 하드웨어 가속을 사용합니다';
@override String get bufferSize => '버퍼 크기';
@@ -3808,6 +3816,8 @@ class _StringsSettingsNl implements _StringsSettingsEn {
@override String get listViewDescription => 'Items weergeven in een lijstindeling';
@override String get useSeasonPosters => 'Gebruik seizoenposters';
@override String get showHeroSection => 'Toon hoofdsectie';
@override String get useGlobalHubs => 'Plex Home-indeling gebruiken';
@override String get useGlobalHubsDescription => 'Toon startpagina-hubs zoals de officiële Plex-client. Indien uitgeschakeld, worden in plaats daarvan aanbevelingen per bibliotheek getoond.';
@override String get hardwareDecoding => 'Hardware decodering';
@override String get hardwareDecodingDescription => 'Gebruik hardware versnelling indien beschikbaar';
@override String get bufferSize => 'Buffer grootte';
@@ -4682,6 +4692,8 @@ class _StringsSettingsSv implements _StringsSettingsEn {
@override String get listViewDescription => 'Visa objekt i listlayout';
@override String get useSeasonPosters => 'Använd säsongsaffischer';
@override String get showHeroSection => 'Visa hjältesektion';
@override String get useGlobalHubs => 'Använd Plex hem-layout';
@override String get useGlobalHubsDescription => 'Visar startsidans hubbar som den officiella Plex-klienten. När av visas rekommendationer per bibliotek istället.';
@override String get hardwareDecoding => 'Hårdvaruavkodning';
@override String get hardwareDecodingDescription => 'Använd hårdvaruacceleration när tillgängligt';
@override String get bufferSize => 'Bufferstorlek';
@@ -5556,6 +5568,8 @@ class _StringsSettingsZh implements _StringsSettingsEn {
@override String get listViewDescription => '以列表布局显示项目';
@override String get useSeasonPosters => '使用季海报';
@override String get showHeroSection => '显示主要精选区';
@override String get useGlobalHubs => '使用 Plex 主页布局';
@override String get useGlobalHubsDescription => '显示与官方 Plex 客户端相同的主页推荐。关闭时将显示按媒体库分类的推荐。';
@override String get hardwareDecoding => '硬件解码';
@override String get hardwareDecodingDescription => '如果可用,使用硬件加速';
@override String get bufferSize => '缓冲区大小';
@@ -6325,6 +6339,8 @@ extension on Translations {
case 'settings.listViewDescription': return 'Display items in a list layout';
case 'settings.useSeasonPosters': return 'Use Season Posters';
case 'settings.showHeroSection': return 'Show Hero Section';
case 'settings.useGlobalHubs': return 'Use Plex Home Layout';
case 'settings.useGlobalHubsDescription': return 'Show home page hubs like the official Plex client. When off, shows per-library recommendations instead.';
case 'settings.hardwareDecoding': return 'Hardware Decoding';
case 'settings.hardwareDecodingDescription': return 'Use hardware acceleration when available';
case 'settings.bufferSize': return 'Buffer Size';
@@ -6840,6 +6856,8 @@ extension on _StringsDe {
case 'settings.listViewDescription': return 'Elemente in Listenansicht anzeigen';
case 'settings.useSeasonPosters': return 'Staffelposter verwenden';
case 'settings.showHeroSection': return 'Hero-Bereich anzeigen';
case 'settings.useGlobalHubs': return 'Plex-Startseiten-Layout verwenden';
case 'settings.useGlobalHubsDescription': return 'Zeigt Startseiten-Hubs wie der offizielle Plex-Client. Wenn deaktiviert, werden stattdessen Empfehlungen pro Bibliothek angezeigt.';
case 'settings.hardwareDecoding': return 'Hardware-Decodierung';
case 'settings.hardwareDecodingDescription': return 'Hardwarebeschleunigung verwenden, sofern verfügbar';
case 'settings.bufferSize': return 'Puffergröße';
@@ -7356,6 +7374,8 @@ extension on _StringsIt {
case 'settings.listViewDescription': return 'Visualizza gli elementi in un layout a elenco';
case 'settings.useSeasonPosters': return 'Usa poster delle stagioni';
case 'settings.showHeroSection': return 'Mostra sezione principale';
case 'settings.useGlobalHubs': return 'Usa layout Home di Plex';
case 'settings.useGlobalHubsDescription': return 'Mostra gli hub della home page come il client Plex ufficiale. Se disattivato, mostra invece i suggerimenti per libreria.';
case 'settings.hardwareDecoding': return 'Decodifica Hardware';
case 'settings.hardwareDecodingDescription': return 'Utilizza l\'accelerazione hardware quando disponibile';
case 'settings.bufferSize': return 'Dimensione buffer';
@@ -7872,6 +7892,8 @@ extension on _StringsKo {
case 'settings.listViewDescription': return '항목을 목록 레이아웃으로 표시합니다';
case 'settings.useSeasonPosters': return '시즌 포스터 사용';
case 'settings.showHeroSection': return '주요 추천 영역 표시';
case 'settings.useGlobalHubs': return 'Plex 홈 레이아웃 사용';
case 'settings.useGlobalHubsDescription': return '공식 Plex 클라이언트처럼 홈 페이지 허브를 표시합니다. 끄면 라이브러리별 추천이 대신 표시됩니다.';
case 'settings.hardwareDecoding': return '하드웨어 디코딩';
case 'settings.hardwareDecodingDescription': return '가능한 경우 하드웨어 가속을 사용합니다';
case 'settings.bufferSize': return '버퍼 크기';
@@ -8388,6 +8410,8 @@ extension on _StringsNl {
case 'settings.listViewDescription': return 'Items weergeven in een lijstindeling';
case 'settings.useSeasonPosters': return 'Gebruik seizoenposters';
case 'settings.showHeroSection': return 'Toon hoofdsectie';
case 'settings.useGlobalHubs': return 'Plex Home-indeling gebruiken';
case 'settings.useGlobalHubsDescription': return 'Toon startpagina-hubs zoals de officiële Plex-client. Indien uitgeschakeld, worden in plaats daarvan aanbevelingen per bibliotheek getoond.';
case 'settings.hardwareDecoding': return 'Hardware decodering';
case 'settings.hardwareDecodingDescription': return 'Gebruik hardware versnelling indien beschikbaar';
case 'settings.bufferSize': return 'Buffer grootte';
@@ -8904,6 +8928,8 @@ extension on _StringsSv {
case 'settings.listViewDescription': return 'Visa objekt i listlayout';
case 'settings.useSeasonPosters': return 'Använd säsongsaffischer';
case 'settings.showHeroSection': return 'Visa hjältesektion';
case 'settings.useGlobalHubs': return 'Använd Plex hem-layout';
case 'settings.useGlobalHubsDescription': return 'Visar startsidans hubbar som den officiella Plex-klienten. När av visas rekommendationer per bibliotek istället.';
case 'settings.hardwareDecoding': return 'Hårdvaruavkodning';
case 'settings.hardwareDecodingDescription': return 'Använd hårdvaruacceleration när tillgängligt';
case 'settings.bufferSize': return 'Bufferstorlek';
@@ -9420,6 +9446,8 @@ extension on _StringsZh {
case 'settings.listViewDescription': return '以列表布局显示项目';
case 'settings.useSeasonPosters': return '使用季海报';
case 'settings.showHeroSection': return '显示主要精选区';
case 'settings.useGlobalHubs': return '使用 Plex 主页布局';
case 'settings.useGlobalHubsDescription': return '显示与官方 Plex 客户端相同的主页推荐。关闭时将显示按媒体库分类的推荐。';
case 'settings.hardwareDecoding': return '硬件解码';
case 'settings.hardwareDecodingDescription': return '如果可用,使用硬件加速';
case 'settings.bufferSize': return '缓冲区大小';
+2
View File
@@ -84,6 +84,8 @@
"listViewDescription": "Display items in a list layout",
"useSeasonPosters": "Use Season Posters",
"showHeroSection": "Show Hero Section",
"useGlobalHubs": "Use Plex Home Layout",
"useGlobalHubsDescription": "Show home page hubs like the official Plex client. When off, shows per-library recommendations instead.",
"hardwareDecoding": "Hardware Decoding",
"hardwareDecodingDescription": "Use hardware acceleration when available",
"bufferSize": "Buffer Size",
+2
View File
@@ -85,6 +85,8 @@
"listViewDescription": "Elemente in Listenansicht anzeigen",
"useSeasonPosters": "Staffelposter verwenden",
"showHeroSection": "Hero-Bereich anzeigen",
"useGlobalHubs": "Plex-Startseiten-Layout verwenden",
"useGlobalHubsDescription": "Zeigt Startseiten-Hubs wie der offizielle Plex-Client. Wenn deaktiviert, werden stattdessen Empfehlungen pro Bibliothek angezeigt.",
"hardwareDecoding": "Hardware-Decodierung",
"hardwareDecodingDescription": "Hardwarebeschleunigung verwenden, sofern verfügbar",
"bufferSize": "Puffergröße",
+2
View File
@@ -85,6 +85,8 @@
"listViewDescription": "Visualizza gli elementi in un layout a elenco",
"useSeasonPosters": "Usa poster delle stagioni",
"showHeroSection": "Mostra sezione principale",
"useGlobalHubs": "Usa layout Home di Plex",
"useGlobalHubsDescription": "Mostra gli hub della home page come il client Plex ufficiale. Se disattivato, mostra invece i suggerimenti per libreria.",
"hardwareDecoding": "Decodifica Hardware",
"hardwareDecodingDescription": "Utilizza l'accelerazione hardware quando disponibile",
"bufferSize": "Dimensione buffer",
+2
View File
@@ -85,6 +85,8 @@
"listViewDescription": "항목을 목록 레이아웃으로 표시합니다",
"useSeasonPosters": "시즌 포스터 사용",
"showHeroSection": "주요 추천 영역 표시",
"useGlobalHubs": "Plex 홈 레이아웃 사용",
"useGlobalHubsDescription": "공식 Plex 클라이언트처럼 홈 페이지 허브를 표시합니다. 끄면 라이브러리별 추천이 대신 표시됩니다.",
"hardwareDecoding": "하드웨어 디코딩",
"hardwareDecodingDescription": "가능한 경우 하드웨어 가속을 사용합니다",
"bufferSize": "버퍼 크기",
+2
View File
@@ -85,6 +85,8 @@
"listViewDescription": "Items weergeven in een lijstindeling",
"useSeasonPosters": "Gebruik seizoenposters",
"showHeroSection": "Toon hoofdsectie",
"useGlobalHubs": "Plex Home-indeling gebruiken",
"useGlobalHubsDescription": "Toon startpagina-hubs zoals de officiële Plex-client. Indien uitgeschakeld, worden in plaats daarvan aanbevelingen per bibliotheek getoond.",
"hardwareDecoding": "Hardware decodering",
"hardwareDecodingDescription": "Gebruik hardware versnelling indien beschikbaar",
"bufferSize": "Buffer grootte",
+2
View File
@@ -85,6 +85,8 @@
"listViewDescription": "Visa objekt i listlayout",
"useSeasonPosters": "Använd säsongsaffischer",
"showHeroSection": "Visa hjältesektion",
"useGlobalHubs": "Använd Plex hem-layout",
"useGlobalHubsDescription": "Visar startsidans hubbar som den officiella Plex-klienten. När av visas rekommendationer per bibliotek istället.",
"hardwareDecoding": "Hårdvaruavkodning",
"hardwareDecodingDescription": "Använd hårdvaruacceleration när tillgängligt",
"bufferSize": "Bufferstorlek",
+2
View File
@@ -85,6 +85,8 @@
"listViewDescription": "以列表布局显示项目",
"useSeasonPosters": "使用季海报",
"showHeroSection": "显示主要精选区",
"useGlobalHubs": "使用 Plex 主页布局",
"useGlobalHubsDescription": "显示与官方 Plex 客户端相同的主页推荐。关闭时将显示按媒体库分类的推荐。",
"hardwareDecoding": "硬件解码",
"hardwareDecodingDescription": "如果可用,使用硬件加速",
"bufferSize": "缓冲区大小",
+13
View File
@@ -7,6 +7,7 @@ class SettingsProvider extends ChangeNotifier {
ViewMode _viewMode = ViewMode.grid;
bool _useSeasonPoster = false;
bool _showHeroSection = true;
bool _useGlobalHubs = true;
bool _isInitialized = false;
Future<void>? _initFuture;
@@ -27,6 +28,7 @@ class SettingsProvider extends ChangeNotifier {
_viewMode = _settingsService!.getViewMode();
_useSeasonPoster = _settingsService!.getUseSeasonPoster();
_showHeroSection = _settingsService!.getShowHeroSection();
_useGlobalHubs = _settingsService!.getUseGlobalHubs();
_isInitialized = true;
notifyListeners();
}
@@ -42,6 +44,8 @@ class SettingsProvider extends ChangeNotifier {
bool get showHeroSection => _showHeroSection;
bool get useGlobalHubs => _useGlobalHubs;
Future<void> setLibraryDensity(LibraryDensity density) async {
if (!_isInitialized) await _initializeSettings();
if (_libraryDensity != density) {
@@ -78,6 +82,15 @@ class SettingsProvider extends ChangeNotifier {
}
}
Future<void> setUseGlobalHubs(bool value) async {
if (!_isInitialized) await _initializeSettings();
if (_useGlobalHubs != value) {
_useGlobalHubs = value;
await _settingsService!.setUseGlobalHubs(value);
notifyListeners();
}
}
String get libraryDensityDisplayName {
switch (_libraryDensity) {
case LibraryDensity.compact:
+16 -17
View File
@@ -479,16 +479,25 @@ class _DiscoverScreenState extends State<DiscoverScreen>
});
try {
appLogger.d('Fetching onDeck and hubs from all Plex servers');
appLogger.d('Fetching onDeck and global hubs from all Plex servers');
final multiServerProvider = Provider.of<MultiServerProvider>(context, listen: false);
if (!multiServerProvider.hasConnectedServers) {
throw Exception('No servers available');
}
// Start OnDeck and libraries fetch in parallel
// Get hidden libraries for filtering
final hiddenLibrariesProvider = Provider.of<HiddenLibrariesProvider>(context, listen: false);
// Get settings for hub mode preference
final settingsProvider = Provider.of<SettingsProvider>(context, listen: false);
// Start OnDeck and hubs fetch in parallel
final onDeckFuture = multiServerProvider.aggregationService.getOnDeckFromAllServers(limit: 20);
final librariesFuture = multiServerProvider.aggregationService.getLibrariesFromAllServersGrouped();
final hubsFuture = multiServerProvider.aggregationService.getHubsFromAllServers(
hiddenLibraryKeys: hiddenLibrariesProvider.hiddenLibraryKeys,
useGlobalHubs: settingsProvider.useGlobalHubs,
);
// Wait for OnDeck to complete and show it immediately
final onDeck = await onDeckFuture;
@@ -511,32 +520,22 @@ class _DiscoverScreenState extends State<DiscoverScreen>
_heroController.jumpToPage(0);
}
// Wait for libraries and then fetch hubs
final librariesByServer = await librariesFuture;
// Wait for global hubs
final allHubs = await hubsFuture;
if (!mounted) return;
// Get hidden libraries to filter from hubs
final hiddenLibrariesProvider = Provider.of<HiddenLibrariesProvider>(context, listen: false);
// Fetch hubs using the pre-fetched libraries and hidden keys
final allHubs = await multiServerProvider.aggregationService.getHubsFromAllServers(
librariesByServer: librariesByServer,
hiddenLibraryKeys: hiddenLibrariesProvider.hiddenLibraryKeys,
);
// Filter out duplicate hubs that we already fetch separately
// Filter out Continue Watching / On Deck hubs (handled separately in hero section)
final filteredHubs = allHubs.where((hub) {
final hubId = hub.hubIdentifier?.toLowerCase() ?? '';
final title = hub.title.toLowerCase();
// Skip "Continue Watching" and "On Deck" hubs (we handle these separately)
return !hubId.contains('ondeck') &&
!hubId.contains('continue') &&
!title.contains('continue watching') &&
!title.contains('on deck');
}).toList();
appLogger.d('Received ${onDeck.length} on deck items and ${filteredHubs.length} hubs from all servers');
appLogger.d('Received ${onDeck.length} on deck items and ${filteredHubs.length} global hubs from all servers');
if (!mounted) return;
setState(() {
_hubs = filteredHubs;
+13
View File
@@ -213,6 +213,19 @@ class _SettingsScreenState extends State<SettingsScreen> {
);
},
),
Consumer<SettingsProvider>(
builder: (context, settingsProvider, child) {
return SwitchListTile(
secondary: const AppIcon(Symbols.home_rounded, fill: 1),
title: Text(t.settings.useGlobalHubs),
subtitle: Text(t.settings.useGlobalHubsDescription),
value: settingsProvider.useGlobalHubs,
onChanged: (value) async {
await settingsProvider.setUseGlobalHubs(value);
},
);
},
),
],
),
);
+96 -4
View File
@@ -98,11 +98,15 @@ class DataAggregationService {
return librariesByServer;
}
/// Fetch recommendation hubs from all servers using pre-fetched libraries
/// Fetch recommendation hubs from all servers
/// When useGlobalHubs is true (default), uses the global /hubs endpoint
/// to get the true home page hubs like "Recently Added Movies", "Recently Added TV"
/// When false, uses per-library hubs from /hubs/sections/{sectionId}
Future<List<PlexHub>> getHubsFromAllServers({
int? limit,
Map<String, List<PlexLibrary>>? librariesByServer,
Set<String>? hiddenLibraryKeys,
Map<String, List<PlexLibrary>>? librariesByServer,
bool useGlobalHubs = true,
}) async {
final clients = _serverManager.onlineClients;
@@ -111,10 +115,98 @@ class DataAggregationService {
return [];
}
if (useGlobalHubs) {
return _fetchGlobalHubs(clients, limit: limit, hiddenLibraryKeys: hiddenLibraryKeys);
} else {
return _fetchLibraryHubs(
clients,
limit: limit,
hiddenLibraryKeys: hiddenLibraryKeys,
librariesByServer: librariesByServer,
);
}
}
/// Fetch global hubs using /hubs endpoint (matches official Plex client)
Future<List<PlexHub>> _fetchGlobalHubs(
Map<String, PlexClient> clients, {
int? limit,
Set<String>? hiddenLibraryKeys,
}) async {
appLogger.d('Fetching global hubs from ${clients.length} servers');
final allHubs = <PlexHub>[];
// Fetch global hubs from all servers in parallel
final hubFutures = clients.entries.map((entry) async {
final serverId = entry.key;
final client = entry.value;
try {
final hubs = await client.getGlobalHubs(limit: limit ?? 10);
appLogger.d('Fetched ${hubs.length} global hubs from server $serverId');
// Filter out items from hidden libraries if specified
if (hiddenLibraryKeys != null && hiddenLibraryKeys.isNotEmpty) {
return hubs.map((hub) {
final filteredItems = hub.items.where((item) {
// 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';
return !hiddenLibraryKeys.contains(globalKey);
}).toList();
if (filteredItems.isEmpty) return null;
return PlexHub(
hubKey: hub.hubKey,
title: hub.title,
type: hub.type,
hubIdentifier: hub.hubIdentifier,
size: filteredItems.length,
more: hub.more,
items: filteredItems,
serverId: hub.serverId,
serverName: hub.serverName,
);
}).whereType<PlexHub>().toList();
}
return hubs;
} catch (e, stackTrace) {
appLogger.e('Failed to fetch hubs from server $serverId', error: e, stackTrace: stackTrace);
_serverManager.updateServerStatus(serverId, false);
return <PlexHub>[];
}
});
final results = await Future.wait(hubFutures);
// Flatten results
for (final hubs in results) {
allHubs.addAll(hubs);
}
// Apply limit if specified (applied after merging from all servers)
final result = limit != null && limit < allHubs.length ? allHubs.sublist(0, limit) : allHubs;
appLogger.i('Fetched ${result.length} global hubs from all servers');
return result;
}
/// Fetch per-library hubs using /hubs/sections/{sectionId} endpoint
Future<List<PlexHub>> _fetchLibraryHubs(
Map<String, PlexClient> clients, {
int? limit,
Set<String>? hiddenLibraryKeys,
Map<String, List<PlexLibrary>>? librariesByServer,
}) async {
// Use pre-fetched libraries or fetch them if not provided
final libraries = librariesByServer ?? await getLibrariesFromAllServersGrouped();
appLogger.d('Fetching hubs from ${clients.length} servers');
appLogger.d('Fetching per-library hubs from ${clients.length} servers');
final allHubs = <PlexHub>[];
@@ -185,7 +277,7 @@ class DataAggregationService {
// Apply limit if specified
final result = limit != null && limit < allHubs.length ? allHubs.sublist(0, limit) : allHubs;
appLogger.i('Fetched ${result.length} hubs from all servers');
appLogger.i('Fetched ${result.length} library hubs from all servers');
return result;
}
+51
View File
@@ -1249,6 +1249,57 @@ class PlexClient {
return [];
}
/// Get global hubs (home page recommendations)
/// Returns actual home page hubs like "Recently Added Movies", "Recently Added TV", etc.
/// This matches the official Plex client's home page layout.
Future<List<PlexHub>> getGlobalHubs({int limit = 10}) async {
try {
final response = await _dio.get(
'/hubs',
queryParameters: {'count': limit, 'includeGuids': 1},
);
final container = _getMediaContainer(response);
if (container != null && container['Hub'] != null) {
final hubs = <PlexHub>[];
for (final hubJson in container['Hub'] as List) {
try {
final hub = PlexHub.fromJson(hubJson);
if (hub.items.isEmpty) continue;
// Filter to only video content (movies, shows, episodes) and tag with server info
final videoItems = hub.items
.where((item) => item.isMovie || item.isShow || item.isEpisode)
.map((item) => item.copyWith(serverId: serverId, serverName: serverName))
.toList();
if (videoItems.isNotEmpty) {
hubs.add(
PlexHub(
hubKey: hub.hubKey,
title: hub.title,
type: hub.type,
hubIdentifier: hub.hubIdentifier,
size: hub.size,
more: hub.more,
items: videoItems,
serverId: serverId,
serverName: serverName,
),
);
}
} catch (e) {
appLogger.w('Failed to parse global hub', error: e);
}
}
return hubs;
}
} catch (e) {
appLogger.e('Failed to get global hubs: $e');
}
return [];
}
/// Get full content from a hub using its hub key
/// Returns the complete list of metadata items in the hub
Future<List<PlexMetadata>> getHubContent(String hubKey) async {
+10
View File
@@ -30,6 +30,7 @@ class SettingsService extends BaseSharedPreferencesService {
static const String _keySeekTimeLarge = 'seek_time_large';
static const String _keyMediaVersionPreferences = 'media_version_preferences';
static const String _keyShowHeroSection = 'show_hero_section';
static const String _keyUseGlobalHubs = 'use_global_hubs';
static const String _keySleepTimerDuration = 'sleep_timer_duration';
static const String _keyAudioSyncOffset = 'audio_sync_offset';
static const String _keySubtitleSyncOffset = 'subtitle_sync_offset';
@@ -170,6 +171,15 @@ class SettingsService extends BaseSharedPreferencesService {
return prefs.getBool(_keyShowHeroSection) ?? true; // Default: true (show hero section)
}
// Use Global Hubs (true = global /hubs endpoint, false = per-library hubs)
Future<void> setUseGlobalHubs(bool enabled) async {
await prefs.setBool(_keyUseGlobalHubs, enabled);
}
bool getUseGlobalHubs() {
return prefs.getBool(_keyUseGlobalHubs) ?? true; // Default: true (use global hubs like official Plex)
}
// Seek Time Small (in seconds)
Future<void> setSeekTimeSmall(int seconds) async {
await prefs.setInt(_keySeekTimeSmall, seconds);