From ffa985aba23b98c4374efb0b2d10d74c88aa3319 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Wed, 6 Sep 2023 23:03:06 -0300 Subject: [PATCH 1/3] feat: Implement copying to clipboard for snapshot id of backups --- .../pages/backups/snapshot_id_list_tile.dart | 52 +++++++++++++++++++ lib/ui/pages/backups/snapshot_modal.dart | 14 +---- 2 files changed, 54 insertions(+), 12 deletions(-) create mode 100644 lib/ui/pages/backups/snapshot_id_list_tile.dart diff --git a/lib/ui/pages/backups/snapshot_id_list_tile.dart b/lib/ui/pages/backups/snapshot_id_list_tile.dart new file mode 100644 index 00000000..dc821264 --- /dev/null +++ b/lib/ui/pages/backups/snapshot_id_list_tile.dart @@ -0,0 +1,52 @@ +import 'dart:async'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class SnapshotIdListTile extends StatefulWidget { + const SnapshotIdListTile({ + required this.snapshotId, + super.key, + }); + + final String snapshotId; + + @override + State createState() => _SnapshotIdListTileState(); +} + +class _SnapshotIdListTileState extends State { + bool copiedToClipboard = false; + + void handleTimeout() { + setState(() { + copiedToClipboard = false; + }); + } + + @override + Widget build(final BuildContext context) => ListTile( + onLongPress: () { + if (copiedToClipboard == false) { + Clipboard.setData(ClipboardData(text: widget.snapshotId)); + Timer(const Duration(seconds: 2), handleTimeout); + setState(() { + copiedToClipboard = true; + }); + } + }, + leading: Icon( + Icons.numbers_outlined, + color: Theme.of(context).colorScheme.onSurface, + ), + title: Text( + copiedToClipboard + ? 'basis.copied_to_clipboard'.tr() + : 'backup.snapshot_id_title'.tr(), + ), + subtitle: Text( + copiedToClipboard ? '' : widget.snapshotId, + ), + ); +} diff --git a/lib/ui/pages/backups/snapshot_modal.dart b/lib/ui/pages/backups/snapshot_modal.dart index 147fe72a..af9269e5 100644 --- a/lib/ui/pages/backups/snapshot_modal.dart +++ b/lib/ui/pages/backups/snapshot_modal.dart @@ -11,6 +11,7 @@ import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; import 'package:selfprivacy/ui/components/cards/outlined_card.dart'; import 'package:selfprivacy/ui/components/info_box/info_box.dart'; +import 'package:selfprivacy/ui/pages/backups/snapshot_id_list_tile.dart'; class SnapshotModal extends StatefulWidget { const SnapshotModal({ @@ -95,18 +96,7 @@ class _SnapshotModalState extends State { '${MaterialLocalizations.of(context).formatShortDate(widget.snapshot.time)} ${TimeOfDay.fromDateTime(widget.snapshot.time).format(context)}', ), ), - ListTile( - leading: Icon( - Icons.numbers_outlined, - color: Theme.of(context).colorScheme.onSurface, - ), - title: Text( - 'backup.snapshot_id_title'.tr(), - ), - subtitle: Text( - widget.snapshot.id, - ), - ), + SnapshotIdListTile(snapshotId: widget.snapshot.id), if (service != null) Column( children: [ From 7b8d9ddca911677ae67c34f5100322c422d6acea Mon Sep 17 00:00:00 2001 From: NaiJi Date: Wed, 6 Sep 2023 23:41:34 -0300 Subject: [PATCH 2/3] fix: Change 'mailserver' string id to 'email' in loginInfo switch --- lib/logic/models/service.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/logic/models/service.dart b/lib/logic/models/service.dart index d79615e8..b7fee609 100644 --- a/lib/logic/models/service.dart +++ b/lib/logic/models/service.dart @@ -56,7 +56,7 @@ class Service { /// TODO Turn loginInfo into dynamic data, not static! String get loginInfo { switch (id) { - case 'mailserver': + case 'email': return 'mail.login_info'.tr(); case 'bitwarden': return 'password_manager.login_info'.tr(); From fe93360870ad5e3417c9568883148085554c6bb2 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Thu, 7 Sep 2023 14:35:42 +0300 Subject: [PATCH 3/3] refactor: Use snackbar to show snapshot id copy notification --- lib/logic/get_it/navigation.dart | 3 +- .../pages/backups/snapshot_id_list_tile.dart | 40 +--- lib/ui/pages/backups/snapshot_modal.dart | 215 +++++++++--------- 3 files changed, 120 insertions(+), 138 deletions(-) diff --git a/lib/logic/get_it/navigation.dart b/lib/logic/get_it/navigation.dart index 90f67203..dac1dc8d 100644 --- a/lib/logic/get_it/navigation.dart +++ b/lib/logic/get_it/navigation.dart @@ -21,11 +21,12 @@ class NavigationService { ); } - void showSnackBar(final String text) { + void showSnackBar(final String text, {final SnackBarBehavior? behavior}) { final ScaffoldMessengerState state = scaffoldMessengerKey.currentState!; final SnackBar snack = SnackBar( content: Text(text), duration: const Duration(seconds: 2), + behavior: behavior, ); state.showSnackBar(snack); } diff --git a/lib/ui/pages/backups/snapshot_id_list_tile.dart b/lib/ui/pages/backups/snapshot_id_list_tile.dart index dc821264..e159d888 100644 --- a/lib/ui/pages/backups/snapshot_id_list_tile.dart +++ b/lib/ui/pages/backups/snapshot_id_list_tile.dart @@ -1,10 +1,9 @@ -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'; -class SnapshotIdListTile extends StatefulWidget { +class SnapshotIdListTile extends StatelessWidget { const SnapshotIdListTile({ required this.snapshotId, super.key, @@ -12,41 +11,20 @@ class SnapshotIdListTile extends StatefulWidget { final String snapshotId; - @override - State createState() => _SnapshotIdListTileState(); -} - -class _SnapshotIdListTileState extends State { - bool copiedToClipboard = false; - - void handleTimeout() { - setState(() { - copiedToClipboard = false; - }); - } - @override Widget build(final BuildContext context) => ListTile( onLongPress: () { - if (copiedToClipboard == false) { - Clipboard.setData(ClipboardData(text: widget.snapshotId)); - Timer(const Duration(seconds: 2), handleTimeout); - setState(() { - copiedToClipboard = true; - }); - } + Clipboard.setData(ClipboardData(text: snapshotId)); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + behavior: SnackBarBehavior.floating, + ); }, leading: Icon( Icons.numbers_outlined, color: Theme.of(context).colorScheme.onSurface, ), - title: Text( - copiedToClipboard - ? 'basis.copied_to_clipboard'.tr() - : 'backup.snapshot_id_title'.tr(), - ), - subtitle: Text( - copiedToClipboard ? '' : widget.snapshotId, - ), + title: Text('backup.snapshot_id_title'.tr()), + subtitle: Text(snapshotId), ); } diff --git a/lib/ui/pages/backups/snapshot_modal.dart b/lib/ui/pages/backups/snapshot_modal.dart index af9269e5..0f1bac3d 100644 --- a/lib/ui/pages/backups/snapshot_modal.dart +++ b/lib/ui/pages/backups/snapshot_modal.dart @@ -52,114 +52,117 @@ class _SnapshotModalState extends State { .state .getServiceById(widget.snapshot.serviceId); - return ListView( - controller: widget.scrollController, - padding: const EdgeInsets.all(16), - children: [ - const SizedBox(height: 16), - Text( - 'backup.snapshot_modal_heading'.tr(), - style: Theme.of(context).textTheme.headlineSmall, - textAlign: TextAlign.center, - ), - const SizedBox(height: 16), - ListTile( - leading: service != null - ? SvgPicture.string( - service.svgIcon, - height: 24, - width: 24, - colorFilter: ColorFilter.mode( - Theme.of(context).colorScheme.onSurface, - BlendMode.srcIn, + return Scaffold( + backgroundColor: Colors.transparent, + body: ListView( + controller: widget.scrollController, + padding: const EdgeInsets.all(16), + children: [ + const SizedBox(height: 16), + Text( + 'backup.snapshot_modal_heading'.tr(), + style: Theme.of(context).textTheme.headlineSmall, + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + ListTile( + leading: service != null + ? SvgPicture.string( + service.svgIcon, + height: 24, + width: 24, + colorFilter: ColorFilter.mode( + Theme.of(context).colorScheme.onSurface, + BlendMode.srcIn, + ), + ) + : const Icon( + Icons.question_mark_outlined, ), - ) - : const Icon( - Icons.question_mark_outlined, - ), - title: Text( - 'backup.snapshot_service_title'.tr(), - ), - subtitle: Text( - service?.displayName ?? widget.snapshot.fallbackServiceName, - ), - ), - ListTile( - leading: Icon( - Icons.access_time_outlined, - color: Theme.of(context).colorScheme.onSurface, - ), - title: Text( - 'backup.snapshot_creation_time_title'.tr(), - ), - subtitle: Text( - '${MaterialLocalizations.of(context).formatShortDate(widget.snapshot.time)} ${TimeOfDay.fromDateTime(widget.snapshot.time).format(context)}', - ), - ), - SnapshotIdListTile(snapshotId: widget.snapshot.id), - if (service != null) - Column( - children: [ - const SizedBox(height: 8), - Text( - 'backup.snapshot_modal_select_strategy'.tr(), - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: 8), - _BackupStrategySelectionCard( - isSelected: selectedStrategy == - BackupRestoreStrategy.downloadVerifyOverwrite, - onTap: () { - setState(() { - selectedStrategy = - BackupRestoreStrategy.downloadVerifyOverwrite; - }); - }, - title: - 'backup.snapshot_modal_download_verify_option_title'.tr(), - subtitle: - 'backup.snapshot_modal_download_verify_option_description' - .tr(), - ), - const SizedBox(height: 8), - _BackupStrategySelectionCard( - isSelected: selectedStrategy == BackupRestoreStrategy.inplace, - onTap: () { - setState(() { - selectedStrategy = BackupRestoreStrategy.inplace; - }); - }, - title: 'backup.snapshot_modal_inplace_option_title'.tr(), - subtitle: - 'backup.snapshot_modal_inplace_option_description'.tr(), - ), - const SizedBox(height: 8), - // Restore backup button - BrandButton.filled( - onPressed: isServiceBusy - ? null - : () { - context.read().restoreBackup( - widget.snapshot.id, - selectedStrategy, - ); - Navigator.of(context).pop(); - getIt() - .showSnackBar('backup.restore_started'.tr()); - }, - text: 'backup.restore'.tr(), - ), - ], - ) - else - Padding( - padding: const EdgeInsets.all(16.0), - child: InfoBox( - isWarning: true, - text: 'backup.snapshot_modal_service_not_found'.tr(), + title: Text( + 'backup.snapshot_service_title'.tr(), ), - ) - ], + subtitle: Text( + service?.displayName ?? widget.snapshot.fallbackServiceName, + ), + ), + ListTile( + leading: Icon( + Icons.access_time_outlined, + color: Theme.of(context).colorScheme.onSurface, + ), + title: Text( + 'backup.snapshot_creation_time_title'.tr(), + ), + subtitle: Text( + '${MaterialLocalizations.of(context).formatShortDate(widget.snapshot.time)} ${TimeOfDay.fromDateTime(widget.snapshot.time).format(context)}', + ), + ), + SnapshotIdListTile(snapshotId: widget.snapshot.id), + if (service != null) + Column( + children: [ + const SizedBox(height: 8), + Text( + 'backup.snapshot_modal_select_strategy'.tr(), + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 8), + _BackupStrategySelectionCard( + isSelected: selectedStrategy == + BackupRestoreStrategy.downloadVerifyOverwrite, + onTap: () { + setState(() { + selectedStrategy = + BackupRestoreStrategy.downloadVerifyOverwrite; + }); + }, + title: + 'backup.snapshot_modal_download_verify_option_title'.tr(), + subtitle: + 'backup.snapshot_modal_download_verify_option_description' + .tr(), + ), + const SizedBox(height: 8), + _BackupStrategySelectionCard( + isSelected: selectedStrategy == BackupRestoreStrategy.inplace, + onTap: () { + setState(() { + selectedStrategy = BackupRestoreStrategy.inplace; + }); + }, + title: 'backup.snapshot_modal_inplace_option_title'.tr(), + subtitle: + 'backup.snapshot_modal_inplace_option_description'.tr(), + ), + const SizedBox(height: 8), + // Restore backup button + BrandButton.filled( + onPressed: isServiceBusy + ? null + : () { + context.read().restoreBackup( + widget.snapshot.id, + selectedStrategy, + ); + Navigator.of(context).pop(); + getIt() + .showSnackBar('backup.restore_started'.tr()); + }, + text: 'backup.restore'.tr(), + ), + ], + ) + else + Padding( + padding: const EdgeInsets.all(16.0), + child: InfoBox( + isWarning: true, + text: 'backup.snapshot_modal_service_not_found'.tr(), + ), + ) + ], + ), ); } }