diff --git a/lib/i18n/de.i18n.json b/lib/i18n/de.i18n.json index b51cce52..ffbcd370 100644 --- a/lib/i18n/de.i18n.json +++ b/lib/i18n/de.i18n.json @@ -94,6 +94,8 @@ "useGlobalHubsDescription": "Zeigt Startseiten-Hubs wie der offizielle Plex-Client. Wenn deaktiviert, werden stattdessen Empfehlungen pro Bibliothek angezeigt.", "showServerNameOnHubs": "Servername bei Hubs anzeigen", "showServerNameOnHubsDescription": "Zeigt immer den Servernamen in Hub-Titeln an. Wenn deaktiviert, nur bei doppelten Hub-Namen.", + "alwaysKeepSidebarOpen": "Seitenleiste immer geöffnet halten", + "alwaysKeepSidebarOpenDescription": "Seitenleiste bleibt erweitert und Inhaltsbereich passt sich an", "playerBackend": "Player-Backend", "exoPlayer": "ExoPlayer (Empfohlen)", "exoPlayerDescription": "Android-nativer Player mit besserer Hardware-Unterstützung", diff --git a/lib/i18n/en.i18n.json b/lib/i18n/en.i18n.json index 032a6310..06120810 100644 --- a/lib/i18n/en.i18n.json +++ b/lib/i18n/en.i18n.json @@ -93,6 +93,8 @@ "useGlobalHubsDescription": "Show home page hubs like the official Plex client. When off, shows per-library recommendations instead.", "showServerNameOnHubs": "Show Server Name on Hubs", "showServerNameOnHubsDescription": "Always display the server name in hub titles. When off, only shows for duplicate hub names.", + "alwaysKeepSidebarOpen": "Always Keep Sidebar Open", + "alwaysKeepSidebarOpenDescription": "Sidebar stays expanded and content area adjusts to fit", "playerBackend": "Player Backend", "exoPlayer": "ExoPlayer (Recommended)", "exoPlayerDescription": "Android native player with better hardware support", diff --git a/lib/i18n/fr.i18n.json b/lib/i18n/fr.i18n.json index 1467f3b4..4eef3272 100644 --- a/lib/i18n/fr.i18n.json +++ b/lib/i18n/fr.i18n.json @@ -93,6 +93,8 @@ "useGlobalHubsDescription": "Afficher les hubs de la page d'accueil comme le client Plex officiel. Lorsque cette option est désactivée, affiche à la place les recommandations par bibliothèque.", "showServerNameOnHubs": "Afficher le nom du serveur sur les hubs", "showServerNameOnHubsDescription": "Toujours afficher le nom du serveur dans les titres des hubs. Lorsque cette option est désactivée, seuls les noms de hubs en double s'affichent.", + "alwaysKeepSidebarOpen": "Toujours garder la barre latérale ouverte", + "alwaysKeepSidebarOpenDescription": "La barre latérale reste étendue et la zone de contenu s'adapte", "playerBackend": "Moteur de lecture", "exoPlayer": "ExoPlayer (Recommandé)", "exoPlayerDescription": "Lecteur natif Android avec meilleur support matériel", diff --git a/lib/i18n/it.i18n.json b/lib/i18n/it.i18n.json index 9bb92ad2..0dd1ec42 100644 --- a/lib/i18n/it.i18n.json +++ b/lib/i18n/it.i18n.json @@ -94,6 +94,8 @@ "useGlobalHubsDescription": "Mostra gli hub della home page come il client Plex ufficiale. Se disattivato, mostra invece i suggerimenti per libreria.", "showServerNameOnHubs": "Mostra nome server sugli hub", "showServerNameOnHubsDescription": "Mostra sempre il nome del server nei titoli degli hub. Se disattivato, solo per nomi hub duplicati.", + "alwaysKeepSidebarOpen": "Mantieni sempre aperta la barra laterale", + "alwaysKeepSidebarOpenDescription": "La barra laterale rimane espansa e l'area del contenuto si adatta", "playerBackend": "Motore di riproduzione", "exoPlayer": "ExoPlayer (Consigliato)", "exoPlayerDescription": "Lettore nativo Android con migliore supporto hardware", diff --git a/lib/i18n/ko.i18n.json b/lib/i18n/ko.i18n.json index 15b81748..497f267a 100644 --- a/lib/i18n/ko.i18n.json +++ b/lib/i18n/ko.i18n.json @@ -94,6 +94,8 @@ "useGlobalHubsDescription": "공식 Plex 클라이언트처럼 홈 페이지 허브를 표시합니다. 끄면 라이브러리별 추천이 대신 표시됩니다.", "showServerNameOnHubs": "허브에 서버 이름 표시", "showServerNameOnHubsDescription": "허브 제목에 항상 서버 이름을 표시합니다. 끄면 중복된 허브 이름에만 표시됩니다.", + "alwaysKeepSidebarOpen": "사이드바 항상 열어두기", + "alwaysKeepSidebarOpenDescription": "사이드바가 확장된 상태로 유지되고 콘텐츠 영역이 맞춰집니다", "playerBackend": "플레이어 백엔드", "exoPlayer": "ExoPlayer (권장)", "exoPlayerDescription": "더 나은 하드웨어 지원을 제공하는 Android 네이티브 플레이어", diff --git a/lib/i18n/nl.i18n.json b/lib/i18n/nl.i18n.json index 9a022cc3..cad3479f 100644 --- a/lib/i18n/nl.i18n.json +++ b/lib/i18n/nl.i18n.json @@ -94,6 +94,8 @@ "useGlobalHubsDescription": "Toon startpagina-hubs zoals de officiële Plex-client. Indien uitgeschakeld, worden in plaats daarvan aanbevelingen per bibliotheek getoond.", "showServerNameOnHubs": "Servernaam tonen bij hubs", "showServerNameOnHubsDescription": "Toon altijd de servernaam in hub-titels. Indien uitgeschakeld, alleen bij dubbele hub-namen.", + "alwaysKeepSidebarOpen": "Zijbalk altijd open houden", + "alwaysKeepSidebarOpenDescription": "Zijbalk blijft uitgevouwen en inhoudsgebied past zich aan", "playerBackend": "Speler backend", "exoPlayer": "ExoPlayer (Aanbevolen)", "exoPlayerDescription": "Android-native speler met betere hardware-ondersteuning", diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index 6ff1b6a0..4af78401 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -4,9 +4,9 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 8 -/// Strings: 4420 (552 per locale) +/// Strings: 4436 (554 per locale) /// -/// Built on 2026-01-22 at 23:24 UTC +/// Built on 2026-01-23 at 22:13 UTC // coverage:ignore-file // ignore_for_file: type=lint, unused_import diff --git a/lib/i18n/strings_de.g.dart b/lib/i18n/strings_de.g.dart index 2ddda2b8..d5caa7e7 100644 --- a/lib/i18n/strings_de.g.dart +++ b/lib/i18n/strings_de.g.dart @@ -207,6 +207,8 @@ class _TranslationsSettingsDe implements TranslationsSettingsEn { @override String get useGlobalHubsDescription => 'Zeigt Startseiten-Hubs wie der offizielle Plex-Client. Wenn deaktiviert, werden stattdessen Empfehlungen pro Bibliothek angezeigt.'; @override String get showServerNameOnHubs => 'Servername bei Hubs anzeigen'; @override String get showServerNameOnHubsDescription => 'Zeigt immer den Servernamen in Hub-Titeln an. Wenn deaktiviert, nur bei doppelten Hub-Namen.'; + @override String get alwaysKeepSidebarOpen => 'Seitenleiste immer geöffnet halten'; + @override String get alwaysKeepSidebarOpenDescription => 'Seitenleiste bleibt erweitert und Inhaltsbereich passt sich an'; @override String get playerBackend => 'Player-Backend'; @override String get exoPlayer => 'ExoPlayer (Empfohlen)'; @override String get exoPlayerDescription => 'Android-nativer Player mit besserer Hardware-Unterstützung'; @@ -1044,6 +1046,8 @@ extension on TranslationsDe { 'settings.useGlobalHubsDescription' => 'Zeigt Startseiten-Hubs wie der offizielle Plex-Client. Wenn deaktiviert, werden stattdessen Empfehlungen pro Bibliothek angezeigt.', 'settings.showServerNameOnHubs' => 'Servername bei Hubs anzeigen', 'settings.showServerNameOnHubsDescription' => 'Zeigt immer den Servernamen in Hub-Titeln an. Wenn deaktiviert, nur bei doppelten Hub-Namen.', + 'settings.alwaysKeepSidebarOpen' => 'Seitenleiste immer geöffnet halten', + 'settings.alwaysKeepSidebarOpenDescription' => 'Seitenleiste bleibt erweitert und Inhaltsbereich passt sich an', 'settings.playerBackend' => 'Player-Backend', 'settings.exoPlayer' => 'ExoPlayer (Empfohlen)', 'settings.exoPlayerDescription' => 'Android-nativer Player mit besserer Hardware-Unterstützung', @@ -1470,10 +1474,10 @@ extension on TranslationsDe { 'collections.removedFromCollection' => 'Aus Sammlung entfernt', 'collections.removeFromCollectionFailed' => 'Entfernen aus Sammlung fehlgeschlagen', 'collections.removeFromCollectionError' => ({required Object error}) => 'Fehler beim Entfernen aus der Sammlung: ${error}', - 'watchTogether.title' => 'Gemeinsam Schauen', - 'watchTogether.description' => 'Inhalte synchron mit Freunden und Familie schauen', _ => null, } ?? switch (path) { + 'watchTogether.title' => 'Gemeinsam Schauen', + 'watchTogether.description' => 'Inhalte synchron mit Freunden und Familie schauen', 'watchTogether.createSession' => 'Sitzung Erstellen', 'watchTogether.creating' => 'Erstellen...', 'watchTogether.joinSession' => 'Sitzung Beitreten', diff --git a/lib/i18n/strings_en.g.dart b/lib/i18n/strings_en.g.dart index a25654ff..ce34ffef 100644 --- a/lib/i18n/strings_en.g.dart +++ b/lib/i18n/strings_en.g.dart @@ -376,6 +376,12 @@ class TranslationsSettingsEn { /// en: 'Always display the server name in hub titles. When off, only shows for duplicate hub names.' String get showServerNameOnHubsDescription => 'Always display the server name in hub titles. When off, only shows for duplicate hub names.'; + /// en: 'Always Keep Sidebar Open' + String get alwaysKeepSidebarOpen => 'Always Keep Sidebar Open'; + + /// en: 'Sidebar stays expanded and content area adjusts to fit' + String get alwaysKeepSidebarOpenDescription => 'Sidebar stays expanded and content area adjusts to fit'; + /// en: 'Player Backend' String get playerBackend => 'Player Backend'; @@ -2149,6 +2155,8 @@ extension on Translations { 'settings.useGlobalHubsDescription' => 'Show home page hubs like the official Plex client. When off, shows per-library recommendations instead.', 'settings.showServerNameOnHubs' => 'Show Server Name on Hubs', 'settings.showServerNameOnHubsDescription' => 'Always display the server name in hub titles. When off, only shows for duplicate hub names.', + 'settings.alwaysKeepSidebarOpen' => 'Always Keep Sidebar Open', + 'settings.alwaysKeepSidebarOpenDescription' => 'Sidebar stays expanded and content area adjusts to fit', 'settings.playerBackend' => 'Player Backend', 'settings.exoPlayer' => 'ExoPlayer (Recommended)', 'settings.exoPlayerDescription' => 'Android native player with better hardware support', @@ -2576,10 +2584,10 @@ extension on Translations { 'watchTogether.hostControlsPlayback' => 'Host controls playback', 'watchTogether.anyoneCanControl' => 'Anyone can control playback', 'watchTogether.hostControls' => 'Host controls', - 'watchTogether.anyoneControls' => 'Anyone controls', - 'watchTogether.participants' => 'Participants', _ => null, } ?? switch (path) { + 'watchTogether.anyoneControls' => 'Anyone controls', + 'watchTogether.participants' => 'Participants', 'watchTogether.host' => 'Host', 'watchTogether.hostBadge' => 'HOST', 'watchTogether.youAreHost' => 'You are the host', diff --git a/lib/i18n/strings_fr.g.dart b/lib/i18n/strings_fr.g.dart index fabe0c5e..c14ce866 100644 --- a/lib/i18n/strings_fr.g.dart +++ b/lib/i18n/strings_fr.g.dart @@ -206,6 +206,8 @@ class _TranslationsSettingsFr implements TranslationsSettingsEn { @override String get useGlobalHubsDescription => 'Afficher les hubs de la page d\'accueil comme le client Plex officiel. Lorsque cette option est désactivée, affiche à la place les recommandations par bibliothèque.'; @override String get showServerNameOnHubs => 'Afficher le nom du serveur sur les hubs'; @override String get showServerNameOnHubsDescription => 'Toujours afficher le nom du serveur dans les titres des hubs. Lorsque cette option est désactivée, seuls les noms de hubs en double s\'affichent.'; + @override String get alwaysKeepSidebarOpen => 'Toujours garder la barre latérale ouverte'; + @override String get alwaysKeepSidebarOpenDescription => 'La barre latérale reste étendue et la zone de contenu s\'adapte'; @override String get playerBackend => 'Moteur de lecture'; @override String get exoPlayer => 'ExoPlayer (Recommandé)'; @override String get exoPlayerDescription => 'Lecteur natif Android avec meilleur support matériel'; @@ -1041,6 +1043,8 @@ extension on TranslationsFr { 'settings.useGlobalHubsDescription' => 'Afficher les hubs de la page d\'accueil comme le client Plex officiel. Lorsque cette option est désactivée, affiche à la place les recommandations par bibliothèque.', 'settings.showServerNameOnHubs' => 'Afficher le nom du serveur sur les hubs', 'settings.showServerNameOnHubsDescription' => 'Toujours afficher le nom du serveur dans les titres des hubs. Lorsque cette option est désactivée, seuls les noms de hubs en double s\'affichent.', + 'settings.alwaysKeepSidebarOpen' => 'Toujours garder la barre latérale ouverte', + 'settings.alwaysKeepSidebarOpenDescription' => 'La barre latérale reste étendue et la zone de contenu s\'adapte', 'settings.playerBackend' => 'Moteur de lecture', 'settings.exoPlayer' => 'ExoPlayer (Recommandé)', 'settings.exoPlayerDescription' => 'Lecteur natif Android avec meilleur support matériel', @@ -1468,10 +1472,10 @@ extension on TranslationsFr { 'watchTogether.hostControlsPlayback' => 'L\'hôte contrôle la lecture', 'watchTogether.anyoneCanControl' => 'Tout le monde peut contrôler la lecture', 'watchTogether.hostControls' => 'Commandes de l\'hôte', - 'watchTogether.anyoneControls' => 'Tout le monde contrôle', - 'watchTogether.participants' => 'Participants', _ => null, } ?? switch (path) { + 'watchTogether.anyoneControls' => 'Tout le monde contrôle', + 'watchTogether.participants' => 'Participants', 'watchTogether.host' => 'Hôte', 'watchTogether.hostBadge' => 'HOST', 'watchTogether.youAreHost' => 'Vous êtes l\'hôte', diff --git a/lib/i18n/strings_it.g.dart b/lib/i18n/strings_it.g.dart index c7286dc3..c9a57ab3 100644 --- a/lib/i18n/strings_it.g.dart +++ b/lib/i18n/strings_it.g.dart @@ -207,6 +207,8 @@ class _TranslationsSettingsIt implements TranslationsSettingsEn { @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 showServerNameOnHubs => 'Mostra nome server sugli hub'; @override String get showServerNameOnHubsDescription => 'Mostra sempre il nome del server nei titoli degli hub. Se disattivato, solo per nomi hub duplicati.'; + @override String get alwaysKeepSidebarOpen => 'Mantieni sempre aperta la barra laterale'; + @override String get alwaysKeepSidebarOpenDescription => 'La barra laterale rimane espansa e l\'area del contenuto si adatta'; @override String get playerBackend => 'Motore di riproduzione'; @override String get exoPlayer => 'ExoPlayer (Consigliato)'; @override String get exoPlayerDescription => 'Lettore nativo Android con migliore supporto hardware'; @@ -1044,6 +1046,8 @@ extension on TranslationsIt { 'settings.useGlobalHubsDescription' => 'Mostra gli hub della home page come il client Plex ufficiale. Se disattivato, mostra invece i suggerimenti per libreria.', 'settings.showServerNameOnHubs' => 'Mostra nome server sugli hub', 'settings.showServerNameOnHubsDescription' => 'Mostra sempre il nome del server nei titoli degli hub. Se disattivato, solo per nomi hub duplicati.', + 'settings.alwaysKeepSidebarOpen' => 'Mantieni sempre aperta la barra laterale', + 'settings.alwaysKeepSidebarOpenDescription' => 'La barra laterale rimane espansa e l\'area del contenuto si adatta', 'settings.playerBackend' => 'Motore di riproduzione', 'settings.exoPlayer' => 'ExoPlayer (Consigliato)', 'settings.exoPlayerDescription' => 'Lettore nativo Android con migliore supporto hardware', @@ -1470,10 +1474,10 @@ extension on TranslationsIt { 'collections.removedFromCollection' => 'Rimosso dalla raccolta', 'collections.removeFromCollectionFailed' => 'Impossibile rimuovere dalla raccolta', 'collections.removeFromCollectionError' => ({required Object error}) => 'Errore durante la rimozione dalla raccolta: ${error}', - 'watchTogether.title' => 'Guarda Insieme', - 'watchTogether.description' => 'Guarda contenuti in sincronia con amici e familiari', _ => null, } ?? switch (path) { + 'watchTogether.title' => 'Guarda Insieme', + 'watchTogether.description' => 'Guarda contenuti in sincronia con amici e familiari', 'watchTogether.createSession' => 'Crea Sessione', 'watchTogether.creating' => 'Creazione...', 'watchTogether.joinSession' => 'Unisciti alla Sessione', diff --git a/lib/i18n/strings_ko.g.dart b/lib/i18n/strings_ko.g.dart index a9f9a4c1..3a94600f 100644 --- a/lib/i18n/strings_ko.g.dart +++ b/lib/i18n/strings_ko.g.dart @@ -207,6 +207,8 @@ class _TranslationsSettingsKo implements TranslationsSettingsEn { @override String get useGlobalHubsDescription => '공식 Plex 클라이언트처럼 홈 페이지 허브를 표시합니다. 끄면 라이브러리별 추천이 대신 표시됩니다.'; @override String get showServerNameOnHubs => '허브에 서버 이름 표시'; @override String get showServerNameOnHubsDescription => '허브 제목에 항상 서버 이름을 표시합니다. 끄면 중복된 허브 이름에만 표시됩니다.'; + @override String get alwaysKeepSidebarOpen => '사이드바 항상 열어두기'; + @override String get alwaysKeepSidebarOpenDescription => '사이드바가 확장된 상태로 유지되고 콘텐츠 영역이 맞춰집니다'; @override String get playerBackend => '플레이어 백엔드'; @override String get exoPlayer => 'ExoPlayer (권장)'; @override String get exoPlayerDescription => '더 나은 하드웨어 지원을 제공하는 Android 네이티브 플레이어'; @@ -1044,6 +1046,8 @@ extension on TranslationsKo { 'settings.useGlobalHubsDescription' => '공식 Plex 클라이언트처럼 홈 페이지 허브를 표시합니다. 끄면 라이브러리별 추천이 대신 표시됩니다.', 'settings.showServerNameOnHubs' => '허브에 서버 이름 표시', 'settings.showServerNameOnHubsDescription' => '허브 제목에 항상 서버 이름을 표시합니다. 끄면 중복된 허브 이름에만 표시됩니다.', + 'settings.alwaysKeepSidebarOpen' => '사이드바 항상 열어두기', + 'settings.alwaysKeepSidebarOpenDescription' => '사이드바가 확장된 상태로 유지되고 콘텐츠 영역이 맞춰집니다', 'settings.playerBackend' => '플레이어 백엔드', 'settings.exoPlayer' => 'ExoPlayer (권장)', 'settings.exoPlayerDescription' => '더 나은 하드웨어 지원을 제공하는 Android 네이티브 플레이어', @@ -1470,10 +1474,10 @@ extension on TranslationsKo { 'watchTogether.inSession' => '세션 중', 'watchTogether.sessionCode' => '세션 코드', 'watchTogether.hostControlsPlayback' => '호스트 재생 제어', - 'watchTogether.anyoneCanControl' => '누구나 재생 제어 가능', - 'watchTogether.hostControls' => '호스트 제어', _ => null, } ?? switch (path) { + 'watchTogether.anyoneCanControl' => '누구나 재생 제어 가능', + 'watchTogether.hostControls' => '호스트 제어', 'watchTogether.anyoneControls' => '누구나 제어', 'watchTogether.participants' => '참가자', 'watchTogether.host' => '호스트', diff --git a/lib/i18n/strings_nl.g.dart b/lib/i18n/strings_nl.g.dart index dd4f6dcc..20c73070 100644 --- a/lib/i18n/strings_nl.g.dart +++ b/lib/i18n/strings_nl.g.dart @@ -207,6 +207,8 @@ class _TranslationsSettingsNl implements TranslationsSettingsEn { @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 showServerNameOnHubs => 'Servernaam tonen bij hubs'; @override String get showServerNameOnHubsDescription => 'Toon altijd de servernaam in hub-titels. Indien uitgeschakeld, alleen bij dubbele hub-namen.'; + @override String get alwaysKeepSidebarOpen => 'Zijbalk altijd open houden'; + @override String get alwaysKeepSidebarOpenDescription => 'Zijbalk blijft uitgevouwen en inhoudsgebied past zich aan'; @override String get playerBackend => 'Speler backend'; @override String get exoPlayer => 'ExoPlayer (Aanbevolen)'; @override String get exoPlayerDescription => 'Android-native speler met betere hardware-ondersteuning'; @@ -1044,6 +1046,8 @@ extension on TranslationsNl { 'settings.useGlobalHubsDescription' => 'Toon startpagina-hubs zoals de officiële Plex-client. Indien uitgeschakeld, worden in plaats daarvan aanbevelingen per bibliotheek getoond.', 'settings.showServerNameOnHubs' => 'Servernaam tonen bij hubs', 'settings.showServerNameOnHubsDescription' => 'Toon altijd de servernaam in hub-titels. Indien uitgeschakeld, alleen bij dubbele hub-namen.', + 'settings.alwaysKeepSidebarOpen' => 'Zijbalk altijd open houden', + 'settings.alwaysKeepSidebarOpenDescription' => 'Zijbalk blijft uitgevouwen en inhoudsgebied past zich aan', 'settings.playerBackend' => 'Speler backend', 'settings.exoPlayer' => 'ExoPlayer (Aanbevolen)', 'settings.exoPlayerDescription' => 'Android-native speler met betere hardware-ondersteuning', @@ -1470,10 +1474,10 @@ extension on TranslationsNl { 'collections.removedFromCollection' => 'Uit collectie verwijderd', 'collections.removeFromCollectionFailed' => 'Verwijderen uit collectie mislukt', 'collections.removeFromCollectionError' => ({required Object error}) => 'Fout bij verwijderen uit collectie: ${error}', - 'watchTogether.title' => 'Samen Kijken', - 'watchTogether.description' => 'Kijk synchroon met vrienden en familie', _ => null, } ?? switch (path) { + 'watchTogether.title' => 'Samen Kijken', + 'watchTogether.description' => 'Kijk synchroon met vrienden en familie', 'watchTogether.createSession' => 'Sessie Maken', 'watchTogether.creating' => 'Maken...', 'watchTogether.joinSession' => 'Sessie Deelnemen', diff --git a/lib/i18n/strings_sv.g.dart b/lib/i18n/strings_sv.g.dart index 8c9e85b0..610771d3 100644 --- a/lib/i18n/strings_sv.g.dart +++ b/lib/i18n/strings_sv.g.dart @@ -207,6 +207,8 @@ class _TranslationsSettingsSv implements TranslationsSettingsEn { @override String get useGlobalHubsDescription => 'Visar startsidans hubbar som den officiella Plex-klienten. När av visas rekommendationer per bibliotek istället.'; @override String get showServerNameOnHubs => 'Visa servernamn på hubbar'; @override String get showServerNameOnHubsDescription => 'Visa alltid servernamnet i hubbtitlar. När av visas endast för duplicerade hubbnamn.'; + @override String get alwaysKeepSidebarOpen => 'Håll sidofältet alltid öppet'; + @override String get alwaysKeepSidebarOpenDescription => 'Sidofältet förblir expanderat och innehållsytan anpassas'; @override String get playerBackend => 'Spelarmotor'; @override String get exoPlayer => 'ExoPlayer (Rekommenderad)'; @override String get exoPlayerDescription => 'Android-nativ spelare med bättre hårdvarustöd'; @@ -1044,6 +1046,8 @@ extension on TranslationsSv { 'settings.useGlobalHubsDescription' => 'Visar startsidans hubbar som den officiella Plex-klienten. När av visas rekommendationer per bibliotek istället.', 'settings.showServerNameOnHubs' => 'Visa servernamn på hubbar', 'settings.showServerNameOnHubsDescription' => 'Visa alltid servernamnet i hubbtitlar. När av visas endast för duplicerade hubbnamn.', + 'settings.alwaysKeepSidebarOpen' => 'Håll sidofältet alltid öppet', + 'settings.alwaysKeepSidebarOpenDescription' => 'Sidofältet förblir expanderat och innehållsytan anpassas', 'settings.playerBackend' => 'Spelarmotor', 'settings.exoPlayer' => 'ExoPlayer (Rekommenderad)', 'settings.exoPlayerDescription' => 'Android-nativ spelare med bättre hårdvarustöd', @@ -1470,10 +1474,10 @@ extension on TranslationsSv { 'collections.removedFromCollection' => 'Borttagen från samling', 'collections.removeFromCollectionFailed' => 'Misslyckades med att ta bort från samling', 'collections.removeFromCollectionError' => ({required Object error}) => 'Fel vid borttagning från samling: ${error}', - 'watchTogether.title' => 'Titta Tillsammans', - 'watchTogether.description' => 'Titta på innehåll synkroniserat med vänner och familj', _ => null, } ?? switch (path) { + 'watchTogether.title' => 'Titta Tillsammans', + 'watchTogether.description' => 'Titta på innehåll synkroniserat med vänner och familj', 'watchTogether.createSession' => 'Skapa Session', 'watchTogether.creating' => 'Skapar...', 'watchTogether.joinSession' => 'Gå med i Session', diff --git a/lib/i18n/strings_zh.g.dart b/lib/i18n/strings_zh.g.dart index 2e416034..c4a77bd8 100644 --- a/lib/i18n/strings_zh.g.dart +++ b/lib/i18n/strings_zh.g.dart @@ -207,6 +207,8 @@ class _TranslationsSettingsZh implements TranslationsSettingsEn { @override String get useGlobalHubsDescription => '显示与官方 Plex 客户端相同的主页推荐。关闭时将显示按媒体库分类的推荐。'; @override String get showServerNameOnHubs => '在推荐栏显示服务器名称'; @override String get showServerNameOnHubsDescription => '始终在推荐栏标题中显示服务器名称。关闭时仅在推荐栏名称重复时显示。'; + @override String get alwaysKeepSidebarOpen => '始终保持侧边栏展开'; + @override String get alwaysKeepSidebarOpenDescription => '侧边栏保持展开状态,内容区域自动调整'; @override String get playerBackend => '播放器引擎'; @override String get exoPlayer => 'ExoPlayer(推荐)'; @override String get exoPlayerDescription => 'Android 原生播放器,硬件支持更好'; @@ -1044,6 +1046,8 @@ extension on TranslationsZh { 'settings.useGlobalHubsDescription' => '显示与官方 Plex 客户端相同的主页推荐。关闭时将显示按媒体库分类的推荐。', 'settings.showServerNameOnHubs' => '在推荐栏显示服务器名称', 'settings.showServerNameOnHubsDescription' => '始终在推荐栏标题中显示服务器名称。关闭时仅在推荐栏名称重复时显示。', + 'settings.alwaysKeepSidebarOpen' => '始终保持侧边栏展开', + 'settings.alwaysKeepSidebarOpenDescription' => '侧边栏保持展开状态,内容区域自动调整', 'settings.playerBackend' => '播放器引擎', 'settings.exoPlayer' => 'ExoPlayer(推荐)', 'settings.exoPlayerDescription' => 'Android 原生播放器,硬件支持更好', @@ -1470,10 +1474,10 @@ extension on TranslationsZh { 'collections.removedFromCollection' => '已从合集移除', 'collections.removeFromCollectionFailed' => '从合集移除失败', 'collections.removeFromCollectionError' => ({required Object error}) => '从合集移除时出错:${error}', - 'watchTogether.title' => '一起看', - 'watchTogether.description' => '与朋友和家人同步观看内容', _ => null, } ?? switch (path) { + 'watchTogether.title' => '一起看', + 'watchTogether.description' => '与朋友和家人同步观看内容', 'watchTogether.createSession' => '创建会话', 'watchTogether.creating' => '创建中...', 'watchTogether.joinSession' => '加入会话', diff --git a/lib/i18n/sv.i18n.json b/lib/i18n/sv.i18n.json index 76cbd33a..470303ea 100644 --- a/lib/i18n/sv.i18n.json +++ b/lib/i18n/sv.i18n.json @@ -94,6 +94,8 @@ "useGlobalHubsDescription": "Visar startsidans hubbar som den officiella Plex-klienten. När av visas rekommendationer per bibliotek istället.", "showServerNameOnHubs": "Visa servernamn på hubbar", "showServerNameOnHubsDescription": "Visa alltid servernamnet i hubbtitlar. När av visas endast för duplicerade hubbnamn.", + "alwaysKeepSidebarOpen": "Håll sidofältet alltid öppet", + "alwaysKeepSidebarOpenDescription": "Sidofältet förblir expanderat och innehållsytan anpassas", "playerBackend": "Spelarmotor", "exoPlayer": "ExoPlayer (Rekommenderad)", "exoPlayerDescription": "Android-nativ spelare med bättre hårdvarustöd", diff --git a/lib/i18n/zh.i18n.json b/lib/i18n/zh.i18n.json index 7b5af3ae..f9693dc5 100644 --- a/lib/i18n/zh.i18n.json +++ b/lib/i18n/zh.i18n.json @@ -94,6 +94,8 @@ "useGlobalHubsDescription": "显示与官方 Plex 客户端相同的主页推荐。关闭时将显示按媒体库分类的推荐。", "showServerNameOnHubs": "在推荐栏显示服务器名称", "showServerNameOnHubsDescription": "始终在推荐栏标题中显示服务器名称。关闭时仅在推荐栏名称重复时显示。", + "alwaysKeepSidebarOpen": "始终保持侧边栏展开", + "alwaysKeepSidebarOpenDescription": "侧边栏保持展开状态,内容区域自动调整", "playerBackend": "播放器引擎", "exoPlayer": "ExoPlayer(推荐)", "exoPlayerDescription": "Android 原生播放器,硬件支持更好", diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index dc60a6be..7152df8e 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -10,6 +10,7 @@ class SettingsProvider extends ChangeNotifier { bool _showHeroSection = true; bool _useGlobalHubs = true; bool _showServerNameOnHubs = false; + bool _alwaysKeepSidebarOpen = false; bool _isInitialized = false; Future? _initFuture; @@ -32,6 +33,7 @@ class SettingsProvider extends ChangeNotifier { _showHeroSection = _settingsService!.getShowHeroSection(); _useGlobalHubs = _settingsService!.getUseGlobalHubs(); _showServerNameOnHubs = _settingsService!.getShowServerNameOnHubs(); + _alwaysKeepSidebarOpen = _settingsService!.getAlwaysKeepSidebarOpen(); _isInitialized = true; notifyListeners(); } @@ -51,6 +53,8 @@ class SettingsProvider extends ChangeNotifier { bool get showServerNameOnHubs => _showServerNameOnHubs; + bool get alwaysKeepSidebarOpen => _alwaysKeepSidebarOpen; + Future setLibraryDensity(LibraryDensity density) async { if (!_isInitialized) await _initializeSettings(); if (_libraryDensity != density) { @@ -105,6 +109,15 @@ class SettingsProvider extends ChangeNotifier { } } + Future setAlwaysKeepSidebarOpen(bool value) async { + if (!_isInitialized) await _initializeSettings(); + if (_alwaysKeepSidebarOpen != value) { + _alwaysKeepSidebarOpen = value; + await _settingsService!.setAlwaysKeepSidebarOpen(value); + notifyListeners(); + } + } + String get libraryDensityDisplayName { switch (_libraryDensity) { case LibraryDensity.compact: diff --git a/lib/screens/main_screen.dart b/lib/screens/main_screen.dart index 5f27d1da..26acb388 100644 --- a/lib/screens/main_screen.dart +++ b/lib/screens/main_screen.dart @@ -19,6 +19,7 @@ import '../providers/multi_server_provider.dart'; import '../providers/server_state_provider.dart'; import '../providers/hidden_libraries_provider.dart'; import '../providers/playback_state_provider.dart'; +import '../providers/settings_provider.dart'; import '../services/offline_watch_sync_service.dart'; import '../providers/offline_mode_provider.dart'; import '../services/plex_auth_service.dart'; @@ -582,66 +583,75 @@ class _MainScreenState extends State with RouteAware, WindowListener final useSideNav = PlatformDetector.shouldUseSideNavigation(context); if (useSideNav) { - // Collapsed sidebar width for content padding - const collapsedSidebarWidth = SideNavigationRailState.collapsedWidth; + return Consumer( + builder: (context, settingsProvider, child) { + final alwaysExpanded = settingsProvider.alwaysKeepSidebarOpen; + final contentLeftPadding = alwaysExpanded + ? SideNavigationRailState.expandedWidth + : SideNavigationRailState.collapsedWidth; - return PopScope( - canPop: false, // Prevent system back from popping on Android TV - onPopInvokedWithResult: (didPop, result) { - // No-op: back key events bubble through widget tree and are handled - // by content screens (e.g., LibrariesScreen) or MainScreen's _handleBackKey. - // We only use PopScope to prevent the system from popping the route. - }, - child: Focus( - onKeyEvent: (node, event) => _handleBackKey(event), - child: MainScreenFocusScope( - focusSidebar: _focusSidebar, - focusContent: _focusContent, - isSidebarFocused: _isSidebarFocused, - child: SideNavigationScope( - child: Stack( - children: [ - // Content with fixed left padding for collapsed sidebar - Positioned.fill( - child: Padding( - padding: const EdgeInsets.only(left: collapsedSidebarWidth), - child: FocusScope( - node: _contentFocusScope, - // No autofocus - we control focus programmatically to prevent - // autofocus from stealing focus back after setState() rebuilds - child: IndexedStack(index: _currentIndex, children: _screens), + return PopScope( + canPop: false, // Prevent system back from popping on Android TV + onPopInvokedWithResult: (didPop, result) { + // No-op: back key events bubble through widget tree and are handled + // by content screens (e.g., LibrariesScreen) or MainScreen's _handleBackKey. + // We only use PopScope to prevent the system from popping the route. + }, + child: Focus( + onKeyEvent: (node, event) => _handleBackKey(event), + child: MainScreenFocusScope( + focusSidebar: _focusSidebar, + focusContent: _focusContent, + isSidebarFocused: _isSidebarFocused, + child: SideNavigationScope( + child: Stack( + children: [ + // Content with animated left padding based on sidebar state + Positioned.fill( + child: AnimatedPadding( + duration: const Duration(milliseconds: 200), + curve: Curves.easeOutCubic, + padding: EdgeInsets.only(left: contentLeftPadding), + child: FocusScope( + node: _contentFocusScope, + // No autofocus - we control focus programmatically to prevent + // autofocus from stealing focus back after setState() rebuilds + child: IndexedStack(index: _currentIndex, children: _screens), + ), + ), ), - ), - ), - // Sidebar overlays content when expanded - Positioned( - top: 0, - bottom: 0, - left: 0, - child: FocusScope( - node: _sidebarFocusScope, - child: SideNavigationRail( - key: _sideNavKey, - selectedIndex: _currentIndex, - selectedLibraryKey: _selectedLibraryGlobalKey, - isOfflineMode: _isOffline, - isSidebarFocused: _isSidebarFocused, - onDestinationSelected: (index) { - _selectTab(index); - _focusContent(); - }, - onLibrarySelected: (key) { - _selectLibrary(key); - _focusContent(); - }, + // Sidebar overlays content when expanded (unless always expanded) + Positioned( + top: 0, + bottom: 0, + left: 0, + child: FocusScope( + node: _sidebarFocusScope, + child: SideNavigationRail( + key: _sideNavKey, + selectedIndex: _currentIndex, + selectedLibraryKey: _selectedLibraryGlobalKey, + isOfflineMode: _isOffline, + isSidebarFocused: _isSidebarFocused, + alwaysExpanded: alwaysExpanded, + onDestinationSelected: (index) { + _selectTab(index); + _focusContent(); + }, + onLibrarySelected: (key) { + _selectLibrary(key); + _focusContent(); + }, + ), + ), ), - ), + ], ), - ], + ), ), ), - ), - ), + ); + }, ); } diff --git a/lib/screens/settings/settings_screen.dart b/lib/screens/settings/settings_screen.dart index 2a76d7b9..1e2157e8 100644 --- a/lib/screens/settings/settings_screen.dart +++ b/lib/screens/settings/settings_screen.dart @@ -243,6 +243,20 @@ class _SettingsScreenState extends State { ); }, ), + if (PlatformDetector.shouldUseSideNavigation(context)) + Consumer( + builder: (context, settingsProvider, child) { + return SwitchListTile( + secondary: const AppIcon(Symbols.dock_to_left_rounded, fill: 1), + title: Text(t.settings.alwaysKeepSidebarOpen), + subtitle: Text(t.settings.alwaysKeepSidebarOpenDescription), + value: settingsProvider.alwaysKeepSidebarOpen, + onChanged: (value) async { + await settingsProvider.setAlwaysKeepSidebarOpen(value); + }, + ); + }, + ), ], ), ); diff --git a/lib/services/settings_service.dart b/lib/services/settings_service.dart index 02568e8c..15634de0 100644 --- a/lib/services/settings_service.dart +++ b/lib/services/settings_service.dart @@ -65,6 +65,7 @@ class SettingsService extends BaseSharedPreferencesService { static const String _keyDefaultPlaybackSpeed = 'default_playback_speed'; static const String _keyAutoPlayNextEpisode = 'auto_play_next_episode'; static const String _keyUseExoPlayer = 'use_exoplayer'; + static const String _keyAlwaysKeepSidebarOpen = 'always_keep_sidebar_open'; SettingsService._(); @@ -992,6 +993,15 @@ class SettingsService extends BaseSharedPreferencesService { return prefs.getBool(_keyUseExoPlayer) ?? true; // Default: ExoPlayer } + // Always Keep Sidebar Open (Desktop/TV only) + Future setAlwaysKeepSidebarOpen(bool enabled) async { + await prefs.setBool(_keyAlwaysKeepSidebarOpen, enabled); + } + + bool getAlwaysKeepSidebarOpen() { + return prefs.getBool(_keyAlwaysKeepSidebarOpen) ?? false; // Default: collapsed + } + // Reset all settings to defaults Future resetAllSettings() async { await Future.wait([ @@ -1037,6 +1047,7 @@ class SettingsService extends BaseSharedPreferencesService { prefs.remove(_keyDefaultPlaybackSpeed), prefs.remove(_keyAutoPlayNextEpisode), prefs.remove(_keyUseExoPlayer), + prefs.remove(_keyAlwaysKeepSidebarOpen), ]); } diff --git a/lib/widgets/side_navigation_rail.dart b/lib/widgets/side_navigation_rail.dart index a5b916a8..cf5b198c 100644 --- a/lib/widgets/side_navigation_rail.dart +++ b/lib/widgets/side_navigation_rail.dart @@ -138,7 +138,7 @@ class NavigationRailItem extends StatelessWidget { constrainedAxis: Axis.vertical, clipBehavior: Clip.hardEdge, child: SizedBox( - width: SideNavigationRailState._expandedWidth - 24, + width: SideNavigationRailState.expandedWidth - 24, child: Padding( padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 17), child: Row( @@ -173,6 +173,7 @@ class SideNavigationRail extends StatefulWidget { final String? selectedLibraryKey; final bool isOfflineMode; final bool isSidebarFocused; + final bool alwaysExpanded; final ValueChanged onDestinationSelected; final ValueChanged onLibrarySelected; @@ -182,6 +183,7 @@ class SideNavigationRail extends StatefulWidget { this.selectedLibraryKey, this.isOfflineMode = false, this.isSidebarFocused = false, + this.alwaysExpanded = false, required this.onDestinationSelected, required this.onLibrarySelected, }); @@ -199,7 +201,7 @@ class SideNavigationRailState extends State { bool _isHovered = false; Timer? _collapseTimer; static const double collapsedWidth = 80.0; - static const double _expandedWidth = 220.0; + static const double expandedWidth = 220.0; static const Duration _collapseDelay = Duration(milliseconds: 150); // Focus keys for main nav items @@ -212,8 +214,8 @@ class SideNavigationRailState extends State { // Unified focus state tracker for all nav items (main + libraries) late final _FocusStateTracker _focusTracker; - /// Whether the sidebar should be expanded (hover or focus) - bool get _shouldExpand => _isHovered || widget.isSidebarFocused; + /// Whether the sidebar should be expanded (always, hover, or focus) + bool get _shouldExpand => widget.alwaysExpanded || _isHovered || widget.isSidebarFocused; @override void initState() { @@ -387,7 +389,7 @@ class SideNavigationRailState extends State { child: AnimatedContainer( duration: t.normal, curve: Curves.easeOutCubic, - width: isCollapsed ? collapsedWidth : _expandedWidth, + width: isCollapsed ? collapsedWidth : expandedWidth, clipBehavior: Clip.hardEdge, decoration: BoxDecoration(color: t.surface), child: Column( @@ -564,7 +566,7 @@ class SideNavigationRailState extends State { constrainedAxis: Axis.vertical, clipBehavior: Clip.hardEdge, child: SizedBox( - width: _expandedWidth - 24, + width: expandedWidth - 24, child: Padding( padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 17), child: Row( @@ -617,11 +619,7 @@ class SideNavigationRailState extends State { curve: Curves.easeOutCubic, builder: (context, value, child) { return ClipRect( - child: Align( - alignment: Alignment.topCenter, - heightFactor: value, - child: child, - ), + child: Align(alignment: Alignment.topCenter, heightFactor: value, child: child), ); }, child: Column( @@ -641,14 +639,14 @@ class SideNavigationRailState extends State { ), ) : visibleLibraries.isEmpty - ? Padding( - padding: const EdgeInsets.all(16), - child: Text( - Translations.of(context).libraries.noLibrariesFound, - style: TextStyle(fontSize: 12, color: t.textMuted), - ), - ) - : _buildLibraryItems(visibleLibraries, t), + ? Padding( + padding: const EdgeInsets.all(16), + child: Text( + Translations.of(context).libraries.noLibrariesFound, + style: TextStyle(fontSize: 12, color: t.textMuted), + ), + ) + : _buildLibraryItems(visibleLibraries, t), ], ), ),