From 5b363a9eb68fc8cc1db485c13a044a36113f45d3 Mon Sep 17 00:00:00 2001 From: Weblate Date: Mon, 7 Aug 2023 09:06:32 +0000 Subject: [PATCH 01/22] Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: SelfPrivacy/SelfPrivacy App Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app/ --- assets/translations/az.json | 3 +-- assets/translations/be.json | 3 +-- assets/translations/cs.json | 3 +-- assets/translations/de.json | 3 +-- assets/translations/fr.json | 3 +-- assets/translations/lv.json | 1 - assets/translations/pl.json | 3 +-- assets/translations/ru.json | 3 +-- assets/translations/sk.json | 3 +-- assets/translations/sl.json | 3 +-- assets/translations/th.json | 3 +-- assets/translations/uk.json | 3 +-- 12 files changed, 11 insertions(+), 23 deletions(-) diff --git a/assets/translations/az.json b/assets/translations/az.json index aae7107a..efe09e29 100644 --- a/assets/translations/az.json +++ b/assets/translations/az.json @@ -123,7 +123,6 @@ "disk": "Disk", "monthly_cost": "Aylıq xərc", "location": "Yerləşdirmə", - "provider": "Provayder", "core_count": { "one": "{} nüvəs", "two": "{} nüvələr", @@ -500,4 +499,4 @@ "reset_onboarding_description": "Enerji ekranını yenidən göstərmək üçün güc açarının sıfırlanması", "cubit_statuses": "Yükləmə kubitlərinin cari vəziyyəti" } -} \ No newline at end of file +} diff --git a/assets/translations/be.json b/assets/translations/be.json index c6dbf7e9..3cbb05db 100644 --- a/assets/translations/be.json +++ b/assets/translations/be.json @@ -425,7 +425,6 @@ "disk": "Дыск", "monthly_cost": "Штомесячны кошт", "location": "Размяшчэнне", - "provider": "Правайдэр", "core_count": { "one": "{} ядро", "two": "{} ядра", @@ -510,4 +509,4 @@ "support": { "title": "Падтрымка SelfPrivacy" } -} \ No newline at end of file +} diff --git a/assets/translations/cs.json b/assets/translations/cs.json index 5186b74e..0c9bcb1a 100644 --- a/assets/translations/cs.json +++ b/assets/translations/cs.json @@ -118,7 +118,6 @@ "disk": "Místní disk", "monthly_cost": "Měsíční náklady", "location": "Umístění", - "provider": "Poskytovatel", "core_count": { "two": "{} jádra", "few": "{} jádra", @@ -510,4 +509,4 @@ "ignore_tls": "Nekontrolujte certifikáty TLS", "ignore_tls_description": "Aplikace nebude při připojování k serveru ověřovat certifikáty TLS." } -} \ No newline at end of file +} diff --git a/assets/translations/de.json b/assets/translations/de.json index 86cc3518..0203a7a1 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -123,7 +123,6 @@ "disk": "Festplatte", "monthly_cost": "Monatliche Kosten", "location": "Standort", - "provider": "Provider", "core_count": { "one": "{} Kern", "two": "{} Kerne", @@ -510,4 +509,4 @@ "ignore_tls": "Überprüfen Sie keine TLS-Zertifikate", "ignore_tls_description": "Die Anwendung validiert TLS-Zertifikate nicht, wenn sie eine Verbindung zum Server herstellt." } -} \ No newline at end of file +} diff --git a/assets/translations/fr.json b/assets/translations/fr.json index e015c782..5fa6d1cc 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -111,7 +111,6 @@ "disk": "Disque local", "monthly_cost": "Coût mensuel", "location": "Localisation", - "provider": "Fournisseur", "core_count": { "one": "{} cœur", "two": "{} cœurs", @@ -286,4 +285,4 @@ "title": "Serveur VPN", "subtitle": "Serveur VPN privé" } -} \ No newline at end of file +} diff --git a/assets/translations/lv.json b/assets/translations/lv.json index 7fbfb797..bac9a852 100644 --- a/assets/translations/lv.json +++ b/assets/translations/lv.json @@ -121,7 +121,6 @@ "disk": "Disks lokāls", "monthly_cost": "Mēneša maksa", "location": "Vieta", - "provider": "Sniedzējs", "core_count": { "one": "{} kodols", "two": "{} kodoli", diff --git a/assets/translations/pl.json b/assets/translations/pl.json index 5a90cc86..fcac7efd 100644 --- a/assets/translations/pl.json +++ b/assets/translations/pl.json @@ -118,7 +118,6 @@ "disk": "Dysk lokalny", "monthly_cost": "Koszt miesięczny", "location": "Lokalizacja danych", - "provider": "Dostawca", "core_count": { "one": "{} jądro", "two": "{} jądra", @@ -509,4 +508,4 @@ "cubit_statuses": "Aktualny stan qubitów ładujących", "ignore_tls": "Używane podczas konfigurowania nowego serwera." } -} \ No newline at end of file +} diff --git a/assets/translations/ru.json b/assets/translations/ru.json index ae4dac6b..e5c200a0 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -124,7 +124,6 @@ "disk": "Диск", "monthly_cost": "Ежемесячная стоимость", "location": "Размещение", - "provider": "Провайдер", "core_count": { "one": "{} ядро", "two": "{} ядра", @@ -535,4 +534,4 @@ "ignore_tls_description": "Приложение не будет проверять сертификаты TLS при подключении к серверу.", "ignore_tls": "Не проверять сертификаты TLS" } -} \ No newline at end of file +} diff --git a/assets/translations/sk.json b/assets/translations/sk.json index c79d251e..fe0a67e0 100644 --- a/assets/translations/sk.json +++ b/assets/translations/sk.json @@ -173,7 +173,6 @@ "disk": "Lokálny disk", "monthly_cost": "Mesačná cena", "location": "Lokalita", - "provider": "Poskytovateľ", "core_count": { "one": "{} jadro", "two": "{} jadrá", @@ -500,4 +499,4 @@ "reset_onboarding_description": "Resetovanie vypínača na opätovné zobrazenie obrazovky zapnutia", "cubit_statuses": "Aktuálny stav načítavania qubitov" } -} \ No newline at end of file +} diff --git a/assets/translations/sl.json b/assets/translations/sl.json index 4cbf91d1..f8250745 100644 --- a/assets/translations/sl.json +++ b/assets/translations/sl.json @@ -116,8 +116,7 @@ "ram": "Glavni pomnilnik", "disk": "Lokalni disk", "monthly_cost": "Mesečni stroški", - "location": "Lokacija", - "provider": "Ponudnik" + "location": "Lokacija" }, "ssh": { "root_subtitle": "Lastniki tukaj navedenih ključev imajo popoln dostop do podatkov in nastavitev strežnika. Dodajte samo svoje ključe.", diff --git a/assets/translations/th.json b/assets/translations/th.json index aa54a203..de7c5082 100644 --- a/assets/translations/th.json +++ b/assets/translations/th.json @@ -149,7 +149,6 @@ "ram": "หน่วยความจำ", "monthly_cost": "รายจ่ายต่อเดือน", "location": "สถานที่", - "provider": "ผู้ให้บริการ", "core_count": { "one": "{} core", "two": "{} จำนวนคอร์", @@ -293,4 +292,4 @@ "title": "เซิฟเวอร์ VPN", "subtitle": "เซิฟเวอร์ VPN ส่วนตัว" } -} \ No newline at end of file +} diff --git a/assets/translations/uk.json b/assets/translations/uk.json index d3384ca5..1c1e5a3a 100644 --- a/assets/translations/uk.json +++ b/assets/translations/uk.json @@ -210,7 +210,6 @@ "server_id": "Сервер ID", "cpu": "Процессор", "ram": "Пам'ять", - "provider": "Провайдер", "core_count": { "one": "{} ядро", "few": "{} ядра", @@ -469,4 +468,4 @@ "root_name": "Не може бути 'root'", "length_not_equal": "Довжина [], має бути {}" } -} \ No newline at end of file +} From 30fde19ce9c09eea8f4dfa6466d5e2b2dd101b8a Mon Sep 17 00:00:00 2001 From: Mithras Date: Sun, 27 Aug 2023 10:57:41 +0000 Subject: [PATCH 02/22] Translated using Weblate (Russian) Currently translated at 100.0% (475 of 475 strings) Translation: SelfPrivacy/SelfPrivacy App Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app/ru/ --- assets/translations/ru.json | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/assets/translations/ru.json b/assets/translations/ru.json index e5c200a0..fd8535f6 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -130,7 +130,9 @@ "few": "{} ядра", "many": "{} ядер", "other": "{} ядер" - } + }, + "server_provider": "Провайдер сервера", + "dns_provider": "Провайдер DNS" }, "record": { "root": "Корневой домен", @@ -199,7 +201,25 @@ "autobackup_period_set": "Период установлен", "backups_encryption_key": "Ключ шифрования", "snapshots_title": "Список снимков", - "forget_snapshot_error": "Не удалось забыть снимок" + "forget_snapshot_error": "Не удалось забыть снимок", + "backups_encryption_key_not_found": "Ключ шифрования пока не найден, повторите попытку позже.", + "forget_snapshot_alert": "Вы собираетесь удалить этот снимок. Вы уверены? Это действие обычно нельзя отменить.", + "snapshot_modal_select_strategy": "Выберите стратегию восстановления", + "snapshot_modal_download_verify_option_description": "Меньше риск, но требуется больше свободного места. Загрузка всего моментального снимка во временное хранилище, его проверка и последующая замена текущих данных.", + "snapshot_modal_service_not_found": "Это снимок службы, которой больше нет на вашем сервере. Обычно этого не должно происходить, и мы не можем выполнить автоматическое восстановление. Вы можете загрузить снимок и восстановить его вручную. Обратитесь в службу поддержки SelfPrivacy, если вам нужна помощь.", + "backups_encryption_key_subtitle": "Храните его в безопасном месте.", + "backups_encryption_key_copy": "Скопируйте ключ шифрования", + "backups_encryption_key_show": "Показать ключ шифрования", + "backups_encryption_key_description": "Этот ключ используется для шифрования резервных копий. Если вы его потеряете, то не сможете восстановить резервные копии. Храните его в надежном месте, так как он может пригодиться в случае необходимости восстановления из резервных копий вручную.", + "forget_snapshot": "Забудьте о моментальном снимке", + "snapshot_modal_heading": "Сведения о снимке", + "snapshot_service_title": "Сервис", + "snapshot_creation_time_title": "Время создания", + "snapshot_id_title": "ID снимка", + "snapshot_modal_download_verify_option_title": "Загрузите, проверьте и затем замените", + "snapshot_modal_inplace_option_title": "Заменить на месте", + "snapshot_modal_inplace_option_description": "Требуется меньше свободного места, но риск выше. При загрузке текущие данные заменяются данными моментального снимка.", + "restore_started": "Восстановление началось, проверьте текущий статус в списке заданий" }, "storage": { "card_title": "Хранилище", From 9d7bb26ab8e156aa23b9ee89ae48cbc60def37bf Mon Sep 17 00:00:00 2001 From: Mithras Date: Sun, 27 Aug 2023 11:30:33 +0000 Subject: [PATCH 03/22] Translated using Weblate (Ukrainian) Currently translated at 88.2% (419 of 475 strings) Translation: SelfPrivacy/SelfPrivacy App Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app/uk/ --- assets/translations/uk.json | 38 ++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/assets/translations/uk.json b/assets/translations/uk.json index 1c1e5a3a..7d9400a9 100644 --- a/assets/translations/uk.json +++ b/assets/translations/uk.json @@ -33,7 +33,8 @@ "delete": "Видалити", "close": "Закрити", "connect": "Підключіться", - "app_name": "SelfPrivacy" + "app_name": "SelfPrivacy", + "copied_to_clipboard": "Скопійовано в буфер обміну!" }, "locale": "ua", "application_settings": { @@ -43,7 +44,10 @@ "dark_theme_description": "Змінити тему додатка", "reset_config_description": "Скинути API ключі та root користувача.", "delete_server_title": "Видалити сервер", - "delete_server_description": "Це видалить ваш сервер. Він більше не буде доступний." + "delete_server_description": "Це видалить ваш сервер. Він більше не буде доступний.", + "system_dark_theme_title": "Системна тема за замовчуванням", + "system_dark_theme_description": "Використовуйте світлу або темну теми залежно від системних налаштувань", + "dangerous_settings": "Небезпечні налаштування" }, "ssh": { "delete_confirm_question": "Ви впевнені, що хочете видалити SSH-ключ?", @@ -223,7 +227,9 @@ "server_timezone": "Часовий пояс сервера", "timezone_search_bar": "Ім'я часового поясу або значення зсуву часу", "monthly_cost": "Щомісячна вартість", - "location": "Місцезнаходження" + "location": "Місцезнаходження", + "server_provider": "Провайдер сервера", + "dns_provider": "Провайдер DNS" }, "record": { "api": "SelfPrivacy API", @@ -250,7 +256,7 @@ "email_title": "Електронна пошта", "email_subtitle": "Записи, необхідні для безпечного обміну електронною поштою.", "update_list": "Лист оновлень", - "error_subtitle": "Нажміть сюди, щоб виправити їх", + "error_subtitle": "Натисніть тут, щоб виправити їх. При цьому також буде видалено користувацькі записи.", "services_subtitle": "Введіть \"А\" записи, необхідні для кожної служби." }, "backup": { @@ -269,7 +275,29 @@ "description": "Врятує ваш день у разі аварії: хакерська атака, видаленя серверу, тощо.", "waiting_for_rebuild": "Ви зможете створити свою першу резервну копію через кілька хвилин.", "restoring": "Відновлення з резервної копії", - "restore_alert": "Ви збираєтеся відновити з резервної копії. створеної на {}. Усі поточні дані будуть втрачені. Ви згодні?" + "restore_alert": "Ви збираєтеся відновити з резервної копії. створеної на {}. Усі поточні дані будуть втрачені. Ви згодні?", + "refetch_backups_subtitle": "Скинути кеш і запросити дані у провайдера. Може спричинити додаткові витрати.", + "reupload_key_subtitle": "Ще раз проініціалізує сховище резервних копій. Використовуйте, якщо щось зламалося.", + "create_new_select_heading": "Вибрати сервіси для копіювання", + "start": "Почати створення копій", + "service_busy": "Зараз створюються інші резервні копії", + "latest_snapshots": "Останні знімки", + "latest_snapshots_subtitle": "Останні 15 знімків", + "show_more": "Показати ще", + "autobackup_period_title": "Період автоматичного копіювання", + "autobackup_period_subtitle": "Створення копій раз на {period}", + "autobackup_period_never": "Автоматичне копіювання вимкнено", + "autobackup_period_every": "Раз у {period}", + "autobackup_period_disable": "Вимкнути автоматичні копіювання", + "autobackup_custom": "Інше", + "autobackup_custom_hint": "Введіть період у хвилинах", + "autobackup_set_period": "Встановити період", + "autobackup_period_set": "Період встановлено", + "backups_encryption_key": "Ключ шифрування", + "backups_encryption_key_subtitle": "Зберігайте його в безпечному місці.", + "backups_encryption_key_copy": "Скопіюйте ключ шифрування", + "card_subtitle": "Керуйте резервними копіями", + "select_all": "Копіювати все" }, "storage": { "card_title": "Серверне сховище", From e07394e8b1899dfa0520f8730ff4c41852a0115b Mon Sep 17 00:00:00 2001 From: NaiJi Date: Wed, 6 Sep 2023 23:31:47 -0300 Subject: [PATCH 04/22] feat: Implement visible accent when backup modal buttons are disabled --- lib/ui/pages/backups/backup_details.dart | 69 ++++++++++++++++++++---- 1 file changed, 60 insertions(+), 9 deletions(-) diff --git a/lib/ui/pages/backups/backup_details.dart b/lib/ui/pages/backups/backup_details.dart index 1d08dfac..02838eda 100644 --- a/lib/ui/pages/backups/backup_details.dart +++ b/lib/ui/pages/backups/backup_details.dart @@ -83,6 +83,9 @@ class BackupDetailsPage extends StatelessWidget { ); } + Color? getOverrideColor() => + preventActions ? Theme.of(context).colorScheme.secondary : null; + return BrandHeroScreen( heroIcon: BrandIcons.save, heroTitle: 'backup.card_title'.tr(), @@ -110,11 +113,15 @@ class BackupDetailsPage extends StatelessWidget { ), ); }, - leading: const Icon( + leading: Icon( Icons.add_circle_outline_rounded, + color: getOverrideColor(), ), title: Text( 'backup.create_new'.tr(), + style: TextStyle( + color: getOverrideColor(), + ), ), ), ListTile( @@ -138,13 +145,20 @@ class BackupDetailsPage extends StatelessWidget { ), ); }, - leading: const Icon( + leading: Icon( Icons.manage_history_outlined, + color: getOverrideColor(), ), title: Text( 'backup.autobackup_period_title'.tr(), + style: TextStyle( + color: getOverrideColor(), + ), ), subtitle: Text( + style: TextStyle( + color: getOverrideColor(), + ), autobackupPeriod != null ? 'backup.autobackup_period_subtitle'.tr( namedArgs: { @@ -175,14 +189,21 @@ class BackupDetailsPage extends StatelessWidget { ), ); }, - leading: const Icon( + leading: Icon( Icons.key_outlined, + color: getOverrideColor(), ), title: Text( 'backup.backups_encryption_key'.tr(), + style: TextStyle( + color: getOverrideColor(), + ), ), subtitle: Text( 'backup.backups_encryption_key_subtitle'.tr(), + style: TextStyle( + color: getOverrideColor(), + ), ), ), const SizedBox(height: 8), @@ -227,10 +248,16 @@ class BackupDetailsPage extends StatelessWidget { ), if (backups.isEmpty) ListTile( - leading: const Icon( + leading: Icon( Icons.error_outline, + color: getOverrideColor(), + ), + title: Text( + 'backup.no_backups'.tr(), + style: TextStyle( + color: getOverrideColor(), + ), ), - title: Text('backup.no_backups'.tr()), ), if (backups.isNotEmpty) Column( @@ -282,9 +309,15 @@ class BackupDetailsPage extends StatelessWidget { ); }, title: Text( + style: TextStyle( + color: getOverrideColor(), + ), '${MaterialLocalizations.of(context).formatShortDate(backup.time)} ${TimeOfDay.fromDateTime(backup.time).format(context)}', ), subtitle: Text( + style: TextStyle( + color: getOverrideColor(), + ), service?.displayName ?? backup.fallbackServiceName, ), leading: service != null @@ -293,12 +326,16 @@ class BackupDetailsPage extends StatelessWidget { height: 24, width: 24, colorFilter: ColorFilter.mode( - Theme.of(context).colorScheme.onBackground, + getOverrideColor() ?? + Theme.of(context) + .colorScheme + .onBackground, BlendMode.srcIn, ), ) - : const Icon( + : Icon( Icons.question_mark_outlined, + color: getOverrideColor(), ), ); }, @@ -339,12 +376,19 @@ class BackupDetailsPage extends StatelessWidget { ListTile( title: Text( 'backup.refetch_backups'.tr(), + style: TextStyle( + color: getOverrideColor(), + ), ), subtitle: Text( 'backup.refetch_backups_subtitle'.tr(), + style: TextStyle( + color: getOverrideColor(), + ), ), - leading: const Icon( + leading: Icon( Icons.cached_outlined, + color: getOverrideColor(), ), onTap: preventActions ? null @@ -356,12 +400,19 @@ class BackupDetailsPage extends StatelessWidget { ListTile( title: Text( 'backup.reupload_key'.tr(), + style: TextStyle( + color: getOverrideColor(), + ), ), subtitle: Text( 'backup.reupload_key_subtitle'.tr(), + style: TextStyle( + color: getOverrideColor(), + ), ), - leading: const Icon( + leading: Icon( Icons.warning_amber_outlined, + color: getOverrideColor(), ), onTap: preventActions ? null From cd452d5f26b8ccc70fb39248b73ba2bccc41fd07 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Thu, 7 Sep 2023 18:04:26 -0300 Subject: [PATCH 05/22] refactor: Change getOverrideColor lambda to simple variable in backup page --- lib/ui/pages/backups/backup_details.dart | 42 ++++++++++++------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/ui/pages/backups/backup_details.dart b/lib/ui/pages/backups/backup_details.dart index 02838eda..31f07e9d 100644 --- a/lib/ui/pages/backups/backup_details.dart +++ b/lib/ui/pages/backups/backup_details.dart @@ -83,7 +83,7 @@ class BackupDetailsPage extends StatelessWidget { ); } - Color? getOverrideColor() => + final Color? overrideColor = preventActions ? Theme.of(context).colorScheme.secondary : null; return BrandHeroScreen( @@ -115,12 +115,12 @@ class BackupDetailsPage extends StatelessWidget { }, leading: Icon( Icons.add_circle_outline_rounded, - color: getOverrideColor(), + color: overrideColor, ), title: Text( 'backup.create_new'.tr(), style: TextStyle( - color: getOverrideColor(), + color: overrideColor, ), ), ), @@ -147,17 +147,17 @@ class BackupDetailsPage extends StatelessWidget { }, leading: Icon( Icons.manage_history_outlined, - color: getOverrideColor(), + color: overrideColor, ), title: Text( 'backup.autobackup_period_title'.tr(), style: TextStyle( - color: getOverrideColor(), + color: overrideColor, ), ), subtitle: Text( style: TextStyle( - color: getOverrideColor(), + color: overrideColor, ), autobackupPeriod != null ? 'backup.autobackup_period_subtitle'.tr( @@ -191,18 +191,18 @@ class BackupDetailsPage extends StatelessWidget { }, leading: Icon( Icons.key_outlined, - color: getOverrideColor(), + color: overrideColor, ), title: Text( 'backup.backups_encryption_key'.tr(), style: TextStyle( - color: getOverrideColor(), + color: overrideColor, ), ), subtitle: Text( 'backup.backups_encryption_key_subtitle'.tr(), style: TextStyle( - color: getOverrideColor(), + color: overrideColor, ), ), ), @@ -250,12 +250,12 @@ class BackupDetailsPage extends StatelessWidget { ListTile( leading: Icon( Icons.error_outline, - color: getOverrideColor(), + color: overrideColor, ), title: Text( 'backup.no_backups'.tr(), style: TextStyle( - color: getOverrideColor(), + color: overrideColor, ), ), ), @@ -310,13 +310,13 @@ class BackupDetailsPage extends StatelessWidget { }, title: Text( style: TextStyle( - color: getOverrideColor(), + color: overrideColor, ), '${MaterialLocalizations.of(context).formatShortDate(backup.time)} ${TimeOfDay.fromDateTime(backup.time).format(context)}', ), subtitle: Text( style: TextStyle( - color: getOverrideColor(), + color: overrideColor, ), service?.displayName ?? backup.fallbackServiceName, ), @@ -326,7 +326,7 @@ class BackupDetailsPage extends StatelessWidget { height: 24, width: 24, colorFilter: ColorFilter.mode( - getOverrideColor() ?? + overrideColor ?? Theme.of(context) .colorScheme .onBackground, @@ -335,7 +335,7 @@ class BackupDetailsPage extends StatelessWidget { ) : Icon( Icons.question_mark_outlined, - color: getOverrideColor(), + color: overrideColor, ), ); }, @@ -377,18 +377,18 @@ class BackupDetailsPage extends StatelessWidget { title: Text( 'backup.refetch_backups'.tr(), style: TextStyle( - color: getOverrideColor(), + color: overrideColor, ), ), subtitle: Text( 'backup.refetch_backups_subtitle'.tr(), style: TextStyle( - color: getOverrideColor(), + color: overrideColor, ), ), leading: Icon( Icons.cached_outlined, - color: getOverrideColor(), + color: overrideColor, ), onTap: preventActions ? null @@ -401,18 +401,18 @@ class BackupDetailsPage extends StatelessWidget { title: Text( 'backup.reupload_key'.tr(), style: TextStyle( - color: getOverrideColor(), + color: overrideColor, ), ), subtitle: Text( 'backup.reupload_key_subtitle'.tr(), style: TextStyle( - color: getOverrideColor(), + color: overrideColor, ), ), leading: Icon( Icons.warning_amber_outlined, - color: getOverrideColor(), + color: overrideColor, ), onTap: preventActions ? null From 14dbdbbc737003f2d01803c8e8b33afd21d5d571 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Fri, 8 Sep 2023 02:54:28 -0300 Subject: [PATCH 06/22] feat: Implement dialogue to choose an domain from several during installation --- .../initializing/domain_setup_cubit.dart | 11 ++--- .../server_installation_repository.dart | 2 +- .../dns_providers/digital_ocean_dns.dart | 11 +++-- .../setup/initializing/initializing.dart | 41 ++++++++++++++++--- 4 files changed, 50 insertions(+), 15 deletions(-) diff --git a/lib/logic/cubit/forms/setup/initializing/domain_setup_cubit.dart b/lib/logic/cubit/forms/setup/initializing/domain_setup_cubit.dart index f4597439..bccbc551 100644 --- a/lib/logic/cubit/forms/setup/initializing/domain_setup_cubit.dart +++ b/lib/logic/cubit/forms/setup/initializing/domain_setup_cubit.dart @@ -18,13 +18,11 @@ class DomainSetupCubit extends Cubit { } else if (result.data.length == 1) { emit(Loaded(result.data.first)); } else { - emit(MoreThenOne()); + emit(MoreThenOne(result.data)); } } - Future saveDomain() async { - assert(state is Loaded, 'wrong state'); - final String domainName = (state as Loaded).domain; + Future saveDomain(final String domainName) async { emit(Loading(LoadingTypes.saving)); final dnsProvider = ProvidersController.currentDnsProvider!; @@ -45,7 +43,10 @@ class Initial extends DomainSetupState {} class Empty extends DomainSetupState {} -class MoreThenOne extends DomainSetupState {} +class MoreThenOne extends DomainSetupState { + MoreThenOne(this.domains); + final List domains; +} class Loading extends DomainSetupState { Loading(this.type); diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index 4018c7b9..30bccfd5 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -209,7 +209,7 @@ class ServerInstallationRepository { return false; } - return domain == domainResult.data[0]; + return domainResult.data.contains(domain); } Future> isDnsAddressesMatch( diff --git a/lib/logic/providers/dns_providers/digital_ocean_dns.dart b/lib/logic/providers/dns_providers/digital_ocean_dns.dart index 4fac4b65..f4dd23bd 100644 --- a/lib/logic/providers/dns_providers/digital_ocean_dns.dart +++ b/lib/logic/providers/dns_providers/digital_ocean_dns.dart @@ -59,10 +59,13 @@ class DigitalOceanDnsProvider extends DnsProvider { } domains = result.data - .map( - (final el) => el.name, - ) - .toList(); + .map( + (final el) => el.name, + ) + .toList() + + + /// TODO: OH MY GOD DON"T YOU DARE NOT REMOVING IT NAIJI NAIJIIII DON'T FORGET PLEASE I BET YOU + ['stub.test']; return GenericResult( success: true, diff --git a/lib/ui/pages/setup/initializing/initializing.dart b/lib/ui/pages/setup/initializing/initializing.dart index 4e9b1a78..db6f9af2 100644 --- a/lib/ui/pages/setup/initializing/initializing.dart +++ b/lib/ui/pages/setup/initializing/initializing.dart @@ -354,9 +354,39 @@ class InitializingPage extends StatelessWidget { style: Theme.of(context).textTheme.bodyMedium, ), if (state is MoreThenOne) - Text( - 'initializing.found_more_domains'.tr(), - style: Theme.of(context).textTheme.bodyMedium, + ...state.domains.map( + (final domain) => Column( + children: [ + SizedBox( + width: double.infinity, + child: Card( + clipBehavior: Clip.antiAlias, + child: InkResponse( + highlightShape: BoxShape.rectangle, + onTap: () => context + .read() + .saveDomain(domain), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + domain, + style: Theme.of(context) + .textTheme + .headlineMedium, + ), + ], + ), + ), + ), + ), + ), + const SizedBox(height: 8), + ], + ), ), if (state is Loaded) ...[ Row( @@ -401,8 +431,9 @@ class InitializingPage extends StatelessWidget { if (state is Loaded) ...[ const SizedBox(height: 32), BrandButton.filled( - onPressed: () => - context.read().saveDomain(), + onPressed: () => context + .read() + .saveDomain(state.domain), text: 'initializing.save_domain'.tr(), ), ], From afa83f0d29a3a971a6025d8eb6b78d5982d1c2ac Mon Sep 17 00:00:00 2001 From: NaiJi Date: Fri, 8 Sep 2023 03:08:02 -0300 Subject: [PATCH 07/22] chore: I embarrassed myself... --- .../providers/dns_providers/digital_ocean_dns.dart | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/logic/providers/dns_providers/digital_ocean_dns.dart b/lib/logic/providers/dns_providers/digital_ocean_dns.dart index f4dd23bd..4fac4b65 100644 --- a/lib/logic/providers/dns_providers/digital_ocean_dns.dart +++ b/lib/logic/providers/dns_providers/digital_ocean_dns.dart @@ -59,13 +59,10 @@ class DigitalOceanDnsProvider extends DnsProvider { } domains = result.data - .map( - (final el) => el.name, - ) - .toList() + - - /// TODO: OH MY GOD DON"T YOU DARE NOT REMOVING IT NAIJI NAIJIIII DON'T FORGET PLEASE I BET YOU - ['stub.test']; + .map( + (final el) => el.name, + ) + .toList(); return GenericResult( success: true, From 062fa725c43b316b54d014bbf85e1466449af71d Mon Sep 17 00:00:00 2001 From: Inex Code Date: Fri, 8 Sep 2023 09:38:08 +0300 Subject: [PATCH 08/22] fix(ui): DNS API key support page was using old code --- lib/ui/components/drawers/support_drawer.dart | 4 ++-- .../initializing/dns_provider_picker.dart | 21 ++++--------------- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/lib/ui/components/drawers/support_drawer.dart b/lib/ui/components/drawers/support_drawer.dart index 7b4c5c2b..01fc292d 100644 --- a/lib/ui/components/drawers/support_drawer.dart +++ b/lib/ui/components/drawers/support_drawer.dart @@ -16,8 +16,8 @@ class SupportDrawer extends StatelessWidget { return Drawer( width: 440, child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(8.0), + child: SafeArea( + minimum: const EdgeInsets.all(8.0), child: Column( children: [ Row( diff --git a/lib/ui/pages/setup/initializing/dns_provider_picker.dart b/lib/ui/pages/setup/initializing/dns_provider_picker.dart index 772c093e..69560f5c 100644 --- a/lib/ui/pages/setup/initializing/dns_provider_picker.dart +++ b/lib/ui/pages/setup/initializing/dns_provider_picker.dart @@ -2,11 +2,10 @@ import 'package:cubit_form/cubit_form.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/setup/initializing/dns_provider_form_cubit.dart'; +import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart'; -import 'package:selfprivacy/ui/components/brand_md/brand_md.dart'; import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; import 'package:selfprivacy/ui/components/buttons/outlined_button.dart'; import 'package:selfprivacy/ui/components/cards/outlined_card.dart'; @@ -125,22 +124,10 @@ class ProviderInputDataPage extends StatelessWidget { const SizedBox(height: 10), BrandOutlinedButton( child: Text('initializing.how'.tr()), - onPressed: () => showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.transparent, - builder: (final BuildContext context) => Padding( - padding: paddingH15V0, - child: ListView( - padding: const EdgeInsets.symmetric(vertical: 16), - children: [ - BrandMarkdown( - fileName: providerInfo.pathToHow, - ), - ], + onPressed: () => context.read().showArticle( + article: providerInfo.pathToHow, + context: context, ), - ), - ), ), ], ); From d5fa841747cf229fa82f82263299d91f3f40add5 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Fri, 8 Sep 2023 09:57:57 +0300 Subject: [PATCH 09/22] chore: Remove unused 'cloudflare_api_token' translation string --- assets/translations/az.json | 3 +-- assets/translations/be.json | 3 +-- assets/translations/cs.json | 3 +-- assets/translations/de.json | 3 +-- assets/translations/en.json | 1 - assets/translations/pl.json | 3 +-- assets/translations/ru.json | 3 +-- assets/translations/sk.json | 3 +-- assets/translations/uk.json | 3 +-- 9 files changed, 8 insertions(+), 17 deletions(-) diff --git a/assets/translations/az.json b/assets/translations/az.json index aae7107a..14551e57 100644 --- a/assets/translations/az.json +++ b/assets/translations/az.json @@ -300,7 +300,6 @@ "manage_domain_dns": "Domeninizin DNS-ni idarə etmək üçün", "use_this_domain": "Biz bu domendən istifadə edirik?", "use_this_domain_text": "Göstərdiyiniz token bu domen üzərində nəzarəti təmin edir", - "cloudflare_api_token": "CloudFlare API Açarı", "connect_backblaze_storage": "Backblaze bulud yaddaşınızı birləşdirin", "no_connected_domains": "Hazırda heç bir bağlı domen yoxdur", "loading_domain_list": "Domenlərin siyahısı yüklənir", @@ -500,4 +499,4 @@ "reset_onboarding_description": "Enerji ekranını yenidən göstərmək üçün güc açarının sıfırlanması", "cubit_statuses": "Yükləmə kubitlərinin cari vəziyyəti" } -} \ No newline at end of file +} diff --git a/assets/translations/be.json b/assets/translations/be.json index c6dbf7e9..453ba2d9 100644 --- a/assets/translations/be.json +++ b/assets/translations/be.json @@ -32,7 +32,6 @@ "manage_domain_dns": "Для кіравання DNS вашага дамена", "use_this_domain": "Ужываем гэты дамен?", "use_this_domain_text": "Указаны вамі токен дае кантроль над гэтым даменам", - "cloudflare_api_token": "API ключ DNS правайдэра", "connect_backblaze_storage": "Падлучыце хмарнае сховішча Backblaze", "no_connected_domains": "У дадзены момант падлучаных даменаў няма", "loading_domain_list": "Загружаем спіс даменаў", @@ -510,4 +509,4 @@ "support": { "title": "Падтрымка SelfPrivacy" } -} \ No newline at end of file +} diff --git a/assets/translations/cs.json b/assets/translations/cs.json index 5186b74e..edb3c13f 100644 --- a/assets/translations/cs.json +++ b/assets/translations/cs.json @@ -144,7 +144,6 @@ "found_more_domains": "Nalezeno více než jedna doména. V zájmu vlastní bezpečnosti vás prosíme o odstranění nepotřebných domén", "server_created": "Vytvořený server. Probíhá kontrola DNS a spouštění serveru…", "choose_server_type_notice": "Důležité je zaměřit se na procesor a paměť RAM. Data vašich služeb budou uložena na připojeném svazku, který lze snadno rozšířit a za který se platí zvlášť.", - "cloudflare_api_token": "Klíč API poskytovatele DNS", "connect_backblaze_storage": "Připojení úložiště Backblaze", "save_domain": "Uložit doménu", "final": "Závěrečný krok", @@ -510,4 +509,4 @@ "ignore_tls": "Nekontrolujte certifikáty TLS", "ignore_tls_description": "Aplikace nebude při připojování k serveru ověřovat certifikáty TLS." } -} \ No newline at end of file +} diff --git a/assets/translations/de.json b/assets/translations/de.json index 86cc3518..f5505d8e 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -278,7 +278,6 @@ "manage_domain_dns": "Zum Verwalten des DNS Ihrer Domain", "use_this_domain": "Diese Domäne verwenden?", "use_this_domain_text": "Das von Ihnen bereitgestellte Token gewährt Zugriff auf die folgende Domäne", - "cloudflare_api_token": "API-Schlüssel des DNS-Anbieters", "connect_backblaze_storage": "Backblaze-Speicher verbinden", "no_connected_domains": "Derzeit keine verbundenen Domains", "loading_domain_list": "Domänenliste wird geladen", @@ -510,4 +509,4 @@ "ignore_tls": "Überprüfen Sie keine TLS-Zertifikate", "ignore_tls_description": "Die Anwendung validiert TLS-Zertifikate nicht, wenn sie eine Verbindung zum Server herstellt." } -} \ No newline at end of file +} diff --git a/assets/translations/en.json b/assets/translations/en.json index 4ba733ac..cb2ccc3b 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -370,7 +370,6 @@ "manage_domain_dns": "To manage your domain's DNS", "use_this_domain": "Use this domain?", "use_this_domain_text": "The token you provided gives access to the following domain", - "cloudflare_api_token": "DNS Provider API Token", "connect_backblaze_storage": "Connect Backblaze storage", "no_connected_domains": "No connected domains at the moment", "loading_domain_list": "Loading domain list", diff --git a/assets/translations/pl.json b/assets/translations/pl.json index 5a90cc86..45c48293 100644 --- a/assets/translations/pl.json +++ b/assets/translations/pl.json @@ -321,7 +321,6 @@ "choose_server_type_payment_per_month": "{} miesięcznie", "no_server_types_found": "Nie znaleziono dostępnych typów serwerów! Proszę upewnić się, że masz dostęp do dostawcy serwera...", "use_this_domain": "Kto używa ten domen?", - "cloudflare_api_token": "Klucz API dostawcy DNS", "connect_backblaze_storage": "Dodajcie Blackblaze", "no_connected_domains": "Niema podłączonych domenów", "what": "Co to znaczy?", @@ -509,4 +508,4 @@ "cubit_statuses": "Aktualny stan qubitów ładujących", "ignore_tls": "Używane podczas konfigurowania nowego serwera." } -} \ No newline at end of file +} diff --git a/assets/translations/ru.json b/assets/translations/ru.json index d5c27d86..052b76c9 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -350,7 +350,6 @@ "manage_domain_dns": "Для управления DNS вашего домена", "use_this_domain": "Используем этот домен?", "use_this_domain_text": "Указанный вами токен даёт контроль над этим доменом", - "cloudflare_api_token": "API ключ DNS провайдера", "connect_backblaze_storage": "Подключите облачное хранилище Backblaze", "no_connected_domains": "На данный момент подлюченных доменов нет", "loading_domain_list": "Загружаем список доменов", @@ -540,4 +539,4 @@ "ignore_tls_description": "Приложение не будет проверять сертификаты TLS при подключении к серверу.", "ignore_tls": "Не проверять сертификаты TLS" } -} \ No newline at end of file +} diff --git a/assets/translations/sk.json b/assets/translations/sk.json index c79d251e..7b447173 100644 --- a/assets/translations/sk.json +++ b/assets/translations/sk.json @@ -280,7 +280,6 @@ "enter_username_and_password": "Zadajte používateľské meno a zložité heslo", "finish": "Všetko je inicializované", "use_this_domain_text": "Token, ktorý ste poskytli, poskytuje prístup k nasledujúcej doméne", - "cloudflare_api_token": "CloudFlare API Token", "connect_backblaze_storage": "Pripojte svoje cloudové úložisko Backblaze", "no_connected_domains": "Momentálne nie sú pripojené žiadne domény", "loading_domain_list": "Načítava sa zoznam domén", @@ -500,4 +499,4 @@ "reset_onboarding_description": "Resetovanie vypínača na opätovné zobrazenie obrazovky zapnutia", "cubit_statuses": "Aktuálny stav načítavania qubitov" } -} \ No newline at end of file +} diff --git a/assets/translations/uk.json b/assets/translations/uk.json index d3384ca5..af472df4 100644 --- a/assets/translations/uk.json +++ b/assets/translations/uk.json @@ -134,7 +134,6 @@ "select_dns": "Тепер давайте оберемо DNS-провайдера", "manage_domain_dns": "Для управління DNS домену", "use_this_domain": "Скористатися цим доменом?", - "cloudflare_api_token": "CloudFlare API токен", "connect_backblaze_storage": "Підключити Backblaze сховище", "no_connected_domains": "Наразі немає пов'язаних доменів", "save_domain": "Зберегти домен", @@ -469,4 +468,4 @@ "root_name": "Не може бути 'root'", "length_not_equal": "Довжина [], має бути {}" } -} \ No newline at end of file +} From 031ad474172b1c23273f471860bb35124de3f865 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Fri, 8 Sep 2023 15:41:12 +0300 Subject: [PATCH 10/22] refactor(ui): Domain selection refresh --- assets/translations/en.json | 2 + .../setup/initializing/domain_picker.dart | 163 ++++++++++++++++++ .../setup/initializing/initializing.dart | 125 +------------- 3 files changed, 167 insertions(+), 123 deletions(-) create mode 100644 lib/ui/pages/setup/initializing/domain_picker.dart diff --git a/assets/translations/en.json b/assets/translations/en.json index cb2ccc3b..99c8d209 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -370,6 +370,8 @@ "manage_domain_dns": "To manage your domain's DNS", "use_this_domain": "Use this domain?", "use_this_domain_text": "The token you provided gives access to the following domain", + "multiple_domains_found": "Multiple domains found", + "multiple_domains_found_text": "The token you provided gives access to the following domains. Please select the one you want to use. For the security of your other domains, you should restrict this token's access to only the domain you want to use with SelfPrivacy.", "connect_backblaze_storage": "Connect Backblaze storage", "no_connected_domains": "No connected domains at the moment", "loading_domain_list": "Loading domain list", diff --git a/lib/ui/pages/setup/initializing/domain_picker.dart b/lib/ui/pages/setup/initializing/domain_picker.dart new file mode 100644 index 00000000..1d64349c --- /dev/null +++ b/lib/ui/pages/setup/initializing/domain_picker.dart @@ -0,0 +1,163 @@ +import 'package:cubit_form/cubit_form.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; +import 'package:selfprivacy/logic/cubit/forms/setup/initializing/domain_setup_cubit.dart'; +import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; +import 'package:selfprivacy/ui/components/cards/outlined_card.dart'; +import 'package:selfprivacy/ui/layouts/responsive_layout_with_infobox.dart'; + +class DomainPicker extends StatefulWidget { + const DomainPicker({ + super.key, + }); + + @override + State createState() => _DomainPickerState(); +} + +class _DomainPickerState extends State { + String? selectedDomain; + + @override + Widget build(final BuildContext context) { + final DomainSetupState state = context.watch().state; + + return ResponsiveLayoutWithInfobox( + topChild: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + (state is MoreThenOne) + ? 'initializing.multiple_domains_found'.tr() + : 'initializing.use_this_domain'.tr(), + style: Theme.of(context).textTheme.headlineSmall, + ), + const SizedBox(height: 16), + Text( + (state is MoreThenOne) + ? 'initializing.multiple_domains_found_text'.tr() + : 'initializing.use_this_domain_text'.tr(), + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + primaryColumn: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (state is Empty) + Text( + 'initializing.no_connected_domains'.tr(), + style: Theme.of(context).textTheme.bodyMedium, + ), + if (state is Loading) + Text( + state.type == LoadingTypes.loadingDomain + ? 'initializing.loading_domain_list'.tr() + : 'basis.saving'.tr(), + style: Theme.of(context).textTheme.bodyMedium, + ), + if (state is MoreThenOne) + ...state.domains.map( + (final domain) => Column( + children: [ + SizedBox( + width: double.infinity, + child: OutlinedCard( + borderColor: domain == selectedDomain + ? Theme.of(context).colorScheme.primary + : null, + borderWidth: domain == selectedDomain ? 3 : 1, + child: InkResponse( + highlightShape: BoxShape.rectangle, + onTap: () => setState(() { + selectedDomain = domain; + }), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Radio( + value: domain, + groupValue: selectedDomain, + onChanged: (final String? value) { + setState(() { + selectedDomain = value; + }); + }, + ), + Text( + domain, + style: Theme.of(context).textTheme.bodyLarge, + ), + ], + ), + ), + ), + ), + ), + const SizedBox(height: 8), + // Button to select and save domain + ], + ), + ), + if (state is MoreThenOne) + BrandButton.filled( + onPressed: (selectedDomain != null && + state.domains.contains(selectedDomain)) + ? () => context + .read() + .saveDomain(selectedDomain!) + : null, + child: Text('initializing.use_this_domain'.tr()), + ), + if (state is Loaded) ...[ + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + state.domain, + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + color: Theme.of(context).colorScheme.onBackground, + ), + textAlign: TextAlign.center, + ), + ], + ), + ], + if (state is Empty) ...[ + const SizedBox(height: 30), + BrandButton.filled( + onPressed: () => context.read().load(), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.refresh, + color: Colors.white, + ), + const SizedBox(width: 10), + Text( + 'domain.update_list'.tr(), + style: Theme.of(context).textTheme.bodyLarge, + ), + ], + ), + ), + ], + if (state is Loaded) ...[ + const SizedBox(height: 32), + BrandButton.filled( + onPressed: () => + context.read().saveDomain(state.domain), + text: 'initializing.save_domain'.tr(), + ), + ], + ], + ), + ); + } +} diff --git a/lib/ui/pages/setup/initializing/initializing.dart b/lib/ui/pages/setup/initializing/initializing.dart index db6f9af2..60088ea3 100644 --- a/lib/ui/pages/setup/initializing/initializing.dart +++ b/lib/ui/pages/setup/initializing/initializing.dart @@ -18,6 +18,7 @@ import 'package:selfprivacy/ui/components/progress_bar/progress_bar.dart'; import 'package:selfprivacy/ui/components/drawers/support_drawer.dart'; import 'package:selfprivacy/ui/layouts/responsive_layout_with_infobox.dart'; import 'package:selfprivacy/ui/pages/setup/initializing/dns_provider_picker.dart'; +import 'package:selfprivacy/ui/pages/setup/initializing/domain_picker.dart'; import 'package:selfprivacy/ui/pages/setup/initializing/server_provider_picker.dart'; import 'package:selfprivacy/ui/pages/setup/initializing/server_type_picker.dart'; import 'package:selfprivacy/ui/pages/setup/recovering/recovery_routing.dart'; @@ -319,129 +320,7 @@ class InitializingPage extends StatelessWidget { Widget _stepDomain(final ServerInstallationCubit initializingCubit) => BlocProvider( create: (final context) => DomainSetupCubit(initializingCubit)..load(), - child: Builder( - builder: (final context) { - final DomainSetupState state = - context.watch().state; - return ResponsiveLayoutWithInfobox( - topChild: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'initializing.use_this_domain'.tr(), - style: Theme.of(context).textTheme.headlineSmall, - ), - const SizedBox(height: 16), - Text( - 'initializing.use_this_domain_text'.tr(), - style: Theme.of(context).textTheme.bodyMedium, - ), - ], - ), - primaryColumn: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (state is Empty) - Text( - 'initializing.no_connected_domains'.tr(), - style: Theme.of(context).textTheme.bodyMedium, - ), - if (state is Loading) - Text( - state.type == LoadingTypes.loadingDomain - ? 'initializing.loading_domain_list'.tr() - : 'basis.saving'.tr(), - style: Theme.of(context).textTheme.bodyMedium, - ), - if (state is MoreThenOne) - ...state.domains.map( - (final domain) => Column( - children: [ - SizedBox( - width: double.infinity, - child: Card( - clipBehavior: Clip.antiAlias, - child: InkResponse( - highlightShape: BoxShape.rectangle, - onTap: () => context - .read() - .saveDomain(domain), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - domain, - style: Theme.of(context) - .textTheme - .headlineMedium, - ), - ], - ), - ), - ), - ), - ), - const SizedBox(height: 8), - ], - ), - ), - if (state is Loaded) ...[ - Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - state.domain, - style: Theme.of(context) - .textTheme - .headlineMedium - ?.copyWith( - color: - Theme.of(context).colorScheme.onBackground, - ), - textAlign: TextAlign.center, - ), - ], - ), - ], - if (state is Empty) ...[ - const SizedBox(height: 30), - BrandButton.filled( - onPressed: () => context.read().load(), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon( - Icons.refresh, - color: Colors.white, - ), - const SizedBox(width: 10), - Text( - 'domain.update_list'.tr(), - style: Theme.of(context).textTheme.bodyLarge, - ), - ], - ), - ), - ], - if (state is Loaded) ...[ - const SizedBox(height: 32), - BrandButton.filled( - onPressed: () => context - .read() - .saveDomain(state.domain), - text: 'initializing.save_domain'.tr(), - ), - ], - ], - ), - ); - }, - ), + child: DomainPicker(), ); Widget _stepUser(final ServerInstallationCubit initializingCubit) => From aac4b2773b965749b370a4eb446c460fcc3e65f3 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Sat, 9 Sep 2023 10:22:43 +0300 Subject: [PATCH 11/22] feat(backups): Show the snapshot creation reason --- assets/translations/en.json | 9 +- .../graphql_maps/schema/backups.graphql | 3 +- .../graphql_maps/schema/backups.graphql.dart | 27 +++ .../graphql_maps/schema/schema.graphql | 25 +++ .../graphql_maps/schema/schema.graphql.dart | 207 ++++++++++++++++++ lib/logic/models/backup.dart | 12 + lib/ui/pages/backups/snapshot_modal.dart | 12 + 7 files changed, 293 insertions(+), 2 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index 99c8d209..1885fd73 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -220,7 +220,14 @@ "snapshot_modal_inplace_option_title": "Replace in place", "snapshot_modal_inplace_option_description": "Less free space needed, but more risk. Replaces current data with the snapshot data during the download.", "snapshot_modal_service_not_found": "This is a snapshot of a service you don't have on your server anymore. Usually this shouldn't happen, and we cannot do the automatic restore. You can still download the snapshot and restore it manually. Contact SelfPrivacy support if you need help.", - "restore_started": "Restore started, check the jobs list for the current status" + "restore_started": "Restore started, check the jobs list for the current status", + "snapshot_reason_title": "Creation reason", + "snapshot_reasons": { + "auto": "Created automatically", + "explicit": "Created by your explicit request", + "pre_restore": "Created as a precaution before risky restore", + "unknown": "Unknown" + } }, "storage": { "card_title": "Server Storage", diff --git a/lib/logic/api_maps/graphql_maps/schema/backups.graphql b/lib/logic/api_maps/graphql_maps/schema/backups.graphql index 9b60564c..410b7343 100644 --- a/lib/logic/api_maps/graphql_maps/schema/backups.graphql +++ b/lib/logic/api_maps/graphql_maps/schema/backups.graphql @@ -20,6 +20,7 @@ query AllBackupSnapshots { displayName id } + reason } } } @@ -98,4 +99,4 @@ mutation ForgetSnapshot($snapshotId: String!) { ...basicMutationReturnFields } } -} \ No newline at end of file +} diff --git a/lib/logic/api_maps/graphql_maps/schema/backups.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/backups.graphql.dart index 7a814aac..9dcd9e91 100644 --- a/lib/logic/api_maps/graphql_maps/schema/backups.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/backups.graphql.dart @@ -1486,6 +1486,13 @@ const documentNodeQueryAllBackupSnapshots = DocumentNode(definitions: [ ), ]), ), + FieldNode( + name: NameNode(value: 'reason'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), FieldNode( name: NameNode(value: '__typename'), alias: null, @@ -1801,6 +1808,7 @@ class Query$AllBackupSnapshots$backup$allSnapshots { required this.id, required this.createdAt, required this.service, + required this.reason, this.$__typename = 'SnapshotInfo', }); @@ -1809,12 +1817,14 @@ class Query$AllBackupSnapshots$backup$allSnapshots { final l$id = json['id']; final l$createdAt = json['createdAt']; final l$service = json['service']; + final l$reason = json['reason']; final l$$__typename = json['__typename']; return Query$AllBackupSnapshots$backup$allSnapshots( id: (l$id as String), createdAt: dateTimeFromJson(l$createdAt), service: Query$AllBackupSnapshots$backup$allSnapshots$service.fromJson( (l$service as Map)), + reason: fromJson$Enum$BackupReason((l$reason as String)), $__typename: (l$$__typename as String), ); } @@ -1825,6 +1835,8 @@ class Query$AllBackupSnapshots$backup$allSnapshots { final Query$AllBackupSnapshots$backup$allSnapshots$service service; + final Enum$BackupReason reason; + final String $__typename; Map toJson() { @@ -1835,6 +1847,8 @@ class Query$AllBackupSnapshots$backup$allSnapshots { _resultData['createdAt'] = dateTimeToJson(l$createdAt); final l$service = service; _resultData['service'] = l$service.toJson(); + final l$reason = reason; + _resultData['reason'] = toJson$Enum$BackupReason(l$reason); final l$$__typename = $__typename; _resultData['__typename'] = l$$__typename; return _resultData; @@ -1845,11 +1859,13 @@ class Query$AllBackupSnapshots$backup$allSnapshots { final l$id = id; final l$createdAt = createdAt; final l$service = service; + final l$reason = reason; final l$$__typename = $__typename; return Object.hashAll([ l$id, l$createdAt, l$service, + l$reason, l$$__typename, ]); } @@ -1878,6 +1894,11 @@ class Query$AllBackupSnapshots$backup$allSnapshots { if (l$service != lOther$service) { return false; } + final l$reason = reason; + final lOther$reason = other.reason; + if (l$reason != lOther$reason) { + return false; + } final l$$__typename = $__typename; final lOther$$__typename = other.$__typename; if (l$$__typename != lOther$$__typename) { @@ -1910,6 +1931,7 @@ abstract class CopyWith$Query$AllBackupSnapshots$backup$allSnapshots { String? id, DateTime? createdAt, Query$AllBackupSnapshots$backup$allSnapshots$service? service, + Enum$BackupReason? reason, String? $__typename, }); CopyWith$Query$AllBackupSnapshots$backup$allSnapshots$service @@ -1933,6 +1955,7 @@ class _CopyWithImpl$Query$AllBackupSnapshots$backup$allSnapshots Object? id = _undefined, Object? createdAt = _undefined, Object? service = _undefined, + Object? reason = _undefined, Object? $__typename = _undefined, }) => _then(Query$AllBackupSnapshots$backup$allSnapshots( @@ -1943,6 +1966,9 @@ class _CopyWithImpl$Query$AllBackupSnapshots$backup$allSnapshots service: service == _undefined || service == null ? _instance.service : (service as Query$AllBackupSnapshots$backup$allSnapshots$service), + reason: reason == _undefined || reason == null + ? _instance.reason + : (reason as Enum$BackupReason), $__typename: $__typename == _undefined || $__typename == null ? _instance.$__typename : ($__typename as String), @@ -1965,6 +1991,7 @@ class _CopyWithStubImpl$Query$AllBackupSnapshots$backup$allSnapshots String? id, DateTime? createdAt, Query$AllBackupSnapshots$backup$allSnapshots$service? service, + Enum$BackupReason? reason, String? $__typename, }) => _res; diff --git a/lib/logic/api_maps/graphql_maps/schema/schema.graphql b/lib/logic/api_maps/graphql_maps/schema/schema.graphql index 368c2b90..cb154193 100644 --- a/lib/logic/api_maps/graphql_maps/schema/schema.graphql +++ b/lib/logic/api_maps/graphql_maps/schema/schema.graphql @@ -75,6 +75,22 @@ type AutoUpgradeSettingsMutationReturn implements MutationReturnInterface { allowReboot: Boolean! } +type AutobackupQuotas { + last: Int! + daily: Int! + weekly: Int! + monthly: Int! + yearly: Int! +} + +input AutobackupQuotasInput { + last: Int! + daily: Int! + weekly: Int! + monthly: Int! + yearly: Int! +} + type Backup { configuration: BackupConfiguration! allSnapshots: [SnapshotInfo!]! @@ -85,6 +101,7 @@ type BackupConfiguration { encryptionKey: String! isInitialized: Boolean! autobackupPeriod: Int + autobackupQuotas: AutobackupQuotas! locationName: String locationId: String } @@ -93,6 +110,7 @@ type BackupMutations { initializeRepository(repository: InitializeRepositoryInput!): GenericBackupConfigReturn! removeRepository: GenericBackupConfigReturn! setAutobackupPeriod(period: Int = null): GenericBackupConfigReturn! + setAutobackupQuotas(quotas: AutobackupQuotasInput!): GenericBackupConfigReturn! startBackup(serviceId: String!): GenericJobMutationReturn! restoreBackup(snapshotId: String!, strategy: RestoreStrategy! = DOWNLOAD_VERIFY_OVERWRITE): GenericJobMutationReturn! forgetSnapshot(snapshotId: String!): GenericMutationReturn! @@ -106,6 +124,12 @@ enum BackupProvider { FILE } +enum BackupReason { + EXPLICIT + AUTO + PRE_RESTORE +} + """Date with time (isoformat)""" scalar DateTime @@ -326,6 +350,7 @@ type SnapshotInfo { id: String! service: Service! createdAt: DateTime! + reason: BackupReason! } input SshMutationInput { diff --git a/lib/logic/api_maps/graphql_maps/schema/schema.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/schema.graphql.dart index 8a78a6f8..538a05ff 100644 --- a/lib/logic/api_maps/graphql_maps/schema/schema.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/schema.graphql.dart @@ -141,6 +141,185 @@ class _CopyWithStubImpl$Input$AutoUpgradeSettingsInput _res; } +class Input$AutobackupQuotasInput { + factory Input$AutobackupQuotasInput({ + required int last, + required int daily, + required int weekly, + required int monthly, + required int yearly, + }) => + Input$AutobackupQuotasInput._({ + r'last': last, + r'daily': daily, + r'weekly': weekly, + r'monthly': monthly, + r'yearly': yearly, + }); + + Input$AutobackupQuotasInput._(this._$data); + + factory Input$AutobackupQuotasInput.fromJson(Map data) { + final result$data = {}; + final l$last = data['last']; + result$data['last'] = (l$last as int); + final l$daily = data['daily']; + result$data['daily'] = (l$daily as int); + final l$weekly = data['weekly']; + result$data['weekly'] = (l$weekly as int); + final l$monthly = data['monthly']; + result$data['monthly'] = (l$monthly as int); + final l$yearly = data['yearly']; + result$data['yearly'] = (l$yearly as int); + return Input$AutobackupQuotasInput._(result$data); + } + + Map _$data; + + int get last => (_$data['last'] as int); + int get daily => (_$data['daily'] as int); + int get weekly => (_$data['weekly'] as int); + int get monthly => (_$data['monthly'] as int); + int get yearly => (_$data['yearly'] as int); + Map toJson() { + final result$data = {}; + final l$last = last; + result$data['last'] = l$last; + final l$daily = daily; + result$data['daily'] = l$daily; + final l$weekly = weekly; + result$data['weekly'] = l$weekly; + final l$monthly = monthly; + result$data['monthly'] = l$monthly; + final l$yearly = yearly; + result$data['yearly'] = l$yearly; + return result$data; + } + + CopyWith$Input$AutobackupQuotasInput + get copyWith => CopyWith$Input$AutobackupQuotasInput( + this, + (i) => i, + ); + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Input$AutobackupQuotasInput) || + runtimeType != other.runtimeType) { + return false; + } + final l$last = last; + final lOther$last = other.last; + if (l$last != lOther$last) { + return false; + } + final l$daily = daily; + final lOther$daily = other.daily; + if (l$daily != lOther$daily) { + return false; + } + final l$weekly = weekly; + final lOther$weekly = other.weekly; + if (l$weekly != lOther$weekly) { + return false; + } + final l$monthly = monthly; + final lOther$monthly = other.monthly; + if (l$monthly != lOther$monthly) { + return false; + } + final l$yearly = yearly; + final lOther$yearly = other.yearly; + if (l$yearly != lOther$yearly) { + return false; + } + return true; + } + + @override + int get hashCode { + final l$last = last; + final l$daily = daily; + final l$weekly = weekly; + final l$monthly = monthly; + final l$yearly = yearly; + return Object.hashAll([ + l$last, + l$daily, + l$weekly, + l$monthly, + l$yearly, + ]); + } +} + +abstract class CopyWith$Input$AutobackupQuotasInput { + factory CopyWith$Input$AutobackupQuotasInput( + Input$AutobackupQuotasInput instance, + TRes Function(Input$AutobackupQuotasInput) then, + ) = _CopyWithImpl$Input$AutobackupQuotasInput; + + factory CopyWith$Input$AutobackupQuotasInput.stub(TRes res) = + _CopyWithStubImpl$Input$AutobackupQuotasInput; + + TRes call({ + int? last, + int? daily, + int? weekly, + int? monthly, + int? yearly, + }); +} + +class _CopyWithImpl$Input$AutobackupQuotasInput + implements CopyWith$Input$AutobackupQuotasInput { + _CopyWithImpl$Input$AutobackupQuotasInput( + this._instance, + this._then, + ); + + final Input$AutobackupQuotasInput _instance; + + final TRes Function(Input$AutobackupQuotasInput) _then; + + static const _undefined = {}; + + TRes call({ + Object? last = _undefined, + Object? daily = _undefined, + Object? weekly = _undefined, + Object? monthly = _undefined, + Object? yearly = _undefined, + }) => + _then(Input$AutobackupQuotasInput._({ + ..._instance._$data, + if (last != _undefined && last != null) 'last': (last as int), + if (daily != _undefined && daily != null) 'daily': (daily as int), + if (weekly != _undefined && weekly != null) 'weekly': (weekly as int), + if (monthly != _undefined && monthly != null) + 'monthly': (monthly as int), + if (yearly != _undefined && yearly != null) 'yearly': (yearly as int), + })); +} + +class _CopyWithStubImpl$Input$AutobackupQuotasInput + implements CopyWith$Input$AutobackupQuotasInput { + _CopyWithStubImpl$Input$AutobackupQuotasInput(this._res); + + TRes _res; + + call({ + int? last, + int? daily, + int? weekly, + int? monthly, + int? yearly, + }) => + _res; +} + class Input$InitializeRepositoryInput { factory Input$InitializeRepositoryInput({ required Enum$BackupProvider provider, @@ -1310,6 +1489,34 @@ Enum$BackupProvider fromJson$Enum$BackupProvider(String value) { } } +enum Enum$BackupReason { EXPLICIT, AUTO, PRE_RESTORE, $unknown } + +String toJson$Enum$BackupReason(Enum$BackupReason e) { + switch (e) { + case Enum$BackupReason.EXPLICIT: + return r'EXPLICIT'; + case Enum$BackupReason.AUTO: + return r'AUTO'; + case Enum$BackupReason.PRE_RESTORE: + return r'PRE_RESTORE'; + case Enum$BackupReason.$unknown: + return r'$unknown'; + } +} + +Enum$BackupReason fromJson$Enum$BackupReason(String value) { + switch (value) { + case r'EXPLICIT': + return Enum$BackupReason.EXPLICIT; + case r'AUTO': + return Enum$BackupReason.AUTO; + case r'PRE_RESTORE': + return Enum$BackupReason.PRE_RESTORE; + default: + return Enum$BackupReason.$unknown; + } +} + enum Enum$DnsProvider { CLOUDFLARE, DIGITALOCEAN, DESEC, $unknown } String toJson$Enum$DnsProvider(Enum$DnsProvider e) { diff --git a/lib/logic/models/backup.dart b/lib/logic/models/backup.dart index 2199e223..de5f9b44 100644 --- a/lib/logic/models/backup.dart +++ b/lib/logic/models/backup.dart @@ -11,6 +11,7 @@ class Backup { time: snapshot.createdAt, serviceId: snapshot.service.id, fallbackServiceName: snapshot.service.displayName, + reason: snapshot.reason, ); Backup({ @@ -18,6 +19,7 @@ class Backup { required this.id, required this.serviceId, required this.fallbackServiceName, + required this.reason, }); // Time of the backup @@ -26,6 +28,16 @@ class Backup { final String id; final String serviceId; final String fallbackServiceName; + final Enum$BackupReason reason; +} + +extension BackupReasonExtension on Enum$BackupReason { + String get displayName => switch (this) { + Enum$BackupReason.AUTO => 'backup.snapshot_reasons.auto', + Enum$BackupReason.EXPLICIT => 'backup.snapshot_reasons.explicit', + Enum$BackupReason.PRE_RESTORE => 'backup.snapshot_reasons.pre_restore', + Enum$BackupReason.$unknown => 'backup.snapshot_reasons.unknown', + }; } class BackupConfiguration { diff --git a/lib/ui/pages/backups/snapshot_modal.dart b/lib/ui/pages/backups/snapshot_modal.dart index 0f1bac3d..9d714c25 100644 --- a/lib/ui/pages/backups/snapshot_modal.dart +++ b/lib/ui/pages/backups/snapshot_modal.dart @@ -99,6 +99,18 @@ class _SnapshotModalState extends State { ), ), SnapshotIdListTile(snapshotId: widget.snapshot.id), + ListTile( + leading: Icon( + Icons.info_outline, + color: Theme.of(context).colorScheme.onSurface, + ), + title: Text( + 'backup.snapshot_reason_title'.tr(), + ), + subtitle: Text( + widget.snapshot.reason.displayName.tr(), + ), + ), if (service != null) Column( children: [ From 1b26f2cf2907ab955378804e0cdec12655a3108c Mon Sep 17 00:00:00 2001 From: Inex Code Date: Sat, 9 Sep 2023 21:13:27 +0300 Subject: [PATCH 12/22] feat(backups): Allow to change snapshot rotation settings --- assets/translations/en.json | 58 + flake.nix | 55 +- .../graphql_maps/schema/backups.graphql | 22 + .../graphql_maps/schema/backups.graphql.dart | 1161 +++++++++++++++++ .../graphql_maps/server_api/backups_api.dart | 45 + lib/logic/cubit/backups/backups_cubit.dart | 21 + lib/logic/cubit/backups/backups_state.dart | 4 + lib/logic/models/backup.dart | 47 + lib/ui/pages/backups/backup_details.dart | 33 + .../backups/change_rotation_quotas_modal.dart | 244 ++++ .../setup/initializing/initializing.dart | 2 +- lib/ui/router/router.gr.dart | 584 ++++----- 12 files changed, 1970 insertions(+), 306 deletions(-) create mode 100644 lib/ui/pages/backups/change_rotation_quotas_modal.dart diff --git a/assets/translations/en.json b/assets/translations/en.json index 1885fd73..8571b1c0 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -227,6 +227,64 @@ "explicit": "Created by your explicit request", "pre_restore": "Created as a precaution before risky restore", "unknown": "Unknown" + }, + "rotation_quotas_title": "Snapshot rotation settings", + "set_rotation_quotas": "Set new rotation quotas", + "quotas_set": "New backup rotation quotas set", + "quota_titles": { + "last": "How many latest backups to keep", + "daily": "How many daily backups to keep", + "weekly": "How many weekly backups to keep", + "monthly": "How many monthly backups to keep", + "yearly": "How many yearly backups to keep" + }, + "quota_subtitles": { + "no_effect": "This rule has no effect because another rule will keep more backups", + "last": { + "zero": "Rule is disabled", + "one": "Last {} backup will be kept regardless of its age", + "two": "Last {} backups will be kept regardless of their age", + "few": "Last {} backups will be kept regardless of their age", + "many": "Last {} backups will be kept regardless of their age", + "other": "Last {} backups will be kept regardless of their age" + }, + "last_infinite": "All backups will be kept", + "daily": { + "zero": "Rule is disabled", + "one": "Last {} daily backup will be kept", + "two": "Last {} daily backups will be kept", + "few": "Last {} daily backups will be kept", + "many": "Last {} daily backups will be kept", + "other": "Last {} daily backups will be kept" + }, + "daily_infinite": "All daily backups will be kept", + "weekly": { + "zero": "Rule is disabled", + "one": "Last {} weekly backup will be kept", + "two": "Last {} weekly backups will be kept", + "few": "Last {} weekly backups will be kept", + "many": "Last {} weekly backups will be kept", + "other": "Last {} weekly backups will be kept" + }, + "weekly_infinite": "All weekly backups will be kept", + "monthly": { + "zero": "Rule is disabled", + "one": "Last {} monthly backup will be kept", + "two": "Last {} monthly backups will be kept", + "few": "Last {} monthly backups will be kept", + "many": "Last {} monthly backups will be kept", + "other": "Last {} monthly backups will be kept" + }, + "monthly_infinite": "All monthly backups will be kept", + "yearly": { + "zero": "Rule is disabled", + "one": "Last {} yearly backup will be kept", + "two": "Last {} yearly backups will be kept", + "few": "Last {} yearly backups will be kept", + "many": "Last {} yearly backups will be kept", + "other": "Last {} yearly backups will be kept" + }, + "yearly_infinite": "All yearly backups will be kept" } }, "storage": { diff --git a/flake.nix b/flake.nix index d1895b0a..a124dc2c 100644 --- a/flake.nix +++ b/flake.nix @@ -1,7 +1,7 @@ { nixConfig.bash-prompt = "\[selfprivacy\]$ "; - inputs.nixpkgs.url = "nixpkgs/nixpkgs-unstable"; + inputs.nixpkgs.url = "nixpkgs/nixos-unstable"; inputs.flake-utils.url = "github:numtide/flake-utils"; inputs.nixgl.url = "github:guibou/nixGL"; @@ -9,19 +9,48 @@ flake-utils.lib.eachDefaultSystem (system: let pkgs = import nixpkgs { + inherit system; config.allowUnfree = true; config.android_sdk.accept_license = true; - system = "x86_64-linux"; overlays = [ nixgl.overlay ]; }; androidComposition = pkgs.androidenv.composeAndroidPackages { - toolsVersion = "26.1.1"; - platformToolsVersion = "33.0.2"; - buildToolsVersions = [ "30.0.3" ]; - platformVersions = [ "31" "30" "29" ]; + platformToolsVersion = "34.0.4"; + buildToolsVersions = [ "34.0.0" ]; + platformVersions = [ "34" "33" "32" "31" "30" ]; }; + spAndroidStudio = pkgs.symlinkJoin { + name = "spAndroidStudio"; + paths = with pkgs; [ + android-studio + flutter.unwrapped + # dart + gnumake + check + pkg-config + glibc + android-tools + jdk + git + ]; + + nativeBuildInputs = [ pkgs.makeWrapper ]; + postBuild = '' + wrapProgram $out/bin/flutter \ + --prefix ANDROID_SDK_ROOT=${androidComposition.androidsdk}/libexec/android-sdk \ + --prefix ANDROID_HOME=${androidComposition.androidsdk}/libexec/android-sdk \ + --prefix ANDROID_JAVA_HOME=${pkgs.jdk.home} + + wrapProgram $out/bin/android-studio \ + --prefix FLUTTER_SDK=${pkgs.flutter.unwrapped} \ + --prefix ANDROID_SDKz_ROOT=${androidComposition.androidsdk}/libexec/android-sdk \ + --prefix ANDROID_HOME=${androidComposition.androidsdk}/libexec/android-sdk \ + --prefix ANDROID_JAVA_HOME=${pkgs.jdk.home} + ''; + }; + buildDeps = with pkgs; [ gtk3 glib @@ -62,23 +91,23 @@ openjdk11_headless clang ]; - + releaseDerivation = pkgs.flutter.mkFlutterApp rec { pname = "selfprivacy"; version = "0.6.0"; - + vendorHash = "sha256-7cbiAyIlaz3HqEsZN/nZxaLZjseJv5CmiIHqsoGa4ZI="; - + nativeBuildInputs = [ pkgs.nixgl.auto.nixGLDefault ]; - + src = ./.; - + desktopItem = pkgs.makeDesktopItem { name = "${pname}"; exec = "@out@/bin/${pname}"; desktopName = "SelfPrivacy"; }; - + postInstall = '' rm $out/bin/$pname @@ -86,7 +115,7 @@ patchShebangs $out/bin/$pname chmod +x $out/bin/$pname wrapProgram $out/bin/$pname --set PATH ${pkgs.lib.makeBinPath [ pkgs.xdg-user-dirs ]} - + mkdir -p $out/share/applications cp $desktopItem/share/applications/*.desktop $out/share/applications substituteInPlace $out/share/applications/*.desktop --subst-var out diff --git a/lib/logic/api_maps/graphql_maps/schema/backups.graphql b/lib/logic/api_maps/graphql_maps/schema/backups.graphql index 410b7343..16930940 100644 --- a/lib/logic/api_maps/graphql_maps/schema/backups.graphql +++ b/lib/logic/api_maps/graphql_maps/schema/backups.graphql @@ -7,6 +7,13 @@ query BackupConfiguration { locationId locationName provider + autobackupQuotas { + last + daily + weekly + monthly + yearly + } } } } @@ -36,6 +43,13 @@ fragment genericBackupConfigReturn on GenericBackupConfigReturn { autobackupPeriod locationName locationId + autobackupQuotas { + last + daily + weekly + monthly + yearly + } } } @@ -66,6 +80,14 @@ mutation SetAutobackupPeriod($period: Int = null) { } } +mutation setAutobackupQuotas($quotas: AutobackupQuotasInput!) { + backup { + setAutobackupQuotas(quotas: $quotas) { + ...genericBackupConfigReturn + } + } +} + mutation RemoveRepository { backup { removeRepository { diff --git a/lib/logic/api_maps/graphql_maps/schema/backups.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/backups.graphql.dart index 9dcd9e91..1df1e8ac 100644 --- a/lib/logic/api_maps/graphql_maps/schema/backups.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/backups.graphql.dart @@ -287,6 +287,56 @@ const fragmentDefinitiongenericBackupConfigReturn = FragmentDefinitionNode( directives: [], selectionSet: null, ), + FieldNode( + name: NameNode(value: 'autobackupQuotas'), + alias: null, + arguments: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'last'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + FieldNode( + name: NameNode(value: 'daily'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + FieldNode( + name: NameNode(value: 'weekly'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + FieldNode( + name: NameNode(value: 'monthly'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + FieldNode( + name: NameNode(value: 'yearly'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + ]), + ), FieldNode( name: NameNode(value: '__typename'), alias: null, @@ -356,6 +406,7 @@ class Fragment$genericBackupConfigReturn$configuration { this.autobackupPeriod, this.locationName, this.locationId, + required this.autobackupQuotas, this.$__typename = 'BackupConfiguration', }); @@ -367,6 +418,7 @@ class Fragment$genericBackupConfigReturn$configuration { final l$autobackupPeriod = json['autobackupPeriod']; final l$locationName = json['locationName']; final l$locationId = json['locationId']; + final l$autobackupQuotas = json['autobackupQuotas']; final l$$__typename = json['__typename']; return Fragment$genericBackupConfigReturn$configuration( provider: fromJson$Enum$BackupProvider((l$provider as String)), @@ -375,6 +427,9 @@ class Fragment$genericBackupConfigReturn$configuration { autobackupPeriod: (l$autobackupPeriod as int?), locationName: (l$locationName as String?), locationId: (l$locationId as String?), + autobackupQuotas: + Fragment$genericBackupConfigReturn$configuration$autobackupQuotas + .fromJson((l$autobackupQuotas as Map)), $__typename: (l$$__typename as String), ); } @@ -391,6 +446,9 @@ class Fragment$genericBackupConfigReturn$configuration { final String? locationId; + final Fragment$genericBackupConfigReturn$configuration$autobackupQuotas + autobackupQuotas; + final String $__typename; Map toJson() { @@ -407,6 +465,8 @@ class Fragment$genericBackupConfigReturn$configuration { _resultData['locationName'] = l$locationName; final l$locationId = locationId; _resultData['locationId'] = l$locationId; + final l$autobackupQuotas = autobackupQuotas; + _resultData['autobackupQuotas'] = l$autobackupQuotas.toJson(); final l$$__typename = $__typename; _resultData['__typename'] = l$$__typename; return _resultData; @@ -420,6 +480,7 @@ class Fragment$genericBackupConfigReturn$configuration { final l$autobackupPeriod = autobackupPeriod; final l$locationName = locationName; final l$locationId = locationId; + final l$autobackupQuotas = autobackupQuotas; final l$$__typename = $__typename; return Object.hashAll([ l$provider, @@ -428,6 +489,7 @@ class Fragment$genericBackupConfigReturn$configuration { l$autobackupPeriod, l$locationName, l$locationId, + l$autobackupQuotas, l$$__typename, ]); } @@ -471,6 +533,11 @@ class Fragment$genericBackupConfigReturn$configuration { if (l$locationId != lOther$locationId) { return false; } + final l$autobackupQuotas = autobackupQuotas; + final lOther$autobackupQuotas = other.autobackupQuotas; + if (l$autobackupQuotas != lOther$autobackupQuotas) { + return false; + } final l$$__typename = $__typename; final lOther$$__typename = other.$__typename; if (l$$__typename != lOther$$__typename) { @@ -507,8 +574,12 @@ abstract class CopyWith$Fragment$genericBackupConfigReturn$configuration { int? autobackupPeriod, String? locationName, String? locationId, + Fragment$genericBackupConfigReturn$configuration$autobackupQuotas? + autobackupQuotas, String? $__typename, }); + CopyWith$Fragment$genericBackupConfigReturn$configuration$autobackupQuotas< + TRes> get autobackupQuotas; } class _CopyWithImpl$Fragment$genericBackupConfigReturn$configuration @@ -531,6 +602,7 @@ class _CopyWithImpl$Fragment$genericBackupConfigReturn$configuration Object? autobackupPeriod = _undefined, Object? locationName = _undefined, Object? locationId = _undefined, + Object? autobackupQuotas = _undefined, Object? $__typename = _undefined, }) => _then(Fragment$genericBackupConfigReturn$configuration( @@ -552,10 +624,21 @@ class _CopyWithImpl$Fragment$genericBackupConfigReturn$configuration locationId: locationId == _undefined ? _instance.locationId : (locationId as String?), + autobackupQuotas: autobackupQuotas == _undefined || + autobackupQuotas == null + ? _instance.autobackupQuotas + : (autobackupQuotas + as Fragment$genericBackupConfigReturn$configuration$autobackupQuotas), $__typename: $__typename == _undefined || $__typename == null ? _instance.$__typename : ($__typename as String), )); + CopyWith$Fragment$genericBackupConfigReturn$configuration$autobackupQuotas< + TRes> get autobackupQuotas { + final local$autobackupQuotas = _instance.autobackupQuotas; + return CopyWith$Fragment$genericBackupConfigReturn$configuration$autobackupQuotas( + local$autobackupQuotas, (e) => call(autobackupQuotas: e)); + } } class _CopyWithStubImpl$Fragment$genericBackupConfigReturn$configuration @@ -571,6 +654,234 @@ class _CopyWithStubImpl$Fragment$genericBackupConfigReturn$configuration int? autobackupPeriod, String? locationName, String? locationId, + Fragment$genericBackupConfigReturn$configuration$autobackupQuotas? + autobackupQuotas, + String? $__typename, + }) => + _res; + CopyWith$Fragment$genericBackupConfigReturn$configuration$autobackupQuotas< + TRes> + get autobackupQuotas => + CopyWith$Fragment$genericBackupConfigReturn$configuration$autobackupQuotas + .stub(_res); +} + +class Fragment$genericBackupConfigReturn$configuration$autobackupQuotas { + Fragment$genericBackupConfigReturn$configuration$autobackupQuotas({ + required this.last, + required this.daily, + required this.weekly, + required this.monthly, + required this.yearly, + this.$__typename = 'AutobackupQuotas', + }); + + factory Fragment$genericBackupConfigReturn$configuration$autobackupQuotas.fromJson( + Map json) { + final l$last = json['last']; + final l$daily = json['daily']; + final l$weekly = json['weekly']; + final l$monthly = json['monthly']; + final l$yearly = json['yearly']; + final l$$__typename = json['__typename']; + return Fragment$genericBackupConfigReturn$configuration$autobackupQuotas( + last: (l$last as int), + daily: (l$daily as int), + weekly: (l$weekly as int), + monthly: (l$monthly as int), + yearly: (l$yearly as int), + $__typename: (l$$__typename as String), + ); + } + + final int last; + + final int daily; + + final int weekly; + + final int monthly; + + final int yearly; + + final String $__typename; + + Map toJson() { + final _resultData = {}; + final l$last = last; + _resultData['last'] = l$last; + final l$daily = daily; + _resultData['daily'] = l$daily; + final l$weekly = weekly; + _resultData['weekly'] = l$weekly; + final l$monthly = monthly; + _resultData['monthly'] = l$monthly; + final l$yearly = yearly; + _resultData['yearly'] = l$yearly; + final l$$__typename = $__typename; + _resultData['__typename'] = l$$__typename; + return _resultData; + } + + @override + int get hashCode { + final l$last = last; + final l$daily = daily; + final l$weekly = weekly; + final l$monthly = monthly; + final l$yearly = yearly; + final l$$__typename = $__typename; + return Object.hashAll([ + l$last, + l$daily, + l$weekly, + l$monthly, + l$yearly, + l$$__typename, + ]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other + is Fragment$genericBackupConfigReturn$configuration$autobackupQuotas) || + runtimeType != other.runtimeType) { + return false; + } + final l$last = last; + final lOther$last = other.last; + if (l$last != lOther$last) { + return false; + } + final l$daily = daily; + final lOther$daily = other.daily; + if (l$daily != lOther$daily) { + return false; + } + final l$weekly = weekly; + final lOther$weekly = other.weekly; + if (l$weekly != lOther$weekly) { + return false; + } + final l$monthly = monthly; + final lOther$monthly = other.monthly; + if (l$monthly != lOther$monthly) { + return false; + } + final l$yearly = yearly; + final lOther$yearly = other.yearly; + if (l$yearly != lOther$yearly) { + return false; + } + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) { + return false; + } + return true; + } +} + +extension UtilityExtension$Fragment$genericBackupConfigReturn$configuration$autobackupQuotas + on Fragment$genericBackupConfigReturn$configuration$autobackupQuotas { + CopyWith$Fragment$genericBackupConfigReturn$configuration$autobackupQuotas< + Fragment$genericBackupConfigReturn$configuration$autobackupQuotas> + get copyWith => + CopyWith$Fragment$genericBackupConfigReturn$configuration$autobackupQuotas( + this, + (i) => i, + ); +} + +abstract class CopyWith$Fragment$genericBackupConfigReturn$configuration$autobackupQuotas< + TRes> { + factory CopyWith$Fragment$genericBackupConfigReturn$configuration$autobackupQuotas( + Fragment$genericBackupConfigReturn$configuration$autobackupQuotas instance, + TRes Function( + Fragment$genericBackupConfigReturn$configuration$autobackupQuotas) + then, + ) = _CopyWithImpl$Fragment$genericBackupConfigReturn$configuration$autobackupQuotas; + + factory CopyWith$Fragment$genericBackupConfigReturn$configuration$autobackupQuotas.stub( + TRes res) = + _CopyWithStubImpl$Fragment$genericBackupConfigReturn$configuration$autobackupQuotas; + + TRes call({ + int? last, + int? daily, + int? weekly, + int? monthly, + int? yearly, + String? $__typename, + }); +} + +class _CopyWithImpl$Fragment$genericBackupConfigReturn$configuration$autobackupQuotas< + TRes> + implements + CopyWith$Fragment$genericBackupConfigReturn$configuration$autobackupQuotas< + TRes> { + _CopyWithImpl$Fragment$genericBackupConfigReturn$configuration$autobackupQuotas( + this._instance, + this._then, + ); + + final Fragment$genericBackupConfigReturn$configuration$autobackupQuotas + _instance; + + final TRes Function( + Fragment$genericBackupConfigReturn$configuration$autobackupQuotas) _then; + + static const _undefined = {}; + + TRes call({ + Object? last = _undefined, + Object? daily = _undefined, + Object? weekly = _undefined, + Object? monthly = _undefined, + Object? yearly = _undefined, + Object? $__typename = _undefined, + }) => + _then(Fragment$genericBackupConfigReturn$configuration$autobackupQuotas( + last: + last == _undefined || last == null ? _instance.last : (last as int), + daily: daily == _undefined || daily == null + ? _instance.daily + : (daily as int), + weekly: weekly == _undefined || weekly == null + ? _instance.weekly + : (weekly as int), + monthly: monthly == _undefined || monthly == null + ? _instance.monthly + : (monthly as int), + yearly: yearly == _undefined || yearly == null + ? _instance.yearly + : (yearly as int), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String), + )); +} + +class _CopyWithStubImpl$Fragment$genericBackupConfigReturn$configuration$autobackupQuotas< + TRes> + implements + CopyWith$Fragment$genericBackupConfigReturn$configuration$autobackupQuotas< + TRes> { + _CopyWithStubImpl$Fragment$genericBackupConfigReturn$configuration$autobackupQuotas( + this._res); + + TRes _res; + + call({ + int? last, + int? daily, + int? weekly, + int? monthly, + int? yearly, String? $__typename, }) => _res; @@ -771,6 +1082,56 @@ const documentNodeQueryBackupConfiguration = DocumentNode(definitions: [ directives: [], selectionSet: null, ), + FieldNode( + name: NameNode(value: 'autobackupQuotas'), + alias: null, + arguments: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'last'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + FieldNode( + name: NameNode(value: 'daily'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + FieldNode( + name: NameNode(value: 'weekly'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + FieldNode( + name: NameNode(value: 'monthly'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + FieldNode( + name: NameNode(value: 'yearly'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + ]), + ), FieldNode( name: NameNode(value: '__typename'), alias: null, @@ -1070,6 +1431,7 @@ class Query$BackupConfiguration$backup$configuration { this.locationId, this.locationName, required this.provider, + required this.autobackupQuotas, this.$__typename = 'BackupConfiguration', }); @@ -1081,6 +1443,7 @@ class Query$BackupConfiguration$backup$configuration { final l$locationId = json['locationId']; final l$locationName = json['locationName']; final l$provider = json['provider']; + final l$autobackupQuotas = json['autobackupQuotas']; final l$$__typename = json['__typename']; return Query$BackupConfiguration$backup$configuration( autobackupPeriod: (l$autobackupPeriod as int?), @@ -1089,6 +1452,9 @@ class Query$BackupConfiguration$backup$configuration { locationId: (l$locationId as String?), locationName: (l$locationName as String?), provider: fromJson$Enum$BackupProvider((l$provider as String)), + autobackupQuotas: + Query$BackupConfiguration$backup$configuration$autobackupQuotas + .fromJson((l$autobackupQuotas as Map)), $__typename: (l$$__typename as String), ); } @@ -1105,6 +1471,9 @@ class Query$BackupConfiguration$backup$configuration { final Enum$BackupProvider provider; + final Query$BackupConfiguration$backup$configuration$autobackupQuotas + autobackupQuotas; + final String $__typename; Map toJson() { @@ -1121,6 +1490,8 @@ class Query$BackupConfiguration$backup$configuration { _resultData['locationName'] = l$locationName; final l$provider = provider; _resultData['provider'] = toJson$Enum$BackupProvider(l$provider); + final l$autobackupQuotas = autobackupQuotas; + _resultData['autobackupQuotas'] = l$autobackupQuotas.toJson(); final l$$__typename = $__typename; _resultData['__typename'] = l$$__typename; return _resultData; @@ -1134,6 +1505,7 @@ class Query$BackupConfiguration$backup$configuration { final l$locationId = locationId; final l$locationName = locationName; final l$provider = provider; + final l$autobackupQuotas = autobackupQuotas; final l$$__typename = $__typename; return Object.hashAll([ l$autobackupPeriod, @@ -1142,6 +1514,7 @@ class Query$BackupConfiguration$backup$configuration { l$locationId, l$locationName, l$provider, + l$autobackupQuotas, l$$__typename, ]); } @@ -1185,6 +1558,11 @@ class Query$BackupConfiguration$backup$configuration { if (l$provider != lOther$provider) { return false; } + final l$autobackupQuotas = autobackupQuotas; + final lOther$autobackupQuotas = other.autobackupQuotas; + if (l$autobackupQuotas != lOther$autobackupQuotas) { + return false; + } final l$$__typename = $__typename; final lOther$$__typename = other.$__typename; if (l$$__typename != lOther$$__typename) { @@ -1221,8 +1599,12 @@ abstract class CopyWith$Query$BackupConfiguration$backup$configuration { String? locationId, String? locationName, Enum$BackupProvider? provider, + Query$BackupConfiguration$backup$configuration$autobackupQuotas? + autobackupQuotas, String? $__typename, }); + CopyWith$Query$BackupConfiguration$backup$configuration$autobackupQuotas + get autobackupQuotas; } class _CopyWithImpl$Query$BackupConfiguration$backup$configuration @@ -1245,6 +1627,7 @@ class _CopyWithImpl$Query$BackupConfiguration$backup$configuration Object? locationId = _undefined, Object? locationName = _undefined, Object? provider = _undefined, + Object? autobackupQuotas = _undefined, Object? $__typename = _undefined, }) => _then(Query$BackupConfiguration$backup$configuration( @@ -1266,10 +1649,21 @@ class _CopyWithImpl$Query$BackupConfiguration$backup$configuration provider: provider == _undefined || provider == null ? _instance.provider : (provider as Enum$BackupProvider), + autobackupQuotas: autobackupQuotas == _undefined || + autobackupQuotas == null + ? _instance.autobackupQuotas + : (autobackupQuotas + as Query$BackupConfiguration$backup$configuration$autobackupQuotas), $__typename: $__typename == _undefined || $__typename == null ? _instance.$__typename : ($__typename as String), )); + CopyWith$Query$BackupConfiguration$backup$configuration$autobackupQuotas + get autobackupQuotas { + final local$autobackupQuotas = _instance.autobackupQuotas; + return CopyWith$Query$BackupConfiguration$backup$configuration$autobackupQuotas( + local$autobackupQuotas, (e) => call(autobackupQuotas: e)); + } } class _CopyWithStubImpl$Query$BackupConfiguration$backup$configuration @@ -1285,6 +1679,233 @@ class _CopyWithStubImpl$Query$BackupConfiguration$backup$configuration String? locationId, String? locationName, Enum$BackupProvider? provider, + Query$BackupConfiguration$backup$configuration$autobackupQuotas? + autobackupQuotas, + String? $__typename, + }) => + _res; + CopyWith$Query$BackupConfiguration$backup$configuration$autobackupQuotas + get autobackupQuotas => + CopyWith$Query$BackupConfiguration$backup$configuration$autobackupQuotas + .stub(_res); +} + +class Query$BackupConfiguration$backup$configuration$autobackupQuotas { + Query$BackupConfiguration$backup$configuration$autobackupQuotas({ + required this.last, + required this.daily, + required this.weekly, + required this.monthly, + required this.yearly, + this.$__typename = 'AutobackupQuotas', + }); + + factory Query$BackupConfiguration$backup$configuration$autobackupQuotas.fromJson( + Map json) { + final l$last = json['last']; + final l$daily = json['daily']; + final l$weekly = json['weekly']; + final l$monthly = json['monthly']; + final l$yearly = json['yearly']; + final l$$__typename = json['__typename']; + return Query$BackupConfiguration$backup$configuration$autobackupQuotas( + last: (l$last as int), + daily: (l$daily as int), + weekly: (l$weekly as int), + monthly: (l$monthly as int), + yearly: (l$yearly as int), + $__typename: (l$$__typename as String), + ); + } + + final int last; + + final int daily; + + final int weekly; + + final int monthly; + + final int yearly; + + final String $__typename; + + Map toJson() { + final _resultData = {}; + final l$last = last; + _resultData['last'] = l$last; + final l$daily = daily; + _resultData['daily'] = l$daily; + final l$weekly = weekly; + _resultData['weekly'] = l$weekly; + final l$monthly = monthly; + _resultData['monthly'] = l$monthly; + final l$yearly = yearly; + _resultData['yearly'] = l$yearly; + final l$$__typename = $__typename; + _resultData['__typename'] = l$$__typename; + return _resultData; + } + + @override + int get hashCode { + final l$last = last; + final l$daily = daily; + final l$weekly = weekly; + final l$monthly = monthly; + final l$yearly = yearly; + final l$$__typename = $__typename; + return Object.hashAll([ + l$last, + l$daily, + l$weekly, + l$monthly, + l$yearly, + l$$__typename, + ]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other + is Query$BackupConfiguration$backup$configuration$autobackupQuotas) || + runtimeType != other.runtimeType) { + return false; + } + final l$last = last; + final lOther$last = other.last; + if (l$last != lOther$last) { + return false; + } + final l$daily = daily; + final lOther$daily = other.daily; + if (l$daily != lOther$daily) { + return false; + } + final l$weekly = weekly; + final lOther$weekly = other.weekly; + if (l$weekly != lOther$weekly) { + return false; + } + final l$monthly = monthly; + final lOther$monthly = other.monthly; + if (l$monthly != lOther$monthly) { + return false; + } + final l$yearly = yearly; + final lOther$yearly = other.yearly; + if (l$yearly != lOther$yearly) { + return false; + } + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) { + return false; + } + return true; + } +} + +extension UtilityExtension$Query$BackupConfiguration$backup$configuration$autobackupQuotas + on Query$BackupConfiguration$backup$configuration$autobackupQuotas { + CopyWith$Query$BackupConfiguration$backup$configuration$autobackupQuotas< + Query$BackupConfiguration$backup$configuration$autobackupQuotas> + get copyWith => + CopyWith$Query$BackupConfiguration$backup$configuration$autobackupQuotas( + this, + (i) => i, + ); +} + +abstract class CopyWith$Query$BackupConfiguration$backup$configuration$autobackupQuotas< + TRes> { + factory CopyWith$Query$BackupConfiguration$backup$configuration$autobackupQuotas( + Query$BackupConfiguration$backup$configuration$autobackupQuotas instance, + TRes Function( + Query$BackupConfiguration$backup$configuration$autobackupQuotas) + then, + ) = _CopyWithImpl$Query$BackupConfiguration$backup$configuration$autobackupQuotas; + + factory CopyWith$Query$BackupConfiguration$backup$configuration$autobackupQuotas.stub( + TRes res) = + _CopyWithStubImpl$Query$BackupConfiguration$backup$configuration$autobackupQuotas; + + TRes call({ + int? last, + int? daily, + int? weekly, + int? monthly, + int? yearly, + String? $__typename, + }); +} + +class _CopyWithImpl$Query$BackupConfiguration$backup$configuration$autobackupQuotas< + TRes> + implements + CopyWith$Query$BackupConfiguration$backup$configuration$autobackupQuotas< + TRes> { + _CopyWithImpl$Query$BackupConfiguration$backup$configuration$autobackupQuotas( + this._instance, + this._then, + ); + + final Query$BackupConfiguration$backup$configuration$autobackupQuotas + _instance; + + final TRes Function( + Query$BackupConfiguration$backup$configuration$autobackupQuotas) _then; + + static const _undefined = {}; + + TRes call({ + Object? last = _undefined, + Object? daily = _undefined, + Object? weekly = _undefined, + Object? monthly = _undefined, + Object? yearly = _undefined, + Object? $__typename = _undefined, + }) => + _then(Query$BackupConfiguration$backup$configuration$autobackupQuotas( + last: + last == _undefined || last == null ? _instance.last : (last as int), + daily: daily == _undefined || daily == null + ? _instance.daily + : (daily as int), + weekly: weekly == _undefined || weekly == null + ? _instance.weekly + : (weekly as int), + monthly: monthly == _undefined || monthly == null + ? _instance.monthly + : (monthly as int), + yearly: yearly == _undefined || yearly == null + ? _instance.yearly + : (yearly as int), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String), + )); +} + +class _CopyWithStubImpl$Query$BackupConfiguration$backup$configuration$autobackupQuotas< + TRes> + implements + CopyWith$Query$BackupConfiguration$backup$configuration$autobackupQuotas< + TRes> { + _CopyWithStubImpl$Query$BackupConfiguration$backup$configuration$autobackupQuotas( + this._res); + + TRes _res; + + call({ + int? last, + int? daily, + int? weekly, + int? monthly, + int? yearly, String? $__typename, }) => _res; @@ -4051,6 +4672,546 @@ class _CopyWithStubImpl$Mutation$SetAutobackupPeriod$backup CopyWith$Fragment$genericBackupConfigReturn.stub(_res); } +class Variables$Mutation$setAutobackupQuotas { + factory Variables$Mutation$setAutobackupQuotas( + {required Input$AutobackupQuotasInput quotas}) => + Variables$Mutation$setAutobackupQuotas._({ + r'quotas': quotas, + }); + + Variables$Mutation$setAutobackupQuotas._(this._$data); + + factory Variables$Mutation$setAutobackupQuotas.fromJson( + Map data) { + final result$data = {}; + final l$quotas = data['quotas']; + result$data['quotas'] = Input$AutobackupQuotasInput.fromJson( + (l$quotas as Map)); + return Variables$Mutation$setAutobackupQuotas._(result$data); + } + + Map _$data; + + Input$AutobackupQuotasInput get quotas => + (_$data['quotas'] as Input$AutobackupQuotasInput); + Map toJson() { + final result$data = {}; + final l$quotas = quotas; + result$data['quotas'] = l$quotas.toJson(); + return result$data; + } + + CopyWith$Variables$Mutation$setAutobackupQuotas< + Variables$Mutation$setAutobackupQuotas> + get copyWith => CopyWith$Variables$Mutation$setAutobackupQuotas( + this, + (i) => i, + ); + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Variables$Mutation$setAutobackupQuotas) || + runtimeType != other.runtimeType) { + return false; + } + final l$quotas = quotas; + final lOther$quotas = other.quotas; + if (l$quotas != lOther$quotas) { + return false; + } + return true; + } + + @override + int get hashCode { + final l$quotas = quotas; + return Object.hashAll([l$quotas]); + } +} + +abstract class CopyWith$Variables$Mutation$setAutobackupQuotas { + factory CopyWith$Variables$Mutation$setAutobackupQuotas( + Variables$Mutation$setAutobackupQuotas instance, + TRes Function(Variables$Mutation$setAutobackupQuotas) then, + ) = _CopyWithImpl$Variables$Mutation$setAutobackupQuotas; + + factory CopyWith$Variables$Mutation$setAutobackupQuotas.stub(TRes res) = + _CopyWithStubImpl$Variables$Mutation$setAutobackupQuotas; + + TRes call({Input$AutobackupQuotasInput? quotas}); +} + +class _CopyWithImpl$Variables$Mutation$setAutobackupQuotas + implements CopyWith$Variables$Mutation$setAutobackupQuotas { + _CopyWithImpl$Variables$Mutation$setAutobackupQuotas( + this._instance, + this._then, + ); + + final Variables$Mutation$setAutobackupQuotas _instance; + + final TRes Function(Variables$Mutation$setAutobackupQuotas) _then; + + static const _undefined = {}; + + TRes call({Object? quotas = _undefined}) => + _then(Variables$Mutation$setAutobackupQuotas._({ + ..._instance._$data, + if (quotas != _undefined && quotas != null) + 'quotas': (quotas as Input$AutobackupQuotasInput), + })); +} + +class _CopyWithStubImpl$Variables$Mutation$setAutobackupQuotas + implements CopyWith$Variables$Mutation$setAutobackupQuotas { + _CopyWithStubImpl$Variables$Mutation$setAutobackupQuotas(this._res); + + TRes _res; + + call({Input$AutobackupQuotasInput? quotas}) => _res; +} + +class Mutation$setAutobackupQuotas { + Mutation$setAutobackupQuotas({ + required this.backup, + this.$__typename = 'Mutation', + }); + + factory Mutation$setAutobackupQuotas.fromJson(Map json) { + final l$backup = json['backup']; + final l$$__typename = json['__typename']; + return Mutation$setAutobackupQuotas( + backup: Mutation$setAutobackupQuotas$backup.fromJson( + (l$backup as Map)), + $__typename: (l$$__typename as String), + ); + } + + final Mutation$setAutobackupQuotas$backup backup; + + final String $__typename; + + Map toJson() { + final _resultData = {}; + final l$backup = backup; + _resultData['backup'] = l$backup.toJson(); + final l$$__typename = $__typename; + _resultData['__typename'] = l$$__typename; + return _resultData; + } + + @override + int get hashCode { + final l$backup = backup; + final l$$__typename = $__typename; + return Object.hashAll([ + l$backup, + l$$__typename, + ]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Mutation$setAutobackupQuotas) || + runtimeType != other.runtimeType) { + return false; + } + final l$backup = backup; + final lOther$backup = other.backup; + if (l$backup != lOther$backup) { + return false; + } + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) { + return false; + } + return true; + } +} + +extension UtilityExtension$Mutation$setAutobackupQuotas + on Mutation$setAutobackupQuotas { + CopyWith$Mutation$setAutobackupQuotas + get copyWith => CopyWith$Mutation$setAutobackupQuotas( + this, + (i) => i, + ); +} + +abstract class CopyWith$Mutation$setAutobackupQuotas { + factory CopyWith$Mutation$setAutobackupQuotas( + Mutation$setAutobackupQuotas instance, + TRes Function(Mutation$setAutobackupQuotas) then, + ) = _CopyWithImpl$Mutation$setAutobackupQuotas; + + factory CopyWith$Mutation$setAutobackupQuotas.stub(TRes res) = + _CopyWithStubImpl$Mutation$setAutobackupQuotas; + + TRes call({ + Mutation$setAutobackupQuotas$backup? backup, + String? $__typename, + }); + CopyWith$Mutation$setAutobackupQuotas$backup get backup; +} + +class _CopyWithImpl$Mutation$setAutobackupQuotas + implements CopyWith$Mutation$setAutobackupQuotas { + _CopyWithImpl$Mutation$setAutobackupQuotas( + this._instance, + this._then, + ); + + final Mutation$setAutobackupQuotas _instance; + + final TRes Function(Mutation$setAutobackupQuotas) _then; + + static const _undefined = {}; + + TRes call({ + Object? backup = _undefined, + Object? $__typename = _undefined, + }) => + _then(Mutation$setAutobackupQuotas( + backup: backup == _undefined || backup == null + ? _instance.backup + : (backup as Mutation$setAutobackupQuotas$backup), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String), + )); + CopyWith$Mutation$setAutobackupQuotas$backup get backup { + final local$backup = _instance.backup; + return CopyWith$Mutation$setAutobackupQuotas$backup( + local$backup, (e) => call(backup: e)); + } +} + +class _CopyWithStubImpl$Mutation$setAutobackupQuotas + implements CopyWith$Mutation$setAutobackupQuotas { + _CopyWithStubImpl$Mutation$setAutobackupQuotas(this._res); + + TRes _res; + + call({ + Mutation$setAutobackupQuotas$backup? backup, + String? $__typename, + }) => + _res; + CopyWith$Mutation$setAutobackupQuotas$backup get backup => + CopyWith$Mutation$setAutobackupQuotas$backup.stub(_res); +} + +const documentNodeMutationsetAutobackupQuotas = DocumentNode(definitions: [ + OperationDefinitionNode( + type: OperationType.mutation, + name: NameNode(value: 'setAutobackupQuotas'), + variableDefinitions: [ + VariableDefinitionNode( + variable: VariableNode(name: NameNode(value: 'quotas')), + type: NamedTypeNode( + name: NameNode(value: 'AutobackupQuotasInput'), + isNonNull: true, + ), + defaultValue: DefaultValueNode(value: null), + directives: [], + ) + ], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'backup'), + alias: null, + arguments: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'setAutobackupQuotas'), + alias: null, + arguments: [ + ArgumentNode( + name: NameNode(value: 'quotas'), + value: VariableNode(name: NameNode(value: 'quotas')), + ) + ], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FragmentSpreadNode( + name: NameNode(value: 'genericBackupConfigReturn'), + directives: [], + ), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + ]), + ), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + ]), + ), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + ]), + ), + fragmentDefinitiongenericBackupConfigReturn, +]); +Mutation$setAutobackupQuotas _parserFn$Mutation$setAutobackupQuotas( + Map data) => + Mutation$setAutobackupQuotas.fromJson(data); +typedef OnMutationCompleted$Mutation$setAutobackupQuotas = FutureOr + Function( + Map?, + Mutation$setAutobackupQuotas?, +); + +class Options$Mutation$setAutobackupQuotas + extends graphql.MutationOptions { + Options$Mutation$setAutobackupQuotas({ + String? operationName, + required Variables$Mutation$setAutobackupQuotas variables, + graphql.FetchPolicy? fetchPolicy, + graphql.ErrorPolicy? errorPolicy, + graphql.CacheRereadPolicy? cacheRereadPolicy, + Object? optimisticResult, + Mutation$setAutobackupQuotas? typedOptimisticResult, + graphql.Context? context, + OnMutationCompleted$Mutation$setAutobackupQuotas? onCompleted, + graphql.OnMutationUpdate? update, + graphql.OnError? onError, + }) : onCompletedWithParsed = onCompleted, + super( + variables: variables.toJson(), + operationName: operationName, + fetchPolicy: fetchPolicy, + errorPolicy: errorPolicy, + cacheRereadPolicy: cacheRereadPolicy, + optimisticResult: optimisticResult ?? typedOptimisticResult?.toJson(), + context: context, + onCompleted: onCompleted == null + ? null + : (data) => onCompleted( + data, + data == null + ? null + : _parserFn$Mutation$setAutobackupQuotas(data), + ), + update: update, + onError: onError, + document: documentNodeMutationsetAutobackupQuotas, + parserFn: _parserFn$Mutation$setAutobackupQuotas, + ); + + final OnMutationCompleted$Mutation$setAutobackupQuotas? onCompletedWithParsed; + + @override + List get properties => [ + ...super.onCompleted == null + ? super.properties + : super.properties.where((property) => property != onCompleted), + onCompletedWithParsed, + ]; +} + +class WatchOptions$Mutation$setAutobackupQuotas + extends graphql.WatchQueryOptions { + WatchOptions$Mutation$setAutobackupQuotas({ + String? operationName, + required Variables$Mutation$setAutobackupQuotas variables, + graphql.FetchPolicy? fetchPolicy, + graphql.ErrorPolicy? errorPolicy, + graphql.CacheRereadPolicy? cacheRereadPolicy, + Object? optimisticResult, + Mutation$setAutobackupQuotas? typedOptimisticResult, + graphql.Context? context, + Duration? pollInterval, + bool? eagerlyFetchResults, + bool carryForwardDataOnException = true, + bool fetchResults = false, + }) : super( + variables: variables.toJson(), + operationName: operationName, + fetchPolicy: fetchPolicy, + errorPolicy: errorPolicy, + cacheRereadPolicy: cacheRereadPolicy, + optimisticResult: optimisticResult ?? typedOptimisticResult?.toJson(), + context: context, + document: documentNodeMutationsetAutobackupQuotas, + pollInterval: pollInterval, + eagerlyFetchResults: eagerlyFetchResults, + carryForwardDataOnException: carryForwardDataOnException, + fetchResults: fetchResults, + parserFn: _parserFn$Mutation$setAutobackupQuotas, + ); +} + +extension ClientExtension$Mutation$setAutobackupQuotas + on graphql.GraphQLClient { + Future> + mutate$setAutobackupQuotas( + Options$Mutation$setAutobackupQuotas options) async => + await this.mutate(options); + graphql.ObservableQuery + watchMutation$setAutobackupQuotas( + WatchOptions$Mutation$setAutobackupQuotas options) => + this.watchMutation(options); +} + +class Mutation$setAutobackupQuotas$backup { + Mutation$setAutobackupQuotas$backup({ + required this.setAutobackupQuotas, + this.$__typename = 'BackupMutations', + }); + + factory Mutation$setAutobackupQuotas$backup.fromJson( + Map json) { + final l$setAutobackupQuotas = json['setAutobackupQuotas']; + final l$$__typename = json['__typename']; + return Mutation$setAutobackupQuotas$backup( + setAutobackupQuotas: Fragment$genericBackupConfigReturn.fromJson( + (l$setAutobackupQuotas as Map)), + $__typename: (l$$__typename as String), + ); + } + + final Fragment$genericBackupConfigReturn setAutobackupQuotas; + + final String $__typename; + + Map toJson() { + final _resultData = {}; + final l$setAutobackupQuotas = setAutobackupQuotas; + _resultData['setAutobackupQuotas'] = l$setAutobackupQuotas.toJson(); + final l$$__typename = $__typename; + _resultData['__typename'] = l$$__typename; + return _resultData; + } + + @override + int get hashCode { + final l$setAutobackupQuotas = setAutobackupQuotas; + final l$$__typename = $__typename; + return Object.hashAll([ + l$setAutobackupQuotas, + l$$__typename, + ]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Mutation$setAutobackupQuotas$backup) || + runtimeType != other.runtimeType) { + return false; + } + final l$setAutobackupQuotas = setAutobackupQuotas; + final lOther$setAutobackupQuotas = other.setAutobackupQuotas; + if (l$setAutobackupQuotas != lOther$setAutobackupQuotas) { + return false; + } + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) { + return false; + } + return true; + } +} + +extension UtilityExtension$Mutation$setAutobackupQuotas$backup + on Mutation$setAutobackupQuotas$backup { + CopyWith$Mutation$setAutobackupQuotas$backup< + Mutation$setAutobackupQuotas$backup> + get copyWith => CopyWith$Mutation$setAutobackupQuotas$backup( + this, + (i) => i, + ); +} + +abstract class CopyWith$Mutation$setAutobackupQuotas$backup { + factory CopyWith$Mutation$setAutobackupQuotas$backup( + Mutation$setAutobackupQuotas$backup instance, + TRes Function(Mutation$setAutobackupQuotas$backup) then, + ) = _CopyWithImpl$Mutation$setAutobackupQuotas$backup; + + factory CopyWith$Mutation$setAutobackupQuotas$backup.stub(TRes res) = + _CopyWithStubImpl$Mutation$setAutobackupQuotas$backup; + + TRes call({ + Fragment$genericBackupConfigReturn? setAutobackupQuotas, + String? $__typename, + }); + CopyWith$Fragment$genericBackupConfigReturn get setAutobackupQuotas; +} + +class _CopyWithImpl$Mutation$setAutobackupQuotas$backup + implements CopyWith$Mutation$setAutobackupQuotas$backup { + _CopyWithImpl$Mutation$setAutobackupQuotas$backup( + this._instance, + this._then, + ); + + final Mutation$setAutobackupQuotas$backup _instance; + + final TRes Function(Mutation$setAutobackupQuotas$backup) _then; + + static const _undefined = {}; + + TRes call({ + Object? setAutobackupQuotas = _undefined, + Object? $__typename = _undefined, + }) => + _then(Mutation$setAutobackupQuotas$backup( + setAutobackupQuotas: + setAutobackupQuotas == _undefined || setAutobackupQuotas == null + ? _instance.setAutobackupQuotas + : (setAutobackupQuotas as Fragment$genericBackupConfigReturn), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String), + )); + CopyWith$Fragment$genericBackupConfigReturn get setAutobackupQuotas { + final local$setAutobackupQuotas = _instance.setAutobackupQuotas; + return CopyWith$Fragment$genericBackupConfigReturn( + local$setAutobackupQuotas, (e) => call(setAutobackupQuotas: e)); + } +} + +class _CopyWithStubImpl$Mutation$setAutobackupQuotas$backup + implements CopyWith$Mutation$setAutobackupQuotas$backup { + _CopyWithStubImpl$Mutation$setAutobackupQuotas$backup(this._res); + + TRes _res; + + call({ + Fragment$genericBackupConfigReturn? setAutobackupQuotas, + String? $__typename, + }) => + _res; + CopyWith$Fragment$genericBackupConfigReturn get setAutobackupQuotas => + CopyWith$Fragment$genericBackupConfigReturn.stub(_res); +} + class Mutation$RemoveRepository { Mutation$RemoveRepository({ required this.backup, diff --git a/lib/logic/api_maps/graphql_maps/server_api/backups_api.dart b/lib/logic/api_maps/graphql_maps/server_api/backups_api.dart index ed54bd53..8e77d452 100644 --- a/lib/logic/api_maps/graphql_maps/server_api/backups_api.dart +++ b/lib/logic/api_maps/graphql_maps/server_api/backups_api.dart @@ -143,6 +143,51 @@ mixin BackupsApi on GraphQLApiMap { return result; } + Future setAutobackupQuotas( + final AutobackupQuotas quotas, + ) async { + QueryResult response; + GenericResult? result; + + try { + final GraphQLClient client = await getClient(); + final variables = Variables$Mutation$setAutobackupQuotas( + quotas: Input$AutobackupQuotasInput( + last: quotas.last, + daily: quotas.daily, + weekly: quotas.weekly, + monthly: quotas.monthly, + yearly: quotas.yearly, + ), + ); + final options = + Options$Mutation$setAutobackupQuotas(variables: variables); + response = await client.mutate$setAutobackupQuotas(options); + if (response.hasException) { + final message = response.exception.toString(); + print(message); + result = GenericResult( + success: false, + data: null, + message: message, + ); + } + result = GenericResult( + success: true, + data: null, + ); + } catch (e) { + print(e); + result = GenericResult( + success: false, + data: null, + message: e.toString(), + ); + } + + return result; + } + Future removeRepository() async { try { final GraphQLClient client = await getClient(); diff --git a/lib/logic/cubit/backups/backups_cubit.dart b/lib/logic/cubit/backups/backups_cubit.dart index 89470656..ca7e0a50 100644 --- a/lib/logic/cubit/backups/backups_cubit.dart +++ b/lib/logic/cubit/backups/backups_cubit.dart @@ -36,6 +36,7 @@ class BackupsCubit extends ServerInstallationDependendCubit { backblazeBucket: bucket, isInitialized: backupConfig?.isInitialized, autobackupPeriod: backupConfig?.autobackupPeriod ?? Duration.zero, + autobackupQuotas: backupConfig?.autobackupQuotas, backups: backups, preventActions: false, refreshing: false, @@ -168,6 +169,7 @@ class BackupsCubit extends ServerInstallationDependendCubit { refreshing: false, isInitialized: backupConfig?.isInitialized ?? false, autobackupPeriod: backupConfig?.autobackupPeriod, + autobackupQuotas: backupConfig?.autobackupQuotas, ), ); if (useTimer) { @@ -227,6 +229,25 @@ class BackupsCubit extends ServerInstallationDependendCubit { await updateBackups(); } + Future setAutobackupQuotas(final AutobackupQuotas quotas) async { + emit(state.copyWith(preventActions: true)); + final result = await api.setAutobackupQuotas(quotas); + if (result.success == false) { + getIt() + .showSnackBar(result.message ?? 'Unknown error'); + emit(state.copyWith(preventActions: false)); + } else { + getIt().showSnackBar('backup.quotas_set'.tr()); + emit( + state.copyWith( + preventActions: false, + autobackupQuotas: quotas, + ), + ); + } + await updateBackups(); + } + Future forgetSnapshot(final String snapshotId) async { final result = await api.forgetSnapshot(snapshotId); if (!result.success) { diff --git a/lib/logic/cubit/backups/backups_state.dart b/lib/logic/cubit/backups/backups_state.dart index 52b9b106..887396d7 100644 --- a/lib/logic/cubit/backups/backups_state.dart +++ b/lib/logic/cubit/backups/backups_state.dart @@ -9,6 +9,7 @@ class BackupsState extends ServerInstallationDependendState { this.refreshing = true, this.autobackupPeriod, this.backblazeBucket, + this.autobackupQuotas, }); final bool isInitialized; @@ -18,6 +19,7 @@ class BackupsState extends ServerInstallationDependendState { final bool refreshing; final Duration? autobackupPeriod; final BackblazeBucket? backblazeBucket; + final AutobackupQuotas? autobackupQuotas; List serviceBackups(final String serviceId) => backups .where((final backup) => backup.serviceId == serviceId) @@ -40,6 +42,7 @@ class BackupsState extends ServerInstallationDependendState { final bool? refreshing, final Duration? autobackupPeriod, final BackblazeBucket? backblazeBucket, + final AutobackupQuotas? autobackupQuotas, }) => BackupsState( isInitialized: isInitialized ?? this.isInitialized, @@ -53,5 +56,6 @@ class BackupsState extends ServerInstallationDependendState { ? null : autobackupPeriod ?? this.autobackupPeriod, backblazeBucket: backblazeBucket ?? this.backblazeBucket, + autobackupQuotas: autobackupQuotas ?? this.autobackupQuotas, ); } diff --git a/lib/logic/models/backup.dart b/lib/logic/models/backup.dart index de5f9b44..1dcf9129 100644 --- a/lib/logic/models/backup.dart +++ b/lib/logic/models/backup.dart @@ -53,6 +53,9 @@ class BackupConfiguration { locationId: configuration.locationId, locationName: configuration.locationName, provider: BackupsProviderType.fromGraphQL(configuration.provider), + autobackupQuotas: AutobackupQuotas.fromGraphQL( + configuration.autobackupQuotas, + ), ); BackupConfiguration({ @@ -62,6 +65,7 @@ class BackupConfiguration { required this.locationId, required this.locationName, required this.provider, + required this.autobackupQuotas, }); final Duration? autobackupPeriod; @@ -70,6 +74,49 @@ class BackupConfiguration { final String? locationId; final String? locationName; final BackupsProviderType provider; + final AutobackupQuotas autobackupQuotas; +} + +class AutobackupQuotas { + AutobackupQuotas.fromGraphQL( + final Query$BackupConfiguration$backup$configuration$autobackupQuotas + autobackupQuotas, + ) : this( + last: autobackupQuotas.last, + daily: autobackupQuotas.daily, + weekly: autobackupQuotas.weekly, + monthly: autobackupQuotas.monthly, + yearly: autobackupQuotas.yearly, + ); + + AutobackupQuotas({ + required this.last, + required this.daily, + required this.weekly, + required this.monthly, + required this.yearly, + }); + + final int last; + final int daily; + final int weekly; + final int monthly; + final int yearly; + + AutobackupQuotas copyWith({ + final int? last, + final int? daily, + final int? weekly, + final int? monthly, + final int? yearly, + }) => + AutobackupQuotas( + last: last ?? this.last, + daily: daily ?? this.daily, + weekly: weekly ?? this.weekly, + monthly: monthly ?? this.monthly, + yearly: yearly ?? this.yearly, + ); } enum BackupRestoreStrategy { diff --git a/lib/ui/pages/backups/backup_details.dart b/lib/ui/pages/backups/backup_details.dart index 31f07e9d..344876b1 100644 --- a/lib/ui/pages/backups/backup_details.dart +++ b/lib/ui/pages/backups/backup_details.dart @@ -16,6 +16,7 @@ import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/helpers/modals.dart'; import 'package:selfprivacy/ui/pages/backups/change_period_modal.dart'; +import 'package:selfprivacy/ui/pages/backups/change_rotation_quotas_modal.dart'; import 'package:selfprivacy/ui/pages/backups/copy_encryption_key_modal.dart'; import 'package:selfprivacy/ui/pages/backups/create_backups_modal.dart'; import 'package:selfprivacy/ui/pages/backups/snapshot_modal.dart'; @@ -168,6 +169,38 @@ class BackupDetailsPage extends StatelessWidget { : 'backup.autobackup_period_never'.tr(), ), ), + ListTile( + onTap: preventActions + ? null + : () { + showModalBottomSheet( + useRootNavigator: true, + context: context, + isScrollControlled: true, + builder: (final BuildContext context) => + DraggableScrollableSheet( + expand: false, + maxChildSize: 0.9, + minChildSize: 0.4, + initialChildSize: 0.6, + builder: (final context, final scrollController) => + ChangeRotationQuotasModal( + scrollController: scrollController, + ), + ), + ); + }, + leading: Icon( + Icons.auto_delete_outlined, + color: overrideColor, + ), + title: Text( + 'backup.rotation_quotas_title'.tr(), + style: TextStyle( + color: overrideColor, + ), + ), + ), ListTile( onTap: preventActions ? null diff --git a/lib/ui/pages/backups/change_rotation_quotas_modal.dart b/lib/ui/pages/backups/change_rotation_quotas_modal.dart new file mode 100644 index 00000000..6d9cba8a --- /dev/null +++ b/lib/ui/pages/backups/change_rotation_quotas_modal.dart @@ -0,0 +1,244 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; +import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart'; +import 'package:selfprivacy/logic/models/backup.dart'; + +class ChangeRotationQuotasModal extends StatefulWidget { + const ChangeRotationQuotasModal({ + required this.scrollController, + super.key, + }); + + final ScrollController scrollController; + + @override + State createState() => + _ChangeRotationQuotasModalState(); +} + +enum QuotaUnits { + last, + daily, + weekly, + monthly, + yearly, +} + +class _ChangeRotationQuotasModalState extends State { + AutobackupQuotas selectedQuotas = AutobackupQuotas( + last: 3, + daily: 7, + weekly: 4, + monthly: 6, + yearly: -1, + ); + + // Set initial period to the one currently set + @override + void initState() { + super.initState(); + selectedQuotas = + context.read().state.autobackupQuotas ?? selectedQuotas; + } + + String generateSubtitle(final int value, final QuotaUnits unit) { + switch (unit) { + case QuotaUnits.last: + return value == -1 + ? 'backup.quota_subtitles.last_infinite'.tr() + : 'backup.quota_subtitles.last'.plural(value); + case QuotaUnits.daily: + if (selectedQuotas.last == -1) { + return 'backup.quota_subtitles.no_effect'.tr(); + } + return value == -1 + ? 'backup.quota_subtitles.daily_infinite'.tr() + : 'backup.quota_subtitles.daily'.plural(value); + case QuotaUnits.weekly: + if (selectedQuotas.last == -1 || selectedQuotas.daily == -1) { + return 'backup.quota_subtitles.no_effect'.tr(); + } + return value == -1 + ? 'backup.quota_subtitles.weekly_infinite'.tr() + : 'backup.quota_subtitles.weekly'.plural(value); + case QuotaUnits.monthly: + if (selectedQuotas.last == -1 || selectedQuotas.daily == -1) { + return 'backup.quota_subtitles.no_effect'.tr(); + } + return value == -1 + ? 'backup.quota_subtitles.monthly_infinite'.tr() + : 'backup.quota_subtitles.monthly'.plural(value); + case QuotaUnits.yearly: + if (selectedQuotas.last == -1 || selectedQuotas.daily == -1) { + return 'backup.quota_subtitles.no_effect'.tr(); + } + return value == -1 + ? 'backup.quota_subtitles.yearly_infinite'.tr() + : 'backup.quota_subtitles.yearly'.plural(value); + } + } + + @override + Widget build(final BuildContext context) { + final AutobackupQuotas? initialAutobackupQuotas = + context.watch().state.autobackupQuotas; + return ListView( + controller: widget.scrollController, + padding: const EdgeInsets.all(16), + children: [ + const SizedBox(height: 16), + Text( + 'backup.rotation_quotas_title'.tr(), + style: Theme.of(context).textTheme.headlineSmall, + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + // Accordions for each quota type. When tapped allows to enter a new int value + // for the quota. + QuotaSelectionTile( + title: 'backup.quota_titles.last'.tr(), + subtitle: generateSubtitle(selectedQuotas.last, QuotaUnits.last), + value: selectedQuotas.last, + min: 1, + max: 30, + callback: (final double value) { + setState(() { + if (value == 31) { + selectedQuotas = selectedQuotas.copyWith(last: -1); + return; + } + selectedQuotas = selectedQuotas.copyWith(last: value.toInt()); + }); + }, + ), + QuotaSelectionTile( + title: 'backup.quota_titles.daily'.tr(), + subtitle: generateSubtitle(selectedQuotas.daily, QuotaUnits.daily), + value: selectedQuotas.daily, + min: 0, + max: 30, + callback: (final double value) { + setState(() { + if (value == 31) { + selectedQuotas = selectedQuotas.copyWith(daily: -1); + return; + } + selectedQuotas = selectedQuotas.copyWith(daily: value.toInt()); + }); + }, + ), + QuotaSelectionTile( + title: 'backup.quota_titles.weekly'.tr(), + subtitle: generateSubtitle(selectedQuotas.weekly, QuotaUnits.weekly), + value: selectedQuotas.weekly, + min: 0, + max: 15, + callback: (final double value) { + setState(() { + if (value == 16) { + selectedQuotas = selectedQuotas.copyWith(weekly: -1); + return; + } + selectedQuotas = selectedQuotas.copyWith(weekly: value.toInt()); + }); + }, + ), + QuotaSelectionTile( + title: 'backup.quota_titles.monthly'.tr(), + subtitle: + generateSubtitle(selectedQuotas.monthly, QuotaUnits.monthly), + value: selectedQuotas.monthly, + min: 0, + max: 24, + callback: (final double value) { + setState(() { + if (value == 25) { + selectedQuotas = selectedQuotas.copyWith(monthly: -1); + return; + } + selectedQuotas = selectedQuotas.copyWith(monthly: value.toInt()); + }); + }, + ), + QuotaSelectionTile( + title: 'backup.quota_titles.yearly'.tr(), + subtitle: generateSubtitle(selectedQuotas.yearly, QuotaUnits.yearly), + value: selectedQuotas.yearly, + min: 0, + max: 5, + callback: (final double value) { + setState(() { + if (value == 6) { + selectedQuotas = selectedQuotas.copyWith(yearly: -1); + return; + } + selectedQuotas = selectedQuotas.copyWith(yearly: value.toInt()); + }); + }, + ), + const SizedBox(height: 16), + FilledButton( + onPressed: selectedQuotas == initialAutobackupQuotas + ? null + : () { + context + .read() + .setAutobackupQuotas(selectedQuotas); + Navigator.of(context).pop(); + }, + child: Text( + 'backup.set_rotation_quotas'.tr(), + ), + ), + ], + ); + } +} + +class QuotaSelectionTile extends StatelessWidget { + const QuotaSelectionTile({ + required this.title, + required this.subtitle, + required this.value, + required this.min, + required this.max, + required this.callback, + super.key, + }); + + final String title; + final String subtitle; + final int value; + final int min; + final int max; + final void Function(double) callback; + + @override + Widget build(final BuildContext context) => ExpansionTile( + title: Text(title), + subtitle: Text(subtitle), + trailing: Text( + value == -1 ? '∞' : value.toString(), + style: Theme.of(context).textTheme.headlineMedium, + ), + children: [ + // Discrete slider to select the new value + if (value >= -1 && value <= max) + Slider( + value: value == -1 ? max + 1 : value.toDouble(), + min: min.toDouble(), + max: (max + 1).toDouble(), + divisions: max - min + 1, + label: value == -1 ? '∞' : value.toString(), + onChanged: callback, + ), + if (value < -1 || value > max) + Text( + 'Manually set to $value', + style: Theme.of(context).textTheme.headlineSmall, + ), + ], + ); +} diff --git a/lib/ui/pages/setup/initializing/initializing.dart b/lib/ui/pages/setup/initializing/initializing.dart index 60088ea3..a681c07a 100644 --- a/lib/ui/pages/setup/initializing/initializing.dart +++ b/lib/ui/pages/setup/initializing/initializing.dart @@ -320,7 +320,7 @@ class InitializingPage extends StatelessWidget { Widget _stepDomain(final ServerInstallationCubit initializingCubit) => BlocProvider( create: (final context) => DomainSetupCubit(initializingCubit)..load(), - child: DomainPicker(), + child: const DomainPicker(), ); Widget _stepUser(final ServerInstallationCubit initializingCubit) => diff --git a/lib/ui/router/router.gr.dart b/lib/ui/router/router.gr.dart index dfc41363..a9d401f3 100644 --- a/lib/ui/router/router.gr.dart +++ b/lib/ui/router/router.gr.dart @@ -15,18 +15,6 @@ abstract class _$RootRouter extends RootStackRouter { @override final Map pagesMap = { - OnboardingRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const OnboardingPage(), - ); - }, - BackupDetailsRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const BackupDetailsPage(), - ); - }, BackupsListRoute.name: (routeData) { final args = routeData.argsAs(); return AutoRoutePage( @@ -37,10 +25,58 @@ abstract class _$RootRouter extends RootStackRouter { ), ); }, - RecoveryKeyRoute.name: (routeData) { + BackupDetailsRoute.name: (routeData) { return AutoRoutePage( routeData: routeData, - child: const RecoveryKeyPage(), + child: const BackupDetailsPage(), + ); + }, + DevicesRoute.name: (routeData) { + return AutoRoutePage( + routeData: routeData, + child: const DevicesScreen(), + ); + }, + DnsDetailsRoute.name: (routeData) { + return AutoRoutePage( + routeData: routeData, + child: const DnsDetailsPage(), + ); + }, + AboutApplicationRoute.name: (routeData) { + return AutoRoutePage( + routeData: routeData, + child: const AboutApplicationPage(), + ); + }, + AppSettingsRoute.name: (routeData) { + return AutoRoutePage( + routeData: routeData, + child: const AppSettingsPage(), + ); + }, + DeveloperSettingsRoute.name: (routeData) { + return AutoRoutePage( + routeData: routeData, + child: const DeveloperSettingsPage(), + ); + }, + ConsoleRoute.name: (routeData) { + return AutoRoutePage( + routeData: routeData, + child: const ConsolePage(), + ); + }, + MoreRoute.name: (routeData) { + return AutoRoutePage( + routeData: routeData, + child: const MorePage(), + ); + }, + OnboardingRoute.name: (routeData) { + return AutoRoutePage( + routeData: routeData, + child: const OnboardingPage(), ); }, ProvidersRoute.name: (routeData) { @@ -49,6 +85,18 @@ abstract class _$RootRouter extends RootStackRouter { child: const ProvidersPage(), ); }, + RecoveryKeyRoute.name: (routeData) { + return AutoRoutePage( + routeData: routeData, + child: const RecoveryKeyPage(), + ); + }, + RootRoute.name: (routeData) { + return AutoRoutePage( + routeData: routeData, + child: WrappedRoute(child: const RootPage()), + ); + }, ServerDetailsRoute.name: (routeData) { return AutoRoutePage( routeData: routeData, @@ -67,17 +115,6 @@ abstract class _$RootRouter extends RootStackRouter { ), ); }, - ExtendingVolumeRoute.name: (routeData) { - final args = routeData.argsAs(); - return AutoRoutePage( - routeData: routeData, - child: ExtendingVolumePage( - diskVolumeToResize: args.diskVolumeToResize, - diskStatus: args.diskStatus, - key: args.key, - ), - ); - }, ServerStorageRoute.name: (routeData) { final args = routeData.argsAs(); return AutoRoutePage( @@ -88,52 +125,15 @@ abstract class _$RootRouter extends RootStackRouter { ), ); }, - DevicesRoute.name: (routeData) { + ExtendingVolumeRoute.name: (routeData) { + final args = routeData.argsAs(); return AutoRoutePage( routeData: routeData, - child: const DevicesScreen(), - ); - }, - RootRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: WrappedRoute(child: const RootPage()), - ); - }, - AboutApplicationRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const AboutApplicationPage(), - ); - }, - ConsoleRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const ConsolePage(), - ); - }, - MoreRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const MorePage(), - ); - }, - AppSettingsRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const AppSettingsPage(), - ); - }, - DeveloperSettingsRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const DeveloperSettingsPage(), - ); - }, - DnsDetailsRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const DnsDetailsPage(), + child: ExtendingVolumePage( + diskVolumeToResize: args.diskVolumeToResize, + diskStatus: args.diskStatus, + key: args.key, + ), ); }, ServiceRoute.name: (routeData) { @@ -152,6 +152,18 @@ abstract class _$RootRouter extends RootStackRouter { child: const ServicesPage(), ); }, + InitializingRoute.name: (routeData) { + return AutoRoutePage( + routeData: routeData, + child: const InitializingPage(), + ); + }, + RecoveryRoute.name: (routeData) { + return AutoRoutePage( + routeData: routeData, + child: const RecoveryRouting(), + ); + }, UsersRoute.name: (routeData) { return AutoRoutePage( routeData: routeData, @@ -174,49 +186,9 @@ abstract class _$RootRouter extends RootStackRouter { ), ); }, - InitializingRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const InitializingPage(), - ); - }, - RecoveryRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const RecoveryRouting(), - ); - }, }; } -/// generated route for -/// [OnboardingPage] -class OnboardingRoute extends PageRouteInfo { - const OnboardingRoute({List? children}) - : super( - OnboardingRoute.name, - initialChildren: children, - ); - - static const String name = 'OnboardingRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [BackupDetailsPage] -class BackupDetailsRoute extends PageRouteInfo { - const BackupDetailsRoute({List? children}) - : super( - BackupDetailsRoute.name, - initialChildren: children, - ); - - static const String name = 'BackupDetailsRoute'; - - static const PageInfo page = PageInfo(name); -} - /// generated route for /// [BackupsListPage] class BackupsListRoute extends PageRouteInfo { @@ -256,15 +228,127 @@ class BackupsListRouteArgs { } /// generated route for -/// [RecoveryKeyPage] -class RecoveryKeyRoute extends PageRouteInfo { - const RecoveryKeyRoute({List? children}) +/// [BackupDetailsPage] +class BackupDetailsRoute extends PageRouteInfo { + const BackupDetailsRoute({List? children}) : super( - RecoveryKeyRoute.name, + BackupDetailsRoute.name, initialChildren: children, ); - static const String name = 'RecoveryKeyRoute'; + static const String name = 'BackupDetailsRoute'; + + static const PageInfo page = PageInfo(name); +} + +/// generated route for +/// [DevicesScreen] +class DevicesRoute extends PageRouteInfo { + const DevicesRoute({List? children}) + : super( + DevicesRoute.name, + initialChildren: children, + ); + + static const String name = 'DevicesRoute'; + + static const PageInfo page = PageInfo(name); +} + +/// generated route for +/// [DnsDetailsPage] +class DnsDetailsRoute extends PageRouteInfo { + const DnsDetailsRoute({List? children}) + : super( + DnsDetailsRoute.name, + initialChildren: children, + ); + + static const String name = 'DnsDetailsRoute'; + + static const PageInfo page = PageInfo(name); +} + +/// generated route for +/// [AboutApplicationPage] +class AboutApplicationRoute extends PageRouteInfo { + const AboutApplicationRoute({List? children}) + : super( + AboutApplicationRoute.name, + initialChildren: children, + ); + + static const String name = 'AboutApplicationRoute'; + + static const PageInfo page = PageInfo(name); +} + +/// generated route for +/// [AppSettingsPage] +class AppSettingsRoute extends PageRouteInfo { + const AppSettingsRoute({List? children}) + : super( + AppSettingsRoute.name, + initialChildren: children, + ); + + static const String name = 'AppSettingsRoute'; + + static const PageInfo page = PageInfo(name); +} + +/// generated route for +/// [DeveloperSettingsPage] +class DeveloperSettingsRoute extends PageRouteInfo { + const DeveloperSettingsRoute({List? children}) + : super( + DeveloperSettingsRoute.name, + initialChildren: children, + ); + + static const String name = 'DeveloperSettingsRoute'; + + static const PageInfo page = PageInfo(name); +} + +/// generated route for +/// [ConsolePage] +class ConsoleRoute extends PageRouteInfo { + const ConsoleRoute({List? children}) + : super( + ConsoleRoute.name, + initialChildren: children, + ); + + static const String name = 'ConsoleRoute'; + + static const PageInfo page = PageInfo(name); +} + +/// generated route for +/// [MorePage] +class MoreRoute extends PageRouteInfo { + const MoreRoute({List? children}) + : super( + MoreRoute.name, + initialChildren: children, + ); + + static const String name = 'MoreRoute'; + + static const PageInfo page = PageInfo(name); +} + +/// generated route for +/// [OnboardingPage] +class OnboardingRoute extends PageRouteInfo { + const OnboardingRoute({List? children}) + : super( + OnboardingRoute.name, + initialChildren: children, + ); + + static const String name = 'OnboardingRoute'; static const PageInfo page = PageInfo(name); } @@ -283,6 +367,34 @@ class ProvidersRoute extends PageRouteInfo { static const PageInfo page = PageInfo(name); } +/// generated route for +/// [RecoveryKeyPage] +class RecoveryKeyRoute extends PageRouteInfo { + const RecoveryKeyRoute({List? children}) + : super( + RecoveryKeyRoute.name, + initialChildren: children, + ); + + static const String name = 'RecoveryKeyRoute'; + + static const PageInfo page = PageInfo(name); +} + +/// generated route for +/// [RootPage] +class RootRoute extends PageRouteInfo { + const RootRoute({List? children}) + : super( + RootRoute.name, + initialChildren: children, + ); + + static const String name = 'RootRoute'; + + static const PageInfo page = PageInfo(name); +} + /// generated route for /// [ServerDetailsScreen] class ServerDetailsRoute extends PageRouteInfo { @@ -345,6 +457,44 @@ class ServicesMigrationRouteArgs { } } +/// generated route for +/// [ServerStoragePage] +class ServerStorageRoute extends PageRouteInfo { + ServerStorageRoute({ + required DiskStatus diskStatus, + Key? key, + List? children, + }) : super( + ServerStorageRoute.name, + args: ServerStorageRouteArgs( + diskStatus: diskStatus, + key: key, + ), + initialChildren: children, + ); + + static const String name = 'ServerStorageRoute'; + + static const PageInfo page = + PageInfo(name); +} + +class ServerStorageRouteArgs { + const ServerStorageRouteArgs({ + required this.diskStatus, + this.key, + }); + + final DiskStatus diskStatus; + + final Key? key; + + @override + String toString() { + return 'ServerStorageRouteArgs{diskStatus: $diskStatus, key: $key}'; + } +} + /// generated route for /// [ExtendingVolumePage] class ExtendingVolumeRoute extends PageRouteInfo { @@ -388,156 +538,6 @@ class ExtendingVolumeRouteArgs { } } -/// generated route for -/// [ServerStoragePage] -class ServerStorageRoute extends PageRouteInfo { - ServerStorageRoute({ - required DiskStatus diskStatus, - Key? key, - List? children, - }) : super( - ServerStorageRoute.name, - args: ServerStorageRouteArgs( - diskStatus: diskStatus, - key: key, - ), - initialChildren: children, - ); - - static const String name = 'ServerStorageRoute'; - - static const PageInfo page = - PageInfo(name); -} - -class ServerStorageRouteArgs { - const ServerStorageRouteArgs({ - required this.diskStatus, - this.key, - }); - - final DiskStatus diskStatus; - - final Key? key; - - @override - String toString() { - return 'ServerStorageRouteArgs{diskStatus: $diskStatus, key: $key}'; - } -} - -/// generated route for -/// [DevicesScreen] -class DevicesRoute extends PageRouteInfo { - const DevicesRoute({List? children}) - : super( - DevicesRoute.name, - initialChildren: children, - ); - - static const String name = 'DevicesRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [RootPage] -class RootRoute extends PageRouteInfo { - const RootRoute({List? children}) - : super( - RootRoute.name, - initialChildren: children, - ); - - static const String name = 'RootRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [AboutApplicationPage] -class AboutApplicationRoute extends PageRouteInfo { - const AboutApplicationRoute({List? children}) - : super( - AboutApplicationRoute.name, - initialChildren: children, - ); - - static const String name = 'AboutApplicationRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [ConsolePage] -class ConsoleRoute extends PageRouteInfo { - const ConsoleRoute({List? children}) - : super( - ConsoleRoute.name, - initialChildren: children, - ); - - static const String name = 'ConsoleRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [MorePage] -class MoreRoute extends PageRouteInfo { - const MoreRoute({List? children}) - : super( - MoreRoute.name, - initialChildren: children, - ); - - static const String name = 'MoreRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [AppSettingsPage] -class AppSettingsRoute extends PageRouteInfo { - const AppSettingsRoute({List? children}) - : super( - AppSettingsRoute.name, - initialChildren: children, - ); - - static const String name = 'AppSettingsRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [DeveloperSettingsPage] -class DeveloperSettingsRoute extends PageRouteInfo { - const DeveloperSettingsRoute({List? children}) - : super( - DeveloperSettingsRoute.name, - initialChildren: children, - ); - - static const String name = 'DeveloperSettingsRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [DnsDetailsPage] -class DnsDetailsRoute extends PageRouteInfo { - const DnsDetailsRoute({List? children}) - : super( - DnsDetailsRoute.name, - initialChildren: children, - ); - - static const String name = 'DnsDetailsRoute'; - - static const PageInfo page = PageInfo(name); -} - /// generated route for /// [ServicePage] class ServiceRoute extends PageRouteInfo { @@ -590,6 +590,34 @@ class ServicesRoute extends PageRouteInfo { static const PageInfo page = PageInfo(name); } +/// generated route for +/// [InitializingPage] +class InitializingRoute extends PageRouteInfo { + const InitializingRoute({List? children}) + : super( + InitializingRoute.name, + initialChildren: children, + ); + + static const String name = 'InitializingRoute'; + + static const PageInfo page = PageInfo(name); +} + +/// generated route for +/// [RecoveryRouting] +class RecoveryRoute extends PageRouteInfo { + const RecoveryRoute({List? children}) + : super( + RecoveryRoute.name, + initialChildren: children, + ); + + static const String name = 'RecoveryRoute'; + + static const PageInfo page = PageInfo(name); +} + /// generated route for /// [UsersPage] class UsersRoute extends PageRouteInfo { @@ -655,31 +683,3 @@ class UserDetailsRouteArgs { return 'UserDetailsRouteArgs{login: $login, key: $key}'; } } - -/// generated route for -/// [InitializingPage] -class InitializingRoute extends PageRouteInfo { - const InitializingRoute({List? children}) - : super( - InitializingRoute.name, - initialChildren: children, - ); - - static const String name = 'InitializingRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [RecoveryRouting] -class RecoveryRoute extends PageRouteInfo { - const RecoveryRoute({List? children}) - : super( - RecoveryRoute.name, - initialChildren: children, - ); - - static const String name = 'RecoveryRoute'; - - static const PageInfo page = PageInfo(name); -} From 0ffd7e61d69314274feb4b12991c663676e7ec75 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Sat, 9 Sep 2023 21:20:21 +0300 Subject: [PATCH 13/22] feat(backups): Add note about the scope of backup rotation --- flake.lock | 8 ++++---- lib/ui/pages/backups/change_rotation_quotas_modal.dart | 6 ++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/flake.lock b/flake.lock index cfd8ecfe..14cdcc98 100644 --- a/flake.lock +++ b/flake.lock @@ -66,16 +66,16 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1662096612, - "narHash": "sha256-R+Q8l5JuyJryRPdiIaYpO5O3A55rT+/pItBrKcy7LM4=", + "lastModified": 1693250523, + "narHash": "sha256-y3up5gXMTbnCsXrNEB5j+7TVantDLUYyQLu/ueiXuyg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "21de2b973f9fee595a7a1ac4693efff791245c34", + "rev": "3efb0f6f404ec8dae31bdb1a9b17705ce0d6986e", "type": "github" }, "original": { "id": "nixpkgs", - "ref": "nixpkgs-unstable", + "ref": "nixos-unstable", "type": "indirect" } }, diff --git a/lib/ui/pages/backups/change_rotation_quotas_modal.dart b/lib/ui/pages/backups/change_rotation_quotas_modal.dart index 6d9cba8a..0ce97eee 100644 --- a/lib/ui/pages/backups/change_rotation_quotas_modal.dart +++ b/lib/ui/pages/backups/change_rotation_quotas_modal.dart @@ -94,6 +94,12 @@ class _ChangeRotationQuotasModalState extends State { style: Theme.of(context).textTheme.headlineSmall, textAlign: TextAlign.center, ), + const SizedBox(height: 8), + Text( + 'backup.quotas_only_applied_to_autobackups'.tr(), + style: Theme.of(context).textTheme.bodyMedium, + textAlign: TextAlign.center, + ), const SizedBox(height: 16), // Accordions for each quota type. When tapped allows to enter a new int value // for the quota. From beb526a63552afb9e808fd5378a4d8b341b9386a Mon Sep 17 00:00:00 2001 From: Inex Code Date: Sun, 10 Sep 2023 12:46:55 +0300 Subject: [PATCH 14/22] chore(i18l): Add missing translation --- assets/translations/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/assets/translations/en.json b/assets/translations/en.json index 8571b1c0..5fb1ce5c 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -231,6 +231,7 @@ "rotation_quotas_title": "Snapshot rotation settings", "set_rotation_quotas": "Set new rotation quotas", "quotas_set": "New backup rotation quotas set", + "quotas_only_applied_to_autobackups": "These settings are only applied to automatic backups. Manual backups won't get deleted.", "quota_titles": { "last": "How many latest backups to keep", "daily": "How many daily backups to keep", From d812066355feb1d1ca837d8d7bd587cf56c1a9c9 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Sun, 10 Sep 2023 09:34:50 +0000 Subject: [PATCH 15/22] Translated using Weblate (Russian) Currently translated at 100.0% (529 of 529 strings) Translation: SelfPrivacy/SelfPrivacy App Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app/ru/ --- assets/translations/ru.json | 71 +++++++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/assets/translations/ru.json b/assets/translations/ru.json index 1836c4f4..e6b7397d 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -220,7 +220,72 @@ "snapshot_modal_download_verify_option_title": "Загрузите, проверьте и затем замените", "snapshot_modal_inplace_option_title": "Заменить на месте", "snapshot_modal_inplace_option_description": "Требуется меньше свободного места, но риск выше. При загрузке текущие данные заменяются данными моментального снимка.", - "restore_started": "Восстановление началось, проверьте текущий статус в списке заданий" + "restore_started": "Восстановление началось, проверьте текущий статус в списке заданий", + "quota_subtitles": { + "no_effect": "Это правило не имеет эффекта, так ак перекрыто другим правилом", + "last": { + "two": "Последние {} снимка будут сохраняться вне зависимости от даты создания", + "many": "Последние {} снимков будут сохраняться вне зависимости от даты создания", + "other": "Последние {} снимков будут сохраняться вне зависимости от даты создания", + "zero": "Правило отключено", + "one": "Последний {} снимок будет сохраняться вне зависимости от даты создания", + "few": "Последние {} снимка будут сохраняться вне зависимости от даты создания" + }, + "daily": { + "two": "Последние {} ежедневных снимка будут сохраняться", + "other": "Последние {} ежедневных снимков будут сохраняться", + "zero": "Правило отключено", + "one": "Последний {} ежедневный снимок будет сохраняться", + "few": "Последние {} ежедневных снимка будут сохраняться", + "many": "Последние {} ежедневных снимков будут сохраняться" + }, + "weekly": { + "two": "Последние {} еженедельных снимка будут сохраняться", + "other": "Последние {} еженедельных снимков будут сохраняться", + "zero": "Правило отключено", + "one": "Последний {} еженедельный снимок будет сохраняться", + "few": "Последние {} еженедельных снимка будут сохраняться", + "many": "Последние {} еженедельных снимков будут сохраняться" + }, + "monthly": { + "two": "Последние {} ежемесячных снимка будут сохраняться", + "other": "Последние {} ежемесячных снимков будут сохраняться", + "zero": "Правило отключено", + "one": "Последний {} ежемесячный снимок будет сохраняться", + "few": "Последние {} ежемесячных снимка будут сохраняться", + "many": "Последние {} ежемесячных снимков будут сохраняться" + }, + "yearly": { + "two": "Последние {} ежегодных снимка будут сохраняться", + "many": "Последние {} ежегодных снимков будут сохраняться", + "zero": "Правило отключено", + "one": "Последний {} ежегодный снимок будет сохраняться", + "few": "Последние {} ежегодных снимка будут сохраняться", + "other": "Последние {} ежегодных снимков будут сохраняться" + }, + "last_infinite": "Все снимки будут сохранены", + "daily_infinite": "Все ежедневные снимки будут сохраняться", + "weekly_infinite": "Все еженедельные снимки будут сохраняться", + "monthly_infinite": "Все ежемесячные снимки будут сохраняться", + "yearly_infinite": "Все ежегодные снимки будут сохраняться" + }, + "snapshot_reason_title": "Причина создания", + "snapshot_reasons": { + "auto": "Создано автоматически", + "explicit": "Создано по вашему явному запросу", + "pre_restore": "Создано в качестве меры предосторожности перед рискованным восстановлением", + "unknown": "Неизвестно" + }, + "rotation_quotas_title": "Настройки ротации снимков", + "set_rotation_quotas": "Задать новые квоты ротации", + "quotas_set": "Новые квоты ротации резервных копий заданы", + "quota_titles": { + "last": "Сколько последних снимков сохранять", + "daily": "Сколько ежедневных снимков сохранять", + "weekly": "Сколько еженедельных снимков сохранять", + "monthly": "Сколько ежемесячных снимков сохранять", + "yearly": "Сколько ежегодных снимков сохранять" + } }, "storage": { "card_title": "Хранилище", @@ -400,7 +465,9 @@ "server_reboot": "Перезагрузка сервера", "final_checks": "Финальные проверки" }, - "server_provider_description": "Место, где будут находиться ваши данные и службы SelfPrivacy:" + "server_provider_description": "Место, где будут находиться ваши данные и службы SelfPrivacy:", + "multiple_domains_found": "Найдено несколько доменов", + "multiple_domains_found_text": "Предоставленный токен дает доступ к следующим доменам. Пожалуйста, выберите тот, который вы хотите использовать. Для обеспечения безопасности других доменов следует ограничить доступ этого токена только тем доменом, который вы хотите использовать с SelfPrivacy." }, "recovering": { "generic_error": "Ошибка проведения операции, попробуйте ещё раз.", From dd7436b2fd469aeebc0c22035dcb9768a4889ab7 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Sun, 10 Sep 2023 09:49:21 +0000 Subject: [PATCH 16/22] Translated using Weblate (Russian) Currently translated at 100.0% (530 of 530 strings) Translation: SelfPrivacy/SelfPrivacy App Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app/ru/ --- assets/translations/ru.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assets/translations/ru.json b/assets/translations/ru.json index e6b7397d..48c81ca9 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -285,7 +285,8 @@ "weekly": "Сколько еженедельных снимков сохранять", "monthly": "Сколько ежемесячных снимков сохранять", "yearly": "Сколько ежегодных снимков сохранять" - } + }, + "quotas_only_applied_to_autobackups": "Эти настройки применяются только к резервным копиям, созданным автоматически. Созданные вручную резервные копии не будут удалены этими правилами." }, "storage": { "card_title": "Хранилище", From 580a68fd7cf07e076dd934758d7bf6c78a233836 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Sun, 10 Sep 2023 11:34:17 +0000 Subject: [PATCH 17/22] Translated using Weblate (Russian) Currently translated at 100.0% (530 of 530 strings) Translation: SelfPrivacy/SelfPrivacy App Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app/ru/ --- assets/translations/ru.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/assets/translations/ru.json b/assets/translations/ru.json index 48c81ca9..ced77198 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -68,8 +68,8 @@ "reset_config_description": "Сбросить API ключи и root пользователя.", "delete_server_title": "Удалить сервер", "delete_server_description": "Действие приведёт к удалению сервера. После этого он будет недоступен.", - "system_dark_theme_title": "Системная тема по-умолчанию", - "system_dark_theme_description": "Используйте светлую или темную темы в зависимости от системных настроек", + "system_dark_theme_title": "Системная тема", + "system_dark_theme_description": "Будет использована светлая или тёмная тема в зависимости от системных настроек", "dangerous_settings": "Опасные настройки" }, "ssh": { @@ -183,7 +183,7 @@ "reupload_key_subtitle": "Ещё раз проинициализирует хранилище резервных копий. Используйте, если что-то сломалось.", "service_busy": "Сейчас создаются другие резервные копии", "autobackup_period_never": "Автоматическое копирование отключено", - "pending_jobs": "Активные задачи на копирование", + "pending_jobs": "Активные задачи резервного копирования", "card_subtitle": "Управляйте резервными копиями", "refetch_backups_subtitle": "Сбросить кэш и запросить данные у провайдера. Может повлечь дополнительные расходы.", "select_all": "Копировать всё", @@ -204,22 +204,22 @@ "snapshots_title": "Список снимков", "forget_snapshot_error": "Не удалось забыть снимок", "backups_encryption_key_not_found": "Ключ шифрования пока не найден, повторите попытку позже.", - "forget_snapshot_alert": "Вы собираетесь удалить этот снимок. Вы уверены? Это действие обычно нельзя отменить.", + "forget_snapshot_alert": "Вы уверены что хотите удалить этот снимок? Это действие обычно нельзя отменить.", "snapshot_modal_select_strategy": "Выберите стратегию восстановления", - "snapshot_modal_download_verify_option_description": "Меньше риск, но требуется больше свободного места. Загрузка всего моментального снимка во временное хранилище, его проверка и последующая замена текущих данных.", - "snapshot_modal_service_not_found": "Это снимок службы, которой больше нет на вашем сервере. Обычно этого не должно происходить, и мы не можем выполнить автоматическое восстановление. Вы можете загрузить снимок и восстановить его вручную. Обратитесь в службу поддержки SelfPrivacy, если вам нужна помощь.", + "snapshot_modal_download_verify_option_description": "Меньше риск, но требуется больше свободного места. Загрузка всей резервной копии во временное хранилище, проверка целостности копии, и последующая замена текущих данных.", + "snapshot_modal_service_not_found": "Это снимок сервиса, которого больше нет на вашем сервере. Обычно этого не должно происходить, и мы не сможем выполнить автоматическое восстановление. Вы можете загрузить снимок и восстановить его вручную. Обратитесь в службу поддержки SelfPrivacy, если вам нужна помощь.", "backups_encryption_key_subtitle": "Храните его в безопасном месте.", "backups_encryption_key_copy": "Скопируйте ключ шифрования", "backups_encryption_key_show": "Показать ключ шифрования", - "backups_encryption_key_description": "Этот ключ используется для шифрования резервных копий. Если вы его потеряете, то не сможете восстановить резервные копии. Храните его в надежном месте, так как он может пригодиться в случае необходимости восстановления из резервных копий вручную.", + "backups_encryption_key_description": "Этот ключ используется для шифрования резервных копий. Если вы его потеряете, то не сможете восстановить данные из резервной копии. Храните его в надежном месте. Он может пригодиться, если придётся восстанавливать данные вручную.", "forget_snapshot": "Забудьте о моментальном снимке", "snapshot_modal_heading": "Сведения о снимке", "snapshot_service_title": "Сервис", "snapshot_creation_time_title": "Время создания", "snapshot_id_title": "ID снимка", - "snapshot_modal_download_verify_option_title": "Загрузите, проверьте и затем замените", + "snapshot_modal_download_verify_option_title": "Загрузить, проверить, и затем заменить", "snapshot_modal_inplace_option_title": "Заменить на месте", - "snapshot_modal_inplace_option_description": "Требуется меньше свободного места, но риск выше. При загрузке текущие данные заменяются данными моментального снимка.", + "snapshot_modal_inplace_option_description": "Требуется меньше свободного места, но выше риск. При загрузке данных из резервной копии заменяют текущие данные сразу.", "restore_started": "Восстановление началось, проверьте текущий статус в списке заданий", "quota_subtitles": { "no_effect": "Это правило не имеет эффекта, так ак перекрыто другим правилом", @@ -386,7 +386,7 @@ "username_rule": "Имя может содержать только маленькие латинские буквы, цифры, подчёркивания, не может начинаться с цифр", "email_login": "Авторизация по Email", "no_ssh_notice": "Для этого пользователя созданы только SSH и Email аккаунт. Единая авторизация для всех сервисов ещё не реализована.", - "details_title": "Пользовательские данные" + "details_title": "Пользователь" }, "initializing": { "dns_provider_description": "Это позволит связать ваш домен с IP адресом:", @@ -458,9 +458,9 @@ "server_type": "Тип сервера", "nixos_installation": "Установка NixOS", "dns_provider": "DNS провайдер", - "backups_provider": "Резервные копии", + "backups_provider": "Резервное копирование", "domain": "Домен", - "master_account": "Мастер аккаунт", + "master_account": "Главная учетная запись", "server": "Сервер", "dns_setup": "Установка DNS", "server_reboot": "Перезагрузка сервера", @@ -618,7 +618,7 @@ "server_setup": "Мастер установки сервера", "use_staging_acme": "Использование тестового ACME сервера", "use_staging_acme_description": "Применяется при настройке нового сервера.", - "routing": "Маршрутизация приложений", + "routing": "Роутинг приложения", "reset_onboarding": "Сбросить флаг посещения приветствия", "cubit_statuses": "Текущий статут кубитов загрузки", "reset_onboarding_description": "Сброс переключателя включения для повторного отображения экрана включения", From 096338cef7b47243b414d2550314705d29408109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?NaiJi=20=E2=9C=A8?= Date: Sun, 10 Sep 2023 11:30:43 +0000 Subject: [PATCH 18/22] Translated using Weblate (Russian) Currently translated at 100.0% (530 of 530 strings) Translation: SelfPrivacy/SelfPrivacy App Translate-URL: http://weblate.selfprivacy.org/projects/selfprivacy/selfprivacy-app/ru/ --- assets/translations/ru.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/translations/ru.json b/assets/translations/ru.json index ced77198..2371da85 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -466,7 +466,7 @@ "server_reboot": "Перезагрузка сервера", "final_checks": "Финальные проверки" }, - "server_provider_description": "Место, где будут находиться ваши данные и службы SelfPrivacy:", + "server_provider_description": "Место, где будут находиться ваши данные и сервисы SelfPrivacy:", "multiple_domains_found": "Найдено несколько доменов", "multiple_domains_found_text": "Предоставленный токен дает доступ к следующим доменам. Пожалуйста, выберите тот, который вы хотите использовать. Для обеспечения безопасности других доменов следует ограничить доступ этого токена только тем доменом, который вы хотите использовать с SelfPrivacy." }, @@ -571,7 +571,7 @@ "you_cant_use_this_api": "Нельзя использовать этот API для доменом с подобным TLD.", "yes": "Да", "no": "Нет", - "volume_creation_error": "Не удалось создать том." + "volume_creation_error": "Не удалось создать хранилище." }, "timer": { "sec": "{} сек" @@ -621,7 +621,7 @@ "routing": "Роутинг приложения", "reset_onboarding": "Сбросить флаг посещения приветствия", "cubit_statuses": "Текущий статут кубитов загрузки", - "reset_onboarding_description": "Сброс переключателя включения для повторного отображения экрана включения", + "reset_onboarding_description": "Принудить показ приветственного экрана", "ignore_tls_description": "Приложение не будет проверять сертификаты TLS при подключении к серверу.", "ignore_tls": "Не проверять сертификаты TLS" } From da85253268b1dc6890277fc40aae705aca13c8e5 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Sun, 10 Sep 2023 14:53:43 +0300 Subject: [PATCH 19/22] chore: Bump app version --- .../android/en-US/changelogs/0.9.0.txt | 145 ++++++++++++++++++ pubspec.yaml | 2 +- 2 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/0.9.0.txt diff --git a/fastlane/metadata/android/en-US/changelogs/0.9.0.txt b/fastlane/metadata/android/en-US/changelogs/0.9.0.txt new file mode 100644 index 00000000..29ac8043 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/0.9.0.txt @@ -0,0 +1,145 @@ +### Features + +- New backups implementation ([#228](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/228), [#274](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/274), [#324](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/324), [#325](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/325), [#326](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/326), [#331](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/331), [#332](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/332)) +- DigitalOcean as a DNS provider ([#213](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/213)) +- DeSEC as a DNS provider ([#211](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/211)) +- Support drawer and basic support documentation logic unit ([#203](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/203)) +- Automatic day/night theme ([#203](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/203)) +- New router and adaptive layouts ([#203](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/203)) +- New Material 3 animation curves ([#203](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/203)) +- Jobs button to the app bar of more screens ([#203](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/203)) +- Refreshed UI of modal sheets ([#228](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/228)) +- Support for `XDG_DATA_HOME` storage path on Linux for app data ([#240](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/240)) +- Accept-Language header for the server API ([#243](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/243), resolves [#205](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/205)) +- Visible providers names during server recovery ([#264](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/264), resolves [#249](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/249)) +- Visible volume and IPv4 cost added to overall monthly cost of the server ([#270](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/270), resolves [#115](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/115)) +- Support for autofocus on text fields for keyboard displaying ([#294](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/294), resolves [#292](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/292)) +- New dialogue to choose a domain if user DNS token provides access to several ([#330](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/330), resolves [#328](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/328)) + +### Bug Fixes + +- Fix opening URLs from the app ([#213](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/213)) +- Fix parsing of RAM size with DigitalOcean ([#200](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/200), resolves [#199](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/199)) +- Devices and Recovery Key cubits couldn't initialize right after server installation ([#203](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/203)) +- Fix BottomBar showing incorrect animation when navigating from sibling routes ([#203](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/203)) +- PopUpDialogs couldn't find the context. ([#203](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/203)) +- Update recovery flow to use new support drawer ([#203](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/203)) +- New app log console ([#203](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/203)) +- Improve installation failure dialogues ([#213](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/213)) +- Privacy policy link pointed at wrong domain ([#207](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/207)) +- Remove price lists for DNS ([#211](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/211)) +- Implement better domain id check on DNS restoration ([#211](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/211)) +- Add forced JSON content type to REST APIs ([#212](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/212)) +- Remove unneded DNS check depending on CLOUDFLARE ([#212](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/212)) +- Add background for dialogue pop ups and move them to root navigator ([#233](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/233), resolves [#231](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/231)) +- Make currency be properly shown again via shortcode ([#234](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/234), related to [#223](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/223)) +- Add proper server type value loading ([#236](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/236), resolves [#215](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/215)) +- Implement proper load functions for DNS and Server providers ([#237](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/237), resolves [#220](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/220)) +- Prevent moving a service if volume is null for some reason ([#245](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/245)) +- Replace hard reset from server provider with direct server reboot ([#269](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/269), resolves [#266](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/266)) +- Normalize Hetzner CPU usage percentage by cached amount of cores ([#272](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/272), resolves [#156](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/156)) +- Change broken validations string for superuser SSH ([#276](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/27)) +- Don't let service migration to start bif the same volume was picked ([#297](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/297), resolves [#289](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/289)) +- Wrap DNS check in catch to avoid runtime crash ([#322](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/322)) +- Implement Backblaze bucket restoration on server recovery ([#324](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/324)) + +### Refactor + +- Migrate to Flutter 3.10 and Dart 3.0 +- Migrate to AutoRouter v6 ([#203](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/203)) +- Get rid of BrandText and restructure the buttons ([#203](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/203)) +- Remove brand alert dialogs and bottom sheet ([#203](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/203)) +- Remove unused UI components ([#203](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/203)) +- Remove BrandCards ([#203](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/203)) +- Allow changing values for TLS settings +- Replace String shortcode with Currency class ([#226](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/226)) +- Rearrange Server Provider interface ([#227](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/227)) +- Remove unused service state getters ([#228](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/228)) +- Remove unused utils, add duration formatter ([#228](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/228)) +- Move rest api methods according to their business logic files positions ([#235](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/235), partially resolves [#217](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/217) and [#219](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/219)) +- Make flag getter a part of server provider location object ([#238](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/238), resolves [#222](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/222)) + + +### Translation contributions + +* Ukrainian + + * FoxMeste (3) + * Mithras (31) + +* Latvian + + * Not Telling Lol (183) + + +* German + + * Mithras (41) + * FoxMeste (213) + + +* Thai + + * FoxMeste (77) + + +* Polish + + * Mithras (41) + * Thary (43) + * FoxMeste (163) + + +* Slovenian + + * Mithras (212) + + +* Czech + + * NaiJi ✨ (2) + * Mithras (109) + * FoxMeste (308) + + +* Russian + + * FoxMeste (4) + * Revertron (8) + * NaiJi ✨ (23) + * Mithras (54) + * Inex Code (59) + + +* Slovak + + * Mithras (29) + * Revertron (396) + + +* Macedonian + + * FoxMeste (7) + + +* Belarusian + + * Thary (1) + * FoxMeste (3) + * Mithras (47) + + +* French + + * Côme (211) + + +* Spanish + + * FoxMeste (7) + + +* Azerbaijani + + * Mithras (28) + * Ortibexon (403) diff --git a/pubspec.yaml b/pubspec.yaml index 4fc007e8..23ddac1e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: selfprivacy description: selfprivacy.org publish_to: 'none' -version: 0.8.0+17 +version: 0.9.0+18 environment: sdk: '>=3.0.2 <4.0.0' From 3370ae2867e65e26bcf1d38b1e72b88addf4f76b Mon Sep 17 00:00:00 2001 From: Inex Code Date: Sun, 10 Sep 2023 15:22:40 +0300 Subject: [PATCH 20/22] ci: Fix appimage version tag --- appimage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appimage.yml b/appimage.yml index ce7bab35..9e783283 100644 --- a/appimage.yml +++ b/appimage.yml @@ -10,7 +10,7 @@ AppDir: id: org.selfprivacy.app name: SelfPrivacy icon: org.selfprivacy.app - version: 0.8.0 + version: 0.9.0 exec: selfprivacy exec_args: $@ apt: From d2ed9f3a752307bcbb57062e5cf524152aef3bae Mon Sep 17 00:00:00 2001 From: NaiJi Date: Sat, 16 Sep 2023 00:45:15 -0300 Subject: [PATCH 21/22] refactor(ui): Move service descriptions above login info for service cards - Resolves https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/341 --- lib/ui/pages/services/services.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/ui/pages/services/services.dart b/lib/ui/pages/services/services.dart index 99bb848e..08446e78 100644 --- a/lib/ui/pages/services/services.dart +++ b/lib/ui/pages/services/services.dart @@ -160,13 +160,15 @@ class _Card extends StatelessWidget { ], ), Text( - service.loginInfo, + service.description, style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: 10), Text( - service.description, - style: Theme.of(context).textTheme.bodyMedium, + service.loginInfo, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.secondary, + ), ), const SizedBox(height: 10), ], From 887302f93656ff7a787e7bfe83dfef53115c31e3 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Sat, 16 Sep 2023 01:41:02 -0300 Subject: [PATCH 22/22] refactor(ui): Add measure units to 'Extending volume' page - Resolves https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/301 --- assets/translations/en.json | 3 ++- lib/ui/pages/server_storage/extending_volume.dart | 10 ++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index 5fb1ce5c..07ffe375 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -304,6 +304,7 @@ "extending_volume_price_info": "Price includes VAT and is estimated from pricing data provided by your server provider. Server will be rebooted after resizing.", "extending_volume_error": "Couldn't initialize volume extending.", "size": "Size", + "price": "Price", "data_migration_title": "Data migration", "data_migration_notice": "During migration all services will be turned off.", "start_migration_button": "Start migration", @@ -625,4 +626,4 @@ "reset_onboarding_description": "Reset onboarding switch to show onboarding screen again", "cubit_statuses": "Cubit loading statuses" } -} +} \ No newline at end of file diff --git a/lib/ui/pages/server_storage/extending_volume.dart b/lib/ui/pages/server_storage/extending_volume.dart index 95325d0c..81414bdb 100644 --- a/lib/ui/pages/server_storage/extending_volume.dart +++ b/lib/ui/pages/server_storage/extending_volume.dart @@ -79,10 +79,12 @@ class _ExtendingVolumePageState extends State { } final price = snapshot.data as Price; _pricePerGb = price.value; - _sizeController.text = _currentSliderGbValue.truncate().toString(); + final currentSizeValue = _currentSliderGbValue.truncate().toString(); + _sizeController.text = 'storage.gb'.tr(args: [currentSizeValue]); _priceController.text = - (_pricePerGb * double.parse(_sizeController.text)) - .toStringAsFixed(2); + '${(_pricePerGb * double.parse(currentSizeValue)).toStringAsFixed(2)}' + ' ' + '${price.currency.shortcode}'; minSize = widget.diskVolumeToResize.sizeTotal + DiskSize.fromGibibyte(3); if (_currentSliderGbValue < 0) { @@ -130,7 +132,7 @@ class _ExtendingVolumePageState extends State { decoration: InputDecoration( border: const OutlineInputBorder(), errorText: _isError ? ' ' : null, - labelText: price.currency.shortcode, + labelText: 'storage.price'.tr(), ), ), ),