feat: tag editing in metadata editor

This commit is contained in:
edde746
2026-03-31 14:10:17 +02:00
parent 2c12a07257
commit da8b583286
36 changed files with 852 additions and 18 deletions
+12 -1
View File
@@ -891,7 +891,18 @@
"subtitleMode": "Auto-vælg underteksttilstand",
"manuallySelected": "Manuelt valgt",
"shownWithForeignAudio": "Vist med fremmedsproget lyd",
"alwaysEnabled": "Altid aktiveret"
"alwaysEnabled": "Altid aktiveret",
"tags": "Tags",
"addTag": "Tilføj tag",
"genre": "Genre",
"director": "Instruktør",
"writer": "Forfatter",
"producer": "Producer",
"country": "Land",
"collection": "Samling",
"label": "Etiket",
"style": "Stil",
"mood": "Stemning"
},
"serverTasks": {
"title": "Serveropgaver",
+12 -1
View File
@@ -891,7 +891,18 @@
"subtitleMode": "Automatische Untertitelauswahl",
"manuallySelected": "Manuell ausgewählt",
"shownWithForeignAudio": "Bei fremdsprachigem Audio anzeigen",
"alwaysEnabled": "Immer aktiviert"
"alwaysEnabled": "Immer aktiviert",
"tags": "Tags",
"addTag": "Tag hinzufügen",
"genre": "Genre",
"director": "Regisseur",
"writer": "Autor",
"producer": "Produzent",
"country": "Land",
"collection": "Sammlung",
"label": "Label",
"style": "Stil",
"mood": "Stimmung"
},
"serverTasks": {
"title": "Serveraufgaben",
+12 -1
View File
@@ -891,7 +891,18 @@
"subtitleMode": "Auto-Select Subtitle Mode",
"manuallySelected": "Manually selected",
"shownWithForeignAudio": "Shown with foreign audio",
"alwaysEnabled": "Always enabled"
"alwaysEnabled": "Always enabled",
"tags": "Tags",
"addTag": "Add tag",
"genre": "Genre",
"director": "Director",
"writer": "Writer",
"producer": "Producer",
"country": "Country",
"collection": "Collection",
"label": "Label",
"style": "Style",
"mood": "Mood"
},
"serverTasks": {
"title": "Server Tasks",
+12 -1
View File
@@ -891,7 +891,18 @@
"subtitleMode": "Selección automática de subtítulos",
"manuallySelected": "Seleccionado manualmente",
"shownWithForeignAudio": "Mostrar con audio extranjero",
"alwaysEnabled": "Siempre activado"
"alwaysEnabled": "Siempre activado",
"tags": "Etiquetas",
"addTag": "Añadir etiqueta",
"genre": "Género",
"director": "Director",
"writer": "Guionista",
"producer": "Productor",
"country": "País",
"collection": "Colección",
"label": "Etiqueta",
"style": "Estilo",
"mood": "Estado de ánimo"
},
"serverTasks": {
"title": "Tareas del servidor",
+12 -1
View File
@@ -891,7 +891,18 @@
"subtitleMode": "Sélection automatique des sous-titres",
"manuallySelected": "Sélectionné manuellement",
"shownWithForeignAudio": "Affichés avec audio étranger",
"alwaysEnabled": "Toujours activé"
"alwaysEnabled": "Toujours activé",
"tags": "Tags",
"addTag": "Ajouter un tag",
"genre": "Genre",
"director": "Réalisateur",
"writer": "Scénariste",
"producer": "Producteur",
"country": "Pays",
"collection": "Collection",
"label": "Label",
"style": "Style",
"mood": "Ambiance"
},
"serverTasks": {
"title": "Tâches du serveur",
+12 -1
View File
@@ -891,7 +891,18 @@
"subtitleMode": "Selezione automatica sottotitoli",
"manuallySelected": "Selezionato manualmente",
"shownWithForeignAudio": "Mostrati con audio straniero",
"alwaysEnabled": "Sempre attivo"
"alwaysEnabled": "Sempre attivo",
"tags": "Tag",
"addTag": "Aggiungi tag",
"genre": "Genere",
"director": "Regista",
"writer": "Sceneggiatore",
"producer": "Produttore",
"country": "Paese",
"collection": "Collezione",
"label": "Etichetta",
"style": "Stile",
"mood": "Atmosfera"
},
"serverTasks": {
"title": "Attività del server",
+12 -1
View File
@@ -891,7 +891,18 @@
"subtitleMode": "字幕自動選択モード",
"manuallySelected": "手動選択",
"shownWithForeignAudio": "外国語音声時に表示",
"alwaysEnabled": "常に有効"
"alwaysEnabled": "常に有効",
"tags": "タグ",
"addTag": "タグを追加",
"genre": "ジャンル",
"director": "監督",
"writer": "脚本",
"producer": "プロデューサー",
"country": "国",
"collection": "コレクション",
"label": "ラベル",
"style": "スタイル",
"mood": "ムード"
},
"serverTasks": {
"title": "サーバータスク",
+12 -1
View File
@@ -891,7 +891,18 @@
"subtitleMode": "자막 자동 선택 모드",
"manuallySelected": "수동 선택",
"shownWithForeignAudio": "외국어 오디오 시 표시",
"alwaysEnabled": "항상 활성화"
"alwaysEnabled": "항상 활성화",
"tags": "태그",
"addTag": "태그 추가",
"genre": "장르",
"director": "감독",
"writer": "작가",
"producer": "프로듀서",
"country": "국가",
"collection": "컬렉션",
"label": "라벨",
"style": "스타일",
"mood": "분위기"
},
"serverTasks": {
"title": "서버 작업",
+12 -1
View File
@@ -891,7 +891,18 @@
"subtitleMode": "Automatisk valg av undertekstmodus",
"manuallySelected": "Manuelt valgt",
"shownWithForeignAudio": "Vist med fremmedspråklig lyd",
"alwaysEnabled": "Alltid aktivert"
"alwaysEnabled": "Alltid aktivert",
"tags": "Tagger",
"addTag": "Legg til tagg",
"genre": "Sjanger",
"director": "Regissør",
"writer": "Forfatter",
"producer": "Produsent",
"country": "Land",
"collection": "Samling",
"label": "Etikett",
"style": "Stil",
"mood": "Stemning"
},
"serverTasks": {
"title": "Serveroppgaver",
+12 -1
View File
@@ -891,7 +891,18 @@
"subtitleMode": "Automatische ondertitelselectie",
"manuallySelected": "Handmatig geselecteerd",
"shownWithForeignAudio": "Weergeven bij anderstalig geluid",
"alwaysEnabled": "Altijd ingeschakeld"
"alwaysEnabled": "Altijd ingeschakeld",
"tags": "Tags",
"addTag": "Tag toevoegen",
"genre": "Genre",
"director": "Regisseur",
"writer": "Schrijver",
"producer": "Producent",
"country": "Land",
"collection": "Collectie",
"label": "Label",
"style": "Stijl",
"mood": "Stemming"
},
"serverTasks": {
"title": "Servertaken",
+12 -1
View File
@@ -891,7 +891,18 @@
"subtitleMode": "Tryb automatycznego wyboru napisów",
"manuallySelected": "Wybrany ręcznie",
"shownWithForeignAudio": "Wyświetlane przy obcojęzycznym audio",
"alwaysEnabled": "Zawsze włączone"
"alwaysEnabled": "Zawsze włączone",
"tags": "Tagi",
"addTag": "Dodaj tag",
"genre": "Gatunek",
"director": "Reżyser",
"writer": "Scenarzysta",
"producer": "Producent",
"country": "Kraj",
"collection": "Kolekcja",
"label": "Etykieta",
"style": "Styl",
"mood": "Nastrój"
},
"serverTasks": {
"title": "Zadania serwera",
+12 -1
View File
@@ -891,7 +891,18 @@
"subtitleMode": "Modo de Seleção Automática de Legendas",
"manuallySelected": "Seleção manual",
"shownWithForeignAudio": "Exibir com áudio estrangeiro",
"alwaysEnabled": "Sempre ativado"
"alwaysEnabled": "Sempre ativado",
"tags": "Tags",
"addTag": "Adicionar tag",
"genre": "Gênero",
"director": "Diretor",
"writer": "Roteirista",
"producer": "Produtor",
"country": "País",
"collection": "Coleção",
"label": "Rótulo",
"style": "Estilo",
"mood": "Humor"
},
"serverTasks": {
"title": "Tarefas do servidor",
+12 -1
View File
@@ -891,7 +891,18 @@
"subtitleMode": "Автовыбор субтитров",
"manuallySelected": "Выбор вручную",
"shownWithForeignAudio": "Показывать при иноязычном аудио",
"alwaysEnabled": "Всегда включены"
"alwaysEnabled": "Всегда включены",
"tags": "Теги",
"addTag": "Добавить тег",
"genre": "Жанр",
"director": "Режиссёр",
"writer": "Сценарист",
"producer": "Продюсер",
"country": "Страна",
"collection": "Коллекция",
"label": "Метка",
"style": "Стиль",
"mood": "Настроение"
},
"serverTasks": {
"title": "Задачи сервера",
+2 -2
View File
@@ -4,9 +4,9 @@
/// To regenerate, run: `dart run slang`
///
/// Locales: 15
/// Strings: 12135 (809 per locale)
/// Strings: 12300 (820 per locale)
///
/// Built on 2026-03-31 at 02:43 UTC
/// Built on 2026-03-31 at 11:48 UTC
// coverage:ignore-file
// ignore_for_file: type=lint, unused_import
+22
View File
@@ -1115,6 +1115,17 @@ class _TranslationsMetadataEditDa implements TranslationsMetadataEditEn {
@override String get manuallySelected => 'Manuelt valgt';
@override String get shownWithForeignAudio => 'Vist med fremmedsproget lyd';
@override String get alwaysEnabled => 'Altid aktiveret';
@override String get tags => 'Tags';
@override String get addTag => 'Tilføj tag';
@override String get genre => 'Genre';
@override String get director => 'Instruktør';
@override String get writer => 'Forfatter';
@override String get producer => 'Producer';
@override String get country => 'Land';
@override String get collection => 'Samling';
@override String get label => 'Etiket';
@override String get style => 'Stil';
@override String get mood => 'Stemning';
}
// Path: serverTasks
@@ -2114,6 +2125,17 @@ extension on TranslationsDa {
'metadataEdit.manuallySelected' => 'Manuelt valgt',
'metadataEdit.shownWithForeignAudio' => 'Vist med fremmedsproget lyd',
'metadataEdit.alwaysEnabled' => 'Altid aktiveret',
'metadataEdit.tags' => 'Tags',
'metadataEdit.addTag' => 'Tilføj tag',
'metadataEdit.genre' => 'Genre',
'metadataEdit.director' => 'Instruktør',
'metadataEdit.writer' => 'Forfatter',
'metadataEdit.producer' => 'Producer',
'metadataEdit.country' => 'Land',
'metadataEdit.collection' => 'Samling',
'metadataEdit.label' => 'Etiket',
'metadataEdit.style' => 'Stil',
'metadataEdit.mood' => 'Stemning',
'serverTasks.title' => 'Serveropgaver',
'serverTasks.failedToLoad' => 'Kunne ikke indlæse opgaver',
'serverTasks.noTasks' => 'Ingen opgaver kører',
+22
View File
@@ -1115,6 +1115,17 @@ class _TranslationsMetadataEditDe implements TranslationsMetadataEditEn {
@override String get manuallySelected => 'Manuell ausgewählt';
@override String get shownWithForeignAudio => 'Bei fremdsprachigem Audio anzeigen';
@override String get alwaysEnabled => 'Immer aktiviert';
@override String get tags => 'Tags';
@override String get addTag => 'Tag hinzufügen';
@override String get genre => 'Genre';
@override String get director => 'Regisseur';
@override String get writer => 'Autor';
@override String get producer => 'Produzent';
@override String get country => 'Land';
@override String get collection => 'Sammlung';
@override String get label => 'Label';
@override String get style => 'Stil';
@override String get mood => 'Stimmung';
}
// Path: serverTasks
@@ -2114,6 +2125,17 @@ extension on TranslationsDe {
'metadataEdit.manuallySelected' => 'Manuell ausgewählt',
'metadataEdit.shownWithForeignAudio' => 'Bei fremdsprachigem Audio anzeigen',
'metadataEdit.alwaysEnabled' => 'Immer aktiviert',
'metadataEdit.tags' => 'Tags',
'metadataEdit.addTag' => 'Tag hinzufügen',
'metadataEdit.genre' => 'Genre',
'metadataEdit.director' => 'Regisseur',
'metadataEdit.writer' => 'Autor',
'metadataEdit.producer' => 'Produzent',
'metadataEdit.country' => 'Land',
'metadataEdit.collection' => 'Sammlung',
'metadataEdit.label' => 'Label',
'metadataEdit.style' => 'Stil',
'metadataEdit.mood' => 'Stimmung',
'serverTasks.title' => 'Serveraufgaben',
'serverTasks.failedToLoad' => 'Aufgaben konnten nicht geladen werden',
'serverTasks.noTasks' => 'Keine laufenden Aufgaben',
+44
View File
@@ -2522,6 +2522,39 @@ class TranslationsMetadataEditEn {
/// en: 'Always enabled'
String get alwaysEnabled => 'Always enabled';
/// en: 'Tags'
String get tags => 'Tags';
/// en: 'Add tag'
String get addTag => 'Add tag';
/// en: 'Genre'
String get genre => 'Genre';
/// en: 'Director'
String get director => 'Director';
/// en: 'Writer'
String get writer => 'Writer';
/// en: 'Producer'
String get producer => 'Producer';
/// en: 'Country'
String get country => 'Country';
/// en: 'Collection'
String get collection => 'Collection';
/// en: 'Label'
String get label => 'Label';
/// en: 'Style'
String get style => 'Style';
/// en: 'Mood'
String get mood => 'Mood';
}
// Path: serverTasks
@@ -3739,6 +3772,17 @@ extension on Translations {
'metadataEdit.manuallySelected' => 'Manually selected',
'metadataEdit.shownWithForeignAudio' => 'Shown with foreign audio',
'metadataEdit.alwaysEnabled' => 'Always enabled',
'metadataEdit.tags' => 'Tags',
'metadataEdit.addTag' => 'Add tag',
'metadataEdit.genre' => 'Genre',
'metadataEdit.director' => 'Director',
'metadataEdit.writer' => 'Writer',
'metadataEdit.producer' => 'Producer',
'metadataEdit.country' => 'Country',
'metadataEdit.collection' => 'Collection',
'metadataEdit.label' => 'Label',
'metadataEdit.style' => 'Style',
'metadataEdit.mood' => 'Mood',
'serverTasks.title' => 'Server Tasks',
'serverTasks.failedToLoad' => 'Failed to load tasks',
'serverTasks.noTasks' => 'No tasks running',
+22
View File
@@ -1115,6 +1115,17 @@ class _TranslationsMetadataEditEs implements TranslationsMetadataEditEn {
@override String get manuallySelected => 'Seleccionado manualmente';
@override String get shownWithForeignAudio => 'Mostrar con audio extranjero';
@override String get alwaysEnabled => 'Siempre activado';
@override String get tags => 'Etiquetas';
@override String get addTag => 'Añadir etiqueta';
@override String get genre => 'Género';
@override String get director => 'Director';
@override String get writer => 'Guionista';
@override String get producer => 'Productor';
@override String get country => 'País';
@override String get collection => 'Colección';
@override String get label => 'Etiqueta';
@override String get style => 'Estilo';
@override String get mood => 'Estado de ánimo';
}
// Path: serverTasks
@@ -2114,6 +2125,17 @@ extension on TranslationsEs {
'metadataEdit.manuallySelected' => 'Seleccionado manualmente',
'metadataEdit.shownWithForeignAudio' => 'Mostrar con audio extranjero',
'metadataEdit.alwaysEnabled' => 'Siempre activado',
'metadataEdit.tags' => 'Etiquetas',
'metadataEdit.addTag' => 'Añadir etiqueta',
'metadataEdit.genre' => 'Género',
'metadataEdit.director' => 'Director',
'metadataEdit.writer' => 'Guionista',
'metadataEdit.producer' => 'Productor',
'metadataEdit.country' => 'País',
'metadataEdit.collection' => 'Colección',
'metadataEdit.label' => 'Etiqueta',
'metadataEdit.style' => 'Estilo',
'metadataEdit.mood' => 'Estado de ánimo',
'serverTasks.title' => 'Tareas del servidor',
'serverTasks.failedToLoad' => 'Error al cargar tareas',
'serverTasks.noTasks' => 'No hay tareas en ejecución',
+22
View File
@@ -1115,6 +1115,17 @@ class _TranslationsMetadataEditFr implements TranslationsMetadataEditEn {
@override String get manuallySelected => 'Sélectionné manuellement';
@override String get shownWithForeignAudio => 'Affichés avec audio étranger';
@override String get alwaysEnabled => 'Toujours activé';
@override String get tags => 'Tags';
@override String get addTag => 'Ajouter un tag';
@override String get genre => 'Genre';
@override String get director => 'Réalisateur';
@override String get writer => 'Scénariste';
@override String get producer => 'Producteur';
@override String get country => 'Pays';
@override String get collection => 'Collection';
@override String get label => 'Label';
@override String get style => 'Style';
@override String get mood => 'Ambiance';
}
// Path: serverTasks
@@ -2114,6 +2125,17 @@ extension on TranslationsFr {
'metadataEdit.manuallySelected' => 'Sélectionné manuellement',
'metadataEdit.shownWithForeignAudio' => 'Affichés avec audio étranger',
'metadataEdit.alwaysEnabled' => 'Toujours activé',
'metadataEdit.tags' => 'Tags',
'metadataEdit.addTag' => 'Ajouter un tag',
'metadataEdit.genre' => 'Genre',
'metadataEdit.director' => 'Réalisateur',
'metadataEdit.writer' => 'Scénariste',
'metadataEdit.producer' => 'Producteur',
'metadataEdit.country' => 'Pays',
'metadataEdit.collection' => 'Collection',
'metadataEdit.label' => 'Label',
'metadataEdit.style' => 'Style',
'metadataEdit.mood' => 'Ambiance',
'serverTasks.title' => 'Tâches du serveur',
'serverTasks.failedToLoad' => 'Échec du chargement des tâches',
'serverTasks.noTasks' => 'Aucune tâche en cours',
+22
View File
@@ -1115,6 +1115,17 @@ class _TranslationsMetadataEditIt implements TranslationsMetadataEditEn {
@override String get manuallySelected => 'Selezionato manualmente';
@override String get shownWithForeignAudio => 'Mostrati con audio straniero';
@override String get alwaysEnabled => 'Sempre attivo';
@override String get tags => 'Tag';
@override String get addTag => 'Aggiungi tag';
@override String get genre => 'Genere';
@override String get director => 'Regista';
@override String get writer => 'Sceneggiatore';
@override String get producer => 'Produttore';
@override String get country => 'Paese';
@override String get collection => 'Collezione';
@override String get label => 'Etichetta';
@override String get style => 'Stile';
@override String get mood => 'Atmosfera';
}
// Path: serverTasks
@@ -2114,6 +2125,17 @@ extension on TranslationsIt {
'metadataEdit.manuallySelected' => 'Selezionato manualmente',
'metadataEdit.shownWithForeignAudio' => 'Mostrati con audio straniero',
'metadataEdit.alwaysEnabled' => 'Sempre attivo',
'metadataEdit.tags' => 'Tag',
'metadataEdit.addTag' => 'Aggiungi tag',
'metadataEdit.genre' => 'Genere',
'metadataEdit.director' => 'Regista',
'metadataEdit.writer' => 'Sceneggiatore',
'metadataEdit.producer' => 'Produttore',
'metadataEdit.country' => 'Paese',
'metadataEdit.collection' => 'Collezione',
'metadataEdit.label' => 'Etichetta',
'metadataEdit.style' => 'Stile',
'metadataEdit.mood' => 'Atmosfera',
'serverTasks.title' => 'Attività del server',
'serverTasks.failedToLoad' => 'Impossibile caricare le attività',
'serverTasks.noTasks' => 'Nessuna attività in corso',
+22
View File
@@ -1115,6 +1115,17 @@ class _TranslationsMetadataEditJa implements TranslationsMetadataEditEn {
@override String get manuallySelected => '手動選択';
@override String get shownWithForeignAudio => '外国語音声時に表示';
@override String get alwaysEnabled => '常に有効';
@override String get tags => 'タグ';
@override String get addTag => 'タグを追加';
@override String get genre => 'ジャンル';
@override String get director => '監督';
@override String get writer => '脚本';
@override String get producer => 'プロデューサー';
@override String get country => '';
@override String get collection => 'コレクション';
@override String get label => 'ラベル';
@override String get style => 'スタイル';
@override String get mood => 'ムード';
}
// Path: serverTasks
@@ -2114,6 +2125,17 @@ extension on TranslationsJa {
'metadataEdit.manuallySelected' => '手動選択',
'metadataEdit.shownWithForeignAudio' => '外国語音声時に表示',
'metadataEdit.alwaysEnabled' => '常に有効',
'metadataEdit.tags' => 'タグ',
'metadataEdit.addTag' => 'タグを追加',
'metadataEdit.genre' => 'ジャンル',
'metadataEdit.director' => '監督',
'metadataEdit.writer' => '脚本',
'metadataEdit.producer' => 'プロデューサー',
'metadataEdit.country' => '',
'metadataEdit.collection' => 'コレクション',
'metadataEdit.label' => 'ラベル',
'metadataEdit.style' => 'スタイル',
'metadataEdit.mood' => 'ムード',
'serverTasks.title' => 'サーバータスク',
'serverTasks.failedToLoad' => 'タスクの読み込みに失敗しました',
'serverTasks.noTasks' => '実行中のタスクはありません',
+22
View File
@@ -1115,6 +1115,17 @@ class _TranslationsMetadataEditKo implements TranslationsMetadataEditEn {
@override String get manuallySelected => '수동 선택';
@override String get shownWithForeignAudio => '외국어 오디오 시 표시';
@override String get alwaysEnabled => '항상 활성화';
@override String get tags => '태그';
@override String get addTag => '태그 추가';
@override String get genre => '장르';
@override String get director => '감독';
@override String get writer => '작가';
@override String get producer => '프로듀서';
@override String get country => '국가';
@override String get collection => '컬렉션';
@override String get label => '라벨';
@override String get style => '스타일';
@override String get mood => '분위기';
}
// Path: serverTasks
@@ -2114,6 +2125,17 @@ extension on TranslationsKo {
'metadataEdit.manuallySelected' => '수동 선택',
'metadataEdit.shownWithForeignAudio' => '외국어 오디오 시 표시',
'metadataEdit.alwaysEnabled' => '항상 활성화',
'metadataEdit.tags' => '태그',
'metadataEdit.addTag' => '태그 추가',
'metadataEdit.genre' => '장르',
'metadataEdit.director' => '감독',
'metadataEdit.writer' => '작가',
'metadataEdit.producer' => '프로듀서',
'metadataEdit.country' => '국가',
'metadataEdit.collection' => '컬렉션',
'metadataEdit.label' => '라벨',
'metadataEdit.style' => '스타일',
'metadataEdit.mood' => '분위기',
'serverTasks.title' => '서버 작업',
'serverTasks.failedToLoad' => '작업을 불러올 수 없습니다',
'serverTasks.noTasks' => '실행 중인 작업 없음',
+22
View File
@@ -1115,6 +1115,17 @@ class _TranslationsMetadataEditNb implements TranslationsMetadataEditEn {
@override String get manuallySelected => 'Manuelt valgt';
@override String get shownWithForeignAudio => 'Vist med fremmedspråklig lyd';
@override String get alwaysEnabled => 'Alltid aktivert';
@override String get tags => 'Tagger';
@override String get addTag => 'Legg til tagg';
@override String get genre => 'Sjanger';
@override String get director => 'Regissør';
@override String get writer => 'Forfatter';
@override String get producer => 'Produsent';
@override String get country => 'Land';
@override String get collection => 'Samling';
@override String get label => 'Etikett';
@override String get style => 'Stil';
@override String get mood => 'Stemning';
}
// Path: serverTasks
@@ -2114,6 +2125,17 @@ extension on TranslationsNb {
'metadataEdit.manuallySelected' => 'Manuelt valgt',
'metadataEdit.shownWithForeignAudio' => 'Vist med fremmedspråklig lyd',
'metadataEdit.alwaysEnabled' => 'Alltid aktivert',
'metadataEdit.tags' => 'Tagger',
'metadataEdit.addTag' => 'Legg til tagg',
'metadataEdit.genre' => 'Sjanger',
'metadataEdit.director' => 'Regissør',
'metadataEdit.writer' => 'Forfatter',
'metadataEdit.producer' => 'Produsent',
'metadataEdit.country' => 'Land',
'metadataEdit.collection' => 'Samling',
'metadataEdit.label' => 'Etikett',
'metadataEdit.style' => 'Stil',
'metadataEdit.mood' => 'Stemning',
'serverTasks.title' => 'Serveroppgaver',
'serverTasks.failedToLoad' => 'Kunne ikke laste oppgaver',
'serverTasks.noTasks' => 'Ingen oppgaver kjører',
+22
View File
@@ -1115,6 +1115,17 @@ class _TranslationsMetadataEditNl implements TranslationsMetadataEditEn {
@override String get manuallySelected => 'Handmatig geselecteerd';
@override String get shownWithForeignAudio => 'Weergeven bij anderstalig geluid';
@override String get alwaysEnabled => 'Altijd ingeschakeld';
@override String get tags => 'Tags';
@override String get addTag => 'Tag toevoegen';
@override String get genre => 'Genre';
@override String get director => 'Regisseur';
@override String get writer => 'Schrijver';
@override String get producer => 'Producent';
@override String get country => 'Land';
@override String get collection => 'Collectie';
@override String get label => 'Label';
@override String get style => 'Stijl';
@override String get mood => 'Stemming';
}
// Path: serverTasks
@@ -2114,6 +2125,17 @@ extension on TranslationsNl {
'metadataEdit.manuallySelected' => 'Handmatig geselecteerd',
'metadataEdit.shownWithForeignAudio' => 'Weergeven bij anderstalig geluid',
'metadataEdit.alwaysEnabled' => 'Altijd ingeschakeld',
'metadataEdit.tags' => 'Tags',
'metadataEdit.addTag' => 'Tag toevoegen',
'metadataEdit.genre' => 'Genre',
'metadataEdit.director' => 'Regisseur',
'metadataEdit.writer' => 'Schrijver',
'metadataEdit.producer' => 'Producent',
'metadataEdit.country' => 'Land',
'metadataEdit.collection' => 'Collectie',
'metadataEdit.label' => 'Label',
'metadataEdit.style' => 'Stijl',
'metadataEdit.mood' => 'Stemming',
'serverTasks.title' => 'Servertaken',
'serverTasks.failedToLoad' => 'Taken konden niet worden geladen',
'serverTasks.noTasks' => 'Geen actieve taken',
+22
View File
@@ -1115,6 +1115,17 @@ class _TranslationsMetadataEditPl implements TranslationsMetadataEditEn {
@override String get manuallySelected => 'Wybrany ręcznie';
@override String get shownWithForeignAudio => 'Wyświetlane przy obcojęzycznym audio';
@override String get alwaysEnabled => 'Zawsze włączone';
@override String get tags => 'Tagi';
@override String get addTag => 'Dodaj tag';
@override String get genre => 'Gatunek';
@override String get director => 'Reżyser';
@override String get writer => 'Scenarzysta';
@override String get producer => 'Producent';
@override String get country => 'Kraj';
@override String get collection => 'Kolekcja';
@override String get label => 'Etykieta';
@override String get style => 'Styl';
@override String get mood => 'Nastrój';
}
// Path: serverTasks
@@ -2114,6 +2125,17 @@ extension on TranslationsPl {
'metadataEdit.manuallySelected' => 'Wybrany ręcznie',
'metadataEdit.shownWithForeignAudio' => 'Wyświetlane przy obcojęzycznym audio',
'metadataEdit.alwaysEnabled' => 'Zawsze włączone',
'metadataEdit.tags' => 'Tagi',
'metadataEdit.addTag' => 'Dodaj tag',
'metadataEdit.genre' => 'Gatunek',
'metadataEdit.director' => 'Reżyser',
'metadataEdit.writer' => 'Scenarzysta',
'metadataEdit.producer' => 'Producent',
'metadataEdit.country' => 'Kraj',
'metadataEdit.collection' => 'Kolekcja',
'metadataEdit.label' => 'Etykieta',
'metadataEdit.style' => 'Styl',
'metadataEdit.mood' => 'Nastrój',
'serverTasks.title' => 'Zadania serwera',
'serverTasks.failedToLoad' => 'Nie udało się załadować zadań',
'serverTasks.noTasks' => 'Brak uruchomionych zadań',
+22
View File
@@ -1115,6 +1115,17 @@ class _TranslationsMetadataEditPt implements TranslationsMetadataEditEn {
@override String get manuallySelected => 'Seleção manual';
@override String get shownWithForeignAudio => 'Exibir com áudio estrangeiro';
@override String get alwaysEnabled => 'Sempre ativado';
@override String get tags => 'Tags';
@override String get addTag => 'Adicionar tag';
@override String get genre => 'Gênero';
@override String get director => 'Diretor';
@override String get writer => 'Roteirista';
@override String get producer => 'Produtor';
@override String get country => 'País';
@override String get collection => 'Coleção';
@override String get label => 'Rótulo';
@override String get style => 'Estilo';
@override String get mood => 'Humor';
}
// Path: serverTasks
@@ -2114,6 +2125,17 @@ extension on TranslationsPt {
'metadataEdit.manuallySelected' => 'Seleção manual',
'metadataEdit.shownWithForeignAudio' => 'Exibir com áudio estrangeiro',
'metadataEdit.alwaysEnabled' => 'Sempre ativado',
'metadataEdit.tags' => 'Tags',
'metadataEdit.addTag' => 'Adicionar tag',
'metadataEdit.genre' => 'Gênero',
'metadataEdit.director' => 'Diretor',
'metadataEdit.writer' => 'Roteirista',
'metadataEdit.producer' => 'Produtor',
'metadataEdit.country' => 'País',
'metadataEdit.collection' => 'Coleção',
'metadataEdit.label' => 'Rótulo',
'metadataEdit.style' => 'Estilo',
'metadataEdit.mood' => 'Humor',
'serverTasks.title' => 'Tarefas do servidor',
'serverTasks.failedToLoad' => 'Falha ao carregar tarefas',
'serverTasks.noTasks' => 'Nenhuma tarefa em execução',
+22
View File
@@ -1115,6 +1115,17 @@ class _TranslationsMetadataEditRu implements TranslationsMetadataEditEn {
@override String get manuallySelected => 'Выбор вручную';
@override String get shownWithForeignAudio => 'Показывать при иноязычном аудио';
@override String get alwaysEnabled => 'Всегда включены';
@override String get tags => 'Теги';
@override String get addTag => 'Добавить тег';
@override String get genre => 'Жанр';
@override String get director => 'Режиссёр';
@override String get writer => 'Сценарист';
@override String get producer => 'Продюсер';
@override String get country => 'Страна';
@override String get collection => 'Коллекция';
@override String get label => 'Метка';
@override String get style => 'Стиль';
@override String get mood => 'Настроение';
}
// Path: serverTasks
@@ -2114,6 +2125,17 @@ extension on TranslationsRu {
'metadataEdit.manuallySelected' => 'Выбор вручную',
'metadataEdit.shownWithForeignAudio' => 'Показывать при иноязычном аудио',
'metadataEdit.alwaysEnabled' => 'Всегда включены',
'metadataEdit.tags' => 'Теги',
'metadataEdit.addTag' => 'Добавить тег',
'metadataEdit.genre' => 'Жанр',
'metadataEdit.director' => 'Режиссёр',
'metadataEdit.writer' => 'Сценарист',
'metadataEdit.producer' => 'Продюсер',
'metadataEdit.country' => 'Страна',
'metadataEdit.collection' => 'Коллекция',
'metadataEdit.label' => 'Метка',
'metadataEdit.style' => 'Стиль',
'metadataEdit.mood' => 'Настроение',
'serverTasks.title' => 'Задачи сервера',
'serverTasks.failedToLoad' => 'Не удалось загрузить задачи',
'serverTasks.noTasks' => 'Нет выполняемых задач',
+22
View File
@@ -1115,6 +1115,17 @@ class _TranslationsMetadataEditSv implements TranslationsMetadataEditEn {
@override String get manuallySelected => 'Manuellt vald';
@override String get shownWithForeignAudio => 'Visas vid främmande ljud';
@override String get alwaysEnabled => 'Alltid aktiverad';
@override String get tags => 'Taggar';
@override String get addTag => 'Lägg till tagg';
@override String get genre => 'Genre';
@override String get director => 'Regissör';
@override String get writer => 'Författare';
@override String get producer => 'Producent';
@override String get country => 'Land';
@override String get collection => 'Samling';
@override String get label => 'Etikett';
@override String get style => 'Stil';
@override String get mood => 'Stämning';
}
// Path: serverTasks
@@ -2114,6 +2125,17 @@ extension on TranslationsSv {
'metadataEdit.manuallySelected' => 'Manuellt vald',
'metadataEdit.shownWithForeignAudio' => 'Visas vid främmande ljud',
'metadataEdit.alwaysEnabled' => 'Alltid aktiverad',
'metadataEdit.tags' => 'Taggar',
'metadataEdit.addTag' => 'Lägg till tagg',
'metadataEdit.genre' => 'Genre',
'metadataEdit.director' => 'Regissör',
'metadataEdit.writer' => 'Författare',
'metadataEdit.producer' => 'Producent',
'metadataEdit.country' => 'Land',
'metadataEdit.collection' => 'Samling',
'metadataEdit.label' => 'Etikett',
'metadataEdit.style' => 'Stil',
'metadataEdit.mood' => 'Stämning',
'serverTasks.title' => 'Serveruppgifter',
'serverTasks.failedToLoad' => 'Kunde inte ladda uppgifter',
'serverTasks.noTasks' => 'Inga pågående uppgifter',
+22
View File
@@ -1115,6 +1115,17 @@ class _TranslationsMetadataEditZh implements TranslationsMetadataEditEn {
@override String get manuallySelected => '手动选择';
@override String get shownWithForeignAudio => '外语音频时显示';
@override String get alwaysEnabled => '始终启用';
@override String get tags => '标签';
@override String get addTag => '添加标签';
@override String get genre => '类型';
@override String get director => '导演';
@override String get writer => '编剧';
@override String get producer => '制片人';
@override String get country => '国家';
@override String get collection => '合集';
@override String get label => '标记';
@override String get style => '风格';
@override String get mood => '氛围';
}
// Path: serverTasks
@@ -2114,6 +2125,17 @@ extension on TranslationsZh {
'metadataEdit.manuallySelected' => '手动选择',
'metadataEdit.shownWithForeignAudio' => '外语音频时显示',
'metadataEdit.alwaysEnabled' => '始终启用',
'metadataEdit.tags' => '标签',
'metadataEdit.addTag' => '添加标签',
'metadataEdit.genre' => '类型',
'metadataEdit.director' => '导演',
'metadataEdit.writer' => '编剧',
'metadataEdit.producer' => '制片人',
'metadataEdit.country' => '国家',
'metadataEdit.collection' => '合集',
'metadataEdit.label' => '标记',
'metadataEdit.style' => '风格',
'metadataEdit.mood' => '氛围',
'serverTasks.title' => '服务器任务',
'serverTasks.failedToLoad' => '加载任务失败',
'serverTasks.noTasks' => '没有正在运行的任务',
+12 -1
View File
@@ -891,7 +891,18 @@
"subtitleMode": "Automatiskt val av undertexter",
"manuallySelected": "Manuellt vald",
"shownWithForeignAudio": "Visas vid främmande ljud",
"alwaysEnabled": "Alltid aktiverad"
"alwaysEnabled": "Alltid aktiverad",
"tags": "Taggar",
"addTag": "Lägg till tagg",
"genre": "Genre",
"director": "Regissör",
"writer": "Författare",
"producer": "Producent",
"country": "Land",
"collection": "Samling",
"label": "Etikett",
"style": "Stil",
"mood": "Stämning"
},
"serverTasks": {
"title": "Serveruppgifter",
+12 -1
View File
@@ -891,7 +891,18 @@
"subtitleMode": "自动选择字幕模式",
"manuallySelected": "手动选择",
"shownWithForeignAudio": "外语音频时显示",
"alwaysEnabled": "始终启用"
"alwaysEnabled": "始终启用",
"tags": "标签",
"addTag": "添加标签",
"genre": "类型",
"director": "导演",
"writer": "编剧",
"producer": "制片人",
"country": "国家",
"collection": "合集",
"label": "标记",
"style": "风格",
"mood": "氛围"
},
"serverTasks": {
"title": "服务器任务",
+48
View File
@@ -13,6 +13,9 @@ part 'plex_metadata.g.dart';
Object? _readRatingKey(Map json, String key) =>
json['ratingKey'] ?? json['key'] ?? '';
List<String>? _tagsFromJson(List? json) =>
json?.cast<Map<String, dynamic>>().map((e) => e['tag'] as String).toList();
int? _flexibleInt(Object? v) => switch (v) {
num n => n.toInt(),
String s => int.tryParse(s),
@@ -102,6 +105,24 @@ class PlexMetadata with MultiServerFields {
final List<PlexRole>? role; // Cast members
@JsonKey(name: 'Media', includeToJson: false)
final List<PlexMediaVersion>? mediaVersions; // Available media versions/editions
@JsonKey(name: 'Genre', fromJson: _tagsFromJson, includeToJson: false)
final List<String>? genre;
@JsonKey(name: 'Director', fromJson: _tagsFromJson, includeToJson: false)
final List<String>? director;
@JsonKey(name: 'Writer', fromJson: _tagsFromJson, includeToJson: false)
final List<String>? writer;
@JsonKey(name: 'Producer', fromJson: _tagsFromJson, includeToJson: false)
final List<String>? producer;
@JsonKey(name: 'Country', fromJson: _tagsFromJson, includeToJson: false)
final List<String>? country;
@JsonKey(name: 'Collection', fromJson: _tagsFromJson, includeToJson: false)
final List<String>? collection;
@JsonKey(name: 'Label', fromJson: _tagsFromJson, includeToJson: false)
final List<String>? label;
@JsonKey(name: 'Style', fromJson: _tagsFromJson, includeToJson: false)
final List<String>? style;
@JsonKey(name: 'Mood', fromJson: _tagsFromJson, includeToJson: false)
final List<String>? mood;
final String? audioLanguage; // Per-media preferred audio language
final String? subtitleLanguage; // Per-media preferred subtitle language
@JsonKey(fromJson: _flexibleInt)
@@ -205,6 +226,15 @@ class PlexMetadata with MultiServerFields {
this.childCount,
this.role,
this.mediaVersions,
this.genre,
this.director,
this.writer,
this.producer,
this.country,
this.collection,
this.label,
this.style,
this.mood,
this.audioLanguage,
this.subtitleLanguage,
this.subtitleMode,
@@ -265,6 +295,15 @@ class PlexMetadata with MultiServerFields {
int? childCount,
List<PlexRole>? role,
List<PlexMediaVersion>? mediaVersions,
List<String>? genre,
List<String>? director,
List<String>? writer,
List<String>? producer,
List<String>? country,
List<String>? collection,
List<String>? label,
List<String>? style,
List<String>? mood,
String? audioLanguage,
String? subtitleLanguage,
int? subtitleMode,
@@ -323,6 +362,15 @@ class PlexMetadata with MultiServerFields {
childCount: childCount ?? this.childCount,
role: role ?? this.role,
mediaVersions: mediaVersions ?? this.mediaVersions,
genre: genre ?? this.genre,
director: director ?? this.director,
writer: writer ?? this.writer,
producer: producer ?? this.producer,
country: country ?? this.country,
collection: collection ?? this.collection,
label: label ?? this.label,
style: style ?? this.style,
mood: mood ?? this.mood,
audioLanguage: audioLanguage ?? this.audioLanguage,
subtitleLanguage: subtitleLanguage ?? this.subtitleLanguage,
subtitleMode: subtitleMode ?? this.subtitleMode,
+9
View File
@@ -48,6 +48,15 @@ PlexMetadata _$PlexMetadataFromJson(Map<String, dynamic> json) => PlexMetadata(
mediaVersions: (json['Media'] as List<dynamic>?)
?.map((e) => PlexMediaVersion.fromJson(e as Map<String, dynamic>))
.toList(),
genre: _tagsFromJson(json['Genre'] as List?),
director: _tagsFromJson(json['Director'] as List?),
writer: _tagsFromJson(json['Writer'] as List?),
producer: _tagsFromJson(json['Producer'] as List?),
country: _tagsFromJson(json['Country'] as List?),
collection: _tagsFromJson(json['Collection'] as List?),
label: _tagsFromJson(json['Label'] as List?),
style: _tagsFromJson(json['Style'] as List?),
mood: _tagsFromJson(json['Mood'] as List?),
audioLanguage: json['audioLanguage'] as String?,
subtitleLanguage: json['subtitleLanguage'] as String?,
subtitleMode: _flexibleInt(json['subtitleMode']),
+118 -1
View File
@@ -13,6 +13,7 @@ import '../widgets/artwork_picker_dialog.dart';
import '../widgets/focusable_list_tile.dart';
import '../widgets/focused_scroll_scaffold.dart';
import '../widgets/plex_optimized_image.dart';
import '../widgets/tag_edit_dialog.dart';
class MetadataEditScreen extends StatefulWidget {
final PlexMetadata metadata;
@@ -49,9 +50,19 @@ class _MetadataEditScreenState extends State<MetadataEditScreen> {
String? _origTagline;
String? _origSummary;
// Tag field values
final Map<String, List<String>> _tags = {};
final Map<String, List<String>> _origTags = {};
// Advanced prefs (loaded from metadata JSON)
final Map<String, String> _currentPrefs = {};
static bool _tagsEqual(List<String> a, List<String> b) =>
a.length == b.length && a.every((e) => b.contains(e));
bool get _hasTagChanges => _tags.keys.any(
(k) => !_tagsEqual(_tags[k] ?? [], _origTags[k] ?? []));
bool get _hasChanges =>
_title != _origTitle ||
_titleSort != _origTitleSort ||
@@ -60,7 +71,8 @@ class _MetadataEditScreenState extends State<MetadataEditScreen> {
_contentRating != _origContentRating ||
_studio != _origStudio ||
_tagline != _origTagline ||
_summary != _origSummary;
_summary != _origSummary ||
_hasTagChanges;
PlexMediaType get _mediaType => widget.metadata.mediaType;
@@ -117,6 +129,21 @@ class _MetadataEditScreenState extends State<MetadataEditScreen> {
_origStudio = _studio;
_origTagline = _tagline;
_origSummary = _summary;
void initTag(String key, List<String>? values) {
_tags[key] = List.of(values ?? []);
_origTags[key] = List.of(values ?? []);
}
initTag('genre', meta.genre);
initTag('director', meta.director);
initTag('writer', meta.writer);
initTag('producer', meta.producer);
initTag('country', meta.country);
initTag('collection', meta.collection);
initTag('label', meta.label);
initTag('style', meta.style);
initTag('mood', meta.mood);
}
Future<void> _save() async {
@@ -130,6 +157,16 @@ class _MetadataEditScreenState extends State<MetadataEditScreen> {
setState(() => _isSaving = true);
Map<String, ({List<String> current, List<String> original})>? tagChanges;
for (final key in _tags.keys) {
final current = _tags[key] ?? [];
final original = _origTags[key] ?? [];
if (!_tagsEqual(current, original)) {
tagChanges ??= {};
tagChanges[key] = (current: current, original: original);
}
}
final success = await _client.updateMetadata(
sectionId: sectionId,
ratingKey: widget.metadata.ratingKey,
@@ -142,6 +179,7 @@ class _MetadataEditScreenState extends State<MetadataEditScreen> {
studio: _studio != _origStudio ? _studio : null,
tagline: _tagline != _origTagline ? _tagline : null,
summary: _summary != _origSummary ? _summary : null,
tagChanges: tagChanges,
);
if (!mounted) return;
@@ -331,6 +369,57 @@ class _MetadataEditScreenState extends State<MetadataEditScreen> {
_mediaType == PlexMediaType.movie || _mediaType == PlexMediaType.show || _mediaType == PlexMediaType.episode;
bool get _showAdvanced => _mediaType != PlexMediaType.episode;
List<({String key, String label})> get _tagFields {
switch (_mediaType) {
case PlexMediaType.movie:
case PlexMediaType.show:
return [
(key: 'genre', label: t.metadataEdit.genre),
(key: 'director', label: t.metadataEdit.director),
(key: 'writer', label: t.metadataEdit.writer),
(key: 'producer', label: t.metadataEdit.producer),
(key: 'country', label: t.metadataEdit.country),
(key: 'collection', label: t.metadataEdit.collection),
(key: 'label', label: t.metadataEdit.label),
];
case PlexMediaType.episode:
return [
(key: 'director', label: t.metadataEdit.director),
(key: 'writer', label: t.metadataEdit.writer),
];
case PlexMediaType.artist:
return [
(key: 'genre', label: t.metadataEdit.genre),
(key: 'style', label: t.metadataEdit.style),
(key: 'mood', label: t.metadataEdit.mood),
(key: 'country', label: t.metadataEdit.country),
(key: 'collection', label: t.metadataEdit.collection),
];
case PlexMediaType.album:
return [
(key: 'genre', label: t.metadataEdit.genre),
(key: 'style', label: t.metadataEdit.style),
(key: 'mood', label: t.metadataEdit.mood),
(key: 'collection', label: t.metadataEdit.collection),
];
default:
return [];
}
}
Future<void> _editTag(String key, String label) async {
final result = await showDialog<List<String>>(
context: context,
builder: (context) => TagEditDialog(
title: label,
initialTags: _tags[key] ?? [],
),
);
if (result != null && mounted) {
setState(() => _tags[key] = result);
}
}
@override
Widget build(BuildContext context) {
if (_isLoading) {
@@ -360,6 +449,10 @@ class _MetadataEditScreenState extends State<MetadataEditScreen> {
sliver: SliverList(
delegate: SliverChildListDelegate([
_buildBasicInfoCard(),
if (_tagFields.isNotEmpty) ...[
const SizedBox(height: 16),
_buildTagsCard(),
],
const SizedBox(height: 16),
_buildArtworkCard(),
if (_showAdvanced) ...[
@@ -489,6 +582,30 @@ class _MetadataEditScreenState extends State<MetadataEditScreen> {
);
}
Widget _buildTagsCard() {
final fields = _tagFields;
return Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Text(
t.metadataEdit.tags,
style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
),
),
for (final field in fields)
_buildFieldTile(
label: field.label,
value: (_tags[field.key] ?? []).isEmpty ? null : (_tags[field.key]!).join(', '),
onTap: () => _editTag(field.key, field.label),
),
],
),
);
}
Widget _buildArtworkCard() {
final meta = _fullMetadata ?? widget.metadata;
+18
View File
@@ -1677,6 +1677,7 @@ class PlexClient {
String? studio,
String? tagline,
String? summary,
Map<String, ({List<String> current, List<String> original})>? tagChanges,
}) {
final queryParams = <String, dynamic>{'type': typeNumber, 'id': ratingKey};
@@ -1696,6 +1697,23 @@ class PlexClient {
addField('tagline', tagline);
addField('summary', summary);
if (tagChanges != null) {
for (final entry in tagChanges.entries) {
final field = entry.key;
final current = entry.value.current;
final original = entry.value.original;
for (var i = 0; i < current.length; i++) {
queryParams['$field[$i].tag.tag'] = current[i];
}
final removed = original.where((t) => !current.contains(t)).toList();
if (removed.isNotEmpty) {
queryParams['$field[].tag.tag-'] =
removed.map(Uri.encodeComponent).join(',');
}
queryParams['$field.locked'] = '1';
}
}
return _wrapBoolApiCall(
() => _dio.put('/library/sections/$sectionId/all', queryParameters: queryParams),
'Failed to update metadata',
+125
View File
@@ -0,0 +1,125 @@
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
import '../focus/dpad_navigator.dart';
import '../focus/focusable_button.dart';
import '../i18n/strings.g.dart';
import '../widgets/app_icon.dart';
import '../widgets/focusable_list_tile.dart';
class TagEditDialog extends StatefulWidget {
final String title;
final List<String> initialTags;
const TagEditDialog({super.key, required this.title, required this.initialTags});
@override
State<TagEditDialog> createState() => _TagEditDialogState();
}
class _TagEditDialogState extends State<TagEditDialog> {
late final TextEditingController _controller;
late final FocusNode _textFieldFocusNode;
late final List<String> _tags;
final _saveFocusNode = FocusNode();
@override
void initState() {
super.initState();
_controller = TextEditingController();
_textFieldFocusNode = FocusNode(
onKeyEvent: (node, event) {
if (!event.isActionable) return KeyEventResult.ignored;
if (event.logicalKey.isDownKey) {
node.nextFocus();
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
},
);
_tags = List.of(widget.initialTags);
}
@override
void dispose() {
_controller.dispose();
_textFieldFocusNode.dispose();
_saveFocusNode.dispose();
super.dispose();
}
void _addTag() {
final text = _controller.text.trim();
if (text.isEmpty || _tags.contains(text)) return;
setState(() {
_tags.add(text);
_controller.clear();
});
_textFieldFocusNode.requestFocus();
}
void _removeTag(int index) {
setState(() => _tags.removeAt(index));
_textFieldFocusNode.requestFocus();
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(widget.title),
content: SizedBox(
width: 400,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: _controller,
focusNode: _textFieldFocusNode,
autofocus: true,
decoration: InputDecoration(
labelText: t.metadataEdit.addTag,
suffixIcon: FocusableButton(
onPressed: _addTag,
child: IconButton(
icon: const AppIcon(Symbols.add_rounded),
onPressed: _addTag,
),
),
),
textInputAction: TextInputAction.done,
onSubmitted: (_) => _addTag(),
),
if (_tags.isNotEmpty) ...[
const SizedBox(height: 12),
ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 300),
child: ListView.builder(
shrinkWrap: true,
itemCount: _tags.length,
itemBuilder: (context, index) => FocusableListTile(
title: Text(_tags[index]),
trailing: const AppIcon(Symbols.close_rounded),
onTap: () => _removeTag(index),
),
),
),
],
],
),
),
actions: [
FocusableButton(
onPressed: () => Navigator.pop(context),
child: TextButton(onPressed: () => Navigator.pop(context), child: Text(t.common.cancel)),
),
FocusableButton(
focusNode: _saveFocusNode,
onPressed: () => Navigator.pop(context, _tags),
child: TextButton(
onPressed: () => Navigator.pop(context, _tags),
child: Text(t.common.save),
),
),
],
);
}
}