From 366ae67341a56b436e569b590305b38f4316f7b6 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Mon, 25 May 2026 22:14:49 +0200 Subject: [PATCH] feat(jellyfin): add unwatched library filter close #1116 --- lib/i18n/da.i18n.json | 3 +- lib/i18n/de.i18n.json | 3 +- lib/i18n/en.i18n.json | 3 +- lib/i18n/es.i18n.json | 3 +- lib/i18n/fr.i18n.json | 3 +- lib/i18n/it.i18n.json | 3 +- lib/i18n/ja.i18n.json | 3 +- lib/i18n/ko.i18n.json | 3 +- lib/i18n/nb.i18n.json | 3 +- lib/i18n/nl.i18n.json | 3 +- lib/i18n/pl.i18n.json | 3 +- lib/i18n/pt.i18n.json | 3 +- lib/i18n/ru.i18n.json | 3 +- lib/i18n/strings.g.dart | 2 +- lib/i18n/strings_da.g.dart | 4 ++- lib/i18n/strings_de.g.dart | 4 ++- lib/i18n/strings_en.g.dart | 6 +++- lib/i18n/strings_es.g.dart | 4 ++- lib/i18n/strings_fr.g.dart | 4 ++- lib/i18n/strings_it.g.dart | 4 ++- lib/i18n/strings_ja.g.dart | 4 ++- lib/i18n/strings_ko.g.dart | 4 ++- lib/i18n/strings_nb.g.dart | 4 ++- lib/i18n/strings_nl.g.dart | 4 ++- lib/i18n/strings_pl.g.dart | 4 ++- lib/i18n/strings_pt.g.dart | 4 ++- lib/i18n/strings_ru.g.dart | 4 ++- lib/i18n/strings_sv.g.dart | 4 ++- lib/i18n/strings_zh.g.dart | 4 ++- lib/i18n/sv.i18n.json | 3 +- lib/i18n/zh.i18n.json | 3 +- .../jellyfin_client/parts/browse.dart | 24 +++++++++---- test/services/jellyfin_client_urls_test.dart | 35 +++++++++++++++++++ 33 files changed, 130 insertions(+), 38 deletions(-) diff --git a/lib/i18n/da.i18n.json b/lib/i18n/da.i18n.json index fee5456c..a7d734b9 100644 --- a/lib/i18n/da.i18n.json +++ b/lib/i18n/da.i18n.json @@ -705,7 +705,8 @@ "genre": "Genre", "year": "År", "contentRating": "Aldersvurdering", - "tag": "Tag" + "tag": "Tag", + "unwatched": "Usete" }, "sortLabels": { "title": "Titel", diff --git a/lib/i18n/de.i18n.json b/lib/i18n/de.i18n.json index 038ea621..91c78e92 100644 --- a/lib/i18n/de.i18n.json +++ b/lib/i18n/de.i18n.json @@ -705,7 +705,8 @@ "genre": "Genre", "year": "Jahr", "contentRating": "Altersfreigabe", - "tag": "Tag" + "tag": "Tag", + "unwatched": "Ungesehene" }, "sortLabels": { "title": "Titel", diff --git a/lib/i18n/en.i18n.json b/lib/i18n/en.i18n.json index 754487d2..8cf366c6 100644 --- a/lib/i18n/en.i18n.json +++ b/lib/i18n/en.i18n.json @@ -705,7 +705,8 @@ "genre": "Genre", "year": "Year", "contentRating": "Content Rating", - "tag": "Tag" + "tag": "Tag", + "unwatched": "Unwatched" }, "sortLabels": { "title": "Title", diff --git a/lib/i18n/es.i18n.json b/lib/i18n/es.i18n.json index 9af7ea27..ddfccfe9 100644 --- a/lib/i18n/es.i18n.json +++ b/lib/i18n/es.i18n.json @@ -705,7 +705,8 @@ "genre": "Género", "year": "Año", "contentRating": "Clasificación", - "tag": "Etiqueta" + "tag": "Etiqueta", + "unwatched": "No vistos" }, "sortLabels": { "title": "Título", diff --git a/lib/i18n/fr.i18n.json b/lib/i18n/fr.i18n.json index 9f098928..3de3d7a5 100644 --- a/lib/i18n/fr.i18n.json +++ b/lib/i18n/fr.i18n.json @@ -705,7 +705,8 @@ "genre": "Genre", "year": "Année", "contentRating": "Classification", - "tag": "Tag" + "tag": "Tag", + "unwatched": "Non vus" }, "sortLabels": { "title": "Titre", diff --git a/lib/i18n/it.i18n.json b/lib/i18n/it.i18n.json index 9e9e118f..21e69c7b 100644 --- a/lib/i18n/it.i18n.json +++ b/lib/i18n/it.i18n.json @@ -705,7 +705,8 @@ "genre": "Genere", "year": "Anno", "contentRating": "Classificazione", - "tag": "Tag" + "tag": "Tag", + "unwatched": "Non visti" }, "sortLabels": { "title": "Titolo", diff --git a/lib/i18n/ja.i18n.json b/lib/i18n/ja.i18n.json index 9b3ba949..540fbf58 100644 --- a/lib/i18n/ja.i18n.json +++ b/lib/i18n/ja.i18n.json @@ -705,7 +705,8 @@ "genre": "ジャンル", "year": "年", "contentRating": "視聴年齢区分", - "tag": "タグ" + "tag": "タグ", + "unwatched": "未視聴" }, "sortLabels": { "title": "タイトル", diff --git a/lib/i18n/ko.i18n.json b/lib/i18n/ko.i18n.json index 5e68e1f6..c622841c 100644 --- a/lib/i18n/ko.i18n.json +++ b/lib/i18n/ko.i18n.json @@ -705,7 +705,8 @@ "genre": "장르", "year": "연도", "contentRating": "시청 등급", - "tag": "태그" + "tag": "태그", + "unwatched": "미시청" }, "sortLabels": { "title": "제목", diff --git a/lib/i18n/nb.i18n.json b/lib/i18n/nb.i18n.json index 2697cbf4..33f0c58d 100644 --- a/lib/i18n/nb.i18n.json +++ b/lib/i18n/nb.i18n.json @@ -705,7 +705,8 @@ "genre": "Sjanger", "year": "År", "contentRating": "Aldersgrense", - "tag": "Tag" + "tag": "Tag", + "unwatched": "Usette" }, "sortLabels": { "title": "Tittel", diff --git a/lib/i18n/nl.i18n.json b/lib/i18n/nl.i18n.json index b221a465..f77d0559 100644 --- a/lib/i18n/nl.i18n.json +++ b/lib/i18n/nl.i18n.json @@ -705,7 +705,8 @@ "genre": "Genre", "year": "Jaar", "contentRating": "Leeftijdsclassificatie", - "tag": "Tag" + "tag": "Tag", + "unwatched": "Onbekeken" }, "sortLabels": { "title": "Titel", diff --git a/lib/i18n/pl.i18n.json b/lib/i18n/pl.i18n.json index 5075178e..ee55715e 100644 --- a/lib/i18n/pl.i18n.json +++ b/lib/i18n/pl.i18n.json @@ -705,7 +705,8 @@ "genre": "Gatunek", "year": "Rok", "contentRating": "Klasyfikacja wiekowa", - "tag": "Tag" + "tag": "Tag", + "unwatched": "Nieobejrzane" }, "sortLabels": { "title": "Tytuł", diff --git a/lib/i18n/pt.i18n.json b/lib/i18n/pt.i18n.json index 3c35f17e..3b39f013 100644 --- a/lib/i18n/pt.i18n.json +++ b/lib/i18n/pt.i18n.json @@ -705,7 +705,8 @@ "genre": "Gênero", "year": "Ano", "contentRating": "Classificação", - "tag": "Tag" + "tag": "Tag", + "unwatched": "Não assistidos" }, "sortLabels": { "title": "Título", diff --git a/lib/i18n/ru.i18n.json b/lib/i18n/ru.i18n.json index 6e5872bf..d252ed7b 100644 --- a/lib/i18n/ru.i18n.json +++ b/lib/i18n/ru.i18n.json @@ -705,7 +705,8 @@ "genre": "Жанр", "year": "Год", "contentRating": "Возрастной рейтинг", - "tag": "Тег" + "tag": "Тег", + "unwatched": "Непросмотренные" }, "sortLabels": { "title": "Название", diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index 8c1b0f9d..08ba1af6 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -4,7 +4,7 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 15 -/// Strings: 17565 (1171 per locale) +/// Strings: 17580 (1172 per locale) // coverage:ignore-file // ignore_for_file: type=lint, unused_import diff --git a/lib/i18n/strings_da.g.dart b/lib/i18n/strings_da.g.dart index add96d7d..401696bf 100644 --- a/lib/i18n/strings_da.g.dart +++ b/lib/i18n/strings_da.g.dart @@ -1621,6 +1621,7 @@ class _TranslationsLibrariesFilterCategoriesDa extends TranslationsLibrariesFilt @override String get year => 'År'; @override String get contentRating => 'Aldersvurdering'; @override String get tag => 'Tag'; + @override String get unwatched => 'Usete'; } // Path: libraries.sortLabels @@ -2453,6 +2454,7 @@ extension on TranslationsDa { 'libraries.filterCategories.year' => 'År', 'libraries.filterCategories.contentRating' => 'Aldersvurdering', 'libraries.filterCategories.tag' => 'Tag', + 'libraries.filterCategories.unwatched' => 'Usete', 'libraries.sortLabels.title' => 'Titel', 'libraries.sortLabels.dateAdded' => 'Tilføjet dato', 'libraries.sortLabels.releaseDate' => 'Udgivelsesdato', @@ -2825,9 +2827,9 @@ extension on TranslationsDa { 'metadataEdit.summary' => 'Resumé', 'metadataEdit.poster' => 'Plakat', 'metadataEdit.background' => 'Baggrund', - 'metadataEdit.logo' => 'Logo', _ => null, } ?? switch (path) { + 'metadataEdit.logo' => 'Logo', 'metadataEdit.squareArt' => 'Kvadratisk billede', 'metadataEdit.selectPoster' => 'Vælg plakat', 'metadataEdit.selectBackground' => 'Vælg baggrund', diff --git a/lib/i18n/strings_de.g.dart b/lib/i18n/strings_de.g.dart index 960cd39b..21539616 100644 --- a/lib/i18n/strings_de.g.dart +++ b/lib/i18n/strings_de.g.dart @@ -1621,6 +1621,7 @@ class _TranslationsLibrariesFilterCategoriesDe extends TranslationsLibrariesFilt @override String get year => 'Jahr'; @override String get contentRating => 'Altersfreigabe'; @override String get tag => 'Tag'; + @override String get unwatched => 'Ungesehene'; } // Path: libraries.sortLabels @@ -2453,6 +2454,7 @@ extension on TranslationsDe { 'libraries.filterCategories.year' => 'Jahr', 'libraries.filterCategories.contentRating' => 'Altersfreigabe', 'libraries.filterCategories.tag' => 'Tag', + 'libraries.filterCategories.unwatched' => 'Ungesehene', 'libraries.sortLabels.title' => 'Titel', 'libraries.sortLabels.dateAdded' => 'Hinzugefügt am', 'libraries.sortLabels.releaseDate' => 'Erscheinungsdatum', @@ -2825,9 +2827,9 @@ extension on TranslationsDe { 'metadataEdit.summary' => 'Zusammenfassung', 'metadataEdit.poster' => 'Poster', 'metadataEdit.background' => 'Hintergrund', - 'metadataEdit.logo' => 'Logo', _ => null, } ?? switch (path) { + 'metadataEdit.logo' => 'Logo', 'metadataEdit.squareArt' => 'Quadratisches Bild', 'metadataEdit.selectPoster' => 'Poster auswählen', 'metadataEdit.selectBackground' => 'Hintergrund auswählen', diff --git a/lib/i18n/strings_en.g.dart b/lib/i18n/strings_en.g.dart index e388edec..6a7b56c6 100644 --- a/lib/i18n/strings_en.g.dart +++ b/lib/i18n/strings_en.g.dart @@ -3775,6 +3775,9 @@ class TranslationsLibrariesFilterCategoriesEn { /// en: 'Tag' String get tag => 'Tag'; + + /// en: 'Unwatched' + String get unwatched => 'Unwatched'; } // Path: libraries.sortLabels @@ -4801,6 +4804,7 @@ extension on Translations { 'libraries.filterCategories.year' => 'Year', 'libraries.filterCategories.contentRating' => 'Content Rating', 'libraries.filterCategories.tag' => 'Tag', + 'libraries.filterCategories.unwatched' => 'Unwatched', 'libraries.sortLabels.title' => 'Title', 'libraries.sortLabels.dateAdded' => 'Date Added', 'libraries.sortLabels.releaseDate' => 'Release Date', @@ -5173,9 +5177,9 @@ extension on Translations { 'metadataEdit.summary' => 'Summary', 'metadataEdit.poster' => 'Poster', 'metadataEdit.background' => 'Background', - 'metadataEdit.logo' => 'Logo', _ => null, } ?? switch (path) { + 'metadataEdit.logo' => 'Logo', 'metadataEdit.squareArt' => 'Square Art', 'metadataEdit.selectPoster' => 'Select Poster', 'metadataEdit.selectBackground' => 'Select Background', diff --git a/lib/i18n/strings_es.g.dart b/lib/i18n/strings_es.g.dart index aa952a6b..773b48d9 100644 --- a/lib/i18n/strings_es.g.dart +++ b/lib/i18n/strings_es.g.dart @@ -1621,6 +1621,7 @@ class _TranslationsLibrariesFilterCategoriesEs extends TranslationsLibrariesFilt @override String get year => 'Año'; @override String get contentRating => 'Clasificación'; @override String get tag => 'Etiqueta'; + @override String get unwatched => 'No vistos'; } // Path: libraries.sortLabels @@ -2453,6 +2454,7 @@ extension on TranslationsEs { 'libraries.filterCategories.year' => 'Año', 'libraries.filterCategories.contentRating' => 'Clasificación', 'libraries.filterCategories.tag' => 'Etiqueta', + 'libraries.filterCategories.unwatched' => 'No vistos', 'libraries.sortLabels.title' => 'Título', 'libraries.sortLabels.dateAdded' => 'Fecha de adición', 'libraries.sortLabels.releaseDate' => 'Fecha de estreno', @@ -2825,9 +2827,9 @@ extension on TranslationsEs { 'metadataEdit.summary' => 'Resumen', 'metadataEdit.poster' => 'Póster', 'metadataEdit.background' => 'Fondo', - 'metadataEdit.logo' => 'Logo', _ => null, } ?? switch (path) { + 'metadataEdit.logo' => 'Logo', 'metadataEdit.squareArt' => 'Imagen cuadrada', 'metadataEdit.selectPoster' => 'Seleccionar póster', 'metadataEdit.selectBackground' => 'Seleccionar fondo', diff --git a/lib/i18n/strings_fr.g.dart b/lib/i18n/strings_fr.g.dart index a706829e..864a5268 100644 --- a/lib/i18n/strings_fr.g.dart +++ b/lib/i18n/strings_fr.g.dart @@ -1621,6 +1621,7 @@ class _TranslationsLibrariesFilterCategoriesFr extends TranslationsLibrariesFilt @override String get year => 'Année'; @override String get contentRating => 'Classification'; @override String get tag => 'Tag'; + @override String get unwatched => 'Non vus'; } // Path: libraries.sortLabels @@ -2453,6 +2454,7 @@ extension on TranslationsFr { 'libraries.filterCategories.year' => 'Année', 'libraries.filterCategories.contentRating' => 'Classification', 'libraries.filterCategories.tag' => 'Tag', + 'libraries.filterCategories.unwatched' => 'Non vus', 'libraries.sortLabels.title' => 'Titre', 'libraries.sortLabels.dateAdded' => 'Date d\'ajout', 'libraries.sortLabels.releaseDate' => 'Date de sortie', @@ -2825,9 +2827,9 @@ extension on TranslationsFr { 'metadataEdit.summary' => 'Résumé', 'metadataEdit.poster' => 'Affiche', 'metadataEdit.background' => 'Arrière-plan', - 'metadataEdit.logo' => 'Logo', _ => null, } ?? switch (path) { + 'metadataEdit.logo' => 'Logo', 'metadataEdit.squareArt' => 'Image carrée', 'metadataEdit.selectPoster' => 'Sélectionner l\'affiche', 'metadataEdit.selectBackground' => 'Sélectionner l\'arrière-plan', diff --git a/lib/i18n/strings_it.g.dart b/lib/i18n/strings_it.g.dart index f51b1ad7..1c443686 100644 --- a/lib/i18n/strings_it.g.dart +++ b/lib/i18n/strings_it.g.dart @@ -1621,6 +1621,7 @@ class _TranslationsLibrariesFilterCategoriesIt extends TranslationsLibrariesFilt @override String get year => 'Anno'; @override String get contentRating => 'Classificazione'; @override String get tag => 'Tag'; + @override String get unwatched => 'Non visti'; } // Path: libraries.sortLabels @@ -2453,6 +2454,7 @@ extension on TranslationsIt { 'libraries.filterCategories.year' => 'Anno', 'libraries.filterCategories.contentRating' => 'Classificazione', 'libraries.filterCategories.tag' => 'Tag', + 'libraries.filterCategories.unwatched' => 'Non visti', 'libraries.sortLabels.title' => 'Titolo', 'libraries.sortLabels.dateAdded' => 'Data di aggiunta', 'libraries.sortLabels.releaseDate' => 'Data di uscita', @@ -2825,9 +2827,9 @@ extension on TranslationsIt { 'metadataEdit.summary' => 'Trama', 'metadataEdit.poster' => 'Poster', 'metadataEdit.background' => 'Sfondo', - 'metadataEdit.logo' => 'Logo', _ => null, } ?? switch (path) { + 'metadataEdit.logo' => 'Logo', 'metadataEdit.squareArt' => 'Immagine quadrata', 'metadataEdit.selectPoster' => 'Seleziona poster', 'metadataEdit.selectBackground' => 'Seleziona sfondo', diff --git a/lib/i18n/strings_ja.g.dart b/lib/i18n/strings_ja.g.dart index e730c9bf..c56acd7c 100644 --- a/lib/i18n/strings_ja.g.dart +++ b/lib/i18n/strings_ja.g.dart @@ -1621,6 +1621,7 @@ class _TranslationsLibrariesFilterCategoriesJa extends TranslationsLibrariesFilt @override String get year => '年'; @override String get contentRating => '視聴年齢区分'; @override String get tag => 'タグ'; + @override String get unwatched => '未視聴'; } // Path: libraries.sortLabels @@ -2453,6 +2454,7 @@ extension on TranslationsJa { 'libraries.filterCategories.year' => '年', 'libraries.filterCategories.contentRating' => '視聴年齢区分', 'libraries.filterCategories.tag' => 'タグ', + 'libraries.filterCategories.unwatched' => '未視聴', 'libraries.sortLabels.title' => 'タイトル', 'libraries.sortLabels.dateAdded' => '追加日', 'libraries.sortLabels.releaseDate' => 'リリース日', @@ -2825,9 +2827,9 @@ extension on TranslationsJa { 'metadataEdit.summary' => 'あらすじ', 'metadataEdit.poster' => 'ポスター', 'metadataEdit.background' => '背景', - 'metadataEdit.logo' => 'ロゴ', _ => null, } ?? switch (path) { + 'metadataEdit.logo' => 'ロゴ', 'metadataEdit.squareArt' => '正方形アート', 'metadataEdit.selectPoster' => 'ポスターを選択', 'metadataEdit.selectBackground' => '背景を選択', diff --git a/lib/i18n/strings_ko.g.dart b/lib/i18n/strings_ko.g.dart index 6994b620..7cc3a616 100644 --- a/lib/i18n/strings_ko.g.dart +++ b/lib/i18n/strings_ko.g.dart @@ -1621,6 +1621,7 @@ class _TranslationsLibrariesFilterCategoriesKo extends TranslationsLibrariesFilt @override String get year => '연도'; @override String get contentRating => '시청 등급'; @override String get tag => '태그'; + @override String get unwatched => '미시청'; } // Path: libraries.sortLabels @@ -2453,6 +2454,7 @@ extension on TranslationsKo { 'libraries.filterCategories.year' => '연도', 'libraries.filterCategories.contentRating' => '시청 등급', 'libraries.filterCategories.tag' => '태그', + 'libraries.filterCategories.unwatched' => '미시청', 'libraries.sortLabels.title' => '제목', 'libraries.sortLabels.dateAdded' => '추가된 날짜', 'libraries.sortLabels.releaseDate' => '출시일', @@ -2825,9 +2827,9 @@ extension on TranslationsKo { 'metadataEdit.summary' => '줄거리', 'metadataEdit.poster' => '포스터', 'metadataEdit.background' => '배경', - 'metadataEdit.logo' => '로고', _ => null, } ?? switch (path) { + 'metadataEdit.logo' => '로고', 'metadataEdit.squareArt' => '정사각형 아트', 'metadataEdit.selectPoster' => '포스터 선택', 'metadataEdit.selectBackground' => '배경 선택', diff --git a/lib/i18n/strings_nb.g.dart b/lib/i18n/strings_nb.g.dart index d4a2e93a..9dbc1a60 100644 --- a/lib/i18n/strings_nb.g.dart +++ b/lib/i18n/strings_nb.g.dart @@ -1621,6 +1621,7 @@ class _TranslationsLibrariesFilterCategoriesNb extends TranslationsLibrariesFilt @override String get year => 'År'; @override String get contentRating => 'Aldersgrense'; @override String get tag => 'Tag'; + @override String get unwatched => 'Usette'; } // Path: libraries.sortLabels @@ -2453,6 +2454,7 @@ extension on TranslationsNb { 'libraries.filterCategories.year' => 'År', 'libraries.filterCategories.contentRating' => 'Aldersgrense', 'libraries.filterCategories.tag' => 'Tag', + 'libraries.filterCategories.unwatched' => 'Usette', 'libraries.sortLabels.title' => 'Tittel', 'libraries.sortLabels.dateAdded' => 'Lagt til-dato', 'libraries.sortLabels.releaseDate' => 'Utgivelsesdato', @@ -2825,9 +2827,9 @@ extension on TranslationsNb { 'metadataEdit.summary' => 'Sammendrag', 'metadataEdit.poster' => 'Plakat', 'metadataEdit.background' => 'Bakgrunn', - 'metadataEdit.logo' => 'Logo', _ => null, } ?? switch (path) { + 'metadataEdit.logo' => 'Logo', 'metadataEdit.squareArt' => 'Kvadratisk bilde', 'metadataEdit.selectPoster' => 'Velg plakat', 'metadataEdit.selectBackground' => 'Velg bakgrunn', diff --git a/lib/i18n/strings_nl.g.dart b/lib/i18n/strings_nl.g.dart index 8f013b64..669571e9 100644 --- a/lib/i18n/strings_nl.g.dart +++ b/lib/i18n/strings_nl.g.dart @@ -1621,6 +1621,7 @@ class _TranslationsLibrariesFilterCategoriesNl extends TranslationsLibrariesFilt @override String get year => 'Jaar'; @override String get contentRating => 'Leeftijdsclassificatie'; @override String get tag => 'Tag'; + @override String get unwatched => 'Onbekeken'; } // Path: libraries.sortLabels @@ -2453,6 +2454,7 @@ extension on TranslationsNl { 'libraries.filterCategories.year' => 'Jaar', 'libraries.filterCategories.contentRating' => 'Leeftijdsclassificatie', 'libraries.filterCategories.tag' => 'Tag', + 'libraries.filterCategories.unwatched' => 'Onbekeken', 'libraries.sortLabels.title' => 'Titel', 'libraries.sortLabels.dateAdded' => 'Toegevoegd op', 'libraries.sortLabels.releaseDate' => 'Uitgavedatum', @@ -2825,9 +2827,9 @@ extension on TranslationsNl { 'metadataEdit.summary' => 'Samenvatting', 'metadataEdit.poster' => 'Poster', 'metadataEdit.background' => 'Achtergrond', - 'metadataEdit.logo' => 'Logo', _ => null, } ?? switch (path) { + 'metadataEdit.logo' => 'Logo', 'metadataEdit.squareArt' => 'Vierkante afbeelding', 'metadataEdit.selectPoster' => 'Poster selecteren', 'metadataEdit.selectBackground' => 'Achtergrond selecteren', diff --git a/lib/i18n/strings_pl.g.dart b/lib/i18n/strings_pl.g.dart index 3aaa9b85..258e9505 100644 --- a/lib/i18n/strings_pl.g.dart +++ b/lib/i18n/strings_pl.g.dart @@ -1621,6 +1621,7 @@ class _TranslationsLibrariesFilterCategoriesPl extends TranslationsLibrariesFilt @override String get year => 'Rok'; @override String get contentRating => 'Klasyfikacja wiekowa'; @override String get tag => 'Tag'; + @override String get unwatched => 'Nieobejrzane'; } // Path: libraries.sortLabels @@ -2453,6 +2454,7 @@ extension on TranslationsPl { 'libraries.filterCategories.year' => 'Rok', 'libraries.filterCategories.contentRating' => 'Klasyfikacja wiekowa', 'libraries.filterCategories.tag' => 'Tag', + 'libraries.filterCategories.unwatched' => 'Nieobejrzane', 'libraries.sortLabels.title' => 'Tytuł', 'libraries.sortLabels.dateAdded' => 'Data dodania', 'libraries.sortLabels.releaseDate' => 'Data premiery', @@ -2825,9 +2827,9 @@ extension on TranslationsPl { 'metadataEdit.summary' => 'Opis', 'metadataEdit.poster' => 'Plakat', 'metadataEdit.background' => 'Tło', - 'metadataEdit.logo' => 'Logo', _ => null, } ?? switch (path) { + 'metadataEdit.logo' => 'Logo', 'metadataEdit.squareArt' => 'Kwadratowy obraz', 'metadataEdit.selectPoster' => 'Wybierz plakat', 'metadataEdit.selectBackground' => 'Wybierz tło', diff --git a/lib/i18n/strings_pt.g.dart b/lib/i18n/strings_pt.g.dart index 4d4bb54b..2fd187a5 100644 --- a/lib/i18n/strings_pt.g.dart +++ b/lib/i18n/strings_pt.g.dart @@ -1621,6 +1621,7 @@ class _TranslationsLibrariesFilterCategoriesPt extends TranslationsLibrariesFilt @override String get year => 'Ano'; @override String get contentRating => 'Classificação'; @override String get tag => 'Tag'; + @override String get unwatched => 'Não assistidos'; } // Path: libraries.sortLabels @@ -2453,6 +2454,7 @@ extension on TranslationsPt { 'libraries.filterCategories.year' => 'Ano', 'libraries.filterCategories.contentRating' => 'Classificação', 'libraries.filterCategories.tag' => 'Tag', + 'libraries.filterCategories.unwatched' => 'Não assistidos', 'libraries.sortLabels.title' => 'Título', 'libraries.sortLabels.dateAdded' => 'Data de adição', 'libraries.sortLabels.releaseDate' => 'Data de lançamento', @@ -2825,9 +2827,9 @@ extension on TranslationsPt { 'metadataEdit.summary' => 'Sinopse', 'metadataEdit.poster' => 'Poster', 'metadataEdit.background' => 'Plano de Fundo', - 'metadataEdit.logo' => 'Logo', _ => null, } ?? switch (path) { + 'metadataEdit.logo' => 'Logo', 'metadataEdit.squareArt' => 'Imagem Quadrada', 'metadataEdit.selectPoster' => 'Selecionar Poster', 'metadataEdit.selectBackground' => 'Selecionar Plano de Fundo', diff --git a/lib/i18n/strings_ru.g.dart b/lib/i18n/strings_ru.g.dart index aaf1b340..337bb6e8 100644 --- a/lib/i18n/strings_ru.g.dart +++ b/lib/i18n/strings_ru.g.dart @@ -1621,6 +1621,7 @@ class _TranslationsLibrariesFilterCategoriesRu extends TranslationsLibrariesFilt @override String get year => 'Год'; @override String get contentRating => 'Возрастной рейтинг'; @override String get tag => 'Тег'; + @override String get unwatched => 'Непросмотренные'; } // Path: libraries.sortLabels @@ -2453,6 +2454,7 @@ extension on TranslationsRu { 'libraries.filterCategories.year' => 'Год', 'libraries.filterCategories.contentRating' => 'Возрастной рейтинг', 'libraries.filterCategories.tag' => 'Тег', + 'libraries.filterCategories.unwatched' => 'Непросмотренные', 'libraries.sortLabels.title' => 'Название', 'libraries.sortLabels.dateAdded' => 'Дата добавления', 'libraries.sortLabels.releaseDate' => 'Дата выхода', @@ -2825,9 +2827,9 @@ extension on TranslationsRu { 'metadataEdit.summary' => 'Описание', 'metadataEdit.poster' => 'Постер', 'metadataEdit.background' => 'Фон', - 'metadataEdit.logo' => 'Логотип', _ => null, } ?? switch (path) { + 'metadataEdit.logo' => 'Логотип', 'metadataEdit.squareArt' => 'Квадратное изображение', 'metadataEdit.selectPoster' => 'Выбрать постер', 'metadataEdit.selectBackground' => 'Выбрать фон', diff --git a/lib/i18n/strings_sv.g.dart b/lib/i18n/strings_sv.g.dart index 6c4df15f..4c1c9a1c 100644 --- a/lib/i18n/strings_sv.g.dart +++ b/lib/i18n/strings_sv.g.dart @@ -1621,6 +1621,7 @@ class _TranslationsLibrariesFilterCategoriesSv extends TranslationsLibrariesFilt @override String get year => 'År'; @override String get contentRating => 'Åldersgräns'; @override String get tag => 'Tagg'; + @override String get unwatched => 'Osedda'; } // Path: libraries.sortLabels @@ -2453,6 +2454,7 @@ extension on TranslationsSv { 'libraries.filterCategories.year' => 'År', 'libraries.filterCategories.contentRating' => 'Åldersgräns', 'libraries.filterCategories.tag' => 'Tagg', + 'libraries.filterCategories.unwatched' => 'Osedda', 'libraries.sortLabels.title' => 'Titel', 'libraries.sortLabels.dateAdded' => 'Tillagd', 'libraries.sortLabels.releaseDate' => 'Releasedatum', @@ -2825,9 +2827,9 @@ extension on TranslationsSv { 'metadataEdit.summary' => 'Sammanfattning', 'metadataEdit.poster' => 'Poster', 'metadataEdit.background' => 'Bakgrund', - 'metadataEdit.logo' => 'Logotyp', _ => null, } ?? switch (path) { + 'metadataEdit.logo' => 'Logotyp', 'metadataEdit.squareArt' => 'Kvadratisk bild', 'metadataEdit.selectPoster' => 'Välj poster', 'metadataEdit.selectBackground' => 'Välj bakgrund', diff --git a/lib/i18n/strings_zh.g.dart b/lib/i18n/strings_zh.g.dart index e7199ca3..e693b86b 100644 --- a/lib/i18n/strings_zh.g.dart +++ b/lib/i18n/strings_zh.g.dart @@ -1621,6 +1621,7 @@ class _TranslationsLibrariesFilterCategoriesZh extends TranslationsLibrariesFilt @override String get year => '年份'; @override String get contentRating => '内容分级'; @override String get tag => '标签'; + @override String get unwatched => '未观看'; } // Path: libraries.sortLabels @@ -2453,6 +2454,7 @@ extension on TranslationsZh { 'libraries.filterCategories.year' => '年份', 'libraries.filterCategories.contentRating' => '内容分级', 'libraries.filterCategories.tag' => '标签', + 'libraries.filterCategories.unwatched' => '未观看', 'libraries.sortLabels.title' => '标题', 'libraries.sortLabels.dateAdded' => '添加日期', 'libraries.sortLabels.releaseDate' => '发行日期', @@ -2825,9 +2827,9 @@ extension on TranslationsZh { 'metadataEdit.summary' => '简介', 'metadataEdit.poster' => '海报', 'metadataEdit.background' => '背景', - 'metadataEdit.logo' => '标志', _ => null, } ?? switch (path) { + 'metadataEdit.logo' => '标志', 'metadataEdit.squareArt' => '方形图片', 'metadataEdit.selectPoster' => '选择海报', 'metadataEdit.selectBackground' => '选择背景', diff --git a/lib/i18n/sv.i18n.json b/lib/i18n/sv.i18n.json index 83b8ea86..e3d350d6 100644 --- a/lib/i18n/sv.i18n.json +++ b/lib/i18n/sv.i18n.json @@ -705,7 +705,8 @@ "genre": "Genre", "year": "År", "contentRating": "Åldersgräns", - "tag": "Tagg" + "tag": "Tagg", + "unwatched": "Osedda" }, "sortLabels": { "title": "Titel", diff --git a/lib/i18n/zh.i18n.json b/lib/i18n/zh.i18n.json index ed19ff6a..8b8a27cd 100644 --- a/lib/i18n/zh.i18n.json +++ b/lib/i18n/zh.i18n.json @@ -705,7 +705,8 @@ "genre": "类型", "year": "年份", "contentRating": "内容分级", - "tag": "标签" + "tag": "标签", + "unwatched": "未观看" }, "sortLabels": { "title": "标题", diff --git a/lib/services/jellyfin_client/parts/browse.dart b/lib/services/jellyfin_client/parts/browse.dart index 38936a88..9fd30bbe 100644 --- a/lib/services/jellyfin_client/parts/browse.dart +++ b/lib/services/jellyfin_client/parts/browse.dart @@ -127,15 +127,26 @@ mixin _JellyfinBrowseMethods on MediaServerCacheMixin { } /// Jellyfin's `/Items/Filters` returns Genres / OfficialRatings / Tags / - /// Categories + values from `/Items/Filters` in a single call. Keys are - /// translated to Plex's filter naming so the existing filter-param map - /// round-trips through `_buildFilterParams` unchanged; the synthesised - /// `MediaFilter.key` is prefixed `jellyfin:` so FiltersBottomSheet can - /// recognise it as cached and skip the per-category value fetch. + /// Categories + values from `/Items/Filters` in a single call. The unwatched + /// boolean is synthetic because Jellyfin exposes it as an `/Items` query + /// filter, not a filter-listing category. Keys are translated to Plex's + /// filter naming so the existing filter-param map round-trips through + /// `_buildFilterParams` unchanged; the synthesised `MediaFilter.key` is + /// prefixed `jellyfin:` so FiltersBottomSheet can recognise it as cached and + /// skip the per-category value fetch. @override Future fetchLibraryFiltersWithValues(String libraryId) async { + final filters = [ + MediaFilter( + filter: 'unwatched', + filterType: 'boolean', + key: 'jellyfin:unwatched', + title: t.libraries.filterCategories.unwatched, + type: 'filter', + ), + ]; final data = await _safeFetchFilterPayload(libraryId); - if (data == null) return LibraryFilterResult.empty; + if (data == null) return LibraryFilterResult(filters: filters, cachedValues: const {}); List stringList(Object? raw) { if (raw is! List) return const []; return raw.whereType().where((s) => s.isNotEmpty).toList(); @@ -157,7 +168,6 @@ mixin _JellyfinBrowseMethods on MediaServerCacheMixin { 'contentRating': t.libraries.filterCategories.contentRating, 'tag': t.libraries.filterCategories.tag, }; - final filters = []; final values = >{}; for (final key in order) { final entries = raw[key]; diff --git a/test/services/jellyfin_client_urls_test.dart b/test/services/jellyfin_client_urls_test.dart index 46ec51fe..3183ac91 100644 --- a/test/services/jellyfin_client_urls_test.dart +++ b/test/services/jellyfin_client_urls_test.dart @@ -1343,6 +1343,41 @@ void main() { expect(captured!.queryParameters['ImageTypeLimit'], '1'); }); + test('fetchLibraryFiltersWithValues adds unwatched boolean filter', () async { + Uri? captured; + final scoped = JellyfinClient.forTesting( + connection: _conn(), + httpClient: MockClient((req) async { + captured = req.url; + return http.Response( + jsonEncode({ + 'Genres': ['Drama', 'Action'], + 'OfficialRatings': ['PG-13'], + 'Tags': ['Holiday'], + 'Years': [2024, 1999], + }), + 200, + headers: {'content-type': 'application/json'}, + ); + }), + ); + addTearDown(scoped.close); + + final result = await scoped.fetchLibraryFiltersWithValues('lib-1'); + + expect(captured, isNotNull); + expect(captured!.path, '/Items/Filters'); + expect(captured!.queryParameters['ParentId'], 'lib-1'); + expect(captured!.queryParameters['userId'], 'user-1'); + expect(result.filters.map((filter) => filter.filter), ['unwatched', 'genre', 'year', 'contentRating', 'tag']); + expect(result.filters.first.filterType, 'boolean'); + expect(result.filters.first.key, 'jellyfin:unwatched'); + expect(result.filters.first.title, 'Unwatched'); + expect(result.cachedValues.containsKey('unwatched'), isFalse); + expect(result.cachedValues['genre']!.map((value) => value.key), ['Action', 'Drama']); + expect(result.cachedValues['year']!.map((value) => value.key), ['2024', '1999']); + }); + test('fetchLibraryContent uses sentinel total fallback when server omits total', () async { final scoped = JellyfinClient.forTesting( connection: _conn(),