feat(livetv): let user pick and remember the recording target library
close #1513
This commit is contained in:
@@ -879,6 +879,7 @@
|
||||
"recordEpisode": "Запиши епизод",
|
||||
"recordSeries": "Запиши сериал",
|
||||
"recordOptions": "Опции за запис",
|
||||
"saveTo": "Запази в",
|
||||
"recordings": "Записи",
|
||||
"scheduledRecordings": "Планирани",
|
||||
"recordingRules": "Правила за запис",
|
||||
|
||||
@@ -879,6 +879,7 @@
|
||||
"recordEpisode": "Optag episode",
|
||||
"recordSeries": "Optag serie",
|
||||
"recordOptions": "Optageindstillinger",
|
||||
"saveTo": "Gem i",
|
||||
"recordings": "Optagelser",
|
||||
"scheduledRecordings": "Planlagt",
|
||||
"recordingRules": "Optagelsesregler",
|
||||
|
||||
@@ -879,6 +879,7 @@
|
||||
"recordEpisode": "Episode aufnehmen",
|
||||
"recordSeries": "Serie aufnehmen",
|
||||
"recordOptions": "Aufnahmeoptionen",
|
||||
"saveTo": "Speichern in",
|
||||
"recordings": "Aufnahmen",
|
||||
"scheduledRecordings": "Geplant",
|
||||
"recordingRules": "Aufnahmeregeln",
|
||||
|
||||
@@ -891,6 +891,7 @@
|
||||
"recordEpisode": "Record Episode",
|
||||
"recordSeries": "Record Series",
|
||||
"recordOptions": "Record Options",
|
||||
"saveTo": "Save to",
|
||||
"recordings": "Recordings",
|
||||
"scheduledRecordings": "Scheduled",
|
||||
"recordingRules": "Recording Rules",
|
||||
|
||||
@@ -879,6 +879,7 @@
|
||||
"recordEpisode": "Grabar episodio",
|
||||
"recordSeries": "Grabar serie",
|
||||
"recordOptions": "Opciones de grabación",
|
||||
"saveTo": "Guardar en",
|
||||
"recordings": "Grabaciones",
|
||||
"scheduledRecordings": "Programadas",
|
||||
"recordingRules": "Reglas de grabación",
|
||||
|
||||
@@ -879,6 +879,7 @@
|
||||
"recordEpisode": "Enregistrer l'épisode",
|
||||
"recordSeries": "Enregistrer la série",
|
||||
"recordOptions": "Options d'enregistrement",
|
||||
"saveTo": "Enregistrer dans",
|
||||
"recordings": "Enregistrements",
|
||||
"scheduledRecordings": "Programmés",
|
||||
"recordingRules": "Règles d'enregistrement",
|
||||
|
||||
@@ -879,6 +879,7 @@
|
||||
"recordEpisode": "Registra episodio",
|
||||
"recordSeries": "Registra serie",
|
||||
"recordOptions": "Opzioni di registrazione",
|
||||
"saveTo": "Salva in",
|
||||
"recordings": "Registrazioni",
|
||||
"scheduledRecordings": "Programmate",
|
||||
"recordingRules": "Regole di registrazione",
|
||||
|
||||
@@ -879,6 +879,7 @@
|
||||
"recordEpisode": "このエピソードを録画",
|
||||
"recordSeries": "シリーズを録画",
|
||||
"recordOptions": "録画オプション",
|
||||
"saveTo": "保存先",
|
||||
"recordings": "録画",
|
||||
"scheduledRecordings": "予約",
|
||||
"recordingRules": "録画ルール",
|
||||
|
||||
@@ -879,6 +879,7 @@
|
||||
"recordEpisode": "에피소드 녹화",
|
||||
"recordSeries": "시리즈 녹화",
|
||||
"recordOptions": "녹화 옵션",
|
||||
"saveTo": "저장 위치",
|
||||
"recordings": "녹화",
|
||||
"scheduledRecordings": "예약됨",
|
||||
"recordingRules": "녹화 규칙",
|
||||
|
||||
@@ -879,6 +879,7 @@
|
||||
"recordEpisode": "Ta opp episode",
|
||||
"recordSeries": "Ta opp serie",
|
||||
"recordOptions": "Opptaksvalg",
|
||||
"saveTo": "Lagre i",
|
||||
"recordings": "Opptak",
|
||||
"scheduledRecordings": "Planlagt",
|
||||
"recordingRules": "Opptaksregler",
|
||||
|
||||
@@ -879,6 +879,7 @@
|
||||
"recordEpisode": "Aflevering opnemen",
|
||||
"recordSeries": "Serie opnemen",
|
||||
"recordOptions": "Opnameopties",
|
||||
"saveTo": "Opslaan in",
|
||||
"recordings": "Opnames",
|
||||
"scheduledRecordings": "Gepland",
|
||||
"recordingRules": "Opnameregels",
|
||||
|
||||
@@ -879,6 +879,7 @@
|
||||
"recordEpisode": "Nagraj odcinek",
|
||||
"recordSeries": "Nagraj serial",
|
||||
"recordOptions": "Opcje nagrywania",
|
||||
"saveTo": "Zapisz w",
|
||||
"recordings": "Nagrania",
|
||||
"scheduledRecordings": "Zaplanowane",
|
||||
"recordingRules": "Reguły nagrywania",
|
||||
|
||||
@@ -879,6 +879,7 @@
|
||||
"recordEpisode": "Gravar episódio",
|
||||
"recordSeries": "Gravar série",
|
||||
"recordOptions": "Opções de gravação",
|
||||
"saveTo": "Salvar em",
|
||||
"recordings": "Gravações",
|
||||
"scheduledRecordings": "Agendadas",
|
||||
"recordingRules": "Regras de gravação",
|
||||
|
||||
@@ -879,6 +879,7 @@
|
||||
"recordEpisode": "Записать эпизод",
|
||||
"recordSeries": "Записать сериал",
|
||||
"recordOptions": "Параметры записи",
|
||||
"saveTo": "Сохранить в",
|
||||
"recordings": "Записи",
|
||||
"scheduledRecordings": "Запланировано",
|
||||
"recordingRules": "Правила записи",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
/// To regenerate, run: `dart run slang`
|
||||
///
|
||||
/// Locales: 16
|
||||
/// Strings: 21360 (1335 per locale)
|
||||
/// Strings: 21376 (1336 per locale)
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint, unused_import
|
||||
|
||||
@@ -1106,6 +1106,7 @@ class _TranslationsLiveTvBg extends TranslationsLiveTvEn {
|
||||
@override String get recordEpisode => 'Запиши епизод';
|
||||
@override String get recordSeries => 'Запиши сериал';
|
||||
@override String get recordOptions => 'Опции за запис';
|
||||
@override String get saveTo => 'Запази в';
|
||||
@override String get recordings => 'Записи';
|
||||
@override String get scheduledRecordings => 'Планирани';
|
||||
@override String get recordingRules => 'Правила за запис';
|
||||
@@ -2804,6 +2805,7 @@ extension on TranslationsBg {
|
||||
'liveTv.recordEpisode' => 'Запиши епизод',
|
||||
'liveTv.recordSeries' => 'Запиши сериал',
|
||||
'liveTv.recordOptions' => 'Опции за запис',
|
||||
'liveTv.saveTo' => 'Запази в',
|
||||
'liveTv.recordings' => 'Записи',
|
||||
'liveTv.scheduledRecordings' => 'Планирани',
|
||||
'liveTv.recordingRules' => 'Правила за запис',
|
||||
@@ -3020,9 +3022,9 @@ extension on TranslationsBg {
|
||||
'downloads.noSyncRules' => 'Няма правила за синхронизация',
|
||||
'downloads.manageSyncRule' => 'Управление на синхронизацията',
|
||||
'downloads.editEpisodeCount' => 'Брой епизоди',
|
||||
'downloads.editSyncFilter' => 'Филтър за синхронизация',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'downloads.editSyncFilter' => 'Филтър за синхронизация',
|
||||
'downloads.syncAllItems' => 'Синхронизират се всички елементи',
|
||||
'downloads.syncUnwatchedItems' => 'Синхронизират се негледаните елементи',
|
||||
'downloads.syncRuleServerContext' => ({required Object server, required Object status}) => 'Сървър: ${server} • ${status}',
|
||||
|
||||
@@ -1106,6 +1106,7 @@ class _TranslationsLiveTvDa extends TranslationsLiveTvEn {
|
||||
@override String get recordEpisode => 'Optag episode';
|
||||
@override String get recordSeries => 'Optag serie';
|
||||
@override String get recordOptions => 'Optageindstillinger';
|
||||
@override String get saveTo => 'Gem i';
|
||||
@override String get recordings => 'Optagelser';
|
||||
@override String get scheduledRecordings => 'Planlagt';
|
||||
@override String get recordingRules => 'Optagelsesregler';
|
||||
@@ -2804,6 +2805,7 @@ extension on TranslationsDa {
|
||||
'liveTv.recordEpisode' => 'Optag episode',
|
||||
'liveTv.recordSeries' => 'Optag serie',
|
||||
'liveTv.recordOptions' => 'Optageindstillinger',
|
||||
'liveTv.saveTo' => 'Gem i',
|
||||
'liveTv.recordings' => 'Optagelser',
|
||||
'liveTv.scheduledRecordings' => 'Planlagt',
|
||||
'liveTv.recordingRules' => 'Optagelsesregler',
|
||||
@@ -3020,9 +3022,9 @@ extension on TranslationsDa {
|
||||
'downloads.noSyncRules' => 'Ingen synkroniseringsregler',
|
||||
'downloads.manageSyncRule' => 'Administrer synkronisering',
|
||||
'downloads.editEpisodeCount' => 'Antal episoder',
|
||||
'downloads.editSyncFilter' => 'Synkroniseringsfilter',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'downloads.editSyncFilter' => 'Synkroniseringsfilter',
|
||||
'downloads.syncAllItems' => 'Synkroniserer alle elementer',
|
||||
'downloads.syncUnwatchedItems' => 'Synkroniserer usete elementer',
|
||||
'downloads.syncRuleServerContext' => ({required Object server, required Object status}) => 'Server: ${server} • ${status}',
|
||||
|
||||
@@ -1106,6 +1106,7 @@ class _TranslationsLiveTvDe extends TranslationsLiveTvEn {
|
||||
@override String get recordEpisode => 'Episode aufnehmen';
|
||||
@override String get recordSeries => 'Serie aufnehmen';
|
||||
@override String get recordOptions => 'Aufnahmeoptionen';
|
||||
@override String get saveTo => 'Speichern in';
|
||||
@override String get recordings => 'Aufnahmen';
|
||||
@override String get scheduledRecordings => 'Geplant';
|
||||
@override String get recordingRules => 'Aufnahmeregeln';
|
||||
@@ -2804,6 +2805,7 @@ extension on TranslationsDe {
|
||||
'liveTv.recordEpisode' => 'Episode aufnehmen',
|
||||
'liveTv.recordSeries' => 'Serie aufnehmen',
|
||||
'liveTv.recordOptions' => 'Aufnahmeoptionen',
|
||||
'liveTv.saveTo' => 'Speichern in',
|
||||
'liveTv.recordings' => 'Aufnahmen',
|
||||
'liveTv.scheduledRecordings' => 'Geplant',
|
||||
'liveTv.recordingRules' => 'Aufnahmeregeln',
|
||||
@@ -3020,9 +3022,9 @@ extension on TranslationsDe {
|
||||
'downloads.noSyncRules' => 'Keine Sync-Regeln',
|
||||
'downloads.manageSyncRule' => 'Synchronisierung verwalten',
|
||||
'downloads.editEpisodeCount' => 'Episodenanzahl',
|
||||
'downloads.editSyncFilter' => 'Synchronisierungsfilter',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'downloads.editSyncFilter' => 'Synchronisierungsfilter',
|
||||
'downloads.syncAllItems' => 'Alle Einträge synchronisieren',
|
||||
'downloads.syncUnwatchedItems' => 'Ungesehene Einträge synchronisieren',
|
||||
'downloads.syncRuleServerContext' => ({required Object server, required Object status}) => 'Server: ${server} • ${status}',
|
||||
|
||||
@@ -2614,6 +2614,9 @@ class TranslationsLiveTvEn {
|
||||
/// en: 'Record Options'
|
||||
String get recordOptions => 'Record Options';
|
||||
|
||||
/// en: 'Save to'
|
||||
String get saveTo => 'Save to';
|
||||
|
||||
/// en: 'Recordings'
|
||||
String get recordings => 'Recordings';
|
||||
|
||||
@@ -5533,6 +5536,7 @@ extension on Translations {
|
||||
'liveTv.recordEpisode' => 'Record Episode',
|
||||
'liveTv.recordSeries' => 'Record Series',
|
||||
'liveTv.recordOptions' => 'Record Options',
|
||||
'liveTv.saveTo' => 'Save to',
|
||||
'liveTv.recordings' => 'Recordings',
|
||||
'liveTv.scheduledRecordings' => 'Scheduled',
|
||||
'liveTv.recordingRules' => 'Recording Rules',
|
||||
@@ -5737,9 +5741,9 @@ extension on Translations {
|
||||
'downloads.customAmount' => 'Custom amount...',
|
||||
'downloads.includeSpecials' => 'Include Specials',
|
||||
'downloads.howManyEpisodes' => 'How many episodes?',
|
||||
'downloads.itemsQueued' => ({required Object count}) => '${count} items queued for download',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'downloads.itemsQueued' => ({required Object count}) => '${count} items queued for download',
|
||||
'downloads.keepSynced' => 'Keep synced',
|
||||
'downloads.downloadOnce' => 'Download once',
|
||||
'downloads.keepNUnwatched' => ({required Object count}) => 'Keep ${count} unwatched',
|
||||
|
||||
@@ -1106,6 +1106,7 @@ class _TranslationsLiveTvEs extends TranslationsLiveTvEn {
|
||||
@override String get recordEpisode => 'Grabar episodio';
|
||||
@override String get recordSeries => 'Grabar serie';
|
||||
@override String get recordOptions => 'Opciones de grabación';
|
||||
@override String get saveTo => 'Guardar en';
|
||||
@override String get recordings => 'Grabaciones';
|
||||
@override String get scheduledRecordings => 'Programadas';
|
||||
@override String get recordingRules => 'Reglas de grabación';
|
||||
@@ -2804,6 +2805,7 @@ extension on TranslationsEs {
|
||||
'liveTv.recordEpisode' => 'Grabar episodio',
|
||||
'liveTv.recordSeries' => 'Grabar serie',
|
||||
'liveTv.recordOptions' => 'Opciones de grabación',
|
||||
'liveTv.saveTo' => 'Guardar en',
|
||||
'liveTv.recordings' => 'Grabaciones',
|
||||
'liveTv.scheduledRecordings' => 'Programadas',
|
||||
'liveTv.recordingRules' => 'Reglas de grabación',
|
||||
@@ -3020,9 +3022,9 @@ extension on TranslationsEs {
|
||||
'downloads.noSyncRules' => 'Sin reglas de sincronización',
|
||||
'downloads.manageSyncRule' => 'Gestionar sincronización',
|
||||
'downloads.editEpisodeCount' => 'Número de episodios',
|
||||
'downloads.editSyncFilter' => 'Filtro de sincronización',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'downloads.editSyncFilter' => 'Filtro de sincronización',
|
||||
'downloads.syncAllItems' => 'Sincronizando todos los elementos',
|
||||
'downloads.syncUnwatchedItems' => 'Sincronizando elementos no vistos',
|
||||
'downloads.syncRuleServerContext' => ({required Object server, required Object status}) => 'Servidor: ${server} • ${status}',
|
||||
|
||||
@@ -1106,6 +1106,7 @@ class _TranslationsLiveTvFr extends TranslationsLiveTvEn {
|
||||
@override String get recordEpisode => 'Enregistrer l\'épisode';
|
||||
@override String get recordSeries => 'Enregistrer la série';
|
||||
@override String get recordOptions => 'Options d\'enregistrement';
|
||||
@override String get saveTo => 'Enregistrer dans';
|
||||
@override String get recordings => 'Enregistrements';
|
||||
@override String get scheduledRecordings => 'Programmés';
|
||||
@override String get recordingRules => 'Règles d\'enregistrement';
|
||||
@@ -2804,6 +2805,7 @@ extension on TranslationsFr {
|
||||
'liveTv.recordEpisode' => 'Enregistrer l\'épisode',
|
||||
'liveTv.recordSeries' => 'Enregistrer la série',
|
||||
'liveTv.recordOptions' => 'Options d\'enregistrement',
|
||||
'liveTv.saveTo' => 'Enregistrer dans',
|
||||
'liveTv.recordings' => 'Enregistrements',
|
||||
'liveTv.scheduledRecordings' => 'Programmés',
|
||||
'liveTv.recordingRules' => 'Règles d\'enregistrement',
|
||||
@@ -3020,9 +3022,9 @@ extension on TranslationsFr {
|
||||
'downloads.noSyncRules' => 'Aucune règle de synchronisation',
|
||||
'downloads.manageSyncRule' => 'Gérer la synchronisation',
|
||||
'downloads.editEpisodeCount' => 'Nombre d’épisodes',
|
||||
'downloads.editSyncFilter' => 'Filtre de synchronisation',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'downloads.editSyncFilter' => 'Filtre de synchronisation',
|
||||
'downloads.syncAllItems' => 'Synchronisation de tous les éléments',
|
||||
'downloads.syncUnwatchedItems' => 'Synchronisation des éléments non vus',
|
||||
'downloads.syncRuleServerContext' => ({required Object server, required Object status}) => 'Serveur : ${server} • ${status}',
|
||||
|
||||
@@ -1106,6 +1106,7 @@ class _TranslationsLiveTvIt extends TranslationsLiveTvEn {
|
||||
@override String get recordEpisode => 'Registra episodio';
|
||||
@override String get recordSeries => 'Registra serie';
|
||||
@override String get recordOptions => 'Opzioni di registrazione';
|
||||
@override String get saveTo => 'Salva in';
|
||||
@override String get recordings => 'Registrazioni';
|
||||
@override String get scheduledRecordings => 'Programmate';
|
||||
@override String get recordingRules => 'Regole di registrazione';
|
||||
@@ -2804,6 +2805,7 @@ extension on TranslationsIt {
|
||||
'liveTv.recordEpisode' => 'Registra episodio',
|
||||
'liveTv.recordSeries' => 'Registra serie',
|
||||
'liveTv.recordOptions' => 'Opzioni di registrazione',
|
||||
'liveTv.saveTo' => 'Salva in',
|
||||
'liveTv.recordings' => 'Registrazioni',
|
||||
'liveTv.scheduledRecordings' => 'Programmate',
|
||||
'liveTv.recordingRules' => 'Regole di registrazione',
|
||||
@@ -3020,9 +3022,9 @@ extension on TranslationsIt {
|
||||
'downloads.noSyncRules' => 'Nessuna regola di sincronizzazione',
|
||||
'downloads.manageSyncRule' => 'Gestisci sincronizzazione',
|
||||
'downloads.editEpisodeCount' => 'Numero di episodi',
|
||||
'downloads.editSyncFilter' => 'Filtro di sincronizzazione',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'downloads.editSyncFilter' => 'Filtro di sincronizzazione',
|
||||
'downloads.syncAllItems' => 'Sincronizzazione di tutti gli elementi',
|
||||
'downloads.syncUnwatchedItems' => 'Sincronizzazione degli elementi non visti',
|
||||
'downloads.syncRuleServerContext' => ({required Object server, required Object status}) => 'Server: ${server} • ${status}',
|
||||
|
||||
@@ -1106,6 +1106,7 @@ class _TranslationsLiveTvJa extends TranslationsLiveTvEn {
|
||||
@override String get recordEpisode => 'このエピソードを録画';
|
||||
@override String get recordSeries => 'シリーズを録画';
|
||||
@override String get recordOptions => '録画オプション';
|
||||
@override String get saveTo => '保存先';
|
||||
@override String get recordings => '録画';
|
||||
@override String get scheduledRecordings => '予約';
|
||||
@override String get recordingRules => '録画ルール';
|
||||
@@ -2803,6 +2804,7 @@ extension on TranslationsJa {
|
||||
'liveTv.recordEpisode' => 'このエピソードを録画',
|
||||
'liveTv.recordSeries' => 'シリーズを録画',
|
||||
'liveTv.recordOptions' => '録画オプション',
|
||||
'liveTv.saveTo' => '保存先',
|
||||
'liveTv.recordings' => '録画',
|
||||
'liveTv.scheduledRecordings' => '予約',
|
||||
'liveTv.recordingRules' => '録画ルール',
|
||||
@@ -3019,9 +3021,9 @@ extension on TranslationsJa {
|
||||
'downloads.noSyncRules' => '同期ルールなし',
|
||||
'downloads.manageSyncRule' => '同期を管理',
|
||||
'downloads.editEpisodeCount' => 'エピソード数',
|
||||
'downloads.editSyncFilter' => '同期フィルター',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'downloads.editSyncFilter' => '同期フィルター',
|
||||
'downloads.syncAllItems' => 'すべてのアイテムを同期中',
|
||||
'downloads.syncUnwatchedItems' => '未視聴のアイテムを同期中',
|
||||
'downloads.syncRuleServerContext' => ({required Object server, required Object status}) => 'サーバー: ${server} • ${status}',
|
||||
|
||||
@@ -1106,6 +1106,7 @@ class _TranslationsLiveTvKo extends TranslationsLiveTvEn {
|
||||
@override String get recordEpisode => '에피소드 녹화';
|
||||
@override String get recordSeries => '시리즈 녹화';
|
||||
@override String get recordOptions => '녹화 옵션';
|
||||
@override String get saveTo => '저장 위치';
|
||||
@override String get recordings => '녹화';
|
||||
@override String get scheduledRecordings => '예약됨';
|
||||
@override String get recordingRules => '녹화 규칙';
|
||||
@@ -2803,6 +2804,7 @@ extension on TranslationsKo {
|
||||
'liveTv.recordEpisode' => '에피소드 녹화',
|
||||
'liveTv.recordSeries' => '시리즈 녹화',
|
||||
'liveTv.recordOptions' => '녹화 옵션',
|
||||
'liveTv.saveTo' => '저장 위치',
|
||||
'liveTv.recordings' => '녹화',
|
||||
'liveTv.scheduledRecordings' => '예약됨',
|
||||
'liveTv.recordingRules' => '녹화 규칙',
|
||||
@@ -3019,9 +3021,9 @@ extension on TranslationsKo {
|
||||
'downloads.noSyncRules' => '동기화 규칙 없음',
|
||||
'downloads.manageSyncRule' => '동기화 관리',
|
||||
'downloads.editEpisodeCount' => '에피소드 수',
|
||||
'downloads.editSyncFilter' => '동기화 필터',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'downloads.editSyncFilter' => '동기화 필터',
|
||||
'downloads.syncAllItems' => '모든 항목 동기화 중',
|
||||
'downloads.syncUnwatchedItems' => '시청하지 않은 항목 동기화 중',
|
||||
'downloads.syncRuleServerContext' => ({required Object server, required Object status}) => '서버: ${server} • ${status}',
|
||||
|
||||
@@ -1106,6 +1106,7 @@ class _TranslationsLiveTvNb extends TranslationsLiveTvEn {
|
||||
@override String get recordEpisode => 'Ta opp episode';
|
||||
@override String get recordSeries => 'Ta opp serie';
|
||||
@override String get recordOptions => 'Opptaksvalg';
|
||||
@override String get saveTo => 'Lagre i';
|
||||
@override String get recordings => 'Opptak';
|
||||
@override String get scheduledRecordings => 'Planlagt';
|
||||
@override String get recordingRules => 'Opptaksregler';
|
||||
@@ -2804,6 +2805,7 @@ extension on TranslationsNb {
|
||||
'liveTv.recordEpisode' => 'Ta opp episode',
|
||||
'liveTv.recordSeries' => 'Ta opp serie',
|
||||
'liveTv.recordOptions' => 'Opptaksvalg',
|
||||
'liveTv.saveTo' => 'Lagre i',
|
||||
'liveTv.recordings' => 'Opptak',
|
||||
'liveTv.scheduledRecordings' => 'Planlagt',
|
||||
'liveTv.recordingRules' => 'Opptaksregler',
|
||||
@@ -3020,9 +3022,9 @@ extension on TranslationsNb {
|
||||
'downloads.noSyncRules' => 'Ingen synkroniseringsregler',
|
||||
'downloads.manageSyncRule' => 'Administrer synkronisering',
|
||||
'downloads.editEpisodeCount' => 'Antall episoder',
|
||||
'downloads.editSyncFilter' => 'Synkroniseringsfilter',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'downloads.editSyncFilter' => 'Synkroniseringsfilter',
|
||||
'downloads.syncAllItems' => 'Synkroniserer alle elementer',
|
||||
'downloads.syncUnwatchedItems' => 'Synkroniserer usette elementer',
|
||||
'downloads.syncRuleServerContext' => ({required Object server, required Object status}) => 'Server: ${server} • ${status}',
|
||||
|
||||
@@ -1106,6 +1106,7 @@ class _TranslationsLiveTvNl extends TranslationsLiveTvEn {
|
||||
@override String get recordEpisode => 'Aflevering opnemen';
|
||||
@override String get recordSeries => 'Serie opnemen';
|
||||
@override String get recordOptions => 'Opnameopties';
|
||||
@override String get saveTo => 'Opslaan in';
|
||||
@override String get recordings => 'Opnames';
|
||||
@override String get scheduledRecordings => 'Gepland';
|
||||
@override String get recordingRules => 'Opnameregels';
|
||||
@@ -2804,6 +2805,7 @@ extension on TranslationsNl {
|
||||
'liveTv.recordEpisode' => 'Aflevering opnemen',
|
||||
'liveTv.recordSeries' => 'Serie opnemen',
|
||||
'liveTv.recordOptions' => 'Opnameopties',
|
||||
'liveTv.saveTo' => 'Opslaan in',
|
||||
'liveTv.recordings' => 'Opnames',
|
||||
'liveTv.scheduledRecordings' => 'Gepland',
|
||||
'liveTv.recordingRules' => 'Opnameregels',
|
||||
@@ -3020,9 +3022,9 @@ extension on TranslationsNl {
|
||||
'downloads.noSyncRules' => 'Geen synchronisatieregels',
|
||||
'downloads.manageSyncRule' => 'Synchronisatie beheren',
|
||||
'downloads.editEpisodeCount' => 'Aantal afleveringen',
|
||||
'downloads.editSyncFilter' => 'Synchronisatiefilter',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'downloads.editSyncFilter' => 'Synchronisatiefilter',
|
||||
'downloads.syncAllItems' => 'Alle items synchroniseren',
|
||||
'downloads.syncUnwatchedItems' => 'Ongekeken items synchroniseren',
|
||||
'downloads.syncRuleServerContext' => ({required Object server, required Object status}) => 'Server: ${server} • ${status}',
|
||||
|
||||
@@ -1106,6 +1106,7 @@ class _TranslationsLiveTvPl extends TranslationsLiveTvEn {
|
||||
@override String get recordEpisode => 'Nagraj odcinek';
|
||||
@override String get recordSeries => 'Nagraj serial';
|
||||
@override String get recordOptions => 'Opcje nagrywania';
|
||||
@override String get saveTo => 'Zapisz w';
|
||||
@override String get recordings => 'Nagrania';
|
||||
@override String get scheduledRecordings => 'Zaplanowane';
|
||||
@override String get recordingRules => 'Reguły nagrywania';
|
||||
@@ -2806,6 +2807,7 @@ extension on TranslationsPl {
|
||||
'liveTv.recordEpisode' => 'Nagraj odcinek',
|
||||
'liveTv.recordSeries' => 'Nagraj serial',
|
||||
'liveTv.recordOptions' => 'Opcje nagrywania',
|
||||
'liveTv.saveTo' => 'Zapisz w',
|
||||
'liveTv.recordings' => 'Nagrania',
|
||||
'liveTv.scheduledRecordings' => 'Zaplanowane',
|
||||
'liveTv.recordingRules' => 'Reguły nagrywania',
|
||||
@@ -3022,9 +3024,9 @@ extension on TranslationsPl {
|
||||
'downloads.noSyncRules' => 'Brak reguł synchronizacji',
|
||||
'downloads.manageSyncRule' => 'Zarządzaj synchronizacją',
|
||||
'downloads.editEpisodeCount' => 'Liczba odcinków',
|
||||
'downloads.editSyncFilter' => 'Filtr synchronizacji',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'downloads.editSyncFilter' => 'Filtr synchronizacji',
|
||||
'downloads.syncAllItems' => 'Synchronizuję wszystkie elementy',
|
||||
'downloads.syncUnwatchedItems' => 'Synchronizuję nieobejrzane elementy',
|
||||
'downloads.syncRuleServerContext' => ({required Object server, required Object status}) => 'Serwer: ${server} • ${status}',
|
||||
|
||||
@@ -1106,6 +1106,7 @@ class _TranslationsLiveTvPt extends TranslationsLiveTvEn {
|
||||
@override String get recordEpisode => 'Gravar episódio';
|
||||
@override String get recordSeries => 'Gravar série';
|
||||
@override String get recordOptions => 'Opções de gravação';
|
||||
@override String get saveTo => 'Salvar em';
|
||||
@override String get recordings => 'Gravações';
|
||||
@override String get scheduledRecordings => 'Agendadas';
|
||||
@override String get recordingRules => 'Regras de gravação';
|
||||
@@ -2804,6 +2805,7 @@ extension on TranslationsPt {
|
||||
'liveTv.recordEpisode' => 'Gravar episódio',
|
||||
'liveTv.recordSeries' => 'Gravar série',
|
||||
'liveTv.recordOptions' => 'Opções de gravação',
|
||||
'liveTv.saveTo' => 'Salvar em',
|
||||
'liveTv.recordings' => 'Gravações',
|
||||
'liveTv.scheduledRecordings' => 'Agendadas',
|
||||
'liveTv.recordingRules' => 'Regras de gravação',
|
||||
@@ -3020,9 +3022,9 @@ extension on TranslationsPt {
|
||||
'downloads.noSyncRules' => 'Nenhuma regra de sincronização',
|
||||
'downloads.manageSyncRule' => 'Gerenciar sincronização',
|
||||
'downloads.editEpisodeCount' => 'Número de episódios',
|
||||
'downloads.editSyncFilter' => 'Filtro de sincronização',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'downloads.editSyncFilter' => 'Filtro de sincronização',
|
||||
'downloads.syncAllItems' => 'Sincronizando todos os itens',
|
||||
'downloads.syncUnwatchedItems' => 'Sincronizando itens não vistos',
|
||||
'downloads.syncRuleServerContext' => ({required Object server, required Object status}) => 'Servidor: ${server} • ${status}',
|
||||
|
||||
@@ -1106,6 +1106,7 @@ class _TranslationsLiveTvRu extends TranslationsLiveTvEn {
|
||||
@override String get recordEpisode => 'Записать эпизод';
|
||||
@override String get recordSeries => 'Записать сериал';
|
||||
@override String get recordOptions => 'Параметры записи';
|
||||
@override String get saveTo => 'Сохранить в';
|
||||
@override String get recordings => 'Записи';
|
||||
@override String get scheduledRecordings => 'Запланировано';
|
||||
@override String get recordingRules => 'Правила записи';
|
||||
@@ -2806,6 +2807,7 @@ extension on TranslationsRu {
|
||||
'liveTv.recordEpisode' => 'Записать эпизод',
|
||||
'liveTv.recordSeries' => 'Записать сериал',
|
||||
'liveTv.recordOptions' => 'Параметры записи',
|
||||
'liveTv.saveTo' => 'Сохранить в',
|
||||
'liveTv.recordings' => 'Записи',
|
||||
'liveTv.scheduledRecordings' => 'Запланировано',
|
||||
'liveTv.recordingRules' => 'Правила записи',
|
||||
@@ -3022,9 +3024,9 @@ extension on TranslationsRu {
|
||||
'downloads.noSyncRules' => 'Нет правил синхронизации',
|
||||
'downloads.manageSyncRule' => 'Управление синхронизацией',
|
||||
'downloads.editEpisodeCount' => 'Количество эпизодов',
|
||||
'downloads.editSyncFilter' => 'Фильтр синхронизации',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'downloads.editSyncFilter' => 'Фильтр синхронизации',
|
||||
'downloads.syncAllItems' => 'Синхронизация всех элементов',
|
||||
'downloads.syncUnwatchedItems' => 'Синхронизация непросмотренных элементов',
|
||||
'downloads.syncRuleServerContext' => ({required Object server, required Object status}) => 'Сервер: ${server} • ${status}',
|
||||
|
||||
@@ -1106,6 +1106,7 @@ class _TranslationsLiveTvSv extends TranslationsLiveTvEn {
|
||||
@override String get recordEpisode => 'Spela in avsnitt';
|
||||
@override String get recordSeries => 'Spela in serie';
|
||||
@override String get recordOptions => 'Inspelningsalternativ';
|
||||
@override String get saveTo => 'Spara till';
|
||||
@override String get recordings => 'Inspelningar';
|
||||
@override String get scheduledRecordings => 'Schemalagda';
|
||||
@override String get recordingRules => 'Inspelningsregler';
|
||||
@@ -2804,6 +2805,7 @@ extension on TranslationsSv {
|
||||
'liveTv.recordEpisode' => 'Spela in avsnitt',
|
||||
'liveTv.recordSeries' => 'Spela in serie',
|
||||
'liveTv.recordOptions' => 'Inspelningsalternativ',
|
||||
'liveTv.saveTo' => 'Spara till',
|
||||
'liveTv.recordings' => 'Inspelningar',
|
||||
'liveTv.scheduledRecordings' => 'Schemalagda',
|
||||
'liveTv.recordingRules' => 'Inspelningsregler',
|
||||
@@ -3020,9 +3022,9 @@ extension on TranslationsSv {
|
||||
'downloads.noSyncRules' => 'Inga synkregler',
|
||||
'downloads.manageSyncRule' => 'Hantera synkronisering',
|
||||
'downloads.editEpisodeCount' => 'Antal avsnitt',
|
||||
'downloads.editSyncFilter' => 'Synkroniseringsfilter',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'downloads.editSyncFilter' => 'Synkroniseringsfilter',
|
||||
'downloads.syncAllItems' => 'Synkroniserar alla objekt',
|
||||
'downloads.syncUnwatchedItems' => 'Synkroniserar osedda objekt',
|
||||
'downloads.syncRuleServerContext' => ({required Object server, required Object status}) => 'Server: ${server} • ${status}',
|
||||
|
||||
@@ -1106,6 +1106,7 @@ class _TranslationsLiveTvZh extends TranslationsLiveTvEn {
|
||||
@override String get recordEpisode => '录制单集';
|
||||
@override String get recordSeries => '录制整部剧集';
|
||||
@override String get recordOptions => '录制选项';
|
||||
@override String get saveTo => '保存到';
|
||||
@override String get recordings => '录制内容';
|
||||
@override String get scheduledRecordings => '已安排';
|
||||
@override String get recordingRules => '录制规则';
|
||||
@@ -2803,6 +2804,7 @@ extension on TranslationsZh {
|
||||
'liveTv.recordEpisode' => '录制单集',
|
||||
'liveTv.recordSeries' => '录制整部剧集',
|
||||
'liveTv.recordOptions' => '录制选项',
|
||||
'liveTv.saveTo' => '保存到',
|
||||
'liveTv.recordings' => '录制内容',
|
||||
'liveTv.scheduledRecordings' => '已安排',
|
||||
'liveTv.recordingRules' => '录制规则',
|
||||
@@ -3019,9 +3021,9 @@ extension on TranslationsZh {
|
||||
'downloads.noSyncRules' => '没有同步规则',
|
||||
'downloads.manageSyncRule' => '管理同步',
|
||||
'downloads.editEpisodeCount' => '剧集数量',
|
||||
'downloads.editSyncFilter' => '同步筛选',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'downloads.editSyncFilter' => '同步筛选',
|
||||
'downloads.syncAllItems' => '同步所有项目',
|
||||
'downloads.syncUnwatchedItems' => '同步未观看项目',
|
||||
'downloads.syncRuleServerContext' => ({required Object server, required Object status}) => '服务器: ${server} • ${status}',
|
||||
|
||||
@@ -879,6 +879,7 @@
|
||||
"recordEpisode": "Spela in avsnitt",
|
||||
"recordSeries": "Spela in serie",
|
||||
"recordOptions": "Inspelningsalternativ",
|
||||
"saveTo": "Spara till",
|
||||
"recordings": "Inspelningar",
|
||||
"scheduledRecordings": "Schemalagda",
|
||||
"recordingRules": "Inspelningsregler",
|
||||
|
||||
@@ -879,6 +879,7 @@
|
||||
"recordEpisode": "录制单集",
|
||||
"recordSeries": "录制整部剧集",
|
||||
"recordOptions": "录制选项",
|
||||
"saveTo": "保存到",
|
||||
"recordings": "录制内容",
|
||||
"scheduledRecordings": "已安排",
|
||||
"recordingRules": "录制规则",
|
||||
|
||||
@@ -190,9 +190,15 @@ class MediaSubscriptionCreateRequest {
|
||||
for (final setting in subscription.settings)
|
||||
if (setting.id.isNotEmpty) setting.id: setting.value ?? setting.defaultValue,
|
||||
};
|
||||
// The template's location id belongs to the template's section; when the
|
||||
// caller redirects to another section it must not be sent along (the
|
||||
// server then uses the new section's default location).
|
||||
final overridingSection =
|
||||
targetLibrarySectionID != null && targetLibrarySectionID != subscription.targetLibrarySectionID;
|
||||
return MediaSubscriptionCreateRequest(
|
||||
targetLibrarySectionID: targetLibrarySectionID ?? subscription.targetLibrarySectionID,
|
||||
targetSectionLocationID: targetSectionLocationID ?? subscription.targetSectionLocationID,
|
||||
targetSectionLocationID:
|
||||
targetSectionLocationID ?? (overridingSection ? null : subscription.targetSectionLocationID),
|
||||
type: subscription.type,
|
||||
parameters: subscription.parameters,
|
||||
prefs: {...templatePrefs, ...prefs},
|
||||
|
||||
@@ -7,10 +7,14 @@ import '../../focus/focusable_button.dart';
|
||||
import '../../focus/focusable_text_field.dart';
|
||||
import '../../focus/focusable_wrapper.dart';
|
||||
import '../../i18n/strings.g.dart';
|
||||
import '../../media/media_kind.dart';
|
||||
import '../../media/media_library.dart';
|
||||
import '../../media/media_server_client.dart';
|
||||
import '../../mixins/controller_disposer_mixin.dart';
|
||||
import '../../models/livetv_program.dart';
|
||||
import '../../models/media_subscription.dart';
|
||||
import '../../services/plex_constants.dart';
|
||||
import '../../services/settings_service.dart';
|
||||
import '../../utils/app_logger.dart';
|
||||
import '../../utils/dialogs.dart';
|
||||
import '../../widgets/app_icon.dart';
|
||||
@@ -87,6 +91,8 @@ class _RecordOptionsContent extends StatefulWidget {
|
||||
class _RecordOptionsContentState extends State<_RecordOptionsContent> {
|
||||
late MediaSubscription _entry;
|
||||
final Map<String, Object?> _dirtyPrefs = {};
|
||||
List<MediaLibrary> _libraries = const [];
|
||||
int? _selectedSectionId;
|
||||
bool _saving = false;
|
||||
final _saveFocusNode = FocusNode(debugLabel: 'record_options_save');
|
||||
|
||||
@@ -94,6 +100,7 @@ class _RecordOptionsContentState extends State<_RecordOptionsContent> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_entry = widget.entries.firstWhere((e) => e.selected == true, orElse: () => widget.entries.first);
|
||||
if (!widget.isEdit) _loadLibraries();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -102,11 +109,60 @@ class _RecordOptionsContentState extends State<_RecordOptionsContent> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _loadLibraries() async {
|
||||
try {
|
||||
final libraries = await widget.client.fetchLibraries();
|
||||
if (mounted) setState(() => _libraries = libraries);
|
||||
} catch (e) {
|
||||
// Non-fatal: the row degrades to a read-only template default.
|
||||
appLogger.w('Failed to fetch libraries for recording target picker', error: e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Library kind a recording of [_entry]'s type lands in, or null when no
|
||||
/// sensible mapping exists (no picker shown).
|
||||
MediaKind? get _entryLibraryKind => switch (_entry.type) {
|
||||
PlexMetadataType.movie => MediaKind.movie,
|
||||
PlexMetadataType.show || PlexMetadataType.season || PlexMetadataType.episode => MediaKind.show,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
/// Sections eligible as recording targets for the selected entry.
|
||||
List<MediaLibrary> get _eligibleLibraries {
|
||||
final kind = _entryLibraryKind;
|
||||
if (kind == null) return const [];
|
||||
return [
|
||||
for (final library in _libraries)
|
||||
if (library.kind == kind && !library.isShared && int.tryParse(library.id) != null) library,
|
||||
];
|
||||
}
|
||||
|
||||
/// Last explicitly picked target for this server + entry type, if it still
|
||||
/// refers to an eligible section.
|
||||
int? _savedSectionId(List<MediaLibrary> eligible) {
|
||||
final type = _entry.type;
|
||||
if (type == null) return null;
|
||||
final saved =
|
||||
SettingsService.instanceOrNull?.read(SettingsService.dvrTargetSectionPref(widget.client.serverId, type)) ?? 0;
|
||||
if (saved <= 0) return null;
|
||||
return eligible.any((library) => library.id == '$saved') ? saved : null;
|
||||
}
|
||||
|
||||
int? _effectiveSectionId(List<MediaLibrary> eligible) =>
|
||||
_selectedSectionId ?? _savedSectionId(eligible) ?? _entry.targetLibrarySectionID;
|
||||
|
||||
String _targetLibraryLabel(List<MediaLibrary> eligible) {
|
||||
final target = _effectiveSectionId(eligible);
|
||||
final match = eligible.where((library) => library.id == '$target').firstOrNull;
|
||||
return match?.title ?? _entry.librarySectionTitle ?? '';
|
||||
}
|
||||
|
||||
void _selectEntry(MediaSubscription entry) {
|
||||
if (identical(entry, _entry)) return;
|
||||
setState(() {
|
||||
_entry = entry;
|
||||
_dirtyPrefs.clear();
|
||||
_selectedSectionId = null;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -134,7 +190,8 @@ class _RecordOptionsContentState extends State<_RecordOptionsContent> {
|
||||
}
|
||||
|
||||
Future<void> _save() async {
|
||||
if (!widget.isEdit && _entry.targetLibrarySectionID == null) {
|
||||
final targetSectionId = widget.isEdit ? null : _effectiveSectionId(_eligibleLibraries);
|
||||
if (!widget.isEdit && targetSectionId == null) {
|
||||
_close(RecordOutcome.targetMissing);
|
||||
return;
|
||||
}
|
||||
@@ -150,8 +207,13 @@ class _RecordOptionsContentState extends State<_RecordOptionsContent> {
|
||||
if (!mounted) return;
|
||||
_close(RecordOutcome.updated);
|
||||
} else {
|
||||
final request = MediaSubscriptionCreateRequest.fromTemplate(_entry, prefs: Map.of(_dirtyPrefs));
|
||||
final request = MediaSubscriptionCreateRequest.fromTemplate(
|
||||
_entry,
|
||||
prefs: Map.of(_dirtyPrefs),
|
||||
targetLibrarySectionID: targetSectionId,
|
||||
);
|
||||
await widget.client.liveTv.createRecordingRule(request);
|
||||
_persistTargetPick();
|
||||
if (!mounted) return;
|
||||
_close(RecordOutcome.scheduled);
|
||||
}
|
||||
@@ -168,14 +230,35 @@ class _RecordOptionsContentState extends State<_RecordOptionsContent> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Remember an explicit library pick (never the template default, so the
|
||||
/// server default keeps applying until the user chooses).
|
||||
void _persistTargetPick() {
|
||||
final picked = _selectedSectionId;
|
||||
final type = _entry.type;
|
||||
if (picked == null || type == null) return;
|
||||
SettingsService.instanceOrNull?.write(SettingsService.dvrTargetSectionPref(widget.client.serverId, type), picked);
|
||||
}
|
||||
|
||||
void _close([RecordOutcome? result]) {
|
||||
OverlaySheetController.popAdaptive(context, result ?? RecordOutcome.cancelled);
|
||||
}
|
||||
|
||||
Future<void> _openTargetPicker(List<MediaLibrary> eligible) async {
|
||||
final selected = await showOptionPickerDialog<int>(
|
||||
context,
|
||||
title: t.liveTv.saveTo,
|
||||
options: [for (final library in eligible) (icon: null, label: library.title, value: int.parse(library.id))],
|
||||
);
|
||||
if (selected != null && mounted) setState(() => _selectedSectionId = selected);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final visibleSettings = _entry.settings.where((s) => !s.hidden && !s.advanced).toList();
|
||||
final eligibleLibraries = widget.isEdit ? const <MediaLibrary>[] : _eligibleLibraries;
|
||||
final targetLabel = widget.isEdit ? (_entry.librarySectionTitle ?? '') : _targetLibraryLabel(eligibleLibraries);
|
||||
final showTargetRow = targetLabel.isNotEmpty || eligibleLibraries.length > 1;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
@@ -209,6 +292,14 @@ class _RecordOptionsContentState extends State<_RecordOptionsContent> {
|
||||
const SizedBox(height: 12),
|
||||
_EntryChooser(entries: widget.entries, selected: _entry, onSelect: _selectEntry),
|
||||
],
|
||||
if (showTargetRow) ...[
|
||||
const SizedBox(height: 8),
|
||||
_PickerRow(
|
||||
label: t.liveTv.saveTo,
|
||||
value: targetLabel,
|
||||
onTap: eligibleLibraries.length > 1 ? () => _openTargetPicker(eligibleLibraries) : null,
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 8),
|
||||
Flexible(
|
||||
child: visibleSettings.isEmpty
|
||||
@@ -416,42 +507,60 @@ class _EnumSettingRow extends StatelessWidget {
|
||||
return match?.label ?? str;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _PickerRow(
|
||||
label: setting.label ?? setting.id,
|
||||
summary: setting.summary,
|
||||
value: _labelForValue(currentValue),
|
||||
autofocus: autofocus,
|
||||
onTap: () => _openPicker(context),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Label / value / chevron row that opens a picker on tap. Renders as a
|
||||
/// static, non-focusable row when [onTap] is null.
|
||||
class _PickerRow extends StatelessWidget {
|
||||
final String label;
|
||||
final String? summary;
|
||||
final String value;
|
||||
final bool autofocus;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const _PickerRow({required this.label, this.summary, required this.value, this.autofocus = false, this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final onTap = this.onTap;
|
||||
final row = Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: .start,
|
||||
children: [
|
||||
Text(label, style: theme.textTheme.bodyMedium),
|
||||
if (summary != null && summary!.isNotEmpty)
|
||||
Text(summary!, style: theme.textTheme.bodySmall?.copyWith(color: theme.colorScheme.onSurfaceVariant)),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(value, style: theme.textTheme.bodyMedium),
|
||||
if (onTap != null) ...[const SizedBox(width: 4), const AppIcon(Symbols.chevron_right_rounded)],
|
||||
],
|
||||
),
|
||||
);
|
||||
if (onTap == null) return row;
|
||||
return FocusableWrapper(
|
||||
autofocus: autofocus,
|
||||
autoScroll: true,
|
||||
useBackgroundFocus: true,
|
||||
onSelect: () => _openPicker(context),
|
||||
child: InkWell(
|
||||
canRequestFocus: false,
|
||||
onTap: () => _openPicker(context),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: .start,
|
||||
children: [
|
||||
Text(setting.label ?? setting.id, style: theme.textTheme.bodyMedium),
|
||||
if (setting.summary != null && setting.summary!.isNotEmpty)
|
||||
Text(
|
||||
setting.summary!,
|
||||
style: theme.textTheme.bodySmall?.copyWith(color: theme.colorScheme.onSurfaceVariant),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(_labelForValue(currentValue), style: theme.textTheme.bodyMedium),
|
||||
const SizedBox(width: 4),
|
||||
const AppIcon(Symbols.chevron_right_rounded),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
onSelect: onTap,
|
||||
child: InkWell(canRequestFocus: false, onTap: onTap, child: row),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -570,6 +570,12 @@ class SettingsService extends BaseSharedPreferencesService {
|
||||
|
||||
static IntPref watchedThresholdPref(ServerId serverId) => IntPref('watched_threshold_$serverId', defaultValue: 90);
|
||||
|
||||
/// Library section the user last picked as a DVR recording target, keyed by
|
||||
/// subscription type (movie/show) so the two don't clobber each other.
|
||||
/// 0 = unset (only explicit picks are written; the server template default
|
||||
/// keeps applying until the user chooses).
|
||||
static IntPref dvrTargetSectionPref(ServerId serverId, int type) => IntPref('dvr_target_section_${type}_$serverId');
|
||||
|
||||
static EnumPref<TrackerLibraryFilterMode> trackerFilterModePref(TrackerService s) => EnumPref(
|
||||
'tracker_library_filter_mode_${s.name}',
|
||||
values: TrackerLibraryFilterMode.values,
|
||||
|
||||
@@ -215,6 +215,43 @@ void main() {
|
||||
expect(created?.key, '18');
|
||||
});
|
||||
|
||||
test('fromTemplate section override drops the template location id', () async {
|
||||
late http.Request captured;
|
||||
final client = makeClient((request) async {
|
||||
captured = request;
|
||||
return jsonResponse({
|
||||
'MediaContainer': {
|
||||
'MediaSubscription': [
|
||||
{'key': '18', 'type': 4, 'title': 'Episode'},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
addTearDown(client.close);
|
||||
|
||||
const template = MediaSubscription(key: '', targetLibrarySectionID: 2, targetSectionLocationID: 7, type: 4);
|
||||
|
||||
final overridden = MediaSubscriptionCreateRequest.fromTemplate(template, targetLibrarySectionID: 5);
|
||||
expect(overridden.targetLibrarySectionID, 5);
|
||||
expect(overridden.targetSectionLocationID, isNull);
|
||||
|
||||
await client.liveTv.createRecordingRule(overridden);
|
||||
expect(captured.url.queryParameters['targetLibrarySectionID'], '5');
|
||||
expect(captured.url.queryParameters.containsKey('targetSectionLocationID'), isFalse);
|
||||
});
|
||||
|
||||
test('fromTemplate without override keeps template section and location', () {
|
||||
const template = MediaSubscription(key: '', targetLibrarySectionID: 2, targetSectionLocationID: 7, type: 4);
|
||||
|
||||
final request = MediaSubscriptionCreateRequest.fromTemplate(template);
|
||||
expect(request.targetLibrarySectionID, 2);
|
||||
expect(request.targetSectionLocationID, 7);
|
||||
|
||||
// Same-section override is a no-op: the template location still applies.
|
||||
final sameSection = MediaSubscriptionCreateRequest.fromTemplate(template, targetLibrarySectionID: 2);
|
||||
expect(sameSection.targetSectionLocationID, 7);
|
||||
});
|
||||
|
||||
test('updateRecordingRule sends prefs as query params', () async {
|
||||
late http.Request captured;
|
||||
final client = makeClient((request) async {
|
||||
|
||||
Reference in New Issue
Block a user