feat(settings): add appearance toggle to hide the Explore tab

The Explore tab appears for every Plex-backed profile because the Plex
Discover catalog source connects implicitly, with no way to opt out
short of a Jellyfin-only profile. Add a Show Explore Tab switch to
Appearance > Navigation that hides the tab in the bottom navigation and
side rail. UI-only: catalog sources stay connected so watchlist
surfaces keep working while the tab is hidden.

close #1844
This commit is contained in:
edde746
2026-08-09 17:14:47 +02:00
parent 8e5279a487
commit 9d13584c00
29 changed files with 118 additions and 13 deletions
+2
View File
@@ -323,6 +323,8 @@
"showNavBarLabels": "Naviqasiya paneli yazılarını göstər", "showNavBarLabels": "Naviqasiya paneli yazılarını göstər",
"showNavBarLabelsDescription": "Naviqasiya paneli ikonlarının altında mətni göstər", "showNavBarLabelsDescription": "Naviqasiya paneli ikonlarının altında mətni göstər",
"startupSection": "Başlanğıc bölməsi", "startupSection": "Başlanğıc bölməsi",
"showExploreTab": "",
"showExploreTabDescription": "",
"liveTvDefaultFavorites": "Canlı TV-də sevimli kanalları defolt et", "liveTvDefaultFavorites": "Canlı TV-də sevimli kanalları defolt et",
"liveTvDefaultFavoritesDescription": "Canlı TV açıldıqda yalnız sevimli kanalları göstər", "liveTvDefaultFavoritesDescription": "Canlı TV açıldıqda yalnız sevimli kanalları göstər",
"display": "Ekran", "display": "Ekran",
+2
View File
@@ -323,6 +323,8 @@
"showNavBarLabels": "Показвай етикети в навигационната лента", "showNavBarLabels": "Показвай етикети в навигационната лента",
"showNavBarLabelsDescription": "Показвай текстови етикети под иконите в навигационната лента", "showNavBarLabelsDescription": "Показвай текстови етикети под иконите в навигационната лента",
"startupSection": "Начален раздел", "startupSection": "Начален раздел",
"showExploreTab": "",
"showExploreTabDescription": "",
"liveTvDefaultFavorites": "По подразбиране към любими канали", "liveTvDefaultFavorites": "По подразбиране към любими канали",
"liveTvDefaultFavoritesDescription": "Показвай само любими канали при отваряне на телевизия на живо", "liveTvDefaultFavoritesDescription": "Показвай само любими канали при отваряне на телевизия на живо",
"display": "Дисплей", "display": "Дисплей",
+2
View File
@@ -323,6 +323,8 @@
"showNavBarLabels": "Vis tekst på navigationslinjen", "showNavBarLabels": "Vis tekst på navigationslinjen",
"showNavBarLabelsDescription": "Vis tekst under ikonerne på navigationslinjen", "showNavBarLabelsDescription": "Vis tekst under ikonerne på navigationslinjen",
"startupSection": "Startsektion", "startupSection": "Startsektion",
"showExploreTab": "",
"showExploreTabDescription": "",
"liveTvDefaultFavorites": "Vis favoritkanaler som standard", "liveTvDefaultFavorites": "Vis favoritkanaler som standard",
"liveTvDefaultFavoritesDescription": "Vis kun favoritkanaler ved åbning af Live TV", "liveTvDefaultFavoritesDescription": "Vis kun favoritkanaler ved åbning af Live TV",
"display": "Skærm", "display": "Skærm",
+2
View File
@@ -323,6 +323,8 @@
"showNavBarLabels": "Navigationsleisten-Beschriftungen anzeigen", "showNavBarLabels": "Navigationsleisten-Beschriftungen anzeigen",
"showNavBarLabelsDescription": "Textbeschriftungen unter den Symbolen der Navigationsleiste anzeigen", "showNavBarLabelsDescription": "Textbeschriftungen unter den Symbolen der Navigationsleiste anzeigen",
"startupSection": "Startbereich", "startupSection": "Startbereich",
"showExploreTab": "",
"showExploreTabDescription": "",
"liveTvDefaultFavorites": "Standardmäßig Favoritenkanäle", "liveTvDefaultFavorites": "Standardmäßig Favoritenkanäle",
"liveTvDefaultFavoritesDescription": "Beim Öffnen von Live TV nur Favoritenkanäle anzeigen", "liveTvDefaultFavoritesDescription": "Beim Öffnen von Live TV nur Favoritenkanäle anzeigen",
"display": "Anzeige", "display": "Anzeige",
+2
View File
@@ -323,6 +323,8 @@
"showNavBarLabels": "Show Navigation Bar Labels", "showNavBarLabels": "Show Navigation Bar Labels",
"showNavBarLabelsDescription": "Display text labels under navigation bar icons", "showNavBarLabelsDescription": "Display text labels under navigation bar icons",
"startupSection": "Startup Section", "startupSection": "Startup Section",
"showExploreTab": "Show Explore Tab",
"showExploreTabDescription": "Display the Explore tab with content from Plex Discover and connected trackers",
"liveTvDefaultFavorites": "Default to Favorite Channels", "liveTvDefaultFavorites": "Default to Favorite Channels",
"liveTvDefaultFavoritesDescription": "Show only favorite channels when opening Live TV", "liveTvDefaultFavoritesDescription": "Show only favorite channels when opening Live TV",
"display": "Display", "display": "Display",
+2
View File
@@ -323,6 +323,8 @@
"showNavBarLabels": "Mostrar etiquetas de la barra de navegación", "showNavBarLabels": "Mostrar etiquetas de la barra de navegación",
"showNavBarLabelsDescription": "Mostrar etiquetas de texto bajo los iconos de la barra de navegación", "showNavBarLabelsDescription": "Mostrar etiquetas de texto bajo los iconos de la barra de navegación",
"startupSection": "Sección de inicio", "startupSection": "Sección de inicio",
"showExploreTab": "",
"showExploreTabDescription": "",
"liveTvDefaultFavorites": "Canales favoritos por defecto", "liveTvDefaultFavorites": "Canales favoritos por defecto",
"liveTvDefaultFavoritesDescription": "Mostrar solo canales favoritos al abrir TV en vivo", "liveTvDefaultFavoritesDescription": "Mostrar solo canales favoritos al abrir TV en vivo",
"display": "Pantalla", "display": "Pantalla",
+2
View File
@@ -323,6 +323,8 @@
"showNavBarLabels": "Afficher les libellés de la barre de navigation", "showNavBarLabels": "Afficher les libellés de la barre de navigation",
"showNavBarLabelsDescription": "Afficher les libellés sous les icônes de la barre de navigation", "showNavBarLabelsDescription": "Afficher les libellés sous les icônes de la barre de navigation",
"startupSection": "Section de démarrage", "startupSection": "Section de démarrage",
"showExploreTab": "",
"showExploreTabDescription": "",
"liveTvDefaultFavorites": "Chaînes favorites par défaut", "liveTvDefaultFavorites": "Chaînes favorites par défaut",
"liveTvDefaultFavoritesDescription": "Afficher uniquement les chaînes favorites à l'ouverture de la TV en direct", "liveTvDefaultFavoritesDescription": "Afficher uniquement les chaînes favorites à l'ouverture de la TV en direct",
"display": "Affichage", "display": "Affichage",
+2
View File
@@ -323,6 +323,8 @@
"showNavBarLabels": "Navigációs sáv címkéinek megjelenítése", "showNavBarLabels": "Navigációs sáv címkéinek megjelenítése",
"showNavBarLabelsDescription": "Szöveges címkék megjelenítése a navigációs sáv ikonjai alatt", "showNavBarLabelsDescription": "Szöveges címkék megjelenítése a navigációs sáv ikonjai alatt",
"startupSection": "Indítási oldal", "startupSection": "Indítási oldal",
"showExploreTab": "",
"showExploreTabDescription": "",
"liveTvDefaultFavorites": "Kedvenc csatornák alapértelmezés szerint", "liveTvDefaultFavorites": "Kedvenc csatornák alapértelmezés szerint",
"liveTvDefaultFavoritesDescription": "Csak a kedvenc csatornák megjelenítése az Élő TV megnyitásakor", "liveTvDefaultFavoritesDescription": "Csak a kedvenc csatornák megjelenítése az Élő TV megnyitásakor",
"display": "Kijelző", "display": "Kijelző",
+2
View File
@@ -323,6 +323,8 @@
"showNavBarLabels": "Mostra le etichette della barra di navigazione", "showNavBarLabels": "Mostra le etichette della barra di navigazione",
"showNavBarLabelsDescription": "Mostra le etichette di testo sotto le icone della barra di navigazione", "showNavBarLabelsDescription": "Mostra le etichette di testo sotto le icone della barra di navigazione",
"startupSection": "Sezione di avvio", "startupSection": "Sezione di avvio",
"showExploreTab": "",
"showExploreTabDescription": "",
"liveTvDefaultFavorites": "Apri sui canali preferiti", "liveTvDefaultFavorites": "Apri sui canali preferiti",
"liveTvDefaultFavoritesDescription": "Mostra solo i canali preferiti quando apri la TV in diretta", "liveTvDefaultFavoritesDescription": "Mostra solo i canali preferiti quando apri la TV in diretta",
"display": "Schermo", "display": "Schermo",
+2
View File
@@ -323,6 +323,8 @@
"showNavBarLabels": "ナビゲーションバーのラベルを表示", "showNavBarLabels": "ナビゲーションバーのラベルを表示",
"showNavBarLabelsDescription": "ナビゲーションバーのアイコンの下にテキストラベルを表示", "showNavBarLabelsDescription": "ナビゲーションバーのアイコンの下にテキストラベルを表示",
"startupSection": "起動時のセクション", "startupSection": "起動時のセクション",
"showExploreTab": "",
"showExploreTabDescription": "",
"liveTvDefaultFavorites": "お気に入りチャンネルをデフォルトに", "liveTvDefaultFavorites": "お気に入りチャンネルをデフォルトに",
"liveTvDefaultFavoritesDescription": "ライブTVを開いたときにお気に入りチャンネルのみ表示", "liveTvDefaultFavoritesDescription": "ライブTVを開いたときにお気に入りチャンネルのみ表示",
"display": "ディスプレイ", "display": "ディスプレイ",
+2
View File
@@ -323,6 +323,8 @@
"showNavBarLabels": "Навигация панелінің мәтіндерін көрсету", "showNavBarLabels": "Навигация панелінің мәтіндерін көрсету",
"showNavBarLabelsDescription": "Навигация белгішелерінің астында мәтінді көрсету", "showNavBarLabelsDescription": "Навигация белгішелерінің астында мәтінді көрсету",
"startupSection": "Бастапқы бөлім", "startupSection": "Бастапқы бөлім",
"showExploreTab": "",
"showExploreTabDescription": "",
"liveTvDefaultFavorites": "Тікелей TV-де таңдаулы арналарды әдепкі ету", "liveTvDefaultFavorites": "Тікелей TV-де таңдаулы арналарды әдепкі ету",
"liveTvDefaultFavoritesDescription": "Тікелей TV ашылғанда тек таңдаулы арналарды көрсету", "liveTvDefaultFavoritesDescription": "Тікелей TV ашылғанда тек таңдаулы арналарды көрсету",
"display": "Дисплей", "display": "Дисплей",
+2
View File
@@ -323,6 +323,8 @@
"showNavBarLabels": "내비게이션 바 라벨 표시", "showNavBarLabels": "내비게이션 바 라벨 표시",
"showNavBarLabelsDescription": "내비게이션 바 아이콘 아래에 텍스트 라벨을 표시합니다", "showNavBarLabelsDescription": "내비게이션 바 아이콘 아래에 텍스트 라벨을 표시합니다",
"startupSection": "시작 섹션", "startupSection": "시작 섹션",
"showExploreTab": "",
"showExploreTabDescription": "",
"liveTvDefaultFavorites": "즐겨찾기 채널 기본 설정", "liveTvDefaultFavorites": "즐겨찾기 채널 기본 설정",
"liveTvDefaultFavoritesDescription": "라이브 TV를 열 때 즐겨찾기 채널만 표시", "liveTvDefaultFavoritesDescription": "라이브 TV를 열 때 즐겨찾기 채널만 표시",
"display": "디스플레이", "display": "디스플레이",
+2
View File
@@ -323,6 +323,8 @@
"showNavBarLabels": "Vis etiketter i navigasjonsfeltet", "showNavBarLabels": "Vis etiketter i navigasjonsfeltet",
"showNavBarLabelsDescription": "Vis tekstetiketter under ikonene i navigasjonsfeltet", "showNavBarLabelsDescription": "Vis tekstetiketter under ikonene i navigasjonsfeltet",
"startupSection": "Startseksjon", "startupSection": "Startseksjon",
"showExploreTab": "",
"showExploreTabDescription": "",
"liveTvDefaultFavorites": "Vis favorittkanaler som standard", "liveTvDefaultFavorites": "Vis favorittkanaler som standard",
"liveTvDefaultFavoritesDescription": "Vis bare favorittkanaler når du åpner Direkte-TV", "liveTvDefaultFavoritesDescription": "Vis bare favorittkanaler når du åpner Direkte-TV",
"display": "Skjerm", "display": "Skjerm",
+2
View File
@@ -323,6 +323,8 @@
"showNavBarLabels": "Labels op navigatiebalk tonen", "showNavBarLabels": "Labels op navigatiebalk tonen",
"showNavBarLabelsDescription": "Tekstlabels onder de pictogrammen op de navigatiebalk weergeven", "showNavBarLabelsDescription": "Tekstlabels onder de pictogrammen op de navigatiebalk weergeven",
"startupSection": "Opstartsectie", "startupSection": "Opstartsectie",
"showExploreTab": "",
"showExploreTabDescription": "",
"liveTvDefaultFavorites": "Standaard favoriete zenders", "liveTvDefaultFavorites": "Standaard favoriete zenders",
"liveTvDefaultFavoritesDescription": "Toon alleen favoriete zenders bij het openen van Live TV", "liveTvDefaultFavoritesDescription": "Toon alleen favoriete zenders bij het openen van Live TV",
"display": "Weergave", "display": "Weergave",
+2
View File
@@ -323,6 +323,8 @@
"showNavBarLabels": "Pokaż etykiety paska nawigacji", "showNavBarLabels": "Pokaż etykiety paska nawigacji",
"showNavBarLabelsDescription": "Wyświetl tekstowe etykiety pod ikonami paska nawigacji", "showNavBarLabelsDescription": "Wyświetl tekstowe etykiety pod ikonami paska nawigacji",
"startupSection": "Sekcja startowa", "startupSection": "Sekcja startowa",
"showExploreTab": "",
"showExploreTabDescription": "",
"liveTvDefaultFavorites": "Domyślnie ulubione kanały", "liveTvDefaultFavorites": "Domyślnie ulubione kanały",
"liveTvDefaultFavoritesDescription": "Pokaż tylko ulubione kanały po otwarciu telewizji na żywo", "liveTvDefaultFavoritesDescription": "Pokaż tylko ulubione kanały po otwarciu telewizji na żywo",
"display": "Ekran", "display": "Ekran",
+2
View File
@@ -323,6 +323,8 @@
"showNavBarLabels": "Mostrar Rótulos da Barra de Navegação", "showNavBarLabels": "Mostrar Rótulos da Barra de Navegação",
"showNavBarLabelsDescription": "Exibir rótulos de texto sob os ícones da barra de navegação", "showNavBarLabelsDescription": "Exibir rótulos de texto sob os ícones da barra de navegação",
"startupSection": "Seção inicial", "startupSection": "Seção inicial",
"showExploreTab": "",
"showExploreTabDescription": "",
"liveTvDefaultFavorites": "Canais favoritos por padrão", "liveTvDefaultFavorites": "Canais favoritos por padrão",
"liveTvDefaultFavoritesDescription": "Mostrar apenas os canais favoritos ao abrir a TV ao vivo", "liveTvDefaultFavoritesDescription": "Mostrar apenas os canais favoritos ao abrir a TV ao vivo",
"display": "Tela", "display": "Tela",
+2
View File
@@ -323,6 +323,8 @@
"showNavBarLabels": "Показывать подписи панели навигации", "showNavBarLabels": "Показывать подписи панели навигации",
"showNavBarLabelsDescription": "Отображать текстовые подписи под иконками панели навигации", "showNavBarLabelsDescription": "Отображать текстовые подписи под иконками панели навигации",
"startupSection": "Начальный раздел", "startupSection": "Начальный раздел",
"showExploreTab": "",
"showExploreTabDescription": "",
"liveTvDefaultFavorites": "Избранные каналы по умолчанию", "liveTvDefaultFavorites": "Избранные каналы по умолчанию",
"liveTvDefaultFavoritesDescription": "Показывать только избранные каналы при открытии ТВ", "liveTvDefaultFavoritesDescription": "Показывать только избранные каналы при открытии ТВ",
"display": "Экран", "display": "Экран",
+1 -1
View File
@@ -4,7 +4,7 @@
/// To regenerate, run: `dart run slang` /// To regenerate, run: `dart run slang`
/// ///
/// Locales: 22 /// Locales: 22
/// Strings: 38354 (1743 per locale) /// Strings: 38356 (1743 per locale)
// coverage:ignore-file // coverage:ignore-file
// ignore_for_file: type=lint, unused_import // ignore_for_file: type=lint, unused_import
+14 -6
View File
@@ -1047,6 +1047,12 @@ class Translations$settings$en {
/// en: 'Startup Section' /// en: 'Startup Section'
String get startupSection => 'Startup Section'; String get startupSection => 'Startup Section';
/// en: 'Show Explore Tab'
String get showExploreTab => 'Show Explore Tab';
/// en: 'Display the Explore tab with content from Plex Discover and connected trackers'
String get showExploreTabDescription => 'Display the Explore tab with content from Plex Discover and connected trackers';
/// en: 'Default to Favorite Channels' /// en: 'Default to Favorite Channels'
String get liveTvDefaultFavorites => 'Default to Favorite Channels'; String get liveTvDefaultFavorites => 'Default to Favorite Channels';
@@ -6384,6 +6390,8 @@ extension on Translations {
'settings.showNavBarLabels' => 'Show Navigation Bar Labels', 'settings.showNavBarLabels' => 'Show Navigation Bar Labels',
'settings.showNavBarLabelsDescription' => 'Display text labels under navigation bar icons', 'settings.showNavBarLabelsDescription' => 'Display text labels under navigation bar icons',
'settings.startupSection' => 'Startup Section', 'settings.startupSection' => 'Startup Section',
'settings.showExploreTab' => 'Show Explore Tab',
'settings.showExploreTabDescription' => 'Display the Explore tab with content from Plex Discover and connected trackers',
'settings.liveTvDefaultFavorites' => 'Default to Favorite Channels', 'settings.liveTvDefaultFavorites' => 'Default to Favorite Channels',
'settings.liveTvDefaultFavoritesDescription' => 'Show only favorite channels when opening Live TV', 'settings.liveTvDefaultFavoritesDescription' => 'Show only favorite channels when opening Live TV',
'settings.display' => 'Display', 'settings.display' => 'Display',
@@ -6583,10 +6591,10 @@ extension on Translations {
'rateSheet.saved' => 'Saved', 'rateSheet.saved' => 'Saved',
'rateSheet.notAvailable' => 'No match found', 'rateSheet.notAvailable' => 'No match found',
'rateSheet.noConnectedServices' => 'Connect a service in Settings to rate there.', 'rateSheet.noConnectedServices' => 'Connect a service in Settings to rate there.',
'accessibility.mediaCardMovie' => ({required Object title}) => '${title}, movie',
'accessibility.mediaCardShow' => ({required Object title}) => '${title}, TV show',
_ => null, _ => null,
} ?? switch (path) { } ?? switch (path) {
'accessibility.mediaCardMovie' => ({required Object title}) => '${title}, movie',
'accessibility.mediaCardShow' => ({required Object title}) => '${title}, TV show',
'accessibility.mediaCardEpisode' => ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}', 'accessibility.mediaCardEpisode' => ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}',
'accessibility.mediaCardSeason' => ({required Object title, required Object seasonInfo}) => '${title}, ${seasonInfo}', 'accessibility.mediaCardSeason' => ({required Object title, required Object seasonInfo}) => '${title}, ${seasonInfo}',
'accessibility.mediaCardWatched' => 'watched', 'accessibility.mediaCardWatched' => 'watched',
@@ -7097,10 +7105,10 @@ extension on Translations {
'explore.stats.viewersAllTime' => ({required Object n}) => '${n} viewers', 'explore.stats.viewersAllTime' => ({required Object n}) => '${n} viewers',
'explore.stats.planning' => ({required Object n}) => '${n} planning to watch', 'explore.stats.planning' => ({required Object n}) => '${n} planning to watch',
'explore.stats.favorited' => ({required Object n}) => '${n} favorites', 'explore.stats.favorited' => ({required Object n}) => '${n} favorites',
'explore.stats.dropRate' => ({required Object percent}) => '${percent} dropped it',
'explore.stats.comments' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('en'))(n, one: '${n} comment', other: '${n} comments', ),
_ => null, _ => null,
} ?? switch (path) { } ?? switch (path) {
'explore.stats.dropRate' => ({required Object percent}) => '${percent} dropped it',
'explore.stats.comments' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('en'))(n, one: '${n} comment', other: '${n} comments', ),
'explore.stats.votes' => ({required Object n}) => '${n} votes', 'explore.stats.votes' => ({required Object n}) => '${n} votes',
'explore.stats.watching' => ({required Object n}) => '${n} watching it', 'explore.stats.watching' => ({required Object n}) => '${n} watching it',
'explore.stats.completed' => ({required Object n}) => '${n} completed', 'explore.stats.completed' => ({required Object n}) => '${n} completed',
@@ -7611,10 +7619,10 @@ extension on Translations {
'externalPlayer.title' => 'External Player', 'externalPlayer.title' => 'External Player',
'externalPlayer.useExternalPlayer' => 'Use External Player', 'externalPlayer.useExternalPlayer' => 'Use External Player',
'externalPlayer.useExternalPlayerDescription' => 'Open videos in another app', 'externalPlayer.useExternalPlayerDescription' => 'Open videos in another app',
'externalPlayer.selectPlayer' => 'Select Player',
'externalPlayer.customPlayers' => 'Custom Players',
_ => null, _ => null,
} ?? switch (path) { } ?? switch (path) {
'externalPlayer.selectPlayer' => 'Select Player',
'externalPlayer.customPlayers' => 'Custom Players',
'externalPlayer.systemDefault' => 'System Default', 'externalPlayer.systemDefault' => 'System Default',
'externalPlayer.addCustomPlayer' => 'Add Custom Player', 'externalPlayer.addCustomPlayer' => 'Add Custom Player',
'externalPlayer.playerName' => 'Player Name', 'externalPlayer.playerName' => 'Player Name',
+2
View File
@@ -323,6 +323,8 @@
"showNavBarLabels": "Visa navigeringsfältets etiketter", "showNavBarLabels": "Visa navigeringsfältets etiketter",
"showNavBarLabelsDescription": "Visa textetiketter under navigeringsfältets ikoner", "showNavBarLabelsDescription": "Visa textetiketter under navigeringsfältets ikoner",
"startupSection": "Startsida", "startupSection": "Startsida",
"showExploreTab": "",
"showExploreTabDescription": "",
"liveTvDefaultFavorites": "Öppna med favoriter", "liveTvDefaultFavorites": "Öppna med favoriter",
"liveTvDefaultFavoritesDescription": "Visa endast favoritkanaler när Live-TV öppnas", "liveTvDefaultFavoritesDescription": "Visa endast favoritkanaler när Live-TV öppnas",
"display": "Skärm", "display": "Skärm",
+2
View File
@@ -323,6 +323,8 @@
"showNavBarLabels": "Gezinme Çubuğu Etiketlerini Göster", "showNavBarLabels": "Gezinme Çubuğu Etiketlerini Göster",
"showNavBarLabelsDescription": "Gezinme çubuğu simgelerinin altında metin etiketlerini göster", "showNavBarLabelsDescription": "Gezinme çubuğu simgelerinin altında metin etiketlerini göster",
"startupSection": "Başlangıç Bölümü", "startupSection": "Başlangıç Bölümü",
"showExploreTab": "",
"showExploreTabDescription": "",
"liveTvDefaultFavorites": "Canlı TV'de Favorileri Varsayılan Yap", "liveTvDefaultFavorites": "Canlı TV'de Favorileri Varsayılan Yap",
"liveTvDefaultFavoritesDescription": "Canlı TV açıldığında yalnızca favori kanalları göster", "liveTvDefaultFavoritesDescription": "Canlı TV açıldığında yalnızca favori kanalları göster",
"display": "Ekran", "display": "Ekran",
+2
View File
@@ -323,6 +323,8 @@
"showNavBarLabels": "Navigatsiya paneli matnlarini koʻrsatish", "showNavBarLabels": "Navigatsiya paneli matnlarini koʻrsatish",
"showNavBarLabelsDescription": "Navigatsiya belgilarining ostida matnni koʻrsatish", "showNavBarLabelsDescription": "Navigatsiya belgilarining ostida matnni koʻrsatish",
"startupSection": "Boshlangʻich boʻlim", "startupSection": "Boshlangʻich boʻlim",
"showExploreTab": "",
"showExploreTabDescription": "",
"liveTvDefaultFavorites": "Jonli TV-da tanlangan kanallarni standart qilish", "liveTvDefaultFavorites": "Jonli TV-da tanlangan kanallarni standart qilish",
"liveTvDefaultFavoritesDescription": "Jonli TV ochilganda faqat tanlangan kanallarni koʻrsatish", "liveTvDefaultFavoritesDescription": "Jonli TV ochilganda faqat tanlangan kanallarni koʻrsatish",
"display": "Displey", "display": "Displey",
+2
View File
@@ -323,6 +323,8 @@
"showNavBarLabels": "顯示導覽列標籤", "showNavBarLabels": "顯示導覽列標籤",
"showNavBarLabelsDescription": "在導覽列圖示下方顯示文字標籤", "showNavBarLabelsDescription": "在導覽列圖示下方顯示文字標籤",
"startupSection": "啟動頁面", "startupSection": "啟動頁面",
"showExploreTab": "",
"showExploreTabDescription": "",
"liveTvDefaultFavorites": "預設顯示最愛頻道", "liveTvDefaultFavorites": "預設顯示最愛頻道",
"liveTvDefaultFavoritesDescription": "開啟直播電視時僅顯示最愛頻道", "liveTvDefaultFavoritesDescription": "開啟直播電視時僅顯示最愛頻道",
"display": "顯示器", "display": "顯示器",
+2
View File
@@ -323,6 +323,8 @@
"showNavBarLabels": "显示导航栏标签", "showNavBarLabels": "显示导航栏标签",
"showNavBarLabelsDescription": "在导航栏图标下方显示文字标签", "showNavBarLabelsDescription": "在导航栏图标下方显示文字标签",
"startupSection": "启动页面", "startupSection": "启动页面",
"showExploreTab": "",
"showExploreTabDescription": "",
"liveTvDefaultFavorites": "默认显示收藏频道", "liveTvDefaultFavorites": "默认显示收藏频道",
"liveTvDefaultFavoritesDescription": "打开直播电视时仅显示收藏频道", "liveTvDefaultFavoritesDescription": "打开直播电视时仅显示收藏频道",
"display": "显示", "display": "显示",
+11 -2
View File
@@ -7,6 +7,7 @@ import 'dart:io' show Platform, exit;
export '../navigation/main_screen_scope.dart' export '../navigation/main_screen_scope.dart'
show MainScreenFocusScope, MainScreenScopeAspect, SideNavigationBleedBuilder; show MainScreenFocusScope, MainScreenScopeAspect, SideNavigationBleedBuilder;
import 'package:flutter/foundation.dart' show ValueListenable;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart' import 'package:flutter/services.dart'
show HardwareKeyboard, KeyDownEvent, KeyRepeatEvent, KeyUpEvent, LogicalKeyboardKey; show HardwareKeyboard, KeyDownEvent, KeyRepeatEvent, KeyUpEvent, LogicalKeyboardKey;
@@ -246,6 +247,7 @@ class _MainScreenState extends State<MainScreen>
OfflineModeProvider? _offlineModeProvider; OfflineModeProvider? _offlineModeProvider;
MultiServerProvider? _multiServerProvider; MultiServerProvider? _multiServerProvider;
CatalogSourcesProvider? _catalogSourcesProvider; CatalogSourcesProvider? _catalogSourcesProvider;
ValueListenable<bool>? _showExploreTabListenable;
RouteObserver<PageRoute<dynamic>>? _profileRouteObserver; RouteObserver<PageRoute<dynamic>>? _profileRouteObserver;
bool _lastHasLiveTv = false; bool _lastHasLiveTv = false;
bool _lastHasExplore = false; bool _lastHasExplore = false;
@@ -347,10 +349,14 @@ class _MainScreenState extends State<MainScreen>
_lastHasLiveTv = false; _lastHasLiveTv = false;
} }
try { try {
_lastHasExplore = context.read<CatalogSourcesProvider>().hasAnySource; _lastHasExplore = context.read<CatalogSourcesProvider>().hasAnySource && _showExploreTabSetting;
} catch (_) { } catch (_) {
_lastHasExplore = false; _lastHasExplore = false;
} }
// Re-evaluate Explore tab visibility when the appearance toggle flips
// mid-session; the catalog-sources listener covers source changes.
_showExploreTabListenable = SettingsService.instanceOrNull?.listenable(SettingsService.showExploreTab);
_showExploreTabListenable?.addListener(_handleCatalogSourcesChanged);
_currentTab = _defaultTabForMode(_isOffline); _currentTab = _defaultTabForMode(_isOffline);
_lastOnlineTabId = _isOffline ? null : NavigationTabId.discover; _lastOnlineTabId = _isOffline ? null : NavigationTabId.discover;
_autoSwitchedToDownloads = _isOffline && _currentTab == NavigationTabId.downloads; _autoSwitchedToDownloads = _isOffline && _currentTab == NavigationTabId.downloads;
@@ -887,6 +893,7 @@ class _MainScreenState extends State<MainScreen>
_offlineModeProvider?.removeListener(_handleOfflineStatusChanged); _offlineModeProvider?.removeListener(_handleOfflineStatusChanged);
_multiServerProvider?.removeListener(_handleLiveTvChanged); _multiServerProvider?.removeListener(_handleLiveTvChanged);
_catalogSourcesProvider?.removeListener(_handleCatalogSourcesChanged); _catalogSourcesProvider?.removeListener(_handleCatalogSourcesChanged);
_showExploreTabListenable?.removeListener(_handleCatalogSourcesChanged);
if (_bindingSettleListener != null) { if (_bindingSettleListener != null) {
_activeProfileForListener?.removeListener(_bindingSettleListener!); _activeProfileForListener?.removeListener(_bindingSettleListener!);
} }
@@ -1078,8 +1085,10 @@ class _MainScreenState extends State<MainScreen>
} }
} }
bool get _showExploreTabSetting => SettingsService.instanceOrNull?.read(SettingsService.showExploreTab) ?? true;
void _handleCatalogSourcesChanged() { void _handleCatalogSourcesChanged() {
final hasExplore = _catalogSourcesProvider?.hasAnySource ?? false; final hasExplore = (_catalogSourcesProvider?.hasAnySource ?? false) && _showExploreTabSetting;
if (hasExplore == _lastHasExplore) return; if (hasExplore == _lastHasExplore) return;
_lastHasExplore = hasExplore; _lastHasExplore = hasExplore;
@@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
import '../../i18n/app_locale_utils.dart'; import '../../i18n/app_locale_utils.dart';
import '../../i18n/strings.g.dart'; import '../../i18n/strings.g.dart';
import '../../providers/catalog_sources_provider.dart';
import '../../providers/multi_server_provider.dart'; import '../../providers/multi_server_provider.dart';
import '../../providers/theme_provider.dart'; import '../../providers/theme_provider.dart';
import '../../profiles/active_profile_provider.dart'; import '../../profiles/active_profile_provider.dart';
@@ -29,9 +30,12 @@ class AppearanceSettingsScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Watched at build level so the tile can be excluded with a plain `if` — // Watched at build level so the tiles can be excluded with a plain `if` —
// a child that renders SizedBox.shrink() would corrupt the group corners. // a child that renders SizedBox.shrink() would corrupt the group corners.
final hasMultipleProfiles = context.watch<ActiveProfileProvider>().hasMultipleProfiles; final hasMultipleProfiles = context.watch<ActiveProfileProvider>().hasMultipleProfiles;
// Nullable watch: hosts without the profile session scope (tests) simply
// never show the Explore toggle, mirroring the tab's own visibility.
final hasExplore = context.watch<CatalogSourcesProvider?>()?.hasAnySource ?? false;
return SettingsPage( return SettingsPage(
title: Text(t.settings.appearance), title: Text(t.settings.appearance),
children: [ children: [
@@ -112,6 +116,13 @@ class AppearanceSettingsScreen extends StatelessWidget {
title: t.settings.navigation, title: t.settings.navigation,
children: [ children: [
_startupSectionSelector(), _startupSectionSelector(),
if (hasExplore)
SettingSwitchTile(
pref: SettingsService.showExploreTab,
icon: Symbols.explore_rounded,
title: t.settings.showExploreTab,
subtitle: t.settings.showExploreTabDescription,
),
if (Platform.isAndroid || PlatformDetector.isDesktopOS()) if (Platform.isAndroid || PlatformDetector.isDesktopOS())
SettingSwitchTile( SettingSwitchTile(
pref: SettingsService.forceTvMode, pref: SettingsService.forceTvMode,
+6
View File
@@ -444,6 +444,11 @@ class SettingsService extends BaseSharedPreferencesService {
values: NavigationTabId.values, values: NavigationTabId.values,
defaultValue: NavigationTabId.discover, defaultValue: NavigationTabId.discover,
); );
/// Whether the Explore tab (Plex Discover / tracker catalog rows) is shown
/// at all. UI-only: catalog sources stay connected so watchlist surfaces
/// keep working while the tab is hidden.
static const showExploreTab = BoolPref('show_explore_tab', defaultValue: true);
static const alwaysKeepSidebarOpen = BoolPref('always_keep_sidebar_open'); static const alwaysKeepSidebarOpen = BoolPref('always_keep_sidebar_open');
static const showUnwatchedCount = BoolPref('show_unwatched_count', defaultValue: true); static const showUnwatchedCount = BoolPref('show_unwatched_count', defaultValue: true);
static const showEpisodeNumberOnCards = BoolPref('show_episode_number_on_cards', defaultValue: true); static const showEpisodeNumberOnCards = BoolPref('show_episode_number_on_cards', defaultValue: true);
@@ -920,6 +925,7 @@ class SettingsService extends BaseSharedPreferencesService {
autoPlayNextEpisode, autoPlayNextEpisode,
useExoPlayer, useExoPlayer,
startupSection, startupSection,
showExploreTab,
alwaysKeepSidebarOpen, alwaysKeepSidebarOpen,
showUnwatchedCount, showUnwatchedCount,
showEpisodeNumberOnCards, showEpisodeNumberOnCards,
+5 -3
View File
@@ -652,7 +652,7 @@ class SideNavigationRailState extends State<SideNavigationRail> with MountedSetS
final hasLiveTv = context.watch<MultiServerProvider>().hasLiveTv; final hasLiveTv = context.watch<MultiServerProvider>().hasLiveTv;
// Nullable watch: rail tests (and any host without the profile session // Nullable watch: rail tests (and any host without the profile session
// scope) simply never show the Explore item. // scope) simply never show the Explore item.
final hasExplore = context.watch<CatalogSourcesProvider?>()?.hasAnySource ?? false; final hasExploreSource = context.watch<CatalogSourcesProvider?>()?.hasAnySource ?? false;
// Nullable watch: rail tests (and any host without the profile session // Nullable watch: rail tests (and any host without the profile session
// scope) simply never show the Now Playing item. TV-only — it is the // scope) simply never show the Now Playing item. TV-only — it is the
// way back into the now-playing screen there; desktop already has the // way back into the now-playing screen there; desktop already has the
@@ -660,14 +660,16 @@ class SideNavigationRailState extends State<SideNavigationRail> with MountedSetS
final musicService = context.watch<MusicPlaybackService?>(); final musicService = context.watch<MusicPlaybackService?>();
final nowPlayingTrack = widget.isOfflineMode || !PlatformDetector.isTV() ? null : musicService?.currentTrack; final nowPlayingTrack = widget.isOfflineMode || !PlatformDetector.isTV() ? null : musicService?.currentTrack;
// Listen to fullscreen + groupLibrariesByServer setting so the rail // Listen to fullscreen + the groupLibrariesByServer / showExploreTab
// rebuilds when the user toggles "Group libraries by server" in Appearance. // settings so the rail rebuilds when they are toggled in Appearance.
return ListenableBuilder( return ListenableBuilder(
listenable: Listenable.merge([ listenable: Listenable.merge([
FullscreenStateManager(), FullscreenStateManager(),
SettingsService.instance.listenable(SettingsService.groupLibrariesByServer), SettingsService.instance.listenable(SettingsService.groupLibrariesByServer),
SettingsService.instance.listenable(SettingsService.showExploreTab),
]), ]),
builder: (context, _) { builder: (context, _) {
final hasExplore = hasExploreSource && SettingsService.instance.read(SettingsService.showExploreTab);
// Server grouping: only when multi-server AND the user-facing toggle is on. // Server grouping: only when multi-server AND the user-facing toggle is on.
final groupByServerSetting = SettingsService.instance.read(SettingsService.groupLibrariesByServer); final groupByServerSetting = SettingsService.instance.read(SettingsService.groupLibrariesByServer);
final showServerHeaders = serverIds.length > 1 && groupByServerSetting; final showServerHeaders = serverIds.length > 1 && groupByServerSetting;
@@ -11,6 +11,7 @@ import 'package:plezy/media/media_backend.dart';
import 'package:plezy/media/media_kind.dart'; import 'package:plezy/media/media_kind.dart';
import 'package:plezy/media/media_library.dart'; import 'package:plezy/media/media_library.dart';
import 'package:plezy/navigation/navigation_tabs.dart'; import 'package:plezy/navigation/navigation_tabs.dart';
import 'package:plezy/providers/catalog_sources_provider.dart';
import 'package:plezy/providers/hidden_libraries_provider.dart'; import 'package:plezy/providers/hidden_libraries_provider.dart';
import 'package:plezy/providers/libraries_provider.dart'; import 'package:plezy/providers/libraries_provider.dart';
import 'package:plezy/providers/multi_server_provider.dart'; import 'package:plezy/providers/multi_server_provider.dart';
@@ -25,6 +26,12 @@ import '../test_helpers/multi_server_fixtures.dart';
import '../test_helpers/prefs.dart'; import '../test_helpers/prefs.dart';
import '../test_helpers/theme.dart'; import '../test_helpers/theme.dart';
/// Minimal source-bearing stand-in: the rail only reads [hasAnySource].
class _FakeCatalogSourcesProvider extends CatalogSourcesProvider {
@override
bool get hasAnySource => true;
}
MediaLibrary _library({ MediaLibrary _library({
required String id, required String id,
required String title, required String title,
@@ -68,6 +75,7 @@ Future<void> _pumpBasicRail(
bool isSidebarFocused = false, bool isSidebarFocused = false,
bool alwaysExpanded = false, bool alwaysExpanded = false,
double? height, double? height,
CatalogSourcesProvider? catalogSources,
}) async { }) async {
await SettingsService.getInstance(); await SettingsService.getInstance();
@@ -102,6 +110,7 @@ Future<void> _pumpBasicRail(
ChangeNotifierProvider<LibrariesProvider>.value(value: librariesProvider), ChangeNotifierProvider<LibrariesProvider>.value(value: librariesProvider),
ChangeNotifierProvider<HiddenLibrariesProvider>.value(value: hiddenLibrariesProvider), ChangeNotifierProvider<HiddenLibrariesProvider>.value(value: hiddenLibrariesProvider),
ChangeNotifierProvider<MultiServerProvider>.value(value: multiServerProvider), ChangeNotifierProvider<MultiServerProvider>.value(value: multiServerProvider),
if (catalogSources != null) ChangeNotifierProvider<CatalogSourcesProvider>.value(value: catalogSources),
], ],
child: MaterialApp( child: MaterialApp(
theme: ThemeData(extensions: const [testMonoTokens]), theme: ThemeData(extensions: const [testMonoTokens]),
@@ -295,6 +304,22 @@ void main() {
expect(targetRect.bottom, lessThanOrEqualTo(railRect.bottom)); expect(targetRect.bottom, lessThanOrEqualTo(railRect.bottom));
}); });
testWidgets('Explore item follows the showExploreTab appearance setting', (tester) async {
final catalogSources = _FakeCatalogSourcesProvider();
addTearDown(catalogSources.dispose);
await _pumpBasicRail(tester, alwaysExpanded: true, catalogSources: catalogSources);
expect(find.widgetWithText(NavigationRailItem, 'Explore'), findsOneWidget);
await SettingsService.instance.write(SettingsService.showExploreTab, false);
await tester.pumpAndSettle();
expect(find.widgetWithText(NavigationRailItem, 'Explore'), findsNothing);
await SettingsService.instance.write(SettingsService.showExploreTab, true);
await tester.pumpAndSettle();
expect(find.widgetWithText(NavigationRailItem, 'Explore'), findsOneWidget);
});
testWidgets('reports interaction expansion for shell content push', (tester) async { testWidgets('reports interaction expansion for shell content push', (tester) async {
await SettingsService.getInstance(); await SettingsService.getInstance();