feat(jellyfin): favorites library filter + heart favorite in the rate sheet
close #1485
This commit is contained in:
@@ -406,9 +406,6 @@
|
||||
"starValue": "${rating} / 5",
|
||||
"scoreValue": "${score} / 10",
|
||||
"setScore": "Задай оценка",
|
||||
"notRated": "Без оценка",
|
||||
"liked": "Харесано",
|
||||
"notLiked": "Не е харесано",
|
||||
"saved": "Запазено",
|
||||
"notAvailable": "Няма намерено съвпадение",
|
||||
"noConnectedTrackers": "Свържете тракер в Настройки, за да оценявате там."
|
||||
|
||||
@@ -406,9 +406,6 @@
|
||||
"starValue": "${rating} / 5",
|
||||
"scoreValue": "${score} / 10",
|
||||
"setScore": "Angiv en score",
|
||||
"notRated": "Ikke bedømt",
|
||||
"liked": "Synes godt om",
|
||||
"notLiked": "Ikke liket",
|
||||
"saved": "Gemt",
|
||||
"notAvailable": "Intet match fundet",
|
||||
"noConnectedTrackers": "Forbind en tracker i Indstillinger for at bedømme der."
|
||||
|
||||
@@ -406,9 +406,6 @@
|
||||
"starValue": "${rating} / 5",
|
||||
"scoreValue": "${score} / 10",
|
||||
"setScore": "Eine Wertung festlegen",
|
||||
"notRated": "Nicht bewertet",
|
||||
"liked": "Gefällt mir",
|
||||
"notLiked": "Nicht geliket",
|
||||
"saved": "Gespeichert",
|
||||
"notAvailable": "Keine Übereinstimmung gefunden",
|
||||
"noConnectedTrackers": "Verbinde einen Tracker in den Einstellungen, um dort zu bewerten."
|
||||
|
||||
@@ -412,9 +412,8 @@
|
||||
"starValue": "${rating} / 5",
|
||||
"scoreValue": "${score} / 10",
|
||||
"setScore": "Set a score",
|
||||
"notRated": "Not rated",
|
||||
"liked": "Liked",
|
||||
"notLiked": "Not liked",
|
||||
"favorite": "Favorite",
|
||||
"favorited": "Favorited",
|
||||
"saved": "Saved",
|
||||
"notAvailable": "No match found",
|
||||
"noConnectedTrackers": "Connect a tracker in Settings to rate there."
|
||||
@@ -778,7 +777,8 @@
|
||||
"year": "Year",
|
||||
"contentRating": "Content Rating",
|
||||
"tag": "Tag",
|
||||
"unwatched": "Unwatched"
|
||||
"unwatched": "Unwatched",
|
||||
"favorites": "Favorites"
|
||||
},
|
||||
"sortLabels": {
|
||||
"title": "Title",
|
||||
|
||||
@@ -406,9 +406,6 @@
|
||||
"starValue": "${rating} / 5",
|
||||
"scoreValue": "${score} / 10",
|
||||
"setScore": "Establecer puntuación",
|
||||
"notRated": "Sin calificar",
|
||||
"liked": "Me gusta",
|
||||
"notLiked": "Sin Me gusta",
|
||||
"saved": "Guardado",
|
||||
"notAvailable": "No se encontró coincidencia",
|
||||
"noConnectedTrackers": "Conecta un tracker en Ajustes para calificar allí."
|
||||
|
||||
@@ -406,9 +406,6 @@
|
||||
"starValue": "${rating} / 5",
|
||||
"scoreValue": "${score} / 10",
|
||||
"setScore": "Définir une note",
|
||||
"notRated": "Non noté",
|
||||
"liked": "Aimé",
|
||||
"notLiked": "Non aimé",
|
||||
"saved": "Enregistré",
|
||||
"notAvailable": "Aucune correspondance trouvée",
|
||||
"noConnectedTrackers": "Connectez un tracker dans Paramètres pour y noter."
|
||||
|
||||
@@ -406,9 +406,6 @@
|
||||
"starValue": "${rating} / 5",
|
||||
"scoreValue": "${score} / 10",
|
||||
"setScore": "Imposta un punteggio",
|
||||
"notRated": "Non valutato",
|
||||
"liked": "Mi piace",
|
||||
"notLiked": "Non contrassegnato con Mi piace",
|
||||
"saved": "Salvato",
|
||||
"notAvailable": "Nessuna corrispondenza trovata",
|
||||
"noConnectedTrackers": "Collega un tracker in Impostazioni per valutarlo lì."
|
||||
|
||||
@@ -406,9 +406,6 @@
|
||||
"starValue": "${rating} / 5",
|
||||
"scoreValue": "${score} / 10",
|
||||
"setScore": "スコアを設定",
|
||||
"notRated": "未評価",
|
||||
"liked": "いいね済み",
|
||||
"notLiked": "いいねなし",
|
||||
"saved": "保存済み",
|
||||
"notAvailable": "一致なし",
|
||||
"noConnectedTrackers": "設定でトラッカーを接続すると、そこで評価できます。"
|
||||
|
||||
@@ -406,9 +406,6 @@
|
||||
"starValue": "${rating} / 5",
|
||||
"scoreValue": "${score} / 10",
|
||||
"setScore": "점수 설정",
|
||||
"notRated": "평가 없음",
|
||||
"liked": "좋아요",
|
||||
"notLiked": "좋아요 아님",
|
||||
"saved": "저장됨",
|
||||
"notAvailable": "일치 항목 없음",
|
||||
"noConnectedTrackers": "설정에서 트래커를 연결하면 거기에도 평가할 수 있습니다."
|
||||
|
||||
@@ -406,9 +406,6 @@
|
||||
"starValue": "${rating} / 5",
|
||||
"scoreValue": "${score} / 10",
|
||||
"setScore": "Sett en poengsum",
|
||||
"notRated": "Ikke vurdert",
|
||||
"liked": "Likt",
|
||||
"notLiked": "Ikke likt",
|
||||
"saved": "Lagret",
|
||||
"notAvailable": "Ingen treff",
|
||||
"noConnectedTrackers": "Koble til en sporer i Innstillinger for å vurdere der."
|
||||
|
||||
@@ -406,9 +406,6 @@
|
||||
"starValue": "${rating} / 5",
|
||||
"scoreValue": "${score} / 10",
|
||||
"setScore": "Score instellen",
|
||||
"notRated": "Niet beoordeeld",
|
||||
"liked": "Geliket",
|
||||
"notLiked": "Niet geliket",
|
||||
"saved": "Opgeslagen",
|
||||
"notAvailable": "Geen match gevonden",
|
||||
"noConnectedTrackers": "Verbind een tracker in Instellingen om daar te beoordelen."
|
||||
|
||||
@@ -406,9 +406,6 @@
|
||||
"starValue": "${rating} / 5",
|
||||
"scoreValue": "${score} / 10",
|
||||
"setScore": "Ustaw ocenę",
|
||||
"notRated": "Nie oceniono",
|
||||
"liked": "Polubione",
|
||||
"notLiked": "Nie polubiono",
|
||||
"saved": "Zapisano",
|
||||
"notAvailable": "Nie znaleziono dopasowania",
|
||||
"noConnectedTrackers": "Połącz tracker w Ustawieniach, aby tam oceniać."
|
||||
|
||||
@@ -406,9 +406,6 @@
|
||||
"starValue": "${rating} / 5",
|
||||
"scoreValue": "${score} / 10",
|
||||
"setScore": "Definir pontuação",
|
||||
"notRated": "Sem avaliação",
|
||||
"liked": "Gostei",
|
||||
"notLiked": "Não marcado como gostei",
|
||||
"saved": "Salvo",
|
||||
"notAvailable": "Nenhuma correspondência encontrada",
|
||||
"noConnectedTrackers": "Conecte um rastreador em Configurações para avaliar lá."
|
||||
|
||||
@@ -406,9 +406,6 @@
|
||||
"starValue": "${rating} / 5",
|
||||
"scoreValue": "${score} / 10",
|
||||
"setScore": "Установить оценку",
|
||||
"notRated": "Без оценки",
|
||||
"liked": "Понравилось",
|
||||
"notLiked": "Не отмечено понравившимся",
|
||||
"saved": "Сохранено",
|
||||
"notAvailable": "Совпадений не найдено",
|
||||
"noConnectedTrackers": "Подключите трекер в настройках, чтобы оценивать там."
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
/// To regenerate, run: `dart run slang`
|
||||
///
|
||||
/// Locales: 16
|
||||
/// Strings: 20761 (1297 per locale)
|
||||
/// Strings: 20716 (1294 per locale)
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint, unused_import
|
||||
|
||||
@@ -544,9 +544,6 @@ class _TranslationsRateSheetBg extends TranslationsRateSheetEn {
|
||||
@override String starValue({required Object rating}) => '${rating} / 5';
|
||||
@override String scoreValue({required Object score}) => '${score} / 10';
|
||||
@override String get setScore => 'Задай оценка';
|
||||
@override String get notRated => 'Без оценка';
|
||||
@override String get liked => 'Харесано';
|
||||
@override String get notLiked => 'Не е харесано';
|
||||
@override String get saved => 'Запазено';
|
||||
@override String get notAvailable => 'Няма намерено съвпадение';
|
||||
@override String get noConnectedTrackers => 'Свържете тракер в Настройки, за да оценявате там.';
|
||||
@@ -2330,9 +2327,6 @@ extension on TranslationsBg {
|
||||
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
|
||||
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
|
||||
'rateSheet.setScore' => 'Задай оценка',
|
||||
'rateSheet.notRated' => 'Без оценка',
|
||||
'rateSheet.liked' => 'Харесано',
|
||||
'rateSheet.notLiked' => 'Не е харесано',
|
||||
'rateSheet.saved' => 'Запазено',
|
||||
'rateSheet.notAvailable' => 'Няма намерено съвпадение',
|
||||
'rateSheet.noConnectedTrackers' => 'Свържете тракер в Настройки, за да оценявате там.',
|
||||
@@ -2458,11 +2452,11 @@ extension on TranslationsBg {
|
||||
'messages.libraryScanFailed' => ({required Object error}) => 'Неуспешно сканиране на библиотеката: ${error}',
|
||||
'messages.metadataRefreshing' => ({required Object title}) => 'Опресняване на метаданни за "${title}"...',
|
||||
'messages.metadataRefreshStarted' => ({required Object title}) => 'Опресняването на метаданни е стартирано за "${title}"',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.metadataRefreshFailed' => ({required Object error}) => 'Неуспешно опресняване на метаданни: ${error}',
|
||||
'messages.logoutConfirm' => 'Сигурни ли сте, че искате да излезете?',
|
||||
'messages.noSeasonsFound' => 'Не са намерени сезони',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.seasonsLoadFailed' => 'Неуспешно зареждане на сезони',
|
||||
'messages.noEpisodesFound' => 'Не са намерени епизоди в първия сезон',
|
||||
'messages.noEpisodesFoundGeneral' => 'Не са намерени епизоди',
|
||||
@@ -2972,11 +2966,11 @@ extension on TranslationsBg {
|
||||
'companionRemote.session.serverStopped' => 'Сървърът за дистанционно управление е спрян',
|
||||
'companionRemote.session.serverRunningDescription' => 'Мобилни устройства във вашата мрежа могат да се свързват с това приложение',
|
||||
'companionRemote.session.serverStoppedDescription' => 'Стартирайте сървъра, за да позволите на мобилни устройства да се свързват',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.usePhoneToControl' => 'Използвайте мобилното си устройство, за да управлявате това приложение',
|
||||
'companionRemote.session.startServer' => 'Стартирай сървър',
|
||||
'companionRemote.session.stopServer' => 'Спри сървър',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.minimize' => 'Минимизирай',
|
||||
'companionRemote.pairing.discoveryDescription' => 'Plezy устройства със същия Plex акаунт се показват тук',
|
||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||
|
||||
@@ -544,9 +544,6 @@ class _TranslationsRateSheetDa extends TranslationsRateSheetEn {
|
||||
@override String starValue({required Object rating}) => '${rating} / 5';
|
||||
@override String scoreValue({required Object score}) => '${score} / 10';
|
||||
@override String get setScore => 'Angiv en score';
|
||||
@override String get notRated => 'Ikke bedømt';
|
||||
@override String get liked => 'Synes godt om';
|
||||
@override String get notLiked => 'Ikke liket';
|
||||
@override String get saved => 'Gemt';
|
||||
@override String get notAvailable => 'Intet match fundet';
|
||||
@override String get noConnectedTrackers => 'Forbind en tracker i Indstillinger for at bedømme der.';
|
||||
@@ -2330,9 +2327,6 @@ extension on TranslationsDa {
|
||||
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
|
||||
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
|
||||
'rateSheet.setScore' => 'Angiv en score',
|
||||
'rateSheet.notRated' => 'Ikke bedømt',
|
||||
'rateSheet.liked' => 'Synes godt om',
|
||||
'rateSheet.notLiked' => 'Ikke liket',
|
||||
'rateSheet.saved' => 'Gemt',
|
||||
'rateSheet.notAvailable' => 'Intet match fundet',
|
||||
'rateSheet.noConnectedTrackers' => 'Forbind en tracker i Indstillinger for at bedømme der.',
|
||||
@@ -2458,11 +2452,11 @@ extension on TranslationsDa {
|
||||
'messages.libraryScanFailed' => ({required Object error}) => 'Kunne ikke scanne bibliotek: ${error}',
|
||||
'messages.metadataRefreshing' => ({required Object title}) => 'Opdaterer metadata for "${title}"...',
|
||||
'messages.metadataRefreshStarted' => ({required Object title}) => 'Metadataopdatering startet for "${title}"',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.metadataRefreshFailed' => ({required Object error}) => 'Kunne ikke opdatere metadata: ${error}',
|
||||
'messages.logoutConfirm' => 'Er du sikker på, at du vil logge ud?',
|
||||
'messages.noSeasonsFound' => 'Ingen sæsoner fundet',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.seasonsLoadFailed' => 'Kunne ikke indlæse sæsoner',
|
||||
'messages.noEpisodesFound' => 'Ingen episoder fundet i første sæson',
|
||||
'messages.noEpisodesFoundGeneral' => 'Ingen episoder fundet',
|
||||
@@ -2972,11 +2966,11 @@ extension on TranslationsDa {
|
||||
'companionRemote.session.serverStopped' => 'Fjernserver stoppet',
|
||||
'companionRemote.session.serverRunningDescription' => 'Mobile enheder på dit netværk kan oprette forbindelse til denne app',
|
||||
'companionRemote.session.serverStoppedDescription' => 'Start serveren for at tillade mobilenheder at oprette forbindelse',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.usePhoneToControl' => 'Brug din mobilenhed til at styre denne app',
|
||||
'companionRemote.session.startServer' => 'Start server',
|
||||
'companionRemote.session.stopServer' => 'Stop server',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.minimize' => 'Minimér',
|
||||
'companionRemote.pairing.discoveryDescription' => 'Plezy-enheder med samme Plex-konto vises her',
|
||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||
|
||||
@@ -544,9 +544,6 @@ class _TranslationsRateSheetDe extends TranslationsRateSheetEn {
|
||||
@override String starValue({required Object rating}) => '${rating} / 5';
|
||||
@override String scoreValue({required Object score}) => '${score} / 10';
|
||||
@override String get setScore => 'Eine Wertung festlegen';
|
||||
@override String get notRated => 'Nicht bewertet';
|
||||
@override String get liked => 'Gefällt mir';
|
||||
@override String get notLiked => 'Nicht geliket';
|
||||
@override String get saved => 'Gespeichert';
|
||||
@override String get notAvailable => 'Keine Übereinstimmung gefunden';
|
||||
@override String get noConnectedTrackers => 'Verbinde einen Tracker in den Einstellungen, um dort zu bewerten.';
|
||||
@@ -2330,9 +2327,6 @@ extension on TranslationsDe {
|
||||
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
|
||||
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
|
||||
'rateSheet.setScore' => 'Eine Wertung festlegen',
|
||||
'rateSheet.notRated' => 'Nicht bewertet',
|
||||
'rateSheet.liked' => 'Gefällt mir',
|
||||
'rateSheet.notLiked' => 'Nicht geliket',
|
||||
'rateSheet.saved' => 'Gespeichert',
|
||||
'rateSheet.notAvailable' => 'Keine Übereinstimmung gefunden',
|
||||
'rateSheet.noConnectedTrackers' => 'Verbinde einen Tracker in den Einstellungen, um dort zu bewerten.',
|
||||
@@ -2458,11 +2452,11 @@ extension on TranslationsDe {
|
||||
'messages.libraryScanFailed' => ({required Object error}) => 'Fehler beim Scannen der Mediathek: ${error}',
|
||||
'messages.metadataRefreshing' => ({required Object title}) => 'Metadaten werden aktualisiert für „${title}“...',
|
||||
'messages.metadataRefreshStarted' => ({required Object title}) => 'Metadaten-Aktualisierung gestartet für „${title}“',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.metadataRefreshFailed' => ({required Object error}) => 'Metadaten konnten nicht aktualisiert werden: ${error}',
|
||||
'messages.logoutConfirm' => 'Abmeldung wirklich durchführen?',
|
||||
'messages.noSeasonsFound' => 'Keine Staffeln gefunden',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.seasonsLoadFailed' => 'Staffeln konnten nicht geladen werden',
|
||||
'messages.noEpisodesFound' => 'Keine Episoden in der ersten Staffel gefunden',
|
||||
'messages.noEpisodesFoundGeneral' => 'Keine Episoden gefunden',
|
||||
@@ -2972,11 +2966,11 @@ extension on TranslationsDe {
|
||||
'companionRemote.session.serverStopped' => 'Remote-Server gestoppt',
|
||||
'companionRemote.session.serverRunningDescription' => 'Mobilgeräte in deinem Netzwerk können sich mit dieser App verbinden',
|
||||
'companionRemote.session.serverStoppedDescription' => 'Starte den Server, um Mobilgeräten die Verbindung zu ermöglichen',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.usePhoneToControl' => 'Verwende dein Mobilgerät, um diese App zu steuern',
|
||||
'companionRemote.session.startServer' => 'Server starten',
|
||||
'companionRemote.session.stopServer' => 'Server stoppen',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.minimize' => 'Minimieren',
|
||||
'companionRemote.pairing.discoveryDescription' => 'Plezy-Geräte mit demselben Plex-Konto erscheinen hier',
|
||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||
|
||||
+11
-11
@@ -1283,14 +1283,11 @@ class TranslationsRateSheetEn {
|
||||
/// en: 'Set a score'
|
||||
String get setScore => 'Set a score';
|
||||
|
||||
/// en: 'Not rated'
|
||||
String get notRated => 'Not rated';
|
||||
/// en: 'Favorite'
|
||||
String get favorite => 'Favorite';
|
||||
|
||||
/// en: 'Liked'
|
||||
String get liked => 'Liked';
|
||||
|
||||
/// en: 'Not liked'
|
||||
String get notLiked => 'Not liked';
|
||||
/// en: 'Favorited'
|
||||
String get favorited => 'Favorited';
|
||||
|
||||
/// en: 'Saved'
|
||||
String get saved => 'Saved';
|
||||
@@ -4167,6 +4164,9 @@ class TranslationsLibrariesFilterCategoriesEn {
|
||||
|
||||
/// en: 'Unwatched'
|
||||
String get unwatched => 'Unwatched';
|
||||
|
||||
/// en: 'Favorites'
|
||||
String get favorites => 'Favorites';
|
||||
}
|
||||
|
||||
// Path: libraries.sortLabels
|
||||
@@ -4963,9 +4963,8 @@ extension on Translations {
|
||||
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
|
||||
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
|
||||
'rateSheet.setScore' => 'Set a score',
|
||||
'rateSheet.notRated' => 'Not rated',
|
||||
'rateSheet.liked' => 'Liked',
|
||||
'rateSheet.notLiked' => 'Not liked',
|
||||
'rateSheet.favorite' => 'Favorite',
|
||||
'rateSheet.favorited' => 'Favorited',
|
||||
'rateSheet.saved' => 'Saved',
|
||||
'rateSheet.notAvailable' => 'No match found',
|
||||
'rateSheet.noConnectedTrackers' => 'Connect a tracker in Settings to rate there.',
|
||||
@@ -5085,9 +5084,9 @@ extension on Translations {
|
||||
'messages.unableToDetermineLibrarySection' => 'Unable to determine library section for this item',
|
||||
'messages.logsCleared' => 'Logs cleared',
|
||||
'messages.logsCopied' => 'Logs copied to clipboard',
|
||||
'messages.noLogsAvailable' => 'No logs available',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.noLogsAvailable' => 'No logs available',
|
||||
'messages.libraryScanning' => ({required Object title}) => 'Scanning "${title}"...',
|
||||
'messages.libraryScanStarted' => ({required Object title}) => 'Library scan started for "${title}"',
|
||||
'messages.libraryScanFailed' => ({required Object error}) => 'Failed to scan library: ${error}',
|
||||
@@ -5299,6 +5298,7 @@ extension on Translations {
|
||||
'libraries.filterCategories.contentRating' => 'Content Rating',
|
||||
'libraries.filterCategories.tag' => 'Tag',
|
||||
'libraries.filterCategories.unwatched' => 'Unwatched',
|
||||
'libraries.filterCategories.favorites' => 'Favorites',
|
||||
'libraries.sortLabels.title' => 'Title',
|
||||
'libraries.sortLabels.dateAdded' => 'Date Added',
|
||||
'libraries.sortLabels.releaseDate' => 'Release Date',
|
||||
|
||||
@@ -544,9 +544,6 @@ class _TranslationsRateSheetEs extends TranslationsRateSheetEn {
|
||||
@override String starValue({required Object rating}) => '${rating} / 5';
|
||||
@override String scoreValue({required Object score}) => '${score} / 10';
|
||||
@override String get setScore => 'Establecer puntuación';
|
||||
@override String get notRated => 'Sin calificar';
|
||||
@override String get liked => 'Me gusta';
|
||||
@override String get notLiked => 'Sin Me gusta';
|
||||
@override String get saved => 'Guardado';
|
||||
@override String get notAvailable => 'No se encontró coincidencia';
|
||||
@override String get noConnectedTrackers => 'Conecta un tracker en Ajustes para calificar allí.';
|
||||
@@ -2330,9 +2327,6 @@ extension on TranslationsEs {
|
||||
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
|
||||
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
|
||||
'rateSheet.setScore' => 'Establecer puntuación',
|
||||
'rateSheet.notRated' => 'Sin calificar',
|
||||
'rateSheet.liked' => 'Me gusta',
|
||||
'rateSheet.notLiked' => 'Sin Me gusta',
|
||||
'rateSheet.saved' => 'Guardado',
|
||||
'rateSheet.notAvailable' => 'No se encontró coincidencia',
|
||||
'rateSheet.noConnectedTrackers' => 'Conecta un tracker en Ajustes para calificar allí.',
|
||||
@@ -2458,11 +2452,11 @@ extension on TranslationsEs {
|
||||
'messages.libraryScanFailed' => ({required Object error}) => 'Error al escanear biblioteca: ${error}',
|
||||
'messages.metadataRefreshing' => ({required Object title}) => 'Actualizando metadatos de "${title}"...',
|
||||
'messages.metadataRefreshStarted' => ({required Object title}) => 'Actualización de metadatos iniciada para "${title}"',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.metadataRefreshFailed' => ({required Object error}) => 'Error al actualizar metadatos: ${error}',
|
||||
'messages.logoutConfirm' => '¿Estás seguro de que quieres cerrar sesión?',
|
||||
'messages.noSeasonsFound' => 'No se encontraron temporadas',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.seasonsLoadFailed' => 'No se pudieron cargar las temporadas',
|
||||
'messages.noEpisodesFound' => 'No se encontraron episodios en la primera temporada',
|
||||
'messages.noEpisodesFoundGeneral' => 'No se encontraron episodios',
|
||||
@@ -2972,11 +2966,11 @@ extension on TranslationsEs {
|
||||
'companionRemote.session.serverStopped' => 'Servidor remoto detenido',
|
||||
'companionRemote.session.serverRunningDescription' => 'Los dispositivos móviles de tu red pueden conectarse a esta app',
|
||||
'companionRemote.session.serverStoppedDescription' => 'Inicia el servidor para permitir que los dispositivos móviles se conecten',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.usePhoneToControl' => 'Usa tu dispositivo móvil para controlar esta aplicación',
|
||||
'companionRemote.session.startServer' => 'Iniciar servidor',
|
||||
'companionRemote.session.stopServer' => 'Detener servidor',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.minimize' => 'Minimizar',
|
||||
'companionRemote.pairing.discoveryDescription' => 'Los dispositivos Plezy con la misma cuenta Plex aparecen aquí',
|
||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||
|
||||
@@ -544,9 +544,6 @@ class _TranslationsRateSheetFr extends TranslationsRateSheetEn {
|
||||
@override String starValue({required Object rating}) => '${rating} / 5';
|
||||
@override String scoreValue({required Object score}) => '${score} / 10';
|
||||
@override String get setScore => 'Définir une note';
|
||||
@override String get notRated => 'Non noté';
|
||||
@override String get liked => 'Aimé';
|
||||
@override String get notLiked => 'Non aimé';
|
||||
@override String get saved => 'Enregistré';
|
||||
@override String get notAvailable => 'Aucune correspondance trouvée';
|
||||
@override String get noConnectedTrackers => 'Connectez un tracker dans Paramètres pour y noter.';
|
||||
@@ -2330,9 +2327,6 @@ extension on TranslationsFr {
|
||||
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
|
||||
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
|
||||
'rateSheet.setScore' => 'Définir une note',
|
||||
'rateSheet.notRated' => 'Non noté',
|
||||
'rateSheet.liked' => 'Aimé',
|
||||
'rateSheet.notLiked' => 'Non aimé',
|
||||
'rateSheet.saved' => 'Enregistré',
|
||||
'rateSheet.notAvailable' => 'Aucune correspondance trouvée',
|
||||
'rateSheet.noConnectedTrackers' => 'Connectez un tracker dans Paramètres pour y noter.',
|
||||
@@ -2458,11 +2452,11 @@ extension on TranslationsFr {
|
||||
'messages.libraryScanFailed' => ({required Object error}) => 'Échec du scan de la bibliothèque: ${error}',
|
||||
'messages.metadataRefreshing' => ({required Object title}) => 'Actualisation des métadonnées pour "${title}"...',
|
||||
'messages.metadataRefreshStarted' => ({required Object title}) => 'Actualisation des métadonnées lancée pour "${title}"',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.metadataRefreshFailed' => ({required Object error}) => 'Échec de l\'actualisation des métadonnées: ${error}',
|
||||
'messages.logoutConfirm' => 'Êtes-vous sûr de vouloir vous déconnecter ?',
|
||||
'messages.noSeasonsFound' => 'Aucune saison trouvée',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.seasonsLoadFailed' => 'Impossible de charger les saisons',
|
||||
'messages.noEpisodesFound' => 'Aucun épisode trouvé dans la première saison',
|
||||
'messages.noEpisodesFoundGeneral' => 'Aucun épisode trouvé',
|
||||
@@ -2972,11 +2966,11 @@ extension on TranslationsFr {
|
||||
'companionRemote.session.serverStopped' => 'Serveur distant arrêté',
|
||||
'companionRemote.session.serverRunningDescription' => 'Les appareils mobiles de votre réseau peuvent se connecter à cette app',
|
||||
'companionRemote.session.serverStoppedDescription' => 'Démarrez le serveur pour permettre aux appareils mobiles de se connecter',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.usePhoneToControl' => 'Utilisez votre appareil mobile pour contrôler cette application',
|
||||
'companionRemote.session.startServer' => 'Démarrer le serveur',
|
||||
'companionRemote.session.stopServer' => 'Arrêter le serveur',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.minimize' => 'Réduire',
|
||||
'companionRemote.pairing.discoveryDescription' => 'Les appareils Plezy avec le même compte Plex apparaissent ici',
|
||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||
|
||||
@@ -544,9 +544,6 @@ class _TranslationsRateSheetIt extends TranslationsRateSheetEn {
|
||||
@override String starValue({required Object rating}) => '${rating} / 5';
|
||||
@override String scoreValue({required Object score}) => '${score} / 10';
|
||||
@override String get setScore => 'Imposta un punteggio';
|
||||
@override String get notRated => 'Non valutato';
|
||||
@override String get liked => 'Mi piace';
|
||||
@override String get notLiked => 'Non contrassegnato con Mi piace';
|
||||
@override String get saved => 'Salvato';
|
||||
@override String get notAvailable => 'Nessuna corrispondenza trovata';
|
||||
@override String get noConnectedTrackers => 'Collega un tracker in Impostazioni per valutarlo lì.';
|
||||
@@ -2330,9 +2327,6 @@ extension on TranslationsIt {
|
||||
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
|
||||
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
|
||||
'rateSheet.setScore' => 'Imposta un punteggio',
|
||||
'rateSheet.notRated' => 'Non valutato',
|
||||
'rateSheet.liked' => 'Mi piace',
|
||||
'rateSheet.notLiked' => 'Non contrassegnato con Mi piace',
|
||||
'rateSheet.saved' => 'Salvato',
|
||||
'rateSheet.notAvailable' => 'Nessuna corrispondenza trovata',
|
||||
'rateSheet.noConnectedTrackers' => 'Collega un tracker in Impostazioni per valutarlo lì.',
|
||||
@@ -2458,11 +2452,11 @@ extension on TranslationsIt {
|
||||
'messages.libraryScanFailed' => ({required Object error}) => 'Impossibile eseguire scansione della libreria: ${error}',
|
||||
'messages.metadataRefreshing' => ({required Object title}) => 'Aggiornamento metadati per "${title}"...',
|
||||
'messages.metadataRefreshStarted' => ({required Object title}) => 'Aggiornamento metadati per "${title}"',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.metadataRefreshFailed' => ({required Object error}) => 'Errore aggiornamento metadati: ${error}',
|
||||
'messages.logoutConfirm' => 'Sei sicuro di volerti disconnettere?',
|
||||
'messages.noSeasonsFound' => 'Nessuna stagione trovata',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.seasonsLoadFailed' => 'Impossibile caricare le stagioni',
|
||||
'messages.noEpisodesFound' => 'Nessun episodio trovato nella prima stagione',
|
||||
'messages.noEpisodesFoundGeneral' => 'Nessun episodio trovato',
|
||||
@@ -2972,11 +2966,11 @@ extension on TranslationsIt {
|
||||
'companionRemote.session.serverStopped' => 'Server remoto arrestato',
|
||||
'companionRemote.session.serverRunningDescription' => 'I dispositivi mobili sulla tua rete possono connettersi a questa app',
|
||||
'companionRemote.session.serverStoppedDescription' => 'Avvia il server per consentire ai dispositivi mobili di connettersi',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.usePhoneToControl' => 'Usa il tuo dispositivo mobile per controllare questa app',
|
||||
'companionRemote.session.startServer' => 'Avvia server',
|
||||
'companionRemote.session.stopServer' => 'Arresta server',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.minimize' => 'Riduci',
|
||||
'companionRemote.pairing.discoveryDescription' => 'I dispositivi Plezy con lo stesso account Plex appaiono qui',
|
||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||
|
||||
@@ -544,9 +544,6 @@ class _TranslationsRateSheetJa extends TranslationsRateSheetEn {
|
||||
@override String starValue({required Object rating}) => '${rating} / 5';
|
||||
@override String scoreValue({required Object score}) => '${score} / 10';
|
||||
@override String get setScore => 'スコアを設定';
|
||||
@override String get notRated => '未評価';
|
||||
@override String get liked => 'いいね済み';
|
||||
@override String get notLiked => 'いいねなし';
|
||||
@override String get saved => '保存済み';
|
||||
@override String get notAvailable => '一致なし';
|
||||
@override String get noConnectedTrackers => '設定でトラッカーを接続すると、そこで評価できます。';
|
||||
@@ -2330,9 +2327,6 @@ extension on TranslationsJa {
|
||||
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
|
||||
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
|
||||
'rateSheet.setScore' => 'スコアを設定',
|
||||
'rateSheet.notRated' => '未評価',
|
||||
'rateSheet.liked' => 'いいね済み',
|
||||
'rateSheet.notLiked' => 'いいねなし',
|
||||
'rateSheet.saved' => '保存済み',
|
||||
'rateSheet.notAvailable' => '一致なし',
|
||||
'rateSheet.noConnectedTrackers' => '設定でトラッカーを接続すると、そこで評価できます。',
|
||||
@@ -2458,11 +2452,11 @@ extension on TranslationsJa {
|
||||
'messages.libraryScanFailed' => ({required Object error}) => 'ライブラリのスキャンに失敗しました: ${error}',
|
||||
'messages.metadataRefreshing' => ({required Object title}) => '"${title}"のメタデータを更新中...',
|
||||
'messages.metadataRefreshStarted' => ({required Object title}) => '"${title}"のメタデータ更新を開始しました',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.metadataRefreshFailed' => ({required Object error}) => 'メタデータの更新に失敗しました: ${error}',
|
||||
'messages.logoutConfirm' => 'ログアウトしてもよろしいですか?',
|
||||
'messages.noSeasonsFound' => 'シーズンが見つかりません',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.seasonsLoadFailed' => 'シーズンを読み込めませんでした',
|
||||
'messages.noEpisodesFound' => '最初のシーズンにエピソードが見つかりません',
|
||||
'messages.noEpisodesFoundGeneral' => 'エピソードが見つかりません',
|
||||
@@ -2972,11 +2966,11 @@ extension on TranslationsJa {
|
||||
'companionRemote.session.serverStopped' => 'リモートサーバー停止中',
|
||||
'companionRemote.session.serverRunningDescription' => 'ネットワーク上のモバイルデバイスがこのアプリに接続できます',
|
||||
'companionRemote.session.serverStoppedDescription' => 'モバイルデバイスの接続を許可するにはサーバーを起動してください',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.usePhoneToControl' => 'モバイルデバイスでこのアプリを操作できます',
|
||||
'companionRemote.session.startServer' => 'サーバーを起動',
|
||||
'companionRemote.session.stopServer' => 'サーバーを停止',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.minimize' => '最小化',
|
||||
'companionRemote.pairing.discoveryDescription' => '同じPlexアカウントのPlezyデバイスがここに表示されます',
|
||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||
|
||||
@@ -544,9 +544,6 @@ class _TranslationsRateSheetKo extends TranslationsRateSheetEn {
|
||||
@override String starValue({required Object rating}) => '${rating} / 5';
|
||||
@override String scoreValue({required Object score}) => '${score} / 10';
|
||||
@override String get setScore => '점수 설정';
|
||||
@override String get notRated => '평가 없음';
|
||||
@override String get liked => '좋아요';
|
||||
@override String get notLiked => '좋아요 아님';
|
||||
@override String get saved => '저장됨';
|
||||
@override String get notAvailable => '일치 항목 없음';
|
||||
@override String get noConnectedTrackers => '설정에서 트래커를 연결하면 거기에도 평가할 수 있습니다.';
|
||||
@@ -2330,9 +2327,6 @@ extension on TranslationsKo {
|
||||
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
|
||||
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
|
||||
'rateSheet.setScore' => '점수 설정',
|
||||
'rateSheet.notRated' => '평가 없음',
|
||||
'rateSheet.liked' => '좋아요',
|
||||
'rateSheet.notLiked' => '좋아요 아님',
|
||||
'rateSheet.saved' => '저장됨',
|
||||
'rateSheet.notAvailable' => '일치 항목 없음',
|
||||
'rateSheet.noConnectedTrackers' => '설정에서 트래커를 연결하면 거기에도 평가할 수 있습니다.',
|
||||
@@ -2458,11 +2452,11 @@ extension on TranslationsKo {
|
||||
'messages.libraryScanFailed' => ({required Object error}) => '미디어 라이브러리 스캔 실패: ${error}',
|
||||
'messages.metadataRefreshing' => ({required Object title}) => '"${title}" 메타데이터 새로고침 중...',
|
||||
'messages.metadataRefreshStarted' => ({required Object title}) => '"${title}" 메타데이터 새로고침 시작됨',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.metadataRefreshFailed' => ({required Object error}) => '메타데이터 새로고침 실패: ${error}',
|
||||
'messages.logoutConfirm' => '로그아웃 하시겠습니까?',
|
||||
'messages.noSeasonsFound' => '시즌을 찾을 수 없음',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.seasonsLoadFailed' => '시즌을 불러오지 못했습니다',
|
||||
'messages.noEpisodesFound' => '시즌 1에서 에피소드를 찾을 수 없습니다',
|
||||
'messages.noEpisodesFoundGeneral' => '에피소드를 찾을 수 없습니다',
|
||||
@@ -2972,11 +2966,11 @@ extension on TranslationsKo {
|
||||
'companionRemote.session.serverStopped' => '원격 서버 중지됨',
|
||||
'companionRemote.session.serverRunningDescription' => '네트워크의 모바일 기기가 이 앱에 연결할 수 있습니다',
|
||||
'companionRemote.session.serverStoppedDescription' => '모바일 기기의 연결을 허용하려면 서버를 시작하세요',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.usePhoneToControl' => '모바일 기기로 이 앱을 제어하세요',
|
||||
'companionRemote.session.startServer' => '서버 시작',
|
||||
'companionRemote.session.stopServer' => '서버 중지',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.minimize' => '최소화',
|
||||
'companionRemote.pairing.discoveryDescription' => '같은 Plex 계정의 Plezy 기기가 여기에 표시됩니다',
|
||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||
|
||||
@@ -544,9 +544,6 @@ class _TranslationsRateSheetNb extends TranslationsRateSheetEn {
|
||||
@override String starValue({required Object rating}) => '${rating} / 5';
|
||||
@override String scoreValue({required Object score}) => '${score} / 10';
|
||||
@override String get setScore => 'Sett en poengsum';
|
||||
@override String get notRated => 'Ikke vurdert';
|
||||
@override String get liked => 'Likt';
|
||||
@override String get notLiked => 'Ikke likt';
|
||||
@override String get saved => 'Lagret';
|
||||
@override String get notAvailable => 'Ingen treff';
|
||||
@override String get noConnectedTrackers => 'Koble til en sporer i Innstillinger for å vurdere der.';
|
||||
@@ -2330,9 +2327,6 @@ extension on TranslationsNb {
|
||||
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
|
||||
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
|
||||
'rateSheet.setScore' => 'Sett en poengsum',
|
||||
'rateSheet.notRated' => 'Ikke vurdert',
|
||||
'rateSheet.liked' => 'Likt',
|
||||
'rateSheet.notLiked' => 'Ikke likt',
|
||||
'rateSheet.saved' => 'Lagret',
|
||||
'rateSheet.notAvailable' => 'Ingen treff',
|
||||
'rateSheet.noConnectedTrackers' => 'Koble til en sporer i Innstillinger for å vurdere der.',
|
||||
@@ -2458,11 +2452,11 @@ extension on TranslationsNb {
|
||||
'messages.libraryScanFailed' => ({required Object error}) => 'Kunne ikke skanne bibliotek: ${error}',
|
||||
'messages.metadataRefreshing' => ({required Object title}) => 'Oppdaterer metadata for "${title}"...',
|
||||
'messages.metadataRefreshStarted' => ({required Object title}) => 'Metadataoppdatering startet for "${title}"',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.metadataRefreshFailed' => ({required Object error}) => 'Kunne ikke oppdatere metadata: ${error}',
|
||||
'messages.logoutConfirm' => 'Er du sikker på at du vil logge ut?',
|
||||
'messages.noSeasonsFound' => 'Ingen sesonger funnet',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.seasonsLoadFailed' => 'Kunne ikke laste sesonger',
|
||||
'messages.noEpisodesFound' => 'Ingen episoder funnet i første sesong',
|
||||
'messages.noEpisodesFoundGeneral' => 'Ingen episoder funnet',
|
||||
@@ -2972,11 +2966,11 @@ extension on TranslationsNb {
|
||||
'companionRemote.session.serverStopped' => 'Fjernserver stoppet',
|
||||
'companionRemote.session.serverRunningDescription' => 'Mobile enheter på nettverket ditt kan koble til denne appen',
|
||||
'companionRemote.session.serverStoppedDescription' => 'Start serveren for å la mobilenheter koble til',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.usePhoneToControl' => 'Bruk mobilenheten din til å styre denne appen',
|
||||
'companionRemote.session.startServer' => 'Start server',
|
||||
'companionRemote.session.stopServer' => 'Stopp server',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.minimize' => 'Minimer',
|
||||
'companionRemote.pairing.discoveryDescription' => 'Plezy-enheter med samme Plex-konto vises her',
|
||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||
|
||||
@@ -544,9 +544,6 @@ class _TranslationsRateSheetNl extends TranslationsRateSheetEn {
|
||||
@override String starValue({required Object rating}) => '${rating} / 5';
|
||||
@override String scoreValue({required Object score}) => '${score} / 10';
|
||||
@override String get setScore => 'Score instellen';
|
||||
@override String get notRated => 'Niet beoordeeld';
|
||||
@override String get liked => 'Geliket';
|
||||
@override String get notLiked => 'Niet geliket';
|
||||
@override String get saved => 'Opgeslagen';
|
||||
@override String get notAvailable => 'Geen match gevonden';
|
||||
@override String get noConnectedTrackers => 'Verbind een tracker in Instellingen om daar te beoordelen.';
|
||||
@@ -2330,9 +2327,6 @@ extension on TranslationsNl {
|
||||
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
|
||||
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
|
||||
'rateSheet.setScore' => 'Score instellen',
|
||||
'rateSheet.notRated' => 'Niet beoordeeld',
|
||||
'rateSheet.liked' => 'Geliket',
|
||||
'rateSheet.notLiked' => 'Niet geliket',
|
||||
'rateSheet.saved' => 'Opgeslagen',
|
||||
'rateSheet.notAvailable' => 'Geen match gevonden',
|
||||
'rateSheet.noConnectedTrackers' => 'Verbind een tracker in Instellingen om daar te beoordelen.',
|
||||
@@ -2458,11 +2452,11 @@ extension on TranslationsNl {
|
||||
'messages.libraryScanFailed' => ({required Object error}) => 'Kon bibliotheek niet scannen: ${error}',
|
||||
'messages.metadataRefreshing' => ({required Object title}) => 'Metadata vernieuwen voor "${title}"...',
|
||||
'messages.metadataRefreshStarted' => ({required Object title}) => 'Metadata vernieuwen gestart voor "${title}"',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.metadataRefreshFailed' => ({required Object error}) => 'Kon metadata niet vernieuwen: ${error}',
|
||||
'messages.logoutConfirm' => 'Weet je zeker dat je wilt uitloggen?',
|
||||
'messages.noSeasonsFound' => 'Geen seizoenen gevonden',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.seasonsLoadFailed' => 'Kan seizoenen niet laden',
|
||||
'messages.noEpisodesFound' => 'Geen afleveringen gevonden in eerste seizoen',
|
||||
'messages.noEpisodesFoundGeneral' => 'Geen afleveringen gevonden',
|
||||
@@ -2972,11 +2966,11 @@ extension on TranslationsNl {
|
||||
'companionRemote.session.serverStopped' => 'Externe server gestopt',
|
||||
'companionRemote.session.serverRunningDescription' => 'Mobiele apparaten op je netwerk kunnen met deze app verbinden',
|
||||
'companionRemote.session.serverStoppedDescription' => 'Start de server om mobiele apparaten te laten verbinden',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.usePhoneToControl' => 'Gebruik je mobiele apparaat om deze app te bedienen',
|
||||
'companionRemote.session.startServer' => 'Server starten',
|
||||
'companionRemote.session.stopServer' => 'Server stoppen',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.minimize' => 'Minimaliseren',
|
||||
'companionRemote.pairing.discoveryDescription' => 'Plezy-apparaten met hetzelfde Plex-account verschijnen hier',
|
||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||
|
||||
@@ -544,9 +544,6 @@ class _TranslationsRateSheetPl extends TranslationsRateSheetEn {
|
||||
@override String starValue({required Object rating}) => '${rating} / 5';
|
||||
@override String scoreValue({required Object score}) => '${score} / 10';
|
||||
@override String get setScore => 'Ustaw ocenę';
|
||||
@override String get notRated => 'Nie oceniono';
|
||||
@override String get liked => 'Polubione';
|
||||
@override String get notLiked => 'Nie polubiono';
|
||||
@override String get saved => 'Zapisano';
|
||||
@override String get notAvailable => 'Nie znaleziono dopasowania';
|
||||
@override String get noConnectedTrackers => 'Połącz tracker w Ustawieniach, aby tam oceniać.';
|
||||
@@ -2330,9 +2327,6 @@ extension on TranslationsPl {
|
||||
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
|
||||
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
|
||||
'rateSheet.setScore' => 'Ustaw ocenę',
|
||||
'rateSheet.notRated' => 'Nie oceniono',
|
||||
'rateSheet.liked' => 'Polubione',
|
||||
'rateSheet.notLiked' => 'Nie polubiono',
|
||||
'rateSheet.saved' => 'Zapisano',
|
||||
'rateSheet.notAvailable' => 'Nie znaleziono dopasowania',
|
||||
'rateSheet.noConnectedTrackers' => 'Połącz tracker w Ustawieniach, aby tam oceniać.',
|
||||
@@ -2458,11 +2452,11 @@ extension on TranslationsPl {
|
||||
'messages.libraryScanFailed' => ({required Object error}) => 'Nie udało się zeskanować biblioteki: ${error}',
|
||||
'messages.metadataRefreshing' => ({required Object title}) => 'Odświeżanie metadanych "${title}"...',
|
||||
'messages.metadataRefreshStarted' => ({required Object title}) => 'Rozpoczęto odświeżanie metadanych "${title}"',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.metadataRefreshFailed' => ({required Object error}) => 'Nie udało się odświeżyć metadanych: ${error}',
|
||||
'messages.logoutConfirm' => 'Czy na pewno chcesz się wylogować?',
|
||||
'messages.noSeasonsFound' => 'Nie znaleziono sezonów',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.seasonsLoadFailed' => 'Nie udało się załadować sezonów',
|
||||
'messages.noEpisodesFound' => 'Nie znaleziono odcinków w pierwszym sezonie',
|
||||
'messages.noEpisodesFoundGeneral' => 'Nie znaleziono odcinków',
|
||||
@@ -2972,11 +2966,11 @@ extension on TranslationsPl {
|
||||
'companionRemote.session.serverStopped' => 'Serwer zdalny zatrzymany',
|
||||
'companionRemote.session.serverRunningDescription' => 'Urządzenia mobilne w Twojej sieci mogą łączyć się z tą aplikacją',
|
||||
'companionRemote.session.serverStoppedDescription' => 'Uruchom serwer, aby umożliwić połączenie urządzeń mobilnych',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.usePhoneToControl' => 'Użyj urządzenia mobilnego, aby sterować tą aplikacją',
|
||||
'companionRemote.session.startServer' => 'Uruchom serwer',
|
||||
'companionRemote.session.stopServer' => 'Zatrzymaj serwer',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.minimize' => 'Minimalizuj',
|
||||
'companionRemote.pairing.discoveryDescription' => 'Urządzenia Plezy z tym samym kontem Plex pojawią się tutaj',
|
||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||
|
||||
@@ -544,9 +544,6 @@ class _TranslationsRateSheetPt extends TranslationsRateSheetEn {
|
||||
@override String starValue({required Object rating}) => '${rating} / 5';
|
||||
@override String scoreValue({required Object score}) => '${score} / 10';
|
||||
@override String get setScore => 'Definir pontuação';
|
||||
@override String get notRated => 'Sem avaliação';
|
||||
@override String get liked => 'Gostei';
|
||||
@override String get notLiked => 'Não marcado como gostei';
|
||||
@override String get saved => 'Salvo';
|
||||
@override String get notAvailable => 'Nenhuma correspondência encontrada';
|
||||
@override String get noConnectedTrackers => 'Conecte um rastreador em Configurações para avaliar lá.';
|
||||
@@ -2330,9 +2327,6 @@ extension on TranslationsPt {
|
||||
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
|
||||
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
|
||||
'rateSheet.setScore' => 'Definir pontuação',
|
||||
'rateSheet.notRated' => 'Sem avaliação',
|
||||
'rateSheet.liked' => 'Gostei',
|
||||
'rateSheet.notLiked' => 'Não marcado como gostei',
|
||||
'rateSheet.saved' => 'Salvo',
|
||||
'rateSheet.notAvailable' => 'Nenhuma correspondência encontrada',
|
||||
'rateSheet.noConnectedTrackers' => 'Conecte um rastreador em Configurações para avaliar lá.',
|
||||
@@ -2458,11 +2452,11 @@ extension on TranslationsPt {
|
||||
'messages.libraryScanFailed' => ({required Object error}) => 'Falha ao escanear biblioteca: ${error}',
|
||||
'messages.metadataRefreshing' => ({required Object title}) => 'Atualizando metadados de "${title}"...',
|
||||
'messages.metadataRefreshStarted' => ({required Object title}) => 'Atualização de metadados iniciada para "${title}"',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.metadataRefreshFailed' => ({required Object error}) => 'Falha ao atualizar metadados: ${error}',
|
||||
'messages.logoutConfirm' => 'Tem certeza que deseja sair?',
|
||||
'messages.noSeasonsFound' => 'Nenhuma temporada encontrada',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.seasonsLoadFailed' => 'Não foi possível carregar as temporadas',
|
||||
'messages.noEpisodesFound' => 'Nenhum episódio encontrado na primeira temporada',
|
||||
'messages.noEpisodesFoundGeneral' => 'Nenhum episódio encontrado',
|
||||
@@ -2972,11 +2966,11 @@ extension on TranslationsPt {
|
||||
'companionRemote.session.serverStopped' => 'Servidor remoto parado',
|
||||
'companionRemote.session.serverRunningDescription' => 'Dispositivos móveis na sua rede podem se conectar a este app',
|
||||
'companionRemote.session.serverStoppedDescription' => 'Inicie o servidor para permitir que dispositivos móveis se conectem',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.usePhoneToControl' => 'Use o seu dispositivo móvel para controlar esta aplicação',
|
||||
'companionRemote.session.startServer' => 'Iniciar servidor',
|
||||
'companionRemote.session.stopServer' => 'Parar servidor',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.minimize' => 'Minimizar',
|
||||
'companionRemote.pairing.discoveryDescription' => 'Dispositivos Plezy com a mesma conta Plex aparecem aqui',
|
||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||
|
||||
@@ -544,9 +544,6 @@ class _TranslationsRateSheetRu extends TranslationsRateSheetEn {
|
||||
@override String starValue({required Object rating}) => '${rating} / 5';
|
||||
@override String scoreValue({required Object score}) => '${score} / 10';
|
||||
@override String get setScore => 'Установить оценку';
|
||||
@override String get notRated => 'Без оценки';
|
||||
@override String get liked => 'Понравилось';
|
||||
@override String get notLiked => 'Не отмечено понравившимся';
|
||||
@override String get saved => 'Сохранено';
|
||||
@override String get notAvailable => 'Совпадений не найдено';
|
||||
@override String get noConnectedTrackers => 'Подключите трекер в настройках, чтобы оценивать там.';
|
||||
@@ -2330,9 +2327,6 @@ extension on TranslationsRu {
|
||||
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
|
||||
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
|
||||
'rateSheet.setScore' => 'Установить оценку',
|
||||
'rateSheet.notRated' => 'Без оценки',
|
||||
'rateSheet.liked' => 'Понравилось',
|
||||
'rateSheet.notLiked' => 'Не отмечено понравившимся',
|
||||
'rateSheet.saved' => 'Сохранено',
|
||||
'rateSheet.notAvailable' => 'Совпадений не найдено',
|
||||
'rateSheet.noConnectedTrackers' => 'Подключите трекер в настройках, чтобы оценивать там.',
|
||||
@@ -2458,11 +2452,11 @@ extension on TranslationsRu {
|
||||
'messages.libraryScanFailed' => ({required Object error}) => 'Не удалось отсканировать библиотеку: ${error}',
|
||||
'messages.metadataRefreshing' => ({required Object title}) => 'Обновление метаданных "${title}"...',
|
||||
'messages.metadataRefreshStarted' => ({required Object title}) => 'Обновление метаданных начато для "${title}"',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.metadataRefreshFailed' => ({required Object error}) => 'Не удалось обновить метаданные: ${error}',
|
||||
'messages.logoutConfirm' => 'Вы уверены, что хотите выйти?',
|
||||
'messages.noSeasonsFound' => 'Сезоны не найдены',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.seasonsLoadFailed' => 'Не удалось загрузить сезоны',
|
||||
'messages.noEpisodesFound' => 'Эпизоды в первом сезоне не найдены',
|
||||
'messages.noEpisodesFoundGeneral' => 'Эпизоды не найдены',
|
||||
@@ -2972,11 +2966,11 @@ extension on TranslationsRu {
|
||||
'companionRemote.session.serverStopped' => 'Удалённый сервер остановлен',
|
||||
'companionRemote.session.serverRunningDescription' => 'Мобильные устройства в вашей сети могут подключаться к этому приложению',
|
||||
'companionRemote.session.serverStoppedDescription' => 'Запустите сервер, чтобы разрешить подключение мобильных устройств',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.usePhoneToControl' => 'Используйте мобильное устройство для управления этим приложением',
|
||||
'companionRemote.session.startServer' => 'Запустить сервер',
|
||||
'companionRemote.session.stopServer' => 'Остановить сервер',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.minimize' => 'Свернуть',
|
||||
'companionRemote.pairing.discoveryDescription' => 'Устройства Plezy с тем же аккаунтом Plex появятся здесь',
|
||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||
|
||||
@@ -544,9 +544,6 @@ class _TranslationsRateSheetSv extends TranslationsRateSheetEn {
|
||||
@override String starValue({required Object rating}) => '${rating} / 5';
|
||||
@override String scoreValue({required Object score}) => '${score} / 10';
|
||||
@override String get setScore => 'Ange betyg';
|
||||
@override String get notRated => 'Inte betygsatt';
|
||||
@override String get liked => 'Gillad';
|
||||
@override String get notLiked => 'Inte gillad';
|
||||
@override String get saved => 'Sparat';
|
||||
@override String get notAvailable => 'Ingen matchning hittades';
|
||||
@override String get noConnectedTrackers => 'Anslut en spårare i Inställningar för att betygsätta där.';
|
||||
@@ -2330,9 +2327,6 @@ extension on TranslationsSv {
|
||||
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
|
||||
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
|
||||
'rateSheet.setScore' => 'Ange betyg',
|
||||
'rateSheet.notRated' => 'Inte betygsatt',
|
||||
'rateSheet.liked' => 'Gillad',
|
||||
'rateSheet.notLiked' => 'Inte gillad',
|
||||
'rateSheet.saved' => 'Sparat',
|
||||
'rateSheet.notAvailable' => 'Ingen matchning hittades',
|
||||
'rateSheet.noConnectedTrackers' => 'Anslut en spårare i Inställningar för att betygsätta där.',
|
||||
@@ -2458,11 +2452,11 @@ extension on TranslationsSv {
|
||||
'messages.libraryScanFailed' => ({required Object error}) => 'Misslyckades att skanna bibliotek: ${error}',
|
||||
'messages.metadataRefreshing' => ({required Object title}) => 'Uppdaterar metadata för "${title}"...',
|
||||
'messages.metadataRefreshStarted' => ({required Object title}) => 'Metadata-uppdatering startad för "${title}"',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.metadataRefreshFailed' => ({required Object error}) => 'Misslyckades att uppdatera metadata: ${error}',
|
||||
'messages.logoutConfirm' => 'Är du säker på att du vill logga ut?',
|
||||
'messages.noSeasonsFound' => 'Inga säsonger hittades',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.seasonsLoadFailed' => 'Det gick inte att läsa in säsonger',
|
||||
'messages.noEpisodesFound' => 'Inga avsnitt hittades i första säsongen',
|
||||
'messages.noEpisodesFoundGeneral' => 'Inga avsnitt hittades',
|
||||
@@ -2972,11 +2966,11 @@ extension on TranslationsSv {
|
||||
'companionRemote.session.serverStopped' => 'Fjärrserver stoppad',
|
||||
'companionRemote.session.serverRunningDescription' => 'Mobila enheter i ditt nätverk kan ansluta till den här appen',
|
||||
'companionRemote.session.serverStoppedDescription' => 'Starta servern för att tillåta mobila enheter att ansluta',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.usePhoneToControl' => 'Använd din mobila enhet för att styra denna app',
|
||||
'companionRemote.session.startServer' => 'Starta server',
|
||||
'companionRemote.session.stopServer' => 'Stoppa server',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.minimize' => 'Minimera',
|
||||
'companionRemote.pairing.discoveryDescription' => 'Plezy-enheter med samma Plex-konto visas här',
|
||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||
|
||||
@@ -544,9 +544,6 @@ class _TranslationsRateSheetZh extends TranslationsRateSheetEn {
|
||||
@override String starValue({required Object rating}) => '${rating} / 5';
|
||||
@override String scoreValue({required Object score}) => '${score} / 10';
|
||||
@override String get setScore => '设置分数';
|
||||
@override String get notRated => '未评分';
|
||||
@override String get liked => '已喜欢';
|
||||
@override String get notLiked => '未喜欢';
|
||||
@override String get saved => '已保存';
|
||||
@override String get notAvailable => '未找到匹配项';
|
||||
@override String get noConnectedTrackers => '在设置中连接跟踪器即可在那里评分。';
|
||||
@@ -2330,9 +2327,6 @@ extension on TranslationsZh {
|
||||
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
|
||||
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
|
||||
'rateSheet.setScore' => '设置分数',
|
||||
'rateSheet.notRated' => '未评分',
|
||||
'rateSheet.liked' => '已喜欢',
|
||||
'rateSheet.notLiked' => '未喜欢',
|
||||
'rateSheet.saved' => '已保存',
|
||||
'rateSheet.notAvailable' => '未找到匹配项',
|
||||
'rateSheet.noConnectedTrackers' => '在设置中连接跟踪器即可在那里评分。',
|
||||
@@ -2458,11 +2452,11 @@ extension on TranslationsZh {
|
||||
'messages.libraryScanFailed' => ({required Object error}) => '无法扫描媒体库: ${error}',
|
||||
'messages.metadataRefreshing' => ({required Object title}) => '正在刷新 “${title}” 的元数据...',
|
||||
'messages.metadataRefreshStarted' => ({required Object title}) => '已开始刷新 “${title}” 的元数据',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.metadataRefreshFailed' => ({required Object error}) => '无法刷新元数据: ${error}',
|
||||
'messages.logoutConfirm' => '你确定要登出吗?',
|
||||
'messages.noSeasonsFound' => '未找到季',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'messages.seasonsLoadFailed' => '无法加载季',
|
||||
'messages.noEpisodesFound' => '在第一季中未找到剧集',
|
||||
'messages.noEpisodesFoundGeneral' => '未找到剧集',
|
||||
@@ -2972,11 +2966,11 @@ extension on TranslationsZh {
|
||||
'companionRemote.session.serverStopped' => '远程服务器已停止',
|
||||
'companionRemote.session.serverRunningDescription' => '网络上的移动设备可以连接到此应用',
|
||||
'companionRemote.session.serverStoppedDescription' => '启动服务器以允许移动设备连接',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.usePhoneToControl' => '使用移动设备控制此应用',
|
||||
'companionRemote.session.startServer' => '启动服务器',
|
||||
'companionRemote.session.stopServer' => '停止服务器',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'companionRemote.session.minimize' => '最小化',
|
||||
'companionRemote.pairing.discoveryDescription' => '使用同一 Plex 账号的 Plezy 设备会显示在这里',
|
||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||
|
||||
@@ -406,9 +406,6 @@
|
||||
"starValue": "${rating} / 5",
|
||||
"scoreValue": "${score} / 10",
|
||||
"setScore": "Ange betyg",
|
||||
"notRated": "Inte betygsatt",
|
||||
"liked": "Gillad",
|
||||
"notLiked": "Inte gillad",
|
||||
"saved": "Sparat",
|
||||
"notAvailable": "Ingen matchning hittades",
|
||||
"noConnectedTrackers": "Anslut en spårare i Inställningar för att betygsätta där."
|
||||
|
||||
@@ -406,9 +406,6 @@
|
||||
"starValue": "${rating} / 5",
|
||||
"scoreValue": "${score} / 10",
|
||||
"setScore": "设置分数",
|
||||
"notRated": "未评分",
|
||||
"liked": "已喜欢",
|
||||
"notLiked": "未喜欢",
|
||||
"saved": "已保存",
|
||||
"notAvailable": "未找到匹配项",
|
||||
"noConnectedTrackers": "在设置中连接跟踪器即可在那里评分。"
|
||||
|
||||
@@ -49,6 +49,10 @@ sealed class LibraryQuery with _$LibraryQuery {
|
||||
/// Whether to include items the active user has already watched.
|
||||
@Default(true) bool includeWatched,
|
||||
|
||||
/// Restrict to items the user marked favorite (Jellyfin `Filters=IsFavorite`).
|
||||
/// Plex has no equivalent; its translator ignores the flag.
|
||||
@Default(false) bool favoritesOnly,
|
||||
|
||||
/// Restrict the result to items whose sort name starts with this string —
|
||||
/// the alpha-jump bar's filter UX. The literal `#` is a sentinel for
|
||||
/// "non-alphabetic" and translates to a `NameLessThan=A` query for backends
|
||||
|
||||
@@ -536,7 +536,9 @@ mixin _$LibraryQuery {
|
||||
int get offset; int get limit; LibrarySort? get sort; List<LibraryFilter> get filters;/// Free-text search restricted to this library. Distinct from the global
|
||||
/// search endpoint.
|
||||
String? get search;/// Whether to include items the active user has already watched.
|
||||
bool get includeWatched;/// Restrict the result to items whose sort name starts with this string —
|
||||
bool get includeWatched;/// Restrict to items the user marked favorite (Jellyfin `Filters=IsFavorite`).
|
||||
/// Plex has no equivalent; its translator ignores the flag.
|
||||
bool get favoritesOnly;/// Restrict the result to items whose sort name starts with this string —
|
||||
/// the alpha-jump bar's filter UX. The literal `#` is a sentinel for
|
||||
/// "non-alphabetic" and translates to a `NameLessThan=A` query for backends
|
||||
/// that support it.
|
||||
@@ -554,16 +556,16 @@ $LibraryQueryCopyWith<LibraryQuery> get copyWith => _$LibraryQueryCopyWithImpl<L
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is LibraryQuery&&(identical(other.kind, kind) || other.kind == kind)&&(identical(other.offset, offset) || other.offset == offset)&&(identical(other.limit, limit) || other.limit == limit)&&(identical(other.sort, sort) || other.sort == sort)&&const DeepCollectionEquality().equals(other.filters, filters)&&(identical(other.search, search) || other.search == search)&&(identical(other.includeWatched, includeWatched) || other.includeWatched == includeWatched)&&(identical(other.nameStartsWith, nameStartsWith) || other.nameStartsWith == nameStartsWith)&&const DeepCollectionEquality().equals(other.genres, genres)&&const DeepCollectionEquality().equals(other.officialRatings, officialRatings)&&const DeepCollectionEquality().equals(other.years, years)&&const DeepCollectionEquality().equals(other.tags, tags));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is LibraryQuery&&(identical(other.kind, kind) || other.kind == kind)&&(identical(other.offset, offset) || other.offset == offset)&&(identical(other.limit, limit) || other.limit == limit)&&(identical(other.sort, sort) || other.sort == sort)&&const DeepCollectionEquality().equals(other.filters, filters)&&(identical(other.search, search) || other.search == search)&&(identical(other.includeWatched, includeWatched) || other.includeWatched == includeWatched)&&(identical(other.favoritesOnly, favoritesOnly) || other.favoritesOnly == favoritesOnly)&&(identical(other.nameStartsWith, nameStartsWith) || other.nameStartsWith == nameStartsWith)&&const DeepCollectionEquality().equals(other.genres, genres)&&const DeepCollectionEquality().equals(other.officialRatings, officialRatings)&&const DeepCollectionEquality().equals(other.years, years)&&const DeepCollectionEquality().equals(other.tags, tags));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,kind,offset,limit,sort,const DeepCollectionEquality().hash(filters),search,includeWatched,nameStartsWith,const DeepCollectionEquality().hash(genres),const DeepCollectionEquality().hash(officialRatings),const DeepCollectionEquality().hash(years),const DeepCollectionEquality().hash(tags));
|
||||
int get hashCode => Object.hash(runtimeType,kind,offset,limit,sort,const DeepCollectionEquality().hash(filters),search,includeWatched,favoritesOnly,nameStartsWith,const DeepCollectionEquality().hash(genres),const DeepCollectionEquality().hash(officialRatings),const DeepCollectionEquality().hash(years),const DeepCollectionEquality().hash(tags));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LibraryQuery(kind: $kind, offset: $offset, limit: $limit, sort: $sort, filters: $filters, search: $search, includeWatched: $includeWatched, nameStartsWith: $nameStartsWith, genres: $genres, officialRatings: $officialRatings, years: $years, tags: $tags)';
|
||||
return 'LibraryQuery(kind: $kind, offset: $offset, limit: $limit, sort: $sort, filters: $filters, search: $search, includeWatched: $includeWatched, favoritesOnly: $favoritesOnly, nameStartsWith: $nameStartsWith, genres: $genres, officialRatings: $officialRatings, years: $years, tags: $tags)';
|
||||
}
|
||||
|
||||
|
||||
@@ -574,7 +576,7 @@ abstract mixin class $LibraryQueryCopyWith<$Res> {
|
||||
factory $LibraryQueryCopyWith(LibraryQuery value, $Res Function(LibraryQuery) _then) = _$LibraryQueryCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
MediaKind? kind, int offset, int limit, LibrarySort? sort, List<LibraryFilter> filters, String? search, bool includeWatched, String? nameStartsWith, List<String>? genres, List<String>? officialRatings, List<int>? years, List<String>? tags
|
||||
MediaKind? kind, int offset, int limit, LibrarySort? sort, List<LibraryFilter> filters, String? search, bool includeWatched, bool favoritesOnly, String? nameStartsWith, List<String>? genres, List<String>? officialRatings, List<int>? years, List<String>? tags
|
||||
});
|
||||
|
||||
|
||||
@@ -591,7 +593,7 @@ class _$LibraryQueryCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of LibraryQuery
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? kind = freezed,Object? offset = null,Object? limit = null,Object? sort = freezed,Object? filters = null,Object? search = freezed,Object? includeWatched = null,Object? nameStartsWith = freezed,Object? genres = freezed,Object? officialRatings = freezed,Object? years = freezed,Object? tags = freezed,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? kind = freezed,Object? offset = null,Object? limit = null,Object? sort = freezed,Object? filters = null,Object? search = freezed,Object? includeWatched = null,Object? favoritesOnly = null,Object? nameStartsWith = freezed,Object? genres = freezed,Object? officialRatings = freezed,Object? years = freezed,Object? tags = freezed,}) {
|
||||
return _then(_self.copyWith(
|
||||
kind: freezed == kind ? _self.kind : kind // ignore: cast_nullable_to_non_nullable
|
||||
as MediaKind?,offset: null == offset ? _self.offset : offset // ignore: cast_nullable_to_non_nullable
|
||||
@@ -600,6 +602,7 @@ as int,sort: freezed == sort ? _self.sort : sort // ignore: cast_nullable_to_non
|
||||
as LibrarySort?,filters: null == filters ? _self.filters : filters // ignore: cast_nullable_to_non_nullable
|
||||
as List<LibraryFilter>,search: freezed == search ? _self.search : search // ignore: cast_nullable_to_non_nullable
|
||||
as String?,includeWatched: null == includeWatched ? _self.includeWatched : includeWatched // ignore: cast_nullable_to_non_nullable
|
||||
as bool,favoritesOnly: null == favoritesOnly ? _self.favoritesOnly : favoritesOnly // ignore: cast_nullable_to_non_nullable
|
||||
as bool,nameStartsWith: freezed == nameStartsWith ? _self.nameStartsWith : nameStartsWith // ignore: cast_nullable_to_non_nullable
|
||||
as String?,genres: freezed == genres ? _self.genres : genres // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>?,officialRatings: freezed == officialRatings ? _self.officialRatings : officialRatings // ignore: cast_nullable_to_non_nullable
|
||||
@@ -699,10 +702,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( MediaKind? kind, int offset, int limit, LibrarySort? sort, List<LibraryFilter> filters, String? search, bool includeWatched, String? nameStartsWith, List<String>? genres, List<String>? officialRatings, List<int>? years, List<String>? tags)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( MediaKind? kind, int offset, int limit, LibrarySort? sort, List<LibraryFilter> filters, String? search, bool includeWatched, bool favoritesOnly, String? nameStartsWith, List<String>? genres, List<String>? officialRatings, List<int>? years, List<String>? tags)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _LibraryQuery() when $default != null:
|
||||
return $default(_that.kind,_that.offset,_that.limit,_that.sort,_that.filters,_that.search,_that.includeWatched,_that.nameStartsWith,_that.genres,_that.officialRatings,_that.years,_that.tags);case _:
|
||||
return $default(_that.kind,_that.offset,_that.limit,_that.sort,_that.filters,_that.search,_that.includeWatched,_that.favoritesOnly,_that.nameStartsWith,_that.genres,_that.officialRatings,_that.years,_that.tags);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -720,10 +723,10 @@ return $default(_that.kind,_that.offset,_that.limit,_that.sort,_that.filters,_th
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( MediaKind? kind, int offset, int limit, LibrarySort? sort, List<LibraryFilter> filters, String? search, bool includeWatched, String? nameStartsWith, List<String>? genres, List<String>? officialRatings, List<int>? years, List<String>? tags) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( MediaKind? kind, int offset, int limit, LibrarySort? sort, List<LibraryFilter> filters, String? search, bool includeWatched, bool favoritesOnly, String? nameStartsWith, List<String>? genres, List<String>? officialRatings, List<int>? years, List<String>? tags) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _LibraryQuery():
|
||||
return $default(_that.kind,_that.offset,_that.limit,_that.sort,_that.filters,_that.search,_that.includeWatched,_that.nameStartsWith,_that.genres,_that.officialRatings,_that.years,_that.tags);}
|
||||
return $default(_that.kind,_that.offset,_that.limit,_that.sort,_that.filters,_that.search,_that.includeWatched,_that.favoritesOnly,_that.nameStartsWith,_that.genres,_that.officialRatings,_that.years,_that.tags);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
@@ -737,10 +740,10 @@ return $default(_that.kind,_that.offset,_that.limit,_that.sort,_that.filters,_th
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( MediaKind? kind, int offset, int limit, LibrarySort? sort, List<LibraryFilter> filters, String? search, bool includeWatched, String? nameStartsWith, List<String>? genres, List<String>? officialRatings, List<int>? years, List<String>? tags)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( MediaKind? kind, int offset, int limit, LibrarySort? sort, List<LibraryFilter> filters, String? search, bool includeWatched, bool favoritesOnly, String? nameStartsWith, List<String>? genres, List<String>? officialRatings, List<int>? years, List<String>? tags)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _LibraryQuery() when $default != null:
|
||||
return $default(_that.kind,_that.offset,_that.limit,_that.sort,_that.filters,_that.search,_that.includeWatched,_that.nameStartsWith,_that.genres,_that.officialRatings,_that.years,_that.tags);case _:
|
||||
return $default(_that.kind,_that.offset,_that.limit,_that.sort,_that.filters,_that.search,_that.includeWatched,_that.favoritesOnly,_that.nameStartsWith,_that.genres,_that.officialRatings,_that.years,_that.tags);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -752,7 +755,7 @@ return $default(_that.kind,_that.offset,_that.limit,_that.sort,_that.filters,_th
|
||||
|
||||
|
||||
class _LibraryQuery implements LibraryQuery {
|
||||
const _LibraryQuery({this.kind, this.offset = 0, this.limit = 50, this.sort, final List<LibraryFilter> filters = const <LibraryFilter>[], this.search, this.includeWatched = true, this.nameStartsWith, final List<String>? genres, final List<String>? officialRatings, final List<int>? years, final List<String>? tags}): _filters = filters,_genres = genres,_officialRatings = officialRatings,_years = years,_tags = tags;
|
||||
const _LibraryQuery({this.kind, this.offset = 0, this.limit = 50, this.sort, final List<LibraryFilter> filters = const <LibraryFilter>[], this.search, this.includeWatched = true, this.favoritesOnly = false, this.nameStartsWith, final List<String>? genres, final List<String>? officialRatings, final List<int>? years, final List<String>? tags}): _filters = filters,_genres = genres,_officialRatings = officialRatings,_years = years,_tags = tags;
|
||||
|
||||
|
||||
/// Restrict to a single kind (e.g. `MediaKind.movie`). Null = library default.
|
||||
@@ -773,6 +776,9 @@ class _LibraryQuery implements LibraryQuery {
|
||||
@override final String? search;
|
||||
/// Whether to include items the active user has already watched.
|
||||
@override@JsonKey() final bool includeWatched;
|
||||
/// Restrict to items the user marked favorite (Jellyfin `Filters=IsFavorite`).
|
||||
/// Plex has no equivalent; its translator ignores the flag.
|
||||
@override@JsonKey() final bool favoritesOnly;
|
||||
/// Restrict the result to items whose sort name starts with this string —
|
||||
/// the alpha-jump bar's filter UX. The literal `#` is a sentinel for
|
||||
/// "non-alphabetic" and translates to a `NameLessThan=A` query for backends
|
||||
@@ -831,16 +837,16 @@ _$LibraryQueryCopyWith<_LibraryQuery> get copyWith => __$LibraryQueryCopyWithImp
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LibraryQuery&&(identical(other.kind, kind) || other.kind == kind)&&(identical(other.offset, offset) || other.offset == offset)&&(identical(other.limit, limit) || other.limit == limit)&&(identical(other.sort, sort) || other.sort == sort)&&const DeepCollectionEquality().equals(other._filters, _filters)&&(identical(other.search, search) || other.search == search)&&(identical(other.includeWatched, includeWatched) || other.includeWatched == includeWatched)&&(identical(other.nameStartsWith, nameStartsWith) || other.nameStartsWith == nameStartsWith)&&const DeepCollectionEquality().equals(other._genres, _genres)&&const DeepCollectionEquality().equals(other._officialRatings, _officialRatings)&&const DeepCollectionEquality().equals(other._years, _years)&&const DeepCollectionEquality().equals(other._tags, _tags));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LibraryQuery&&(identical(other.kind, kind) || other.kind == kind)&&(identical(other.offset, offset) || other.offset == offset)&&(identical(other.limit, limit) || other.limit == limit)&&(identical(other.sort, sort) || other.sort == sort)&&const DeepCollectionEquality().equals(other._filters, _filters)&&(identical(other.search, search) || other.search == search)&&(identical(other.includeWatched, includeWatched) || other.includeWatched == includeWatched)&&(identical(other.favoritesOnly, favoritesOnly) || other.favoritesOnly == favoritesOnly)&&(identical(other.nameStartsWith, nameStartsWith) || other.nameStartsWith == nameStartsWith)&&const DeepCollectionEquality().equals(other._genres, _genres)&&const DeepCollectionEquality().equals(other._officialRatings, _officialRatings)&&const DeepCollectionEquality().equals(other._years, _years)&&const DeepCollectionEquality().equals(other._tags, _tags));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,kind,offset,limit,sort,const DeepCollectionEquality().hash(_filters),search,includeWatched,nameStartsWith,const DeepCollectionEquality().hash(_genres),const DeepCollectionEquality().hash(_officialRatings),const DeepCollectionEquality().hash(_years),const DeepCollectionEquality().hash(_tags));
|
||||
int get hashCode => Object.hash(runtimeType,kind,offset,limit,sort,const DeepCollectionEquality().hash(_filters),search,includeWatched,favoritesOnly,nameStartsWith,const DeepCollectionEquality().hash(_genres),const DeepCollectionEquality().hash(_officialRatings),const DeepCollectionEquality().hash(_years),const DeepCollectionEquality().hash(_tags));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LibraryQuery(kind: $kind, offset: $offset, limit: $limit, sort: $sort, filters: $filters, search: $search, includeWatched: $includeWatched, nameStartsWith: $nameStartsWith, genres: $genres, officialRatings: $officialRatings, years: $years, tags: $tags)';
|
||||
return 'LibraryQuery(kind: $kind, offset: $offset, limit: $limit, sort: $sort, filters: $filters, search: $search, includeWatched: $includeWatched, favoritesOnly: $favoritesOnly, nameStartsWith: $nameStartsWith, genres: $genres, officialRatings: $officialRatings, years: $years, tags: $tags)';
|
||||
}
|
||||
|
||||
|
||||
@@ -851,7 +857,7 @@ abstract mixin class _$LibraryQueryCopyWith<$Res> implements $LibraryQueryCopyWi
|
||||
factory _$LibraryQueryCopyWith(_LibraryQuery value, $Res Function(_LibraryQuery) _then) = __$LibraryQueryCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
MediaKind? kind, int offset, int limit, LibrarySort? sort, List<LibraryFilter> filters, String? search, bool includeWatched, String? nameStartsWith, List<String>? genres, List<String>? officialRatings, List<int>? years, List<String>? tags
|
||||
MediaKind? kind, int offset, int limit, LibrarySort? sort, List<LibraryFilter> filters, String? search, bool includeWatched, bool favoritesOnly, String? nameStartsWith, List<String>? genres, List<String>? officialRatings, List<int>? years, List<String>? tags
|
||||
});
|
||||
|
||||
|
||||
@@ -868,7 +874,7 @@ class __$LibraryQueryCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of LibraryQuery
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? kind = freezed,Object? offset = null,Object? limit = null,Object? sort = freezed,Object? filters = null,Object? search = freezed,Object? includeWatched = null,Object? nameStartsWith = freezed,Object? genres = freezed,Object? officialRatings = freezed,Object? years = freezed,Object? tags = freezed,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? kind = freezed,Object? offset = null,Object? limit = null,Object? sort = freezed,Object? filters = null,Object? search = freezed,Object? includeWatched = null,Object? favoritesOnly = null,Object? nameStartsWith = freezed,Object? genres = freezed,Object? officialRatings = freezed,Object? years = freezed,Object? tags = freezed,}) {
|
||||
return _then(_LibraryQuery(
|
||||
kind: freezed == kind ? _self.kind : kind // ignore: cast_nullable_to_non_nullable
|
||||
as MediaKind?,offset: null == offset ? _self.offset : offset // ignore: cast_nullable_to_non_nullable
|
||||
@@ -877,6 +883,7 @@ as int,sort: freezed == sort ? _self.sort : sort // ignore: cast_nullable_to_non
|
||||
as LibrarySort?,filters: null == filters ? _self._filters : filters // ignore: cast_nullable_to_non_nullable
|
||||
as List<LibraryFilter>,search: freezed == search ? _self.search : search // ignore: cast_nullable_to_non_nullable
|
||||
as String?,includeWatched: null == includeWatched ? _self.includeWatched : includeWatched // ignore: cast_nullable_to_non_nullable
|
||||
as bool,favoritesOnly: null == favoritesOnly ? _self.favoritesOnly : favoritesOnly // ignore: cast_nullable_to_non_nullable
|
||||
as bool,nameStartsWith: freezed == nameStartsWith ? _self.nameStartsWith : nameStartsWith // ignore: cast_nullable_to_non_nullable
|
||||
as String?,genres: freezed == genres ? _self._genres : genres // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>?,officialRatings: freezed == officialRatings ? _self._officialRatings : officialRatings // ignore: cast_nullable_to_non_nullable
|
||||
|
||||
@@ -60,6 +60,7 @@ sealed class MediaItem with _$MediaItem {
|
||||
int? updatedAt,
|
||||
double? rating,
|
||||
double? userRating,
|
||||
bool? isFavorite,
|
||||
List<String>? genres,
|
||||
List<String>? directors,
|
||||
List<String>? writers,
|
||||
@@ -119,6 +120,7 @@ sealed class MediaItem with _$MediaItem {
|
||||
updatedAt: updatedAt,
|
||||
rating: rating,
|
||||
userRating: userRating,
|
||||
isFavorite: isFavorite,
|
||||
genres: genres,
|
||||
directors: directors,
|
||||
writers: writers,
|
||||
@@ -177,6 +179,7 @@ sealed class MediaItem with _$MediaItem {
|
||||
updatedAt: updatedAt,
|
||||
rating: rating,
|
||||
userRating: userRating,
|
||||
isFavorite: isFavorite,
|
||||
genres: genres,
|
||||
directors: directors,
|
||||
writers: writers,
|
||||
@@ -243,6 +246,7 @@ sealed class MediaItem with _$MediaItem {
|
||||
@JsonKey(fromJson: flexibleDouble) double? rating,
|
||||
@JsonKey(fromJson: flexibleDouble) double? audienceRating,
|
||||
@JsonKey(fromJson: flexibleDouble) double? userRating,
|
||||
bool? isFavorite,
|
||||
String? ratingImage,
|
||||
String? audienceRatingImage,
|
||||
@JsonKey(fromJson: _mediaItemStringList) List<String>? genres,
|
||||
@@ -316,6 +320,7 @@ sealed class MediaItem with _$MediaItem {
|
||||
@JsonKey(fromJson: flexibleInt) int? updatedAt,
|
||||
@JsonKey(fromJson: flexibleDouble) double? rating,
|
||||
@JsonKey(fromJson: flexibleDouble) double? userRating,
|
||||
bool? isFavorite,
|
||||
@JsonKey(fromJson: _mediaItemStringList) List<String>? genres,
|
||||
@JsonKey(fromJson: _mediaItemStringList) List<String>? directors,
|
||||
@JsonKey(fromJson: _mediaItemStringList) List<String>? writers,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -46,6 +46,7 @@ PlexMediaItem _$PlexMediaItemFromJson(Map<String, dynamic> json) =>
|
||||
rating: flexibleDouble(json['rating']),
|
||||
audienceRating: flexibleDouble(json['audienceRating']),
|
||||
userRating: flexibleDouble(json['userRating']),
|
||||
isFavorite: json['isFavorite'] as bool?,
|
||||
ratingImage: json['ratingImage'] as String?,
|
||||
audienceRatingImage: json['audienceRatingImage'] as String?,
|
||||
genres: _mediaItemStringList(json['genres']),
|
||||
@@ -115,6 +116,7 @@ Map<String, dynamic> _$PlexMediaItemToJson(PlexMediaItem instance) =>
|
||||
'rating': ?instance.rating,
|
||||
'audienceRating': ?instance.audienceRating,
|
||||
'userRating': ?instance.userRating,
|
||||
'isFavorite': ?instance.isFavorite,
|
||||
'ratingImage': ?instance.ratingImage,
|
||||
'audienceRatingImage': ?instance.audienceRatingImage,
|
||||
'genres': ?instance.genres,
|
||||
@@ -182,6 +184,7 @@ JellyfinMediaItem _$JellyfinMediaItemFromJson(Map<String, dynamic> json) =>
|
||||
updatedAt: flexibleInt(json['updatedAt']),
|
||||
rating: flexibleDouble(json['rating']),
|
||||
userRating: flexibleDouble(json['userRating']),
|
||||
isFavorite: json['isFavorite'] as bool?,
|
||||
genres: _mediaItemStringList(json['genres']),
|
||||
directors: _mediaItemStringList(json['directors']),
|
||||
writers: _mediaItemStringList(json['writers']),
|
||||
@@ -241,6 +244,7 @@ Map<String, dynamic> _$JellyfinMediaItemToJson(JellyfinMediaItem instance) =>
|
||||
'updatedAt': ?instance.updatedAt,
|
||||
'rating': ?instance.rating,
|
||||
'userRating': ?instance.userRating,
|
||||
'isFavorite': ?instance.isFavorite,
|
||||
'genres': ?instance.genres,
|
||||
'directors': ?instance.directors,
|
||||
'writers': ?instance.writers,
|
||||
|
||||
@@ -310,6 +310,11 @@ abstract class MediaServerClient {
|
||||
/// awaited call in `try/catch` and surface a snackbar on the catch arm.
|
||||
Future<void> rate(MediaItem item, double rating);
|
||||
|
||||
/// Set or clear the per-user favorite flag ("heart") for [item]. Only call
|
||||
/// when [ServerCapabilities.userFavorites] is true; unsupported backends
|
||||
/// throw [UnsupportedError]. Throws [MediaServerHttpException] on failure.
|
||||
Future<void> setFavorite(MediaItem item, bool isFavorite);
|
||||
|
||||
Future<List<MediaPlaylist>> fetchPlaylists({String playlistType = 'video', bool? smart});
|
||||
|
||||
/// Page through server playlists. Used by the library Playlists tab so large
|
||||
|
||||
@@ -56,10 +56,14 @@ class ServerCapabilities {
|
||||
/// returns synthesized hubs but with sparser categorisation.
|
||||
final bool richHubs;
|
||||
|
||||
/// Numeric ratings (Plex 0–10 via [Item.userRating]). Jellyfin offers
|
||||
/// only a binary like/dislike, so star sliders should be hidden.
|
||||
/// Numeric ratings (Plex 0–10 via [Item.userRating]). Jellyfin has no
|
||||
/// numeric user rating, so star sliders should be hidden.
|
||||
final bool numericUserRating;
|
||||
|
||||
/// Per-user favorite flag ("heart") on media items. Jellyfin exposes it via
|
||||
/// `/Users/{userId}/FavoriteItems/{itemId}`; Plex has no equivalent.
|
||||
final bool userFavorites;
|
||||
|
||||
/// Hide an item from Continue Watching without changing watch state or
|
||||
/// playback progress. Plex exposes this directly; Jellyfin does not.
|
||||
final bool continueWatchingRemoval;
|
||||
@@ -115,6 +119,7 @@ class ServerCapabilities {
|
||||
this.serverSideSync = false,
|
||||
this.richHubs = false,
|
||||
this.numericUserRating = false,
|
||||
this.userFavorites = false,
|
||||
this.continueWatchingRemoval = false,
|
||||
this.externalSubtitleSearch = false,
|
||||
this.trackPreferencePersistence = false,
|
||||
@@ -138,6 +143,7 @@ class ServerCapabilities {
|
||||
serverSideSync: true,
|
||||
richHubs: true,
|
||||
numericUserRating: true,
|
||||
userFavorites: false,
|
||||
continueWatchingRemoval: true,
|
||||
externalSubtitleSearch: true,
|
||||
trackPreferencePersistence: true,
|
||||
@@ -169,6 +175,7 @@ class ServerCapabilities {
|
||||
serverSideSync: false,
|
||||
richHubs: false,
|
||||
numericUserRating: false,
|
||||
userFavorites: true,
|
||||
externalSubtitleSearch: false,
|
||||
trackPreferencePersistence: true,
|
||||
endpointFailover: true,
|
||||
@@ -190,6 +197,7 @@ class ServerCapabilities {
|
||||
bool? serverSideSync,
|
||||
bool? richHubs,
|
||||
bool? numericUserRating,
|
||||
bool? userFavorites,
|
||||
bool? continueWatchingRemoval,
|
||||
bool? externalSubtitleSearch,
|
||||
bool? trackPreferencePersistence,
|
||||
@@ -211,6 +219,7 @@ class ServerCapabilities {
|
||||
serverSideSync: serverSideSync ?? this.serverSideSync,
|
||||
richHubs: richHubs ?? this.richHubs,
|
||||
numericUserRating: numericUserRating ?? this.numericUserRating,
|
||||
userFavorites: userFavorites ?? this.userFavorites,
|
||||
continueWatchingRemoval: continueWatchingRemoval ?? this.continueWatchingRemoval,
|
||||
externalSubtitleSearch: externalSubtitleSearch ?? this.externalSubtitleSearch,
|
||||
trackPreferencePersistence: trackPreferencePersistence ?? this.trackPreferencePersistence,
|
||||
|
||||
@@ -953,11 +953,12 @@ class _MediaDetailScreenState extends State<MediaDetailScreen>
|
||||
final isNumeric = mediaClient?.capabilities.numericUserRating ?? true;
|
||||
final hasRating = metadata.userRating != null && metadata.userRating! > 0;
|
||||
final starValue = hasRating ? metadata.userRating! / 2.0 : 0.0;
|
||||
final active = isNumeric ? hasRating : metadata.isFavorite == true;
|
||||
|
||||
final iconData = isNumeric ? Symbols.star_rounded : Symbols.thumb_up_rounded;
|
||||
final activeIconColor = isNumeric ? Colors.amber : Colors.teal;
|
||||
// Numeric backends show the formatted rating when set; binary backends
|
||||
// rely on the filled icon to communicate the like state and keep the
|
||||
final iconData = isNumeric ? Symbols.star_rounded : Symbols.favorite_rounded;
|
||||
final activeIconColor = isNumeric ? Colors.amber : Colors.redAccent;
|
||||
// Numeric backends show the formatted rating when set; favorite backends
|
||||
// rely on the filled heart to communicate the favorite state and keep the
|
||||
// "Rate" label as the action prompt either way.
|
||||
final label = isNumeric && hasRating ? formatRating(starValue) : t.mediaMenu.rate;
|
||||
|
||||
@@ -1004,8 +1005,8 @@ class _MediaDetailScreenState extends State<MediaDetailScreen>
|
||||
children: [
|
||||
AppIcon(
|
||||
iconData,
|
||||
fill: hasRating ? 1 : 0,
|
||||
color: showFocus ? fgColor : (hasRating ? activeIconColor : fgColor),
|
||||
fill: active ? 1 : 0,
|
||||
color: showFocus ? fgColor : (active ? activeIconColor : fgColor),
|
||||
size: 16,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
@@ -1033,6 +1034,11 @@ class _MediaDetailScreenState extends State<MediaDetailScreen>
|
||||
_fullMetadata = (_fullMetadata ?? widget.metadata).copyWith(userRating: rating);
|
||||
});
|
||||
},
|
||||
onServerFavoriteChanged: (favorite) {
|
||||
setStateIfMounted(() {
|
||||
_fullMetadata = (_fullMetadata ?? widget.metadata).copyWith(isFavorite: favorite);
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -208,6 +208,13 @@ mixin _JellyfinBrowseMethods on MediaServerCacheMixin {
|
||||
title: t.libraries.filterCategories.unwatched,
|
||||
type: 'filter',
|
||||
),
|
||||
MediaFilter(
|
||||
filter: 'favorite',
|
||||
filterType: 'boolean',
|
||||
key: 'jellyfin:favorite',
|
||||
title: t.libraries.filterCategories.favorites,
|
||||
type: 'filter',
|
||||
),
|
||||
];
|
||||
final data = await _safeFetchFilterPayload(libraryId);
|
||||
if (data == null) return LibraryFilterResult(filters: filters, cachedValues: const {});
|
||||
|
||||
@@ -143,14 +143,6 @@ mixin _JellyfinLiveTvMethods on MediaServerCacheMixin {
|
||||
|
||||
@override
|
||||
LiveTvSupport get liveTv => _JellyfinLiveTvSupport(this as JellyfinClient);
|
||||
|
||||
/// Toggle the per-user `IsFavorite` flag for [itemId]. Used by the live-TV
|
||||
/// favorite-channel adapter; works on any Jellyfin item.
|
||||
Future<void> _setItemFavorite(String itemId, bool isFavorite) async {
|
||||
final path = '/Users/${_segment(connection.userId)}/FavoriteItems/${_segment(itemId)}';
|
||||
final response = isFavorite ? await _http.post(path) : await _http.delete(path);
|
||||
throwIfHttpError(response);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adapter from [LiveTvSupport] to Jellyfin channel/program helpers.
|
||||
|
||||
@@ -32,6 +32,8 @@ mixin _JellyfinWatchStateMethods on MediaServerCacheMixin {
|
||||
// Lossy mapping — Jellyfin only stores a binary like/dislike. Treat
|
||||
// a negative input as "clear the rating" (DELETE), >= 6/10 as a like
|
||||
// (POST Likes=true), and the rest as a dislike (POST Likes=false).
|
||||
// No longer reachable from the rate sheet, which uses [setFavorite]
|
||||
// for Jellyfin instead; kept as transport for the abstract member.
|
||||
final response = rating < 0
|
||||
? await _http.delete('/UserItems/${_segment(item.id)}/Rating', queryParameters: {'userId': connection.userId})
|
||||
: await _http.post(
|
||||
@@ -40,4 +42,15 @@ mixin _JellyfinWatchStateMethods on MediaServerCacheMixin {
|
||||
);
|
||||
throwIfHttpError(response);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setFavorite(MediaItem item, bool isFavorite) => _setItemFavorite(item.id, isFavorite);
|
||||
|
||||
/// Toggle the per-user `IsFavorite` flag for [itemId]. Backs [setFavorite]
|
||||
/// and the live-TV favorite-channel adapter; works on any Jellyfin item.
|
||||
Future<void> _setItemFavorite(String itemId, bool isFavorite) async {
|
||||
final path = '/Users/${_segment(connection.userId)}/FavoriteItems/${_segment(itemId)}';
|
||||
final response = isFavorite ? await _http.post(path) : await _http.delete(path);
|
||||
throwIfHttpError(response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,14 +198,7 @@ class JellyfinMappers {
|
||||
addedAt: jellyfinIsoToEpochSeconds(item['DateCreated'] as String?),
|
||||
updatedAt: jellyfinIsoToEpochSeconds(item['DateLastSaved'] as String? ?? item['DateModified'] as String?),
|
||||
rating: (item['CommunityRating'] as num?)?.toDouble(),
|
||||
// Jellyfin stores a binary `Likes` flag rather than a numeric rating.
|
||||
// Map true → 10 / false → 0 so the existing UI's `userRating > 0`
|
||||
// check renders the chip as filled for liked items.
|
||||
userRating: switch (_userData(item)?['Likes']) {
|
||||
true => 10.0,
|
||||
false => 0.0,
|
||||
_ => null,
|
||||
},
|
||||
isFavorite: _userData(item)?['IsFavorite'] as bool?,
|
||||
genres: _stringList(item['Genres']),
|
||||
directors: _peopleByType(item['People'], 'Director'),
|
||||
writers: _peopleByType(item['People'], 'Writer'),
|
||||
|
||||
@@ -131,6 +131,7 @@ LibraryQuery libraryQueryFromPlexMap({
|
||||
'contentRating',
|
||||
'tag',
|
||||
'unwatched',
|
||||
'favorite',
|
||||
'sort',
|
||||
'type',
|
||||
'alphaPrefix',
|
||||
@@ -168,6 +169,7 @@ LibraryQuery libraryQueryFromPlexMap({
|
||||
offset: offset,
|
||||
limit: limit,
|
||||
includeWatched: nonEmpty(map['unwatched']) != '1',
|
||||
favoritesOnly: nonEmpty(map['favorite']) == '1',
|
||||
nameStartsWith: nonEmpty(map['alphaPrefix']),
|
||||
search: nonEmpty(map['title']),
|
||||
genres: singleton(nonEmpty(map['genre'])),
|
||||
@@ -221,8 +223,9 @@ class JellyfinLibraryQueryTranslator implements LibraryQueryTranslator {
|
||||
'Fields': fields,
|
||||
...jellyfinImageQueryParameters,
|
||||
};
|
||||
if (!query.includeWatched) {
|
||||
params['Filters'] = 'IsUnplayed';
|
||||
final wireFilters = <String>[if (!query.includeWatched) 'IsUnplayed', if (query.favoritesOnly) 'IsFavorite'];
|
||||
if (wireFilters.isNotEmpty) {
|
||||
params['Filters'] = wireFilters.join(',');
|
||||
}
|
||||
if (query.genres != null && query.genres!.isNotEmpty) {
|
||||
// Jellyfin uses `|` as the multi-value separator for Genres.
|
||||
|
||||
@@ -3765,6 +3765,11 @@ class PlexClient
|
||||
throwIfHttpError(response);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setFavorite(MediaItem item, bool isFavorite) async {
|
||||
throw UnsupportedError('Plex does not support user favorites.');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<MediaPlaylist>> fetchPlaylists({String playlistType = 'video', bool? smart}) async {
|
||||
final playlists = await _getPlaylists(playlistType: playlistType, smart: smart);
|
||||
|
||||
@@ -1137,8 +1137,12 @@ class MediaContextMenuState extends State<MediaContextMenu> {
|
||||
await OverlaySheetController.showAdaptive(
|
||||
context,
|
||||
showDragHandle: true,
|
||||
builder: (context) =>
|
||||
RatingBottomSheet(item: item, serverClient: client, onServerRatingChanged: (_) => _notifyRefresh(item.id)),
|
||||
builder: (context) => RatingBottomSheet(
|
||||
item: item,
|
||||
serverClient: client,
|
||||
onServerRatingChanged: (_) => _notifyRefresh(item.id),
|
||||
onServerFavoriteChanged: (_) => _notifyRefresh(item.id),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -33,8 +32,15 @@ class RatingBottomSheet extends StatefulWidget {
|
||||
final MediaItem item;
|
||||
final MediaServerClient? serverClient;
|
||||
final ValueChanged<double>? onServerRatingChanged;
|
||||
final ValueChanged<bool>? onServerFavoriteChanged;
|
||||
|
||||
const RatingBottomSheet({super.key, required this.item, required this.serverClient, this.onServerRatingChanged});
|
||||
const RatingBottomSheet({
|
||||
super.key,
|
||||
required this.item,
|
||||
required this.serverClient,
|
||||
this.onServerRatingChanged,
|
||||
this.onServerFavoriteChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
State<RatingBottomSheet> createState() => _RatingBottomSheetState();
|
||||
@@ -45,7 +51,7 @@ class _RatingBottomSheetState extends State<RatingBottomSheet> {
|
||||
|
||||
late double _serverStars;
|
||||
late double _serverRating;
|
||||
late bool? _serverLike;
|
||||
late bool _serverFavorite;
|
||||
late final FocusNode _serverFocusNode;
|
||||
|
||||
final Map<TrackerService, int> _trackerScores = {};
|
||||
@@ -67,7 +73,7 @@ class _RatingBottomSheetState extends State<RatingBottomSheet> {
|
||||
final rawServerRating = widget.item.userRating;
|
||||
_serverRating = rawServerRating != null && rawServerRating > 0 ? rawServerRating : 0.0;
|
||||
_serverStars = _serverRating > 0 ? _serverRating / 2.0 : 0.0;
|
||||
_serverLike = rawServerRating == null ? null : rawServerRating >= 6.0;
|
||||
_serverFavorite = widget.item.isFavorite == true;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -94,8 +100,10 @@ class _RatingBottomSheetState extends State<RatingBottomSheet> {
|
||||
_resolverNeedsFribb = trackers.isMalConnected || trackers.isAnilistConnected;
|
||||
_queueTrackerScoreLoad(allTrackerSources);
|
||||
|
||||
final serverCaps = widget.serverClient?.capabilities;
|
||||
final showServerRow = serverCaps != null && (serverCaps.numericUserRating || serverCaps.userFavorites);
|
||||
final focusNodes = <FocusNode>[
|
||||
if (widget.serverClient != null) _serverFocusNode,
|
||||
if (showServerRow) _serverFocusNode,
|
||||
for (final source in trackerSources) _trackerFocusNode(source.service),
|
||||
];
|
||||
var focusIndex = 0;
|
||||
@@ -110,7 +118,7 @@ class _RatingBottomSheetState extends State<RatingBottomSheet> {
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.fromLTRB(10, 4, 10, 12),
|
||||
children: [
|
||||
if (widget.serverClient != null)
|
||||
if (showServerRow)
|
||||
_buildServerRow(
|
||||
widget.serverClient!,
|
||||
_serverFocusNode,
|
||||
@@ -183,11 +191,6 @@ class _RatingBottomSheetState extends State<RatingBottomSheet> {
|
||||
);
|
||||
}
|
||||
|
||||
final value = switch (_serverLike) {
|
||||
null => 0,
|
||||
false => 1,
|
||||
true => 2,
|
||||
};
|
||||
return _RatingRow(
|
||||
focusNode: focusNode,
|
||||
autofocus: autofocus,
|
||||
@@ -199,14 +202,13 @@ class _RatingBottomSheetState extends State<RatingBottomSheet> {
|
||||
enabled: !loading,
|
||||
onNavigateUp: onNavigateUp,
|
||||
onNavigateDown: onNavigateDown,
|
||||
onDecrease: () => _setServerLikeValue(value - 1),
|
||||
onIncrease: () => _setServerLikeValue(value + 1),
|
||||
onSubmit: () => unawaited(_submitServerLike()),
|
||||
control: _BinaryControl(
|
||||
value: value,
|
||||
onDecrease: () => _setServerFavorite(false),
|
||||
onIncrease: () => _setServerFavorite(true),
|
||||
onSubmit: () => unawaited(_submitServerFavorite(value: !_serverFavorite)),
|
||||
control: _FavoriteControl(
|
||||
value: _serverFavorite,
|
||||
enabled: !loading,
|
||||
onChanged: _setServerLikeValue,
|
||||
onSubmitValue: (next) => unawaited(_submitServerLike(value: _likeFromIndex(next))),
|
||||
onToggle: (next) => unawaited(_submitServerFavorite(value: next)),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -407,23 +409,15 @@ class _RatingBottomSheetState extends State<RatingBottomSheet> {
|
||||
_scheduleAutoSave(_serverKey, _submitServerStars);
|
||||
}
|
||||
|
||||
void _setServerLikeValue(int value) {
|
||||
final clamped = value.clamp(0, 2).toInt();
|
||||
final next = _likeFromIndex(clamped);
|
||||
if (_serverLike == next) return;
|
||||
void _setServerFavorite(bool value) {
|
||||
if (_serverFavorite == value) return;
|
||||
setState(() {
|
||||
_serverLike = next;
|
||||
_serverFavorite = value;
|
||||
_statuses.remove(_serverKey);
|
||||
});
|
||||
_scheduleAutoSave(_serverKey, _submitServerLike);
|
||||
_scheduleAutoSave(_serverKey, _submitServerFavorite);
|
||||
}
|
||||
|
||||
bool? _likeFromIndex(int value) => switch (value.clamp(0, 2).toInt()) {
|
||||
0 => null,
|
||||
1 => false,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
void _setTrackerScore(_TrackerRatingSource source, int score) {
|
||||
final clamped = score.clamp(0, 10).toInt();
|
||||
if ((_trackerScores[source.service] ?? 0) == clamped) return;
|
||||
@@ -457,25 +451,17 @@ class _RatingBottomSheetState extends State<RatingBottomSheet> {
|
||||
await widget.serverClient!.rate(widget.item, -1);
|
||||
_serverRating = 0;
|
||||
_serverStars = 0;
|
||||
_serverLike = null;
|
||||
widget.onServerRatingChanged?.call(0);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _submitServerLike({bool? value}) async {
|
||||
Future<void> _submitServerFavorite({bool? value}) async {
|
||||
_cancelAutoSave(_serverKey);
|
||||
final selected = value ?? _serverLike;
|
||||
if (selected == null) {
|
||||
await _clearServerRating();
|
||||
return;
|
||||
}
|
||||
|
||||
final rating = selected ? 10.0 : 0.0;
|
||||
final selected = value ?? _serverFavorite;
|
||||
await _run(_serverKey, () async {
|
||||
await widget.serverClient!.rate(widget.item, rating);
|
||||
_serverLike = selected;
|
||||
_serverRating = rating;
|
||||
widget.onServerRatingChanged?.call(_serverRating);
|
||||
await widget.serverClient!.setFavorite(widget.item, selected);
|
||||
_serverFavorite = selected;
|
||||
widget.onServerFavoriteChanged?.call(selected);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -589,10 +575,8 @@ class _RatingBottomSheetState extends State<RatingBottomSheet> {
|
||||
return;
|
||||
}
|
||||
|
||||
final selected = _serverLike;
|
||||
final rating = selected == null ? -1.0 : (selected ? 10.0 : 0.0);
|
||||
await client.rate(widget.item, rating);
|
||||
widget.onServerRatingChanged?.call(rating < 0 ? 0 : rating);
|
||||
await client.setFavorite(widget.item, _serverFavorite);
|
||||
widget.onServerFavoriteChanged?.call(_serverFavorite);
|
||||
} catch (e) {
|
||||
appLogger.w('Failed to update rating after sheet close', error: e);
|
||||
}
|
||||
@@ -858,104 +842,47 @@ class _StarRatingControlState extends State<_StarRatingControl> {
|
||||
}
|
||||
}
|
||||
|
||||
class _BinaryControl extends StatelessWidget {
|
||||
final int value;
|
||||
class _FavoriteControl extends StatelessWidget {
|
||||
final bool value;
|
||||
final bool enabled;
|
||||
final ValueChanged<int> onChanged;
|
||||
final ValueChanged<int> onSubmitValue;
|
||||
final ValueChanged<bool> onToggle;
|
||||
|
||||
const _BinaryControl({
|
||||
required this.value,
|
||||
required this.enabled,
|
||||
required this.onChanged,
|
||||
required this.onSubmitValue,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final label = switch (value) {
|
||||
0 => t.rateSheet.notRated,
|
||||
1 => t.rateSheet.notLiked,
|
||||
_ => t.rateSheet.liked,
|
||||
};
|
||||
return _StepperPill(
|
||||
label: label,
|
||||
enabled: enabled,
|
||||
onDecrease: () => onChanged((value - 1).clamp(0, 2).toInt()),
|
||||
onIncrease: () => onChanged((value + 1).clamp(0, 2).toInt()),
|
||||
onSubmit: () => onSubmitValue(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _StepperPill extends StatelessWidget {
|
||||
final String label;
|
||||
final bool enabled;
|
||||
final VoidCallback onDecrease;
|
||||
final VoidCallback onIncrease;
|
||||
final VoidCallback onSubmit;
|
||||
|
||||
const _StepperPill({
|
||||
required this.label,
|
||||
required this.enabled,
|
||||
required this.onDecrease,
|
||||
required this.onIncrease,
|
||||
required this.onSubmit,
|
||||
});
|
||||
const _FavoriteControl({required this.value, required this.enabled, required this.onToggle});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final scheme = theme.colorScheme;
|
||||
return Container(
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: scheme.surfaceContainerHighest.withValues(alpha: 0.54),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(100)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
_arrow(context, Symbols.chevron_left_rounded, onDecrease),
|
||||
Expanded(
|
||||
child: ClickableCursor(
|
||||
enabled: enabled,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: enabled ? onSubmit : null,
|
||||
child: Center(
|
||||
child: Text(
|
||||
label,
|
||||
maxLines: 1,
|
||||
overflow: .ellipsis,
|
||||
style: theme.textTheme.labelMedium?.copyWith(fontWeight: .w700),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
_arrow(context, Symbols.chevron_right_rounded, onIncrease),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _arrow(BuildContext context, IconData icon, VoidCallback action) {
|
||||
final theme = Theme.of(context);
|
||||
return ClickableCursor(
|
||||
enabled: enabled,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: enabled ? action : null,
|
||||
child: SizedBox(
|
||||
width: 30,
|
||||
onTap: enabled ? () => onToggle(!value) : null,
|
||||
child: Container(
|
||||
height: 32,
|
||||
child: Center(
|
||||
child: AppIcon(
|
||||
icon,
|
||||
fill: 1,
|
||||
color: enabled ? theme.colorScheme.onSurfaceVariant : theme.disabledColor,
|
||||
size: 20,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: scheme.surfaceContainerHighest.withValues(alpha: 0.54),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(100)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: .center,
|
||||
children: [
|
||||
AppIcon(
|
||||
Symbols.favorite_rounded,
|
||||
fill: value ? 1 : 0,
|
||||
color: value ? Colors.redAccent : (enabled ? scheme.onSurfaceVariant : theme.disabledColor),
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Flexible(
|
||||
child: Text(
|
||||
value ? t.rateSheet.favorited : t.rateSheet.favorite,
|
||||
maxLines: 1,
|
||||
overflow: .ellipsis,
|
||||
style: theme.textTheme.labelMedium?.copyWith(fontWeight: .w700),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1598,11 +1598,22 @@ void main() {
|
||||
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.map((filter) => filter.filter), [
|
||||
'unwatched',
|
||||
'favorite',
|
||||
'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.filters[1].filterType, 'boolean');
|
||||
expect(result.filters[1].key, 'jellyfin:favorite');
|
||||
expect(result.filters[1].title, 'Favorites');
|
||||
expect(result.cachedValues.containsKey('unwatched'), isFalse);
|
||||
expect(result.cachedValues.containsKey('favorite'), isFalse);
|
||||
expect(result.cachedValues['genre']!.map((value) => value.key), ['Action', 'Drama']);
|
||||
expect(result.cachedValues['year']!.map((value) => value.key), ['2024', '1999']);
|
||||
});
|
||||
|
||||
@@ -100,6 +100,32 @@ void main() {
|
||||
expect(item.isWatched, isFalse);
|
||||
});
|
||||
|
||||
test('maps UserData.IsFavorite and ignores the legacy Likes flag', () {
|
||||
final json = {
|
||||
'Id': 'fav-1',
|
||||
'Name': 'Favorited Movie',
|
||||
'Type': 'Movie',
|
||||
'UserData': {'IsFavorite': true, 'Likes': true},
|
||||
};
|
||||
|
||||
final item = JellyfinMappers.mediaItem(json, serverId: ServerId(_serverId), absolutizer: null)!;
|
||||
|
||||
expect(item.isFavorite, isTrue);
|
||||
// Likes used to map to userRating 10.0/0.0; the rate sheet now uses
|
||||
// IsFavorite for Jellyfin, so Likes must no longer surface anywhere.
|
||||
expect(item.userRating, isNull);
|
||||
});
|
||||
|
||||
test('missing UserData leaves isFavorite null', () {
|
||||
final item = JellyfinMappers.mediaItem(
|
||||
{'Id': 'no-userdata', 'Name': 'No UserData', 'Type': 'Movie'},
|
||||
serverId: ServerId(_serverId),
|
||||
absolutizer: null,
|
||||
)!;
|
||||
|
||||
expect(item.isFavorite, isNull);
|
||||
});
|
||||
|
||||
test('maps generic Jellyfin video types to playable clips', () {
|
||||
final video = JellyfinMappers.mediaItem(
|
||||
{'Id': 'home-video', 'Name': 'Home Video', 'Type': 'Video'},
|
||||
|
||||
@@ -57,6 +57,10 @@ void main() {
|
||||
expect(params['unwatched'], '1');
|
||||
});
|
||||
|
||||
test('favoritesOnly is ignored (Plex has no favorites)', () {
|
||||
expect(translator.toQueryParameters(const LibraryQuery(favoritesOnly: true)), isEmpty);
|
||||
});
|
||||
|
||||
test('arbitrary filter clauses pass through verbatim', () {
|
||||
final params = translator.toQueryParameters(
|
||||
const LibraryQuery(
|
||||
@@ -210,6 +214,16 @@ void main() {
|
||||
expect(params['Filters'], 'IsUnplayed');
|
||||
});
|
||||
|
||||
test('favoritesOnly sets Filters=IsFavorite', () {
|
||||
final params = translator.toQueryParameters(const LibraryQuery(favoritesOnly: true));
|
||||
expect(params['Filters'], 'IsFavorite');
|
||||
});
|
||||
|
||||
test('unwatched + favorites combine into a comma-separated Filters list', () {
|
||||
final params = translator.toQueryParameters(const LibraryQuery(includeWatched: false, favoritesOnly: true));
|
||||
expect(params['Filters'], 'IsUnplayed,IsFavorite');
|
||||
});
|
||||
|
||||
test('search puts text in SearchTerm', () {
|
||||
final params = translator.toQueryParameters(const LibraryQuery(search: 'matrix'));
|
||||
expect(params['SearchTerm'], 'matrix');
|
||||
@@ -298,6 +312,14 @@ void main() {
|
||||
expect(roundTrip(input)['director'], '12345');
|
||||
});
|
||||
|
||||
test('favorite=1 maps to favoritesOnly, not a generic filter entry', () {
|
||||
// Jellyfin-only key; the Plex translator deliberately drops it, so it
|
||||
// must not leak into the verbatim-pass-through filters bucket either.
|
||||
final query = libraryQueryFromPlexMap(map: {'favorite': '1'});
|
||||
expect(query.favoritesOnly, isTrue);
|
||||
expect(query.filters, isEmpty);
|
||||
});
|
||||
|
||||
test('libraryKind argument overrides any type entry in the map', () {
|
||||
// The browse tab always passes the library's actual kind; map's `type`
|
||||
// is dropped if the explicit arg is present.
|
||||
|
||||
Reference in New Issue
Block a user