@@ -555,29 +555,17 @@
|
||||
"title": "Live-TV",
|
||||
"channels": "Kanäle",
|
||||
"guide": "Programmführer",
|
||||
"recordings": "Aufnahmen",
|
||||
"subscriptions": "Aufnahmeregeln",
|
||||
"scheduled": "Geplant",
|
||||
"noChannels": "Keine Kanäle verfügbar",
|
||||
"noDvr": "Kein DVR auf einem Server konfiguriert",
|
||||
"tuneFailed": "Kanal konnte nicht eingestellt werden",
|
||||
"loading": "Kanäle werden geladen...",
|
||||
"nowPlaying": "Läuft gerade",
|
||||
"record": "Aufnehmen",
|
||||
"recordSeries": "Serie aufnehmen",
|
||||
"cancelRecording": "Aufnahme abbrechen",
|
||||
"deleteSubscription": "Aufnahmeregel löschen",
|
||||
"deleteSubscriptionConfirm": "Möchten Sie diese Aufnahmeregel wirklich löschen?",
|
||||
"subscriptionDeleted": "Aufnahmeregel gelöscht",
|
||||
"noPrograms": "Keine Programmdaten verfügbar",
|
||||
"noRecordings": "Keine Aufnahmen geplant",
|
||||
"noSubscriptions": "Keine Aufnahmeregeln",
|
||||
"channelNumber": "Kanal ${number}",
|
||||
"live": "LIVE",
|
||||
"hd": "HD",
|
||||
"premiere": "NEU",
|
||||
"reloadGuide": "Programmführer neu laden",
|
||||
"guideReloaded": "Programmdaten neu geladen",
|
||||
"allChannels": "Alle Kanäle",
|
||||
"now": "Jetzt",
|
||||
"today": "Heute",
|
||||
|
||||
@@ -555,29 +555,17 @@
|
||||
"title": "Live TV",
|
||||
"channels": "Channels",
|
||||
"guide": "Guide",
|
||||
"recordings": "Recordings",
|
||||
"subscriptions": "Subscriptions",
|
||||
"scheduled": "Scheduled",
|
||||
"noChannels": "No channels available",
|
||||
"noDvr": "No DVR configured on any server",
|
||||
"tuneFailed": "Failed to tune channel",
|
||||
"loading": "Loading channels...",
|
||||
"nowPlaying": "Now Playing",
|
||||
"record": "Record",
|
||||
"recordSeries": "Record Series",
|
||||
"cancelRecording": "Cancel Recording",
|
||||
"deleteSubscription": "Delete Recording Rule",
|
||||
"deleteSubscriptionConfirm": "Are you sure you want to delete this recording rule?",
|
||||
"subscriptionDeleted": "Recording rule deleted",
|
||||
"noPrograms": "No program data available",
|
||||
"noRecordings": "No recordings scheduled",
|
||||
"noSubscriptions": "No recording rules",
|
||||
"channelNumber": "Ch. ${number}",
|
||||
"live": "LIVE",
|
||||
"hd": "HD",
|
||||
"premiere": "NEW",
|
||||
"reloadGuide": "Reload Guide",
|
||||
"guideReloaded": "Guide data reloaded",
|
||||
"allChannels": "All Channels",
|
||||
"now": "Now",
|
||||
"today": "Today",
|
||||
|
||||
@@ -555,29 +555,17 @@
|
||||
"title": "TV en vivo",
|
||||
"channels": "Canales",
|
||||
"guide": "Guía",
|
||||
"recordings": "Grabaciones",
|
||||
"subscriptions": "Reglas de grabación",
|
||||
"scheduled": "Programadas",
|
||||
"noChannels": "No hay canales disponibles",
|
||||
"noDvr": "No hay DVR configurado en ningún servidor",
|
||||
"tuneFailed": "Error al sintonizar el canal",
|
||||
"loading": "Cargando canales...",
|
||||
"nowPlaying": "Reproduciendo ahora",
|
||||
"record": "Grabar",
|
||||
"recordSeries": "Grabar serie",
|
||||
"cancelRecording": "Cancelar grabación",
|
||||
"deleteSubscription": "Eliminar regla de grabación",
|
||||
"deleteSubscriptionConfirm": "¿Estás seguro de que quieres eliminar esta regla de grabación?",
|
||||
"subscriptionDeleted": "Regla de grabación eliminada",
|
||||
"noPrograms": "No hay datos de programación disponibles",
|
||||
"noRecordings": "No hay grabaciones programadas",
|
||||
"noSubscriptions": "No hay reglas de grabación",
|
||||
"channelNumber": "Canal ${number}",
|
||||
"live": "EN VIVO",
|
||||
"hd": "HD",
|
||||
"premiere": "NUEVO",
|
||||
"reloadGuide": "Recargar guía",
|
||||
"guideReloaded": "Datos de la guía recargados",
|
||||
"allChannels": "Todos los canales",
|
||||
"now": "Ahora",
|
||||
"today": "Hoy",
|
||||
|
||||
@@ -555,29 +555,17 @@
|
||||
"title": "TV en direct",
|
||||
"channels": "Chaînes",
|
||||
"guide": "Guide",
|
||||
"recordings": "Enregistrements",
|
||||
"subscriptions": "Règles d'enregistrement",
|
||||
"scheduled": "Programmés",
|
||||
"noChannels": "Aucune chaîne disponible",
|
||||
"noDvr": "Aucun DVR configuré sur les serveurs",
|
||||
"tuneFailed": "Impossible de syntoniser la chaîne",
|
||||
"loading": "Chargement des chaînes...",
|
||||
"nowPlaying": "En cours de lecture",
|
||||
"record": "Enregistrer",
|
||||
"recordSeries": "Enregistrer la série",
|
||||
"cancelRecording": "Annuler l'enregistrement",
|
||||
"deleteSubscription": "Supprimer la règle d'enregistrement",
|
||||
"deleteSubscriptionConfirm": "Voulez-vous vraiment supprimer cette règle d'enregistrement ?",
|
||||
"subscriptionDeleted": "Règle d'enregistrement supprimée",
|
||||
"noPrograms": "Aucune donnée de programme disponible",
|
||||
"noRecordings": "Aucun enregistrement programmé",
|
||||
"noSubscriptions": "Aucune règle d'enregistrement",
|
||||
"channelNumber": "Ch. ${number}",
|
||||
"live": "EN DIRECT",
|
||||
"hd": "HD",
|
||||
"premiere": "NOUVEAU",
|
||||
"reloadGuide": "Recharger le guide",
|
||||
"guideReloaded": "Données du guide rechargées",
|
||||
"allChannels": "Toutes les chaînes",
|
||||
"now": "Maintenant",
|
||||
"today": "Aujourd'hui",
|
||||
|
||||
@@ -555,29 +555,17 @@
|
||||
"title": "TV in diretta",
|
||||
"channels": "Canali",
|
||||
"guide": "Guida",
|
||||
"recordings": "Registrazioni",
|
||||
"subscriptions": "Regole di registrazione",
|
||||
"scheduled": "Programmati",
|
||||
"noChannels": "Nessun canale disponibile",
|
||||
"noDvr": "Nessun DVR configurato su nessun server",
|
||||
"tuneFailed": "Impossibile sintonizzare il canale",
|
||||
"loading": "Caricamento canali...",
|
||||
"nowPlaying": "In riproduzione",
|
||||
"record": "Registra",
|
||||
"recordSeries": "Registra serie",
|
||||
"cancelRecording": "Annulla registrazione",
|
||||
"deleteSubscription": "Elimina regola di registrazione",
|
||||
"deleteSubscriptionConfirm": "Sei sicuro di voler eliminare questa regola di registrazione?",
|
||||
"subscriptionDeleted": "Regola di registrazione eliminata",
|
||||
"noPrograms": "Nessun dato di programma disponibile",
|
||||
"noRecordings": "Nessuna registrazione programmata",
|
||||
"noSubscriptions": "Nessuna regola di registrazione",
|
||||
"channelNumber": "Canale ${number}",
|
||||
"live": "IN DIRETTA",
|
||||
"hd": "HD",
|
||||
"premiere": "NUOVO",
|
||||
"reloadGuide": "Ricarica guida",
|
||||
"guideReloaded": "Dati della guida ricaricati",
|
||||
"allChannels": "Tutti i canali",
|
||||
"now": "Ora",
|
||||
"today": "Oggi",
|
||||
|
||||
@@ -555,29 +555,17 @@
|
||||
"title": "실시간 TV",
|
||||
"channels": "채널",
|
||||
"guide": "편성표",
|
||||
"recordings": "녹화",
|
||||
"subscriptions": "녹화 규칙",
|
||||
"scheduled": "예약됨",
|
||||
"noChannels": "사용 가능한 채널이 없습니다",
|
||||
"noDvr": "서버에 DVR이 구성되어 있지 않습니다",
|
||||
"tuneFailed": "채널 튜닝에 실패했습니다",
|
||||
"loading": "채널 로딩 중...",
|
||||
"nowPlaying": "현재 재생 중",
|
||||
"record": "녹화",
|
||||
"recordSeries": "시리즈 녹화",
|
||||
"cancelRecording": "녹화 취소",
|
||||
"deleteSubscription": "녹화 규칙 삭제",
|
||||
"deleteSubscriptionConfirm": "이 녹화 규칙을 삭제하시겠습니까?",
|
||||
"subscriptionDeleted": "녹화 규칙이 삭제되었습니다",
|
||||
"noPrograms": "프로그램 데이터가 없습니다",
|
||||
"noRecordings": "예약된 녹화가 없습니다",
|
||||
"noSubscriptions": "녹화 규칙이 없습니다",
|
||||
"channelNumber": "채널 ${number}",
|
||||
"live": "실시간",
|
||||
"hd": "HD",
|
||||
"premiere": "신규",
|
||||
"reloadGuide": "편성표 새로고침",
|
||||
"guideReloaded": "편성표 데이터가 새로고침되었습니다",
|
||||
"allChannels": "전체 채널",
|
||||
"now": "지금",
|
||||
"today": "오늘",
|
||||
|
||||
@@ -555,29 +555,17 @@
|
||||
"title": "Live TV",
|
||||
"channels": "Zenders",
|
||||
"guide": "Gids",
|
||||
"recordings": "Opnames",
|
||||
"subscriptions": "Opnameregels",
|
||||
"scheduled": "Gepland",
|
||||
"noChannels": "Geen zenders beschikbaar",
|
||||
"noDvr": "Geen DVR geconfigureerd op een server",
|
||||
"tuneFailed": "Kan zender niet afstemmen",
|
||||
"loading": "Zenders laden...",
|
||||
"nowPlaying": "Nu aan het afspelen",
|
||||
"record": "Opnemen",
|
||||
"recordSeries": "Serie opnemen",
|
||||
"cancelRecording": "Opname annuleren",
|
||||
"deleteSubscription": "Opnameregel verwijderen",
|
||||
"deleteSubscriptionConfirm": "Weet je zeker dat je deze opnameregel wilt verwijderen?",
|
||||
"subscriptionDeleted": "Opnameregel verwijderd",
|
||||
"noPrograms": "Geen programmagegevens beschikbaar",
|
||||
"noRecordings": "Geen opnames gepland",
|
||||
"noSubscriptions": "Geen opnameregels",
|
||||
"channelNumber": "Kanaal ${number}",
|
||||
"live": "LIVE",
|
||||
"hd": "HD",
|
||||
"premiere": "NIEUW",
|
||||
"reloadGuide": "Gids herladen",
|
||||
"guideReloaded": "Gidsgegevens herladen",
|
||||
"allChannels": "Alle zenders",
|
||||
"now": "Nu",
|
||||
"today": "Vandaag",
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
/// To regenerate, run: `dart run slang`
|
||||
///
|
||||
/// Locales: 9
|
||||
/// Strings: 7281 (809 per locale)
|
||||
/// Strings: 7173 (797 per locale)
|
||||
///
|
||||
/// Built on 2026-02-24 at 03:45 UTC
|
||||
/// Built on 2026-02-24 at 21:50 UTC
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint, unused_import
|
||||
|
||||
@@ -797,29 +797,17 @@ class _TranslationsLiveTvDe implements TranslationsLiveTvEn {
|
||||
@override String get title => 'Live-TV';
|
||||
@override String get channels => 'Kanäle';
|
||||
@override String get guide => 'Programmführer';
|
||||
@override String get recordings => 'Aufnahmen';
|
||||
@override String get subscriptions => 'Aufnahmeregeln';
|
||||
@override String get scheduled => 'Geplant';
|
||||
@override String get noChannels => 'Keine Kanäle verfügbar';
|
||||
@override String get noDvr => 'Kein DVR auf einem Server konfiguriert';
|
||||
@override String get tuneFailed => 'Kanal konnte nicht eingestellt werden';
|
||||
@override String get loading => 'Kanäle werden geladen...';
|
||||
@override String get nowPlaying => 'Läuft gerade';
|
||||
@override String get record => 'Aufnehmen';
|
||||
@override String get recordSeries => 'Serie aufnehmen';
|
||||
@override String get cancelRecording => 'Aufnahme abbrechen';
|
||||
@override String get deleteSubscription => 'Aufnahmeregel löschen';
|
||||
@override String get deleteSubscriptionConfirm => 'Möchten Sie diese Aufnahmeregel wirklich löschen?';
|
||||
@override String get subscriptionDeleted => 'Aufnahmeregel gelöscht';
|
||||
@override String get noPrograms => 'Keine Programmdaten verfügbar';
|
||||
@override String get noRecordings => 'Keine Aufnahmen geplant';
|
||||
@override String get noSubscriptions => 'Keine Aufnahmeregeln';
|
||||
@override String channelNumber({required Object number}) => 'Kanal ${number}';
|
||||
@override String get live => 'LIVE';
|
||||
@override String get hd => 'HD';
|
||||
@override String get premiere => 'NEU';
|
||||
@override String get reloadGuide => 'Programmführer neu laden';
|
||||
@override String get guideReloaded => 'Programmdaten neu geladen';
|
||||
@override String get allChannels => 'Alle Kanäle';
|
||||
@override String get now => 'Jetzt';
|
||||
@override String get today => 'Heute';
|
||||
@@ -1797,31 +1785,17 @@ extension on TranslationsDe {
|
||||
'liveTv.title' => 'Live-TV',
|
||||
'liveTv.channels' => 'Kanäle',
|
||||
'liveTv.guide' => 'Programmführer',
|
||||
'liveTv.recordings' => 'Aufnahmen',
|
||||
'liveTv.subscriptions' => 'Aufnahmeregeln',
|
||||
'liveTv.scheduled' => 'Geplant',
|
||||
'liveTv.noChannels' => 'Keine Kanäle verfügbar',
|
||||
'liveTv.noDvr' => 'Kein DVR auf einem Server konfiguriert',
|
||||
'liveTv.tuneFailed' => 'Kanal konnte nicht eingestellt werden',
|
||||
'liveTv.loading' => 'Kanäle werden geladen...',
|
||||
'liveTv.nowPlaying' => 'Läuft gerade',
|
||||
'liveTv.record' => 'Aufnehmen',
|
||||
'liveTv.recordSeries' => 'Serie aufnehmen',
|
||||
'liveTv.cancelRecording' => 'Aufnahme abbrechen',
|
||||
'liveTv.deleteSubscription' => 'Aufnahmeregel löschen',
|
||||
'liveTv.deleteSubscriptionConfirm' => 'Möchten Sie diese Aufnahmeregel wirklich löschen?',
|
||||
'liveTv.subscriptionDeleted' => 'Aufnahmeregel gelöscht',
|
||||
'liveTv.noPrograms' => 'Keine Programmdaten verfügbar',
|
||||
'liveTv.noRecordings' => 'Keine Aufnahmen geplant',
|
||||
'liveTv.noSubscriptions' => 'Keine Aufnahmeregeln',
|
||||
'liveTv.channelNumber' => ({required Object number}) => 'Kanal ${number}',
|
||||
'liveTv.live' => 'LIVE',
|
||||
'liveTv.hd' => 'HD',
|
||||
'liveTv.premiere' => 'NEU',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.reloadGuide' => 'Programmführer neu laden',
|
||||
'liveTv.guideReloaded' => 'Programmdaten neu geladen',
|
||||
'liveTv.allChannels' => 'Alle Kanäle',
|
||||
'liveTv.now' => 'Jetzt',
|
||||
'liveTv.today' => 'Heute',
|
||||
@@ -1832,6 +1806,8 @@ 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',
|
||||
|
||||
@@ -1718,15 +1718,6 @@ class TranslationsLiveTvEn {
|
||||
/// en: 'Guide'
|
||||
String get guide => 'Guide';
|
||||
|
||||
/// en: 'Recordings'
|
||||
String get recordings => 'Recordings';
|
||||
|
||||
/// en: 'Subscriptions'
|
||||
String get subscriptions => 'Subscriptions';
|
||||
|
||||
/// en: 'Scheduled'
|
||||
String get scheduled => 'Scheduled';
|
||||
|
||||
/// en: 'No channels available'
|
||||
String get noChannels => 'No channels available';
|
||||
|
||||
@@ -1742,33 +1733,9 @@ class TranslationsLiveTvEn {
|
||||
/// en: 'Now Playing'
|
||||
String get nowPlaying => 'Now Playing';
|
||||
|
||||
/// en: 'Record'
|
||||
String get record => 'Record';
|
||||
|
||||
/// en: 'Record Series'
|
||||
String get recordSeries => 'Record Series';
|
||||
|
||||
/// en: 'Cancel Recording'
|
||||
String get cancelRecording => 'Cancel Recording';
|
||||
|
||||
/// en: 'Delete Recording Rule'
|
||||
String get deleteSubscription => 'Delete Recording Rule';
|
||||
|
||||
/// en: 'Are you sure you want to delete this recording rule?'
|
||||
String get deleteSubscriptionConfirm => 'Are you sure you want to delete this recording rule?';
|
||||
|
||||
/// en: 'Recording rule deleted'
|
||||
String get subscriptionDeleted => 'Recording rule deleted';
|
||||
|
||||
/// en: 'No program data available'
|
||||
String get noPrograms => 'No program data available';
|
||||
|
||||
/// en: 'No recordings scheduled'
|
||||
String get noRecordings => 'No recordings scheduled';
|
||||
|
||||
/// en: 'No recording rules'
|
||||
String get noSubscriptions => 'No recording rules';
|
||||
|
||||
/// en: 'Ch. ${number}'
|
||||
String channelNumber({required Object number}) => 'Ch. ${number}';
|
||||
|
||||
@@ -1784,9 +1751,6 @@ class TranslationsLiveTvEn {
|
||||
/// en: 'Reload Guide'
|
||||
String get reloadGuide => 'Reload Guide';
|
||||
|
||||
/// en: 'Guide data reloaded'
|
||||
String get guideReloaded => 'Guide data reloaded';
|
||||
|
||||
/// en: 'All Channels'
|
||||
String get allChannels => 'All Channels';
|
||||
|
||||
@@ -3422,31 +3386,17 @@ extension on Translations {
|
||||
'liveTv.title' => 'Live TV',
|
||||
'liveTv.channels' => 'Channels',
|
||||
'liveTv.guide' => 'Guide',
|
||||
'liveTv.recordings' => 'Recordings',
|
||||
'liveTv.subscriptions' => 'Subscriptions',
|
||||
'liveTv.scheduled' => 'Scheduled',
|
||||
'liveTv.noChannels' => 'No channels available',
|
||||
'liveTv.noDvr' => 'No DVR configured on any server',
|
||||
'liveTv.tuneFailed' => 'Failed to tune channel',
|
||||
'liveTv.loading' => 'Loading channels...',
|
||||
'liveTv.nowPlaying' => 'Now Playing',
|
||||
'liveTv.record' => 'Record',
|
||||
'liveTv.recordSeries' => 'Record Series',
|
||||
'liveTv.cancelRecording' => 'Cancel Recording',
|
||||
'liveTv.deleteSubscription' => 'Delete Recording Rule',
|
||||
'liveTv.deleteSubscriptionConfirm' => 'Are you sure you want to delete this recording rule?',
|
||||
'liveTv.subscriptionDeleted' => 'Recording rule deleted',
|
||||
'liveTv.noPrograms' => 'No program data available',
|
||||
'liveTv.noRecordings' => 'No recordings scheduled',
|
||||
'liveTv.noSubscriptions' => 'No recording rules',
|
||||
'liveTv.channelNumber' => ({required Object number}) => 'Ch. ${number}',
|
||||
'liveTv.live' => 'LIVE',
|
||||
'liveTv.hd' => 'HD',
|
||||
'liveTv.premiere' => 'NEW',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.reloadGuide' => 'Reload Guide',
|
||||
'liveTv.guideReloaded' => 'Guide data reloaded',
|
||||
'liveTv.allChannels' => 'All Channels',
|
||||
'liveTv.now' => 'Now',
|
||||
'liveTv.today' => 'Today',
|
||||
@@ -3457,6 +3407,8 @@ 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',
|
||||
|
||||
@@ -797,29 +797,17 @@ class _TranslationsLiveTvEs implements TranslationsLiveTvEn {
|
||||
@override String get title => 'TV en vivo';
|
||||
@override String get channels => 'Canales';
|
||||
@override String get guide => 'Guía';
|
||||
@override String get recordings => 'Grabaciones';
|
||||
@override String get subscriptions => 'Reglas de grabación';
|
||||
@override String get scheduled => 'Programadas';
|
||||
@override String get noChannels => 'No hay canales disponibles';
|
||||
@override String get noDvr => 'No hay DVR configurado en ningún servidor';
|
||||
@override String get tuneFailed => 'Error al sintonizar el canal';
|
||||
@override String get loading => 'Cargando canales...';
|
||||
@override String get nowPlaying => 'Reproduciendo ahora';
|
||||
@override String get record => 'Grabar';
|
||||
@override String get recordSeries => 'Grabar serie';
|
||||
@override String get cancelRecording => 'Cancelar grabación';
|
||||
@override String get deleteSubscription => 'Eliminar regla de grabación';
|
||||
@override String get deleteSubscriptionConfirm => '¿Estás seguro de que quieres eliminar esta regla de grabación?';
|
||||
@override String get subscriptionDeleted => 'Regla de grabación eliminada';
|
||||
@override String get noPrograms => 'No hay datos de programación disponibles';
|
||||
@override String get noRecordings => 'No hay grabaciones programadas';
|
||||
@override String get noSubscriptions => 'No hay reglas de grabación';
|
||||
@override String channelNumber({required Object number}) => 'Canal ${number}';
|
||||
@override String get live => 'EN VIVO';
|
||||
@override String get hd => 'HD';
|
||||
@override String get premiere => 'NUEVO';
|
||||
@override String get reloadGuide => 'Recargar guía';
|
||||
@override String get guideReloaded => 'Datos de la guía recargados';
|
||||
@override String get allChannels => 'Todos los canales';
|
||||
@override String get now => 'Ahora';
|
||||
@override String get today => 'Hoy';
|
||||
@@ -1797,31 +1785,17 @@ extension on TranslationsEs {
|
||||
'liveTv.title' => 'TV en vivo',
|
||||
'liveTv.channels' => 'Canales',
|
||||
'liveTv.guide' => 'Guía',
|
||||
'liveTv.recordings' => 'Grabaciones',
|
||||
'liveTv.subscriptions' => 'Reglas de grabación',
|
||||
'liveTv.scheduled' => 'Programadas',
|
||||
'liveTv.noChannels' => 'No hay canales disponibles',
|
||||
'liveTv.noDvr' => 'No hay DVR configurado en ningún servidor',
|
||||
'liveTv.tuneFailed' => 'Error al sintonizar el canal',
|
||||
'liveTv.loading' => 'Cargando canales...',
|
||||
'liveTv.nowPlaying' => 'Reproduciendo ahora',
|
||||
'liveTv.record' => 'Grabar',
|
||||
'liveTv.recordSeries' => 'Grabar serie',
|
||||
'liveTv.cancelRecording' => 'Cancelar grabación',
|
||||
'liveTv.deleteSubscription' => 'Eliminar regla de grabación',
|
||||
'liveTv.deleteSubscriptionConfirm' => '¿Estás seguro de que quieres eliminar esta regla de grabación?',
|
||||
'liveTv.subscriptionDeleted' => 'Regla de grabación eliminada',
|
||||
'liveTv.noPrograms' => 'No hay datos de programación disponibles',
|
||||
'liveTv.noRecordings' => 'No hay grabaciones programadas',
|
||||
'liveTv.noSubscriptions' => 'No hay reglas de grabación',
|
||||
'liveTv.channelNumber' => ({required Object number}) => 'Canal ${number}',
|
||||
'liveTv.live' => 'EN VIVO',
|
||||
'liveTv.hd' => 'HD',
|
||||
'liveTv.premiere' => 'NUEVO',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.reloadGuide' => 'Recargar guía',
|
||||
'liveTv.guideReloaded' => 'Datos de la guía recargados',
|
||||
'liveTv.allChannels' => 'Todos los canales',
|
||||
'liveTv.now' => 'Ahora',
|
||||
'liveTv.today' => 'Hoy',
|
||||
@@ -1832,6 +1806,8 @@ 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',
|
||||
|
||||
@@ -797,29 +797,17 @@ class _TranslationsLiveTvFr implements TranslationsLiveTvEn {
|
||||
@override String get title => 'TV en direct';
|
||||
@override String get channels => 'Chaînes';
|
||||
@override String get guide => 'Guide';
|
||||
@override String get recordings => 'Enregistrements';
|
||||
@override String get subscriptions => 'Règles d\'enregistrement';
|
||||
@override String get scheduled => 'Programmés';
|
||||
@override String get noChannels => 'Aucune chaîne disponible';
|
||||
@override String get noDvr => 'Aucun DVR configuré sur les serveurs';
|
||||
@override String get tuneFailed => 'Impossible de syntoniser la chaîne';
|
||||
@override String get loading => 'Chargement des chaînes...';
|
||||
@override String get nowPlaying => 'En cours de lecture';
|
||||
@override String get record => 'Enregistrer';
|
||||
@override String get recordSeries => 'Enregistrer la série';
|
||||
@override String get cancelRecording => 'Annuler l\'enregistrement';
|
||||
@override String get deleteSubscription => 'Supprimer la règle d\'enregistrement';
|
||||
@override String get deleteSubscriptionConfirm => 'Voulez-vous vraiment supprimer cette règle d\'enregistrement ?';
|
||||
@override String get subscriptionDeleted => 'Règle d\'enregistrement supprimée';
|
||||
@override String get noPrograms => 'Aucune donnée de programme disponible';
|
||||
@override String get noRecordings => 'Aucun enregistrement programmé';
|
||||
@override String get noSubscriptions => 'Aucune règle d\'enregistrement';
|
||||
@override String channelNumber({required Object number}) => 'Ch. ${number}';
|
||||
@override String get live => 'EN DIRECT';
|
||||
@override String get hd => 'HD';
|
||||
@override String get premiere => 'NOUVEAU';
|
||||
@override String get reloadGuide => 'Recharger le guide';
|
||||
@override String get guideReloaded => 'Données du guide rechargées';
|
||||
@override String get allChannels => 'Toutes les chaînes';
|
||||
@override String get now => 'Maintenant';
|
||||
@override String get today => 'Aujourd\'hui';
|
||||
@@ -1797,31 +1785,17 @@ extension on TranslationsFr {
|
||||
'liveTv.title' => 'TV en direct',
|
||||
'liveTv.channels' => 'Chaînes',
|
||||
'liveTv.guide' => 'Guide',
|
||||
'liveTv.recordings' => 'Enregistrements',
|
||||
'liveTv.subscriptions' => 'Règles d\'enregistrement',
|
||||
'liveTv.scheduled' => 'Programmés',
|
||||
'liveTv.noChannels' => 'Aucune chaîne disponible',
|
||||
'liveTv.noDvr' => 'Aucun DVR configuré sur les serveurs',
|
||||
'liveTv.tuneFailed' => 'Impossible de syntoniser la chaîne',
|
||||
'liveTv.loading' => 'Chargement des chaînes...',
|
||||
'liveTv.nowPlaying' => 'En cours de lecture',
|
||||
'liveTv.record' => 'Enregistrer',
|
||||
'liveTv.recordSeries' => 'Enregistrer la série',
|
||||
'liveTv.cancelRecording' => 'Annuler l\'enregistrement',
|
||||
'liveTv.deleteSubscription' => 'Supprimer la règle d\'enregistrement',
|
||||
'liveTv.deleteSubscriptionConfirm' => 'Voulez-vous vraiment supprimer cette règle d\'enregistrement ?',
|
||||
'liveTv.subscriptionDeleted' => 'Règle d\'enregistrement supprimée',
|
||||
'liveTv.noPrograms' => 'Aucune donnée de programme disponible',
|
||||
'liveTv.noRecordings' => 'Aucun enregistrement programmé',
|
||||
'liveTv.noSubscriptions' => 'Aucune règle d\'enregistrement',
|
||||
'liveTv.channelNumber' => ({required Object number}) => 'Ch. ${number}',
|
||||
'liveTv.live' => 'EN DIRECT',
|
||||
'liveTv.hd' => 'HD',
|
||||
'liveTv.premiere' => 'NOUVEAU',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.reloadGuide' => 'Recharger le guide',
|
||||
'liveTv.guideReloaded' => 'Données du guide rechargées',
|
||||
'liveTv.allChannels' => 'Toutes les chaînes',
|
||||
'liveTv.now' => 'Maintenant',
|
||||
'liveTv.today' => 'Aujourd\'hui',
|
||||
@@ -1832,6 +1806,8 @@ 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',
|
||||
|
||||
@@ -797,29 +797,17 @@ class _TranslationsLiveTvIt implements TranslationsLiveTvEn {
|
||||
@override String get title => 'TV in diretta';
|
||||
@override String get channels => 'Canali';
|
||||
@override String get guide => 'Guida';
|
||||
@override String get recordings => 'Registrazioni';
|
||||
@override String get subscriptions => 'Regole di registrazione';
|
||||
@override String get scheduled => 'Programmati';
|
||||
@override String get noChannels => 'Nessun canale disponibile';
|
||||
@override String get noDvr => 'Nessun DVR configurato su nessun server';
|
||||
@override String get tuneFailed => 'Impossibile sintonizzare il canale';
|
||||
@override String get loading => 'Caricamento canali...';
|
||||
@override String get nowPlaying => 'In riproduzione';
|
||||
@override String get record => 'Registra';
|
||||
@override String get recordSeries => 'Registra serie';
|
||||
@override String get cancelRecording => 'Annulla registrazione';
|
||||
@override String get deleteSubscription => 'Elimina regola di registrazione';
|
||||
@override String get deleteSubscriptionConfirm => 'Sei sicuro di voler eliminare questa regola di registrazione?';
|
||||
@override String get subscriptionDeleted => 'Regola di registrazione eliminata';
|
||||
@override String get noPrograms => 'Nessun dato di programma disponibile';
|
||||
@override String get noRecordings => 'Nessuna registrazione programmata';
|
||||
@override String get noSubscriptions => 'Nessuna regola di registrazione';
|
||||
@override String channelNumber({required Object number}) => 'Canale ${number}';
|
||||
@override String get live => 'IN DIRETTA';
|
||||
@override String get hd => 'HD';
|
||||
@override String get premiere => 'NUOVO';
|
||||
@override String get reloadGuide => 'Ricarica guida';
|
||||
@override String get guideReloaded => 'Dati della guida ricaricati';
|
||||
@override String get allChannels => 'Tutti i canali';
|
||||
@override String get now => 'Ora';
|
||||
@override String get today => 'Oggi';
|
||||
@@ -1797,31 +1785,17 @@ extension on TranslationsIt {
|
||||
'liveTv.title' => 'TV in diretta',
|
||||
'liveTv.channels' => 'Canali',
|
||||
'liveTv.guide' => 'Guida',
|
||||
'liveTv.recordings' => 'Registrazioni',
|
||||
'liveTv.subscriptions' => 'Regole di registrazione',
|
||||
'liveTv.scheduled' => 'Programmati',
|
||||
'liveTv.noChannels' => 'Nessun canale disponibile',
|
||||
'liveTv.noDvr' => 'Nessun DVR configurato su nessun server',
|
||||
'liveTv.tuneFailed' => 'Impossibile sintonizzare il canale',
|
||||
'liveTv.loading' => 'Caricamento canali...',
|
||||
'liveTv.nowPlaying' => 'In riproduzione',
|
||||
'liveTv.record' => 'Registra',
|
||||
'liveTv.recordSeries' => 'Registra serie',
|
||||
'liveTv.cancelRecording' => 'Annulla registrazione',
|
||||
'liveTv.deleteSubscription' => 'Elimina regola di registrazione',
|
||||
'liveTv.deleteSubscriptionConfirm' => 'Sei sicuro di voler eliminare questa regola di registrazione?',
|
||||
'liveTv.subscriptionDeleted' => 'Regola di registrazione eliminata',
|
||||
'liveTv.noPrograms' => 'Nessun dato di programma disponibile',
|
||||
'liveTv.noRecordings' => 'Nessuna registrazione programmata',
|
||||
'liveTv.noSubscriptions' => 'Nessuna regola di registrazione',
|
||||
'liveTv.channelNumber' => ({required Object number}) => 'Canale ${number}',
|
||||
'liveTv.live' => 'IN DIRETTA',
|
||||
'liveTv.hd' => 'HD',
|
||||
'liveTv.premiere' => 'NUOVO',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.reloadGuide' => 'Ricarica guida',
|
||||
'liveTv.guideReloaded' => 'Dati della guida ricaricati',
|
||||
'liveTv.allChannels' => 'Tutti i canali',
|
||||
'liveTv.now' => 'Ora',
|
||||
'liveTv.today' => 'Oggi',
|
||||
@@ -1832,6 +1806,8 @@ 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',
|
||||
|
||||
@@ -797,29 +797,17 @@ class _TranslationsLiveTvKo implements TranslationsLiveTvEn {
|
||||
@override String get title => '실시간 TV';
|
||||
@override String get channels => '채널';
|
||||
@override String get guide => '편성표';
|
||||
@override String get recordings => '녹화';
|
||||
@override String get subscriptions => '녹화 규칙';
|
||||
@override String get scheduled => '예약됨';
|
||||
@override String get noChannels => '사용 가능한 채널이 없습니다';
|
||||
@override String get noDvr => '서버에 DVR이 구성되어 있지 않습니다';
|
||||
@override String get tuneFailed => '채널 튜닝에 실패했습니다';
|
||||
@override String get loading => '채널 로딩 중...';
|
||||
@override String get nowPlaying => '현재 재생 중';
|
||||
@override String get record => '녹화';
|
||||
@override String get recordSeries => '시리즈 녹화';
|
||||
@override String get cancelRecording => '녹화 취소';
|
||||
@override String get deleteSubscription => '녹화 규칙 삭제';
|
||||
@override String get deleteSubscriptionConfirm => '이 녹화 규칙을 삭제하시겠습니까?';
|
||||
@override String get subscriptionDeleted => '녹화 규칙이 삭제되었습니다';
|
||||
@override String get noPrograms => '프로그램 데이터가 없습니다';
|
||||
@override String get noRecordings => '예약된 녹화가 없습니다';
|
||||
@override String get noSubscriptions => '녹화 규칙이 없습니다';
|
||||
@override String channelNumber({required Object number}) => '채널 ${number}';
|
||||
@override String get live => '실시간';
|
||||
@override String get hd => 'HD';
|
||||
@override String get premiere => '신규';
|
||||
@override String get reloadGuide => '편성표 새로고침';
|
||||
@override String get guideReloaded => '편성표 데이터가 새로고침되었습니다';
|
||||
@override String get allChannels => '전체 채널';
|
||||
@override String get now => '지금';
|
||||
@override String get today => '오늘';
|
||||
@@ -1797,31 +1785,17 @@ extension on TranslationsKo {
|
||||
'liveTv.title' => '실시간 TV',
|
||||
'liveTv.channels' => '채널',
|
||||
'liveTv.guide' => '편성표',
|
||||
'liveTv.recordings' => '녹화',
|
||||
'liveTv.subscriptions' => '녹화 규칙',
|
||||
'liveTv.scheduled' => '예약됨',
|
||||
'liveTv.noChannels' => '사용 가능한 채널이 없습니다',
|
||||
'liveTv.noDvr' => '서버에 DVR이 구성되어 있지 않습니다',
|
||||
'liveTv.tuneFailed' => '채널 튜닝에 실패했습니다',
|
||||
'liveTv.loading' => '채널 로딩 중...',
|
||||
'liveTv.nowPlaying' => '현재 재생 중',
|
||||
'liveTv.record' => '녹화',
|
||||
'liveTv.recordSeries' => '시리즈 녹화',
|
||||
'liveTv.cancelRecording' => '녹화 취소',
|
||||
'liveTv.deleteSubscription' => '녹화 규칙 삭제',
|
||||
'liveTv.deleteSubscriptionConfirm' => '이 녹화 규칙을 삭제하시겠습니까?',
|
||||
'liveTv.subscriptionDeleted' => '녹화 규칙이 삭제되었습니다',
|
||||
'liveTv.noPrograms' => '프로그램 데이터가 없습니다',
|
||||
'liveTv.noRecordings' => '예약된 녹화가 없습니다',
|
||||
'liveTv.noSubscriptions' => '녹화 규칙이 없습니다',
|
||||
'liveTv.channelNumber' => ({required Object number}) => '채널 ${number}',
|
||||
'liveTv.live' => '실시간',
|
||||
'liveTv.hd' => 'HD',
|
||||
'liveTv.premiere' => '신규',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.reloadGuide' => '편성표 새로고침',
|
||||
'liveTv.guideReloaded' => '편성표 데이터가 새로고침되었습니다',
|
||||
'liveTv.allChannels' => '전체 채널',
|
||||
'liveTv.now' => '지금',
|
||||
'liveTv.today' => '오늘',
|
||||
@@ -1832,6 +1806,8 @@ extension on TranslationsKo {
|
||||
'liveTv.evening' => '저녁',
|
||||
'liveTv.lateNight' => '심야 방송',
|
||||
'liveTv.whatsOn' => '지금 방송 중',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.watchChannel' => '채널 시청',
|
||||
'collections.title' => '컬렉션',
|
||||
'collections.collection' => '컬렉션',
|
||||
|
||||
@@ -797,29 +797,17 @@ class _TranslationsLiveTvNl implements TranslationsLiveTvEn {
|
||||
@override String get title => 'Live TV';
|
||||
@override String get channels => 'Zenders';
|
||||
@override String get guide => 'Gids';
|
||||
@override String get recordings => 'Opnames';
|
||||
@override String get subscriptions => 'Opnameregels';
|
||||
@override String get scheduled => 'Gepland';
|
||||
@override String get noChannels => 'Geen zenders beschikbaar';
|
||||
@override String get noDvr => 'Geen DVR geconfigureerd op een server';
|
||||
@override String get tuneFailed => 'Kan zender niet afstemmen';
|
||||
@override String get loading => 'Zenders laden...';
|
||||
@override String get nowPlaying => 'Nu aan het afspelen';
|
||||
@override String get record => 'Opnemen';
|
||||
@override String get recordSeries => 'Serie opnemen';
|
||||
@override String get cancelRecording => 'Opname annuleren';
|
||||
@override String get deleteSubscription => 'Opnameregel verwijderen';
|
||||
@override String get deleteSubscriptionConfirm => 'Weet je zeker dat je deze opnameregel wilt verwijderen?';
|
||||
@override String get subscriptionDeleted => 'Opnameregel verwijderd';
|
||||
@override String get noPrograms => 'Geen programmagegevens beschikbaar';
|
||||
@override String get noRecordings => 'Geen opnames gepland';
|
||||
@override String get noSubscriptions => 'Geen opnameregels';
|
||||
@override String channelNumber({required Object number}) => 'Kanaal ${number}';
|
||||
@override String get live => 'LIVE';
|
||||
@override String get hd => 'HD';
|
||||
@override String get premiere => 'NIEUW';
|
||||
@override String get reloadGuide => 'Gids herladen';
|
||||
@override String get guideReloaded => 'Gidsgegevens herladen';
|
||||
@override String get allChannels => 'Alle zenders';
|
||||
@override String get now => 'Nu';
|
||||
@override String get today => 'Vandaag';
|
||||
@@ -1797,31 +1785,17 @@ extension on TranslationsNl {
|
||||
'liveTv.title' => 'Live TV',
|
||||
'liveTv.channels' => 'Zenders',
|
||||
'liveTv.guide' => 'Gids',
|
||||
'liveTv.recordings' => 'Opnames',
|
||||
'liveTv.subscriptions' => 'Opnameregels',
|
||||
'liveTv.scheduled' => 'Gepland',
|
||||
'liveTv.noChannels' => 'Geen zenders beschikbaar',
|
||||
'liveTv.noDvr' => 'Geen DVR geconfigureerd op een server',
|
||||
'liveTv.tuneFailed' => 'Kan zender niet afstemmen',
|
||||
'liveTv.loading' => 'Zenders laden...',
|
||||
'liveTv.nowPlaying' => 'Nu aan het afspelen',
|
||||
'liveTv.record' => 'Opnemen',
|
||||
'liveTv.recordSeries' => 'Serie opnemen',
|
||||
'liveTv.cancelRecording' => 'Opname annuleren',
|
||||
'liveTv.deleteSubscription' => 'Opnameregel verwijderen',
|
||||
'liveTv.deleteSubscriptionConfirm' => 'Weet je zeker dat je deze opnameregel wilt verwijderen?',
|
||||
'liveTv.subscriptionDeleted' => 'Opnameregel verwijderd',
|
||||
'liveTv.noPrograms' => 'Geen programmagegevens beschikbaar',
|
||||
'liveTv.noRecordings' => 'Geen opnames gepland',
|
||||
'liveTv.noSubscriptions' => 'Geen opnameregels',
|
||||
'liveTv.channelNumber' => ({required Object number}) => 'Kanaal ${number}',
|
||||
'liveTv.live' => 'LIVE',
|
||||
'liveTv.hd' => 'HD',
|
||||
'liveTv.premiere' => 'NIEUW',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.reloadGuide' => 'Gids herladen',
|
||||
'liveTv.guideReloaded' => 'Gidsgegevens herladen',
|
||||
'liveTv.allChannels' => 'Alle zenders',
|
||||
'liveTv.now' => 'Nu',
|
||||
'liveTv.today' => 'Vandaag',
|
||||
@@ -1832,6 +1806,8 @@ 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',
|
||||
|
||||
@@ -797,29 +797,17 @@ class _TranslationsLiveTvSv implements TranslationsLiveTvEn {
|
||||
@override String get title => 'Live-TV';
|
||||
@override String get channels => 'Kanaler';
|
||||
@override String get guide => 'Programguide';
|
||||
@override String get recordings => 'Inspelningar';
|
||||
@override String get subscriptions => 'Inspelningsregler';
|
||||
@override String get scheduled => 'Schemalagda';
|
||||
@override String get noChannels => 'Inga kanaler tillgängliga';
|
||||
@override String get noDvr => 'Ingen DVR konfigurerad på någon server';
|
||||
@override String get tuneFailed => 'Kunde inte ställa in kanalen';
|
||||
@override String get loading => 'Laddar kanaler...';
|
||||
@override String get nowPlaying => 'Spelas nu';
|
||||
@override String get record => 'Spela in';
|
||||
@override String get recordSeries => 'Spela in serie';
|
||||
@override String get cancelRecording => 'Avbryt inspelning';
|
||||
@override String get deleteSubscription => 'Ta bort inspelningsregel';
|
||||
@override String get deleteSubscriptionConfirm => 'Är du säker på att du vill ta bort denna inspelningsregel?';
|
||||
@override String get subscriptionDeleted => 'Inspelningsregel borttagen';
|
||||
@override String get noPrograms => 'Ingen programdata tillgänglig';
|
||||
@override String get noRecordings => 'Inga inspelningar schemalagda';
|
||||
@override String get noSubscriptions => 'Inga inspelningsregler';
|
||||
@override String channelNumber({required Object number}) => 'Kanal ${number}';
|
||||
@override String get live => 'LIVE';
|
||||
@override String get hd => 'HD';
|
||||
@override String get premiere => 'NY';
|
||||
@override String get reloadGuide => 'Ladda om programguide';
|
||||
@override String get guideReloaded => 'Programdata omladdad';
|
||||
@override String get allChannels => 'Alla kanaler';
|
||||
@override String get now => 'Nu';
|
||||
@override String get today => 'Idag';
|
||||
@@ -1797,31 +1785,17 @@ extension on TranslationsSv {
|
||||
'liveTv.title' => 'Live-TV',
|
||||
'liveTv.channels' => 'Kanaler',
|
||||
'liveTv.guide' => 'Programguide',
|
||||
'liveTv.recordings' => 'Inspelningar',
|
||||
'liveTv.subscriptions' => 'Inspelningsregler',
|
||||
'liveTv.scheduled' => 'Schemalagda',
|
||||
'liveTv.noChannels' => 'Inga kanaler tillgängliga',
|
||||
'liveTv.noDvr' => 'Ingen DVR konfigurerad på någon server',
|
||||
'liveTv.tuneFailed' => 'Kunde inte ställa in kanalen',
|
||||
'liveTv.loading' => 'Laddar kanaler...',
|
||||
'liveTv.nowPlaying' => 'Spelas nu',
|
||||
'liveTv.record' => 'Spela in',
|
||||
'liveTv.recordSeries' => 'Spela in serie',
|
||||
'liveTv.cancelRecording' => 'Avbryt inspelning',
|
||||
'liveTv.deleteSubscription' => 'Ta bort inspelningsregel',
|
||||
'liveTv.deleteSubscriptionConfirm' => 'Är du säker på att du vill ta bort denna inspelningsregel?',
|
||||
'liveTv.subscriptionDeleted' => 'Inspelningsregel borttagen',
|
||||
'liveTv.noPrograms' => 'Ingen programdata tillgänglig',
|
||||
'liveTv.noRecordings' => 'Inga inspelningar schemalagda',
|
||||
'liveTv.noSubscriptions' => 'Inga inspelningsregler',
|
||||
'liveTv.channelNumber' => ({required Object number}) => 'Kanal ${number}',
|
||||
'liveTv.live' => 'LIVE',
|
||||
'liveTv.hd' => 'HD',
|
||||
'liveTv.premiere' => 'NY',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.reloadGuide' => 'Ladda om programguide',
|
||||
'liveTv.guideReloaded' => 'Programdata omladdad',
|
||||
'liveTv.allChannels' => 'Alla kanaler',
|
||||
'liveTv.now' => 'Nu',
|
||||
'liveTv.today' => 'Idag',
|
||||
@@ -1832,6 +1806,8 @@ 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',
|
||||
|
||||
@@ -797,29 +797,17 @@ class _TranslationsLiveTvZh implements TranslationsLiveTvEn {
|
||||
@override String get title => '电视直播';
|
||||
@override String get channels => '频道';
|
||||
@override String get guide => '节目指南';
|
||||
@override String get recordings => '录制';
|
||||
@override String get subscriptions => '录制规则';
|
||||
@override String get scheduled => '已计划';
|
||||
@override String get noChannels => '没有可用的频道';
|
||||
@override String get noDvr => '没有服务器配置了DVR';
|
||||
@override String get tuneFailed => '无法调谐频道';
|
||||
@override String get loading => '正在加载频道...';
|
||||
@override String get nowPlaying => '正在播放';
|
||||
@override String get record => '录制';
|
||||
@override String get recordSeries => '录制系列';
|
||||
@override String get cancelRecording => '取消录制';
|
||||
@override String get deleteSubscription => '删除录制规则';
|
||||
@override String get deleteSubscriptionConfirm => '确定要删除此录制规则吗?';
|
||||
@override String get subscriptionDeleted => '录制规则已删除';
|
||||
@override String get noPrograms => '没有可用的节目数据';
|
||||
@override String get noRecordings => '没有计划的录制';
|
||||
@override String get noSubscriptions => '没有录制规则';
|
||||
@override String channelNumber({required Object number}) => '频道 ${number}';
|
||||
@override String get live => '直播';
|
||||
@override String get hd => '高清';
|
||||
@override String get premiere => '新';
|
||||
@override String get reloadGuide => '重新加载节目指南';
|
||||
@override String get guideReloaded => '节目指南已重新加载';
|
||||
@override String get allChannels => '所有频道';
|
||||
@override String get now => '现在';
|
||||
@override String get today => '今天';
|
||||
@@ -1797,31 +1785,17 @@ extension on TranslationsZh {
|
||||
'liveTv.title' => '电视直播',
|
||||
'liveTv.channels' => '频道',
|
||||
'liveTv.guide' => '节目指南',
|
||||
'liveTv.recordings' => '录制',
|
||||
'liveTv.subscriptions' => '录制规则',
|
||||
'liveTv.scheduled' => '已计划',
|
||||
'liveTv.noChannels' => '没有可用的频道',
|
||||
'liveTv.noDvr' => '没有服务器配置了DVR',
|
||||
'liveTv.tuneFailed' => '无法调谐频道',
|
||||
'liveTv.loading' => '正在加载频道...',
|
||||
'liveTv.nowPlaying' => '正在播放',
|
||||
'liveTv.record' => '录制',
|
||||
'liveTv.recordSeries' => '录制系列',
|
||||
'liveTv.cancelRecording' => '取消录制',
|
||||
'liveTv.deleteSubscription' => '删除录制规则',
|
||||
'liveTv.deleteSubscriptionConfirm' => '确定要删除此录制规则吗?',
|
||||
'liveTv.subscriptionDeleted' => '录制规则已删除',
|
||||
'liveTv.noPrograms' => '没有可用的节目数据',
|
||||
'liveTv.noRecordings' => '没有计划的录制',
|
||||
'liveTv.noSubscriptions' => '没有录制规则',
|
||||
'liveTv.channelNumber' => ({required Object number}) => '频道 ${number}',
|
||||
'liveTv.live' => '直播',
|
||||
'liveTv.hd' => '高清',
|
||||
'liveTv.premiere' => '新',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.reloadGuide' => '重新加载节目指南',
|
||||
'liveTv.guideReloaded' => '节目指南已重新加载',
|
||||
'liveTv.allChannels' => '所有频道',
|
||||
'liveTv.now' => '现在',
|
||||
'liveTv.today' => '今天',
|
||||
@@ -1832,6 +1806,8 @@ extension on TranslationsZh {
|
||||
'liveTv.evening' => '晚上',
|
||||
'liveTv.lateNight' => '深夜',
|
||||
'liveTv.whatsOn' => '正在播出',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'liveTv.watchChannel' => '观看频道',
|
||||
'downloads.title' => '下载',
|
||||
'downloads.manage' => '管理',
|
||||
|
||||
@@ -555,29 +555,17 @@
|
||||
"title": "Live-TV",
|
||||
"channels": "Kanaler",
|
||||
"guide": "Programguide",
|
||||
"recordings": "Inspelningar",
|
||||
"subscriptions": "Inspelningsregler",
|
||||
"scheduled": "Schemalagda",
|
||||
"noChannels": "Inga kanaler tillgängliga",
|
||||
"noDvr": "Ingen DVR konfigurerad på någon server",
|
||||
"tuneFailed": "Kunde inte ställa in kanalen",
|
||||
"loading": "Laddar kanaler...",
|
||||
"nowPlaying": "Spelas nu",
|
||||
"record": "Spela in",
|
||||
"recordSeries": "Spela in serie",
|
||||
"cancelRecording": "Avbryt inspelning",
|
||||
"deleteSubscription": "Ta bort inspelningsregel",
|
||||
"deleteSubscriptionConfirm": "Är du säker på att du vill ta bort denna inspelningsregel?",
|
||||
"subscriptionDeleted": "Inspelningsregel borttagen",
|
||||
"noPrograms": "Ingen programdata tillgänglig",
|
||||
"noRecordings": "Inga inspelningar schemalagda",
|
||||
"noSubscriptions": "Inga inspelningsregler",
|
||||
"channelNumber": "Kanal ${number}",
|
||||
"live": "LIVE",
|
||||
"hd": "HD",
|
||||
"premiere": "NY",
|
||||
"reloadGuide": "Ladda om programguide",
|
||||
"guideReloaded": "Programdata omladdad",
|
||||
"allChannels": "Alla kanaler",
|
||||
"now": "Nu",
|
||||
"today": "Idag",
|
||||
|
||||
@@ -555,29 +555,17 @@
|
||||
"title": "电视直播",
|
||||
"channels": "频道",
|
||||
"guide": "节目指南",
|
||||
"recordings": "录制",
|
||||
"subscriptions": "录制规则",
|
||||
"scheduled": "已计划",
|
||||
"noChannels": "没有可用的频道",
|
||||
"noDvr": "没有服务器配置了DVR",
|
||||
"tuneFailed": "无法调谐频道",
|
||||
"loading": "正在加载频道...",
|
||||
"nowPlaying": "正在播放",
|
||||
"record": "录制",
|
||||
"recordSeries": "录制系列",
|
||||
"cancelRecording": "取消录制",
|
||||
"deleteSubscription": "删除录制规则",
|
||||
"deleteSubscriptionConfirm": "确定要删除此录制规则吗?",
|
||||
"subscriptionDeleted": "录制规则已删除",
|
||||
"noPrograms": "没有可用的节目数据",
|
||||
"noRecordings": "没有计划的录制",
|
||||
"noSubscriptions": "没有录制规则",
|
||||
"channelNumber": "频道 ${number}",
|
||||
"live": "直播",
|
||||
"hd": "高清",
|
||||
"premiere": "新",
|
||||
"reloadGuide": "重新加载节目指南",
|
||||
"guideReloaded": "节目指南已重新加载",
|
||||
"allChannels": "所有频道",
|
||||
"now": "现在",
|
||||
"today": "今天",
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
/// Represents an upcoming scheduled recording from the DVR
|
||||
class ScheduledRecording {
|
||||
final String? key;
|
||||
final String? ratingKey;
|
||||
final String? guid;
|
||||
final String title;
|
||||
final String? summary;
|
||||
final String? type;
|
||||
final int? beginsAt;
|
||||
final int? endsAt;
|
||||
final String? grandparentTitle;
|
||||
final String? parentTitle;
|
||||
final int? index;
|
||||
final int? parentIndex;
|
||||
final String? thumb;
|
||||
final String? art;
|
||||
final String? channelIdentifier;
|
||||
final String? channelCallSign;
|
||||
final String? subscriptionID;
|
||||
final String? status;
|
||||
|
||||
ScheduledRecording({
|
||||
this.key,
|
||||
this.ratingKey,
|
||||
this.guid,
|
||||
required this.title,
|
||||
this.summary,
|
||||
this.type,
|
||||
this.beginsAt,
|
||||
this.endsAt,
|
||||
this.grandparentTitle,
|
||||
this.parentTitle,
|
||||
this.index,
|
||||
this.parentIndex,
|
||||
this.thumb,
|
||||
this.art,
|
||||
this.channelIdentifier,
|
||||
this.channelCallSign,
|
||||
this.subscriptionID,
|
||||
this.status,
|
||||
});
|
||||
|
||||
factory ScheduledRecording.fromJson(Map<String, dynamic> json) {
|
||||
return ScheduledRecording(
|
||||
key: json['key'] as String?,
|
||||
ratingKey: json['ratingKey'] as String?,
|
||||
guid: json['guid'] as String?,
|
||||
title: json['title'] as String? ?? 'Unknown',
|
||||
summary: json['summary'] as String?,
|
||||
type: json['type'] as String?,
|
||||
beginsAt: (json['beginsAt'] as num?)?.toInt(),
|
||||
endsAt: (json['endsAt'] as num?)?.toInt(),
|
||||
grandparentTitle: json['grandparentTitle'] as String?,
|
||||
parentTitle: json['parentTitle'] as String?,
|
||||
index: (json['index'] as num?)?.toInt(),
|
||||
parentIndex: (json['parentIndex'] as num?)?.toInt(),
|
||||
thumb: json['thumb'] as String?,
|
||||
art: json['art'] as String?,
|
||||
channelIdentifier: json['channelIdentifier'] as String?,
|
||||
channelCallSign: json['channelCallSign'] as String?,
|
||||
subscriptionID: json['subscriptionID'] as String?,
|
||||
status: json['status'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
/// Start time as DateTime
|
||||
DateTime? get startTime => beginsAt != null ? DateTime.fromMillisecondsSinceEpoch(beginsAt! * 1000) : null;
|
||||
|
||||
/// End time as DateTime
|
||||
DateTime? get endTime => endsAt != null ? DateTime.fromMillisecondsSinceEpoch(endsAt! * 1000) : null;
|
||||
|
||||
/// Duration in minutes
|
||||
int get durationMinutes {
|
||||
if (beginsAt == null || endsAt == null) return 0;
|
||||
return ((endsAt! - beginsAt!) / 60).round();
|
||||
}
|
||||
|
||||
/// Display title including series info for episodes
|
||||
String get displayTitle {
|
||||
if (grandparentTitle != null && index != null) {
|
||||
final seasonEpisode = parentIndex != null ? 'S${parentIndex}E$index' : 'E$index';
|
||||
return '$grandparentTitle - $seasonEpisode - $title';
|
||||
}
|
||||
return title;
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
/// Represents a DVR recording subscription (recording rule)
|
||||
class LiveTvSubscription {
|
||||
final String key;
|
||||
final String? ratingKey;
|
||||
final String? guid;
|
||||
final String title;
|
||||
final String? summary;
|
||||
final String? type;
|
||||
final String? thumb;
|
||||
final String? art;
|
||||
final int? targetLibrarySectionID;
|
||||
final int? targetSectionID;
|
||||
final int? createdAt;
|
||||
final List<SubscriptionSetting> settings;
|
||||
|
||||
// Multi-server support
|
||||
final String? serverId;
|
||||
|
||||
LiveTvSubscription({
|
||||
required this.key,
|
||||
this.ratingKey,
|
||||
this.guid,
|
||||
required this.title,
|
||||
this.summary,
|
||||
this.type,
|
||||
this.thumb,
|
||||
this.art,
|
||||
this.targetLibrarySectionID,
|
||||
this.targetSectionID,
|
||||
this.createdAt,
|
||||
this.settings = const [],
|
||||
this.serverId,
|
||||
});
|
||||
|
||||
factory LiveTvSubscription.fromJson(Map<String, dynamic> json) {
|
||||
final settingsList = <SubscriptionSetting>[];
|
||||
if (json['Setting'] != null) {
|
||||
for (final item in json['Setting'] as List) {
|
||||
try {
|
||||
settingsList.add(SubscriptionSetting.fromJson(item as Map<String, dynamic>));
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
return LiveTvSubscription(
|
||||
key: json['key'] as String? ?? '',
|
||||
ratingKey: json['ratingKey'] as String?,
|
||||
guid: json['guid'] as String?,
|
||||
title: json['title'] as String? ?? 'Unknown',
|
||||
summary: json['summary'] as String?,
|
||||
type: json['type'] as String?,
|
||||
thumb: json['thumb'] as String?,
|
||||
art: json['art'] as String?,
|
||||
targetLibrarySectionID: (json['targetLibrarySectionID'] as num?)?.toInt(),
|
||||
targetSectionID: (json['targetSectionID'] as num?)?.toInt(),
|
||||
createdAt: (json['createdAt'] as num?)?.toInt(),
|
||||
settings: settingsList,
|
||||
);
|
||||
}
|
||||
|
||||
/// Creation time as DateTime
|
||||
DateTime? get createdAtTime => createdAt != null ? DateTime.fromMillisecondsSinceEpoch(createdAt! * 1000) : null;
|
||||
}
|
||||
|
||||
/// Represents a setting within a DVR subscription
|
||||
class SubscriptionSetting {
|
||||
final String id;
|
||||
final String? label;
|
||||
final String? summary;
|
||||
final String type; // e.g., "bool", "enum", "int"
|
||||
final String? value;
|
||||
final String? defaultValue;
|
||||
final bool? hidden;
|
||||
final bool? advanced;
|
||||
final List<SubscriptionSettingOption>? enumValues;
|
||||
|
||||
SubscriptionSetting({
|
||||
required this.id,
|
||||
this.label,
|
||||
this.summary,
|
||||
required this.type,
|
||||
this.value,
|
||||
this.defaultValue,
|
||||
this.hidden,
|
||||
this.advanced,
|
||||
this.enumValues,
|
||||
});
|
||||
|
||||
factory SubscriptionSetting.fromJson(Map<String, dynamic> json) {
|
||||
List<SubscriptionSettingOption>? options;
|
||||
if (json['enumValues'] != null) {
|
||||
final parts = (json['enumValues'] as String).split('|');
|
||||
options = parts.map((part) {
|
||||
final kv = part.split(':');
|
||||
return SubscriptionSettingOption(value: kv.first, label: kv.length > 1 ? kv[1] : kv.first);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
return SubscriptionSetting(
|
||||
id: json['id'] as String? ?? '',
|
||||
label: json['label'] as String?,
|
||||
summary: json['summary'] as String?,
|
||||
type: json['type'] as String? ?? 'text',
|
||||
value: json['value']?.toString(),
|
||||
defaultValue: json['default']?.toString(),
|
||||
hidden: json['hidden'] == true || json['hidden'] == 1,
|
||||
advanced: json['advanced'] == true || json['advanced'] == 1,
|
||||
enumValues: options,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an option in an enum-type subscription setting
|
||||
class SubscriptionSettingOption {
|
||||
final String value;
|
||||
final String label;
|
||||
|
||||
SubscriptionSettingOption({required this.value, required this.label});
|
||||
}
|
||||
@@ -1,433 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../focus/focusable_button.dart';
|
||||
import '../../focus/focusable_wrapper.dart';
|
||||
import '../../focus/key_event_utils.dart';
|
||||
import '../../i18n/strings.g.dart';
|
||||
import '../../models/livetv_scheduled_recording.dart';
|
||||
import '../../models/livetv_subscription.dart';
|
||||
import '../../providers/multi_server_provider.dart';
|
||||
import '../../utils/app_logger.dart';
|
||||
import '../../utils/formatters.dart';
|
||||
import '../../utils/snackbar_helper.dart';
|
||||
import '../../widgets/app_icon.dart';
|
||||
import '../../widgets/desktop_app_bar.dart';
|
||||
|
||||
/// Screen for managing DVR recording subscriptions and scheduled recordings
|
||||
class DvrRecordingsScreen extends StatefulWidget {
|
||||
const DvrRecordingsScreen({super.key});
|
||||
|
||||
@override
|
||||
State<DvrRecordingsScreen> createState() => _DvrRecordingsScreenState();
|
||||
}
|
||||
|
||||
class _DvrRecordingsScreenState extends State<DvrRecordingsScreen> with SingleTickerProviderStateMixin {
|
||||
late TabController _tabController;
|
||||
|
||||
List<LiveTvSubscription> _subscriptions = [];
|
||||
List<ScheduledRecording> _scheduled = [];
|
||||
bool _isLoading = true;
|
||||
String? _error;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(length: 2, vsync: this);
|
||||
_loadData();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tabController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _loadData() async {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_error = null;
|
||||
});
|
||||
|
||||
try {
|
||||
final multiServer = context.read<MultiServerProvider>();
|
||||
final liveTvServers = multiServer.liveTvServers;
|
||||
|
||||
if (liveTvServers.isEmpty) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_error = t.liveTv.noDvr;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final allSubscriptions = <LiveTvSubscription>[];
|
||||
final allScheduled = <ScheduledRecording>[];
|
||||
|
||||
for (final serverInfo in liveTvServers) {
|
||||
final client = multiServer.getClientForServer(serverInfo.serverId);
|
||||
if (client == null) continue;
|
||||
|
||||
final subs = await client.getSubscriptions();
|
||||
allSubscriptions.addAll(subs);
|
||||
|
||||
final scheduled = await client.getScheduledRecordings();
|
||||
allScheduled.addAll(scheduled);
|
||||
}
|
||||
|
||||
// Sort scheduled by start time
|
||||
allScheduled.sort((a, b) => (a.beginsAt ?? 0).compareTo(b.beginsAt ?? 0));
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_subscriptions = allSubscriptions;
|
||||
_scheduled = allScheduled;
|
||||
_isLoading = false;
|
||||
});
|
||||
} catch (e) {
|
||||
appLogger.e('Failed to load DVR data', error: e);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_error = e.toString();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _deleteSubscription(LiveTvSubscription subscription) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(t.liveTv.deleteSubscription),
|
||||
content: Text(t.liveTv.deleteSubscriptionConfirm),
|
||||
actions: [
|
||||
FocusableButton(
|
||||
autofocus: true,
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: Text(t.common.cancel),
|
||||
),
|
||||
),
|
||||
FocusableButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
child: FilledButton(onPressed: () => Navigator.of(context).pop(true), child: Text(t.common.delete)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (confirmed != true || !mounted) return;
|
||||
|
||||
final multiServer = context.read<MultiServerProvider>();
|
||||
final client = subscription.serverId != null ? multiServer.getClientForServer(subscription.serverId!) : null;
|
||||
|
||||
if (client != null) {
|
||||
final success = await client.deleteSubscription(subscription.key);
|
||||
if (success && mounted) {
|
||||
showSnackBar(context, t.liveTv.subscriptionDeleted);
|
||||
await _loadData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _editSubscription(LiveTvSubscription subscription) async {
|
||||
// Filter to visible settings only
|
||||
final editableSettings = subscription.settings.where((s) => s.hidden != true).toList();
|
||||
|
||||
if (editableSettings.isEmpty) return;
|
||||
|
||||
final prefs = <String, String>{};
|
||||
for (final setting in editableSettings) {
|
||||
prefs[setting.id] = setting.value ?? setting.defaultValue ?? '';
|
||||
}
|
||||
|
||||
final result = await showDialog<Map<String, String>?>(
|
||||
context: context,
|
||||
builder: (dialogContext) =>
|
||||
_SubscriptionEditDialog(subscription: subscription, settings: editableSettings, initialPrefs: prefs),
|
||||
);
|
||||
|
||||
if (result == null || !mounted) return;
|
||||
|
||||
final multiServer = context.read<MultiServerProvider>();
|
||||
final client = subscription.serverId != null ? multiServer.getClientForServer(subscription.serverId!) : null;
|
||||
|
||||
if (client != null) {
|
||||
final success = await client.editSubscription(subscription.key, result);
|
||||
if (success) {
|
||||
await _loadData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Focus(
|
||||
canRequestFocus: false,
|
||||
onKeyEvent: (_, event) => handleBackKeyNavigation(context, event),
|
||||
child: Scaffold(
|
||||
body: NestedScrollView(
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
CustomAppBar(
|
||||
title: Text(t.liveTv.recordings),
|
||||
pinned: true,
|
||||
bottom: TabBar(
|
||||
controller: _tabController,
|
||||
tabs: [
|
||||
Tab(text: t.liveTv.subscriptions),
|
||||
Tab(text: t.liveTv.scheduled),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
body: _buildRecordingsBody(theme),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRecordingsBody(ThemeData theme) {
|
||||
if (_isLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (_error != null) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(_error!, style: theme.textTheme.bodyLarge),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton.icon(
|
||||
onPressed: _loadData,
|
||||
icon: const AppIcon(Symbols.refresh_rounded),
|
||||
label: Text(t.common.retry),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return TabBarView(controller: _tabController, children: [_buildSubscriptionsTab(theme), _buildScheduledTab(theme)]);
|
||||
}
|
||||
|
||||
Widget _buildSubscriptionsTab(ThemeData theme) {
|
||||
if (_subscriptions.isEmpty) {
|
||||
return Center(child: Text(t.liveTv.noSubscriptions));
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
itemCount: _subscriptions.length,
|
||||
itemBuilder: (context, index) {
|
||||
final sub = _subscriptions[index];
|
||||
return FocusableWrapper(
|
||||
autofocus: index == 0,
|
||||
autoScroll: true,
|
||||
useComfortableZone: true,
|
||||
onSelect: () => _editSubscription(sub),
|
||||
onBack: () => Navigator.pop(context),
|
||||
child: _buildSubscriptionCard(sub, theme),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSubscriptionCard(LiveTvSubscription subscription, ThemeData theme) {
|
||||
return ExcludeFocus(
|
||||
child: Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
child: ListTile(
|
||||
leading: const AppIcon(Symbols.fiber_dvr_rounded, size: 32),
|
||||
title: Text(subscription.title, maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||
subtitle: subscription.type != null
|
||||
? Text(
|
||||
subscription.type!,
|
||||
style: theme.textTheme.bodySmall?.copyWith(color: theme.colorScheme.onSurfaceVariant),
|
||||
)
|
||||
: null,
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (subscription.settings.isNotEmpty)
|
||||
IconButton(
|
||||
icon: const AppIcon(Symbols.settings_rounded),
|
||||
onPressed: () => _editSubscription(subscription),
|
||||
),
|
||||
IconButton(
|
||||
icon: AppIcon(Symbols.delete_rounded, color: theme.colorScheme.error),
|
||||
onPressed: () => _deleteSubscription(subscription),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildScheduledTab(ThemeData theme) {
|
||||
if (_scheduled.isEmpty) {
|
||||
return Center(child: Text(t.liveTv.noRecordings));
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
itemCount: _scheduled.length,
|
||||
itemBuilder: (context, index) {
|
||||
final recording = _scheduled[index];
|
||||
return FocusableWrapper(
|
||||
autofocus: index == 0,
|
||||
autoScroll: true,
|
||||
useComfortableZone: true,
|
||||
onBack: () => Navigator.pop(context),
|
||||
child: _buildScheduledCard(recording, theme),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildScheduledCard(ScheduledRecording recording, ThemeData theme) {
|
||||
final startTime = recording.startTime;
|
||||
final timeStr = startTime != null
|
||||
? '${startTime.month}/${startTime.day} ${startTime.hour.toString().padLeft(2, '0')}:${startTime.minute.toString().padLeft(2, '0')}'
|
||||
: '';
|
||||
|
||||
return ExcludeFocus(
|
||||
child: Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
child: ListTile(
|
||||
leading: const AppIcon(Symbols.fiber_manual_record_rounded, size: 32, color: Colors.red),
|
||||
title: Text(recording.displayTitle, maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||
subtitle: Text(
|
||||
[
|
||||
if (recording.channelCallSign != null) recording.channelCallSign!,
|
||||
timeStr,
|
||||
if (recording.durationMinutes > 0) formatDurationTextual(recording.durationMinutes * 60000),
|
||||
].join(' · '),
|
||||
style: theme.textTheme.bodySmall?.copyWith(color: theme.colorScheme.onSurfaceVariant),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Dialog for editing subscription settings
|
||||
class _SubscriptionEditDialog extends StatefulWidget {
|
||||
final LiveTvSubscription subscription;
|
||||
final List<SubscriptionSetting> settings;
|
||||
final Map<String, String> initialPrefs;
|
||||
|
||||
const _SubscriptionEditDialog({required this.subscription, required this.settings, required this.initialPrefs});
|
||||
|
||||
@override
|
||||
State<_SubscriptionEditDialog> createState() => _SubscriptionEditDialogState();
|
||||
}
|
||||
|
||||
class _SubscriptionEditDialogState extends State<_SubscriptionEditDialog> {
|
||||
late Map<String, String> _prefs;
|
||||
final Map<String, TextEditingController> _textControllers = {};
|
||||
final _saveFocusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_prefs = Map.from(widget.initialPrefs);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
for (final controller in _textControllers.values) {
|
||||
controller.dispose();
|
||||
}
|
||||
_saveFocusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(widget.subscription.title),
|
||||
content: SizedBox(
|
||||
width: 400,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: widget.settings.map((setting) {
|
||||
return _buildSettingRow(setting);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
FocusableButton(
|
||||
onPressed: () => Navigator.of(context).pop(null),
|
||||
child: TextButton(onPressed: () => Navigator.of(context).pop(null), child: Text(t.common.cancel)),
|
||||
),
|
||||
FocusableButton(
|
||||
focusNode: _saveFocusNode,
|
||||
onPressed: () => Navigator.of(context).pop(_prefs),
|
||||
child: FilledButton(
|
||||
onPressed: () => Navigator.of(context).pop(_prefs),
|
||||
child: Text(t.common.save),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSettingRow(SubscriptionSetting setting) {
|
||||
final value = _prefs[setting.id] ?? setting.defaultValue ?? '';
|
||||
|
||||
if (setting.type == 'bool') {
|
||||
return SwitchListTile(
|
||||
title: Text(setting.label ?? setting.id),
|
||||
subtitle: setting.summary != null ? Text(setting.summary!) : null,
|
||||
value: value == '1' || value == 'true',
|
||||
onChanged: (newValue) {
|
||||
setState(() {
|
||||
_prefs[setting.id] = newValue ? '1' : '0';
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (setting.type == 'enum' && setting.enumValues != null) {
|
||||
return ListTile(
|
||||
title: Text(setting.label ?? setting.id),
|
||||
subtitle: setting.summary != null ? Text(setting.summary!) : null,
|
||||
trailing: DropdownButton<String>(
|
||||
value: setting.enumValues!.any((e) => e.value == value) ? value : null,
|
||||
items: setting.enumValues!.map((option) {
|
||||
return DropdownMenuItem(value: option.value, child: Text(option.label));
|
||||
}).toList(),
|
||||
onChanged: (newValue) {
|
||||
if (newValue != null) {
|
||||
setState(() {
|
||||
_prefs[setting.id] = newValue;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Default: text field
|
||||
final controller = _textControllers.putIfAbsent(setting.id, () => TextEditingController(text: value));
|
||||
return ListTile(
|
||||
title: Text(setting.label ?? setting.id),
|
||||
subtitle: TextField(
|
||||
controller: controller,
|
||||
textInputAction: TextInputAction.done,
|
||||
onChanged: (newValue) {
|
||||
_prefs[setting.id] = newValue;
|
||||
},
|
||||
onSubmitted: (_) => _saveFocusNode.requestFocus(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ import '../../utils/app_logger.dart';
|
||||
import '../../utils/platform_detector.dart';
|
||||
import '../../widgets/app_icon.dart';
|
||||
import '../../widgets/focusable_tab_chip.dart';
|
||||
import 'dvr_recordings_screen.dart';
|
||||
import 'tabs/guide_tab.dart';
|
||||
import 'tabs/whats_on_tab.dart';
|
||||
|
||||
@@ -34,9 +33,7 @@ class _LiveTvScreenState extends State<LiveTvScreen>
|
||||
|
||||
// App bar action button focus
|
||||
final _refreshButtonFocusNode = FocusNode(debugLabel: 'RefreshButton');
|
||||
final _dvrButtonFocusNode = FocusNode(debugLabel: 'DvrButton');
|
||||
bool _isRefreshFocused = false;
|
||||
bool _isDvrFocused = false;
|
||||
|
||||
List<LiveTvChannel> _channels = [];
|
||||
bool _isLoading = true;
|
||||
@@ -51,7 +48,6 @@ class _LiveTvScreenState extends State<LiveTvScreen>
|
||||
suppressAutoFocus = true;
|
||||
initTabNavigation();
|
||||
_refreshButtonFocusNode.addListener(_onRefreshFocusChange);
|
||||
_dvrButtonFocusNode.addListener(_onDvrFocusChange);
|
||||
_loadChannels();
|
||||
}
|
||||
|
||||
@@ -61,8 +57,6 @@ class _LiveTvScreenState extends State<LiveTvScreen>
|
||||
_whatsOnTabFocusNode.dispose();
|
||||
_refreshButtonFocusNode.removeListener(_onRefreshFocusChange);
|
||||
_refreshButtonFocusNode.dispose();
|
||||
_dvrButtonFocusNode.removeListener(_onDvrFocusChange);
|
||||
_dvrButtonFocusNode.dispose();
|
||||
disposeTabNavigation();
|
||||
super.dispose();
|
||||
}
|
||||
@@ -71,10 +65,6 @@ class _LiveTvScreenState extends State<LiveTvScreen>
|
||||
if (mounted) setState(() => _isRefreshFocused = _refreshButtonFocusNode.hasFocus);
|
||||
}
|
||||
|
||||
void _onDvrFocusChange() {
|
||||
if (mounted) setState(() => _isDvrFocused = _dvrButtonFocusNode.hasFocus);
|
||||
}
|
||||
|
||||
@override
|
||||
void onTabChanged() {
|
||||
if (!tabController.indexIsChanging) {
|
||||
@@ -204,10 +194,6 @@ class _LiveTvScreenState extends State<LiveTvScreen>
|
||||
}
|
||||
}
|
||||
|
||||
void _openRecordings() {
|
||||
Navigator.of(context).push(MaterialPageRoute(builder: (_) => const DvrRecordingsScreen()));
|
||||
}
|
||||
|
||||
void _focusCurrentTab() {
|
||||
if (tabController.index == 0) {
|
||||
_guideTabKey.currentState?.focusContent();
|
||||
@@ -235,7 +221,6 @@ class _LiveTvScreenState extends State<LiveTvScreen>
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
if (key.isRightKey) {
|
||||
_dvrButtonFocusNode.requestFocus();
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
if (key.isDownKey) {
|
||||
@@ -252,28 +237,6 @@ class _LiveTvScreenState extends State<LiveTvScreen>
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
KeyEventResult _handleDvrKeyEvent(FocusNode _, KeyEvent event) {
|
||||
if (!event.isActionable) return KeyEventResult.ignored;
|
||||
final key = event.logicalKey;
|
||||
|
||||
if (key.isLeftKey) {
|
||||
_refreshButtonFocusNode.requestFocus();
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
if (key.isRightKey || key.isUpKey) {
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
if (key.isDownKey) {
|
||||
_focusCurrentTab();
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
if (key.isSelectKey) {
|
||||
_openRecordings();
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tab chips
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -355,21 +318,6 @@ class _LiveTvScreenState extends State<LiveTvScreen>
|
||||
),
|
||||
),
|
||||
),
|
||||
Focus(
|
||||
focusNode: _dvrButtonFocusNode,
|
||||
onKeyEvent: _handleDvrKeyEvent,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: _isDvrFocused ? Colors.white.withValues(alpha: 0.2) : Colors.transparent,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
child: IconButton(
|
||||
icon: const AppIcon(Symbols.fiber_dvr_rounded),
|
||||
tooltip: t.liveTv.recordings,
|
||||
onPressed: _openRecordings,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: _buildLiveTvBody(theme, useSideNav),
|
||||
|
||||
@@ -68,8 +68,6 @@ class _ProgramDetailsSheetContentState extends State<_ProgramDetailsSheetContent
|
||||
void _buildButtonFocusNodes() {
|
||||
int count = 0;
|
||||
if (widget.program.isCurrentlyAiring && widget.onTuneChannel != null) count++;
|
||||
// TODO: Implement recording
|
||||
// count++; // Record button
|
||||
if (!widget.program.isCurrentlyAiring && widget.onTuneChannel != null) count++;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
@@ -121,38 +119,6 @@ class _ProgramDetailsSheetContentState extends State<_ProgramDetailsSheetContent
|
||||
buttonIndex++;
|
||||
}
|
||||
|
||||
// TODO: Implement recording
|
||||
// if (program.isCurrentlyAiring && widget.onTuneChannel != null) {
|
||||
// buttons.add(const SizedBox(width: 8));
|
||||
// }
|
||||
// // Record button
|
||||
// {
|
||||
// final idx = buttonIndex;
|
||||
// buttons.add(
|
||||
// FocusableWrapper(
|
||||
// focusNode: _buttonFocusNodes[idx],
|
||||
// onSelect: () {
|
||||
// Navigator.of(context).pop();
|
||||
// },
|
||||
// onNavigateLeft: idx > 0 ? () => _focusButton(idx - 1) : null,
|
||||
// onNavigateRight: idx < _buttonFocusNodes.length - 1 ? () => _focusButton(idx + 1) : null,
|
||||
// onBack: () => Navigator.of(context).pop(),
|
||||
// borderRadius: 100,
|
||||
// useBackgroundFocus: true,
|
||||
// disableScale: true,
|
||||
// child: OutlinedButton.icon(
|
||||
// style: OutlinedButton.styleFrom(tapTargetSize: MaterialTapTargetSize.shrinkWrap),
|
||||
// onPressed: () {
|
||||
// Navigator.of(context).pop();
|
||||
// },
|
||||
// icon: const AppIcon(Symbols.fiber_manual_record_rounded),
|
||||
// label: Text(t.liveTv.record),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// buttonIndex++;
|
||||
// }
|
||||
|
||||
if (!program.isCurrentlyAiring && widget.onTuneChannel != null) {
|
||||
buttons.add(const SizedBox(width: 8));
|
||||
final idx = buttonIndex;
|
||||
|
||||
@@ -9,8 +9,6 @@ import '../models/livetv_channel.dart';
|
||||
import '../models/livetv_dvr.dart';
|
||||
import '../models/livetv_hub_result.dart';
|
||||
import '../models/livetv_program.dart';
|
||||
import '../models/livetv_scheduled_recording.dart';
|
||||
import '../models/livetv_subscription.dart';
|
||||
import '../models/plex_config.dart';
|
||||
import '../models/play_queue_response.dart';
|
||||
import '../models/plex_file_info.dart';
|
||||
@@ -2408,11 +2406,6 @@ class PlexClient {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reload the DVR guide data
|
||||
Future<bool> reloadGuide(String dvrKey) {
|
||||
return _wrapBoolApiCall(() => _dio.post('/livetv/dvrs/$dvrKey/reloadGuide'), 'Failed to reload guide');
|
||||
}
|
||||
|
||||
/// Get active live TV sessions
|
||||
Future<List<PlexMetadata>> getLiveTvSessions() {
|
||||
return _wrapListApiCall<PlexMetadata>(
|
||||
@@ -2422,113 +2415,6 @@ class PlexClient {
|
||||
);
|
||||
}
|
||||
|
||||
/// Get all DVR recording subscriptions
|
||||
Future<List<LiveTvSubscription>> getSubscriptions() async {
|
||||
return _wrapListApiCall<LiveTvSubscription>(() => _dio.get('/media/subscriptions'), (response) {
|
||||
final container = _getMediaContainer(response);
|
||||
if (container != null && container['MediaSubscription'] != null) {
|
||||
return (container['MediaSubscription'] as List).map((json) {
|
||||
final sub = LiveTvSubscription.fromJson(json as Map<String, dynamic>);
|
||||
return LiveTvSubscription(
|
||||
key: sub.key,
|
||||
ratingKey: sub.ratingKey,
|
||||
guid: sub.guid,
|
||||
title: sub.title,
|
||||
summary: sub.summary,
|
||||
type: sub.type,
|
||||
thumb: sub.thumb,
|
||||
art: sub.art,
|
||||
targetLibrarySectionID: sub.targetLibrarySectionID,
|
||||
targetSectionID: sub.targetSectionID,
|
||||
createdAt: sub.createdAt,
|
||||
settings: sub.settings,
|
||||
serverId: serverId,
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
return [];
|
||||
}, 'Failed to get subscriptions');
|
||||
}
|
||||
|
||||
/// Create a DVR recording subscription
|
||||
Future<LiveTvSubscription?> createSubscription({
|
||||
required String type,
|
||||
required int targetSectionID,
|
||||
required int targetLibrarySectionID,
|
||||
Map<String, String>? prefs,
|
||||
String? hint,
|
||||
String? uri,
|
||||
}) async {
|
||||
try {
|
||||
final queryParams = <String, dynamic>{
|
||||
'type': type,
|
||||
'targetSectionID': targetSectionID,
|
||||
'targetLibrarySectionID': targetLibrarySectionID,
|
||||
};
|
||||
if (hint != null) queryParams['hint'] = hint;
|
||||
if (uri != null) queryParams['uri'] = uri;
|
||||
if (prefs != null) {
|
||||
for (final entry in prefs.entries) {
|
||||
queryParams['prefs[${entry.key}]'] = entry.value;
|
||||
}
|
||||
}
|
||||
|
||||
final response = await _dio.post('/media/subscriptions', queryParameters: queryParams);
|
||||
final container = _getMediaContainer(response);
|
||||
if (container != null && container['MediaSubscription'] != null) {
|
||||
final subs = container['MediaSubscription'] as List;
|
||||
if (subs.isNotEmpty) {
|
||||
return LiveTvSubscription.fromJson(subs.first as Map<String, dynamic>);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
appLogger.e('Failed to create subscription', error: e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete a DVR recording subscription
|
||||
Future<bool> deleteSubscription(String subscriptionId) {
|
||||
return _wrapBoolApiCall(() => _dio.delete('/media/subscriptions/$subscriptionId'), 'Failed to delete subscription');
|
||||
}
|
||||
|
||||
/// Edit a DVR recording subscription's preferences
|
||||
Future<bool> editSubscription(String subscriptionId, Map<String, String> prefs) {
|
||||
final queryParams = <String, dynamic>{};
|
||||
for (final entry in prefs.entries) {
|
||||
queryParams['prefs[${entry.key}]'] = entry.value;
|
||||
}
|
||||
return _wrapBoolApiCall(
|
||||
() => _dio.put('/media/subscriptions/$subscriptionId', queryParameters: queryParams),
|
||||
'Failed to edit subscription',
|
||||
);
|
||||
}
|
||||
|
||||
/// Get scheduled DVR recordings
|
||||
Future<List<ScheduledRecording>> getScheduledRecordings() async {
|
||||
return _wrapListApiCall<ScheduledRecording>(() => _dio.get('/media/subscriptions/scheduled'), (response) {
|
||||
final container = _getMediaContainer(response);
|
||||
if (container != null && container['Metadata'] != null) {
|
||||
return (container['Metadata'] as List)
|
||||
.map((json) => ScheduledRecording.fromJson(json as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
return [];
|
||||
}, 'Failed to get scheduled recordings');
|
||||
}
|
||||
|
||||
/// Get subscription template for a program (used for recording setup)
|
||||
Future<Map<String, dynamic>?> getSubscriptionTemplate(String guid) async {
|
||||
try {
|
||||
final response = await _dio.get('/media/subscriptions/template', queryParameters: {'guid': guid});
|
||||
return _getMediaContainer(response);
|
||||
} catch (e) {
|
||||
appLogger.e('Failed to get subscription template', error: e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _handleEndpointSwitch(String newBaseUrl) async {
|
||||
if (config.baseUrl == newBaseUrl) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user