feat(jellyfin): favorites library filter + heart favorite in the rate sheet

close #1485
This commit is contained in:
edde746
2026-07-04 18:33:58 +02:00
parent 30de8be2cd
commit 7632d4bea7
52 changed files with 328 additions and 415 deletions
-3
View File
@@ -406,9 +406,6 @@
"starValue": "${rating} / 5",
"scoreValue": "${score} / 10",
"setScore": "Задай оценка",
"notRated": "Без оценка",
"liked": "Харесано",
"notLiked": "Не е харесано",
"saved": "Запазено",
"notAvailable": "Няма намерено съвпадение",
"noConnectedTrackers": "Свържете тракер в Настройки, за да оценявате там."
-3
View File
@@ -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."
-3
View File
@@ -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."
+4 -4
View File
@@ -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",
-3
View File
@@ -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í."
-3
View File
@@ -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."
-3
View File
@@ -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ì."
-3
View File
@@ -406,9 +406,6 @@
"starValue": "${rating} / 5",
"scoreValue": "${score} / 10",
"setScore": "スコアを設定",
"notRated": "未評価",
"liked": "いいね済み",
"notLiked": "いいねなし",
"saved": "保存済み",
"notAvailable": "一致なし",
"noConnectedTrackers": "設定でトラッカーを接続すると、そこで評価できます。"
-3
View File
@@ -406,9 +406,6 @@
"starValue": "${rating} / 5",
"scoreValue": "${score} / 10",
"setScore": "점수 설정",
"notRated": "평가 없음",
"liked": "좋아요",
"notLiked": "좋아요 아님",
"saved": "저장됨",
"notAvailable": "일치 항목 없음",
"noConnectedTrackers": "설정에서 트래커를 연결하면 거기에도 평가할 수 있습니다."
-3
View File
@@ -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."
-3
View File
@@ -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."
-3
View File
@@ -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ć."
-3
View File
@@ -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á."
-3
View File
@@ -406,9 +406,6 @@
"starValue": "${rating} / 5",
"scoreValue": "${score} / 10",
"setScore": "Установить оценку",
"notRated": "Без оценки",
"liked": "Понравилось",
"notLiked": "Не отмечено понравившимся",
"saved": "Сохранено",
"notAvailable": "Совпадений не найдено",
"noConnectedTrackers": "Подключите трекер в настройках, чтобы оценивать там."
+1 -1
View File
@@ -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
+4 -10
View File
@@ -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',
+4 -10
View File
@@ -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',
+4 -10
View File
@@ -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
View File
@@ -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',
+4 -10
View File
@@ -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',
+4 -10
View File
@@ -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',
+4 -10
View File
@@ -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',
+4 -10
View File
@@ -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',
+4 -10
View File
@@ -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',
+4 -10
View File
@@ -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',
+4 -10
View File
@@ -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',
+4 -10
View File
@@ -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',
+4 -10
View File
@@ -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',
+4 -10
View File
@@ -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',
+4 -10
View File
@@ -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',
+4 -10
View File
@@ -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',
-3
View File
@@ -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."
-3
View File
@@ -406,9 +406,6 @@
"starValue": "${rating} / 5",
"scoreValue": "${score} / 10",
"setScore": "设置分数",
"notRated": "未评分",
"liked": "已喜欢",
"notLiked": "未喜欢",
"saved": "已保存",
"notAvailable": "未找到匹配项",
"noConnectedTrackers": "在设置中连接跟踪器即可在那里评分。"
+4
View File
@@ -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
+25 -18
View File
@@ -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
+5
View File
@@ -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
+4
View File
@@ -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,
+5
View File
@@ -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
+11 -2
View File
@@ -56,10 +56,14 @@ class ServerCapabilities {
/// returns synthesized hubs but with sparser categorisation.
final bool richHubs;
/// Numeric ratings (Plex 010 via [Item.userRating]). Jellyfin offers
/// only a binary like/dislike, so star sliders should be hidden.
/// Numeric ratings (Plex 010 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,
+12 -6
View File
@@ -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);
}
}
+1 -8
View File
@@ -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'),
+5 -2
View File
@@ -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.
+5
View File
@@ -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);
+6 -2
View File
@@ -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),
),
);
}
+60 -133
View File
@@ -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),
),
),
],
),
),
),