From c3752673f7fd311756ea587f9afbbbdd91426d28 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Thu, 20 Jul 2023 18:35:13 -0300 Subject: [PATCH 1/4] feat(backups): Implement modal for copying backups encryption key --- assets/translations/en.json | 6 +- assets/translations/ru.json | 4 +- lib/ui/pages/backups/backup_details.dart | 29 ++++++ .../backups/copy_encryption_key_modal.dart | 88 +++++++++++++++++++ 4 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 lib/ui/pages/backups/copy_encryption_key_modal.dart diff --git a/assets/translations/en.json b/assets/translations/en.json index fc7e1eb2..f1ef524f 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -34,7 +34,8 @@ "apply": "Apply", "done": "Done", "continue": "Continue", - "alert": "Alert" + "alert": "Alert", + "copied_to_clipboard": "Copied to clipboard!" }, "more_page": { "configuration_wizard": "Setup wizard", @@ -196,6 +197,7 @@ "autobackup_custom_hint": "Enter custom period in minutes", "autobackup_set_period": "Set period", "autobackup_period_set": "Period set", + "backups_encryption_key": "Encryption key", "pending_jobs": "Currently running backup jobs", "snapshots_title": "Snapshot list" }, @@ -536,4 +538,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/assets/translations/ru.json b/assets/translations/ru.json index 12c161b9..e906e838 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -34,6 +34,7 @@ "done": "Готово", "continue": "Продолжить", "alert": "Уведомление", + "copied_to_clipboard": "Скопировано в буфер обмена!", "app_name": "SelfPrivacy" }, "more_page": { @@ -197,6 +198,7 @@ "autobackup_custom_hint": "Введите период в минутах", "autobackup_set_period": "Установить период", "autobackup_period_set": "Период установлен", + "backups_encryption_key": "Ключ шифрования", "snapshots_title": "Список снимков" }, "storage": { @@ -536,4 +538,4 @@ "ignore_tls_description": "Приложение не будет проверять сертификаты TLS при подключении к серверу.", "ignore_tls": "Не проверять сертификаты TLS" } -} +} \ No newline at end of file diff --git a/lib/ui/pages/backups/backup_details.dart b/lib/ui/pages/backups/backup_details.dart index e3fe427b..54136412 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/copy_encryption_key_modal.dart'; import 'package:selfprivacy/ui/pages/backups/create_backups_modal.dart'; import 'package:selfprivacy/ui/router/router.dart'; import 'package:selfprivacy/utils/extensions/duration.dart'; @@ -144,6 +145,34 @@ 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.6, + minChildSize: 0.3, + initialChildSize: 0.3, + builder: (final context, final scrollController) => + CopyEncryptionKeyModal( + scrollController: scrollController, + ), + ), + ); + }, + leading: const Icon( + Icons.key_outlined, + ), + title: Text( + 'backup.backups_encryption_key'.tr(), + ), + ), const SizedBox(height: 16), if (backupJobs.isNotEmpty) Column( diff --git a/lib/ui/pages/backups/copy_encryption_key_modal.dart b/lib/ui/pages/backups/copy_encryption_key_modal.dart new file mode 100644 index 00000000..5421b2a8 --- /dev/null +++ b/lib/ui/pages/backups/copy_encryption_key_modal.dart @@ -0,0 +1,88 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:selfprivacy/config/get_it_config.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'; + +class CopyEncryptionKeyModal extends StatefulWidget { + const CopyEncryptionKeyModal({ + required this.scrollController, + super.key, + }); + + final ScrollController scrollController; + + @override + State createState() => _CopyEncryptionKeyModalState(); +} + +class _CopyEncryptionKeyModalState extends State { + bool isKeyVisible = false; + @override + Widget build(final BuildContext context) { + final String encryptionKey = + context.watch().state.backblazeBucket!.encryptionKey; + return ListView( + controller: widget.scrollController, + padding: const EdgeInsets.all(16), + children: [ + const SizedBox(height: 16), + Text( + 'backup.backups_encryption_key'.tr(), + style: Theme.of(context).textTheme.headlineSmall, + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton( + icon: Icon( + isKeyVisible + ? Icons.visibility_outlined + : Icons.visibility_off_outlined, + ), + onPressed: () { + setState( + () { + isKeyVisible = !isKeyVisible; + }, + ); + }, + ), + IconButton( + icon: const Icon(Icons.copy_all_outlined), + onPressed: () { + getIt() + .showSnackBar('basis.copied_to_clipboard'.tr()); + Clipboard.setData( + ClipboardData( + text: encryptionKey, + ), + ); + }, + ), + ], + ), + Flexible( + child: isKeyVisible + ? SelectableText( + encryptionKey, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: Theme.of(context).colorScheme.onBackground, + ), + ) + : Text( + ''.padLeft(encryptionKey.length, '●'), + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: Theme.of(context).colorScheme.onBackground, + ), + ), + ), + ], + ); + } +} From 3b1e71d7712b824d30722948f7b3cd20a959b21e Mon Sep 17 00:00:00 2001 From: Inex Code Date: Tue, 25 Jul 2023 18:39:58 +0300 Subject: [PATCH 2/4] fix: Add a workaround for the case when we don't have sreverTypeId --- .../server_installation_repository.dart | 53 +++++++++++++------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index 717b7535..5b39463b 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -76,21 +76,44 @@ class ServerInstallationRepository { if (box.get(BNames.hasFinalChecked, defaultValue: false)) { TlsOptions.verifyCertificate = true; - return ServerInstallationFinished( - installationDialoguePopUp: null, - providerApiToken: providerApiToken!, - serverTypeIdentificator: serverTypeIdentificator!, - dnsApiToken: dnsApiToken!, - serverDomain: serverDomain!, - backblazeCredential: backblazeCredential!, - serverDetails: serverDetails!, - rootUser: box.get(BNames.rootUser), - isServerStarted: box.get(BNames.isServerStarted, defaultValue: false), - isServerResetedFirstTime: - box.get(BNames.isServerResetedFirstTime, defaultValue: false), - isServerResetedSecondTime: - box.get(BNames.isServerResetedSecondTime, defaultValue: false), - ); + if (serverTypeIdentificator == null && serverDetails != null) { + final finalServerType = await ProvidersController.currentServerProvider! + .getServerType(serverDetails.id); + await saveServerType(finalServerType.data!); + await ProvidersController.currentServerProvider! + .trySetServerLocation(finalServerType.data!.location.identifier); + return ServerInstallationFinished( + installationDialoguePopUp: null, + providerApiToken: providerApiToken!, + serverTypeIdentificator: finalServerType.data!.identifier, + dnsApiToken: dnsApiToken!, + serverDomain: serverDomain!, + backblazeCredential: backblazeCredential!, + serverDetails: serverDetails, + rootUser: box.get(BNames.rootUser), + isServerStarted: box.get(BNames.isServerStarted, defaultValue: false), + isServerResetedFirstTime: + box.get(BNames.isServerResetedFirstTime, defaultValue: false), + isServerResetedSecondTime: + box.get(BNames.isServerResetedSecondTime, defaultValue: false), + ); + } else { + return ServerInstallationFinished( + installationDialoguePopUp: null, + providerApiToken: providerApiToken!, + serverTypeIdentificator: serverTypeIdentificator!, + dnsApiToken: dnsApiToken!, + serverDomain: serverDomain!, + backblazeCredential: backblazeCredential!, + serverDetails: serverDetails!, + rootUser: box.get(BNames.rootUser), + isServerStarted: box.get(BNames.isServerStarted, defaultValue: false), + isServerResetedFirstTime: + box.get(BNames.isServerResetedFirstTime, defaultValue: false), + isServerResetedSecondTime: + box.get(BNames.isServerResetedSecondTime, defaultValue: false), + ); + } } if (box.get(BNames.isRecoveringServer, defaultValue: false) && From cfcfd5d70859f779a4f73d39b6185d82fddc36d0 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Tue, 25 Jul 2023 22:25:08 +0300 Subject: [PATCH 3/4] feat(backups): Update the UI of the encryption key modal --- assets/translations/en.json | 6 +- lib/ui/pages/backups/backup_details.dart | 9 +- lib/ui/pages/backups/change_period_modal.dart | 3 - .../backups/copy_encryption_key_modal.dart | 108 +++++++++++------- 4 files changed, 78 insertions(+), 48 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index f1ef524f..b442b683 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -198,6 +198,10 @@ "autobackup_set_period": "Set period", "autobackup_period_set": "Period set", "backups_encryption_key": "Encryption key", + "backups_encryption_key_subtitle": "Keep it in a safe place.", + "backups_encryption_key_copy": "Copy the encryption key", + "backups_encryption_key_show": "Show the encryption key", + "backups_encryption_key_description": "This key is used to encrypt your backups. If you lose it, you will not be able to restore your backups. Keep it in a safe place, as it will be useful if you ever need to restore from backups manually.", "pending_jobs": "Currently running backup jobs", "snapshots_title": "Snapshot list" }, @@ -538,4 +542,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/backups/backup_details.dart b/lib/ui/pages/backups/backup_details.dart index 54136412..44995577 100644 --- a/lib/ui/pages/backups/backup_details.dart +++ b/lib/ui/pages/backups/backup_details.dart @@ -156,9 +156,9 @@ class BackupDetailsPage extends StatelessWidget { builder: (final BuildContext context) => DraggableScrollableSheet( expand: false, - maxChildSize: 0.6, - minChildSize: 0.3, - initialChildSize: 0.3, + maxChildSize: 0.9, + minChildSize: 0.5, + initialChildSize: 0.7, builder: (final context, final scrollController) => CopyEncryptionKeyModal( scrollController: scrollController, @@ -172,6 +172,9 @@ class BackupDetailsPage extends StatelessWidget { title: Text( 'backup.backups_encryption_key'.tr(), ), + subtitle: Text( + 'backup.backups_encryption_key_subtitle'.tr(), + ), ), const SizedBox(height: 16), if (backupJobs.isNotEmpty) diff --git a/lib/ui/pages/backups/change_period_modal.dart b/lib/ui/pages/backups/change_period_modal.dart index f3fb2ce3..dbee981e 100644 --- a/lib/ui/pages/backups/change_period_modal.dart +++ b/lib/ui/pages/backups/change_period_modal.dart @@ -20,9 +20,6 @@ class ChangeAutobackupsPeriodModal extends StatefulWidget { class _ChangeAutobackupsPeriodModalState extends State { - // This is a modal with radio buttons to select the autobackup period - // Period might be none, selected from predefined list or custom - // Store in state the selected period Duration? selectedPeriod; static const List autobackupPeriods = [ diff --git a/lib/ui/pages/backups/copy_encryption_key_modal.dart b/lib/ui/pages/backups/copy_encryption_key_modal.dart index 5421b2a8..7b9ce40f 100644 --- a/lib/ui/pages/backups/copy_encryption_key_modal.dart +++ b/lib/ui/pages/backups/copy_encryption_key_modal.dart @@ -20,6 +20,7 @@ class CopyEncryptionKeyModal extends StatefulWidget { class _CopyEncryptionKeyModalState extends State { bool isKeyVisible = false; + @override Widget build(final BuildContext context) { final String encryptionKey = @@ -35,52 +36,77 @@ class _CopyEncryptionKeyModalState extends State { textAlign: TextAlign.center, ), const SizedBox(height: 8), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - IconButton( - icon: Icon( - isKeyVisible - ? Icons.visibility_outlined - : Icons.visibility_off_outlined, - ), - onPressed: () { - setState( - () { - isKeyVisible = !isKeyVisible; - }, - ); - }, - ), - IconButton( - icon: const Icon(Icons.copy_all_outlined), - onPressed: () { - getIt() - .showSnackBar('basis.copied_to_clipboard'.tr()); - Clipboard.setData( - ClipboardData( - text: encryptionKey, - ), - ); - }, - ), - ], + Text( + 'backup.backups_encryption_key_description'.tr(), + style: Theme.of(context).textTheme.bodyMedium, + textAlign: TextAlign.center, ), - Flexible( - child: isKeyVisible - ? SelectableText( + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: Theme.of(context).colorScheme.surfaceVariant, + ), + padding: const EdgeInsets.all(16), + child: Stack( + children: [ + SelectableText( encryptionKey, style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: Theme.of(context).colorScheme.onBackground, - ), - ) - : Text( - ''.padLeft(encryptionKey.length, '●'), - style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: Theme.of(context).colorScheme.onBackground, + color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), + Positioned.fill( + child: InkWell( + onTap: () { + setState( + () { + isKeyVisible = !isKeyVisible; + }, + ); + }, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 200), + opacity: isKeyVisible ? 0 : 1, + child: Container( + color: Theme.of(context).colorScheme.surfaceVariant, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.visibility_outlined), + const SizedBox(width: 8), + Text( + 'backup.backups_encryption_key_show'.tr(), + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + ), + ), + ], + )), + ), + ), + ), + ], + )), + const SizedBox(height: 8), + FilledButton.icon( + onPressed: () { + getIt() + .showSnackBar('basis.copied_to_clipboard'.tr()); + Clipboard.setData( + ClipboardData( + text: encryptionKey, + ), + ); + }, + icon: const Icon(Icons.copy_all_outlined), + label: Text('backup.backups_encryption_key_copy'.tr()), ), ], ); From bbc619deed692dfd5b9b706d375722d8b9f77abd Mon Sep 17 00:00:00 2001 From: Inex Code Date: Tue, 25 Jul 2023 22:43:28 +0300 Subject: [PATCH 4/4] feat(backups): Show the user that the key is copied --- .../backups/copy_encryption_key_modal.dart | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/lib/ui/pages/backups/copy_encryption_key_modal.dart b/lib/ui/pages/backups/copy_encryption_key_modal.dart index 7b9ce40f..289ea184 100644 --- a/lib/ui/pages/backups/copy_encryption_key_modal.dart +++ b/lib/ui/pages/backups/copy_encryption_key_modal.dart @@ -1,7 +1,8 @@ +import 'dart:async'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:selfprivacy/config/get_it_config.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'; @@ -20,6 +21,14 @@ class CopyEncryptionKeyModal extends StatefulWidget { class _CopyEncryptionKeyModalState extends State { bool isKeyVisible = false; + bool copiedToClipboard = false; + Timer? copyToClipboardTimer; + + @override + void dispose() { + copyToClipboardTimer?.cancel(); + super.dispose(); + } @override Widget build(final BuildContext context) { @@ -97,8 +106,23 @@ class _CopyEncryptionKeyModalState extends State { const SizedBox(height: 8), FilledButton.icon( onPressed: () { - getIt() - .showSnackBar('basis.copied_to_clipboard'.tr()); + setState( + () { + copiedToClipboard = true; + }, + ); + // Make a timer to reset the copyToClipboardTime + setState(() { + copyToClipboardTimer?.cancel(); + copyToClipboardTimer = Timer( + const Duration(seconds: 5), + () { + setState(() { + copiedToClipboard = false; + }); + }, + ); + }); Clipboard.setData( ClipboardData( text: encryptionKey, @@ -106,7 +130,11 @@ class _CopyEncryptionKeyModalState extends State { ); }, icon: const Icon(Icons.copy_all_outlined), - label: Text('backup.backups_encryption_key_copy'.tr()), + label: Text( + copiedToClipboard + ? 'basis.copied_to_clipboard'.tr() + : 'backup.backups_encryption_key_copy'.tr(), + ), ), ], );