refactor: M3 settings screen redesign
This commit is contained in:
@@ -220,7 +220,15 @@
|
||||
"showNavBarLabels": "Vis navigationsbarlabels",
|
||||
"showNavBarLabelsDescription": "Vis tekstlabels under navigationsbarikoner",
|
||||
"liveTvDefaultFavorites": "Standard til favoritkanaler",
|
||||
"liveTvDefaultFavoritesDescription": "Vis kun favoritkanaler ved åbning af Live TV"
|
||||
"liveTvDefaultFavoritesDescription": "Vis kun favoritkanaler ved åbning af Live TV",
|
||||
"display": "Display",
|
||||
"homeScreen": "Home Screen",
|
||||
"navigation": "Navigation",
|
||||
"content": "Content",
|
||||
"player": "Player",
|
||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||
"seekAndTiming": "Seek & Timing",
|
||||
"behavior": "Behavior"
|
||||
},
|
||||
"search": {
|
||||
"hint": "Søg film, serier, musik...",
|
||||
|
||||
@@ -220,7 +220,15 @@
|
||||
"showNavBarLabels": "Navigationsleisten-Beschriftungen anzeigen",
|
||||
"showNavBarLabelsDescription": "Textbeschriftungen unter den Symbolen der Navigationsleiste anzeigen",
|
||||
"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": "Display",
|
||||
"homeScreen": "Home Screen",
|
||||
"navigation": "Navigation",
|
||||
"content": "Content",
|
||||
"player": "Player",
|
||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||
"seekAndTiming": "Seek & Timing",
|
||||
"behavior": "Behavior"
|
||||
},
|
||||
"search": {
|
||||
"hint": "Filme, Serien, Musik suchen...",
|
||||
|
||||
@@ -220,7 +220,15 @@
|
||||
"showNavBarLabels": "Show Navigation Bar Labels",
|
||||
"showNavBarLabelsDescription": "Display text labels under navigation bar icons",
|
||||
"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",
|
||||
"homeScreen": "Home Screen",
|
||||
"navigation": "Navigation",
|
||||
"content": "Content",
|
||||
"player": "Player",
|
||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||
"seekAndTiming": "Seek & Timing",
|
||||
"behavior": "Behavior"
|
||||
},
|
||||
"search": {
|
||||
"hint": "Search movies, shows, music...",
|
||||
|
||||
@@ -220,7 +220,15 @@
|
||||
"showNavBarLabels": "Mostrar etiquetas de la barra de navegación",
|
||||
"showNavBarLabelsDescription": "Mostrar etiquetas de texto bajo los iconos de la barra de navegación",
|
||||
"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": "Display",
|
||||
"homeScreen": "Home Screen",
|
||||
"navigation": "Navigation",
|
||||
"content": "Content",
|
||||
"player": "Player",
|
||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||
"seekAndTiming": "Seek & Timing",
|
||||
"behavior": "Behavior"
|
||||
},
|
||||
"search": {
|
||||
"hint": "Buscar películas, series, música...",
|
||||
|
||||
@@ -220,7 +220,15 @@
|
||||
"showNavBarLabels": "Afficher les libellés de la barre de navigation",
|
||||
"showNavBarLabelsDescription": "Afficher les libellés sous les icônes de la barre de navigation",
|
||||
"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": "Display",
|
||||
"homeScreen": "Home Screen",
|
||||
"navigation": "Navigation",
|
||||
"content": "Content",
|
||||
"player": "Player",
|
||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||
"seekAndTiming": "Seek & Timing",
|
||||
"behavior": "Behavior"
|
||||
},
|
||||
"search": {
|
||||
"hint": "Rechercher des films, des séries, de la musique...",
|
||||
|
||||
@@ -220,7 +220,15 @@
|
||||
"showNavBarLabels": "Mostra etichette barra di navigazione",
|
||||
"showNavBarLabelsDescription": "Mostra le etichette sotto le icone della barra di navigazione",
|
||||
"liveTvDefaultFavorites": "Canali preferiti predefiniti",
|
||||
"liveTvDefaultFavoritesDescription": "Mostra solo i canali preferiti all'apertura della TV dal vivo"
|
||||
"liveTvDefaultFavoritesDescription": "Mostra solo i canali preferiti all'apertura della TV dal vivo",
|
||||
"display": "Display",
|
||||
"homeScreen": "Home Screen",
|
||||
"navigation": "Navigation",
|
||||
"content": "Content",
|
||||
"player": "Player",
|
||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||
"seekAndTiming": "Seek & Timing",
|
||||
"behavior": "Behavior"
|
||||
},
|
||||
"search": {
|
||||
"hint": "Cerca film. spettacoli, musica...",
|
||||
|
||||
@@ -220,7 +220,15 @@
|
||||
"showNavBarLabels": "ナビゲーションバーラベルを表示",
|
||||
"showNavBarLabelsDescription": "ナビゲーションバーアイコンの下にテキストラベルを表示",
|
||||
"liveTvDefaultFavorites": "お気に入りチャンネルをデフォルトに",
|
||||
"liveTvDefaultFavoritesDescription": "ライブTV を開いたときにお気に入りチャンネルのみ表示"
|
||||
"liveTvDefaultFavoritesDescription": "ライブTV を開いたときにお気に入りチャンネルのみ表示",
|
||||
"display": "Display",
|
||||
"homeScreen": "Home Screen",
|
||||
"navigation": "Navigation",
|
||||
"content": "Content",
|
||||
"player": "Player",
|
||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||
"seekAndTiming": "Seek & Timing",
|
||||
"behavior": "Behavior"
|
||||
},
|
||||
"search": {
|
||||
"hint": "映画、番組、音楽を検索...",
|
||||
|
||||
@@ -220,7 +220,15 @@
|
||||
"showNavBarLabels": "내비게이션 바 라벨 표시",
|
||||
"showNavBarLabelsDescription": "내비게이션 바 아이콘 아래에 텍스트 라벨을 표시합니다",
|
||||
"liveTvDefaultFavorites": "즐겨찾기 채널 기본 설정",
|
||||
"liveTvDefaultFavoritesDescription": "라이브 TV를 열 때 즐겨찾기 채널만 표시"
|
||||
"liveTvDefaultFavoritesDescription": "라이브 TV를 열 때 즐겨찾기 채널만 표시",
|
||||
"display": "Display",
|
||||
"homeScreen": "Home Screen",
|
||||
"navigation": "Navigation",
|
||||
"content": "Content",
|
||||
"player": "Player",
|
||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||
"seekAndTiming": "Seek & Timing",
|
||||
"behavior": "Behavior"
|
||||
},
|
||||
"search": {
|
||||
"hint": "영화, 시리즈, 음악 등을 검색하세요...",
|
||||
|
||||
@@ -220,7 +220,15 @@
|
||||
"showNavBarLabels": "Vis navigasjonsfeltlabeler",
|
||||
"showNavBarLabelsDescription": "Vis tekstlabeler under navigasjonsfeltikoner",
|
||||
"liveTvDefaultFavorites": "Standard til favorittkanaler",
|
||||
"liveTvDefaultFavoritesDescription": "Vis kun favorittkanaler når du åpner Live TV"
|
||||
"liveTvDefaultFavoritesDescription": "Vis kun favorittkanaler når du åpner Live TV",
|
||||
"display": "Display",
|
||||
"homeScreen": "Home Screen",
|
||||
"navigation": "Navigation",
|
||||
"content": "Content",
|
||||
"player": "Player",
|
||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||
"seekAndTiming": "Seek & Timing",
|
||||
"behavior": "Behavior"
|
||||
},
|
||||
"search": {
|
||||
"hint": "Søk i filmer, serier, musikk...",
|
||||
|
||||
@@ -220,7 +220,15 @@
|
||||
"showNavBarLabels": "Navigatiebalk labels tonen",
|
||||
"showNavBarLabelsDescription": "Tekstlabels onder de pictogrammen van de navigatiebalk weergeven",
|
||||
"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": "Display",
|
||||
"homeScreen": "Home Screen",
|
||||
"navigation": "Navigation",
|
||||
"content": "Content",
|
||||
"player": "Player",
|
||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||
"seekAndTiming": "Seek & Timing",
|
||||
"behavior": "Behavior"
|
||||
},
|
||||
"search": {
|
||||
"hint": "Zoek films, series, muziek...",
|
||||
|
||||
@@ -220,7 +220,15 @@
|
||||
"showNavBarLabels": "Pokaż etykiety paska nawigacji",
|
||||
"showNavBarLabelsDescription": "Wyświetl tekstowe etykiety pod ikonami paska nawigacji",
|
||||
"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": "Display",
|
||||
"homeScreen": "Home Screen",
|
||||
"navigation": "Navigation",
|
||||
"content": "Content",
|
||||
"player": "Player",
|
||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||
"seekAndTiming": "Seek & Timing",
|
||||
"behavior": "Behavior"
|
||||
},
|
||||
"search": {
|
||||
"hint": "Szukaj filmów, seriali, muzyki...",
|
||||
|
||||
@@ -220,7 +220,15 @@
|
||||
"showNavBarLabels": "Mostrar Rótulos da Barra de Navegação",
|
||||
"showNavBarLabelsDescription": "Exibir rótulos de texto sob os ícones da barra de navegação",
|
||||
"liveTvDefaultFavorites": "Canais favoritos por padrão",
|
||||
"liveTvDefaultFavoritesDescription": "Mostrar apenas canais favoritos ao abrir TV ao vivo"
|
||||
"liveTvDefaultFavoritesDescription": "Mostrar apenas canais favoritos ao abrir TV ao vivo",
|
||||
"display": "Display",
|
||||
"homeScreen": "Home Screen",
|
||||
"navigation": "Navigation",
|
||||
"content": "Content",
|
||||
"player": "Player",
|
||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||
"seekAndTiming": "Seek & Timing",
|
||||
"behavior": "Behavior"
|
||||
},
|
||||
"search": {
|
||||
"hint": "Buscar filmes, séries, músicas...",
|
||||
|
||||
@@ -220,7 +220,15 @@
|
||||
"showNavBarLabels": "Показывать подписи панели навигации",
|
||||
"showNavBarLabelsDescription": "Отображать текстовые подписи под иконками панели навигации",
|
||||
"liveTvDefaultFavorites": "Избранные каналы по умолчанию",
|
||||
"liveTvDefaultFavoritesDescription": "Показывать только избранные каналы при открытии ТВ"
|
||||
"liveTvDefaultFavoritesDescription": "Показывать только избранные каналы при открытии ТВ",
|
||||
"display": "Display",
|
||||
"homeScreen": "Home Screen",
|
||||
"navigation": "Navigation",
|
||||
"content": "Content",
|
||||
"player": "Player",
|
||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||
"seekAndTiming": "Seek & Timing",
|
||||
"behavior": "Behavior"
|
||||
},
|
||||
"search": {
|
||||
"hint": "Поиск фильмов, сериалов, музыки...",
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
/// To regenerate, run: `dart run slang`
|
||||
///
|
||||
/// Locales: 15
|
||||
/// Strings: 12465 (831 per locale)
|
||||
/// Strings: 12585 (839 per locale)
|
||||
///
|
||||
/// Built on 2026-04-01 at 15:18 UTC
|
||||
/// Built on 2026-04-01 at 22:20 UTC
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint, unused_import
|
||||
|
||||
@@ -340,6 +340,14 @@ class _TranslationsSettingsDa implements TranslationsSettingsEn {
|
||||
@override String get showNavBarLabelsDescription => 'Vis tekstlabels under navigationsbarikoner';
|
||||
@override String get liveTvDefaultFavorites => 'Standard til favoritkanaler';
|
||||
@override String get liveTvDefaultFavoritesDescription => 'Vis kun favoritkanaler ved åbning af Live TV';
|
||||
@override String get display => 'Display';
|
||||
@override String get homeScreen => 'Home Screen';
|
||||
@override String get navigation => 'Navigation';
|
||||
@override String get content => 'Content';
|
||||
@override String get player => 'Player';
|
||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||
@override String get seekAndTiming => 'Seek & Timing';
|
||||
@override String get behavior => 'Behavior';
|
||||
}
|
||||
|
||||
// Path: search
|
||||
@@ -1539,6 +1547,14 @@ extension on TranslationsDa {
|
||||
'settings.showNavBarLabelsDescription' => 'Vis tekstlabels under navigationsbarikoner',
|
||||
'settings.liveTvDefaultFavorites' => 'Standard til favoritkanaler',
|
||||
'settings.liveTvDefaultFavoritesDescription' => 'Vis kun favoritkanaler ved åbning af Live TV',
|
||||
'settings.display' => 'Display',
|
||||
'settings.homeScreen' => 'Home Screen',
|
||||
'settings.navigation' => 'Navigation',
|
||||
'settings.content' => 'Content',
|
||||
'settings.player' => 'Player',
|
||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||
'settings.seekAndTiming' => 'Seek & Timing',
|
||||
'settings.behavior' => 'Behavior',
|
||||
'search.hint' => 'Søg film, serier, musik...',
|
||||
'search.tryDifferentTerm' => 'Prøv en anden søgning',
|
||||
'search.searchYourMedia' => 'Søg i dine medier',
|
||||
@@ -1832,6 +1848,8 @@ extension on TranslationsDa {
|
||||
'licenses.relatedPackages' => 'Relaterede pakker',
|
||||
'licenses.license' => 'Licens',
|
||||
'licenses.licenseNumber' => ({required Object number}) => 'Licens ${number}',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'licenses.licensesCount' => ({required Object count}) => '${count} licenser',
|
||||
'navigation.libraries' => 'Biblioteker',
|
||||
'navigation.downloads' => 'Downloads',
|
||||
@@ -1840,8 +1858,6 @@ extension on TranslationsDa {
|
||||
'liveTv.guide' => 'Guide',
|
||||
'liveTv.noChannels' => 'Ingen kanaler tilgængelige',
|
||||
'liveTv.noDvr' => 'Ingen DVR konfigureret på nogen server',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.noPrograms' => 'Ingen programdata tilgængelig',
|
||||
'liveTv.live' => 'LIVE',
|
||||
'liveTv.reloadGuide' => 'Genindlæs guide',
|
||||
|
||||
@@ -340,6 +340,14 @@ class _TranslationsSettingsDe implements TranslationsSettingsEn {
|
||||
@override String get showNavBarLabelsDescription => 'Textbeschriftungen unter den Symbolen der Navigationsleiste anzeigen';
|
||||
@override String get liveTvDefaultFavorites => 'Standardmäßig Favoritenkanäle';
|
||||
@override String get liveTvDefaultFavoritesDescription => 'Beim Öffnen von Live TV nur Favoritenkanäle anzeigen';
|
||||
@override String get display => 'Display';
|
||||
@override String get homeScreen => 'Home Screen';
|
||||
@override String get navigation => 'Navigation';
|
||||
@override String get content => 'Content';
|
||||
@override String get player => 'Player';
|
||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||
@override String get seekAndTiming => 'Seek & Timing';
|
||||
@override String get behavior => 'Behavior';
|
||||
}
|
||||
|
||||
// Path: search
|
||||
@@ -1539,6 +1547,14 @@ extension on TranslationsDe {
|
||||
'settings.showNavBarLabelsDescription' => 'Textbeschriftungen unter den Symbolen der Navigationsleiste anzeigen',
|
||||
'settings.liveTvDefaultFavorites' => 'Standardmäßig Favoritenkanäle',
|
||||
'settings.liveTvDefaultFavoritesDescription' => 'Beim Öffnen von Live TV nur Favoritenkanäle anzeigen',
|
||||
'settings.display' => 'Display',
|
||||
'settings.homeScreen' => 'Home Screen',
|
||||
'settings.navigation' => 'Navigation',
|
||||
'settings.content' => 'Content',
|
||||
'settings.player' => 'Player',
|
||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||
'settings.seekAndTiming' => 'Seek & Timing',
|
||||
'settings.behavior' => 'Behavior',
|
||||
'search.hint' => 'Filme, Serien, Musik suchen...',
|
||||
'search.tryDifferentTerm' => 'Anderen Suchbegriff versuchen',
|
||||
'search.searchYourMedia' => 'In den eigenen Medien suchen',
|
||||
@@ -1832,6 +1848,8 @@ extension on TranslationsDe {
|
||||
'licenses.relatedPackages' => 'Verwandte Pakete',
|
||||
'licenses.license' => 'Lizenz',
|
||||
'licenses.licenseNumber' => ({required Object number}) => 'Lizenz ${number}',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'licenses.licensesCount' => ({required Object count}) => '${count} Lizenzen',
|
||||
'navigation.libraries' => 'Medien',
|
||||
'navigation.downloads' => 'Downloads',
|
||||
@@ -1840,8 +1858,6 @@ extension on TranslationsDe {
|
||||
'liveTv.guide' => 'Programmführer',
|
||||
'liveTv.noChannels' => 'Keine Kanäle verfügbar',
|
||||
'liveTv.noDvr' => 'Kein DVR auf einem Server konfiguriert',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.noPrograms' => 'Keine Programmdaten verfügbar',
|
||||
'liveTv.live' => 'LIVE',
|
||||
'liveTv.reloadGuide' => 'Programmführer neu laden',
|
||||
|
||||
@@ -765,6 +765,30 @@ class TranslationsSettingsEn {
|
||||
|
||||
/// en: 'Show only favorite channels when opening Live TV'
|
||||
String get liveTvDefaultFavoritesDescription => 'Show only favorite channels when opening Live TV';
|
||||
|
||||
/// en: 'Display'
|
||||
String get display => 'Display';
|
||||
|
||||
/// en: 'Home Screen'
|
||||
String get homeScreen => 'Home Screen';
|
||||
|
||||
/// en: 'Navigation'
|
||||
String get navigation => 'Navigation';
|
||||
|
||||
/// en: 'Content'
|
||||
String get content => 'Content';
|
||||
|
||||
/// en: 'Player'
|
||||
String get player => 'Player';
|
||||
|
||||
/// en: 'Subtitles & Configuration'
|
||||
String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||
|
||||
/// en: 'Seek & Timing'
|
||||
String get seekAndTiming => 'Seek & Timing';
|
||||
|
||||
/// en: 'Behavior'
|
||||
String get behavior => 'Behavior';
|
||||
}
|
||||
|
||||
// Path: search
|
||||
@@ -3208,6 +3232,14 @@ extension on Translations {
|
||||
'settings.showNavBarLabelsDescription' => 'Display text labels under navigation bar icons',
|
||||
'settings.liveTvDefaultFavorites' => 'Default to Favorite Channels',
|
||||
'settings.liveTvDefaultFavoritesDescription' => 'Show only favorite channels when opening Live TV',
|
||||
'settings.display' => 'Display',
|
||||
'settings.homeScreen' => 'Home Screen',
|
||||
'settings.navigation' => 'Navigation',
|
||||
'settings.content' => 'Content',
|
||||
'settings.player' => 'Player',
|
||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||
'settings.seekAndTiming' => 'Seek & Timing',
|
||||
'settings.behavior' => 'Behavior',
|
||||
'search.hint' => 'Search movies, shows, music...',
|
||||
'search.tryDifferentTerm' => 'Try a different search term',
|
||||
'search.searchYourMedia' => 'Search your media',
|
||||
@@ -3501,6 +3533,8 @@ extension on Translations {
|
||||
'licenses.relatedPackages' => 'Related Packages',
|
||||
'licenses.license' => 'License',
|
||||
'licenses.licenseNumber' => ({required Object number}) => 'License ${number}',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'licenses.licensesCount' => ({required Object count}) => '${count} licenses',
|
||||
'navigation.libraries' => 'Libraries',
|
||||
'navigation.downloads' => 'Downloads',
|
||||
@@ -3509,8 +3543,6 @@ extension on Translations {
|
||||
'liveTv.guide' => 'Guide',
|
||||
'liveTv.noChannels' => 'No channels available',
|
||||
'liveTv.noDvr' => 'No DVR configured on any server',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.noPrograms' => 'No program data available',
|
||||
'liveTv.live' => 'LIVE',
|
||||
'liveTv.reloadGuide' => 'Reload Guide',
|
||||
|
||||
@@ -340,6 +340,14 @@ class _TranslationsSettingsEs implements TranslationsSettingsEn {
|
||||
@override String get showNavBarLabelsDescription => 'Mostrar etiquetas de texto bajo los iconos de la barra de navegación';
|
||||
@override String get liveTvDefaultFavorites => 'Canales favoritos por defecto';
|
||||
@override String get liveTvDefaultFavoritesDescription => 'Mostrar solo canales favoritos al abrir TV en vivo';
|
||||
@override String get display => 'Display';
|
||||
@override String get homeScreen => 'Home Screen';
|
||||
@override String get navigation => 'Navigation';
|
||||
@override String get content => 'Content';
|
||||
@override String get player => 'Player';
|
||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||
@override String get seekAndTiming => 'Seek & Timing';
|
||||
@override String get behavior => 'Behavior';
|
||||
}
|
||||
|
||||
// Path: search
|
||||
@@ -1539,6 +1547,14 @@ extension on TranslationsEs {
|
||||
'settings.showNavBarLabelsDescription' => 'Mostrar etiquetas de texto bajo los iconos de la barra de navegación',
|
||||
'settings.liveTvDefaultFavorites' => 'Canales favoritos por defecto',
|
||||
'settings.liveTvDefaultFavoritesDescription' => 'Mostrar solo canales favoritos al abrir TV en vivo',
|
||||
'settings.display' => 'Display',
|
||||
'settings.homeScreen' => 'Home Screen',
|
||||
'settings.navigation' => 'Navigation',
|
||||
'settings.content' => 'Content',
|
||||
'settings.player' => 'Player',
|
||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||
'settings.seekAndTiming' => 'Seek & Timing',
|
||||
'settings.behavior' => 'Behavior',
|
||||
'search.hint' => 'Buscar películas, series, música...',
|
||||
'search.tryDifferentTerm' => 'Prueba con un término de búsqueda diferente',
|
||||
'search.searchYourMedia' => 'Busca en tu contenido',
|
||||
@@ -1832,6 +1848,8 @@ extension on TranslationsEs {
|
||||
'licenses.relatedPackages' => 'Paquetes relacionados',
|
||||
'licenses.license' => 'Licencia',
|
||||
'licenses.licenseNumber' => ({required Object number}) => 'Licencia ${number}',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'licenses.licensesCount' => ({required Object count}) => '${count} licencias',
|
||||
'navigation.libraries' => 'Medios',
|
||||
'navigation.downloads' => 'Descargas',
|
||||
@@ -1840,8 +1858,6 @@ extension on TranslationsEs {
|
||||
'liveTv.guide' => 'Guía',
|
||||
'liveTv.noChannels' => 'No hay canales disponibles',
|
||||
'liveTv.noDvr' => 'No hay DVR configurado en ningún servidor',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.noPrograms' => 'No hay datos de programación disponibles',
|
||||
'liveTv.live' => 'EN VIVO',
|
||||
'liveTv.reloadGuide' => 'Recargar guía',
|
||||
|
||||
@@ -340,6 +340,14 @@ class _TranslationsSettingsFr implements TranslationsSettingsEn {
|
||||
@override String get showNavBarLabelsDescription => 'Afficher les libellés sous les icônes de la barre de navigation';
|
||||
@override String get liveTvDefaultFavorites => 'Chaînes favorites par défaut';
|
||||
@override String get liveTvDefaultFavoritesDescription => 'Afficher uniquement les chaînes favorites à l\'ouverture de la TV en direct';
|
||||
@override String get display => 'Display';
|
||||
@override String get homeScreen => 'Home Screen';
|
||||
@override String get navigation => 'Navigation';
|
||||
@override String get content => 'Content';
|
||||
@override String get player => 'Player';
|
||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||
@override String get seekAndTiming => 'Seek & Timing';
|
||||
@override String get behavior => 'Behavior';
|
||||
}
|
||||
|
||||
// Path: search
|
||||
@@ -1539,6 +1547,14 @@ extension on TranslationsFr {
|
||||
'settings.showNavBarLabelsDescription' => 'Afficher les libellés sous les icônes de la barre de navigation',
|
||||
'settings.liveTvDefaultFavorites' => 'Chaînes favorites par défaut',
|
||||
'settings.liveTvDefaultFavoritesDescription' => 'Afficher uniquement les chaînes favorites à l\'ouverture de la TV en direct',
|
||||
'settings.display' => 'Display',
|
||||
'settings.homeScreen' => 'Home Screen',
|
||||
'settings.navigation' => 'Navigation',
|
||||
'settings.content' => 'Content',
|
||||
'settings.player' => 'Player',
|
||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||
'settings.seekAndTiming' => 'Seek & Timing',
|
||||
'settings.behavior' => 'Behavior',
|
||||
'search.hint' => 'Rechercher des films, des séries, de la musique...',
|
||||
'search.tryDifferentTerm' => 'Essayez un autre terme de recherche',
|
||||
'search.searchYourMedia' => 'Rechercher dans vos médias',
|
||||
@@ -1832,6 +1848,8 @@ extension on TranslationsFr {
|
||||
'licenses.relatedPackages' => 'Package associés',
|
||||
'licenses.license' => 'Licence',
|
||||
'licenses.licenseNumber' => ({required Object number}) => 'Licence ${number}',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'licenses.licensesCount' => ({required Object count}) => '${count} licences',
|
||||
'navigation.libraries' => 'Medias',
|
||||
'navigation.downloads' => 'Téléch.',
|
||||
@@ -1840,8 +1858,6 @@ extension on TranslationsFr {
|
||||
'liveTv.guide' => 'Guide',
|
||||
'liveTv.noChannels' => 'Aucune chaîne disponible',
|
||||
'liveTv.noDvr' => 'Aucun DVR configuré sur les serveurs',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.noPrograms' => 'Aucune donnée de programme disponible',
|
||||
'liveTv.live' => 'EN DIRECT',
|
||||
'liveTv.reloadGuide' => 'Recharger le guide',
|
||||
|
||||
@@ -340,6 +340,14 @@ class _TranslationsSettingsIt implements TranslationsSettingsEn {
|
||||
@override String get showNavBarLabelsDescription => 'Mostra le etichette sotto le icone della barra di navigazione';
|
||||
@override String get liveTvDefaultFavorites => 'Canali preferiti predefiniti';
|
||||
@override String get liveTvDefaultFavoritesDescription => 'Mostra solo i canali preferiti all\'apertura della TV dal vivo';
|
||||
@override String get display => 'Display';
|
||||
@override String get homeScreen => 'Home Screen';
|
||||
@override String get navigation => 'Navigation';
|
||||
@override String get content => 'Content';
|
||||
@override String get player => 'Player';
|
||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||
@override String get seekAndTiming => 'Seek & Timing';
|
||||
@override String get behavior => 'Behavior';
|
||||
}
|
||||
|
||||
// Path: search
|
||||
@@ -1539,6 +1547,14 @@ extension on TranslationsIt {
|
||||
'settings.showNavBarLabelsDescription' => 'Mostra le etichette sotto le icone della barra di navigazione',
|
||||
'settings.liveTvDefaultFavorites' => 'Canali preferiti predefiniti',
|
||||
'settings.liveTvDefaultFavoritesDescription' => 'Mostra solo i canali preferiti all\'apertura della TV dal vivo',
|
||||
'settings.display' => 'Display',
|
||||
'settings.homeScreen' => 'Home Screen',
|
||||
'settings.navigation' => 'Navigation',
|
||||
'settings.content' => 'Content',
|
||||
'settings.player' => 'Player',
|
||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||
'settings.seekAndTiming' => 'Seek & Timing',
|
||||
'settings.behavior' => 'Behavior',
|
||||
'search.hint' => 'Cerca film. spettacoli, musica...',
|
||||
'search.tryDifferentTerm' => 'Prova altri termini di ricerca',
|
||||
'search.searchYourMedia' => 'Cerca nei tuoi media',
|
||||
@@ -1832,6 +1848,8 @@ extension on TranslationsIt {
|
||||
'licenses.relatedPackages' => 'Pacchetti correlati',
|
||||
'licenses.license' => 'Licenza',
|
||||
'licenses.licenseNumber' => ({required Object number}) => 'Licenza ${number}',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'licenses.licensesCount' => ({required Object count}) => '${count} licenze',
|
||||
'navigation.libraries' => 'Librerie',
|
||||
'navigation.downloads' => 'Download',
|
||||
@@ -1840,8 +1858,6 @@ extension on TranslationsIt {
|
||||
'liveTv.guide' => 'Guida',
|
||||
'liveTv.noChannels' => 'Nessun canale disponibile',
|
||||
'liveTv.noDvr' => 'Nessun DVR configurato su nessun server',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.noPrograms' => 'Nessun dato di programma disponibile',
|
||||
'liveTv.live' => 'IN DIRETTA',
|
||||
'liveTv.reloadGuide' => 'Ricarica guida',
|
||||
|
||||
@@ -340,6 +340,14 @@ class _TranslationsSettingsJa implements TranslationsSettingsEn {
|
||||
@override String get showNavBarLabelsDescription => 'ナビゲーションバーアイコンの下にテキストラベルを表示';
|
||||
@override String get liveTvDefaultFavorites => 'お気に入りチャンネルをデフォルトに';
|
||||
@override String get liveTvDefaultFavoritesDescription => 'ライブTV を開いたときにお気に入りチャンネルのみ表示';
|
||||
@override String get display => 'Display';
|
||||
@override String get homeScreen => 'Home Screen';
|
||||
@override String get navigation => 'Navigation';
|
||||
@override String get content => 'Content';
|
||||
@override String get player => 'Player';
|
||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||
@override String get seekAndTiming => 'Seek & Timing';
|
||||
@override String get behavior => 'Behavior';
|
||||
}
|
||||
|
||||
// Path: search
|
||||
@@ -1539,6 +1547,14 @@ extension on TranslationsJa {
|
||||
'settings.showNavBarLabelsDescription' => 'ナビゲーションバーアイコンの下にテキストラベルを表示',
|
||||
'settings.liveTvDefaultFavorites' => 'お気に入りチャンネルをデフォルトに',
|
||||
'settings.liveTvDefaultFavoritesDescription' => 'ライブTV を開いたときにお気に入りチャンネルのみ表示',
|
||||
'settings.display' => 'Display',
|
||||
'settings.homeScreen' => 'Home Screen',
|
||||
'settings.navigation' => 'Navigation',
|
||||
'settings.content' => 'Content',
|
||||
'settings.player' => 'Player',
|
||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||
'settings.seekAndTiming' => 'Seek & Timing',
|
||||
'settings.behavior' => 'Behavior',
|
||||
'search.hint' => '映画、番組、音楽を検索...',
|
||||
'search.tryDifferentTerm' => '別の検索語をお試しください',
|
||||
'search.searchYourMedia' => 'メディアを検索',
|
||||
@@ -1832,6 +1848,8 @@ extension on TranslationsJa {
|
||||
'licenses.relatedPackages' => '関連パッケージ',
|
||||
'licenses.license' => 'ライセンス',
|
||||
'licenses.licenseNumber' => ({required Object number}) => 'ライセンス ${number}',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'licenses.licensesCount' => ({required Object count}) => '${count}件のライセンス',
|
||||
'navigation.libraries' => 'ライブラリ',
|
||||
'navigation.downloads' => 'ダウンロード',
|
||||
@@ -1840,8 +1858,6 @@ extension on TranslationsJa {
|
||||
'liveTv.guide' => '番組表',
|
||||
'liveTv.noChannels' => 'チャンネルがありません',
|
||||
'liveTv.noDvr' => 'どのサーバーにもDVRが設定されていません',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.noPrograms' => '番組データがありません',
|
||||
'liveTv.live' => 'ライブ',
|
||||
'liveTv.reloadGuide' => '番組表を再読込',
|
||||
|
||||
@@ -340,6 +340,14 @@ class _TranslationsSettingsKo implements TranslationsSettingsEn {
|
||||
@override String get showNavBarLabelsDescription => '내비게이션 바 아이콘 아래에 텍스트 라벨을 표시합니다';
|
||||
@override String get liveTvDefaultFavorites => '즐겨찾기 채널 기본 설정';
|
||||
@override String get liveTvDefaultFavoritesDescription => '라이브 TV를 열 때 즐겨찾기 채널만 표시';
|
||||
@override String get display => 'Display';
|
||||
@override String get homeScreen => 'Home Screen';
|
||||
@override String get navigation => 'Navigation';
|
||||
@override String get content => 'Content';
|
||||
@override String get player => 'Player';
|
||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||
@override String get seekAndTiming => 'Seek & Timing';
|
||||
@override String get behavior => 'Behavior';
|
||||
}
|
||||
|
||||
// Path: search
|
||||
@@ -1539,6 +1547,14 @@ extension on TranslationsKo {
|
||||
'settings.showNavBarLabelsDescription' => '내비게이션 바 아이콘 아래에 텍스트 라벨을 표시합니다',
|
||||
'settings.liveTvDefaultFavorites' => '즐겨찾기 채널 기본 설정',
|
||||
'settings.liveTvDefaultFavoritesDescription' => '라이브 TV를 열 때 즐겨찾기 채널만 표시',
|
||||
'settings.display' => 'Display',
|
||||
'settings.homeScreen' => 'Home Screen',
|
||||
'settings.navigation' => 'Navigation',
|
||||
'settings.content' => 'Content',
|
||||
'settings.player' => 'Player',
|
||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||
'settings.seekAndTiming' => 'Seek & Timing',
|
||||
'settings.behavior' => 'Behavior',
|
||||
'search.hint' => '영화, 시리즈, 음악 등을 검색하세요...',
|
||||
'search.tryDifferentTerm' => '다른 검색어를 시도해 보세요',
|
||||
'search.searchYourMedia' => '미디어 검색',
|
||||
@@ -1832,6 +1848,8 @@ extension on TranslationsKo {
|
||||
'licenses.relatedPackages' => '관련 소프트웨어 패키지',
|
||||
'licenses.license' => '라이선스',
|
||||
'licenses.licenseNumber' => ({required Object number}) => '라이선스 ${number}',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'licenses.licensesCount' => ({required Object count}) => '${count} 개의 라이선스',
|
||||
'navigation.libraries' => '미디어 라이브러리',
|
||||
'navigation.downloads' => '다운로드',
|
||||
@@ -1840,8 +1858,6 @@ extension on TranslationsKo {
|
||||
'liveTv.guide' => '편성표',
|
||||
'liveTv.noChannels' => '사용 가능한 채널이 없습니다',
|
||||
'liveTv.noDvr' => '서버에 DVR이 구성되어 있지 않습니다',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.noPrograms' => '프로그램 데이터가 없습니다',
|
||||
'liveTv.live' => '실시간',
|
||||
'liveTv.reloadGuide' => '편성표 새로고침',
|
||||
|
||||
@@ -340,6 +340,14 @@ class _TranslationsSettingsNb implements TranslationsSettingsEn {
|
||||
@override String get showNavBarLabelsDescription => 'Vis tekstlabeler under navigasjonsfeltikoner';
|
||||
@override String get liveTvDefaultFavorites => 'Standard til favorittkanaler';
|
||||
@override String get liveTvDefaultFavoritesDescription => 'Vis kun favorittkanaler når du åpner Live TV';
|
||||
@override String get display => 'Display';
|
||||
@override String get homeScreen => 'Home Screen';
|
||||
@override String get navigation => 'Navigation';
|
||||
@override String get content => 'Content';
|
||||
@override String get player => 'Player';
|
||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||
@override String get seekAndTiming => 'Seek & Timing';
|
||||
@override String get behavior => 'Behavior';
|
||||
}
|
||||
|
||||
// Path: search
|
||||
@@ -1539,6 +1547,14 @@ extension on TranslationsNb {
|
||||
'settings.showNavBarLabelsDescription' => 'Vis tekstlabeler under navigasjonsfeltikoner',
|
||||
'settings.liveTvDefaultFavorites' => 'Standard til favorittkanaler',
|
||||
'settings.liveTvDefaultFavoritesDescription' => 'Vis kun favorittkanaler når du åpner Live TV',
|
||||
'settings.display' => 'Display',
|
||||
'settings.homeScreen' => 'Home Screen',
|
||||
'settings.navigation' => 'Navigation',
|
||||
'settings.content' => 'Content',
|
||||
'settings.player' => 'Player',
|
||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||
'settings.seekAndTiming' => 'Seek & Timing',
|
||||
'settings.behavior' => 'Behavior',
|
||||
'search.hint' => 'Søk i filmer, serier, musikk...',
|
||||
'search.tryDifferentTerm' => 'Prøv et annet søkeord',
|
||||
'search.searchYourMedia' => 'Søk i mediene dine',
|
||||
@@ -1832,6 +1848,8 @@ extension on TranslationsNb {
|
||||
'licenses.relatedPackages' => 'Relaterte pakker',
|
||||
'licenses.license' => 'Lisens',
|
||||
'licenses.licenseNumber' => ({required Object number}) => 'Lisens ${number}',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'licenses.licensesCount' => ({required Object count}) => '${count} lisenser',
|
||||
'navigation.libraries' => 'Biblioteker',
|
||||
'navigation.downloads' => 'Nedlastinger',
|
||||
@@ -1840,8 +1858,6 @@ extension on TranslationsNb {
|
||||
'liveTv.guide' => 'Programguide',
|
||||
'liveTv.noChannels' => 'Ingen kanaler tilgjengelig',
|
||||
'liveTv.noDvr' => 'Ingen DVR konfigurert på noen server',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.noPrograms' => 'Ingen programdata tilgjengelig',
|
||||
'liveTv.live' => 'DIREKTE',
|
||||
'liveTv.reloadGuide' => 'Last inn programguide på nytt',
|
||||
|
||||
@@ -340,6 +340,14 @@ class _TranslationsSettingsNl implements TranslationsSettingsEn {
|
||||
@override String get showNavBarLabelsDescription => 'Tekstlabels onder de pictogrammen van de navigatiebalk weergeven';
|
||||
@override String get liveTvDefaultFavorites => 'Standaard favoriete zenders';
|
||||
@override String get liveTvDefaultFavoritesDescription => 'Toon alleen favoriete zenders bij het openen van Live TV';
|
||||
@override String get display => 'Display';
|
||||
@override String get homeScreen => 'Home Screen';
|
||||
@override String get navigation => 'Navigation';
|
||||
@override String get content => 'Content';
|
||||
@override String get player => 'Player';
|
||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||
@override String get seekAndTiming => 'Seek & Timing';
|
||||
@override String get behavior => 'Behavior';
|
||||
}
|
||||
|
||||
// Path: search
|
||||
@@ -1539,6 +1547,14 @@ extension on TranslationsNl {
|
||||
'settings.showNavBarLabelsDescription' => 'Tekstlabels onder de pictogrammen van de navigatiebalk weergeven',
|
||||
'settings.liveTvDefaultFavorites' => 'Standaard favoriete zenders',
|
||||
'settings.liveTvDefaultFavoritesDescription' => 'Toon alleen favoriete zenders bij het openen van Live TV',
|
||||
'settings.display' => 'Display',
|
||||
'settings.homeScreen' => 'Home Screen',
|
||||
'settings.navigation' => 'Navigation',
|
||||
'settings.content' => 'Content',
|
||||
'settings.player' => 'Player',
|
||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||
'settings.seekAndTiming' => 'Seek & Timing',
|
||||
'settings.behavior' => 'Behavior',
|
||||
'search.hint' => 'Zoek films, series, muziek...',
|
||||
'search.tryDifferentTerm' => 'Probeer een andere zoekterm',
|
||||
'search.searchYourMedia' => 'Zoek in je media',
|
||||
@@ -1832,6 +1848,8 @@ extension on TranslationsNl {
|
||||
'licenses.relatedPackages' => 'Gerelateerde pakketten',
|
||||
'licenses.license' => 'Licentie',
|
||||
'licenses.licenseNumber' => ({required Object number}) => 'Licentie ${number}',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'licenses.licensesCount' => ({required Object count}) => '${count} licenties',
|
||||
'navigation.libraries' => 'Media',
|
||||
'navigation.downloads' => 'Downloads',
|
||||
@@ -1840,8 +1858,6 @@ extension on TranslationsNl {
|
||||
'liveTv.guide' => 'Gids',
|
||||
'liveTv.noChannels' => 'Geen zenders beschikbaar',
|
||||
'liveTv.noDvr' => 'Geen DVR geconfigureerd op een server',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.noPrograms' => 'Geen programmagegevens beschikbaar',
|
||||
'liveTv.live' => 'LIVE',
|
||||
'liveTv.reloadGuide' => 'Gids herladen',
|
||||
|
||||
@@ -340,6 +340,14 @@ class _TranslationsSettingsPl implements TranslationsSettingsEn {
|
||||
@override String get showNavBarLabelsDescription => 'Wyświetl tekstowe etykiety pod ikonami paska nawigacji';
|
||||
@override String get liveTvDefaultFavorites => 'Domyślnie ulubione kanały';
|
||||
@override String get liveTvDefaultFavoritesDescription => 'Pokaż tylko ulubione kanały po otwarciu telewizji na żywo';
|
||||
@override String get display => 'Display';
|
||||
@override String get homeScreen => 'Home Screen';
|
||||
@override String get navigation => 'Navigation';
|
||||
@override String get content => 'Content';
|
||||
@override String get player => 'Player';
|
||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||
@override String get seekAndTiming => 'Seek & Timing';
|
||||
@override String get behavior => 'Behavior';
|
||||
}
|
||||
|
||||
// Path: search
|
||||
@@ -1539,6 +1547,14 @@ extension on TranslationsPl {
|
||||
'settings.showNavBarLabelsDescription' => 'Wyświetl tekstowe etykiety pod ikonami paska nawigacji',
|
||||
'settings.liveTvDefaultFavorites' => 'Domyślnie ulubione kanały',
|
||||
'settings.liveTvDefaultFavoritesDescription' => 'Pokaż tylko ulubione kanały po otwarciu telewizji na żywo',
|
||||
'settings.display' => 'Display',
|
||||
'settings.homeScreen' => 'Home Screen',
|
||||
'settings.navigation' => 'Navigation',
|
||||
'settings.content' => 'Content',
|
||||
'settings.player' => 'Player',
|
||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||
'settings.seekAndTiming' => 'Seek & Timing',
|
||||
'settings.behavior' => 'Behavior',
|
||||
'search.hint' => 'Szukaj filmów, seriali, muzyki...',
|
||||
'search.tryDifferentTerm' => 'Spróbuj innego wyszukiwania',
|
||||
'search.searchYourMedia' => 'Przeszukaj swoje media',
|
||||
@@ -1832,6 +1848,8 @@ extension on TranslationsPl {
|
||||
'licenses.relatedPackages' => 'Powiązane pakiety',
|
||||
'licenses.license' => 'Licencja',
|
||||
'licenses.licenseNumber' => ({required Object number}) => 'Licencja ${number}',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'licenses.licensesCount' => ({required Object count}) => '${count} licencji',
|
||||
'navigation.libraries' => 'Biblioteki',
|
||||
'navigation.downloads' => 'Pobrania',
|
||||
@@ -1840,8 +1858,6 @@ extension on TranslationsPl {
|
||||
'liveTv.guide' => 'Przewodnik',
|
||||
'liveTv.noChannels' => 'Brak dostępnych kanałów',
|
||||
'liveTv.noDvr' => 'Brak skonfigurowanego DVR na żadnym serwerze',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.noPrograms' => 'Brak danych o programach',
|
||||
'liveTv.live' => 'NA ŻYWO',
|
||||
'liveTv.reloadGuide' => 'Odśwież przewodnik',
|
||||
|
||||
@@ -340,6 +340,14 @@ class _TranslationsSettingsPt implements TranslationsSettingsEn {
|
||||
@override String get showNavBarLabelsDescription => 'Exibir rótulos de texto sob os ícones da barra de navegação';
|
||||
@override String get liveTvDefaultFavorites => 'Canais favoritos por padrão';
|
||||
@override String get liveTvDefaultFavoritesDescription => 'Mostrar apenas canais favoritos ao abrir TV ao vivo';
|
||||
@override String get display => 'Display';
|
||||
@override String get homeScreen => 'Home Screen';
|
||||
@override String get navigation => 'Navigation';
|
||||
@override String get content => 'Content';
|
||||
@override String get player => 'Player';
|
||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||
@override String get seekAndTiming => 'Seek & Timing';
|
||||
@override String get behavior => 'Behavior';
|
||||
}
|
||||
|
||||
// Path: search
|
||||
@@ -1539,6 +1547,14 @@ extension on TranslationsPt {
|
||||
'settings.showNavBarLabelsDescription' => 'Exibir rótulos de texto sob os ícones da barra de navegação',
|
||||
'settings.liveTvDefaultFavorites' => 'Canais favoritos por padrão',
|
||||
'settings.liveTvDefaultFavoritesDescription' => 'Mostrar apenas canais favoritos ao abrir TV ao vivo',
|
||||
'settings.display' => 'Display',
|
||||
'settings.homeScreen' => 'Home Screen',
|
||||
'settings.navigation' => 'Navigation',
|
||||
'settings.content' => 'Content',
|
||||
'settings.player' => 'Player',
|
||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||
'settings.seekAndTiming' => 'Seek & Timing',
|
||||
'settings.behavior' => 'Behavior',
|
||||
'search.hint' => 'Buscar filmes, séries, músicas...',
|
||||
'search.tryDifferentTerm' => 'Tente um termo de busca diferente',
|
||||
'search.searchYourMedia' => 'Buscar suas mídias',
|
||||
@@ -1832,6 +1848,8 @@ extension on TranslationsPt {
|
||||
'licenses.relatedPackages' => 'Pacotes Relacionados',
|
||||
'licenses.license' => 'Licença',
|
||||
'licenses.licenseNumber' => ({required Object number}) => 'Licença ${number}',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'licenses.licensesCount' => ({required Object count}) => '${count} licenças',
|
||||
'navigation.libraries' => 'Bibliotecas',
|
||||
'navigation.downloads' => 'Downloads',
|
||||
@@ -1840,8 +1858,6 @@ extension on TranslationsPt {
|
||||
'liveTv.guide' => 'Guia',
|
||||
'liveTv.noChannels' => 'Nenhum canal disponível',
|
||||
'liveTv.noDvr' => 'Nenhum DVR configurado em nenhum servidor',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.noPrograms' => 'Nenhum dado de programação disponível',
|
||||
'liveTv.live' => 'AO VIVO',
|
||||
'liveTv.reloadGuide' => 'Recarregar Guia',
|
||||
|
||||
@@ -340,6 +340,14 @@ class _TranslationsSettingsRu implements TranslationsSettingsEn {
|
||||
@override String get showNavBarLabelsDescription => 'Отображать текстовые подписи под иконками панели навигации';
|
||||
@override String get liveTvDefaultFavorites => 'Избранные каналы по умолчанию';
|
||||
@override String get liveTvDefaultFavoritesDescription => 'Показывать только избранные каналы при открытии ТВ';
|
||||
@override String get display => 'Display';
|
||||
@override String get homeScreen => 'Home Screen';
|
||||
@override String get navigation => 'Navigation';
|
||||
@override String get content => 'Content';
|
||||
@override String get player => 'Player';
|
||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||
@override String get seekAndTiming => 'Seek & Timing';
|
||||
@override String get behavior => 'Behavior';
|
||||
}
|
||||
|
||||
// Path: search
|
||||
@@ -1539,6 +1547,14 @@ extension on TranslationsRu {
|
||||
'settings.showNavBarLabelsDescription' => 'Отображать текстовые подписи под иконками панели навигации',
|
||||
'settings.liveTvDefaultFavorites' => 'Избранные каналы по умолчанию',
|
||||
'settings.liveTvDefaultFavoritesDescription' => 'Показывать только избранные каналы при открытии ТВ',
|
||||
'settings.display' => 'Display',
|
||||
'settings.homeScreen' => 'Home Screen',
|
||||
'settings.navigation' => 'Navigation',
|
||||
'settings.content' => 'Content',
|
||||
'settings.player' => 'Player',
|
||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||
'settings.seekAndTiming' => 'Seek & Timing',
|
||||
'settings.behavior' => 'Behavior',
|
||||
'search.hint' => 'Поиск фильмов, сериалов, музыки...',
|
||||
'search.tryDifferentTerm' => 'Попробуйте другой запрос',
|
||||
'search.searchYourMedia' => 'Поиск в вашей медиатеке',
|
||||
@@ -1832,6 +1848,8 @@ extension on TranslationsRu {
|
||||
'licenses.relatedPackages' => 'Связанные пакеты',
|
||||
'licenses.license' => 'Лицензия',
|
||||
'licenses.licenseNumber' => ({required Object number}) => 'Лицензия ${number}',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'licenses.licensesCount' => ({required Object count}) => '${count} лицензий',
|
||||
'navigation.libraries' => 'Библиотеки',
|
||||
'navigation.downloads' => 'Загрузки',
|
||||
@@ -1840,8 +1858,6 @@ extension on TranslationsRu {
|
||||
'liveTv.guide' => 'Программа',
|
||||
'liveTv.noChannels' => 'Нет доступных каналов',
|
||||
'liveTv.noDvr' => 'DVR не настроен ни на одном сервере',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.noPrograms' => 'Нет данных о программах',
|
||||
'liveTv.live' => 'ЭФИР',
|
||||
'liveTv.reloadGuide' => 'Перезагрузить программу',
|
||||
|
||||
@@ -340,6 +340,14 @@ class _TranslationsSettingsSv implements TranslationsSettingsEn {
|
||||
@override String get showNavBarLabelsDescription => 'Visa textetiketter under navigeringsfältets ikoner';
|
||||
@override String get liveTvDefaultFavorites => 'Standard till favoritkanaler';
|
||||
@override String get liveTvDefaultFavoritesDescription => 'Visa bara favoritkanaler när du öppnar Live TV';
|
||||
@override String get display => 'Display';
|
||||
@override String get homeScreen => 'Home Screen';
|
||||
@override String get navigation => 'Navigation';
|
||||
@override String get content => 'Content';
|
||||
@override String get player => 'Player';
|
||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||
@override String get seekAndTiming => 'Seek & Timing';
|
||||
@override String get behavior => 'Behavior';
|
||||
}
|
||||
|
||||
// Path: search
|
||||
@@ -1539,6 +1547,14 @@ extension on TranslationsSv {
|
||||
'settings.showNavBarLabelsDescription' => 'Visa textetiketter under navigeringsfältets ikoner',
|
||||
'settings.liveTvDefaultFavorites' => 'Standard till favoritkanaler',
|
||||
'settings.liveTvDefaultFavoritesDescription' => 'Visa bara favoritkanaler när du öppnar Live TV',
|
||||
'settings.display' => 'Display',
|
||||
'settings.homeScreen' => 'Home Screen',
|
||||
'settings.navigation' => 'Navigation',
|
||||
'settings.content' => 'Content',
|
||||
'settings.player' => 'Player',
|
||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||
'settings.seekAndTiming' => 'Seek & Timing',
|
||||
'settings.behavior' => 'Behavior',
|
||||
'search.hint' => 'Sök filmer, serier, musik...',
|
||||
'search.tryDifferentTerm' => 'Prova en annan sökterm',
|
||||
'search.searchYourMedia' => 'Sök i dina media',
|
||||
@@ -1832,6 +1848,8 @@ extension on TranslationsSv {
|
||||
'licenses.relatedPackages' => 'Relaterade paket',
|
||||
'licenses.license' => 'Licens',
|
||||
'licenses.licenseNumber' => ({required Object number}) => 'Licens ${number}',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'licenses.licensesCount' => ({required Object count}) => '${count} licenser',
|
||||
'navigation.libraries' => 'Bibliotek',
|
||||
'navigation.downloads' => 'Nerladdat',
|
||||
@@ -1840,8 +1858,6 @@ extension on TranslationsSv {
|
||||
'liveTv.guide' => 'Programguide',
|
||||
'liveTv.noChannels' => 'Inga kanaler tillgängliga',
|
||||
'liveTv.noDvr' => 'Ingen DVR konfigurerad på någon server',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.noPrograms' => 'Ingen programdata tillgänglig',
|
||||
'liveTv.live' => 'LIVE',
|
||||
'liveTv.reloadGuide' => 'Ladda om programguide',
|
||||
|
||||
@@ -340,6 +340,14 @@ class _TranslationsSettingsZh implements TranslationsSettingsEn {
|
||||
@override String get showNavBarLabelsDescription => '在导航栏图标下方显示文字标签';
|
||||
@override String get liveTvDefaultFavorites => '默认显示收藏频道';
|
||||
@override String get liveTvDefaultFavoritesDescription => '打开直播电视时仅显示收藏频道';
|
||||
@override String get display => 'Display';
|
||||
@override String get homeScreen => 'Home Screen';
|
||||
@override String get navigation => 'Navigation';
|
||||
@override String get content => 'Content';
|
||||
@override String get player => 'Player';
|
||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||
@override String get seekAndTiming => 'Seek & Timing';
|
||||
@override String get behavior => 'Behavior';
|
||||
}
|
||||
|
||||
// Path: search
|
||||
@@ -1539,6 +1547,14 @@ extension on TranslationsZh {
|
||||
'settings.showNavBarLabelsDescription' => '在导航栏图标下方显示文字标签',
|
||||
'settings.liveTvDefaultFavorites' => '默认显示收藏频道',
|
||||
'settings.liveTvDefaultFavoritesDescription' => '打开直播电视时仅显示收藏频道',
|
||||
'settings.display' => 'Display',
|
||||
'settings.homeScreen' => 'Home Screen',
|
||||
'settings.navigation' => 'Navigation',
|
||||
'settings.content' => 'Content',
|
||||
'settings.player' => 'Player',
|
||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||
'settings.seekAndTiming' => 'Seek & Timing',
|
||||
'settings.behavior' => 'Behavior',
|
||||
'search.hint' => '搜索电影、系列、音乐...',
|
||||
'search.tryDifferentTerm' => '尝试不同的搜索词',
|
||||
'search.searchYourMedia' => '搜索媒体',
|
||||
@@ -1832,6 +1848,8 @@ extension on TranslationsZh {
|
||||
'licenses.relatedPackages' => '相关软件包',
|
||||
'licenses.license' => '许可证',
|
||||
'licenses.licenseNumber' => ({required Object number}) => '许可证 ${number}',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'licenses.licensesCount' => ({required Object count}) => '${count} 个许可证',
|
||||
'navigation.libraries' => '媒体库',
|
||||
'navigation.downloads' => '下载',
|
||||
@@ -1840,8 +1858,6 @@ extension on TranslationsZh {
|
||||
'liveTv.guide' => '节目指南',
|
||||
'liveTv.noChannels' => '没有可用的频道',
|
||||
'liveTv.noDvr' => '没有服务器配置了DVR',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.noPrograms' => '没有可用的节目数据',
|
||||
'liveTv.live' => '直播',
|
||||
'liveTv.reloadGuide' => '重新加载节目指南',
|
||||
|
||||
@@ -220,7 +220,15 @@
|
||||
"showNavBarLabels": "Visa navigeringsfältets etiketter",
|
||||
"showNavBarLabelsDescription": "Visa textetiketter under navigeringsfältets ikoner",
|
||||
"liveTvDefaultFavorites": "Standard till favoritkanaler",
|
||||
"liveTvDefaultFavoritesDescription": "Visa bara favoritkanaler när du öppnar Live TV"
|
||||
"liveTvDefaultFavoritesDescription": "Visa bara favoritkanaler när du öppnar Live TV",
|
||||
"display": "Display",
|
||||
"homeScreen": "Home Screen",
|
||||
"navigation": "Navigation",
|
||||
"content": "Content",
|
||||
"player": "Player",
|
||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||
"seekAndTiming": "Seek & Timing",
|
||||
"behavior": "Behavior"
|
||||
},
|
||||
"search": {
|
||||
"hint": "Sök filmer, serier, musik...",
|
||||
|
||||
@@ -220,7 +220,15 @@
|
||||
"showNavBarLabels": "显示导航栏标签",
|
||||
"showNavBarLabelsDescription": "在导航栏图标下方显示文字标签",
|
||||
"liveTvDefaultFavorites": "默认显示收藏频道",
|
||||
"liveTvDefaultFavoritesDescription": "打开直播电视时仅显示收藏频道"
|
||||
"liveTvDefaultFavoritesDescription": "打开直播电视时仅显示收藏频道",
|
||||
"display": "Display",
|
||||
"homeScreen": "Home Screen",
|
||||
"navigation": "Navigation",
|
||||
"content": "Content",
|
||||
"player": "Player",
|
||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||
"seekAndTiming": "Seek & Timing",
|
||||
"behavior": "Behavior"
|
||||
},
|
||||
"search": {
|
||||
"hint": "搜索电影、系列、音乐...",
|
||||
|
||||
@@ -0,0 +1,396 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../i18n/strings.g.dart';
|
||||
import '../../providers/settings_provider.dart';
|
||||
import '../../providers/theme_provider.dart';
|
||||
import '../../providers/user_profile_provider.dart';
|
||||
import '../../services/settings_service.dart' as settings;
|
||||
import '../../utils/platform_detector.dart';
|
||||
import '../../widgets/app_icon.dart';
|
||||
import '../../widgets/focused_scroll_scaffold.dart';
|
||||
import '../../widgets/settings_section.dart';
|
||||
import 'settings_utils.dart';
|
||||
|
||||
class AppearanceSettingsScreen extends StatefulWidget {
|
||||
const AppearanceSettingsScreen({super.key});
|
||||
|
||||
@override
|
||||
State<AppearanceSettingsScreen> createState() => _AppearanceSettingsScreenState();
|
||||
}
|
||||
|
||||
class _AppearanceSettingsScreenState extends State<AppearanceSettingsScreen> {
|
||||
late settings.SettingsService _settingsService;
|
||||
bool _isLoading = true;
|
||||
bool _requireProfileSelectionOnOpen = false;
|
||||
bool _confirmExitOnBack = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadSettings();
|
||||
}
|
||||
|
||||
Future<void> _loadSettings() async {
|
||||
_settingsService = await settings.SettingsService.getInstance();
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_requireProfileSelectionOnOpen = _settingsService.getRequireProfileSelectionOnOpen();
|
||||
_confirmExitOnBack = _settingsService.getConfirmExitOnBack();
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_isLoading) {
|
||||
return FocusedScrollScaffold(
|
||||
title: Text(t.settings.appearance),
|
||||
slivers: [const SliverFillRemaining(child: Center(child: CircularProgressIndicator()))],
|
||||
);
|
||||
}
|
||||
|
||||
return FocusedScrollScaffold(
|
||||
title: Text(t.settings.appearance),
|
||||
slivers: [
|
||||
SliverList(
|
||||
delegate: SliverChildListDelegate([
|
||||
// --- Display ---
|
||||
SettingsSectionHeader(t.settings.display),
|
||||
_buildThemeSelector(),
|
||||
_buildLanguageSelector(),
|
||||
_buildDensitySelector(),
|
||||
_buildViewModeSelector(),
|
||||
_buildEpisodePosterModeSelector(),
|
||||
|
||||
// --- Home Screen ---
|
||||
SettingsSectionHeader(t.settings.homeScreen),
|
||||
_buildShowHeroSection(),
|
||||
_buildUseGlobalHubs(),
|
||||
_buildShowServerNameOnHubs(),
|
||||
|
||||
// --- Navigation ---
|
||||
SettingsSectionHeader(t.settings.navigation),
|
||||
if (PlatformDetector.shouldUseSideNavigation(context)) _buildAlwaysKeepSidebarOpen(),
|
||||
if (!PlatformDetector.shouldUseSideNavigation(context)) _buildShowNavBarLabels(),
|
||||
_buildShowUnwatchedCount(),
|
||||
|
||||
// --- Content ---
|
||||
SettingsSectionHeader(t.settings.content),
|
||||
_buildLiveTvDefaultFavorites(),
|
||||
_buildHideSpoilers(),
|
||||
_buildRequireProfileSelection(),
|
||||
if (PlatformDetector.isTV()) _buildConfirmExitOnBack(),
|
||||
const SizedBox(height: 24),
|
||||
]),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// --- Display section ---
|
||||
|
||||
Widget _buildThemeSelector() {
|
||||
return Consumer<ThemeProvider>(
|
||||
builder: (context, themeProvider, child) {
|
||||
return SegmentedSetting<settings.ThemeMode>(
|
||||
icon: themeProvider.themeModeIcon,
|
||||
title: t.settings.theme,
|
||||
segments: [
|
||||
ButtonSegment(value: settings.ThemeMode.system, label: Text(t.settings.systemTheme)),
|
||||
ButtonSegment(value: settings.ThemeMode.light, label: Text(t.settings.lightTheme)),
|
||||
ButtonSegment(value: settings.ThemeMode.dark, label: Text(t.settings.darkTheme)),
|
||||
ButtonSegment(value: settings.ThemeMode.oled, label: Text(t.settings.oledTheme)),
|
||||
],
|
||||
selected: themeProvider.themeMode,
|
||||
onChanged: (value) => themeProvider.setThemeMode(value),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLanguageSelector() {
|
||||
return ListTile(
|
||||
leading: const AppIcon(Symbols.language_rounded, fill: 1),
|
||||
title: Text(t.settings.language),
|
||||
subtitle: Text(_getLanguageDisplayName(LocaleSettings.currentLocale)),
|
||||
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
|
||||
onTap: () async {
|
||||
final value = await showSelectionDialog<AppLocale>(
|
||||
context: context,
|
||||
title: t.settings.language,
|
||||
options: AppLocale.values
|
||||
.map((locale) => DialogOption(value: locale, title: _getLanguageDisplayName(locale)))
|
||||
.toList(),
|
||||
currentValue: LocaleSettings.currentLocale,
|
||||
);
|
||||
if (value != null) {
|
||||
await _settingsService.setAppLocale(value);
|
||||
LocaleSettings.setLocale(value);
|
||||
_restartApp();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDensitySelector() {
|
||||
return Consumer<SettingsProvider>(
|
||||
builder: (context, settingsProvider, child) {
|
||||
return SegmentedSetting<settings.LibraryDensity>(
|
||||
icon: Symbols.grid_view_rounded,
|
||||
title: t.settings.libraryDensity,
|
||||
segments: [
|
||||
ButtonSegment(value: settings.LibraryDensity.compact, label: Text(t.settings.compact)),
|
||||
ButtonSegment(value: settings.LibraryDensity.normal, label: Text(t.settings.normal)),
|
||||
ButtonSegment(value: settings.LibraryDensity.comfortable, label: Text(t.settings.comfortable)),
|
||||
],
|
||||
selected: settingsProvider.libraryDensity,
|
||||
onChanged: (value) => settingsProvider.setLibraryDensity(value),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildViewModeSelector() {
|
||||
return Consumer<SettingsProvider>(
|
||||
builder: (context, settingsProvider, child) {
|
||||
return SegmentedSetting<settings.ViewMode>(
|
||||
icon: Symbols.view_list_rounded,
|
||||
title: t.settings.viewMode,
|
||||
segments: [
|
||||
ButtonSegment(value: settings.ViewMode.grid, label: Text(t.settings.gridView)),
|
||||
ButtonSegment(value: settings.ViewMode.list, label: Text(t.settings.listView)),
|
||||
],
|
||||
selected: settingsProvider.viewMode,
|
||||
onChanged: (value) => settingsProvider.setViewMode(value),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEpisodePosterModeSelector() {
|
||||
return Consumer<SettingsProvider>(
|
||||
builder: (context, settingsProvider, child) {
|
||||
return SegmentedSetting<settings.EpisodePosterMode>(
|
||||
icon: Symbols.image_rounded,
|
||||
title: t.settings.episodePosterMode,
|
||||
segments: [
|
||||
ButtonSegment(value: settings.EpisodePosterMode.seriesPoster, label: Text(t.settings.seriesPoster)),
|
||||
ButtonSegment(value: settings.EpisodePosterMode.seasonPoster, label: Text(t.settings.seasonPoster)),
|
||||
ButtonSegment(
|
||||
value: settings.EpisodePosterMode.episodeThumbnail, label: Text(t.settings.episodeThumbnail)),
|
||||
],
|
||||
selected: settingsProvider.episodePosterMode,
|
||||
onChanged: (value) => settingsProvider.setEpisodePosterMode(value),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// --- Home Screen section ---
|
||||
|
||||
Widget _buildShowHeroSection() {
|
||||
return Consumer<SettingsProvider>(
|
||||
builder: (context, settingsProvider, child) {
|
||||
return SwitchListTile(
|
||||
secondary: const AppIcon(Symbols.featured_play_list_rounded, fill: 1),
|
||||
title: Text(t.settings.showHeroSection),
|
||||
subtitle: Text(t.settings.showHeroSectionDescription),
|
||||
value: settingsProvider.showHeroSection,
|
||||
onChanged: (value) async {
|
||||
await settingsProvider.setShowHeroSection(value);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildUseGlobalHubs() {
|
||||
return 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);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildShowServerNameOnHubs() {
|
||||
return Consumer<SettingsProvider>(
|
||||
builder: (context, settingsProvider, child) {
|
||||
return SwitchListTile(
|
||||
secondary: const AppIcon(Symbols.dns_rounded, fill: 1),
|
||||
title: Text(t.settings.showServerNameOnHubs),
|
||||
subtitle: Text(t.settings.showServerNameOnHubsDescription),
|
||||
value: settingsProvider.showServerNameOnHubs,
|
||||
onChanged: (value) async {
|
||||
await settingsProvider.setShowServerNameOnHubs(value);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// --- Navigation section ---
|
||||
|
||||
Widget _buildAlwaysKeepSidebarOpen() {
|
||||
return Consumer<SettingsProvider>(
|
||||
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);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildShowNavBarLabels() {
|
||||
return Consumer<SettingsProvider>(
|
||||
builder: (context, settingsProvider, child) {
|
||||
return SwitchListTile(
|
||||
secondary: const AppIcon(Symbols.label_rounded, fill: 1),
|
||||
title: Text(t.settings.showNavBarLabels),
|
||||
subtitle: Text(t.settings.showNavBarLabelsDescription),
|
||||
value: settingsProvider.showNavBarLabels,
|
||||
onChanged: (value) async {
|
||||
await settingsProvider.setShowNavBarLabels(value);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildShowUnwatchedCount() {
|
||||
return Consumer<SettingsProvider>(
|
||||
builder: (context, settingsProvider, child) {
|
||||
return SwitchListTile(
|
||||
secondary: const AppIcon(Symbols.counter_1_rounded, fill: 1),
|
||||
title: Text(t.settings.showUnwatchedCount),
|
||||
subtitle: Text(t.settings.showUnwatchedCountDescription),
|
||||
value: settingsProvider.showUnwatchedCount,
|
||||
onChanged: (value) async {
|
||||
await settingsProvider.setShowUnwatchedCount(value);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// --- Content section ---
|
||||
|
||||
Widget _buildLiveTvDefaultFavorites() {
|
||||
return Consumer<SettingsProvider>(
|
||||
builder: (context, settingsProvider, child) {
|
||||
return SwitchListTile(
|
||||
secondary: const AppIcon(Symbols.star_rounded, fill: 1),
|
||||
title: Text(t.settings.liveTvDefaultFavorites),
|
||||
subtitle: Text(t.settings.liveTvDefaultFavoritesDescription),
|
||||
value: settingsProvider.liveTvDefaultFavorites,
|
||||
onChanged: (value) async {
|
||||
await settingsProvider.setLiveTvDefaultFavorites(value);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHideSpoilers() {
|
||||
return Consumer<SettingsProvider>(
|
||||
builder: (context, settingsProvider, child) {
|
||||
return SwitchListTile(
|
||||
secondary: const AppIcon(Symbols.visibility_off_rounded, fill: 1),
|
||||
title: Text(t.settings.hideSpoilers),
|
||||
subtitle: Text(t.settings.hideSpoilersDescription),
|
||||
value: settingsProvider.hideSpoilers,
|
||||
onChanged: (value) async {
|
||||
await settingsProvider.setHideSpoilers(value);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRequireProfileSelection() {
|
||||
return Consumer<UserProfileProvider>(
|
||||
builder: (context, userProfileProvider, child) {
|
||||
if (!userProfileProvider.hasMultipleUsers) return const SizedBox.shrink();
|
||||
return SwitchListTile(
|
||||
secondary: const AppIcon(Symbols.person_rounded, fill: 1),
|
||||
title: Text(t.settings.requireProfileSelectionOnOpen),
|
||||
subtitle: Text(t.settings.requireProfileSelectionOnOpenDescription),
|
||||
value: _requireProfileSelectionOnOpen,
|
||||
onChanged: (value) async {
|
||||
setState(() => _requireProfileSelectionOnOpen = value);
|
||||
await _settingsService.setRequireProfileSelectionOnOpen(value);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildConfirmExitOnBack() {
|
||||
return SwitchListTile(
|
||||
secondary: const AppIcon(Symbols.exit_to_app_rounded, fill: 1),
|
||||
title: Text(t.settings.confirmExitOnBack),
|
||||
subtitle: Text(t.settings.confirmExitOnBackDescription),
|
||||
value: _confirmExitOnBack,
|
||||
onChanged: (value) async {
|
||||
setState(() => _confirmExitOnBack = value);
|
||||
await _settingsService.setConfirmExitOnBack(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
String _getLanguageDisplayName(AppLocale locale) {
|
||||
switch (locale) {
|
||||
case AppLocale.en:
|
||||
return 'English';
|
||||
case AppLocale.sv:
|
||||
return 'Svenska';
|
||||
case AppLocale.fr:
|
||||
return 'Français';
|
||||
case AppLocale.it:
|
||||
return 'Italiano';
|
||||
case AppLocale.nl:
|
||||
return 'Nederlands';
|
||||
case AppLocale.de:
|
||||
return 'Deutsch';
|
||||
case AppLocale.zh:
|
||||
return '中文';
|
||||
case AppLocale.ko:
|
||||
return '한국어';
|
||||
case AppLocale.es:
|
||||
return 'Español';
|
||||
case AppLocale.pt:
|
||||
return 'Português';
|
||||
case AppLocale.ja:
|
||||
return '日本語';
|
||||
case AppLocale.ru:
|
||||
return 'Русский';
|
||||
case AppLocale.pl:
|
||||
return 'Polski';
|
||||
case AppLocale.da:
|
||||
return 'Dansk';
|
||||
case AppLocale.nb:
|
||||
return 'Norsk bokmål';
|
||||
}
|
||||
}
|
||||
|
||||
void _restartApp() {
|
||||
Navigator.pushNamedAndRemoveUntil(context, '/', (route) => false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../i18n/strings.g.dart';
|
||||
import '../../models/hotkey_model.dart';
|
||||
import '../../services/keyboard_shortcuts_service.dart';
|
||||
import '../../utils/snackbar_helper.dart';
|
||||
import '../../widgets/focused_scroll_scaffold.dart';
|
||||
import 'hotkey_recorder_widget.dart';
|
||||
|
||||
class KeyboardShortcutsScreen extends StatefulWidget {
|
||||
final KeyboardShortcutsService keyboardService;
|
||||
|
||||
const KeyboardShortcutsScreen({super.key, required this.keyboardService});
|
||||
|
||||
@override
|
||||
State<KeyboardShortcutsScreen> createState() => _KeyboardShortcutsScreenState();
|
||||
}
|
||||
|
||||
class _KeyboardShortcutsScreenState extends State<KeyboardShortcutsScreen> {
|
||||
Map<String, HotKey> _hotkeys = {};
|
||||
bool _isLoading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadHotkeys();
|
||||
}
|
||||
|
||||
Future<void> _loadHotkeys() async {
|
||||
await widget.keyboardService.refreshFromStorage();
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_hotkeys = widget.keyboardService.hotkeys;
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FocusedScrollScaffold(
|
||||
title: Text(t.settings.keyboardShortcuts),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await widget.keyboardService.resetToDefaults();
|
||||
await _loadHotkeys();
|
||||
if (mounted) {
|
||||
showSuccessSnackBar(this.context, t.settings.shortcutsReset);
|
||||
}
|
||||
},
|
||||
child: Text(t.common.reset),
|
||||
),
|
||||
],
|
||||
slivers: _isLoading
|
||||
? [const SliverFillRemaining(child: Center(child: CircularProgressIndicator()))]
|
||||
: [
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
final actions = _hotkeys.keys.toList();
|
||||
final action = actions[index];
|
||||
final hotkey = _hotkeys[action]!;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: ListTile(
|
||||
title: Text(widget.keyboardService.getActionDisplayName(action)),
|
||||
subtitle: Text(action),
|
||||
trailing: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.fromBorderSide(BorderSide(color: Theme.of(context).dividerColor)),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(6)),
|
||||
),
|
||||
child: Text(
|
||||
widget.keyboardService.formatHotkey(hotkey),
|
||||
style: const TextStyle(fontFamily: 'monospace'),
|
||||
),
|
||||
),
|
||||
onTap: () => _editHotkey(action, hotkey),
|
||||
),
|
||||
);
|
||||
}, childCount: _hotkeys.length),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _editHotkey(String action, HotKey currentHotkey) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return HotKeyRecorderWidget(
|
||||
actionName: widget.keyboardService.getActionDisplayName(action),
|
||||
currentHotKey: currentHotkey,
|
||||
onHotKeyRecorded: (newHotkey) async {
|
||||
final navigator = Navigator.of(context);
|
||||
|
||||
// Check for conflicts
|
||||
final existingAction = widget.keyboardService.getActionForHotkey(newHotkey);
|
||||
if (existingAction != null && existingAction != action) {
|
||||
navigator.pop();
|
||||
showErrorSnackBar(
|
||||
context,
|
||||
t.settings.shortcutAlreadyAssigned(action: widget.keyboardService.getActionDisplayName(existingAction)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Save the new hotkey
|
||||
await widget.keyboardService.setHotkey(action, newHotkey);
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_hotkeys[action] = newHotkey;
|
||||
});
|
||||
|
||||
navigator.pop();
|
||||
|
||||
showSuccessSnackBar(
|
||||
this.context,
|
||||
t.settings.shortcutUpdated(action: widget.keyboardService.getActionDisplayName(action)),
|
||||
);
|
||||
}
|
||||
},
|
||||
onCancel: () => Navigator.pop(context),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,553 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
import '../../i18n/strings.g.dart';
|
||||
import '../../mpv/player/platform/player_android.dart';
|
||||
import '../../services/discord_rpc_service.dart';
|
||||
import '../../services/keyboard_shortcuts_service.dart';
|
||||
import '../../services/settings_service.dart' as settings;
|
||||
import '../../utils/platform_detector.dart';
|
||||
import '../../utils/snackbar_helper.dart';
|
||||
import '../../widgets/app_icon.dart';
|
||||
import '../../widgets/focused_scroll_scaffold.dart';
|
||||
import '../../widgets/settings_section.dart';
|
||||
import 'external_player_screen.dart';
|
||||
import 'mpv_config_screen.dart';
|
||||
import 'settings_utils.dart';
|
||||
import 'subtitle_styling_screen.dart';
|
||||
|
||||
class PlaybackSettingsScreen extends StatefulWidget {
|
||||
const PlaybackSettingsScreen({super.key});
|
||||
|
||||
@override
|
||||
State<PlaybackSettingsScreen> createState() => _PlaybackSettingsScreenState();
|
||||
}
|
||||
|
||||
class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
late settings.SettingsService _settingsService;
|
||||
KeyboardShortcutsService? _keyboardService;
|
||||
bool _isLoading = true;
|
||||
|
||||
bool _enableHardwareDecoding = true;
|
||||
int _bufferSize = 0;
|
||||
int _seekTimeSmall = 10;
|
||||
int _seekTimeLarge = 30;
|
||||
int _rewindOnResume = 0;
|
||||
int _sleepTimerDuration = 30;
|
||||
bool _rememberTrackSelections = true;
|
||||
bool _clickVideoTogglesPlayback = false;
|
||||
bool _autoSkipIntro = false;
|
||||
bool _autoSkipCredits = false;
|
||||
int _autoSkipDelay = 5;
|
||||
String _introPattern = settings.SettingsService.defaultIntroPattern;
|
||||
String _creditsPattern = settings.SettingsService.defaultCreditsPattern;
|
||||
int _maxVolume = 100;
|
||||
bool _enableDiscordRPC = false;
|
||||
bool _autoPip = true;
|
||||
bool _matchContentFrameRate = false;
|
||||
bool _tunneledPlayback = true;
|
||||
bool _useExoPlayer = true;
|
||||
bool _useExternalPlayer = false;
|
||||
String _selectedExternalPlayerName = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadSettings();
|
||||
}
|
||||
|
||||
Future<void> _loadSettings() async {
|
||||
_settingsService = await settings.SettingsService.getInstance();
|
||||
if (KeyboardShortcutsService.isPlatformSupported()) {
|
||||
_keyboardService = await KeyboardShortcutsService.getInstance();
|
||||
}
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_enableHardwareDecoding = _settingsService.getEnableHardwareDecoding();
|
||||
_bufferSize = _settingsService.getBufferSize();
|
||||
_seekTimeSmall = _settingsService.getSeekTimeSmall();
|
||||
_seekTimeLarge = _settingsService.getSeekTimeLarge();
|
||||
_rewindOnResume = _settingsService.getRewindOnResume();
|
||||
_sleepTimerDuration = _settingsService.getSleepTimerDuration();
|
||||
_rememberTrackSelections = _settingsService.getRememberTrackSelections();
|
||||
_clickVideoTogglesPlayback = _settingsService.getClickVideoTogglesPlayback();
|
||||
_autoSkipIntro = _settingsService.getAutoSkipIntro();
|
||||
_autoSkipCredits = _settingsService.getAutoSkipCredits();
|
||||
_autoSkipDelay = _settingsService.getAutoSkipDelay();
|
||||
_introPattern = _settingsService.getIntroPattern();
|
||||
_creditsPattern = _settingsService.getCreditsPattern();
|
||||
_maxVolume = _settingsService.getMaxVolume();
|
||||
_enableDiscordRPC = _settingsService.getEnableDiscordRPC();
|
||||
_autoPip = _settingsService.getAutoPip();
|
||||
_matchContentFrameRate = _settingsService.getMatchContentFrameRate();
|
||||
_tunneledPlayback = _settingsService.getTunneledPlayback();
|
||||
_useExoPlayer = _settingsService.getUseExoPlayer();
|
||||
_useExternalPlayer = _settingsService.getUseExternalPlayer();
|
||||
_selectedExternalPlayerName = _settingsService.getSelectedExternalPlayer().name;
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_isLoading) {
|
||||
return FocusedScrollScaffold(
|
||||
title: Text(t.settings.videoPlayback),
|
||||
slivers: [const SliverFillRemaining(child: Center(child: CircularProgressIndicator()))],
|
||||
);
|
||||
}
|
||||
|
||||
final isMobile = PlatformDetector.isMobile(context);
|
||||
|
||||
return FocusedScrollScaffold(
|
||||
title: Text(t.settings.videoPlayback),
|
||||
slivers: [
|
||||
SliverList(
|
||||
delegate: SliverChildListDelegate([
|
||||
// --- Player ---
|
||||
SettingsSectionHeader(t.settings.player),
|
||||
if (Platform.isAndroid) _buildPlayerBackendSelector(),
|
||||
_buildExternalPlayerTile(),
|
||||
_buildHardwareDecoding(),
|
||||
if ((Platform.isAndroid && !PlatformDetector.isTV()) || Platform.isIOS || Platform.isMacOS) _buildAutoPip(),
|
||||
if (Platform.isAndroid) _buildMatchContentFrameRate(),
|
||||
if (Platform.isAndroid && _useExoPlayer) _buildTunneledPlayback(),
|
||||
_buildBufferSizeSelector(),
|
||||
|
||||
// --- Subtitles & Config ---
|
||||
SettingsSectionHeader(t.settings.subtitlesAndConfig),
|
||||
_buildSubtitleStylingTile(),
|
||||
if (!Platform.isAndroid || !_useExoPlayer) _buildMpvConfigTile(),
|
||||
|
||||
// --- Seek & Timing ---
|
||||
SettingsSectionHeader(t.settings.seekAndTiming),
|
||||
_buildSmallSkipDuration(),
|
||||
_buildLargeSkipDuration(),
|
||||
_buildRewindOnResume(),
|
||||
_buildDefaultSleepTimer(),
|
||||
_buildMaxVolume(),
|
||||
|
||||
// --- Behavior ---
|
||||
SettingsSectionHeader(t.settings.behavior),
|
||||
if (DiscordRPCService.isAvailable) _buildDiscordRPC(),
|
||||
_buildRememberTrackSelections(),
|
||||
if (!isMobile) _buildClickVideoTogglesPlayback(),
|
||||
|
||||
// --- Auto-Skip ---
|
||||
SettingsSectionHeader(t.settings.autoSkip),
|
||||
_buildAutoSkipIntro(),
|
||||
_buildAutoSkipCredits(),
|
||||
_buildAutoSkipDelay(),
|
||||
_buildIntroPattern(),
|
||||
_buildCreditsPattern(),
|
||||
const SizedBox(height: 24),
|
||||
]),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// --- Player section ---
|
||||
|
||||
Widget _buildPlayerBackendSelector() {
|
||||
return SegmentedSetting<bool>(
|
||||
icon: Symbols.play_circle_rounded,
|
||||
title: t.settings.playerBackend,
|
||||
segments: [
|
||||
ButtonSegment(value: true, label: Text(t.settings.exoPlayer)),
|
||||
ButtonSegment(value: false, label: Text(t.settings.mpv)),
|
||||
],
|
||||
selected: _useExoPlayer,
|
||||
onChanged: (value) async {
|
||||
setState(() => _useExoPlayer = value);
|
||||
await _settingsService.setUseExoPlayer(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildExternalPlayerTile() {
|
||||
return ListTile(
|
||||
leading: const AppIcon(Symbols.open_in_new_rounded, fill: 1),
|
||||
title: Text(t.externalPlayer.title),
|
||||
subtitle: Text(_useExternalPlayer ? _selectedExternalPlayerName : t.externalPlayer.off),
|
||||
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
|
||||
onTap: () async {
|
||||
await Navigator.push(context, MaterialPageRoute(builder: (context) => const ExternalPlayerScreen()));
|
||||
final s = await settings.SettingsService.getInstance();
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_useExternalPlayer = s.getUseExternalPlayer();
|
||||
_selectedExternalPlayerName = s.getSelectedExternalPlayer().name;
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHardwareDecoding() {
|
||||
return SwitchListTile(
|
||||
secondary: const AppIcon(Symbols.hardware_rounded, fill: 1),
|
||||
title: Text(t.settings.hardwareDecoding),
|
||||
subtitle: Text(t.settings.hardwareDecodingDescription),
|
||||
value: _enableHardwareDecoding,
|
||||
onChanged: (value) async {
|
||||
setState(() => _enableHardwareDecoding = value);
|
||||
await _settingsService.setEnableHardwareDecoding(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAutoPip() {
|
||||
return SwitchListTile(
|
||||
secondary: const AppIcon(Symbols.picture_in_picture_alt_rounded, fill: 1),
|
||||
title: Text(t.settings.autoPip),
|
||||
subtitle: Text(t.settings.autoPipDescription),
|
||||
value: _autoPip,
|
||||
onChanged: (value) async {
|
||||
setState(() => _autoPip = value);
|
||||
await _settingsService.setAutoPip(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMatchContentFrameRate() {
|
||||
return SwitchListTile(
|
||||
secondary: const AppIcon(Symbols.display_settings_rounded, fill: 1),
|
||||
title: Text(t.settings.matchContentFrameRate),
|
||||
subtitle: Text(t.settings.matchContentFrameRateDescription),
|
||||
value: _matchContentFrameRate,
|
||||
onChanged: (value) async {
|
||||
setState(() => _matchContentFrameRate = value);
|
||||
await _settingsService.setMatchContentFrameRate(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTunneledPlayback() {
|
||||
return SwitchListTile(
|
||||
secondary: const AppIcon(Symbols.tv_options_input_settings_rounded, fill: 1),
|
||||
title: Text(t.settings.tunneledPlayback),
|
||||
subtitle: Text(t.settings.tunneledPlaybackDescription),
|
||||
value: _tunneledPlayback,
|
||||
onChanged: (value) async {
|
||||
setState(() => _tunneledPlayback = value);
|
||||
await _settingsService.setTunneledPlayback(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBufferSizeSelector() {
|
||||
return ListTile(
|
||||
leading: const AppIcon(Symbols.memory_rounded, fill: 1),
|
||||
title: Text(t.settings.bufferSize),
|
||||
subtitle: Text(
|
||||
_bufferSize == 0 ? t.settings.bufferSizeAuto : t.settings.bufferSizeMB(size: _bufferSize.toString()),
|
||||
),
|
||||
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
|
||||
onTap: () async {
|
||||
final bufferOptions = [0, 64, 128, 256, 512, 1024];
|
||||
final value = await showSelectionDialog<int>(
|
||||
context: context,
|
||||
title: t.settings.bufferSize,
|
||||
options: bufferOptions
|
||||
.map((size) => DialogOption(value: size, title: size == 0 ? t.settings.bufferSizeAuto : '${size}MB'))
|
||||
.toList(),
|
||||
currentValue: _bufferSize,
|
||||
);
|
||||
if (value != null) {
|
||||
setState(() {
|
||||
_bufferSize = value;
|
||||
_settingsService.setBufferSize(value);
|
||||
});
|
||||
if (Platform.isAndroid && value > 0) {
|
||||
final heapMB = await PlayerAndroid.getHeapSize();
|
||||
if (heapMB > 0 && value > heapMB ~/ 4 && mounted) {
|
||||
showAppSnackBar(
|
||||
context,
|
||||
t.settings.bufferSizeWarning(heap: heapMB.toString(), size: value.toString()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// --- Subtitles & Config section ---
|
||||
|
||||
Widget _buildSubtitleStylingTile() {
|
||||
return ListTile(
|
||||
leading: const AppIcon(Symbols.subtitles_rounded, fill: 1),
|
||||
title: Text(t.settings.subtitleStyling),
|
||||
subtitle: Text(t.settings.subtitleStylingDescription),
|
||||
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
|
||||
onTap: () {
|
||||
Navigator.push(context, MaterialPageRoute(builder: (context) => const SubtitleStylingScreen()));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMpvConfigTile() {
|
||||
return ListTile(
|
||||
leading: const AppIcon(Symbols.tune_rounded, fill: 1),
|
||||
title: Text(t.mpvConfig.title),
|
||||
subtitle: Text(t.mpvConfig.description),
|
||||
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
|
||||
onTap: () {
|
||||
Navigator.push(context, MaterialPageRoute(builder: (context) => const MpvConfigScreen()));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// --- Seek & Timing section ---
|
||||
|
||||
Widget _buildSmallSkipDuration() {
|
||||
return ListTile(
|
||||
leading: const AppIcon(Symbols.replay_10_rounded, fill: 1),
|
||||
title: Text(t.settings.smallSkipDuration),
|
||||
subtitle: Text(t.settings.secondsUnit(seconds: _seekTimeSmall.toString())),
|
||||
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
|
||||
onTap: () => showNumericInputDialog(
|
||||
context: context,
|
||||
title: t.settings.smallSkipDuration,
|
||||
labelText: t.settings.secondsLabel,
|
||||
suffixText: t.settings.secondsShort,
|
||||
min: 1,
|
||||
max: 120,
|
||||
currentValue: _seekTimeSmall,
|
||||
onSave: (value) async {
|
||||
setState(() {
|
||||
_seekTimeSmall = value;
|
||||
_settingsService.setSeekTimeSmall(value);
|
||||
});
|
||||
await _keyboardService?.refreshFromStorage();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLargeSkipDuration() {
|
||||
return ListTile(
|
||||
leading: const AppIcon(Symbols.replay_30_rounded, fill: 1),
|
||||
title: Text(t.settings.largeSkipDuration),
|
||||
subtitle: Text(t.settings.secondsUnit(seconds: _seekTimeLarge.toString())),
|
||||
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
|
||||
onTap: () => showNumericInputDialog(
|
||||
context: context,
|
||||
title: t.settings.largeSkipDuration,
|
||||
labelText: t.settings.secondsLabel,
|
||||
suffixText: t.settings.secondsShort,
|
||||
min: 1,
|
||||
max: 120,
|
||||
currentValue: _seekTimeLarge,
|
||||
onSave: (value) async {
|
||||
setState(() {
|
||||
_seekTimeLarge = value;
|
||||
_settingsService.setSeekTimeLarge(value);
|
||||
});
|
||||
await _keyboardService?.refreshFromStorage();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRewindOnResume() {
|
||||
return ListTile(
|
||||
leading: const AppIcon(Symbols.replay_rounded, fill: 1),
|
||||
title: Text(t.settings.rewindOnResume),
|
||||
subtitle: Text(t.settings.secondsUnit(seconds: _rewindOnResume.toString())),
|
||||
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
|
||||
onTap: () => showNumericInputDialog(
|
||||
context: context,
|
||||
title: t.settings.rewindOnResume,
|
||||
labelText: t.settings.secondsLabel,
|
||||
suffixText: t.settings.secondsShort,
|
||||
min: 0,
|
||||
max: 10,
|
||||
currentValue: _rewindOnResume,
|
||||
onSave: (value) async {
|
||||
setState(() {
|
||||
_rewindOnResume = value;
|
||||
_settingsService.setRewindOnResume(value);
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDefaultSleepTimer() {
|
||||
return ListTile(
|
||||
leading: const AppIcon(Symbols.bedtime_rounded, fill: 1),
|
||||
title: Text(t.settings.defaultSleepTimer),
|
||||
subtitle: Text(t.settings.minutesUnit(minutes: _sleepTimerDuration.toString())),
|
||||
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
|
||||
onTap: () => showNumericInputDialog(
|
||||
context: context,
|
||||
title: t.settings.defaultSleepTimer,
|
||||
labelText: t.settings.minutesLabel,
|
||||
suffixText: t.settings.minutesShort,
|
||||
min: 5,
|
||||
max: 240,
|
||||
currentValue: _sleepTimerDuration,
|
||||
onSave: (value) async {
|
||||
setState(() => _sleepTimerDuration = value);
|
||||
await _settingsService.setSleepTimerDuration(value);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMaxVolume() {
|
||||
return ListTile(
|
||||
leading: const AppIcon(Symbols.volume_up_rounded, fill: 1),
|
||||
title: Text(t.settings.maxVolume),
|
||||
subtitle: Text(t.settings.maxVolumePercent(percent: _maxVolume.toString())),
|
||||
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
|
||||
onTap: () => showNumericInputDialog(
|
||||
context: context,
|
||||
title: t.settings.maxVolume,
|
||||
labelText: t.settings.maxVolumeDescription,
|
||||
suffixText: '%',
|
||||
min: 100,
|
||||
max: 300,
|
||||
currentValue: _maxVolume,
|
||||
onSave: (value) async {
|
||||
setState(() => _maxVolume = value);
|
||||
await _settingsService.setMaxVolume(value);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// --- Behavior section ---
|
||||
|
||||
Widget _buildDiscordRPC() {
|
||||
return SwitchListTile(
|
||||
secondary: const AppIcon(Symbols.chat_rounded, fill: 1),
|
||||
title: Text(t.settings.discordRichPresence),
|
||||
subtitle: Text(t.settings.discordRichPresenceDescription),
|
||||
value: _enableDiscordRPC,
|
||||
onChanged: (value) async {
|
||||
setState(() => _enableDiscordRPC = value);
|
||||
await _settingsService.setEnableDiscordRPC(value);
|
||||
await DiscordRPCService.instance.setEnabled(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRememberTrackSelections() {
|
||||
return SwitchListTile(
|
||||
secondary: const AppIcon(Symbols.bookmark_rounded, fill: 1),
|
||||
title: Text(t.settings.rememberTrackSelections),
|
||||
subtitle: Text(t.settings.rememberTrackSelectionsDescription),
|
||||
value: _rememberTrackSelections,
|
||||
onChanged: (value) async {
|
||||
setState(() => _rememberTrackSelections = value);
|
||||
await _settingsService.setRememberTrackSelections(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildClickVideoTogglesPlayback() {
|
||||
return SwitchListTile(
|
||||
secondary: const AppIcon(Symbols.play_pause_rounded, fill: 1),
|
||||
title: Text(t.settings.clickVideoTogglesPlayback),
|
||||
subtitle: Text(t.settings.clickVideoTogglesPlaybackDescription),
|
||||
value: _clickVideoTogglesPlayback,
|
||||
onChanged: (value) async {
|
||||
setState(() => _clickVideoTogglesPlayback = value);
|
||||
await _settingsService.setClickVideoTogglesPlayback(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// --- Auto-Skip section ---
|
||||
|
||||
Widget _buildAutoSkipIntro() {
|
||||
return SwitchListTile(
|
||||
secondary: const AppIcon(Symbols.fast_forward_rounded, fill: 1),
|
||||
title: Text(t.settings.autoSkipIntro),
|
||||
subtitle: Text(t.settings.autoSkipIntroDescription),
|
||||
value: _autoSkipIntro,
|
||||
onChanged: (value) async {
|
||||
setState(() => _autoSkipIntro = value);
|
||||
await _settingsService.setAutoSkipIntro(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAutoSkipCredits() {
|
||||
return SwitchListTile(
|
||||
secondary: const AppIcon(Symbols.skip_next_rounded, fill: 1),
|
||||
title: Text(t.settings.autoSkipCredits),
|
||||
subtitle: Text(t.settings.autoSkipCreditsDescription),
|
||||
value: _autoSkipCredits,
|
||||
onChanged: (value) async {
|
||||
setState(() => _autoSkipCredits = value);
|
||||
await _settingsService.setAutoSkipCredits(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAutoSkipDelay() {
|
||||
return ListTile(
|
||||
leading: const AppIcon(Symbols.timer_rounded, fill: 1),
|
||||
title: Text(t.settings.autoSkipDelay),
|
||||
subtitle: Text(t.settings.autoSkipDelayDescription(seconds: _autoSkipDelay.toString())),
|
||||
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
|
||||
onTap: () => showNumericInputDialog(
|
||||
context: context,
|
||||
title: t.settings.autoSkipDelay,
|
||||
labelText: t.settings.secondsLabel,
|
||||
suffixText: t.settings.secondsShort,
|
||||
min: 1,
|
||||
max: 30,
|
||||
currentValue: _autoSkipDelay,
|
||||
onSave: (value) async {
|
||||
setState(() => _autoSkipDelay = value);
|
||||
await _settingsService.setAutoSkipDelay(value);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIntroPattern() {
|
||||
return ListTile(
|
||||
leading: const AppIcon(Symbols.match_case_rounded, fill: 1),
|
||||
title: Text(t.settings.introPattern),
|
||||
subtitle: Text(t.settings.introPatternDescription),
|
||||
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
|
||||
onTap: () => showTextInputDialog(
|
||||
context: context,
|
||||
title: t.settings.introPattern,
|
||||
currentValue: _introPattern,
|
||||
defaultValue: settings.SettingsService.defaultIntroPattern,
|
||||
onSave: (value) async {
|
||||
setState(() => _introPattern = value);
|
||||
await _settingsService.setIntroPattern(value);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCreditsPattern() {
|
||||
return ListTile(
|
||||
leading: const AppIcon(Symbols.match_case_rounded, fill: 1),
|
||||
title: Text(t.settings.creditsPattern),
|
||||
subtitle: Text(t.settings.creditsPatternDescription),
|
||||
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
|
||||
onTap: () => showTextInputDialog(
|
||||
context: context,
|
||||
title: t.settings.creditsPattern,
|
||||
currentValue: _creditsPattern,
|
||||
defaultValue: settings.SettingsService.defaultCreditsPattern,
|
||||
onSave: (value) async {
|
||||
setState(() => _creditsPattern = value);
|
||||
await _settingsService.setCreditsPattern(value);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,290 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../focus/input_mode_tracker.dart';
|
||||
import '../../i18n/strings.g.dart';
|
||||
import '../../widgets/tv_number_spinner.dart';
|
||||
|
||||
/// Model for option selection dialogs.
|
||||
class DialogOption<T> {
|
||||
final T value;
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
|
||||
const DialogOption({required this.value, required this.title, this.subtitle});
|
||||
}
|
||||
|
||||
/// Shows an M3-conformant AlertDialog with RadioListTiles for multi-option selection.
|
||||
/// Used for settings with 5+ options (language, buffer size, etc.).
|
||||
Future<T?> showSelectionDialog<T>({
|
||||
required BuildContext context,
|
||||
required String title,
|
||||
required List<DialogOption<T>> options,
|
||||
required T currentValue,
|
||||
}) {
|
||||
return showDialog<T>(
|
||||
context: context,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
title: Text(title),
|
||||
contentPadding: const EdgeInsets.only(top: 12, bottom: 24),
|
||||
content: SingleChildScrollView(
|
||||
child: RadioGroup<T>(
|
||||
groupValue: currentValue,
|
||||
onChanged: (value) => Navigator.pop(dialogContext, value),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: options
|
||||
.map((option) => RadioListTile<T>(
|
||||
title: Text(option.title),
|
||||
subtitle: option.subtitle != null ? Text(option.subtitle!) : null,
|
||||
value: option.value,
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Generic numeric input dialog.
|
||||
/// On TV/keyboard mode, uses a spinner widget with +/- buttons for D-pad navigation.
|
||||
/// On other platforms, uses a TextField with focus management.
|
||||
void showNumericInputDialog({
|
||||
required BuildContext context,
|
||||
required String title,
|
||||
required String labelText,
|
||||
required String suffixText,
|
||||
required int min,
|
||||
required int max,
|
||||
required int currentValue,
|
||||
required Future<void> Function(int value) onSave,
|
||||
}) {
|
||||
final useDpadControls = InputModeTracker.isKeyboardMode(context);
|
||||
|
||||
if (useDpadControls) {
|
||||
_showNumericInputDialogTV(
|
||||
context: context,
|
||||
title: title,
|
||||
suffixText: suffixText,
|
||||
min: min,
|
||||
max: max,
|
||||
currentValue: currentValue,
|
||||
onSave: onSave,
|
||||
);
|
||||
} else {
|
||||
_showNumericInputDialogStandard(
|
||||
context: context,
|
||||
title: title,
|
||||
labelText: labelText,
|
||||
suffixText: suffixText,
|
||||
min: min,
|
||||
max: max,
|
||||
currentValue: currentValue,
|
||||
onSave: onSave,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _showNumericInputDialogTV({
|
||||
required BuildContext context,
|
||||
required String title,
|
||||
required String suffixText,
|
||||
required int min,
|
||||
required int max,
|
||||
required int currentValue,
|
||||
required Future<void> Function(int value) onSave,
|
||||
}) {
|
||||
int spinnerValue = currentValue;
|
||||
final saveFocusNode = FocusNode();
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext dialogContext) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, setDialogState) {
|
||||
return AlertDialog(
|
||||
title: Text(title),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TvNumberSpinner(
|
||||
value: spinnerValue,
|
||||
min: min,
|
||||
max: max,
|
||||
suffix: suffixText,
|
||||
autofocus: true,
|
||||
onChanged: (value) {
|
||||
setDialogState(() {
|
||||
spinnerValue = value;
|
||||
});
|
||||
},
|
||||
onConfirm: () => saveFocusNode.requestFocus(),
|
||||
onCancel: () => Navigator.pop(dialogContext),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
t.settings.durationHint(min: min, max: max),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(dialogContext), child: Text(t.common.cancel)),
|
||||
TextButton(
|
||||
focusNode: saveFocusNode,
|
||||
onPressed: () async {
|
||||
await onSave(spinnerValue);
|
||||
if (dialogContext.mounted) {
|
||||
Navigator.pop(dialogContext);
|
||||
}
|
||||
},
|
||||
child: Text(t.common.save),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
).then((_) => saveFocusNode.dispose());
|
||||
}
|
||||
|
||||
void _showNumericInputDialogStandard({
|
||||
required BuildContext context,
|
||||
required String title,
|
||||
required String labelText,
|
||||
required String suffixText,
|
||||
required int min,
|
||||
required int max,
|
||||
required int currentValue,
|
||||
required Future<void> Function(int value) onSave,
|
||||
}) {
|
||||
final controller = TextEditingController(text: currentValue.toString());
|
||||
String? errorText;
|
||||
final saveFocusNode = FocusNode();
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext dialogContext) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, setDialogState) {
|
||||
return AlertDialog(
|
||||
title: Text(title),
|
||||
content: TextField(
|
||||
controller: controller,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: InputDecoration(
|
||||
labelText: labelText,
|
||||
hintText: t.settings.durationHint(min: min, max: max),
|
||||
errorText: errorText,
|
||||
suffixText: suffixText,
|
||||
),
|
||||
autofocus: true,
|
||||
textInputAction: TextInputAction.done,
|
||||
onEditingComplete: () {
|
||||
saveFocusNode.requestFocus();
|
||||
},
|
||||
onChanged: (value) {
|
||||
final parsed = int.tryParse(value);
|
||||
setDialogState(() {
|
||||
if (parsed == null) {
|
||||
errorText = t.settings.validationErrorEnterNumber;
|
||||
} else if (parsed < min || parsed > max) {
|
||||
errorText = t.settings.validationErrorDuration(min: min, max: max, unit: labelText.toLowerCase());
|
||||
} else {
|
||||
errorText = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(dialogContext), child: Text(t.common.cancel)),
|
||||
TextButton(
|
||||
focusNode: saveFocusNode,
|
||||
onPressed: () async {
|
||||
final parsed = int.tryParse(controller.text);
|
||||
if (parsed != null && parsed >= min && parsed <= max) {
|
||||
await onSave(parsed);
|
||||
if (dialogContext.mounted) {
|
||||
Navigator.pop(dialogContext);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Text(t.common.save),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
).then((_) {
|
||||
saveFocusNode.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
/// Shows a text input dialog with regex validation and reset-to-default support.
|
||||
void showTextInputDialog({
|
||||
required BuildContext context,
|
||||
required String title,
|
||||
required String currentValue,
|
||||
required String defaultValue,
|
||||
required Future<void> Function(String value) onSave,
|
||||
}) {
|
||||
final controller = TextEditingController(text: currentValue);
|
||||
String? errorText;
|
||||
final saveFocusNode = FocusNode();
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext dialogContext) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, setDialogState) {
|
||||
return AlertDialog(
|
||||
title: Text(title),
|
||||
content: TextField(
|
||||
controller: controller,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Regex',
|
||||
errorText: errorText,
|
||||
),
|
||||
autofocus: true,
|
||||
textInputAction: TextInputAction.done,
|
||||
onEditingComplete: () => saveFocusNode.requestFocus(),
|
||||
onChanged: (value) {
|
||||
setDialogState(() {
|
||||
try {
|
||||
RegExp(value, caseSensitive: false);
|
||||
errorText = null;
|
||||
} catch (_) {
|
||||
errorText = t.settings.invalidRegex;
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
controller.text = defaultValue;
|
||||
setDialogState(() => errorText = null);
|
||||
},
|
||||
child: Text(t.settings.resetToDefault),
|
||||
),
|
||||
TextButton(onPressed: () => Navigator.pop(dialogContext), child: Text(t.common.cancel)),
|
||||
TextButton(
|
||||
focusNode: saveFocusNode,
|
||||
onPressed: () async {
|
||||
if (errorText != null) return;
|
||||
await onSave(controller.text);
|
||||
if (dialogContext.mounted) Navigator.pop(dialogContext);
|
||||
},
|
||||
child: Text(t.common.save),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
).then((_) => saveFocusNode.dispose());
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../theme/mono_tokens.dart';
|
||||
import 'app_icon.dart';
|
||||
|
||||
class SettingsSectionHeader extends StatelessWidget {
|
||||
final String title;
|
||||
|
||||
const SettingsSectionHeader(this.title, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 24, 16, 8),
|
||||
child: Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.labelLarge?.copyWith(
|
||||
color: tokens(context).textMuted,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A setting with a label + icon row and a full-width SegmentedButton below.
|
||||
/// Used for settings with 2-4 short options.
|
||||
class SegmentedSetting<T> extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final List<ButtonSegment<T>> segments;
|
||||
final T selected;
|
||||
final ValueChanged<T> onChanged;
|
||||
|
||||
const SegmentedSetting({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.segments,
|
||||
required this.selected,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
AppIcon(icon, fill: 1),
|
||||
const SizedBox(width: 16),
|
||||
Text(title, style: Theme.of(context).textTheme.bodyLarge),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: SegmentedButton<T>(
|
||||
segments: segments,
|
||||
selected: {selected},
|
||||
onSelectionChanged: (Set<T> newSelection) {
|
||||
onChanged(newSelection.first);
|
||||
},
|
||||
showSelectedIcon: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user