From bc57ffb930ef3e3dbf372bb36cf0832eb5b818fe Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Mon, 20 Apr 2026 20:17:57 +0200 Subject: [PATCH] feat: export/import settings --- lib/i18n/da.i18n.json | 12 + lib/i18n/de.i18n.json | 12 + lib/i18n/en.i18n.json | 12 + lib/i18n/es.i18n.json | 12 + lib/i18n/fr.i18n.json | 12 + lib/i18n/it.i18n.json | 12 + lib/i18n/ja.i18n.json | 12 + lib/i18n/ko.i18n.json | 12 + lib/i18n/nb.i18n.json | 12 + lib/i18n/nl.i18n.json | 12 + lib/i18n/pl.i18n.json | 12 + lib/i18n/pt.i18n.json | 12 + lib/i18n/ru.i18n.json | 12 + lib/i18n/strings.g.dart | 4 +- lib/i18n/strings_da.g.dart | 28 +- lib/i18n/strings_de.g.dart | 28 +- lib/i18n/strings_en.g.dart | 52 +++- lib/i18n/strings_es.g.dart | 28 +- lib/i18n/strings_fr.g.dart | 28 +- lib/i18n/strings_it.g.dart | 28 +- lib/i18n/strings_ja.g.dart | 28 +- lib/i18n/strings_ko.g.dart | 28 +- lib/i18n/strings_nb.g.dart | 28 +- lib/i18n/strings_nl.g.dart | 28 +- lib/i18n/strings_pl.g.dart | 28 +- lib/i18n/strings_pt.g.dart | 28 +- lib/i18n/strings_ru.g.dart | 28 +- lib/i18n/strings_sv.g.dart | 28 +- lib/i18n/strings_zh.g.dart | 28 +- lib/i18n/sv.i18n.json | 12 + lib/i18n/zh.i18n.json | 12 + lib/providers/settings_provider.dart | 39 ++- lib/providers/theme_provider.dart | 10 + lib/screens/settings/settings_screen.dart | 105 +++++++ lib/services/file_picker_service.dart | 19 ++ lib/services/settings_export_service.dart | 362 ++++++++++++++++++++++ 36 files changed, 1118 insertions(+), 45 deletions(-) create mode 100644 lib/services/settings_export_service.dart diff --git a/lib/i18n/da.i18n.json b/lib/i18n/da.i18n.json index 10767165..174073af 100644 --- a/lib/i18n/da.i18n.json +++ b/lib/i18n/da.i18n.json @@ -153,6 +153,18 @@ "resetSettings": "Nulstil indstillinger", "resetSettingsDescription": "Alle indstillinger nulstilles til standardværdier. Denne handling kan ikke fortrydes.", "resetSettingsSuccess": "Indstillinger nulstillet", + "backup": "Sikkerhedskopi", + "exportSettings": "Eksportér indstillinger", + "exportSettingsDescription": "Gem dine præferencer i en fil", + "exportSettingsSuccess": "Indstillinger eksporteret", + "exportSettingsFailed": "Kunne ikke eksportere indstillinger", + "importSettings": "Importér indstillinger", + "importSettingsDescription": "Gendan præferencer fra en fil", + "importSettingsConfirm": "Dette vil erstatte dine nuværende indstillinger. Fortsæt?", + "importSettingsSuccess": "Indstillinger importeret", + "importSettingsFailed": "Kunne ikke importere indstillinger", + "importSettingsInvalidFile": "Denne fil er ikke en gyldig Plezy-indstillingseksport", + "importSettingsNoUser": "Log ind før import af indstillinger", "shortcutsReset": "Genveje nulstillet til standard", "about": "Om", "aboutDescription": "App-information og licenser", diff --git a/lib/i18n/de.i18n.json b/lib/i18n/de.i18n.json index b532d308..9098eb6f 100644 --- a/lib/i18n/de.i18n.json +++ b/lib/i18n/de.i18n.json @@ -153,6 +153,18 @@ "resetSettings": "Einstellungen zurücksetzen", "resetSettingsDescription": "Alle Einstellungen auf Standard zurücksetzen. Dies kann nicht rückgängig gemacht werden.", "resetSettingsSuccess": "Einstellungen erfolgreich zurückgesetzt", + "backup": "Sicherung", + "exportSettings": "Einstellungen exportieren", + "exportSettingsDescription": "Speichere deine Einstellungen in einer Datei", + "exportSettingsSuccess": "Einstellungen exportiert", + "exportSettingsFailed": "Einstellungen konnten nicht exportiert werden", + "importSettings": "Einstellungen importieren", + "importSettingsDescription": "Einstellungen aus einer Datei wiederherstellen", + "importSettingsConfirm": "Dies ersetzt deine aktuellen Einstellungen. Fortfahren?", + "importSettingsSuccess": "Einstellungen importiert", + "importSettingsFailed": "Einstellungen konnten nicht importiert werden", + "importSettingsInvalidFile": "Diese Datei ist kein gültiger Plezy-Einstellungsexport", + "importSettingsNoUser": "Vor dem Import bitte anmelden", "shortcutsReset": "Tastenkürzel auf Standard zurückgesetzt", "about": "Über", "aboutDescription": "App-Informationen und Lizenzen", diff --git a/lib/i18n/en.i18n.json b/lib/i18n/en.i18n.json index 7914b30f..5e21f818 100644 --- a/lib/i18n/en.i18n.json +++ b/lib/i18n/en.i18n.json @@ -153,6 +153,18 @@ "resetSettings": "Reset Settings", "resetSettingsDescription": "This will reset all settings to their default values. This action cannot be undone.", "resetSettingsSuccess": "Settings reset successfully", + "backup": "Backup", + "exportSettings": "Export Settings", + "exportSettingsDescription": "Save your preferences to a file", + "exportSettingsSuccess": "Settings exported", + "exportSettingsFailed": "Could not export settings", + "importSettings": "Import Settings", + "importSettingsDescription": "Restore preferences from a file", + "importSettingsConfirm": "This will replace your current settings. Continue?", + "importSettingsSuccess": "Settings imported", + "importSettingsFailed": "Could not import settings", + "importSettingsInvalidFile": "This file isn't a valid Plezy settings export", + "importSettingsNoUser": "Sign in before importing settings", "shortcutsReset": "Shortcuts reset to defaults", "about": "About", "aboutDescription": "App information and licenses", diff --git a/lib/i18n/es.i18n.json b/lib/i18n/es.i18n.json index e29fe882..c03ece00 100644 --- a/lib/i18n/es.i18n.json +++ b/lib/i18n/es.i18n.json @@ -153,6 +153,18 @@ "resetSettings": "Restablecer Configuración", "resetSettingsDescription": "Esto restablecerá todos los ajustes a sus valores predeterminados. Esta acción no se puede deshacer.", "resetSettingsSuccess": "Configuración restablecida con éxito", + "backup": "Copia de seguridad", + "exportSettings": "Exportar Configuración", + "exportSettingsDescription": "Guardar tus preferencias en un archivo", + "exportSettingsSuccess": "Configuración exportada", + "exportSettingsFailed": "No se pudo exportar la configuración", + "importSettings": "Importar Configuración", + "importSettingsDescription": "Restaurar preferencias desde un archivo", + "importSettingsConfirm": "Esto reemplazará tu configuración actual. ¿Continuar?", + "importSettingsSuccess": "Configuración importada", + "importSettingsFailed": "No se pudo importar la configuración", + "importSettingsInvalidFile": "Este archivo no es una exportación válida de Plezy", + "importSettingsNoUser": "Inicia sesión antes de importar la configuración", "shortcutsReset": "Atajos restablecidos a los valores predeterminados", "about": "Acerca de", "aboutDescription": "Información de la aplicación y licencias", diff --git a/lib/i18n/fr.i18n.json b/lib/i18n/fr.i18n.json index d042800c..2d02cce9 100644 --- a/lib/i18n/fr.i18n.json +++ b/lib/i18n/fr.i18n.json @@ -153,6 +153,18 @@ "resetSettings": "Réinitialiser les paramètres", "resetSettingsDescription": "Cela réinitialisera tous les paramètres à leurs valeurs par défaut. Cette action ne peut pas être annulée.", "resetSettingsSuccess": "Réinitialisation des paramètres réussie", + "backup": "Sauvegarde", + "exportSettings": "Exporter les paramètres", + "exportSettingsDescription": "Enregistrer vos préférences dans un fichier", + "exportSettingsSuccess": "Paramètres exportés", + "exportSettingsFailed": "Impossible d'exporter les paramètres", + "importSettings": "Importer les paramètres", + "importSettingsDescription": "Restaurer les préférences depuis un fichier", + "importSettingsConfirm": "Cela remplacera vos paramètres actuels. Continuer ?", + "importSettingsSuccess": "Paramètres importés", + "importSettingsFailed": "Impossible d'importer les paramètres", + "importSettingsInvalidFile": "Ce fichier n'est pas un export Plezy valide", + "importSettingsNoUser": "Connectez-vous avant d'importer les paramètres", "shortcutsReset": "Raccourcis réinitialisés aux valeurs par défaut", "about": "À propos", "aboutDescription": "Informations sur l'application et licences", diff --git a/lib/i18n/it.i18n.json b/lib/i18n/it.i18n.json index 19f0de1d..a30eb9ef 100644 --- a/lib/i18n/it.i18n.json +++ b/lib/i18n/it.i18n.json @@ -153,6 +153,18 @@ "resetSettings": "Ripristina impostazioni", "resetSettingsDescription": "Questa opzione ripristinerà tutte le impostazioni ai valori predefiniti. Non può essere annullata.", "resetSettingsSuccess": "Impostazioni ripristinate correttamente", + "backup": "Backup", + "exportSettings": "Esporta impostazioni", + "exportSettingsDescription": "Salva le tue preferenze in un file", + "exportSettingsSuccess": "Impostazioni esportate", + "exportSettingsFailed": "Impossibile esportare le impostazioni", + "importSettings": "Importa impostazioni", + "importSettingsDescription": "Ripristina le preferenze da un file", + "importSettingsConfirm": "Questa azione sostituirà le impostazioni attuali. Continuare?", + "importSettingsSuccess": "Impostazioni importate", + "importSettingsFailed": "Impossibile importare le impostazioni", + "importSettingsInvalidFile": "Questo file non è un'esportazione Plezy valida", + "importSettingsNoUser": "Accedi prima di importare le impostazioni", "shortcutsReset": "Scorciatoie ripristinate alle impostazioni predefinite", "about": "Informazioni", "aboutDescription": "Informazioni sull'app e le licenze", diff --git a/lib/i18n/ja.i18n.json b/lib/i18n/ja.i18n.json index 47760589..1df52845 100644 --- a/lib/i18n/ja.i18n.json +++ b/lib/i18n/ja.i18n.json @@ -153,6 +153,18 @@ "resetSettings": "設定をリセット", "resetSettingsDescription": "すべての設定をデフォルト値にリセットします。この操作は元に戻せません。", "resetSettingsSuccess": "設定を正常にリセットしました", + "backup": "バックアップ", + "exportSettings": "設定をエクスポート", + "exportSettingsDescription": "設定をファイルに保存", + "exportSettingsSuccess": "設定をエクスポートしました", + "exportSettingsFailed": "設定をエクスポートできませんでした", + "importSettings": "設定をインポート", + "importSettingsDescription": "ファイルから設定を復元", + "importSettingsConfirm": "現在の設定を置き換えます。続行しますか?", + "importSettingsSuccess": "設定をインポートしました", + "importSettingsFailed": "設定をインポートできませんでした", + "importSettingsInvalidFile": "このファイルは有効なPlezyの設定エクスポートではありません", + "importSettingsNoUser": "設定をインポートする前にサインインしてください", "shortcutsReset": "ショートカットをデフォルトにリセットしました", "about": "アプリについて", "aboutDescription": "アプリ情報とライセンス", diff --git a/lib/i18n/ko.i18n.json b/lib/i18n/ko.i18n.json index 5766826f..94256152 100644 --- a/lib/i18n/ko.i18n.json +++ b/lib/i18n/ko.i18n.json @@ -153,6 +153,18 @@ "resetSettings": "설정 재설정", "resetSettingsDescription": "모든 설정을 기본값으로 재설정합니다. 이 작업은 되돌릴 수 없습니다.", "resetSettingsSuccess": "설정 재설정 성공", + "backup": "백업", + "exportSettings": "설정 내보내기", + "exportSettingsDescription": "기본 설정을 파일로 저장", + "exportSettingsSuccess": "설정 내보내기 완료", + "exportSettingsFailed": "설정을 내보낼 수 없습니다", + "importSettings": "설정 가져오기", + "importSettingsDescription": "파일에서 기본 설정 복원", + "importSettingsConfirm": "현재 설정을 대체합니다. 계속하시겠습니까?", + "importSettingsSuccess": "설정 가져오기 완료", + "importSettingsFailed": "설정을 가져올 수 없습니다", + "importSettingsInvalidFile": "유효한 Plezy 설정 내보내기 파일이 아닙니다", + "importSettingsNoUser": "설정을 가져오기 전에 로그인하세요", "shortcutsReset": "단축키가 기본값으로 재설정되었습니다", "about": "정보", "aboutDescription": "응용 프로그램 정보 및 라이선스", diff --git a/lib/i18n/nb.i18n.json b/lib/i18n/nb.i18n.json index 205e4145..0633fb03 100644 --- a/lib/i18n/nb.i18n.json +++ b/lib/i18n/nb.i18n.json @@ -153,6 +153,18 @@ "resetSettings": "Tilbakestill innstillinger", "resetSettingsDescription": "Dette vil tilbakestille alle innstillinger til standardverdier. Denne handlingen kan ikke angres.", "resetSettingsSuccess": "Innstillinger tilbakestilt", + "backup": "Sikkerhetskopi", + "exportSettings": "Eksporter innstillinger", + "exportSettingsDescription": "Lagre innstillingene i en fil", + "exportSettingsSuccess": "Innstillinger eksportert", + "exportSettingsFailed": "Kunne ikke eksportere innstillinger", + "importSettings": "Importer innstillinger", + "importSettingsDescription": "Gjenopprett innstillinger fra en fil", + "importSettingsConfirm": "Dette vil erstatte nåværende innstillinger. Fortsette?", + "importSettingsSuccess": "Innstillinger importert", + "importSettingsFailed": "Kunne ikke importere innstillinger", + "importSettingsInvalidFile": "Denne filen er ikke en gyldig Plezy-innstillingseksport", + "importSettingsNoUser": "Logg inn før import av innstillinger", "shortcutsReset": "Snarveier tilbakestilt til standard", "about": "Om", "aboutDescription": "Appinformasjon og lisenser", diff --git a/lib/i18n/nl.i18n.json b/lib/i18n/nl.i18n.json index 287daaa7..502a0320 100644 --- a/lib/i18n/nl.i18n.json +++ b/lib/i18n/nl.i18n.json @@ -153,6 +153,18 @@ "resetSettings": "Instellingen resetten", "resetSettingsDescription": "Dit reset alle instellingen naar hun standaard waarden. Deze actie kan niet ongedaan gemaakt worden.", "resetSettingsSuccess": "Instellingen succesvol gereset", + "backup": "Back-up", + "exportSettings": "Instellingen exporteren", + "exportSettingsDescription": "Sla je voorkeuren op in een bestand", + "exportSettingsSuccess": "Instellingen geëxporteerd", + "exportSettingsFailed": "Kon instellingen niet exporteren", + "importSettings": "Instellingen importeren", + "importSettingsDescription": "Voorkeuren herstellen vanuit een bestand", + "importSettingsConfirm": "Hiermee worden je huidige instellingen vervangen. Doorgaan?", + "importSettingsSuccess": "Instellingen geïmporteerd", + "importSettingsFailed": "Kon instellingen niet importeren", + "importSettingsInvalidFile": "Dit bestand is geen geldige Plezy-export", + "importSettingsNoUser": "Meld je aan voordat je instellingen importeert", "shortcutsReset": "Sneltoetsen gereset naar standaard", "about": "Over", "aboutDescription": "App informatie en licenties", diff --git a/lib/i18n/pl.i18n.json b/lib/i18n/pl.i18n.json index 97df2dbc..cdd11079 100644 --- a/lib/i18n/pl.i18n.json +++ b/lib/i18n/pl.i18n.json @@ -153,6 +153,18 @@ "resetSettings": "Zresetuj ustawienia", "resetSettingsDescription": "Wszystkie ustawienia zostaną przywrócone do wartości domyślnych. Tej operacji nie można cofnąć.", "resetSettingsSuccess": "Ustawienia zresetowane pomyślnie", + "backup": "Kopia zapasowa", + "exportSettings": "Eksportuj ustawienia", + "exportSettingsDescription": "Zapisz swoje preferencje do pliku", + "exportSettingsSuccess": "Ustawienia wyeksportowane", + "exportSettingsFailed": "Nie można wyeksportować ustawień", + "importSettings": "Importuj ustawienia", + "importSettingsDescription": "Przywróć preferencje z pliku", + "importSettingsConfirm": "Bieżące ustawienia zostaną zastąpione. Kontynuować?", + "importSettingsSuccess": "Ustawienia zaimportowane", + "importSettingsFailed": "Nie można zaimportować ustawień", + "importSettingsInvalidFile": "Ten plik nie jest prawidłowym eksportem Plezy", + "importSettingsNoUser": "Zaloguj się przed importem ustawień", "shortcutsReset": "Skróty przywrócone do domyślnych", "about": "O aplikacji", "aboutDescription": "Informacje o aplikacji i licencje", diff --git a/lib/i18n/pt.i18n.json b/lib/i18n/pt.i18n.json index 63afa50c..9b375d03 100644 --- a/lib/i18n/pt.i18n.json +++ b/lib/i18n/pt.i18n.json @@ -153,6 +153,18 @@ "resetSettings": "Redefinir Configurações", "resetSettingsDescription": "Isso redefinirá todas as configurações para os valores padrão. Esta ação não pode ser desfeita.", "resetSettingsSuccess": "Configurações redefinidas com sucesso", + "backup": "Backup", + "exportSettings": "Exportar Configurações", + "exportSettingsDescription": "Salve suas preferências em um arquivo", + "exportSettingsSuccess": "Configurações exportadas", + "exportSettingsFailed": "Não foi possível exportar as configurações", + "importSettings": "Importar Configurações", + "importSettingsDescription": "Restaurar preferências a partir de um arquivo", + "importSettingsConfirm": "Isso substituirá suas configurações atuais. Continuar?", + "importSettingsSuccess": "Configurações importadas", + "importSettingsFailed": "Não foi possível importar as configurações", + "importSettingsInvalidFile": "Este arquivo não é uma exportação válida do Plezy", + "importSettingsNoUser": "Entre na conta antes de importar as configurações", "shortcutsReset": "Atalhos redefinidos para o padrão", "about": "Sobre", "aboutDescription": "Informações do app e licenças", diff --git a/lib/i18n/ru.i18n.json b/lib/i18n/ru.i18n.json index 8bd31468..d8439933 100644 --- a/lib/i18n/ru.i18n.json +++ b/lib/i18n/ru.i18n.json @@ -153,6 +153,18 @@ "resetSettings": "Сбросить настройки", "resetSettingsDescription": "Все настройки будут сброшены до значений по умолчанию. Это действие нельзя отменить.", "resetSettingsSuccess": "Настройки успешно сброшены", + "backup": "Резервная копия", + "exportSettings": "Экспорт настроек", + "exportSettingsDescription": "Сохранить настройки в файл", + "exportSettingsSuccess": "Настройки экспортированы", + "exportSettingsFailed": "Не удалось экспортировать настройки", + "importSettings": "Импорт настроек", + "importSettingsDescription": "Восстановить настройки из файла", + "importSettingsConfirm": "Это заменит ваши текущие настройки. Продолжить?", + "importSettingsSuccess": "Настройки импортированы", + "importSettingsFailed": "Не удалось импортировать настройки", + "importSettingsInvalidFile": "Этот файл не является действительным экспортом настроек Plezy", + "importSettingsNoUser": "Войдите в систему перед импортом настроек", "shortcutsReset": "Горячие клавиши сброшены по умолчанию", "about": "О приложении", "aboutDescription": "Информация о приложении и лицензии", diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index 539ac58b..1b555bc4 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -4,9 +4,9 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 15 -/// Strings: 13290 (886 per locale) +/// Strings: 13470 (898 per locale) /// -/// Built on 2026-04-20 at 14:18 UTC +/// Built on 2026-04-20 at 16:17 UTC // coverage:ignore-file // ignore_for_file: type=lint, unused_import diff --git a/lib/i18n/strings_da.g.dart b/lib/i18n/strings_da.g.dart index 502a3af3..b0f34ebe 100644 --- a/lib/i18n/strings_da.g.dart +++ b/lib/i18n/strings_da.g.dart @@ -273,6 +273,18 @@ class _TranslationsSettingsDa implements TranslationsSettingsEn { @override String get resetSettings => 'Nulstil indstillinger'; @override String get resetSettingsDescription => 'Alle indstillinger nulstilles til standardværdier. Denne handling kan ikke fortrydes.'; @override String get resetSettingsSuccess => 'Indstillinger nulstillet'; + @override String get backup => 'Sikkerhedskopi'; + @override String get exportSettings => 'Eksportér indstillinger'; + @override String get exportSettingsDescription => 'Gem dine præferencer i en fil'; + @override String get exportSettingsSuccess => 'Indstillinger eksporteret'; + @override String get exportSettingsFailed => 'Kunne ikke eksportere indstillinger'; + @override String get importSettings => 'Importér indstillinger'; + @override String get importSettingsDescription => 'Gendan præferencer fra en fil'; + @override String get importSettingsConfirm => 'Dette vil erstatte dine nuværende indstillinger. Fortsæt?'; + @override String get importSettingsSuccess => 'Indstillinger importeret'; + @override String get importSettingsFailed => 'Kunne ikke importere indstillinger'; + @override String get importSettingsInvalidFile => 'Denne fil er ikke en gyldig Plezy-indstillingseksport'; + @override String get importSettingsNoUser => 'Log ind før import af indstillinger'; @override String get shortcutsReset => 'Genveje nulstillet til standard'; @override String get about => 'Om'; @override String get aboutDescription => 'App-information og licenser'; @@ -1536,6 +1548,18 @@ extension on TranslationsDa { 'settings.resetSettings' => 'Nulstil indstillinger', 'settings.resetSettingsDescription' => 'Alle indstillinger nulstilles til standardværdier. Denne handling kan ikke fortrydes.', 'settings.resetSettingsSuccess' => 'Indstillinger nulstillet', + 'settings.backup' => 'Sikkerhedskopi', + 'settings.exportSettings' => 'Eksportér indstillinger', + 'settings.exportSettingsDescription' => 'Gem dine præferencer i en fil', + 'settings.exportSettingsSuccess' => 'Indstillinger eksporteret', + 'settings.exportSettingsFailed' => 'Kunne ikke eksportere indstillinger', + 'settings.importSettings' => 'Importér indstillinger', + 'settings.importSettingsDescription' => 'Gendan præferencer fra en fil', + 'settings.importSettingsConfirm' => 'Dette vil erstatte dine nuværende indstillinger. Fortsæt?', + 'settings.importSettingsSuccess' => 'Indstillinger importeret', + 'settings.importSettingsFailed' => 'Kunne ikke importere indstillinger', + 'settings.importSettingsInvalidFile' => 'Denne fil er ikke en gyldig Plezy-indstillingseksport', + 'settings.importSettingsNoUser' => 'Log ind før import af indstillinger', 'settings.shortcutsReset' => 'Genveje nulstillet til standard', 'settings.about' => 'Om', 'settings.aboutDescription' => 'App-information og licenser', @@ -1893,6 +1917,8 @@ extension on TranslationsDa { 'libraries.tabs.collections' => 'Samlinger', 'libraries.tabs.playlists' => 'Playlister', 'libraries.groupings.title' => 'Gruppering', + _ => null, + } ?? switch (path) { 'libraries.groupings.all' => 'Alle', 'libraries.groupings.movies' => 'Film', 'libraries.groupings.shows' => 'TV-serier', @@ -1905,8 +1931,6 @@ extension on TranslationsDa { 'about.appDescription' => 'En smuk Plex-klient til Flutter', 'about.viewLicensesDescription' => 'Se licenser for tredjepartsbiblioteker', 'serverSelection.allServerConnectionsFailed' => 'Kunne ikke forbinde til nogen servere. Tjek dit netværk og prøv igen.', - _ => null, - } ?? switch (path) { 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Ingen servere fundet for ${username} (${email})', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'Kunne ikke indlæse servere: ${error}', 'hubDetail.title' => 'Titel', diff --git a/lib/i18n/strings_de.g.dart b/lib/i18n/strings_de.g.dart index 15de6669..44d27a64 100644 --- a/lib/i18n/strings_de.g.dart +++ b/lib/i18n/strings_de.g.dart @@ -273,6 +273,18 @@ class _TranslationsSettingsDe implements TranslationsSettingsEn { @override String get resetSettings => 'Einstellungen zurücksetzen'; @override String get resetSettingsDescription => 'Alle Einstellungen auf Standard zurücksetzen. Dies kann nicht rückgängig gemacht werden.'; @override String get resetSettingsSuccess => 'Einstellungen erfolgreich zurückgesetzt'; + @override String get backup => 'Sicherung'; + @override String get exportSettings => 'Einstellungen exportieren'; + @override String get exportSettingsDescription => 'Speichere deine Einstellungen in einer Datei'; + @override String get exportSettingsSuccess => 'Einstellungen exportiert'; + @override String get exportSettingsFailed => 'Einstellungen konnten nicht exportiert werden'; + @override String get importSettings => 'Einstellungen importieren'; + @override String get importSettingsDescription => 'Einstellungen aus einer Datei wiederherstellen'; + @override String get importSettingsConfirm => 'Dies ersetzt deine aktuellen Einstellungen. Fortfahren?'; + @override String get importSettingsSuccess => 'Einstellungen importiert'; + @override String get importSettingsFailed => 'Einstellungen konnten nicht importiert werden'; + @override String get importSettingsInvalidFile => 'Diese Datei ist kein gültiger Plezy-Einstellungsexport'; + @override String get importSettingsNoUser => 'Vor dem Import bitte anmelden'; @override String get shortcutsReset => 'Tastenkürzel auf Standard zurückgesetzt'; @override String get about => 'Über'; @override String get aboutDescription => 'App-Informationen und Lizenzen'; @@ -1536,6 +1548,18 @@ extension on TranslationsDe { 'settings.resetSettings' => 'Einstellungen zurücksetzen', 'settings.resetSettingsDescription' => 'Alle Einstellungen auf Standard zurücksetzen. Dies kann nicht rückgängig gemacht werden.', 'settings.resetSettingsSuccess' => 'Einstellungen erfolgreich zurückgesetzt', + 'settings.backup' => 'Sicherung', + 'settings.exportSettings' => 'Einstellungen exportieren', + 'settings.exportSettingsDescription' => 'Speichere deine Einstellungen in einer Datei', + 'settings.exportSettingsSuccess' => 'Einstellungen exportiert', + 'settings.exportSettingsFailed' => 'Einstellungen konnten nicht exportiert werden', + 'settings.importSettings' => 'Einstellungen importieren', + 'settings.importSettingsDescription' => 'Einstellungen aus einer Datei wiederherstellen', + 'settings.importSettingsConfirm' => 'Dies ersetzt deine aktuellen Einstellungen. Fortfahren?', + 'settings.importSettingsSuccess' => 'Einstellungen importiert', + 'settings.importSettingsFailed' => 'Einstellungen konnten nicht importiert werden', + 'settings.importSettingsInvalidFile' => 'Diese Datei ist kein gültiger Plezy-Einstellungsexport', + 'settings.importSettingsNoUser' => 'Vor dem Import bitte anmelden', 'settings.shortcutsReset' => 'Tastenkürzel auf Standard zurückgesetzt', 'settings.about' => 'Über', 'settings.aboutDescription' => 'App-Informationen und Lizenzen', @@ -1893,6 +1917,8 @@ extension on TranslationsDe { 'libraries.tabs.collections' => 'Sammlungen', 'libraries.tabs.playlists' => 'Wiedergabelisten', 'libraries.groupings.title' => 'Gruppierung', + _ => null, + } ?? switch (path) { 'libraries.groupings.all' => 'Alle', 'libraries.groupings.movies' => 'Filme', 'libraries.groupings.shows' => 'Serien', @@ -1905,8 +1931,6 @@ extension on TranslationsDe { 'about.appDescription' => 'Ein schöner Plex-Client für Flutter', 'about.viewLicensesDescription' => 'Lizenzen von Drittanbieter-Bibliotheken anzeigen', 'serverSelection.allServerConnectionsFailed' => 'Verbindung zu allen Servern fehlgeschlagen. Bitte Netzwerk prüfen und erneut versuchen.', - _ => null, - } ?? switch (path) { 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Keine Server gefunden für ${username} (${email})', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'Server konnten nicht geladen werden: ${error}', 'hubDetail.title' => 'Titel', diff --git a/lib/i18n/strings_en.g.dart b/lib/i18n/strings_en.g.dart index c4992b05..4498fabe 100644 --- a/lib/i18n/strings_en.g.dart +++ b/lib/i18n/strings_en.g.dart @@ -563,6 +563,42 @@ class TranslationsSettingsEn { /// en: 'Settings reset successfully' String get resetSettingsSuccess => 'Settings reset successfully'; + /// en: 'Backup' + String get backup => 'Backup'; + + /// en: 'Export Settings' + String get exportSettings => 'Export Settings'; + + /// en: 'Save your preferences to a file' + String get exportSettingsDescription => 'Save your preferences to a file'; + + /// en: 'Settings exported' + String get exportSettingsSuccess => 'Settings exported'; + + /// en: 'Could not export settings' + String get exportSettingsFailed => 'Could not export settings'; + + /// en: 'Import Settings' + String get importSettings => 'Import Settings'; + + /// en: 'Restore preferences from a file' + String get importSettingsDescription => 'Restore preferences from a file'; + + /// en: 'This will replace your current settings. Continue?' + String get importSettingsConfirm => 'This will replace your current settings. Continue?'; + + /// en: 'Settings imported' + String get importSettingsSuccess => 'Settings imported'; + + /// en: 'Could not import settings' + String get importSettingsFailed => 'Could not import settings'; + + /// en: 'This file isn't a valid Plezy settings export' + String get importSettingsInvalidFile => 'This file isn\'t a valid Plezy settings export'; + + /// en: 'Sign in before importing settings' + String get importSettingsNoUser => 'Sign in before importing settings'; + /// en: 'Shortcuts reset to defaults' String get shortcutsReset => 'Shortcuts reset to defaults'; @@ -3315,6 +3351,18 @@ extension on Translations { 'settings.resetSettings' => 'Reset Settings', 'settings.resetSettingsDescription' => 'This will reset all settings to their default values. This action cannot be undone.', 'settings.resetSettingsSuccess' => 'Settings reset successfully', + 'settings.backup' => 'Backup', + 'settings.exportSettings' => 'Export Settings', + 'settings.exportSettingsDescription' => 'Save your preferences to a file', + 'settings.exportSettingsSuccess' => 'Settings exported', + 'settings.exportSettingsFailed' => 'Could not export settings', + 'settings.importSettings' => 'Import Settings', + 'settings.importSettingsDescription' => 'Restore preferences from a file', + 'settings.importSettingsConfirm' => 'This will replace your current settings. Continue?', + 'settings.importSettingsSuccess' => 'Settings imported', + 'settings.importSettingsFailed' => 'Could not import settings', + 'settings.importSettingsInvalidFile' => 'This file isn\'t a valid Plezy settings export', + 'settings.importSettingsNoUser' => 'Sign in before importing settings', 'settings.shortcutsReset' => 'Shortcuts reset to defaults', 'settings.about' => 'About', 'settings.aboutDescription' => 'App information and licenses', @@ -3672,6 +3720,8 @@ extension on Translations { 'libraries.tabs.collections' => 'Collections', 'libraries.tabs.playlists' => 'Playlists', 'libraries.groupings.title' => 'Grouping', + _ => null, + } ?? switch (path) { 'libraries.groupings.all' => 'All', 'libraries.groupings.movies' => 'Movies', 'libraries.groupings.shows' => 'TV Shows', @@ -3684,8 +3734,6 @@ extension on Translations { 'about.appDescription' => 'A beautiful Plex client for Flutter', 'about.viewLicensesDescription' => 'View licenses of third-party libraries', 'serverSelection.allServerConnectionsFailed' => 'Failed to connect to any servers. Please check your network and try again.', - _ => null, - } ?? switch (path) { 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'No servers found for ${username} (${email})', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'Failed to load servers: ${error}', 'hubDetail.title' => 'Title', diff --git a/lib/i18n/strings_es.g.dart b/lib/i18n/strings_es.g.dart index 601536bf..1b823889 100644 --- a/lib/i18n/strings_es.g.dart +++ b/lib/i18n/strings_es.g.dart @@ -273,6 +273,18 @@ class _TranslationsSettingsEs implements TranslationsSettingsEn { @override String get resetSettings => 'Restablecer Configuración'; @override String get resetSettingsDescription => 'Esto restablecerá todos los ajustes a sus valores predeterminados. Esta acción no se puede deshacer.'; @override String get resetSettingsSuccess => 'Configuración restablecida con éxito'; + @override String get backup => 'Copia de seguridad'; + @override String get exportSettings => 'Exportar Configuración'; + @override String get exportSettingsDescription => 'Guardar tus preferencias en un archivo'; + @override String get exportSettingsSuccess => 'Configuración exportada'; + @override String get exportSettingsFailed => 'No se pudo exportar la configuración'; + @override String get importSettings => 'Importar Configuración'; + @override String get importSettingsDescription => 'Restaurar preferencias desde un archivo'; + @override String get importSettingsConfirm => 'Esto reemplazará tu configuración actual. ¿Continuar?'; + @override String get importSettingsSuccess => 'Configuración importada'; + @override String get importSettingsFailed => 'No se pudo importar la configuración'; + @override String get importSettingsInvalidFile => 'Este archivo no es una exportación válida de Plezy'; + @override String get importSettingsNoUser => 'Inicia sesión antes de importar la configuración'; @override String get shortcutsReset => 'Atajos restablecidos a los valores predeterminados'; @override String get about => 'Acerca de'; @override String get aboutDescription => 'Información de la aplicación y licencias'; @@ -1536,6 +1548,18 @@ extension on TranslationsEs { 'settings.resetSettings' => 'Restablecer Configuración', 'settings.resetSettingsDescription' => 'Esto restablecerá todos los ajustes a sus valores predeterminados. Esta acción no se puede deshacer.', 'settings.resetSettingsSuccess' => 'Configuración restablecida con éxito', + 'settings.backup' => 'Copia de seguridad', + 'settings.exportSettings' => 'Exportar Configuración', + 'settings.exportSettingsDescription' => 'Guardar tus preferencias en un archivo', + 'settings.exportSettingsSuccess' => 'Configuración exportada', + 'settings.exportSettingsFailed' => 'No se pudo exportar la configuración', + 'settings.importSettings' => 'Importar Configuración', + 'settings.importSettingsDescription' => 'Restaurar preferencias desde un archivo', + 'settings.importSettingsConfirm' => 'Esto reemplazará tu configuración actual. ¿Continuar?', + 'settings.importSettingsSuccess' => 'Configuración importada', + 'settings.importSettingsFailed' => 'No se pudo importar la configuración', + 'settings.importSettingsInvalidFile' => 'Este archivo no es una exportación válida de Plezy', + 'settings.importSettingsNoUser' => 'Inicia sesión antes de importar la configuración', 'settings.shortcutsReset' => 'Atajos restablecidos a los valores predeterminados', 'settings.about' => 'Acerca de', 'settings.aboutDescription' => 'Información de la aplicación y licencias', @@ -1893,6 +1917,8 @@ extension on TranslationsEs { 'libraries.tabs.collections' => 'Colecciones', 'libraries.tabs.playlists' => 'Listas', 'libraries.groupings.title' => 'Agrupación', + _ => null, + } ?? switch (path) { 'libraries.groupings.all' => 'Todo', 'libraries.groupings.movies' => 'Películas', 'libraries.groupings.shows' => 'Series', @@ -1905,8 +1931,6 @@ extension on TranslationsEs { 'about.appDescription' => 'Un cliente de Plex para Flutter', 'about.viewLicensesDescription' => 'Ver licencias de librerías de terceros', 'serverSelection.allServerConnectionsFailed' => 'No se pudo conectar con ningún servidor. Por favor, comprueba tu conexión e inténtalo de nuevo.', - _ => null, - } ?? switch (path) { 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'No se encontraron servidores para ${username} (${email})', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'Error al cargar servidores: ${error}', 'hubDetail.title' => 'Título', diff --git a/lib/i18n/strings_fr.g.dart b/lib/i18n/strings_fr.g.dart index ff43a505..b86a2093 100644 --- a/lib/i18n/strings_fr.g.dart +++ b/lib/i18n/strings_fr.g.dart @@ -273,6 +273,18 @@ class _TranslationsSettingsFr implements TranslationsSettingsEn { @override String get resetSettings => 'Réinitialiser les paramètres'; @override String get resetSettingsDescription => 'Cela réinitialisera tous les paramètres à leurs valeurs par défaut. Cette action ne peut pas être annulée.'; @override String get resetSettingsSuccess => 'Réinitialisation des paramètres réussie'; + @override String get backup => 'Sauvegarde'; + @override String get exportSettings => 'Exporter les paramètres'; + @override String get exportSettingsDescription => 'Enregistrer vos préférences dans un fichier'; + @override String get exportSettingsSuccess => 'Paramètres exportés'; + @override String get exportSettingsFailed => 'Impossible d\'exporter les paramètres'; + @override String get importSettings => 'Importer les paramètres'; + @override String get importSettingsDescription => 'Restaurer les préférences depuis un fichier'; + @override String get importSettingsConfirm => 'Cela remplacera vos paramètres actuels. Continuer ?'; + @override String get importSettingsSuccess => 'Paramètres importés'; + @override String get importSettingsFailed => 'Impossible d\'importer les paramètres'; + @override String get importSettingsInvalidFile => 'Ce fichier n\'est pas un export Plezy valide'; + @override String get importSettingsNoUser => 'Connectez-vous avant d\'importer les paramètres'; @override String get shortcutsReset => 'Raccourcis réinitialisés aux valeurs par défaut'; @override String get about => 'À propos'; @override String get aboutDescription => 'Informations sur l\'application et licences'; @@ -1536,6 +1548,18 @@ extension on TranslationsFr { 'settings.resetSettings' => 'Réinitialiser les paramètres', 'settings.resetSettingsDescription' => 'Cela réinitialisera tous les paramètres à leurs valeurs par défaut. Cette action ne peut pas être annulée.', 'settings.resetSettingsSuccess' => 'Réinitialisation des paramètres réussie', + 'settings.backup' => 'Sauvegarde', + 'settings.exportSettings' => 'Exporter les paramètres', + 'settings.exportSettingsDescription' => 'Enregistrer vos préférences dans un fichier', + 'settings.exportSettingsSuccess' => 'Paramètres exportés', + 'settings.exportSettingsFailed' => 'Impossible d\'exporter les paramètres', + 'settings.importSettings' => 'Importer les paramètres', + 'settings.importSettingsDescription' => 'Restaurer les préférences depuis un fichier', + 'settings.importSettingsConfirm' => 'Cela remplacera vos paramètres actuels. Continuer ?', + 'settings.importSettingsSuccess' => 'Paramètres importés', + 'settings.importSettingsFailed' => 'Impossible d\'importer les paramètres', + 'settings.importSettingsInvalidFile' => 'Ce fichier n\'est pas un export Plezy valide', + 'settings.importSettingsNoUser' => 'Connectez-vous avant d\'importer les paramètres', 'settings.shortcutsReset' => 'Raccourcis réinitialisés aux valeurs par défaut', 'settings.about' => 'À propos', 'settings.aboutDescription' => 'Informations sur l\'application et licences', @@ -1893,6 +1917,8 @@ extension on TranslationsFr { 'libraries.tabs.collections' => 'Collections', 'libraries.tabs.playlists' => 'Playlists', 'libraries.groupings.title' => 'Regroupement', + _ => null, + } ?? switch (path) { 'libraries.groupings.all' => 'Tous', 'libraries.groupings.movies' => 'Films', 'libraries.groupings.shows' => 'Show TV', @@ -1905,8 +1931,6 @@ extension on TranslationsFr { 'about.appDescription' => 'Un magnifique client Plex pour Flutter', 'about.viewLicensesDescription' => 'Afficher les licences des bibliothèques tierces', 'serverSelection.allServerConnectionsFailed' => 'Impossible de se connecter à un serveur. Veuillez vérifier votre connexion réseau et réessayer.', - _ => null, - } ?? switch (path) { 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Aucun serveur trouvé pour ${username} (${email})', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'Échec du chargement des serveurs: ${error}', 'hubDetail.title' => 'Titre', diff --git a/lib/i18n/strings_it.g.dart b/lib/i18n/strings_it.g.dart index 5e3c3a59..b5b1c409 100644 --- a/lib/i18n/strings_it.g.dart +++ b/lib/i18n/strings_it.g.dart @@ -273,6 +273,18 @@ class _TranslationsSettingsIt implements TranslationsSettingsEn { @override String get resetSettings => 'Ripristina impostazioni'; @override String get resetSettingsDescription => 'Questa opzione ripristinerà tutte le impostazioni ai valori predefiniti. Non può essere annullata.'; @override String get resetSettingsSuccess => 'Impostazioni ripristinate correttamente'; + @override String get backup => 'Backup'; + @override String get exportSettings => 'Esporta impostazioni'; + @override String get exportSettingsDescription => 'Salva le tue preferenze in un file'; + @override String get exportSettingsSuccess => 'Impostazioni esportate'; + @override String get exportSettingsFailed => 'Impossibile esportare le impostazioni'; + @override String get importSettings => 'Importa impostazioni'; + @override String get importSettingsDescription => 'Ripristina le preferenze da un file'; + @override String get importSettingsConfirm => 'Questa azione sostituirà le impostazioni attuali. Continuare?'; + @override String get importSettingsSuccess => 'Impostazioni importate'; + @override String get importSettingsFailed => 'Impossibile importare le impostazioni'; + @override String get importSettingsInvalidFile => 'Questo file non è un\'esportazione Plezy valida'; + @override String get importSettingsNoUser => 'Accedi prima di importare le impostazioni'; @override String get shortcutsReset => 'Scorciatoie ripristinate alle impostazioni predefinite'; @override String get about => 'Informazioni'; @override String get aboutDescription => 'Informazioni sull\'app e le licenze'; @@ -1536,6 +1548,18 @@ extension on TranslationsIt { 'settings.resetSettings' => 'Ripristina impostazioni', 'settings.resetSettingsDescription' => 'Questa opzione ripristinerà tutte le impostazioni ai valori predefiniti. Non può essere annullata.', 'settings.resetSettingsSuccess' => 'Impostazioni ripristinate correttamente', + 'settings.backup' => 'Backup', + 'settings.exportSettings' => 'Esporta impostazioni', + 'settings.exportSettingsDescription' => 'Salva le tue preferenze in un file', + 'settings.exportSettingsSuccess' => 'Impostazioni esportate', + 'settings.exportSettingsFailed' => 'Impossibile esportare le impostazioni', + 'settings.importSettings' => 'Importa impostazioni', + 'settings.importSettingsDescription' => 'Ripristina le preferenze da un file', + 'settings.importSettingsConfirm' => 'Questa azione sostituirà le impostazioni attuali. Continuare?', + 'settings.importSettingsSuccess' => 'Impostazioni importate', + 'settings.importSettingsFailed' => 'Impossibile importare le impostazioni', + 'settings.importSettingsInvalidFile' => 'Questo file non è un\'esportazione Plezy valida', + 'settings.importSettingsNoUser' => 'Accedi prima di importare le impostazioni', 'settings.shortcutsReset' => 'Scorciatoie ripristinate alle impostazioni predefinite', 'settings.about' => 'Informazioni', 'settings.aboutDescription' => 'Informazioni sull\'app e le licenze', @@ -1893,6 +1917,8 @@ extension on TranslationsIt { 'libraries.tabs.collections' => 'Raccolte', 'libraries.tabs.playlists' => 'Playlist', 'libraries.groupings.title' => 'Raggruppamento', + _ => null, + } ?? switch (path) { 'libraries.groupings.all' => 'Tutti', 'libraries.groupings.movies' => 'Film', 'libraries.groupings.shows' => 'Serie TV', @@ -1905,8 +1931,6 @@ extension on TranslationsIt { 'about.appDescription' => 'Un bellissimo client Plex per Flutter', 'about.viewLicensesDescription' => 'Visualizza le licenze delle librerie di terze parti', 'serverSelection.allServerConnectionsFailed' => 'Impossibile connettersi a nessun server. Controlla la tua rete e riprova.', - _ => null, - } ?? switch (path) { 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Nessun server trovato per ${username} (${email})', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'Impossibile caricare i server: ${error}', 'hubDetail.title' => 'Titolo', diff --git a/lib/i18n/strings_ja.g.dart b/lib/i18n/strings_ja.g.dart index 2c5e749d..04d978e7 100644 --- a/lib/i18n/strings_ja.g.dart +++ b/lib/i18n/strings_ja.g.dart @@ -273,6 +273,18 @@ class _TranslationsSettingsJa implements TranslationsSettingsEn { @override String get resetSettings => '設定をリセット'; @override String get resetSettingsDescription => 'すべての設定をデフォルト値にリセットします。この操作は元に戻せません。'; @override String get resetSettingsSuccess => '設定を正常にリセットしました'; + @override String get backup => 'バックアップ'; + @override String get exportSettings => '設定をエクスポート'; + @override String get exportSettingsDescription => '設定をファイルに保存'; + @override String get exportSettingsSuccess => '設定をエクスポートしました'; + @override String get exportSettingsFailed => '設定をエクスポートできませんでした'; + @override String get importSettings => '設定をインポート'; + @override String get importSettingsDescription => 'ファイルから設定を復元'; + @override String get importSettingsConfirm => '現在の設定を置き換えます。続行しますか?'; + @override String get importSettingsSuccess => '設定をインポートしました'; + @override String get importSettingsFailed => '設定をインポートできませんでした'; + @override String get importSettingsInvalidFile => 'このファイルは有効なPlezyの設定エクスポートではありません'; + @override String get importSettingsNoUser => '設定をインポートする前にサインインしてください'; @override String get shortcutsReset => 'ショートカットをデフォルトにリセットしました'; @override String get about => 'アプリについて'; @override String get aboutDescription => 'アプリ情報とライセンス'; @@ -1536,6 +1548,18 @@ extension on TranslationsJa { 'settings.resetSettings' => '設定をリセット', 'settings.resetSettingsDescription' => 'すべての設定をデフォルト値にリセットします。この操作は元に戻せません。', 'settings.resetSettingsSuccess' => '設定を正常にリセットしました', + 'settings.backup' => 'バックアップ', + 'settings.exportSettings' => '設定をエクスポート', + 'settings.exportSettingsDescription' => '設定をファイルに保存', + 'settings.exportSettingsSuccess' => '設定をエクスポートしました', + 'settings.exportSettingsFailed' => '設定をエクスポートできませんでした', + 'settings.importSettings' => '設定をインポート', + 'settings.importSettingsDescription' => 'ファイルから設定を復元', + 'settings.importSettingsConfirm' => '現在の設定を置き換えます。続行しますか?', + 'settings.importSettingsSuccess' => '設定をインポートしました', + 'settings.importSettingsFailed' => '設定をインポートできませんでした', + 'settings.importSettingsInvalidFile' => 'このファイルは有効なPlezyの設定エクスポートではありません', + 'settings.importSettingsNoUser' => '設定をインポートする前にサインインしてください', 'settings.shortcutsReset' => 'ショートカットをデフォルトにリセットしました', 'settings.about' => 'アプリについて', 'settings.aboutDescription' => 'アプリ情報とライセンス', @@ -1893,6 +1917,8 @@ extension on TranslationsJa { 'libraries.tabs.collections' => 'コレクション', 'libraries.tabs.playlists' => 'プレイリスト', 'libraries.groupings.title' => 'グループ', + _ => null, + } ?? switch (path) { 'libraries.groupings.all' => 'すべて', 'libraries.groupings.movies' => '映画', 'libraries.groupings.shows' => 'テレビ番組', @@ -1905,8 +1931,6 @@ extension on TranslationsJa { 'about.appDescription' => 'Flutter製の美しいPlexクライアント', 'about.viewLicensesDescription' => 'サードパーティライブラリのライセンスを表示', 'serverSelection.allServerConnectionsFailed' => 'どのサーバーにも接続できませんでした。ネットワークを確認してもう一度お試しください。', - _ => null, - } ?? switch (path) { 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => '${username} (${email})のサーバーが見つかりません', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'サーバーの読み込みに失敗しました: ${error}', 'hubDetail.title' => 'タイトル', diff --git a/lib/i18n/strings_ko.g.dart b/lib/i18n/strings_ko.g.dart index 28d39b31..ed00c9be 100644 --- a/lib/i18n/strings_ko.g.dart +++ b/lib/i18n/strings_ko.g.dart @@ -273,6 +273,18 @@ class _TranslationsSettingsKo implements TranslationsSettingsEn { @override String get resetSettings => '설정 재설정'; @override String get resetSettingsDescription => '모든 설정을 기본값으로 재설정합니다. 이 작업은 되돌릴 수 없습니다.'; @override String get resetSettingsSuccess => '설정 재설정 성공'; + @override String get backup => '백업'; + @override String get exportSettings => '설정 내보내기'; + @override String get exportSettingsDescription => '기본 설정을 파일로 저장'; + @override String get exportSettingsSuccess => '설정 내보내기 완료'; + @override String get exportSettingsFailed => '설정을 내보낼 수 없습니다'; + @override String get importSettings => '설정 가져오기'; + @override String get importSettingsDescription => '파일에서 기본 설정 복원'; + @override String get importSettingsConfirm => '현재 설정을 대체합니다. 계속하시겠습니까?'; + @override String get importSettingsSuccess => '설정 가져오기 완료'; + @override String get importSettingsFailed => '설정을 가져올 수 없습니다'; + @override String get importSettingsInvalidFile => '유효한 Plezy 설정 내보내기 파일이 아닙니다'; + @override String get importSettingsNoUser => '설정을 가져오기 전에 로그인하세요'; @override String get shortcutsReset => '단축키가 기본값으로 재설정되었습니다'; @override String get about => '정보'; @override String get aboutDescription => '응용 프로그램 정보 및 라이선스'; @@ -1536,6 +1548,18 @@ extension on TranslationsKo { 'settings.resetSettings' => '설정 재설정', 'settings.resetSettingsDescription' => '모든 설정을 기본값으로 재설정합니다. 이 작업은 되돌릴 수 없습니다.', 'settings.resetSettingsSuccess' => '설정 재설정 성공', + 'settings.backup' => '백업', + 'settings.exportSettings' => '설정 내보내기', + 'settings.exportSettingsDescription' => '기본 설정을 파일로 저장', + 'settings.exportSettingsSuccess' => '설정 내보내기 완료', + 'settings.exportSettingsFailed' => '설정을 내보낼 수 없습니다', + 'settings.importSettings' => '설정 가져오기', + 'settings.importSettingsDescription' => '파일에서 기본 설정 복원', + 'settings.importSettingsConfirm' => '현재 설정을 대체합니다. 계속하시겠습니까?', + 'settings.importSettingsSuccess' => '설정 가져오기 완료', + 'settings.importSettingsFailed' => '설정을 가져올 수 없습니다', + 'settings.importSettingsInvalidFile' => '유효한 Plezy 설정 내보내기 파일이 아닙니다', + 'settings.importSettingsNoUser' => '설정을 가져오기 전에 로그인하세요', 'settings.shortcutsReset' => '단축키가 기본값으로 재설정되었습니다', 'settings.about' => '정보', 'settings.aboutDescription' => '응용 프로그램 정보 및 라이선스', @@ -1893,6 +1917,8 @@ extension on TranslationsKo { 'libraries.tabs.collections' => '컬렉션', 'libraries.tabs.playlists' => '재생 목록', 'libraries.groupings.title' => '그룹', + _ => null, + } ?? switch (path) { 'libraries.groupings.all' => '전체', 'libraries.groupings.movies' => '영화', 'libraries.groupings.shows' => 'TV 프로그램', @@ -1905,8 +1931,6 @@ extension on TranslationsKo { 'about.appDescription' => '아름다운 Flutter Plex 클라이언트', 'about.viewLicensesDescription' => '타사 라이브러리 라이선스 보기', 'serverSelection.allServerConnectionsFailed' => '어떤 서버에도 연결할 수 없습니다. 네트워크를 확인하고 다시 시도하세요.', - _ => null, - } ?? switch (path) { 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => '${username} (${email})의 서버를 찾을 수 없습니다.', 'serverSelection.failedToLoadServers' => ({required Object error}) => '서버를 로드할 수 없습니다: ${error}', 'hubDetail.title' => '제목', diff --git a/lib/i18n/strings_nb.g.dart b/lib/i18n/strings_nb.g.dart index 6edebf6d..72bf4eb8 100644 --- a/lib/i18n/strings_nb.g.dart +++ b/lib/i18n/strings_nb.g.dart @@ -273,6 +273,18 @@ class _TranslationsSettingsNb implements TranslationsSettingsEn { @override String get resetSettings => 'Tilbakestill innstillinger'; @override String get resetSettingsDescription => 'Dette vil tilbakestille alle innstillinger til standardverdier. Denne handlingen kan ikke angres.'; @override String get resetSettingsSuccess => 'Innstillinger tilbakestilt'; + @override String get backup => 'Sikkerhetskopi'; + @override String get exportSettings => 'Eksporter innstillinger'; + @override String get exportSettingsDescription => 'Lagre innstillingene i en fil'; + @override String get exportSettingsSuccess => 'Innstillinger eksportert'; + @override String get exportSettingsFailed => 'Kunne ikke eksportere innstillinger'; + @override String get importSettings => 'Importer innstillinger'; + @override String get importSettingsDescription => 'Gjenopprett innstillinger fra en fil'; + @override String get importSettingsConfirm => 'Dette vil erstatte nåværende innstillinger. Fortsette?'; + @override String get importSettingsSuccess => 'Innstillinger importert'; + @override String get importSettingsFailed => 'Kunne ikke importere innstillinger'; + @override String get importSettingsInvalidFile => 'Denne filen er ikke en gyldig Plezy-innstillingseksport'; + @override String get importSettingsNoUser => 'Logg inn før import av innstillinger'; @override String get shortcutsReset => 'Snarveier tilbakestilt til standard'; @override String get about => 'Om'; @override String get aboutDescription => 'Appinformasjon og lisenser'; @@ -1536,6 +1548,18 @@ extension on TranslationsNb { 'settings.resetSettings' => 'Tilbakestill innstillinger', 'settings.resetSettingsDescription' => 'Dette vil tilbakestille alle innstillinger til standardverdier. Denne handlingen kan ikke angres.', 'settings.resetSettingsSuccess' => 'Innstillinger tilbakestilt', + 'settings.backup' => 'Sikkerhetskopi', + 'settings.exportSettings' => 'Eksporter innstillinger', + 'settings.exportSettingsDescription' => 'Lagre innstillingene i en fil', + 'settings.exportSettingsSuccess' => 'Innstillinger eksportert', + 'settings.exportSettingsFailed' => 'Kunne ikke eksportere innstillinger', + 'settings.importSettings' => 'Importer innstillinger', + 'settings.importSettingsDescription' => 'Gjenopprett innstillinger fra en fil', + 'settings.importSettingsConfirm' => 'Dette vil erstatte nåværende innstillinger. Fortsette?', + 'settings.importSettingsSuccess' => 'Innstillinger importert', + 'settings.importSettingsFailed' => 'Kunne ikke importere innstillinger', + 'settings.importSettingsInvalidFile' => 'Denne filen er ikke en gyldig Plezy-innstillingseksport', + 'settings.importSettingsNoUser' => 'Logg inn før import av innstillinger', 'settings.shortcutsReset' => 'Snarveier tilbakestilt til standard', 'settings.about' => 'Om', 'settings.aboutDescription' => 'Appinformasjon og lisenser', @@ -1893,6 +1917,8 @@ extension on TranslationsNb { 'libraries.tabs.collections' => 'Samlinger', 'libraries.tabs.playlists' => 'Spillelister', 'libraries.groupings.title' => 'Gruppering', + _ => null, + } ?? switch (path) { 'libraries.groupings.all' => 'Alle', 'libraries.groupings.movies' => 'Filmer', 'libraries.groupings.shows' => 'TV-serier', @@ -1905,8 +1931,6 @@ extension on TranslationsNb { 'about.appDescription' => 'En vakker Plex-klient for Flutter', 'about.viewLicensesDescription' => 'Vis lisenser for tredjepartsbiblioteker', 'serverSelection.allServerConnectionsFailed' => 'Kunne ikke koble til noen servere. Sjekk nettverket ditt og prøv igjen.', - _ => null, - } ?? switch (path) { 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Ingen servere funnet for ${username} (${email})', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'Kunne ikke laste servere: ${error}', 'hubDetail.title' => 'Tittel', diff --git a/lib/i18n/strings_nl.g.dart b/lib/i18n/strings_nl.g.dart index d9bfa6b6..caaec153 100644 --- a/lib/i18n/strings_nl.g.dart +++ b/lib/i18n/strings_nl.g.dart @@ -273,6 +273,18 @@ class _TranslationsSettingsNl implements TranslationsSettingsEn { @override String get resetSettings => 'Instellingen resetten'; @override String get resetSettingsDescription => 'Dit reset alle instellingen naar hun standaard waarden. Deze actie kan niet ongedaan gemaakt worden.'; @override String get resetSettingsSuccess => 'Instellingen succesvol gereset'; + @override String get backup => 'Back-up'; + @override String get exportSettings => 'Instellingen exporteren'; + @override String get exportSettingsDescription => 'Sla je voorkeuren op in een bestand'; + @override String get exportSettingsSuccess => 'Instellingen geëxporteerd'; + @override String get exportSettingsFailed => 'Kon instellingen niet exporteren'; + @override String get importSettings => 'Instellingen importeren'; + @override String get importSettingsDescription => 'Voorkeuren herstellen vanuit een bestand'; + @override String get importSettingsConfirm => 'Hiermee worden je huidige instellingen vervangen. Doorgaan?'; + @override String get importSettingsSuccess => 'Instellingen geïmporteerd'; + @override String get importSettingsFailed => 'Kon instellingen niet importeren'; + @override String get importSettingsInvalidFile => 'Dit bestand is geen geldige Plezy-export'; + @override String get importSettingsNoUser => 'Meld je aan voordat je instellingen importeert'; @override String get shortcutsReset => 'Sneltoetsen gereset naar standaard'; @override String get about => 'Over'; @override String get aboutDescription => 'App informatie en licenties'; @@ -1536,6 +1548,18 @@ extension on TranslationsNl { 'settings.resetSettings' => 'Instellingen resetten', 'settings.resetSettingsDescription' => 'Dit reset alle instellingen naar hun standaard waarden. Deze actie kan niet ongedaan gemaakt worden.', 'settings.resetSettingsSuccess' => 'Instellingen succesvol gereset', + 'settings.backup' => 'Back-up', + 'settings.exportSettings' => 'Instellingen exporteren', + 'settings.exportSettingsDescription' => 'Sla je voorkeuren op in een bestand', + 'settings.exportSettingsSuccess' => 'Instellingen geëxporteerd', + 'settings.exportSettingsFailed' => 'Kon instellingen niet exporteren', + 'settings.importSettings' => 'Instellingen importeren', + 'settings.importSettingsDescription' => 'Voorkeuren herstellen vanuit een bestand', + 'settings.importSettingsConfirm' => 'Hiermee worden je huidige instellingen vervangen. Doorgaan?', + 'settings.importSettingsSuccess' => 'Instellingen geïmporteerd', + 'settings.importSettingsFailed' => 'Kon instellingen niet importeren', + 'settings.importSettingsInvalidFile' => 'Dit bestand is geen geldige Plezy-export', + 'settings.importSettingsNoUser' => 'Meld je aan voordat je instellingen importeert', 'settings.shortcutsReset' => 'Sneltoetsen gereset naar standaard', 'settings.about' => 'Over', 'settings.aboutDescription' => 'App informatie en licenties', @@ -1893,6 +1917,8 @@ extension on TranslationsNl { 'libraries.tabs.collections' => 'Collecties', 'libraries.tabs.playlists' => 'Afspeellijsten', 'libraries.groupings.title' => 'Groepering', + _ => null, + } ?? switch (path) { 'libraries.groupings.all' => 'Alles', 'libraries.groupings.movies' => 'Films', 'libraries.groupings.shows' => 'Series', @@ -1905,8 +1931,6 @@ extension on TranslationsNl { 'about.appDescription' => 'Een mooie Plex client voor Flutter', 'about.viewLicensesDescription' => 'Bekijk licenties van third-party bibliotheken', 'serverSelection.allServerConnectionsFailed' => 'Kon niet verbinden met servers. Controleer je netwerk en probeer opnieuw.', - _ => null, - } ?? switch (path) { 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Geen servers gevonden voor ${username} (${email})', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'Kon servers niet laden: ${error}', 'hubDetail.title' => 'Titel', diff --git a/lib/i18n/strings_pl.g.dart b/lib/i18n/strings_pl.g.dart index 9e5adece..88c53693 100644 --- a/lib/i18n/strings_pl.g.dart +++ b/lib/i18n/strings_pl.g.dart @@ -273,6 +273,18 @@ class _TranslationsSettingsPl implements TranslationsSettingsEn { @override String get resetSettings => 'Zresetuj ustawienia'; @override String get resetSettingsDescription => 'Wszystkie ustawienia zostaną przywrócone do wartości domyślnych. Tej operacji nie można cofnąć.'; @override String get resetSettingsSuccess => 'Ustawienia zresetowane pomyślnie'; + @override String get backup => 'Kopia zapasowa'; + @override String get exportSettings => 'Eksportuj ustawienia'; + @override String get exportSettingsDescription => 'Zapisz swoje preferencje do pliku'; + @override String get exportSettingsSuccess => 'Ustawienia wyeksportowane'; + @override String get exportSettingsFailed => 'Nie można wyeksportować ustawień'; + @override String get importSettings => 'Importuj ustawienia'; + @override String get importSettingsDescription => 'Przywróć preferencje z pliku'; + @override String get importSettingsConfirm => 'Bieżące ustawienia zostaną zastąpione. Kontynuować?'; + @override String get importSettingsSuccess => 'Ustawienia zaimportowane'; + @override String get importSettingsFailed => 'Nie można zaimportować ustawień'; + @override String get importSettingsInvalidFile => 'Ten plik nie jest prawidłowym eksportem Plezy'; + @override String get importSettingsNoUser => 'Zaloguj się przed importem ustawień'; @override String get shortcutsReset => 'Skróty przywrócone do domyślnych'; @override String get about => 'O aplikacji'; @override String get aboutDescription => 'Informacje o aplikacji i licencje'; @@ -1536,6 +1548,18 @@ extension on TranslationsPl { 'settings.resetSettings' => 'Zresetuj ustawienia', 'settings.resetSettingsDescription' => 'Wszystkie ustawienia zostaną przywrócone do wartości domyślnych. Tej operacji nie można cofnąć.', 'settings.resetSettingsSuccess' => 'Ustawienia zresetowane pomyślnie', + 'settings.backup' => 'Kopia zapasowa', + 'settings.exportSettings' => 'Eksportuj ustawienia', + 'settings.exportSettingsDescription' => 'Zapisz swoje preferencje do pliku', + 'settings.exportSettingsSuccess' => 'Ustawienia wyeksportowane', + 'settings.exportSettingsFailed' => 'Nie można wyeksportować ustawień', + 'settings.importSettings' => 'Importuj ustawienia', + 'settings.importSettingsDescription' => 'Przywróć preferencje z pliku', + 'settings.importSettingsConfirm' => 'Bieżące ustawienia zostaną zastąpione. Kontynuować?', + 'settings.importSettingsSuccess' => 'Ustawienia zaimportowane', + 'settings.importSettingsFailed' => 'Nie można zaimportować ustawień', + 'settings.importSettingsInvalidFile' => 'Ten plik nie jest prawidłowym eksportem Plezy', + 'settings.importSettingsNoUser' => 'Zaloguj się przed importem ustawień', 'settings.shortcutsReset' => 'Skróty przywrócone do domyślnych', 'settings.about' => 'O aplikacji', 'settings.aboutDescription' => 'Informacje o aplikacji i licencje', @@ -1893,6 +1917,8 @@ extension on TranslationsPl { 'libraries.tabs.collections' => 'Kolekcje', 'libraries.tabs.playlists' => 'Playlisty', 'libraries.groupings.title' => 'Grupowanie', + _ => null, + } ?? switch (path) { 'libraries.groupings.all' => 'Wszystkie', 'libraries.groupings.movies' => 'Filmy', 'libraries.groupings.shows' => 'Seriale TV', @@ -1905,8 +1931,6 @@ extension on TranslationsPl { 'about.appDescription' => 'Piękny klient Plex na Flutter', 'about.viewLicensesDescription' => 'Zobacz licencje bibliotek zewnętrznych', 'serverSelection.allServerConnectionsFailed' => 'Nie udało się połączyć z żadnym serwerem. Sprawdź sieć i spróbuj ponownie.', - _ => null, - } ?? switch (path) { 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Nie znaleziono serwerów dla ${username} (${email})', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'Nie udało się załadować serwerów: ${error}', 'hubDetail.title' => 'Tytuł', diff --git a/lib/i18n/strings_pt.g.dart b/lib/i18n/strings_pt.g.dart index 97df50d5..53edf2e4 100644 --- a/lib/i18n/strings_pt.g.dart +++ b/lib/i18n/strings_pt.g.dart @@ -273,6 +273,18 @@ class _TranslationsSettingsPt implements TranslationsSettingsEn { @override String get resetSettings => 'Redefinir Configurações'; @override String get resetSettingsDescription => 'Isso redefinirá todas as configurações para os valores padrão. Esta ação não pode ser desfeita.'; @override String get resetSettingsSuccess => 'Configurações redefinidas com sucesso'; + @override String get backup => 'Backup'; + @override String get exportSettings => 'Exportar Configurações'; + @override String get exportSettingsDescription => 'Salve suas preferências em um arquivo'; + @override String get exportSettingsSuccess => 'Configurações exportadas'; + @override String get exportSettingsFailed => 'Não foi possível exportar as configurações'; + @override String get importSettings => 'Importar Configurações'; + @override String get importSettingsDescription => 'Restaurar preferências a partir de um arquivo'; + @override String get importSettingsConfirm => 'Isso substituirá suas configurações atuais. Continuar?'; + @override String get importSettingsSuccess => 'Configurações importadas'; + @override String get importSettingsFailed => 'Não foi possível importar as configurações'; + @override String get importSettingsInvalidFile => 'Este arquivo não é uma exportação válida do Plezy'; + @override String get importSettingsNoUser => 'Entre na conta antes de importar as configurações'; @override String get shortcutsReset => 'Atalhos redefinidos para o padrão'; @override String get about => 'Sobre'; @override String get aboutDescription => 'Informações do app e licenças'; @@ -1536,6 +1548,18 @@ extension on TranslationsPt { 'settings.resetSettings' => 'Redefinir Configurações', 'settings.resetSettingsDescription' => 'Isso redefinirá todas as configurações para os valores padrão. Esta ação não pode ser desfeita.', 'settings.resetSettingsSuccess' => 'Configurações redefinidas com sucesso', + 'settings.backup' => 'Backup', + 'settings.exportSettings' => 'Exportar Configurações', + 'settings.exportSettingsDescription' => 'Salve suas preferências em um arquivo', + 'settings.exportSettingsSuccess' => 'Configurações exportadas', + 'settings.exportSettingsFailed' => 'Não foi possível exportar as configurações', + 'settings.importSettings' => 'Importar Configurações', + 'settings.importSettingsDescription' => 'Restaurar preferências a partir de um arquivo', + 'settings.importSettingsConfirm' => 'Isso substituirá suas configurações atuais. Continuar?', + 'settings.importSettingsSuccess' => 'Configurações importadas', + 'settings.importSettingsFailed' => 'Não foi possível importar as configurações', + 'settings.importSettingsInvalidFile' => 'Este arquivo não é uma exportação válida do Plezy', + 'settings.importSettingsNoUser' => 'Entre na conta antes de importar as configurações', 'settings.shortcutsReset' => 'Atalhos redefinidos para o padrão', 'settings.about' => 'Sobre', 'settings.aboutDescription' => 'Informações do app e licenças', @@ -1893,6 +1917,8 @@ extension on TranslationsPt { 'libraries.tabs.collections' => 'Coleções', 'libraries.tabs.playlists' => 'Playlists', 'libraries.groupings.title' => 'Agrupamento', + _ => null, + } ?? switch (path) { 'libraries.groupings.all' => 'Todos', 'libraries.groupings.movies' => 'Filmes', 'libraries.groupings.shows' => 'Séries de TV', @@ -1905,8 +1931,6 @@ extension on TranslationsPt { 'about.appDescription' => 'Um belo cliente Plex para Flutter', 'about.viewLicensesDescription' => 'Ver licenças de bibliotecas de terceiros', 'serverSelection.allServerConnectionsFailed' => 'Falha ao conectar a qualquer servidor. Verifique sua rede e tente novamente.', - _ => null, - } ?? switch (path) { 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Nenhum servidor encontrado para ${username} (${email})', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'Falha ao carregar servidores: ${error}', 'hubDetail.title' => 'Título', diff --git a/lib/i18n/strings_ru.g.dart b/lib/i18n/strings_ru.g.dart index a3d3da2c..554946ca 100644 --- a/lib/i18n/strings_ru.g.dart +++ b/lib/i18n/strings_ru.g.dart @@ -273,6 +273,18 @@ class _TranslationsSettingsRu implements TranslationsSettingsEn { @override String get resetSettings => 'Сбросить настройки'; @override String get resetSettingsDescription => 'Все настройки будут сброшены до значений по умолчанию. Это действие нельзя отменить.'; @override String get resetSettingsSuccess => 'Настройки успешно сброшены'; + @override String get backup => 'Резервная копия'; + @override String get exportSettings => 'Экспорт настроек'; + @override String get exportSettingsDescription => 'Сохранить настройки в файл'; + @override String get exportSettingsSuccess => 'Настройки экспортированы'; + @override String get exportSettingsFailed => 'Не удалось экспортировать настройки'; + @override String get importSettings => 'Импорт настроек'; + @override String get importSettingsDescription => 'Восстановить настройки из файла'; + @override String get importSettingsConfirm => 'Это заменит ваши текущие настройки. Продолжить?'; + @override String get importSettingsSuccess => 'Настройки импортированы'; + @override String get importSettingsFailed => 'Не удалось импортировать настройки'; + @override String get importSettingsInvalidFile => 'Этот файл не является действительным экспортом настроек Plezy'; + @override String get importSettingsNoUser => 'Войдите в систему перед импортом настроек'; @override String get shortcutsReset => 'Горячие клавиши сброшены по умолчанию'; @override String get about => 'О приложении'; @override String get aboutDescription => 'Информация о приложении и лицензии'; @@ -1536,6 +1548,18 @@ extension on TranslationsRu { 'settings.resetSettings' => 'Сбросить настройки', 'settings.resetSettingsDescription' => 'Все настройки будут сброшены до значений по умолчанию. Это действие нельзя отменить.', 'settings.resetSettingsSuccess' => 'Настройки успешно сброшены', + 'settings.backup' => 'Резервная копия', + 'settings.exportSettings' => 'Экспорт настроек', + 'settings.exportSettingsDescription' => 'Сохранить настройки в файл', + 'settings.exportSettingsSuccess' => 'Настройки экспортированы', + 'settings.exportSettingsFailed' => 'Не удалось экспортировать настройки', + 'settings.importSettings' => 'Импорт настроек', + 'settings.importSettingsDescription' => 'Восстановить настройки из файла', + 'settings.importSettingsConfirm' => 'Это заменит ваши текущие настройки. Продолжить?', + 'settings.importSettingsSuccess' => 'Настройки импортированы', + 'settings.importSettingsFailed' => 'Не удалось импортировать настройки', + 'settings.importSettingsInvalidFile' => 'Этот файл не является действительным экспортом настроек Plezy', + 'settings.importSettingsNoUser' => 'Войдите в систему перед импортом настроек', 'settings.shortcutsReset' => 'Горячие клавиши сброшены по умолчанию', 'settings.about' => 'О приложении', 'settings.aboutDescription' => 'Информация о приложении и лицензии', @@ -1893,6 +1917,8 @@ extension on TranslationsRu { 'libraries.tabs.collections' => 'Коллекции', 'libraries.tabs.playlists' => 'Плейлисты', 'libraries.groupings.title' => 'Группировка', + _ => null, + } ?? switch (path) { 'libraries.groupings.all' => 'Все', 'libraries.groupings.movies' => 'Фильмы', 'libraries.groupings.shows' => 'Сериалы', @@ -1905,8 +1931,6 @@ extension on TranslationsRu { 'about.appDescription' => 'Красивый клиент Plex на Flutter', 'about.viewLicensesDescription' => 'Просмотр лицензий сторонних библиотек', 'serverSelection.allServerConnectionsFailed' => 'Не удалось подключиться ни к одному серверу. Проверьте сеть и попробуйте снова.', - _ => null, - } ?? switch (path) { 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Серверы не найдены для ${username} (${email})', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'Не удалось загрузить серверы: ${error}', 'hubDetail.title' => 'Название', diff --git a/lib/i18n/strings_sv.g.dart b/lib/i18n/strings_sv.g.dart index d62ab48b..91677700 100644 --- a/lib/i18n/strings_sv.g.dart +++ b/lib/i18n/strings_sv.g.dart @@ -273,6 +273,18 @@ class _TranslationsSettingsSv implements TranslationsSettingsEn { @override String get resetSettings => 'Återställ inställningar'; @override String get resetSettingsDescription => 'Detta återställer alla inställningar till standardvärden. Denna åtgärd kan inte ångras.'; @override String get resetSettingsSuccess => 'Inställningar återställda framgångsrikt'; + @override String get backup => 'Säkerhetskopia'; + @override String get exportSettings => 'Exportera inställningar'; + @override String get exportSettingsDescription => 'Spara dina inställningar till en fil'; + @override String get exportSettingsSuccess => 'Inställningar exporterade'; + @override String get exportSettingsFailed => 'Kunde inte exportera inställningar'; + @override String get importSettings => 'Importera inställningar'; + @override String get importSettingsDescription => 'Återställ inställningar från en fil'; + @override String get importSettingsConfirm => 'Detta ersätter dina nuvarande inställningar. Fortsätta?'; + @override String get importSettingsSuccess => 'Inställningar importerade'; + @override String get importSettingsFailed => 'Kunde inte importera inställningar'; + @override String get importSettingsInvalidFile => 'Denna fil är inte en giltig Plezy-export'; + @override String get importSettingsNoUser => 'Logga in innan du importerar inställningar'; @override String get shortcutsReset => 'Genvägar återställda till standard'; @override String get about => 'Om'; @override String get aboutDescription => 'Appinformation och licenser'; @@ -1536,6 +1548,18 @@ extension on TranslationsSv { 'settings.resetSettings' => 'Återställ inställningar', 'settings.resetSettingsDescription' => 'Detta återställer alla inställningar till standardvärden. Denna åtgärd kan inte ångras.', 'settings.resetSettingsSuccess' => 'Inställningar återställda framgångsrikt', + 'settings.backup' => 'Säkerhetskopia', + 'settings.exportSettings' => 'Exportera inställningar', + 'settings.exportSettingsDescription' => 'Spara dina inställningar till en fil', + 'settings.exportSettingsSuccess' => 'Inställningar exporterade', + 'settings.exportSettingsFailed' => 'Kunde inte exportera inställningar', + 'settings.importSettings' => 'Importera inställningar', + 'settings.importSettingsDescription' => 'Återställ inställningar från en fil', + 'settings.importSettingsConfirm' => 'Detta ersätter dina nuvarande inställningar. Fortsätta?', + 'settings.importSettingsSuccess' => 'Inställningar importerade', + 'settings.importSettingsFailed' => 'Kunde inte importera inställningar', + 'settings.importSettingsInvalidFile' => 'Denna fil är inte en giltig Plezy-export', + 'settings.importSettingsNoUser' => 'Logga in innan du importerar inställningar', 'settings.shortcutsReset' => 'Genvägar återställda till standard', 'settings.about' => 'Om', 'settings.aboutDescription' => 'Appinformation och licenser', @@ -1893,6 +1917,8 @@ extension on TranslationsSv { 'libraries.tabs.collections' => 'Samlingar', 'libraries.tabs.playlists' => 'Spellistor', 'libraries.groupings.title' => 'Gruppering', + _ => null, + } ?? switch (path) { 'libraries.groupings.all' => 'Alla', 'libraries.groupings.movies' => 'Filmer', 'libraries.groupings.shows' => 'Serier', @@ -1905,8 +1931,6 @@ extension on TranslationsSv { 'about.appDescription' => 'En vacker Plex-klient för Flutter', 'about.viewLicensesDescription' => 'Visa licenser för tredjepartsbibliotek', 'serverSelection.allServerConnectionsFailed' => 'Misslyckades att ansluta till servrar. Kontrollera ditt nätverk och försök igen.', - _ => null, - } ?? switch (path) { 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Inga servrar hittades för ${username} (${email})', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'Misslyckades att ladda servrar: ${error}', 'hubDetail.title' => 'Titel', diff --git a/lib/i18n/strings_zh.g.dart b/lib/i18n/strings_zh.g.dart index b330067b..95fdc15d 100644 --- a/lib/i18n/strings_zh.g.dart +++ b/lib/i18n/strings_zh.g.dart @@ -273,6 +273,18 @@ class _TranslationsSettingsZh implements TranslationsSettingsEn { @override String get resetSettings => '重置设置'; @override String get resetSettingsDescription => '这会将所有设置重置为其默认值。此操作无法撤销。'; @override String get resetSettingsSuccess => '设置重置成功'; + @override String get backup => '备份'; + @override String get exportSettings => '导出设置'; + @override String get exportSettingsDescription => '将您的偏好保存到文件'; + @override String get exportSettingsSuccess => '设置已导出'; + @override String get exportSettingsFailed => '无法导出设置'; + @override String get importSettings => '导入设置'; + @override String get importSettingsDescription => '从文件恢复偏好'; + @override String get importSettingsConfirm => '这将替换您当前的设置。继续吗?'; + @override String get importSettingsSuccess => '设置已导入'; + @override String get importSettingsFailed => '无法导入设置'; + @override String get importSettingsInvalidFile => '此文件不是有效的 Plezy 设置导出'; + @override String get importSettingsNoUser => '导入设置前请先登录'; @override String get shortcutsReset => '快捷键已重置为默认值'; @override String get about => '关于'; @override String get aboutDescription => '应用程序信息和许可证'; @@ -1536,6 +1548,18 @@ extension on TranslationsZh { 'settings.resetSettings' => '重置设置', 'settings.resetSettingsDescription' => '这会将所有设置重置为其默认值。此操作无法撤销。', 'settings.resetSettingsSuccess' => '设置重置成功', + 'settings.backup' => '备份', + 'settings.exportSettings' => '导出设置', + 'settings.exportSettingsDescription' => '将您的偏好保存到文件', + 'settings.exportSettingsSuccess' => '设置已导出', + 'settings.exportSettingsFailed' => '无法导出设置', + 'settings.importSettings' => '导入设置', + 'settings.importSettingsDescription' => '从文件恢复偏好', + 'settings.importSettingsConfirm' => '这将替换您当前的设置。继续吗?', + 'settings.importSettingsSuccess' => '设置已导入', + 'settings.importSettingsFailed' => '无法导入设置', + 'settings.importSettingsInvalidFile' => '此文件不是有效的 Plezy 设置导出', + 'settings.importSettingsNoUser' => '导入设置前请先登录', 'settings.shortcutsReset' => '快捷键已重置为默认值', 'settings.about' => '关于', 'settings.aboutDescription' => '应用程序信息和许可证', @@ -1893,6 +1917,8 @@ extension on TranslationsZh { 'libraries.tabs.collections' => '合集', 'libraries.tabs.playlists' => '播放列表', 'libraries.groupings.title' => '分组', + _ => null, + } ?? switch (path) { 'libraries.groupings.all' => '全部', 'libraries.groupings.movies' => '电影', 'libraries.groupings.shows' => '剧集', @@ -1905,8 +1931,6 @@ extension on TranslationsZh { 'about.appDescription' => '一款精美的 Flutter Plex 客户端', 'about.viewLicensesDescription' => '查看第三方库的许可证', 'serverSelection.allServerConnectionsFailed' => '无法连接到任何服务器。请检查你的网络并重试。', - _ => null, - } ?? switch (path) { 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => '未找到 ${username} (${email}) 的服务器', 'serverSelection.failedToLoadServers' => ({required Object error}) => '无法加载服务器: ${error}', 'hubDetail.title' => '标题', diff --git a/lib/i18n/sv.i18n.json b/lib/i18n/sv.i18n.json index d5f47c21..50036b7a 100644 --- a/lib/i18n/sv.i18n.json +++ b/lib/i18n/sv.i18n.json @@ -153,6 +153,18 @@ "resetSettings": "Återställ inställningar", "resetSettingsDescription": "Detta återställer alla inställningar till standardvärden. Denna åtgärd kan inte ångras.", "resetSettingsSuccess": "Inställningar återställda framgångsrikt", + "backup": "Säkerhetskopia", + "exportSettings": "Exportera inställningar", + "exportSettingsDescription": "Spara dina inställningar till en fil", + "exportSettingsSuccess": "Inställningar exporterade", + "exportSettingsFailed": "Kunde inte exportera inställningar", + "importSettings": "Importera inställningar", + "importSettingsDescription": "Återställ inställningar från en fil", + "importSettingsConfirm": "Detta ersätter dina nuvarande inställningar. Fortsätta?", + "importSettingsSuccess": "Inställningar importerade", + "importSettingsFailed": "Kunde inte importera inställningar", + "importSettingsInvalidFile": "Denna fil är inte en giltig Plezy-export", + "importSettingsNoUser": "Logga in innan du importerar inställningar", "shortcutsReset": "Genvägar återställda till standard", "about": "Om", "aboutDescription": "Appinformation och licenser", diff --git a/lib/i18n/zh.i18n.json b/lib/i18n/zh.i18n.json index 5862d636..6c4fdf2c 100644 --- a/lib/i18n/zh.i18n.json +++ b/lib/i18n/zh.i18n.json @@ -153,6 +153,18 @@ "resetSettings": "重置设置", "resetSettingsDescription": "这会将所有设置重置为其默认值。此操作无法撤销。", "resetSettingsSuccess": "设置重置成功", + "backup": "备份", + "exportSettings": "导出设置", + "exportSettingsDescription": "将您的偏好保存到文件", + "exportSettingsSuccess": "设置已导出", + "exportSettingsFailed": "无法导出设置", + "importSettings": "导入设置", + "importSettingsDescription": "从文件恢复偏好", + "importSettingsConfirm": "这将替换您当前的设置。继续吗?", + "importSettingsSuccess": "设置已导入", + "importSettingsFailed": "无法导入设置", + "importSettingsInvalidFile": "此文件不是有效的 Plezy 设置导出", + "importSettingsNoUser": "导入设置前请先登录", "shortcutsReset": "快捷键已重置为默认值", "about": "关于", "aboutDescription": "应用程序信息和许可证", diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index 0a2a210d..8f1e194f 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -29,20 +29,33 @@ class SettingsProvider extends ChangeNotifier { Future _initializeSettings() async { if (_isInitialized) return; - _settingsService = await SettingsService.getInstance(); - _libraryDensity = _settingsService!.getLibraryDensity(); - _viewMode = _settingsService!.getViewMode(); - _episodePosterMode = _settingsService!.getEpisodePosterMode(); - _showHeroSection = _settingsService!.getShowHeroSection(); - _useGlobalHubs = _settingsService!.getUseGlobalHubs(); - _showServerNameOnHubs = _settingsService!.getShowServerNameOnHubs(); - _alwaysKeepSidebarOpen = _settingsService!.getAlwaysKeepSidebarOpen(); - _showUnwatchedCount = _settingsService!.getShowUnwatchedCount(); - _hideSpoilers = _settingsService!.getHideSpoilers(); - _showNavBarLabels = _settingsService!.getShowNavBarLabels(); - _liveTvDefaultFavorites = _settingsService!.getLiveTvDefaultFavorites(); - _autoHidePerformanceOverlay = _settingsService!.getAutoHidePerformanceOverlay(); + _readAllFromService(); + _isInitialized = true; + notifyListeners(); + } + + void _readAllFromService() { + final s = _settingsService!; + _libraryDensity = s.getLibraryDensity(); + _viewMode = s.getViewMode(); + _episodePosterMode = s.getEpisodePosterMode(); + _showHeroSection = s.getShowHeroSection(); + _useGlobalHubs = s.getUseGlobalHubs(); + _showServerNameOnHubs = s.getShowServerNameOnHubs(); + _alwaysKeepSidebarOpen = s.getAlwaysKeepSidebarOpen(); + _showUnwatchedCount = s.getShowUnwatchedCount(); + _hideSpoilers = s.getHideSpoilers(); + _showNavBarLabels = s.getShowNavBarLabels(); + _liveTvDefaultFavorites = s.getLiveTvDefaultFavorites(); + _autoHidePerformanceOverlay = s.getAutoHidePerformanceOverlay(); + } + + /// Re-read all settings from SharedPreferences. Used after imports or resets + /// that change persisted settings outside this provider. + Future reload() async { + _settingsService = await SettingsService.getInstance(); + _readAllFromService(); _isInitialized = true; notifyListeners(); } diff --git a/lib/providers/theme_provider.dart b/lib/providers/theme_provider.dart index 4f47c02e..3799da7b 100644 --- a/lib/providers/theme_provider.dart +++ b/lib/providers/theme_provider.dart @@ -77,6 +77,16 @@ class ThemeProvider extends ChangeNotifier { } } + /// Re-read the theme mode from SharedPreferences. Used after imports or + /// resets that change persisted settings outside this provider. + Future reload() async { + _settingsService = await settings.SettingsService.getInstance(); + final mode = _settingsService.getThemeMode(); + _themeMode = mode; + _updateSplashTheme(mode); + notifyListeners(); + } + void _updateSplashTheme(settings.ThemeMode mode) { if (!Platform.isAndroid) return; final name = switch (mode) { diff --git a/lib/screens/settings/settings_screen.dart b/lib/screens/settings/settings_screen.dart index 0e990001..95b1f806 100644 --- a/lib/screens/settings/settings_screen.dart +++ b/lib/screens/settings/settings_screen.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:flutter/foundation.dart'; @@ -14,10 +15,13 @@ import '../../focus/input_mode_tracker.dart'; import '../../i18n/strings.g.dart'; import '../main_screen.dart'; import '../../mixins/refreshable.dart'; +import '../../providers/hidden_libraries_provider.dart'; +import '../../providers/libraries_provider.dart'; import '../../services/donation_service.dart'; import '../../services/download_storage_service.dart'; import '../../services/file_picker_service.dart'; import '../../services/saf_storage_service.dart'; +import '../../services/settings_export_service.dart'; import '../../providers/settings_provider.dart'; import '../../providers/theme_provider.dart'; import '../../providers/trakt_account_provider.dart'; @@ -64,6 +68,8 @@ class _SettingsScreenState extends State with FocusableTab { static const _kCheckForUpdates = 'check_for_updates'; static const _kAbout = 'about'; static const _kWatchTogetherRelay = 'watch_together_relay'; + static const _kExportSettings = 'export_settings'; + static const _kImportSettings = 'import_settings'; KeyboardShortcutsService? _keyboardService; late final bool _keyboardShortcutsSupported = KeyboardShortcutsService.isPlatformSupported(); @@ -175,6 +181,9 @@ class _SettingsScreenState extends State with FocusableTab { // --- Updates (conditional) --- if (UpdateService.isUpdateCheckEnabled) ...[_buildUpdateSection()], + // --- Backup --- + _buildBackupSection(), + // --- About --- ListTile( focusNode: _focusTracker.get(_kAbout), @@ -445,6 +454,31 @@ class _SettingsScreenState extends State with FocusableTab { ); } + Widget _buildBackupSection() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SettingsSectionHeader(t.settings.backup), + ListTile( + focusNode: _focusTracker.get(_kExportSettings), + leading: const AppIcon(Symbols.upload_rounded, fill: 1), + title: Text(t.settings.exportSettings), + subtitle: Text(t.settings.exportSettingsDescription), + trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), + onTap: _handleExportSettings, + ), + ListTile( + focusNode: _focusTracker.get(_kImportSettings), + leading: const AppIcon(Symbols.download_rounded, fill: 1), + title: Text(t.settings.importSettings), + subtitle: Text(t.settings.importSettingsDescription), + trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), + onTap: _showImportSettingsDialog, + ), + ], + ); + } + Widget _buildUpdateSection() { if (UpdateService.useNativeUpdater) { return Column( @@ -703,6 +737,77 @@ class _SettingsScreenState extends State with FocusableTab { ); } + Future _handleExportSettings() async { + try { + final path = await SettingsExportService.exportToFile(); + if (!mounted) return; + if (path == null) return; // user cancelled + showSuccessSnackBar(context, t.settings.exportSettingsSuccess); + } on SettingsExportException { + if (mounted) showErrorSnackBar(context, t.settings.exportSettingsFailed); + } catch (_) { + if (mounted) showErrorSnackBar(context, t.settings.exportSettingsFailed); + } + } + + void _showImportSettingsDialog() { + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return AlertDialog( + title: Text(t.settings.importSettings), + content: Text(t.settings.importSettingsConfirm), + actions: [ + TextButton(autofocus: true, onPressed: () => Navigator.pop(dialogContext), child: Text(t.common.cancel)), + TextButton( + onPressed: () { + Navigator.pop(dialogContext); + _handleImportSettings(); + }, + child: Text(t.settings.importSettings), + ), + ], + ); + }, + ); + } + + Future _handleImportSettings() async { + // Capture providers before any awaits so we don't reach through `context` + // after the widget may have been unmounted. + final themeProvider = context.read(); + final settingsProvider = context.read(); + final hiddenLibrariesProvider = context.read(); + final librariesProvider = context.read(); + + try { + final result = await SettingsExportService.importFromFile(); + if (!mounted) return; + if (result == null) return; // user cancelled file picker + + LocaleSettings.setLocale(_settingsService.getAppLocale()); + await Future.wait([ + themeProvider.reload(), + settingsProvider.reload(), + hiddenLibrariesProvider.refresh(), + if (_keyboardService != null) _keyboardService!.refreshFromStorage(), + ]); + unawaited(librariesProvider.refresh()); + + if (!mounted) return; + _loadSettings(); + showSuccessSnackBar(context, t.settings.importSettingsSuccess); + } on NoUserSignedInException { + if (mounted) showErrorSnackBar(context, t.settings.importSettingsNoUser); + } on InvalidExportFileException { + if (mounted) showErrorSnackBar(context, t.settings.importSettingsInvalidFile); + } on SettingsExportException { + if (mounted) showErrorSnackBar(context, t.settings.importSettingsFailed); + } catch (_) { + if (mounted) showErrorSnackBar(context, t.settings.importSettingsFailed); + } + } + Future _checkForUpdates() async { setState(() => _isCheckingForUpdate = true); diff --git a/lib/services/file_picker_service.dart b/lib/services/file_picker_service.dart index 38b59f0d..cfa01285 100644 --- a/lib/services/file_picker_service.dart +++ b/lib/services/file_picker_service.dart @@ -40,4 +40,23 @@ class FilePickerService { Future getDirectoryPath({String? dialogTitle}) { return _guard('getDirectoryPath', () => FilePicker.platform.getDirectoryPath(dialogTitle: dialogTitle)); } + + Future saveFile({ + String? dialogTitle, + String? fileName, + Uint8List? bytes, + FileType type = FileType.any, + List? allowedExtensions, + }) { + return _guard( + 'saveFile', + () => FilePicker.platform.saveFile( + dialogTitle: dialogTitle, + fileName: fileName, + bytes: bytes, + type: type, + allowedExtensions: allowedExtensions, + ), + ); + } } diff --git a/lib/services/settings_export_service.dart b/lib/services/settings_export_service.dart new file mode 100644 index 00000000..e3e228c1 --- /dev/null +++ b/lib/services/settings_export_service.dart @@ -0,0 +1,362 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:file_picker/file_picker.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../utils/app_logger.dart'; +import '../utils/platform_detector.dart'; +import 'file_picker_service.dart'; +import 'storage_service.dart'; + +class ImportResult { + final int keysImported; + final int keysSkipped; + const ImportResult({required this.keysImported, required this.keysSkipped}); +} + +class SettingsExportException implements Exception { + final String message; + const SettingsExportException(this.message); + @override + String toString() => 'SettingsExportException: $message'; +} + +/// Thrown when an import is attempted without an active Plex user, since the +/// user prefix needed to re-scope library preferences is unavailable. +class NoUserSignedInException extends SettingsExportException { + const NoUserSignedInException() : super('No user is signed in'); +} + +/// Thrown when the chosen file isn't a valid Plezy settings export. +class InvalidExportFileException extends SettingsExportException { + const InvalidExportFileException(super.message); +} + +/// Serializes / restores user-facing SharedPreferences to a JSON file. +/// +/// Strategy is allow-by-default: every key is exported unless it matches an +/// exact denylist or a prefix denylist of auth/cache/internal keys. User-scoped +/// keys (prefixed with `user_{uuid}_`) have that prefix stripped on export and +/// re-applied with the current user's prefix on import, so preferences follow +/// whichever account is signed in on the target device. +class SettingsExportService { + static const int formatVersion = 1; + static const String fileExtension = 'json'; + + // Type markers written into the export JSON. One per SharedPreferences setter. + static const String _typeBool = 'bool'; + static const String _typeInt = 'int'; + static const String _typeDouble = 'double'; + static const String _typeString = 'string'; + static const String _typeStringList = 'stringList'; + + /// Exact keys never included in the export. Matches the auth/account state + /// tracked by [StorageService] plus multi-server and view-state keys. + static const Set _denyKeys = { + // Credentials (from StorageService._credentialKeys) + 'server_url', + 'token', + 'plex_token', + 'server_data', + 'client_identifier', + 'user_profile', + 'current_user_uuid', + 'home_users_cache', + 'home_users_cache_expiry', + // Multi-server routing + 'servers_list', + 'server_order', + // View state, not settings + 'selected_library_index', + 'selected_library_key', + // Internal migration flags + 'buffer_size_migrated_to_auto', + }; + + /// Prefix denylist. A key is excluded if it starts with any of these. + static const List _denyPrefixes = ['server_endpoint_', 'episode_count_', 'watched_threshold_']; + + /// Literal prefix used by [StorageService._userPrefix] for any scoped key. + static const String _userPrefixRoot = 'user_'; + + static bool _isExportable(String strippedKey) { + if (_denyKeys.contains(strippedKey)) return false; + for (final prefix in _denyPrefixes) { + if (strippedKey.startsWith(prefix)) return false; + } + return true; + } + + /// Builds the export map from the given prefs. Pure and testable. + /// + /// [currentUserUuid] — if set, keys prefixed with `user_{uuid}_` have that + /// prefix stripped so they can be re-scoped on import. Keys belonging to any + /// OTHER user are skipped (we only export the active user's prefs). + static Map buildExportMap( + SharedPreferences prefs, { + String? currentUserUuid, + String appVersion = '', + }) { + final prefsOut = >{}; + final currentUserPrefix = (currentUserUuid != null && currentUserUuid.isNotEmpty) + ? '$_userPrefixRoot${currentUserUuid}_' + : null; + + for (final fullKey in prefs.getKeys()) { + String baseKey; + if (currentUserPrefix != null && fullKey.startsWith(currentUserPrefix)) { + baseKey = fullKey.substring(currentUserPrefix.length); + } else if (fullKey.startsWith(_userPrefixRoot)) { + // Scoped to some other user — skip so we only export the active user. + continue; + } else { + baseKey = fullKey; + } + + if (!_isExportable(baseKey)) continue; + + final value = prefs.get(fullKey); + final entry = _encodeValue(value); + if (entry == null) continue; + prefsOut[baseKey] = entry; + } + + return { + 'formatVersion': formatVersion, + 'appVersion': appVersion, + 'exportedAt': DateTime.now().toUtc().toIso8601String(), + 'platform': Platform.operatingSystem, + 'prefs': prefsOut, + }; + } + + static Map? _encodeValue(Object? value) { + if (value is bool) return {'type': _typeBool, 'value': value}; + if (value is int) return {'type': _typeInt, 'value': value}; + if (value is double) return {'type': _typeDouble, 'value': value}; + if (value is String) return {'type': _typeString, 'value': value}; + if (value is List) { + // SharedPreferences only supports List. + return {'type': _typeStringList, 'value': value.map((e) => e.toString()).toList()}; + } + return null; + } + + /// Applies a parsed export map to [prefs]. Pure and testable. + /// + /// Each key in the import overwrites whatever value currently exists at the + /// same (possibly re-scoped) key. Keys not present in the import are left + /// alone — this is a per-key replacement, not a global wipe. + /// + /// Throws [SettingsExportException] for structural problems. + static Future applyImportMap( + Map data, + SharedPreferences prefs, { + required String currentUserUuid, + }) async { + final version = data['formatVersion']; + if (version is! int) { + throw const InvalidExportFileException('Missing formatVersion'); + } + if (version > formatVersion) { + throw InvalidExportFileException('Unsupported formatVersion: $version'); + } + + final rawPrefs = data['prefs']; + if (rawPrefs is! Map) { + throw const InvalidExportFileException('Missing prefs object'); + } + + final userPrefix = 'user_${currentUserUuid}_'; + int imported = 0; + int skipped = 0; + + for (final entry in rawPrefs.entries) { + final baseKey = entry.key.toString(); + if (!_isExportable(baseKey)) { + skipped++; + continue; + } + + final rawEntry = entry.value; + if (rawEntry is! Map) { + skipped++; + continue; + } + + final type = rawEntry['type']; + final value = rawEntry['value']; + if (type is! String) { + skipped++; + continue; + } + + final targetKey = _isUserScopedBaseKey(baseKey) ? '$userPrefix$baseKey' : baseKey; + + final ok = await _writeTyped(prefs, targetKey, type, value); + if (ok) { + imported++; + } else { + skipped++; + appLogger.w('Skipped import of $targetKey (type=$type)'); + } + } + + return ImportResult(keysImported: imported, keysSkipped: skipped); + } + + /// Base keys that [StorageService] persists under the user prefix. These need + /// to be re-scoped to the current user on import. + static bool _isUserScopedBaseKey(String baseKey) { + const exact = {'hidden_libraries', 'library_filters', 'library_order'}; + if (exact.contains(baseKey)) return true; + const prefixes = ['library_filters_', 'library_sort_', 'library_grouping_', 'library_tab_']; + return prefixes.any(baseKey.startsWith); + } + + static Future _writeTyped(SharedPreferences prefs, String key, String type, Object? value) async { + try { + switch (type) { + case _typeBool: + if (value is! bool) return false; + await prefs.setBool(key, value); + return true; + case _typeInt: + if (value is! int) return false; + await prefs.setInt(key, value); + return true; + case _typeDouble: + if (value is num) { + await prefs.setDouble(key, value.toDouble()); + return true; + } + return false; + case _typeString: + if (value is! String) return false; + await prefs.setString(key, value); + return true; + case _typeStringList: + if (value is! List) return false; + await prefs.setStringList(key, value.map((e) => e.toString()).toList()); + return true; + } + } catch (e, st) { + appLogger.e('Failed to import key $key', error: e, stackTrace: st); + } + return false; + } + + // --- File operations ----------------------------------------------------- + + static Future _defaultFileName() async { + final now = DateTime.now(); + final y = now.year.toString().padLeft(4, '0'); + final m = now.month.toString().padLeft(2, '0'); + final d = now.day.toString().padLeft(2, '0'); + return 'plezy-settings-$y$m$d.$fileExtension'; + } + + /// Serializes the current user's settings and writes them to a location of + /// the user's choosing. Returns the saved path, or `null` if the user + /// cancelled the picker. + /// + /// Throws [SettingsExportException] on failure. + static Future exportToFile() async { + final prefs = await SharedPreferences.getInstance(); + final storage = await StorageService.getInstance(); + String appVersion = ''; + try { + final info = await PackageInfo.fromPlatform(); + appVersion = info.version; + } catch (_) { + // best-effort; tolerate platforms without PackageInfo + } + + final exportMap = buildExportMap(prefs, currentUserUuid: storage.getCurrentUserUUID(), appVersion: appVersion); + final jsonString = const JsonEncoder.withIndent(' ').convert(exportMap); + final bytes = Uint8List.fromList(utf8.encode(jsonString)); + final fileName = await _defaultFileName(); + + // Android TV has no document picker — write to the app docs dir and let + // the caller surface the path. + if (Platform.isAndroid && TvDetectionService.isTVSync()) { + return _writeToAppDocuments(fileName, bytes); + } + + try { + final savedPath = await FilePickerService.instance.saveFile( + dialogTitle: 'Export Plezy settings', + fileName: fileName, + bytes: bytes, + type: FileType.custom, + allowedExtensions: const [fileExtension], + ); + return savedPath; + } catch (e, st) { + appLogger.e('Settings export failed', error: e, stackTrace: st); + throw const SettingsExportException('Could not write export file'); + } + } + + static Future _writeToAppDocuments(String fileName, Uint8List bytes) async { + final dir = await getApplicationDocumentsDirectory(); + final file = File(p.join(dir.path, fileName)); + await file.writeAsBytes(bytes, flush: true); + return file.path; + } + + /// Prompts the user to pick a settings JSON and writes its contents into + /// SharedPreferences. Requires a signed-in user. + /// + /// Returns `null` if the user cancelled. Throws [SettingsExportException] on + /// malformed files or unsupported versions. + static Future importFromFile() async { + final storage = await StorageService.getInstance(); + final uuid = storage.getCurrentUserUUID(); + if (uuid == null || uuid.isEmpty) { + throw const NoUserSignedInException(); + } + + final picked = await FilePickerService.instance.pickFiles( + type: FileType.custom, + allowedExtensions: const [fileExtension], + withData: true, + ); + if (picked == null || picked.files.isEmpty) return null; + + final file = picked.files.first; + String contents; + try { + final bytes = file.bytes; + if (bytes != null) { + contents = utf8.decode(bytes); + } else if (file.path != null) { + contents = await File(file.path!).readAsString(); + } else { + throw const InvalidExportFileException('Could not read the selected file'); + } + } catch (e, st) { + appLogger.e('Settings import read failed', error: e, stackTrace: st); + throw const InvalidExportFileException('Could not read the selected file'); + } + + Map data; + try { + final decoded = json.decode(contents); + if (decoded is! Map) { + throw const InvalidExportFileException('Invalid export file'); + } + data = decoded; + } catch (_) { + throw const InvalidExportFileException('Invalid export file'); + } + + final prefs = await SharedPreferences.getInstance(); + return applyImportMap(data, prefs, currentUserUuid: uuid); + } +}