feat: add back button exit confirmation on Android TV

close #467
This commit is contained in:
edde746
2026-02-16 01:22:48 +01:00
parent 1770109ff9
commit 53c94b14cc
23 changed files with 313 additions and 39 deletions
+8 -2
View File
@@ -47,7 +47,11 @@
"mute": "Stumm",
"ok": "OK",
"loading": "Laden...",
"reconnect": "Erneut verbinden"
"reconnect": "Erneut verbinden",
"exitConfirmTitle": "App beenden?",
"exitConfirmMessage": "Möchtest du die App wirklich beenden?",
"dontAskAgain": "Nicht erneut fragen",
"exit": "Beenden"
},
"screens": {
"licenses": "Lizenzen",
@@ -186,7 +190,9 @@
"matchContentFrameRate": "Inhalts-Bildrate anpassen",
"matchContentFrameRateDescription": "Bildwiederholfrequenz des Displays an den Videoinhalt anpassen, reduziert Ruckeln und spart Akku",
"requireProfileSelectionOnOpen": "Profil beim Öffnen abfragen",
"requireProfileSelectionOnOpenDescription": "Profilauswahl bei jedem Öffnen der App anzeigen"
"requireProfileSelectionOnOpenDescription": "Profilauswahl bei jedem Öffnen der App anzeigen",
"confirmExitOnBack": "Vor dem Beenden bestätigen",
"confirmExitOnBackDescription": "Bestätigungsdialog anzeigen, wenn Zurück zum Beenden der App gedrückt wird"
},
"search": {
"hint": "Filme, Serien, Musik suchen...",
+8 -2
View File
@@ -47,7 +47,11 @@
"mute": "Mute",
"ok": "OK",
"loading": "Loading...",
"reconnect": "Reconnect"
"reconnect": "Reconnect",
"exitConfirmTitle": "Exit app?",
"exitConfirmMessage": "Are you sure you want to exit?",
"dontAskAgain": "Don't ask again",
"exit": "Exit"
},
"screens": {
"licenses": "Licenses",
@@ -186,7 +190,9 @@
"matchContentFrameRate": "Match Content Frame Rate",
"matchContentFrameRateDescription": "Adjust display refresh rate to match video content, reducing judder and saving battery",
"requireProfileSelectionOnOpen": "Ask for profile on app open",
"requireProfileSelectionOnOpenDescription": "Show profile selection every time the app is opened"
"requireProfileSelectionOnOpenDescription": "Show profile selection every time the app is opened",
"confirmExitOnBack": "Confirm before exiting",
"confirmExitOnBackDescription": "Show a confirmation dialog when pressing back to exit the app"
},
"search": {
"hint": "Search movies, shows, music...",
+8 -2
View File
@@ -47,7 +47,11 @@
"mute": "Silencio",
"ok": "OK",
"loading": "Cargando...",
"reconnect": "Reconectar"
"reconnect": "Reconectar",
"exitConfirmTitle": "¿Salir de la app?",
"exitConfirmMessage": "¿Estás seguro de que quieres salir?",
"dontAskAgain": "No volver a preguntar",
"exit": "Salir"
},
"screens": {
"licenses": "Licencias",
@@ -186,7 +190,9 @@
"matchContentFrameRate": "Ajustar frecuencia de actualización",
"matchContentFrameRateDescription": "Ajustar la frecuencia de actualización de la pantalla para que coincida con el video, reduciendo tirones y ahorrando batería",
"requireProfileSelectionOnOpen": "Pedir perfil al abrir la app",
"requireProfileSelectionOnOpenDescription": "Mostrar selección de perfil cada vez que se abre la aplicación"
"requireProfileSelectionOnOpenDescription": "Mostrar selección de perfil cada vez que se abre la aplicación",
"confirmExitOnBack": "Confirmar antes de salir",
"confirmExitOnBackDescription": "Mostrar un diálogo de confirmación al presionar atrás para salir de la app"
},
"search": {
"hint": "Buscar películas, series, música...",
+8 -2
View File
@@ -47,7 +47,11 @@
"mute": "Muet",
"ok": "OK",
"loading": "Chargement...",
"reconnect": "Reconnecter"
"reconnect": "Reconnecter",
"exitConfirmTitle": "Quitter l'application ?",
"exitConfirmMessage": "Êtes-vous sûr de vouloir quitter ?",
"dontAskAgain": "Ne plus demander",
"exit": "Quitter"
},
"screens": {
"licenses": "Licenses",
@@ -186,7 +190,9 @@
"matchContentFrameRate": "Fréquence d'images du contenu correspondant",
"matchContentFrameRateDescription": "Ajustez la fréquence de rafraîchissement de l'écran en fonction du contenu vidéo, ce qui réduit les saccades et économise la batterie",
"requireProfileSelectionOnOpen": "Demander le profil à l'ouverture",
"requireProfileSelectionOnOpenDescription": "Afficher la sélection de profil à chaque ouverture de l'application"
"requireProfileSelectionOnOpenDescription": "Afficher la sélection de profil à chaque ouverture de l'application",
"confirmExitOnBack": "Confirmer avant de quitter",
"confirmExitOnBackDescription": "Afficher une boîte de dialogue de confirmation en appuyant sur retour pour quitter"
},
"search": {
"hint": "Rechercher des films, des séries, de la musique...",
+8 -2
View File
@@ -47,7 +47,11 @@
"mute": "Muto",
"ok": "OK",
"loading": "Caricamento...",
"reconnect": "Riconnetti"
"reconnect": "Riconnetti",
"exitConfirmTitle": "Uscire dall'app?",
"exitConfirmMessage": "Sei sicuro di voler uscire?",
"dontAskAgain": "Non chiedere più",
"exit": "Esci"
},
"screens": {
"licenses": "Licenze",
@@ -186,7 +190,9 @@
"matchContentFrameRate": "Adatta frequenza fotogrammi",
"matchContentFrameRateDescription": "Regola la frequenza di aggiornamento del display in base al contenuto video, riducendo i tremolii e risparmiando batteria",
"requireProfileSelectionOnOpen": "Chiedi profilo all'apertura",
"requireProfileSelectionOnOpenDescription": "Mostra la selezione del profilo ogni volta che l'app viene aperta"
"requireProfileSelectionOnOpenDescription": "Mostra la selezione del profilo ogni volta che l'app viene aperta",
"confirmExitOnBack": "Conferma prima di uscire",
"confirmExitOnBackDescription": "Mostra una finestra di conferma quando si preme indietro per uscire dall'app"
},
"search": {
"hint": "Cerca film. spettacoli, musica...",
+8 -2
View File
@@ -47,7 +47,11 @@
"mute": "음소거",
"ok": "확인",
"loading": "로딩 중...",
"reconnect": "다시 연결"
"reconnect": "다시 연결",
"exitConfirmTitle": "앱을 종료하시겠습니까?",
"exitConfirmMessage": "정말 종료하시겠습니까?",
"dontAskAgain": "다시 묻지 않기",
"exit": "종료"
},
"screens": {
"licenses": "라이선스",
@@ -186,7 +190,9 @@
"matchContentFrameRate": "콘텐츠 프레임 레이트 맞춤",
"matchContentFrameRateDescription": "비디오 콘텐츠에 맞게 디스플레이 주사율을 조정하여 떨림을 줄이고 배터리를 절약합니다",
"requireProfileSelectionOnOpen": "앱 실행 시 프로필 선택",
"requireProfileSelectionOnOpenDescription": "앱을 열 때마다 프로필 선택 화면을 표시합니다"
"requireProfileSelectionOnOpenDescription": "앱을 열 때마다 프로필 선택 화면을 표시합니다",
"confirmExitOnBack": "종료 전 확인",
"confirmExitOnBackDescription": "뒤로 버튼을 눌러 앱을 종료할 때 확인 대화상자를 표시합니다"
},
"search": {
"hint": "영화, 시리즈, 음악 등을 검색하세요...",
+8 -2
View File
@@ -47,7 +47,11 @@
"mute": "Dempen",
"ok": "OK",
"loading": "Laden...",
"reconnect": "Opnieuw verbinden"
"reconnect": "Opnieuw verbinden",
"exitConfirmTitle": "App afsluiten?",
"exitConfirmMessage": "Weet je zeker dat je wilt afsluiten?",
"dontAskAgain": "Niet meer vragen",
"exit": "Afsluiten"
},
"screens": {
"licenses": "Licenties",
@@ -186,7 +190,9 @@
"matchContentFrameRate": "Inhoudsframesnelheid afstemmen",
"matchContentFrameRateDescription": "Pas de schermverversingssnelheid aan op de video-inhoud, vermindert haperingen en bespaart batterij",
"requireProfileSelectionOnOpen": "Vraag om profiel bij openen",
"requireProfileSelectionOnOpenDescription": "Toon profielselectie telkens wanneer de app wordt geopend"
"requireProfileSelectionOnOpenDescription": "Toon profielselectie telkens wanneer de app wordt geopend",
"confirmExitOnBack": "Bevestigen voor afsluiten",
"confirmExitOnBackDescription": "Toon een bevestigingsvenster bij het drukken op terug om de app af te sluiten"
},
"search": {
"hint": "Zoek films, series, muziek...",
+2 -2
View File
@@ -4,9 +4,9 @@
/// To regenerate, run: `dart run slang`
///
/// Locales: 9
/// Strings: 6588 (732 per locale)
/// Strings: 6642 (738 per locale)
///
/// Built on 2026-02-15 at 04:13 UTC
/// Built on 2026-02-16 at 00:20 UTC
// coverage:ignore-file
// ignore_for_file: type=lint, unused_import
+14 -2
View File
@@ -145,6 +145,10 @@ class _TranslationsCommonDe implements TranslationsCommonEn {
@override String get ok => 'OK';
@override String get loading => 'Laden...';
@override String get reconnect => 'Erneut verbinden';
@override String get exitConfirmTitle => 'App beenden?';
@override String get exitConfirmMessage => 'Möchtest du die App wirklich beenden?';
@override String get dontAskAgain => 'Nicht erneut fragen';
@override String get exit => 'Beenden';
}
// Path: screens
@@ -305,6 +309,8 @@ class _TranslationsSettingsDe implements TranslationsSettingsEn {
@override String get matchContentFrameRateDescription => 'Bildwiederholfrequenz des Displays an den Videoinhalt anpassen, reduziert Ruckeln und spart Akku';
@override String get requireProfileSelectionOnOpen => 'Profil beim Öffnen abfragen';
@override String get requireProfileSelectionOnOpenDescription => 'Profilauswahl bei jedem Öffnen der App anzeigen';
@override String get confirmExitOnBack => 'Vor dem Beenden bestätigen';
@override String get confirmExitOnBackDescription => 'Bestätigungsdialog anzeigen, wenn Zurück zum Beenden der App gedrückt wird';
}
// Path: search
@@ -1263,6 +1269,10 @@ extension on TranslationsDe {
'common.ok' => 'OK',
'common.loading' => 'Laden...',
'common.reconnect' => 'Erneut verbinden',
'common.exitConfirmTitle' => 'App beenden?',
'common.exitConfirmMessage' => 'Möchtest du die App wirklich beenden?',
'common.dontAskAgain' => 'Nicht erneut fragen',
'common.exit' => 'Beenden',
'screens.licenses' => 'Lizenzen',
'screens.switchProfile' => 'Profil wechseln',
'screens.subtitleStyling' => 'Untertitel-Stil',
@@ -1396,6 +1406,8 @@ extension on TranslationsDe {
'settings.matchContentFrameRateDescription' => 'Bildwiederholfrequenz des Displays an den Videoinhalt anpassen, reduziert Ruckeln und spart Akku',
'settings.requireProfileSelectionOnOpen' => 'Profil beim Öffnen abfragen',
'settings.requireProfileSelectionOnOpenDescription' => 'Profilauswahl bei jedem Öffnen der App anzeigen',
'settings.confirmExitOnBack' => 'Vor dem Beenden bestätigen',
'settings.confirmExitOnBackDescription' => 'Bestätigungsdialog anzeigen, wenn Zurück zum Beenden der App gedrückt wird',
'search.hint' => 'Filme, Serien, Musik suchen...',
'search.tryDifferentTerm' => 'Anderen Suchbegriff versuchen',
'search.searchYourMedia' => 'In den eigenen Medien suchen',
@@ -1725,14 +1737,14 @@ extension on TranslationsDe {
'liveTv.evening' => 'Abend',
'liveTv.lateNight' => 'Spätnacht',
'liveTv.whatsOn' => 'Jetzt im TV',
_ => null,
} ?? switch (path) {
'liveTv.watchChannel' => 'Kanal ansehen',
'downloads.title' => 'Downloads',
'downloads.manage' => 'Verwalten',
'downloads.tvShows' => 'Serien',
'downloads.movies' => 'Filme',
'downloads.noDownloads' => 'Noch keine Downloads',
_ => null,
} ?? switch (path) {
'downloads.noDownloadsDescription' => 'Heruntergeladene Inhalte werden hier für die Offline-Wiedergabe angezeigt',
'downloads.downloadNow' => 'Herunterladen',
'downloads.deleteDownload' => 'Download löschen',
+26 -2
View File
@@ -236,6 +236,18 @@ class TranslationsCommonEn {
/// en: 'Reconnect'
String get reconnect => 'Reconnect';
/// en: 'Exit app?'
String get exitConfirmTitle => 'Exit app?';
/// en: 'Are you sure you want to exit?'
String get exitConfirmMessage => 'Are you sure you want to exit?';
/// en: 'Don't ask again'
String get dontAskAgain => 'Don\'t ask again';
/// en: 'Exit'
String get exit => 'Exit';
}
// Path: screens
@@ -662,6 +674,12 @@ class TranslationsSettingsEn {
/// en: 'Show profile selection every time the app is opened'
String get requireProfileSelectionOnOpenDescription => 'Show profile selection every time the app is opened';
/// en: 'Confirm before exiting'
String get confirmExitOnBack => 'Confirm before exiting';
/// en: 'Show a confirmation dialog when pressing back to exit the app'
String get confirmExitOnBackDescription => 'Show a confirmation dialog when pressing back to exit the app';
}
// Path: search
@@ -2734,6 +2752,10 @@ extension on Translations {
'common.ok' => 'OK',
'common.loading' => 'Loading...',
'common.reconnect' => 'Reconnect',
'common.exitConfirmTitle' => 'Exit app?',
'common.exitConfirmMessage' => 'Are you sure you want to exit?',
'common.dontAskAgain' => 'Don\'t ask again',
'common.exit' => 'Exit',
'screens.licenses' => 'Licenses',
'screens.switchProfile' => 'Switch Profile',
'screens.subtitleStyling' => 'Subtitle Styling',
@@ -2867,6 +2889,8 @@ extension on Translations {
'settings.matchContentFrameRateDescription' => 'Adjust display refresh rate to match video content, reducing judder and saving battery',
'settings.requireProfileSelectionOnOpen' => 'Ask for profile on app open',
'settings.requireProfileSelectionOnOpenDescription' => 'Show profile selection every time the app is opened',
'settings.confirmExitOnBack' => 'Confirm before exiting',
'settings.confirmExitOnBackDescription' => 'Show a confirmation dialog when pressing back to exit the app',
'search.hint' => 'Search movies, shows, music...',
'search.tryDifferentTerm' => 'Try a different search term',
'search.searchYourMedia' => 'Search your media',
@@ -3196,14 +3220,14 @@ extension on Translations {
'liveTv.evening' => 'Evening',
'liveTv.lateNight' => 'Late Night',
'liveTv.whatsOn' => 'What\'s On',
_ => null,
} ?? switch (path) {
'liveTv.watchChannel' => 'Watch Channel',
'collections.title' => 'Collections',
'collections.collection' => 'Collection',
'collections.empty' => 'Collection is empty',
'collections.unknownLibrarySection' => 'Cannot delete: Unknown library section',
'collections.deleteCollection' => 'Delete Collection',
_ => null,
} ?? switch (path) {
'collections.deleteConfirm' => ({required Object title}) => 'Are you sure you want to delete "${title}"? This action cannot be undone.',
'collections.deleted' => 'Collection deleted',
'collections.deleteFailed' => 'Failed to delete collection',
+14 -2
View File
@@ -145,6 +145,10 @@ class _TranslationsCommonEs implements TranslationsCommonEn {
@override String get ok => 'OK';
@override String get loading => 'Cargando...';
@override String get reconnect => 'Reconectar';
@override String get exitConfirmTitle => '¿Salir de la app?';
@override String get exitConfirmMessage => '¿Estás seguro de que quieres salir?';
@override String get dontAskAgain => 'No volver a preguntar';
@override String get exit => 'Salir';
}
// Path: screens
@@ -305,6 +309,8 @@ class _TranslationsSettingsEs implements TranslationsSettingsEn {
@override String get matchContentFrameRateDescription => 'Ajustar la frecuencia de actualización de la pantalla para que coincida con el video, reduciendo tirones y ahorrando batería';
@override String get requireProfileSelectionOnOpen => 'Pedir perfil al abrir la app';
@override String get requireProfileSelectionOnOpenDescription => 'Mostrar selección de perfil cada vez que se abre la aplicación';
@override String get confirmExitOnBack => 'Confirmar antes de salir';
@override String get confirmExitOnBackDescription => 'Mostrar un diálogo de confirmación al presionar atrás para salir de la app';
}
// Path: search
@@ -1263,6 +1269,10 @@ extension on TranslationsEs {
'common.ok' => 'OK',
'common.loading' => 'Cargando...',
'common.reconnect' => 'Reconectar',
'common.exitConfirmTitle' => '¿Salir de la app?',
'common.exitConfirmMessage' => '¿Estás seguro de que quieres salir?',
'common.dontAskAgain' => 'No volver a preguntar',
'common.exit' => 'Salir',
'screens.licenses' => 'Licencias',
'screens.switchProfile' => 'Cambiar Perfil',
'screens.subtitleStyling' => 'Estilo de Subtítulos',
@@ -1396,6 +1406,8 @@ extension on TranslationsEs {
'settings.matchContentFrameRateDescription' => 'Ajustar la frecuencia de actualización de la pantalla para que coincida con el video, reduciendo tirones y ahorrando batería',
'settings.requireProfileSelectionOnOpen' => 'Pedir perfil al abrir la app',
'settings.requireProfileSelectionOnOpenDescription' => 'Mostrar selección de perfil cada vez que se abre la aplicación',
'settings.confirmExitOnBack' => 'Confirmar antes de salir',
'settings.confirmExitOnBackDescription' => 'Mostrar un diálogo de confirmación al presionar atrás para salir de la app',
'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',
@@ -1725,14 +1737,14 @@ extension on TranslationsEs {
'liveTv.evening' => 'Noche',
'liveTv.lateNight' => 'Trasnoche',
'liveTv.whatsOn' => 'En emisión',
_ => null,
} ?? switch (path) {
'liveTv.watchChannel' => 'Ver canal',
'collections.title' => 'Colecciones',
'collections.collection' => 'Colección',
'collections.empty' => 'La colección está vacía',
'collections.unknownLibrarySection' => 'No se puede eliminar: Sección de biblioteca desconocida',
'collections.deleteCollection' => 'Eliminar Colección',
_ => null,
} ?? switch (path) {
'collections.deleteConfirm' => ({required Object title}) => '¿Estás seguro de que quieres eliminar "${title}"? Esta acción no se puede deshacer.',
'collections.deleted' => 'Colección eliminada',
'collections.deleteFailed' => 'Error al eliminar la colección',
+14 -2
View File
@@ -145,6 +145,10 @@ class _TranslationsCommonFr implements TranslationsCommonEn {
@override String get ok => 'OK';
@override String get loading => 'Chargement...';
@override String get reconnect => 'Reconnecter';
@override String get exitConfirmTitle => 'Quitter l\'application ?';
@override String get exitConfirmMessage => 'Êtes-vous sûr de vouloir quitter ?';
@override String get dontAskAgain => 'Ne plus demander';
@override String get exit => 'Quitter';
}
// Path: screens
@@ -305,6 +309,8 @@ class _TranslationsSettingsFr implements TranslationsSettingsEn {
@override String get matchContentFrameRateDescription => 'Ajustez la fréquence de rafraîchissement de l\'écran en fonction du contenu vidéo, ce qui réduit les saccades et économise la batterie';
@override String get requireProfileSelectionOnOpen => 'Demander le profil à l\'ouverture';
@override String get requireProfileSelectionOnOpenDescription => 'Afficher la sélection de profil à chaque ouverture de l\'application';
@override String get confirmExitOnBack => 'Confirmer avant de quitter';
@override String get confirmExitOnBackDescription => 'Afficher une boîte de dialogue de confirmation en appuyant sur retour pour quitter';
}
// Path: search
@@ -1263,6 +1269,10 @@ extension on TranslationsFr {
'common.ok' => 'OK',
'common.loading' => 'Chargement...',
'common.reconnect' => 'Reconnecter',
'common.exitConfirmTitle' => 'Quitter l\'application ?',
'common.exitConfirmMessage' => 'Êtes-vous sûr de vouloir quitter ?',
'common.dontAskAgain' => 'Ne plus demander',
'common.exit' => 'Quitter',
'screens.licenses' => 'Licenses',
'screens.switchProfile' => 'Changer de profil',
'screens.subtitleStyling' => 'Configuration des sous-titres',
@@ -1396,6 +1406,8 @@ extension on TranslationsFr {
'settings.matchContentFrameRateDescription' => 'Ajustez la fréquence de rafraîchissement de l\'écran en fonction du contenu vidéo, ce qui réduit les saccades et économise la batterie',
'settings.requireProfileSelectionOnOpen' => 'Demander le profil à l\'ouverture',
'settings.requireProfileSelectionOnOpenDescription' => 'Afficher la sélection de profil à chaque ouverture de l\'application',
'settings.confirmExitOnBack' => 'Confirmer avant de quitter',
'settings.confirmExitOnBackDescription' => 'Afficher une boîte de dialogue de confirmation en appuyant sur retour pour quitter',
'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',
@@ -1725,14 +1737,14 @@ extension on TranslationsFr {
'liveTv.evening' => 'Soirée',
'liveTv.lateNight' => 'Nuit tardive',
'liveTv.whatsOn' => 'En ce moment',
_ => null,
} ?? switch (path) {
'liveTv.watchChannel' => 'Regarder la chaîne',
'collections.title' => 'Collections',
'collections.collection' => 'Collection',
'collections.empty' => 'La collection est vide',
'collections.unknownLibrarySection' => 'Impossible de supprimer : section de bibliothèque inconnue',
'collections.deleteCollection' => 'Supprimer la collection',
_ => null,
} ?? switch (path) {
'collections.deleteConfirm' => ({required Object title}) => 'Êtes-vous sûr de vouloir supprimer "${title}" ? Cette action ne peut pas être annulée.',
'collections.deleted' => 'Collection supprimée',
'collections.deleteFailed' => 'Échec de la suppression de la collection',
+14 -2
View File
@@ -145,6 +145,10 @@ class _TranslationsCommonIt implements TranslationsCommonEn {
@override String get ok => 'OK';
@override String get loading => 'Caricamento...';
@override String get reconnect => 'Riconnetti';
@override String get exitConfirmTitle => 'Uscire dall\'app?';
@override String get exitConfirmMessage => 'Sei sicuro di voler uscire?';
@override String get dontAskAgain => 'Non chiedere più';
@override String get exit => 'Esci';
}
// Path: screens
@@ -305,6 +309,8 @@ class _TranslationsSettingsIt implements TranslationsSettingsEn {
@override String get matchContentFrameRateDescription => 'Regola la frequenza di aggiornamento del display in base al contenuto video, riducendo i tremolii e risparmiando batteria';
@override String get requireProfileSelectionOnOpen => 'Chiedi profilo all\'apertura';
@override String get requireProfileSelectionOnOpenDescription => 'Mostra la selezione del profilo ogni volta che l\'app viene aperta';
@override String get confirmExitOnBack => 'Conferma prima di uscire';
@override String get confirmExitOnBackDescription => 'Mostra una finestra di conferma quando si preme indietro per uscire dall\'app';
}
// Path: search
@@ -1263,6 +1269,10 @@ extension on TranslationsIt {
'common.ok' => 'OK',
'common.loading' => 'Caricamento...',
'common.reconnect' => 'Riconnetti',
'common.exitConfirmTitle' => 'Uscire dall\'app?',
'common.exitConfirmMessage' => 'Sei sicuro di voler uscire?',
'common.dontAskAgain' => 'Non chiedere più',
'common.exit' => 'Esci',
'screens.licenses' => 'Licenze',
'screens.switchProfile' => 'Cambia profilo',
'screens.subtitleStyling' => 'Stile sottotitoli',
@@ -1396,6 +1406,8 @@ extension on TranslationsIt {
'settings.matchContentFrameRateDescription' => 'Regola la frequenza di aggiornamento del display in base al contenuto video, riducendo i tremolii e risparmiando batteria',
'settings.requireProfileSelectionOnOpen' => 'Chiedi profilo all\'apertura',
'settings.requireProfileSelectionOnOpenDescription' => 'Mostra la selezione del profilo ogni volta che l\'app viene aperta',
'settings.confirmExitOnBack' => 'Conferma prima di uscire',
'settings.confirmExitOnBackDescription' => 'Mostra una finestra di conferma quando si preme indietro per uscire dall\'app',
'search.hint' => 'Cerca film. spettacoli, musica...',
'search.tryDifferentTerm' => 'Prova altri termini di ricerca',
'search.searchYourMedia' => 'Cerca nei tuoi media',
@@ -1725,14 +1737,14 @@ extension on TranslationsIt {
'liveTv.evening' => 'Sera',
'liveTv.lateNight' => 'Notte tarda',
'liveTv.whatsOn' => 'In onda ora',
_ => null,
} ?? switch (path) {
'liveTv.watchChannel' => 'Guarda canale',
'downloads.title' => 'Download',
'downloads.manage' => 'Gestisci',
'downloads.tvShows' => 'Serie TV',
'downloads.movies' => 'Film',
'downloads.noDownloads' => 'Nessun download',
_ => null,
} ?? switch (path) {
'downloads.noDownloadsDescription' => 'I contenuti scaricati appariranno qui per la visualizzazione offline',
'downloads.downloadNow' => 'Scarica',
'downloads.deleteDownload' => 'Elimina download',
+14 -2
View File
@@ -145,6 +145,10 @@ class _TranslationsCommonKo implements TranslationsCommonEn {
@override String get ok => '확인';
@override String get loading => '로딩 중...';
@override String get reconnect => '다시 연결';
@override String get exitConfirmTitle => '앱을 종료하시겠습니까?';
@override String get exitConfirmMessage => '정말 종료하시겠습니까?';
@override String get dontAskAgain => '다시 묻지 않기';
@override String get exit => '종료';
}
// Path: screens
@@ -305,6 +309,8 @@ class _TranslationsSettingsKo implements TranslationsSettingsEn {
@override String get matchContentFrameRateDescription => '비디오 콘텐츠에 맞게 디스플레이 주사율을 조정하여 떨림을 줄이고 배터리를 절약합니다';
@override String get requireProfileSelectionOnOpen => '앱 실행 시 프로필 선택';
@override String get requireProfileSelectionOnOpenDescription => '앱을 열 때마다 프로필 선택 화면을 표시합니다';
@override String get confirmExitOnBack => '종료 전 확인';
@override String get confirmExitOnBackDescription => '뒤로 버튼을 눌러 앱을 종료할 때 확인 대화상자를 표시합니다';
}
// Path: search
@@ -1263,6 +1269,10 @@ extension on TranslationsKo {
'common.ok' => '확인',
'common.loading' => '로딩 중...',
'common.reconnect' => '다시 연결',
'common.exitConfirmTitle' => '앱을 종료하시겠습니까?',
'common.exitConfirmMessage' => '정말 종료하시겠습니까?',
'common.dontAskAgain' => '다시 묻지 않기',
'common.exit' => '종료',
'screens.licenses' => '라이선스',
'screens.switchProfile' => '프로필 전환',
'screens.subtitleStyling' => '자막 스타일 설정',
@@ -1396,6 +1406,8 @@ extension on TranslationsKo {
'settings.matchContentFrameRateDescription' => '비디오 콘텐츠에 맞게 디스플레이 주사율을 조정하여 떨림을 줄이고 배터리를 절약합니다',
'settings.requireProfileSelectionOnOpen' => '앱 실행 시 프로필 선택',
'settings.requireProfileSelectionOnOpenDescription' => '앱을 열 때마다 프로필 선택 화면을 표시합니다',
'settings.confirmExitOnBack' => '종료 전 확인',
'settings.confirmExitOnBackDescription' => '뒤로 버튼을 눌러 앱을 종료할 때 확인 대화상자를 표시합니다',
'search.hint' => '영화, 시리즈, 음악 등을 검색하세요...',
'search.tryDifferentTerm' => '다른 검색어를 시도해 보세요',
'search.searchYourMedia' => '미디어 검색',
@@ -1725,14 +1737,14 @@ extension on TranslationsKo {
'liveTv.evening' => '저녁',
'liveTv.lateNight' => '심야 방송',
'liveTv.whatsOn' => '지금 방송 중',
_ => null,
} ?? switch (path) {
'liveTv.watchChannel' => '채널 시청',
'collections.title' => '컬렉션',
'collections.collection' => '컬렉션',
'collections.empty' => '컬렉션이 비어 있습니다',
'collections.unknownLibrarySection' => '삭제할 수 없습니다: 알 수 없는 미디어 라이브러리 섹션입니다',
'collections.deleteCollection' => '컬렉션 삭제',
_ => null,
} ?? switch (path) {
'collections.deleteConfirm' => ({required Object title}) => '"${title}"을(를) 삭제 하시겠습니까? 이 작업은 되돌릴 수 없습니다.',
'collections.deleted' => '컬렉션 삭제됨',
'collections.deleteFailed' => '컬렉션 삭제 실패',
+14 -2
View File
@@ -145,6 +145,10 @@ class _TranslationsCommonNl implements TranslationsCommonEn {
@override String get ok => 'OK';
@override String get loading => 'Laden...';
@override String get reconnect => 'Opnieuw verbinden';
@override String get exitConfirmTitle => 'App afsluiten?';
@override String get exitConfirmMessage => 'Weet je zeker dat je wilt afsluiten?';
@override String get dontAskAgain => 'Niet meer vragen';
@override String get exit => 'Afsluiten';
}
// Path: screens
@@ -305,6 +309,8 @@ class _TranslationsSettingsNl implements TranslationsSettingsEn {
@override String get matchContentFrameRateDescription => 'Pas de schermverversingssnelheid aan op de video-inhoud, vermindert haperingen en bespaart batterij';
@override String get requireProfileSelectionOnOpen => 'Vraag om profiel bij openen';
@override String get requireProfileSelectionOnOpenDescription => 'Toon profielselectie telkens wanneer de app wordt geopend';
@override String get confirmExitOnBack => 'Bevestigen voor afsluiten';
@override String get confirmExitOnBackDescription => 'Toon een bevestigingsvenster bij het drukken op terug om de app af te sluiten';
}
// Path: search
@@ -1263,6 +1269,10 @@ extension on TranslationsNl {
'common.ok' => 'OK',
'common.loading' => 'Laden...',
'common.reconnect' => 'Opnieuw verbinden',
'common.exitConfirmTitle' => 'App afsluiten?',
'common.exitConfirmMessage' => 'Weet je zeker dat je wilt afsluiten?',
'common.dontAskAgain' => 'Niet meer vragen',
'common.exit' => 'Afsluiten',
'screens.licenses' => 'Licenties',
'screens.switchProfile' => 'Wissel van profiel',
'screens.subtitleStyling' => 'Ondertitel opmaak',
@@ -1396,6 +1406,8 @@ extension on TranslationsNl {
'settings.matchContentFrameRateDescription' => 'Pas de schermverversingssnelheid aan op de video-inhoud, vermindert haperingen en bespaart batterij',
'settings.requireProfileSelectionOnOpen' => 'Vraag om profiel bij openen',
'settings.requireProfileSelectionOnOpenDescription' => 'Toon profielselectie telkens wanneer de app wordt geopend',
'settings.confirmExitOnBack' => 'Bevestigen voor afsluiten',
'settings.confirmExitOnBackDescription' => 'Toon een bevestigingsvenster bij het drukken op terug om de app af te sluiten',
'search.hint' => 'Zoek films, series, muziek...',
'search.tryDifferentTerm' => 'Probeer een andere zoekterm',
'search.searchYourMedia' => 'Zoek in je media',
@@ -1725,14 +1737,14 @@ extension on TranslationsNl {
'liveTv.evening' => 'Avond',
'liveTv.lateNight' => 'Late avond',
'liveTv.whatsOn' => 'Nu op TV',
_ => null,
} ?? switch (path) {
'liveTv.watchChannel' => 'Kanaal bekijken',
'downloads.title' => 'Downloads',
'downloads.manage' => 'Beheren',
'downloads.tvShows' => 'Series',
'downloads.movies' => 'Films',
'downloads.noDownloads' => 'Nog geen downloads',
_ => null,
} ?? switch (path) {
'downloads.noDownloadsDescription' => 'Gedownloade content verschijnt hier voor offline weergave',
'downloads.downloadNow' => 'Download',
'downloads.deleteDownload' => 'Download verwijderen',
+14 -2
View File
@@ -145,6 +145,10 @@ class _TranslationsCommonSv implements TranslationsCommonEn {
@override String get ok => 'OK';
@override String get loading => 'Laddar...';
@override String get reconnect => 'Återanslut';
@override String get exitConfirmTitle => 'Avsluta appen?';
@override String get exitConfirmMessage => 'Är du säker på att du vill avsluta?';
@override String get dontAskAgain => 'Fråga inte igen';
@override String get exit => 'Avsluta';
}
// Path: screens
@@ -305,6 +309,8 @@ class _TranslationsSettingsSv implements TranslationsSettingsEn {
@override String get matchContentFrameRateDescription => 'Justera skärmens uppdateringsfrekvens för att matcha videoinnehållet, minskar hackighet och sparar batteri';
@override String get requireProfileSelectionOnOpen => 'Fråga efter profil vid appstart';
@override String get requireProfileSelectionOnOpenDescription => 'Visa profilval varje gång appen öppnas';
@override String get confirmExitOnBack => 'Bekräfta innan avslut';
@override String get confirmExitOnBackDescription => 'Visa en bekräftelsedialog när du trycker tillbaka för att avsluta appen';
}
// Path: search
@@ -1263,6 +1269,10 @@ extension on TranslationsSv {
'common.ok' => 'OK',
'common.loading' => 'Laddar...',
'common.reconnect' => 'Återanslut',
'common.exitConfirmTitle' => 'Avsluta appen?',
'common.exitConfirmMessage' => 'Är du säker på att du vill avsluta?',
'common.dontAskAgain' => 'Fråga inte igen',
'common.exit' => 'Avsluta',
'screens.licenses' => 'Licenser',
'screens.switchProfile' => 'Byt profil',
'screens.subtitleStyling' => 'Undertext-styling',
@@ -1396,6 +1406,8 @@ extension on TranslationsSv {
'settings.matchContentFrameRateDescription' => 'Justera skärmens uppdateringsfrekvens för att matcha videoinnehållet, minskar hackighet och sparar batteri',
'settings.requireProfileSelectionOnOpen' => 'Fråga efter profil vid appstart',
'settings.requireProfileSelectionOnOpenDescription' => 'Visa profilval varje gång appen öppnas',
'settings.confirmExitOnBack' => 'Bekräfta innan avslut',
'settings.confirmExitOnBackDescription' => 'Visa en bekräftelsedialog när du trycker tillbaka för att avsluta appen',
'search.hint' => 'Sök filmer, serier, musik...',
'search.tryDifferentTerm' => 'Prova en annan sökterm',
'search.searchYourMedia' => 'Sök i dina media',
@@ -1725,14 +1737,14 @@ extension on TranslationsSv {
'liveTv.evening' => 'Kväll',
'liveTv.lateNight' => 'Sen kväll',
'liveTv.whatsOn' => 'På TV nu',
_ => null,
} ?? switch (path) {
'liveTv.watchChannel' => 'Titta på kanal',
'downloads.title' => 'Nedladdningar',
'downloads.manage' => 'Hantera',
'downloads.tvShows' => 'TV-serier',
'downloads.movies' => 'Filmer',
'downloads.noDownloads' => 'Inga nedladdningar ännu',
_ => null,
} ?? switch (path) {
'downloads.noDownloadsDescription' => 'Nedladdat innehåll visas här för offline-visning',
'downloads.downloadNow' => 'Ladda ner',
'downloads.deleteDownload' => 'Ta bort nedladdning',
+14 -2
View File
@@ -145,6 +145,10 @@ class _TranslationsCommonZh implements TranslationsCommonEn {
@override String get ok => '确定';
@override String get loading => '加载中...';
@override String get reconnect => '重新连接';
@override String get exitConfirmTitle => '退出应用?';
@override String get exitConfirmMessage => '确定要退出吗?';
@override String get dontAskAgain => '不再询问';
@override String get exit => '退出';
}
// Path: screens
@@ -305,6 +309,8 @@ class _TranslationsSettingsZh implements TranslationsSettingsEn {
@override String get matchContentFrameRateDescription => '调整显示刷新率以匹配视频内容,减少画面抖动并节省电量';
@override String get requireProfileSelectionOnOpen => '打开应用时询问配置文件';
@override String get requireProfileSelectionOnOpenDescription => '每次打开应用时显示配置文件选择';
@override String get confirmExitOnBack => '退出前确认';
@override String get confirmExitOnBackDescription => '按返回键退出应用时显示确认对话框';
}
// Path: search
@@ -1263,6 +1269,10 @@ extension on TranslationsZh {
'common.ok' => '确定',
'common.loading' => '加载中...',
'common.reconnect' => '重新连接',
'common.exitConfirmTitle' => '退出应用?',
'common.exitConfirmMessage' => '确定要退出吗?',
'common.dontAskAgain' => '不再询问',
'common.exit' => '退出',
'screens.licenses' => '许可证',
'screens.switchProfile' => '切换用户',
'screens.subtitleStyling' => '字幕样式',
@@ -1396,6 +1406,8 @@ extension on TranslationsZh {
'settings.matchContentFrameRateDescription' => '调整显示刷新率以匹配视频内容,减少画面抖动并节省电量',
'settings.requireProfileSelectionOnOpen' => '打开应用时询问配置文件',
'settings.requireProfileSelectionOnOpenDescription' => '每次打开应用时显示配置文件选择',
'settings.confirmExitOnBack' => '退出前确认',
'settings.confirmExitOnBackDescription' => '按返回键退出应用时显示确认对话框',
'search.hint' => '搜索电影、系列、音乐...',
'search.tryDifferentTerm' => '尝试不同的搜索词',
'search.searchYourMedia' => '搜索媒体',
@@ -1725,14 +1737,14 @@ extension on TranslationsZh {
'liveTv.evening' => '晚上',
'liveTv.lateNight' => '深夜',
'liveTv.whatsOn' => '正在播出',
_ => null,
} ?? switch (path) {
'liveTv.watchChannel' => '观看频道',
'downloads.title' => '下载',
'downloads.manage' => '管理',
'downloads.tvShows' => '电视剧',
'downloads.movies' => '电影',
'downloads.noDownloads' => '暂无下载',
_ => null,
} ?? switch (path) {
'downloads.noDownloadsDescription' => '下载的内容将在此处显示以供离线观看',
'downloads.downloadNow' => '下载',
'downloads.deleteDownload' => '删除下载',
+8 -2
View File
@@ -47,7 +47,11 @@
"mute": "Ljud av",
"ok": "OK",
"loading": "Laddar...",
"reconnect": "Återanslut"
"reconnect": "Återanslut",
"exitConfirmTitle": "Avsluta appen?",
"exitConfirmMessage": "Är du säker på att du vill avsluta?",
"dontAskAgain": "Fråga inte igen",
"exit": "Avsluta"
},
"screens": {
"licenses": "Licenser",
@@ -186,7 +190,9 @@
"matchContentFrameRate": "Matcha innehållets bildfrekvens",
"matchContentFrameRateDescription": "Justera skärmens uppdateringsfrekvens för att matcha videoinnehållet, minskar hackighet och sparar batteri",
"requireProfileSelectionOnOpen": "Fråga efter profil vid appstart",
"requireProfileSelectionOnOpenDescription": "Visa profilval varje gång appen öppnas"
"requireProfileSelectionOnOpenDescription": "Visa profilval varje gång appen öppnas",
"confirmExitOnBack": "Bekräfta innan avslut",
"confirmExitOnBackDescription": "Visa en bekräftelsedialog när du trycker tillbaka för att avsluta appen"
},
"search": {
"hint": "Sök filmer, serier, musik...",
+8 -2
View File
@@ -47,7 +47,11 @@
"mute": "静音",
"ok": "确定",
"loading": "加载中...",
"reconnect": "重新连接"
"reconnect": "重新连接",
"exitConfirmTitle": "退出应用?",
"exitConfirmMessage": "确定要退出吗?",
"dontAskAgain": "不再询问",
"exit": "退出"
},
"screens": {
"licenses": "许可证",
@@ -186,7 +190,9 @@
"matchContentFrameRate": "匹配内容帧率",
"matchContentFrameRateDescription": "调整显示刷新率以匹配视频内容,减少画面抖动并节省电量",
"requireProfileSelectionOnOpen": "打开应用时询问配置文件",
"requireProfileSelectionOnOpenDescription": "每次打开应用时显示配置文件选择"
"requireProfileSelectionOnOpenDescription": "每次打开应用时显示配置文件选择",
"confirmExitOnBack": "退出前确认",
"confirmExitOnBackDescription": "按返回键退出应用时显示确认对话框"
},
"search": {
"hint": "搜索电影、系列、音乐...",
+20 -1
View File
@@ -10,6 +10,7 @@ import '../../services/plex_client.dart';
import '../i18n/strings.g.dart';
import '../services/update_service.dart';
import '../utils/app_logger.dart';
import '../utils/dialogs.dart';
import '../utils/provider_extensions.dart';
import '../utils/platform_detector.dart';
import '../utils/video_player_navigation.dart';
@@ -702,7 +703,25 @@ class _MainScreenState extends State<MainScreen> with RouteAware, WindowListener
}
// Sidebar focused → exit app
return handleBackKeyAction(event, () => SystemNavigator.pop());
return handleBackKeyAction(event, () async {
if (PlatformDetector.isTV()) {
final settings = await SettingsService.getInstance();
if (settings.getConfirmExitOnBack() && mounted) {
final result = await showConfirmDialogWithCheckbox(
context,
title: t.common.exitConfirmTitle,
message: t.common.exitConfirmMessage,
confirmText: t.common.exit,
checkboxLabel: t.common.dontAskAgain,
);
if (result.checked) {
await settings.setConfirmExitOnBack(false);
}
if (!result.confirmed) return;
}
}
SystemNavigator.pop();
});
}
@override
+15
View File
@@ -68,6 +68,7 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
static const _kAlwaysKeepSidebarOpen = 'always_keep_sidebar_open';
static const _kShowUnwatchedCount = 'show_unwatched_count';
static const _kRequireProfileSelectionOnOpen = 'require_profile_selection_on_open';
static const _kConfirmExitOnBack = 'confirm_exit_on_back';
static const _kPlayerBackend = 'player_backend';
static const _kExternalPlayer = 'external_player';
static const _kHardwareDecoding = 'hardware_decoding';
@@ -118,6 +119,7 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
bool _useExoPlayer = true; // Android only: ExoPlayer vs MPV
bool _requireProfileSelectionOnOpen = false;
bool _useExternalPlayer = false;
bool _confirmExitOnBack = true;
String _selectedExternalPlayerName = '';
// Update checking state
@@ -190,6 +192,7 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
_requireProfileSelectionOnOpen = _settingsService.getRequireProfileSelectionOnOpen();
_useExternalPlayer = _settingsService.getUseExternalPlayer();
_selectedExternalPlayerName = _settingsService.getSelectedExternalPlayer().name;
_confirmExitOnBack = _settingsService.getConfirmExitOnBack();
_isLoading = false;
});
}
@@ -390,6 +393,18 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
);
},
),
if (PlatformDetector.isTV())
SwitchListTile(
focusNode: _focusTracker.get(_kConfirmExitOnBack),
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);
},
),
],
),
);
+11
View File
@@ -74,6 +74,7 @@ class SettingsService extends BaseSharedPreferencesService {
static const String _keyUseExternalPlayer = 'use_external_player';
static const String _keySelectedExternalPlayer = 'selected_external_player';
static const String _keyCustomExternalPlayers = 'custom_external_players';
static const String _keyConfirmExitOnBack = 'confirm_exit_on_back';
SettingsService._();
@@ -1107,6 +1108,15 @@ class SettingsService extends BaseSharedPreferencesService {
}
}
// Confirm Exit on Back (Android TV)
Future<void> setConfirmExitOnBack(bool value) async {
await prefs.setBool(_keyConfirmExitOnBack, value);
}
bool getConfirmExitOnBack() {
return prefs.getBool(_keyConfirmExitOnBack) ?? true; // Default: enabled
}
// Reset all settings to defaults
Future<void> resetAllSettings() async {
await Future.wait([
@@ -1160,6 +1170,7 @@ class SettingsService extends BaseSharedPreferencesService {
prefs.remove(_keyUseExternalPlayer),
prefs.remove(_keySelectedExternalPlayer),
prefs.remove(_keyCustomExternalPlayers),
prefs.remove(_keyConfirmExitOnBack),
]);
}
+55
View File
@@ -45,6 +45,61 @@ Future<bool> showConfirmDialog(
return confirmed ?? false;
}
/// Shows a confirmation dialog with an optional checkbox (e.g. "Don't ask again").
/// Returns a record with [confirmed] and [checked] booleans.
Future<({bool confirmed, bool checked})> showConfirmDialogWithCheckbox(
BuildContext context, {
required String title,
required String message,
required String confirmText,
required String checkboxLabel,
String? cancelText,
}) async {
var checked = false;
final confirmed = await showDialog<bool>(
context: context,
builder: (dialogContext) {
return StatefulBuilder(
builder: (context, setDialogState) {
return AlertDialog(
title: Text(title),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(message),
const SizedBox(height: 12),
CheckboxListTile(
value: checked,
onChanged: (v) => setDialogState(() => checked = v ?? false),
title: Text(checkboxLabel),
contentPadding: EdgeInsets.zero,
controlAffinity: ListTileControlAffinity.leading,
dense: true,
),
],
),
actions: [
TextButton(
autofocus: true,
onPressed: () => Navigator.pop(dialogContext, false),
style: TextButton.styleFrom(padding: _buttonPadding, shape: _buttonShape),
child: Text(cancelText ?? t.common.cancel),
),
FilledButton(
onPressed: () => Navigator.pop(dialogContext, true),
child: Text(confirmText),
),
],
);
},
);
},
);
return (confirmed: confirmed ?? false, checked: checked);
}
/// Shows a delete confirmation dialog.
/// Convenience wrapper around [showConfirmDialog] with destructive styling.
Future<bool> showDeleteConfirmation(BuildContext context, {required String title, required String message}) {