fix: remove DVR recording features (plex pass)

close #530
This commit is contained in:
edde746
2026-02-24 23:13:38 +01:00
parent 238391b886
commit af41d937c3
25 changed files with 20 additions and 1206 deletions
-12
View File
@@ -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",
-12
View File
@@ -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",
-12
View File
@@ -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",
-12
View File
@@ -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",
-12
View File
@@ -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",
-12
View File
@@ -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": "오늘",
-12
View File
@@ -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",
+2 -2
View File
@@ -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
+2 -26
View File
@@ -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',
+2 -50
View File
@@ -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',
+2 -26
View File
@@ -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',
+2 -26
View File
@@ -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',
+2 -26
View File
@@ -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',
+2 -26
View File
@@ -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' => '컬렉션',
+2 -26
View File
@@ -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',
+2 -26
View File
@@ -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',
+2 -26
View File
@@ -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' => '管理',
-12
View File
@@ -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",
-12
View File
@@ -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;
}
}
-119
View File
@@ -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(),
),
);
}
}
-52
View File
@@ -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;
-114
View File
@@ -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;