From 9cebcb97c504fcb70ec13cf18f0180fb2a6ee173 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Sun, 7 Jun 2026 17:50:36 +0200 Subject: [PATCH] feat(plex): add Plays and User Rating sort options close #1264 --- lib/i18n/bg.i18n.json | 1 + lib/i18n/da.i18n.json | 1 + lib/i18n/de.i18n.json | 1 + lib/i18n/en.i18n.json | 1 + lib/i18n/es.i18n.json | 1 + lib/i18n/fr.i18n.json | 1 + lib/i18n/it.i18n.json | 1 + lib/i18n/ja.i18n.json | 1 + lib/i18n/ko.i18n.json | 1 + lib/i18n/nb.i18n.json | 1 + lib/i18n/nl.i18n.json | 1 + lib/i18n/pl.i18n.json | 1 + lib/i18n/pt.i18n.json | 1 + lib/i18n/ru.i18n.json | 1 + lib/i18n/strings.g.dart | 2 +- lib/i18n/strings_bg.g.dart | 4 +- 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 | 1 + lib/i18n/zh.i18n.json | 1 + lib/services/plex_client.dart | 39 +++++++++++-- test/services/plex_library_details_test.dart | 60 ++++++++++++++++++++ 35 files changed, 160 insertions(+), 23 deletions(-) diff --git a/lib/i18n/bg.i18n.json b/lib/i18n/bg.i18n.json index d40064b6..47e7ba93 100644 --- a/lib/i18n/bg.i18n.json +++ b/lib/i18n/bg.i18n.json @@ -751,6 +751,7 @@ "rating": "Рейтинг", "communityRating": "Оценка от общността", "criticRating": "Оценка от критиците", + "userRating": "Потребителска оценка", "lastPlayed": "Последно възпроизведено", "datePlayed": "Дата на възпроизвеждане", "playCount": "Брой възпроизвеждания", diff --git a/lib/i18n/da.i18n.json b/lib/i18n/da.i18n.json index e7b54db7..a6b1cc2a 100644 --- a/lib/i18n/da.i18n.json +++ b/lib/i18n/da.i18n.json @@ -751,6 +751,7 @@ "rating": "Vurdering", "communityRating": "Fællesskabsvurdering", "criticRating": "Kritikerbedømmelse", + "userRating": "Brugerbedømmelse", "lastPlayed": "Sidst afspillet", "datePlayed": "Afspilningsdato", "playCount": "Antal afspilninger", diff --git a/lib/i18n/de.i18n.json b/lib/i18n/de.i18n.json index 3a856269..59bf2d9c 100644 --- a/lib/i18n/de.i18n.json +++ b/lib/i18n/de.i18n.json @@ -751,6 +751,7 @@ "rating": "Bewertung", "communityRating": "Community-Bewertung", "criticRating": "Kritikerbewertung", + "userRating": "Benutzerbewertung", "lastPlayed": "Zuletzt abgespielt", "datePlayed": "Wiedergabedatum", "playCount": "Wiedergaben", diff --git a/lib/i18n/en.i18n.json b/lib/i18n/en.i18n.json index 351eafb2..d52b592c 100644 --- a/lib/i18n/en.i18n.json +++ b/lib/i18n/en.i18n.json @@ -751,6 +751,7 @@ "rating": "Rating", "communityRating": "Community Rating", "criticRating": "Critic Rating", + "userRating": "User Rating", "lastPlayed": "Last Played", "datePlayed": "Date Played", "playCount": "Play Count", diff --git a/lib/i18n/es.i18n.json b/lib/i18n/es.i18n.json index 65d746b8..9e0404ef 100644 --- a/lib/i18n/es.i18n.json +++ b/lib/i18n/es.i18n.json @@ -751,6 +751,7 @@ "rating": "Valoración", "communityRating": "Calificación de comunidad", "criticRating": "Calificación de crítica", + "userRating": "Calificación del usuario", "lastPlayed": "Última reproducción", "datePlayed": "Fecha de reproducción", "playCount": "Reproducciones", diff --git a/lib/i18n/fr.i18n.json b/lib/i18n/fr.i18n.json index 6f60da83..630d0910 100644 --- a/lib/i18n/fr.i18n.json +++ b/lib/i18n/fr.i18n.json @@ -751,6 +751,7 @@ "rating": "Note", "communityRating": "Note communautaire", "criticRating": "Note critique", + "userRating": "Note utilisateur", "lastPlayed": "Dernière lecture", "datePlayed": "Date de lecture", "playCount": "Lectures", diff --git a/lib/i18n/it.i18n.json b/lib/i18n/it.i18n.json index ba48ddd5..06f57159 100644 --- a/lib/i18n/it.i18n.json +++ b/lib/i18n/it.i18n.json @@ -751,6 +751,7 @@ "rating": "Valutazione", "communityRating": "Valutazione della community", "criticRating": "Valutazione critica", + "userRating": "Valutazione utente", "lastPlayed": "Ultima riproduzione", "datePlayed": "Data di riproduzione", "playCount": "Riproduzioni", diff --git a/lib/i18n/ja.i18n.json b/lib/i18n/ja.i18n.json index 47114313..1123a033 100644 --- a/lib/i18n/ja.i18n.json +++ b/lib/i18n/ja.i18n.json @@ -751,6 +751,7 @@ "rating": "評価", "communityRating": "コミュニティ評価", "criticRating": "批評家評価", + "userRating": "ユーザー評価", "lastPlayed": "最終再生", "datePlayed": "再生日", "playCount": "再生回数", diff --git a/lib/i18n/ko.i18n.json b/lib/i18n/ko.i18n.json index b197b9a6..0de05171 100644 --- a/lib/i18n/ko.i18n.json +++ b/lib/i18n/ko.i18n.json @@ -751,6 +751,7 @@ "rating": "평점", "communityRating": "커뮤니티 평점", "criticRating": "평론가 평점", + "userRating": "사용자 평점", "lastPlayed": "마지막 재생", "datePlayed": "재생일", "playCount": "재생 횟수", diff --git a/lib/i18n/nb.i18n.json b/lib/i18n/nb.i18n.json index 414c089d..2d52f916 100644 --- a/lib/i18n/nb.i18n.json +++ b/lib/i18n/nb.i18n.json @@ -751,6 +751,7 @@ "rating": "Vurdering", "communityRating": "Fellesskapsvurdering", "criticRating": "Kritikervurdering", + "userRating": "Brukervurdering", "lastPlayed": "Sist spilt", "datePlayed": "Avspillingsdato", "playCount": "Avspillinger", diff --git a/lib/i18n/nl.i18n.json b/lib/i18n/nl.i18n.json index 49829254..0038a8db 100644 --- a/lib/i18n/nl.i18n.json +++ b/lib/i18n/nl.i18n.json @@ -751,6 +751,7 @@ "rating": "Beoordeling", "communityRating": "Communitybeoordeling", "criticRating": "Criticusbeoordeling", + "userRating": "Gebruikersbeoordeling", "lastPlayed": "Laatst afgespeeld", "datePlayed": "Afspeeldatum", "playCount": "Aantal afspelingen", diff --git a/lib/i18n/pl.i18n.json b/lib/i18n/pl.i18n.json index 719209b3..c4426769 100644 --- a/lib/i18n/pl.i18n.json +++ b/lib/i18n/pl.i18n.json @@ -751,6 +751,7 @@ "rating": "Ocena", "communityRating": "Ocena społeczności", "criticRating": "Ocena krytyków", + "userRating": "Ocena użytkownika", "lastPlayed": "Ostatnio odtwarzane", "datePlayed": "Data odtworzenia", "playCount": "Liczba odtworzeń", diff --git a/lib/i18n/pt.i18n.json b/lib/i18n/pt.i18n.json index ee6002be..ca86f143 100644 --- a/lib/i18n/pt.i18n.json +++ b/lib/i18n/pt.i18n.json @@ -751,6 +751,7 @@ "rating": "Avaliação", "communityRating": "Avaliação da comunidade", "criticRating": "Avaliação da crítica", + "userRating": "Avaliação do usuário", "lastPlayed": "Última reprodução", "datePlayed": "Data de reprodução", "playCount": "Reproduções", diff --git a/lib/i18n/ru.i18n.json b/lib/i18n/ru.i18n.json index ebad5a2a..505e5269 100644 --- a/lib/i18n/ru.i18n.json +++ b/lib/i18n/ru.i18n.json @@ -751,6 +751,7 @@ "rating": "Рейтинг", "communityRating": "Оценка сообщества", "criticRating": "Оценка критиков", + "userRating": "Пользовательская оценка", "lastPlayed": "Последний просмотр", "datePlayed": "Дата просмотра", "playCount": "Количество просмотров", diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index 11fc8007..a291ef63 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -4,7 +4,7 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 16 -/// Strings: 20320 (1270 per locale) +/// Strings: 20336 (1271 per locale) // coverage:ignore-file // ignore_for_file: type=lint, unused_import diff --git a/lib/i18n/strings_bg.g.dart b/lib/i18n/strings_bg.g.dart index f31ad4c0..1a712714 100644 --- a/lib/i18n/strings_bg.g.dart +++ b/lib/i18n/strings_bg.g.dart @@ -1740,6 +1740,7 @@ class _TranslationsLibrariesSortLabelsBg extends TranslationsLibrariesSortLabels @override String get rating => 'Рейтинг'; @override String get communityRating => 'Оценка от общността'; @override String get criticRating => 'Оценка от критиците'; + @override String get userRating => 'Потребителска оценка'; @override String get lastPlayed => 'Последно възпроизведено'; @override String get datePlayed => 'Дата на възпроизвеждане'; @override String get playCount => 'Брой възпроизвеждания'; @@ -2615,6 +2616,7 @@ extension on TranslationsBg { 'libraries.sortLabels.rating' => 'Рейтинг', 'libraries.sortLabels.communityRating' => 'Оценка от общността', 'libraries.sortLabels.criticRating' => 'Оценка от критиците', + 'libraries.sortLabels.userRating' => 'Потребителска оценка', 'libraries.sortLabels.lastPlayed' => 'Последно възпроизведено', 'libraries.sortLabels.datePlayed' => 'Дата на възпроизвеждане', 'libraries.sortLabels.playCount' => 'Брой възпроизвеждания', @@ -2944,9 +2946,9 @@ extension on TranslationsBg { 'companionRemote.remote.tabLibraries' => 'Библиотеки', 'companionRemote.remote.tabSearch' => 'Търсене', 'companionRemote.remote.tabDownloads' => 'Изтегляния', - 'companionRemote.remote.tabSettings' => 'Настройки', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabSettings' => 'Настройки', 'companionRemote.remote.previous' => 'Предишен', 'companionRemote.remote.playPause' => 'Пускане/пауза', 'companionRemote.remote.next' => 'Следващ', diff --git a/lib/i18n/strings_da.g.dart b/lib/i18n/strings_da.g.dart index 73fa4f05..a138b4df 100644 --- a/lib/i18n/strings_da.g.dart +++ b/lib/i18n/strings_da.g.dart @@ -1740,6 +1740,7 @@ class _TranslationsLibrariesSortLabelsDa extends TranslationsLibrariesSortLabels @override String get rating => 'Vurdering'; @override String get communityRating => 'Fællesskabsvurdering'; @override String get criticRating => 'Kritikerbedømmelse'; + @override String get userRating => 'Brugerbedømmelse'; @override String get lastPlayed => 'Sidst afspillet'; @override String get datePlayed => 'Afspilningsdato'; @override String get playCount => 'Antal afspilninger'; @@ -2615,6 +2616,7 @@ extension on TranslationsDa { 'libraries.sortLabels.rating' => 'Vurdering', 'libraries.sortLabels.communityRating' => 'Fællesskabsvurdering', 'libraries.sortLabels.criticRating' => 'Kritikerbedømmelse', + 'libraries.sortLabels.userRating' => 'Brugerbedømmelse', 'libraries.sortLabels.lastPlayed' => 'Sidst afspillet', 'libraries.sortLabels.datePlayed' => 'Afspilningsdato', 'libraries.sortLabels.playCount' => 'Antal afspilninger', @@ -2944,9 +2946,9 @@ extension on TranslationsDa { 'companionRemote.remote.tabLibraries' => 'Biblioteker', 'companionRemote.remote.tabSearch' => 'Søg', 'companionRemote.remote.tabDownloads' => 'Downloads', - 'companionRemote.remote.tabSettings' => 'Indstillinger', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabSettings' => 'Indstillinger', 'companionRemote.remote.previous' => 'Forrige', 'companionRemote.remote.playPause' => 'Afspil/Pause', 'companionRemote.remote.next' => 'Næste', diff --git a/lib/i18n/strings_de.g.dart b/lib/i18n/strings_de.g.dart index d901a2bc..1edfd786 100644 --- a/lib/i18n/strings_de.g.dart +++ b/lib/i18n/strings_de.g.dart @@ -1740,6 +1740,7 @@ class _TranslationsLibrariesSortLabelsDe extends TranslationsLibrariesSortLabels @override String get rating => 'Bewertung'; @override String get communityRating => 'Community-Bewertung'; @override String get criticRating => 'Kritikerbewertung'; + @override String get userRating => 'Benutzerbewertung'; @override String get lastPlayed => 'Zuletzt abgespielt'; @override String get datePlayed => 'Wiedergabedatum'; @override String get playCount => 'Wiedergaben'; @@ -2615,6 +2616,7 @@ extension on TranslationsDe { 'libraries.sortLabels.rating' => 'Bewertung', 'libraries.sortLabels.communityRating' => 'Community-Bewertung', 'libraries.sortLabels.criticRating' => 'Kritikerbewertung', + 'libraries.sortLabels.userRating' => 'Benutzerbewertung', 'libraries.sortLabels.lastPlayed' => 'Zuletzt abgespielt', 'libraries.sortLabels.datePlayed' => 'Wiedergabedatum', 'libraries.sortLabels.playCount' => 'Wiedergaben', @@ -2944,9 +2946,9 @@ extension on TranslationsDe { 'companionRemote.remote.tabLibraries' => 'Mediatheken', 'companionRemote.remote.tabSearch' => 'Suche', 'companionRemote.remote.tabDownloads' => 'Downloads', - 'companionRemote.remote.tabSettings' => 'Einstellungen', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabSettings' => 'Einstellungen', 'companionRemote.remote.previous' => 'Zurück', 'companionRemote.remote.playPause' => 'Wiedergabe/Pause', 'companionRemote.remote.next' => 'Weiter', diff --git a/lib/i18n/strings_en.g.dart b/lib/i18n/strings_en.g.dart index 7b351214..66d426f3 100644 --- a/lib/i18n/strings_en.g.dart +++ b/lib/i18n/strings_en.g.dart @@ -4093,6 +4093,9 @@ class TranslationsLibrariesSortLabelsEn { /// en: 'Critic Rating' String get criticRating => 'Critic Rating'; + /// en: 'User Rating' + String get userRating => 'User Rating'; + /// en: 'Last Played' String get lastPlayed => 'Last Played'; @@ -5161,6 +5164,7 @@ extension on Translations { 'libraries.sortLabels.rating' => 'Rating', 'libraries.sortLabels.communityRating' => 'Community Rating', 'libraries.sortLabels.criticRating' => 'Critic Rating', + 'libraries.sortLabels.userRating' => 'User Rating', 'libraries.sortLabels.lastPlayed' => 'Last Played', 'libraries.sortLabels.datePlayed' => 'Date Played', 'libraries.sortLabels.playCount' => 'Play Count', @@ -5490,9 +5494,9 @@ extension on Translations { 'companionRemote.remote.tabLibraries' => 'Libraries', 'companionRemote.remote.tabSearch' => 'Search', 'companionRemote.remote.tabDownloads' => 'Downloads', - 'companionRemote.remote.tabSettings' => 'Settings', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabSettings' => 'Settings', 'companionRemote.remote.previous' => 'Previous', 'companionRemote.remote.playPause' => 'Play/Pause', 'companionRemote.remote.next' => 'Next', diff --git a/lib/i18n/strings_es.g.dart b/lib/i18n/strings_es.g.dart index 53423ac4..99e90eb2 100644 --- a/lib/i18n/strings_es.g.dart +++ b/lib/i18n/strings_es.g.dart @@ -1740,6 +1740,7 @@ class _TranslationsLibrariesSortLabelsEs extends TranslationsLibrariesSortLabels @override String get rating => 'Valoración'; @override String get communityRating => 'Calificación de comunidad'; @override String get criticRating => 'Calificación de crítica'; + @override String get userRating => 'Calificación del usuario'; @override String get lastPlayed => 'Última reproducción'; @override String get datePlayed => 'Fecha de reproducción'; @override String get playCount => 'Reproducciones'; @@ -2615,6 +2616,7 @@ extension on TranslationsEs { 'libraries.sortLabels.rating' => 'Valoración', 'libraries.sortLabels.communityRating' => 'Calificación de comunidad', 'libraries.sortLabels.criticRating' => 'Calificación de crítica', + 'libraries.sortLabels.userRating' => 'Calificación del usuario', 'libraries.sortLabels.lastPlayed' => 'Última reproducción', 'libraries.sortLabels.datePlayed' => 'Fecha de reproducción', 'libraries.sortLabels.playCount' => 'Reproducciones', @@ -2944,9 +2946,9 @@ extension on TranslationsEs { 'companionRemote.remote.tabLibraries' => 'Bibliotecas', 'companionRemote.remote.tabSearch' => 'Buscar', 'companionRemote.remote.tabDownloads' => 'Descargas', - 'companionRemote.remote.tabSettings' => 'Configuración', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabSettings' => 'Configuración', 'companionRemote.remote.previous' => 'Anterior', 'companionRemote.remote.playPause' => 'Reproducir/Pausar', 'companionRemote.remote.next' => 'Siguiente', diff --git a/lib/i18n/strings_fr.g.dart b/lib/i18n/strings_fr.g.dart index e657b0ed..6b1a5ccc 100644 --- a/lib/i18n/strings_fr.g.dart +++ b/lib/i18n/strings_fr.g.dart @@ -1740,6 +1740,7 @@ class _TranslationsLibrariesSortLabelsFr extends TranslationsLibrariesSortLabels @override String get rating => 'Note'; @override String get communityRating => 'Note communautaire'; @override String get criticRating => 'Note critique'; + @override String get userRating => 'Note utilisateur'; @override String get lastPlayed => 'Dernière lecture'; @override String get datePlayed => 'Date de lecture'; @override String get playCount => 'Lectures'; @@ -2615,6 +2616,7 @@ extension on TranslationsFr { 'libraries.sortLabels.rating' => 'Note', 'libraries.sortLabels.communityRating' => 'Note communautaire', 'libraries.sortLabels.criticRating' => 'Note critique', + 'libraries.sortLabels.userRating' => 'Note utilisateur', 'libraries.sortLabels.lastPlayed' => 'Dernière lecture', 'libraries.sortLabels.datePlayed' => 'Date de lecture', 'libraries.sortLabels.playCount' => 'Lectures', @@ -2944,9 +2946,9 @@ extension on TranslationsFr { 'companionRemote.remote.tabLibraries' => 'Bibliothèques', 'companionRemote.remote.tabSearch' => 'Rechercher', 'companionRemote.remote.tabDownloads' => 'Téléchargements', - 'companionRemote.remote.tabSettings' => 'Paramètres', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabSettings' => 'Paramètres', 'companionRemote.remote.previous' => 'Précédent', 'companionRemote.remote.playPause' => 'Lecture/Pause', 'companionRemote.remote.next' => 'Suivant', diff --git a/lib/i18n/strings_it.g.dart b/lib/i18n/strings_it.g.dart index 86455867..7e1288dd 100644 --- a/lib/i18n/strings_it.g.dart +++ b/lib/i18n/strings_it.g.dart @@ -1740,6 +1740,7 @@ class _TranslationsLibrariesSortLabelsIt extends TranslationsLibrariesSortLabels @override String get rating => 'Valutazione'; @override String get communityRating => 'Valutazione della community'; @override String get criticRating => 'Valutazione critica'; + @override String get userRating => 'Valutazione utente'; @override String get lastPlayed => 'Ultima riproduzione'; @override String get datePlayed => 'Data di riproduzione'; @override String get playCount => 'Riproduzioni'; @@ -2615,6 +2616,7 @@ extension on TranslationsIt { 'libraries.sortLabels.rating' => 'Valutazione', 'libraries.sortLabels.communityRating' => 'Valutazione della community', 'libraries.sortLabels.criticRating' => 'Valutazione critica', + 'libraries.sortLabels.userRating' => 'Valutazione utente', 'libraries.sortLabels.lastPlayed' => 'Ultima riproduzione', 'libraries.sortLabels.datePlayed' => 'Data di riproduzione', 'libraries.sortLabels.playCount' => 'Riproduzioni', @@ -2944,9 +2946,9 @@ extension on TranslationsIt { 'companionRemote.remote.tabLibraries' => 'Librerie', 'companionRemote.remote.tabSearch' => 'Cerca', 'companionRemote.remote.tabDownloads' => 'Download', - 'companionRemote.remote.tabSettings' => 'Impostazioni', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabSettings' => 'Impostazioni', 'companionRemote.remote.previous' => 'Precedente', 'companionRemote.remote.playPause' => 'Riproduci/Pausa', 'companionRemote.remote.next' => 'Successivo', diff --git a/lib/i18n/strings_ja.g.dart b/lib/i18n/strings_ja.g.dart index 72dfaf59..7b5f4aa2 100644 --- a/lib/i18n/strings_ja.g.dart +++ b/lib/i18n/strings_ja.g.dart @@ -1740,6 +1740,7 @@ class _TranslationsLibrariesSortLabelsJa extends TranslationsLibrariesSortLabels @override String get rating => '評価'; @override String get communityRating => 'コミュニティ評価'; @override String get criticRating => '批評家評価'; + @override String get userRating => 'ユーザー評価'; @override String get lastPlayed => '最終再生'; @override String get datePlayed => '再生日'; @override String get playCount => '再生回数'; @@ -2615,6 +2616,7 @@ extension on TranslationsJa { 'libraries.sortLabels.rating' => '評価', 'libraries.sortLabels.communityRating' => 'コミュニティ評価', 'libraries.sortLabels.criticRating' => '批評家評価', + 'libraries.sortLabels.userRating' => 'ユーザー評価', 'libraries.sortLabels.lastPlayed' => '最終再生', 'libraries.sortLabels.datePlayed' => '再生日', 'libraries.sortLabels.playCount' => '再生回数', @@ -2944,9 +2946,9 @@ extension on TranslationsJa { 'companionRemote.remote.tabLibraries' => 'ライブラリ', 'companionRemote.remote.tabSearch' => '検索', 'companionRemote.remote.tabDownloads' => 'ダウンロード', - 'companionRemote.remote.tabSettings' => '設定', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabSettings' => '設定', 'companionRemote.remote.previous' => '前へ', 'companionRemote.remote.playPause' => '再生/一時停止', 'companionRemote.remote.next' => '次へ', diff --git a/lib/i18n/strings_ko.g.dart b/lib/i18n/strings_ko.g.dart index 0ddad951..94a5015f 100644 --- a/lib/i18n/strings_ko.g.dart +++ b/lib/i18n/strings_ko.g.dart @@ -1740,6 +1740,7 @@ class _TranslationsLibrariesSortLabelsKo extends TranslationsLibrariesSortLabels @override String get rating => '평점'; @override String get communityRating => '커뮤니티 평점'; @override String get criticRating => '평론가 평점'; + @override String get userRating => '사용자 평점'; @override String get lastPlayed => '마지막 재생'; @override String get datePlayed => '재생일'; @override String get playCount => '재생 횟수'; @@ -2615,6 +2616,7 @@ extension on TranslationsKo { 'libraries.sortLabels.rating' => '평점', 'libraries.sortLabels.communityRating' => '커뮤니티 평점', 'libraries.sortLabels.criticRating' => '평론가 평점', + 'libraries.sortLabels.userRating' => '사용자 평점', 'libraries.sortLabels.lastPlayed' => '마지막 재생', 'libraries.sortLabels.datePlayed' => '재생일', 'libraries.sortLabels.playCount' => '재생 횟수', @@ -2944,9 +2946,9 @@ extension on TranslationsKo { 'companionRemote.remote.tabLibraries' => '미디어 라이브러리', 'companionRemote.remote.tabSearch' => '검색', 'companionRemote.remote.tabDownloads' => '다운로드', - 'companionRemote.remote.tabSettings' => '설정', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabSettings' => '설정', 'companionRemote.remote.previous' => '이전', 'companionRemote.remote.playPause' => '재생/일시정지', 'companionRemote.remote.next' => '다음', diff --git a/lib/i18n/strings_nb.g.dart b/lib/i18n/strings_nb.g.dart index 6dc141e9..1470ef6f 100644 --- a/lib/i18n/strings_nb.g.dart +++ b/lib/i18n/strings_nb.g.dart @@ -1740,6 +1740,7 @@ class _TranslationsLibrariesSortLabelsNb extends TranslationsLibrariesSortLabels @override String get rating => 'Vurdering'; @override String get communityRating => 'Fellesskapsvurdering'; @override String get criticRating => 'Kritikervurdering'; + @override String get userRating => 'Brukervurdering'; @override String get lastPlayed => 'Sist spilt'; @override String get datePlayed => 'Avspillingsdato'; @override String get playCount => 'Avspillinger'; @@ -2615,6 +2616,7 @@ extension on TranslationsNb { 'libraries.sortLabels.rating' => 'Vurdering', 'libraries.sortLabels.communityRating' => 'Fellesskapsvurdering', 'libraries.sortLabels.criticRating' => 'Kritikervurdering', + 'libraries.sortLabels.userRating' => 'Brukervurdering', 'libraries.sortLabels.lastPlayed' => 'Sist spilt', 'libraries.sortLabels.datePlayed' => 'Avspillingsdato', 'libraries.sortLabels.playCount' => 'Avspillinger', @@ -2944,9 +2946,9 @@ extension on TranslationsNb { 'companionRemote.remote.tabLibraries' => 'Biblioteker', 'companionRemote.remote.tabSearch' => 'Søk', 'companionRemote.remote.tabDownloads' => 'Nedlastinger', - 'companionRemote.remote.tabSettings' => 'Innstillinger', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabSettings' => 'Innstillinger', 'companionRemote.remote.previous' => 'Forrige', 'companionRemote.remote.playPause' => 'Spill av/Pause', 'companionRemote.remote.next' => 'Neste', diff --git a/lib/i18n/strings_nl.g.dart b/lib/i18n/strings_nl.g.dart index 3d08b65a..4f0acfea 100644 --- a/lib/i18n/strings_nl.g.dart +++ b/lib/i18n/strings_nl.g.dart @@ -1740,6 +1740,7 @@ class _TranslationsLibrariesSortLabelsNl extends TranslationsLibrariesSortLabels @override String get rating => 'Beoordeling'; @override String get communityRating => 'Communitybeoordeling'; @override String get criticRating => 'Criticusbeoordeling'; + @override String get userRating => 'Gebruikersbeoordeling'; @override String get lastPlayed => 'Laatst afgespeeld'; @override String get datePlayed => 'Afspeeldatum'; @override String get playCount => 'Aantal afspelingen'; @@ -2615,6 +2616,7 @@ extension on TranslationsNl { 'libraries.sortLabels.rating' => 'Beoordeling', 'libraries.sortLabels.communityRating' => 'Communitybeoordeling', 'libraries.sortLabels.criticRating' => 'Criticusbeoordeling', + 'libraries.sortLabels.userRating' => 'Gebruikersbeoordeling', 'libraries.sortLabels.lastPlayed' => 'Laatst afgespeeld', 'libraries.sortLabels.datePlayed' => 'Afspeeldatum', 'libraries.sortLabels.playCount' => 'Aantal afspelingen', @@ -2944,9 +2946,9 @@ extension on TranslationsNl { 'companionRemote.remote.tabLibraries' => 'Bibliotheken', 'companionRemote.remote.tabSearch' => 'Zoeken', 'companionRemote.remote.tabDownloads' => 'Downloads', - 'companionRemote.remote.tabSettings' => 'Instellingen', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabSettings' => 'Instellingen', 'companionRemote.remote.previous' => 'Vorige', 'companionRemote.remote.playPause' => 'Afspelen/Pauzeren', 'companionRemote.remote.next' => 'Volgende', diff --git a/lib/i18n/strings_pl.g.dart b/lib/i18n/strings_pl.g.dart index d94d5e95..d2755b61 100644 --- a/lib/i18n/strings_pl.g.dart +++ b/lib/i18n/strings_pl.g.dart @@ -1740,6 +1740,7 @@ class _TranslationsLibrariesSortLabelsPl extends TranslationsLibrariesSortLabels @override String get rating => 'Ocena'; @override String get communityRating => 'Ocena społeczności'; @override String get criticRating => 'Ocena krytyków'; + @override String get userRating => 'Ocena użytkownika'; @override String get lastPlayed => 'Ostatnio odtwarzane'; @override String get datePlayed => 'Data odtworzenia'; @override String get playCount => 'Liczba odtworzeń'; @@ -2615,6 +2616,7 @@ extension on TranslationsPl { 'libraries.sortLabels.rating' => 'Ocena', 'libraries.sortLabels.communityRating' => 'Ocena społeczności', 'libraries.sortLabels.criticRating' => 'Ocena krytyków', + 'libraries.sortLabels.userRating' => 'Ocena użytkownika', 'libraries.sortLabels.lastPlayed' => 'Ostatnio odtwarzane', 'libraries.sortLabels.datePlayed' => 'Data odtworzenia', 'libraries.sortLabels.playCount' => 'Liczba odtworzeń', @@ -2944,9 +2946,9 @@ extension on TranslationsPl { 'companionRemote.remote.tabLibraries' => 'Biblioteki', 'companionRemote.remote.tabSearch' => 'Szukaj', 'companionRemote.remote.tabDownloads' => 'Pobrania', - 'companionRemote.remote.tabSettings' => 'Ustawienia', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabSettings' => 'Ustawienia', 'companionRemote.remote.previous' => 'Poprzedni', 'companionRemote.remote.playPause' => 'Odtwórz/Pauza', 'companionRemote.remote.next' => 'Następny', diff --git a/lib/i18n/strings_pt.g.dart b/lib/i18n/strings_pt.g.dart index 66de92b1..64a02478 100644 --- a/lib/i18n/strings_pt.g.dart +++ b/lib/i18n/strings_pt.g.dart @@ -1740,6 +1740,7 @@ class _TranslationsLibrariesSortLabelsPt extends TranslationsLibrariesSortLabels @override String get rating => 'Avaliação'; @override String get communityRating => 'Avaliação da comunidade'; @override String get criticRating => 'Avaliação da crítica'; + @override String get userRating => 'Avaliação do usuário'; @override String get lastPlayed => 'Última reprodução'; @override String get datePlayed => 'Data de reprodução'; @override String get playCount => 'Reproduções'; @@ -2615,6 +2616,7 @@ extension on TranslationsPt { 'libraries.sortLabels.rating' => 'Avaliação', 'libraries.sortLabels.communityRating' => 'Avaliação da comunidade', 'libraries.sortLabels.criticRating' => 'Avaliação da crítica', + 'libraries.sortLabels.userRating' => 'Avaliação do usuário', 'libraries.sortLabels.lastPlayed' => 'Última reprodução', 'libraries.sortLabels.datePlayed' => 'Data de reprodução', 'libraries.sortLabels.playCount' => 'Reproduções', @@ -2944,9 +2946,9 @@ extension on TranslationsPt { 'companionRemote.remote.tabLibraries' => 'Bibliotecas', 'companionRemote.remote.tabSearch' => 'Buscar', 'companionRemote.remote.tabDownloads' => 'Downloads', - 'companionRemote.remote.tabSettings' => 'Configurações', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabSettings' => 'Configurações', 'companionRemote.remote.previous' => 'Anterior', 'companionRemote.remote.playPause' => 'Reproduzir/Pausar', 'companionRemote.remote.next' => 'Próximo', diff --git a/lib/i18n/strings_ru.g.dart b/lib/i18n/strings_ru.g.dart index 41be4dde..6f845155 100644 --- a/lib/i18n/strings_ru.g.dart +++ b/lib/i18n/strings_ru.g.dart @@ -1740,6 +1740,7 @@ class _TranslationsLibrariesSortLabelsRu extends TranslationsLibrariesSortLabels @override String get rating => 'Рейтинг'; @override String get communityRating => 'Оценка сообщества'; @override String get criticRating => 'Оценка критиков'; + @override String get userRating => 'Пользовательская оценка'; @override String get lastPlayed => 'Последний просмотр'; @override String get datePlayed => 'Дата просмотра'; @override String get playCount => 'Количество просмотров'; @@ -2615,6 +2616,7 @@ extension on TranslationsRu { 'libraries.sortLabels.rating' => 'Рейтинг', 'libraries.sortLabels.communityRating' => 'Оценка сообщества', 'libraries.sortLabels.criticRating' => 'Оценка критиков', + 'libraries.sortLabels.userRating' => 'Пользовательская оценка', 'libraries.sortLabels.lastPlayed' => 'Последний просмотр', 'libraries.sortLabels.datePlayed' => 'Дата просмотра', 'libraries.sortLabels.playCount' => 'Количество просмотров', @@ -2944,9 +2946,9 @@ extension on TranslationsRu { 'companionRemote.remote.tabLibraries' => 'Библиотеки', 'companionRemote.remote.tabSearch' => 'Поиск', 'companionRemote.remote.tabDownloads' => 'Загрузки', - 'companionRemote.remote.tabSettings' => 'Настройки', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabSettings' => 'Настройки', 'companionRemote.remote.previous' => 'Предыдущий', 'companionRemote.remote.playPause' => 'Воспроизведение/Пауза', 'companionRemote.remote.next' => 'Следующий', diff --git a/lib/i18n/strings_sv.g.dart b/lib/i18n/strings_sv.g.dart index c9d69900..e2690ec6 100644 --- a/lib/i18n/strings_sv.g.dart +++ b/lib/i18n/strings_sv.g.dart @@ -1740,6 +1740,7 @@ class _TranslationsLibrariesSortLabelsSv extends TranslationsLibrariesSortLabels @override String get rating => 'Betyg'; @override String get communityRating => 'Användarbetyg'; @override String get criticRating => 'Kritikerbetyg'; + @override String get userRating => 'Användarbetyg'; @override String get lastPlayed => 'Senast spelad'; @override String get datePlayed => 'Speldatum'; @override String get playCount => 'Antal spelningar'; @@ -2615,6 +2616,7 @@ extension on TranslationsSv { 'libraries.sortLabels.rating' => 'Betyg', 'libraries.sortLabels.communityRating' => 'Användarbetyg', 'libraries.sortLabels.criticRating' => 'Kritikerbetyg', + 'libraries.sortLabels.userRating' => 'Användarbetyg', 'libraries.sortLabels.lastPlayed' => 'Senast spelad', 'libraries.sortLabels.datePlayed' => 'Speldatum', 'libraries.sortLabels.playCount' => 'Antal spelningar', @@ -2944,9 +2946,9 @@ extension on TranslationsSv { 'companionRemote.remote.tabLibraries' => 'Bibliotek', 'companionRemote.remote.tabSearch' => 'Sök', 'companionRemote.remote.tabDownloads' => 'Nedladdningar', - 'companionRemote.remote.tabSettings' => 'Inställningar', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabSettings' => 'Inställningar', 'companionRemote.remote.previous' => 'Föregående', 'companionRemote.remote.playPause' => 'Spela/Pausa', 'companionRemote.remote.next' => 'Nästa', diff --git a/lib/i18n/strings_zh.g.dart b/lib/i18n/strings_zh.g.dart index f573f217..b7940d69 100644 --- a/lib/i18n/strings_zh.g.dart +++ b/lib/i18n/strings_zh.g.dart @@ -1740,6 +1740,7 @@ class _TranslationsLibrariesSortLabelsZh extends TranslationsLibrariesSortLabels @override String get rating => '评分'; @override String get communityRating => '社区评分'; @override String get criticRating => '影评人评分'; + @override String get userRating => '用户评分'; @override String get lastPlayed => '最近播放'; @override String get datePlayed => '播放日期'; @override String get playCount => '播放次数'; @@ -2615,6 +2616,7 @@ extension on TranslationsZh { 'libraries.sortLabels.rating' => '评分', 'libraries.sortLabels.communityRating' => '社区评分', 'libraries.sortLabels.criticRating' => '影评人评分', + 'libraries.sortLabels.userRating' => '用户评分', 'libraries.sortLabels.lastPlayed' => '最近播放', 'libraries.sortLabels.datePlayed' => '播放日期', 'libraries.sortLabels.playCount' => '播放次数', @@ -2944,9 +2946,9 @@ extension on TranslationsZh { 'companionRemote.remote.tabLibraries' => '媒体库', 'companionRemote.remote.tabSearch' => '搜索', 'companionRemote.remote.tabDownloads' => '下载', - 'companionRemote.remote.tabSettings' => '设置', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabSettings' => '设置', 'companionRemote.remote.previous' => '上一个', 'companionRemote.remote.playPause' => '播放/暂停', 'companionRemote.remote.next' => '下一个', diff --git a/lib/i18n/sv.i18n.json b/lib/i18n/sv.i18n.json index 1e98b82f..37704582 100644 --- a/lib/i18n/sv.i18n.json +++ b/lib/i18n/sv.i18n.json @@ -751,6 +751,7 @@ "rating": "Betyg", "communityRating": "Användarbetyg", "criticRating": "Kritikerbetyg", + "userRating": "Användarbetyg", "lastPlayed": "Senast spelad", "datePlayed": "Speldatum", "playCount": "Antal spelningar", diff --git a/lib/i18n/zh.i18n.json b/lib/i18n/zh.i18n.json index 86943266..16cb9dc5 100644 --- a/lib/i18n/zh.i18n.json +++ b/lib/i18n/zh.i18n.json @@ -751,6 +751,7 @@ "rating": "评分", "communityRating": "社区评分", "criticRating": "影评人评分", + "userRating": "用户评分", "lastPlayed": "最近播放", "datePlayed": "播放日期", "playCount": "播放次数", diff --git a/lib/services/plex_client.dart b/lib/services/plex_client.dart index 84d1a396..1b186125 100644 --- a/lib/services/plex_client.dart +++ b/lib/services/plex_client.dart @@ -1834,19 +1834,46 @@ class PlexClient final response = await _getWithFailover('/library/sections/$sectionId/sorts'); final sorts = _extractDirectoryList(response, MediaSort.fromJson); - if (sorts.isNotEmpty) { - return sorts; - } - // Fallback: return common sort options if API doesn't provide them - return _getFallbackSorts(libraryType); + final base = sorts.isNotEmpty ? sorts : _getFallbackSorts(libraryType); + return _withExtraSorts(base, libraryType); } catch (e) { appLogger.e('Failed to get library sorts: $e'); // Return fallback sort options on error - return _getFallbackSorts(libraryType); + return _withExtraSorts(_getFallbackSorts(libraryType), libraryType); } } + /// Append sort options that Plex honors via the `sort=` parameter but does not + /// advertise in `/library/sections/{id}/sorts`. + /// + /// Plays (`viewCount`) and the signed-in user's rating (`userRating`) both + /// sort correctly on movie/show libraries, so we surface them client-side + /// (mirroring how the Jellyfin sort list is built). De-duped by key so we + /// never double up if a future Plex version starts advertising them. + List _withExtraSorts(List base, String? libraryType) { + final type = libraryType?.toLowerCase(); + if (type != 'movie' && type != 'show') return base; + + final keys = base.map((s) => s.key).toSet(); + final extras = [ + MediaSort( + key: 'viewCount', + descKey: 'viewCount:desc', + title: t.libraries.sortLabels.playCount, + defaultDirection: 'desc', + ), + MediaSort( + key: 'userRating', + descKey: 'userRating:desc', + title: t.libraries.sortLabels.userRating, + defaultDirection: 'desc', + ), + ].where((s) => !keys.contains(s.key)); + + return [...base, ...extras]; + } + /// Build fallback sort options based on library type. /// /// If [libraryType] is null, returns generic sorts without the show-specific options. diff --git a/test/services/plex_library_details_test.dart b/test/services/plex_library_details_test.dart index c5e87975..631ed673 100644 --- a/test/services/plex_library_details_test.dart +++ b/test/services/plex_library_details_test.dart @@ -71,9 +71,69 @@ void main() { 'episode.addedAt', 'lastViewedAt', 'random', + // Plex doesn't advertise these in /sorts; we append them for movie/show. + 'viewCount', + 'userRating', ]); }); + test('appends Plays and User Rating sorts only for movie/show libraries', () async { + PlexClient clientReturning() => makeClient((request) async { + if (request.url.path == '/library/sections/1/sorts') { + return http.Response( + jsonEncode({ + 'MediaContainer': { + 'Directory': [ + {'key': 'titleSort', 'title': 'Title', 'defaultDirection': 'asc'}, + ], + }, + }), + 200, + headers: {'content-type': 'application/json'}, + ); + } + return http.Response('not found', 404); + }); + + for (final type in ['movie', 'show']) { + final client = clientReturning(); + addTearDown(client.close); + final sorts = await client.fetchSortOptions('1', libraryType: type); + expect(sorts.map((s) => s.key), ['titleSort', 'viewCount', 'userRating'], reason: type); + } + + // Other library types (e.g. music) are left as the server returned them. + final musicClient = clientReturning(); + addTearDown(musicClient.close); + final musicSorts = await musicClient.fetchSortOptions('1', libraryType: 'artist'); + expect(musicSorts.map((s) => s.key), ['titleSort']); + }); + + test('does not duplicate Plays/User Rating when the server already advertises them', () async { + final client = makeClient((request) async { + if (request.url.path == '/library/sections/1/sorts') { + return http.Response( + jsonEncode({ + 'MediaContainer': { + 'Directory': [ + {'key': 'titleSort', 'title': 'Title', 'defaultDirection': 'asc'}, + {'key': 'viewCount', 'title': 'Plays', 'defaultDirection': 'desc'}, + ], + }, + }), + 200, + headers: {'content-type': 'application/json'}, + ); + } + return http.Response('not found', 404); + }); + addTearDown(client.close); + + final sorts = await client.fetchSortOptions('1', libraryType: 'movie'); + // viewCount already advertised -> not duplicated; userRating still appended. + expect(sorts.map((s) => s.key), ['titleSort', 'viewCount', 'userRating']); + }); + test('library content stamps known section when Plex omits librarySectionID on rows', () async { final client = makeClient((request) async { if (request.url.path == '/library/sections/7/all') {