+30
-2
@@ -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 '缓冲区大小';
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -85,6 +85,8 @@
|
||||
"listViewDescription": "항목을 목록 레이아웃으로 표시합니다",
|
||||
"useSeasonPosters": "시즌 포스터 사용",
|
||||
"showHeroSection": "주요 추천 영역 표시",
|
||||
"useGlobalHubs": "Plex 홈 레이아웃 사용",
|
||||
"useGlobalHubsDescription": "공식 Plex 클라이언트처럼 홈 페이지 허브를 표시합니다. 끄면 라이브러리별 추천이 대신 표시됩니다.",
|
||||
"hardwareDecoding": "하드웨어 디코딩",
|
||||
"hardwareDecodingDescription": "가능한 경우 하드웨어 가속을 사용합니다",
|
||||
"bufferSize": "버퍼 크기",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -85,6 +85,8 @@
|
||||
"listViewDescription": "以列表布局显示项目",
|
||||
"useSeasonPosters": "使用季海报",
|
||||
"showHeroSection": "显示主要精选区",
|
||||
"useGlobalHubs": "使用 Plex 主页布局",
|
||||
"useGlobalHubsDescription": "显示与官方 Plex 客户端相同的主页推荐。关闭时将显示按媒体库分类的推荐。",
|
||||
"hardwareDecoding": "硬件解码",
|
||||
"hardwareDecodingDescription": "如果可用,使用硬件加速",
|
||||
"bufferSize": "缓冲区大小",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user