separate swb file saving from encryption call, enable android swb file location selection, and clean up some of the swb ui code a bit
What changed, and why it matters
This commit rewrites how Stack Wallet creates its encrypted backup (.swb) files. The main functional change is that the encryption helper no longer writes the file itself; instead it returns the encrypted text, and the caller writes it to disk. On Android, the file is now written with a plain file API to a user-chosen location (a TODO comment notes that Storage Access Framework support is still pending). The change also enables Android users to pick a backup folder, which they could not do before. There is no direct evidence in the commit that this fixes a known security vulnerability, but it does change where sensitive backup data is stored and how file-overwrite behavior is handled.
Treat as a routine refactor with potential security side effects. Review the new Android file-writing path for unsafe directory selection, path traversal, and world-readable file permissions. Verify that the removed file-existence guard does not allow accidental or malicious overwrite of existing backups. Confirm whether the missing SAF implementation is tracked as a security item. No urgent patch is indicated solely from this diff.
Security signals we found
Sensitive backup ciphertext now written by caller code instead of library helper
Android backup location selection enabled, changing file storage scope
File-existence guard before backup write removed, allowing overwrite behavior change
TODO SAF comment indicates Android file writing still uses direct file API rather than Storage Access Framework
No explicit security advisory, CVE, or researcher attribution in commit or supplied references
Evidence from the diff
The patch refactors SWB.encryptStackWalletWithPassphrase and SWB.encryptStackWalletWithADK from Future!Platform.isAndroid && !Platform.isIOS to !Platform.isIOS, exposing directory selection on Android. A shared validateFail helper is introduced for input validation. The commit also removes the Tuple2-based decryptStackWalletWithPassphrase file API and switches decryptStackWalletStringWithPassphrase to a named-record parameter.
Changed components
lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dartlib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dartlib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dartlib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dartlib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_encrypted_string_view.dartlib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dartlib/pages_desktop_specific/password/forgotten_passphrase_restore_from_swb.dartlib/pages_desktop_specific/settings/settings_menu/backup_and_restore/create_auto_backup.dartlib/services/auto_swb_service.dartInspect captured patch +1360 / −1951
diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart
index 5616ccd..eae292f 100644
--- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart
+++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart
@@ -8,6 +8,7 @@
*
*/
+import 'dart:async';
import 'dart:convert';
import 'dart:io';
@@ -19,7 +20,6 @@ import 'package:stack_wallet_backup/stack_wallet_backup.dart';
import 'package:zxcvbn/zxcvbn.dart';
import '../../../../app_config.dart';
-import '../../../../notifications/show_flush_bar.dart';
import '../../../../providers/global/prefs_provider.dart';
import '../../../../providers/global/secure_store_provider.dart';
import '../../../../themes/stack_colors.dart';
@@ -28,6 +28,7 @@ import '../../../../utilities/constants.dart';
import '../../../../utilities/flutter_secure_storage_interface.dart';
import '../../../../utilities/format.dart';
import '../../../../utilities/logger.dart';
+import '../../../../utilities/show_loading.dart';
import '../../../../utilities/text_styles.dart';
import '../../../../utilities/util.dart';
import '../../../../widgets/background.dart';
@@ -77,6 +78,108 @@ class _EnableAutoBackupViewState extends ConsumerState<CreateAutoBackupView> {
passwordRepeatController.text.isNotEmpty;
}
+ Future<void> _createEnableAutoBackup() async {
+ final String pathToSave = fileLocationController.text;
+ final String passphrase = passwordController.text;
+ final String repeatPassphrase = passwordRepeatController.text;
+
+ if (validateFail(context, pathToSave, passphrase, repeatPassphrase)) return;
+
+ if (mounted) {
+ final now = DateTime.now();
+ Exception? ex;
+ final savedPath = await showLoading(
+ whileFuture: () async {
+ String adkString;
+ int adkVersion;
+ try {
+ final adk = await compute(generateAdk, passphrase);
+ adkString = Format.uint8listToString(adk.item2);
+ adkVersion = adk.item1;
+ } on Exception catch (e, s) {
+ final String err = getErrorMessageFromSWBException(e);
+ Logging.instance.e(err, error: e, stackTrace: s);
+ rethrow;
+ }
+
+ await secureStore.write(key: "auto_adk_string", value: adkString);
+ await secureStore.write(
+ key: "auto_adk_version_string",
+ value: adkVersion.toString(),
+ );
+
+ final fileToSavePath = createAutoBackupFilename(pathToSave, now);
+
+ final backup = await SWB.createStackWalletJSON(
+ secureStorage: secureStore,
+ );
+
+ final encryptedDataString = await SWB.encryptStackWalletWithADK(
+ adkString,
+ jsonEncode(backup),
+ adkVersion,
+ );
+
+ if (Platform.isAndroid) {
+ // TODO SAF
+ File(
+ fileToSavePath,
+ ).writeAsStringSync(encryptedDataString, flush: true);
+ } else {
+ File(
+ fileToSavePath,
+ ).writeAsStringSync(encryptedDataString, flush: true);
+ }
+
+ return fileToSavePath;
+ }(),
+ context: context,
+ message: "Encrypting initial backup",
+ subMessage: "This shouldn't take long",
+ delay: const Duration(seconds: 1),
+ onException: (e) => ex = e,
+ );
+
+ if (mounted) {
+ // pop encryption progress dialog
+ Navigator.of(context).pop();
+
+ if (savedPath != null) {
+ ref.read(prefsChangeNotifierProvider).autoBackupLocation = savedPath;
+ ref.read(prefsChangeNotifierProvider).lastAutoBackup = now;
+
+ ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled = true;
+
+ await showDialog<dynamic>(
+ context: context,
+ barrierDismissible: false,
+ builder: (_) => StackOkDialog(
+ title: "${AppConfig.prefix} Auto Backup enabled and saved to:",
+ message: savedPath,
+ ),
+ );
+ if (mounted) {
+ passwordController.text = "";
+ passwordRepeatController.text = "";
+
+ Navigator.of(
+ context,
+ ).popUntil(ModalRoute.withName(AutoBackupView.routeName));
+ }
+ } else {
+ await showDialog<dynamic>(
+ context: context,
+ barrierDismissible: false,
+ builder: (_) => StackOkDialog(
+ title: "Failed to enable Auto Backup",
+ message: ex?.toString(),
+ ),
+ );
+ }
+ }
+ }
+ }
+
@override
void initState() {
secureStore = ref.read(secureStoreProvider);
@@ -88,7 +191,7 @@ class _EnableAutoBackupViewState extends ConsumerState<CreateAutoBackupView> {
passwordFocusNode = FocusNode();
passwordRepeatFocusNode = FocusNode();
- if (Platform.isAndroid || Platform.isIOS) {
+ if (Platform.isIOS) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
final dir = await stackFileSystem.prepareStorage();
if (mounted) {
@@ -151,38 +254,36 @@ class _EnableAutoBackupViewState extends ConsumerState<CreateAutoBackupView> {
style: STextStyles.smallMed12(context),
),
const SizedBox(height: 10),
- if (!Platform.isAndroid && !Platform.isIOS)
+ if (!Platform.isIOS)
TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
- onTap:
- Platform.isAndroid || Platform.isIOS
- ? null
- : () async {
- try {
- await stackFileSystem
- .prepareStorage();
-
- if (mounted) {
- await stackFileSystem.pickDir(
- context,
- );
- }
-
- if (mounted) {
- setState(() {
- fileLocationController.text =
- stackFileSystem.dirPath ?? "";
- });
- }
- } catch (e, s) {
- Logging.instance.e(
- "$e\n$s",
- error: e,
- stackTrace: s,
+ onTap: Platform.isIOS
+ ? null
+ : () async {
+ try {
+ await stackFileSystem.prepareStorage();
+
+ if (mounted) {
+ await stackFileSystem.pickDir(
+ context,
);
}
- },
+
+ if (mounted) {
+ setState(() {
+ fileLocationController.text =
+ stackFileSystem.dirPath ?? "";
+ });
+ }
+ } catch (e, s) {
+ Logging.instance.e(
+ "$e\n$s",
+ error: e,
+ stackTrace: s,
+ );
+ }
+ },
controller: fileLocationController,
style: STextStyles.field(context),
decoration: InputDecoration(
@@ -194,10 +295,9 @@ class _EnableAutoBackupViewState extends ConsumerState<CreateAutoBackupView> {
const SizedBox(width: 16),
SvgPicture.asset(
Assets.svg.folder,
- color:
- Theme.of(context)
- .extension<StackColors>()!
- .textDark3,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textDark3,
width: 16,
height: 16,
),
@@ -218,8 +318,7 @@ class _EnableAutoBackupViewState extends ConsumerState<CreateAutoBackupView> {
),
onChanged: (newValue) {},
),
- if (!Platform.isAndroid && !Platform.isIOS)
- const SizedBox(height: 10),
+ if (!Platform.isIOS) const SizedBox(height: 10),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
@@ -232,41 +331,41 @@ class _EnableAutoBackupViewState extends ConsumerState<CreateAutoBackupView> {
obscureText: hidePassword,
enableSuggestions: false,
autocorrect: false,
- decoration: standardInputDecoration(
- "Create passphrase",
- passwordFocusNode,
- context,
- ).copyWith(
- suffixIcon: UnconstrainedBox(
- child: Row(
- children: [
- const SizedBox(width: 16),
- GestureDetector(
- key: const Key(
- "createBackupPasswordFieldShowPasswordButtonKey",
- ),
- onTap: () async {
- setState(() {
- hidePassword = !hidePassword;
- });
- },
- child: SvgPicture.asset(
- hidePassword
- ? Assets.svg.eye
- : Assets.svg.eyeSlash,
- color:
- Theme.of(context)
+ decoration:
+ standardInputDecoration(
+ "Create passphrase",
+ passwordFocusNode,
+ context,
+ ).copyWith(
+ suffixIcon: UnconstrainedBox(
+ child: Row(
+ children: [
+ const SizedBox(width: 16),
+ GestureDetector(
+ key: const Key(
+ "createBackupPasswordFieldShowPasswordButtonKey",
+ ),
+ onTap: () async {
+ setState(() {
+ hidePassword = !hidePassword;
+ });
+ },
+ child: SvgPicture.asset(
+ hidePassword
+ ? Assets.svg.eye
+ : Assets.svg.eyeSlash,
+ color: Theme.of(context)
.extension<StackColors>()!
.textDark3,
- width: 16,
- height: 16,
- ),
+ width: 16,
+ height: 16,
+ ),
+ ),
+ const SizedBox(width: 12),
+ ],
),
- const SizedBox(width: 12),
- ],
+ ),
),
- ),
- ),
onChanged: (newValue) {
if (newValue.isEmpty) {
setState(() {
@@ -317,13 +416,12 @@ class _EnableAutoBackupViewState extends ConsumerState<CreateAutoBackupView> {
right: 12,
top: passwordFeedback.isNotEmpty ? 4 : 0,
),
- child:
- passwordFeedback.isNotEmpty
- ? Text(
- passwordFeedback,
- style: STextStyles.infoSmall(context),
- )
- : null,
+ child: passwordFeedback.isNotEmpty
+ ? Text(
+ passwordFeedback,
+ style: STextStyles.infoSmall(context),
+ )
+ : null,
),
if (passwordFocusNode.hasFocus ||
passwordRepeatFocusNode.hasFocus ||
@@ -339,26 +437,23 @@ class _EnableAutoBackupViewState extends ConsumerState<CreateAutoBackupView> {
width:
MediaQuery.of(context).size.width - 32 - 24,
height: 5,
- fillColor:
- passwordStrength < 0.51
- ? Theme.of(context)
- .extension<StackColors>()!
- .accentColorRed
- : passwordStrength < 1
- ? Theme.of(context)
- .extension<StackColors>()!
- .accentColorYellow
- : Theme.of(context)
- .extension<StackColors>()!
- .accentColorGreen,
- backgroundColor:
- Theme.of(context)
- .extension<StackColors>()!
- .buttonBackSecondary,
- percent:
- passwordStrength < 0.25
- ? 0.03
- : passwordStrength,
+ fillColor: passwordStrength < 0.51
+ ? Theme.of(
+ context,
+ ).extension<StackColors>()!.accentColorRed
+ : passwordStrength < 1
+ ? Theme.of(context)
+ .extension<StackColors>()!
+ .accentColorYellow
+ : Theme.of(context)
+ .extension<StackColors>()!
+ .accentColorGreen,
+ backgroundColor: Theme.of(
+ context,
+ ).extension<StackColors>()!.buttonBackSecondary,
+ percent: passwordStrength < 0.25
+ ? 0.03
+ : passwordStrength,
),
),
const SizedBox(height: 10),
@@ -374,41 +469,41 @@ class _EnableAutoBackupViewState extends ConsumerState<CreateAutoBackupView> {
obscureText: hidePassword,
enableSuggestions: false,
autocorrect: false,
- decoration: standardInputDecoration(
- "Confirm passphrase",
- passwordRepeatFocusNode,
- context,
- ).copyWith(
- suffixIcon: UnconstrainedBox(
- child: Row(
- children: [
- const SizedBox(width: 16),
- GestureDetector(
- key: const Key(
- "createBackupPasswordFieldShowPasswordButtonKey",
- ),
- onTap: () async {
- setState(() {
- hidePassword = !hidePassword;
- });
- },
- child: SvgPicture.asset(
- hidePassword
- ? Assets.svg.eye
- : Assets.svg.eyeSlash,
- color:
- Theme.of(context)
+ decoration:
+ standardInputDecoration(
+ "Confirm passphrase",
+ passwordRepeatFocusNode,
+ context,
+ ).copyWith(
+ suffixIcon: UnconstrainedBox(
+ child: Row(
+ children: [
+ const SizedBox(width: 16),
+ GestureDetector(
+ key: const Key(
+ "createBackupPasswordFieldShowPasswordButtonKey",
+ ),
+ onTap: () async {
+ setState(() {
+ hidePassword = !hidePassword;
+ });
+ },
+ child: SvgPicture.asset(
+ hidePassword
+ ? Assets.svg.eye
+ : Assets.svg.eyeSlash,
+ color: Theme.of(context)
.extension<StackColors>()!
.textDark3,
- width: 16,
- height: 16,
- ),
+ width: 16,
+ height: 16,
+ ),
+ ),
+ const SizedBox(width: 12),
+ ],
),
- const SizedBox(width: 12),
- ],
+ ),
),
- ),
- ),
onChanged: (newValue) {
setState(() {});
// TODO: ? check if passwords match?
@@ -425,17 +520,17 @@ class _EnableAutoBackupViewState extends ConsumerState<CreateAutoBackupView> {
children: [
TextField(
autocorrect: Util.isDesktop ? false : true,
- enableSuggestions:
- Util.isDesktop ? false : true,
+ enableSuggestions: Util.isDesktop
+ ? false
+ : true,
readOnly: true,
textInputAction: TextInputAction.none,
),
Positioned.fill(
child: RawMaterialButton(
- splashColor:
- Theme.of(
- context,
- ).extension<StackColors>()!.highlight,
+ splashColor: Theme.of(
+ context,
+ ).extension<StackColors>()!.highlight,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
@@ -450,9 +545,8 @@ class _EnableAutoBackupViewState extends ConsumerState<CreateAutoBackupView> {
top: Radius.circular(20),
),
),
- builder:
- (_) =>
- const BackupFrequencyTypeSelectSheet(),
+ builder: (_) =>
+ const BackupFrequencyTypeSelectSheet(),
);
},
child: Padding(
@@ -466,10 +560,11 @@ class _EnableAutoBackupViewState extends ConsumerState<CreateAutoBackupView> {
Text(
Format.prettyFrequencyType(
ref.watch(
- prefsChangeNotifierProvider.select(
- (value) =>
- value.backupFrequencyType,
- ),
+ prefsChangeNotifierProvider
+ .select(
+ (value) => value
+ .backupFrequencyType,
+ ),
),
),
style: STextStyles.itemSubtitle12(
@@ -482,10 +577,9 @@ class _EnableAutoBackupViewState extends ConsumerState<CreateAutoBackupView> {
),
child: SvgPicture.asset(
Assets.svg.chevronDown,
- color:
- Theme.of(context)
- .extension<StackColors>()!
- .textSubtitle2,
+ color: Theme.of(context)
+ .extension<StackColors>()!
+ .textSubtitle2,
width: 12,
height: 6,
),
@@ -500,207 +594,16 @@ class _EnableAutoBackupViewState extends ConsumerState<CreateAutoBackupView> {
const Spacer(),
const SizedBox(height: 10),
TextButton(
- style:
- shouldEnableCreate
- ? Theme.of(context)
- .extension<StackColors>()!
- .getPrimaryEnabledButtonStyle(context)
- : Theme.of(context)
- .extension<StackColors>()!
- .getPrimaryDisabledButtonStyle(context),
- onPressed:
- !shouldEnableCreate
- ? null
- : () async {
- final String pathToSave =
- fileLocationController.text;
- final String passphrase =
- passwordController.text;
- final String repeatPassphrase =
- passwordRepeatController.text;
-
- if (pathToSave.isEmpty) {
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: "Directory not chosen",
- context: context,
- );
- return;
- }
- if (!(await Directory(
- pathToSave,
- ).exists())) {
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: "Directory does not exist",
- context: context,
- );
- return;
- }
- if (passphrase.isEmpty) {
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: "A passphrase is required",
- context: context,
- );
- return;
- }
- if (passphrase != repeatPassphrase) {
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: "Passphrase does not match",
- context: context,
- );
- return;
- }
-
- showDialog<dynamic>(
- context: context,
- barrierDismissible: false,
- builder:
- (_) => const StackDialog(
- title:
- "Encrypting initial backup",
- message:
- "This shouldn't take long",
- ),
- );
-
- // make sure the dialog is able to be displayed for at least some time
- final fut = Future<void>.delayed(
- const Duration(milliseconds: 300),
- );
-
- String adkString;
- int adkVersion;
- try {
- final adk = await compute(
- generateAdk,
- passphrase,
- );
- adkString = Format.uint8listToString(
- adk.item2,
- );
- adkVersion = adk.item1;
- } on Exception catch (e, s) {
- final String err =
- getErrorMessageFromSWBException(e);
- Logging.instance.e(
- "$err\n$s",
- error: e,
- stackTrace: s,
- );
- // pop encryption progress dialog
- Navigator.of(context).pop();
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: err,
- context: context,
- );
- return;
- } catch (e, s) {
- Logging.instance.e(
- "",
- error: e,
- stackTrace: s,
- );
- // pop encryption progress dialog
- Navigator.of(context).pop();
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: "$e",
- context: context,
- );
- return;
- }
-
- await secureStore.write(
- key: "auto_adk_string",
- value: adkString,
- );
- await secureStore.write(
- key: "auto_adk_version_string",
- value: adkVersion.toString(),
- );
-
- final DateTime now = DateTime.now();
- final String fileToSave =
- createAutoBackupFilename(
- pathToSave,
- now,
- );
-
- final backup = await SWB
- .createStackWalletJSON(
- secureStorage: secureStore,
- );
-
- final bool result = await SWB
- .encryptStackWalletWithADK(
- fileToSave,
- adkString,
- jsonEncode(backup),
- adkVersion,
- );
-
- // this future should already be complete unless there was an error encrypting
- await Future.wait([fut]);
-
- if (mounted) {
- // pop encryption progress dialog
- Navigator.of(context).pop();
-
- if (result) {
- ref
- .read(prefsChangeNotifierProvider)
- .autoBackupLocation = pathToSave;
- ref
- .read(prefsChangeNotifierProvider)
- .lastAutoBackup = now;
-
- ref
- .read(prefsChangeNotifierProvider)
- .isAutoBackupEnabled = true;
-
- await showDialog<dynamic>(
- context: context,
- barrierDismissible: false,
- builder:
- (_) =>
- Platform.isAndroid
- ? StackOkDialog(
- title:
- "${AppConfig.prefix} Auto Backup enabled and saved to:",
- message: fileToSave,
- )
- : const StackOkDialog(
- title:
- "${AppConfig.prefix} Auto Backup enabled!",
- ),
- );
- if (mounted) {
- passwordController.text = "";
- passwordRepeatController.text = "";
-
- Navigator.of(context).popUntil(
- ModalRoute.withName(
- AutoBackupView.routeName,
- ),
- );
- }
- } else {
- await showDialog<dynamic>(
- context: context,
- barrierDismissible: false,
- builder:
- (_) => const StackOkDialog(
- title:
- "Failed to enable Auto Backup",
- ),
- );
- }
- }
- },
+ style: shouldEnableCreate
+ ? Theme.of(context)
+ .extension<StackColors>()!
+ .getPrimaryEnabledButtonStyle(context)
+ : Theme.of(context)
+ .extension<StackColors>()!
+ .getPrimaryDisabledButtonStyle(context),
+ onPressed: !shouldEnableCreate
+ ? null
+ : _createEnableAutoBackup,
child: Text(
"Enable Auto Backup",
style: STextStyles.button(context),
diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart
index 57a8017..f2d2bc2 100644
--- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart
+++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart
@@ -18,12 +18,12 @@ import 'package:flutter_svg/svg.dart';
import 'package:zxcvbn/zxcvbn.dart';
import '../../../../app_config.dart';
-import '../../../../notifications/show_flush_bar.dart';
import '../../../../providers/global/secure_store_provider.dart';
import '../../../../themes/stack_colors.dart';
import '../../../../utilities/assets.dart';
import '../../../../utilities/constants.dart';
import '../../../../utilities/logger.dart';
+import '../../../../utilities/show_loading.dart';
import '../../../../utilities/text_styles.dart';
import '../../../../utilities/util.dart';
import '../../../../widgets/background.dart';
@@ -38,16 +38,16 @@ import '../../../../widgets/stack_text_field.dart';
import 'helpers/restore_create_backup.dart';
import 'helpers/swb_file_system.dart';
-class CreateBackupView extends StatefulWidget {
+class CreateBackupView extends ConsumerStatefulWidget {
const CreateBackupView({super.key});
static const String routeName = "/createBackup";
@override
- State<CreateBackupView> createState() => _RestoreFromFileViewState();
+ ConsumerState<CreateBackupView> createState() => _RestoreFromFileViewState();
}
-class _RestoreFromFileViewState extends State<CreateBackupView> {
+class _RestoreFromFileViewState extends ConsumerState<CreateBackupView> {
late final TextEditingController fileLocationController;
late final TextEditingController passwordController;
late final TextEditingController passwordRepeatController;
@@ -72,6 +72,121 @@ class _RestoreFromFileViewState extends State<CreateBackupView> {
passwordRepeatController.text.isNotEmpty;
}
+ Future<void> _createBackup() async {
+ final String pathToSave = fileLocationController.text;
+ final String passphrase = passwordController.text;
+ final String repeatPassphrase = passwordRepeatController.text;
+
+ if (validateFail(context, pathToSave, passphrase, repeatPassphrase)) return;
+
+ if (mounted) {
+ Exception? ex;
+ final savedPath = await showLoading(
+ whileFuture: () async {
+ final DateTime now = DateTime.now();
+ final String fileToSavePath =
+ "$pathToSave/stackbackup"
+ "_${now.year}"
+ "_${now.month}"
+ "_${now.day}"
+ "_${now.hour}"
+ "_${now.minute}"
+ "_${now.second}.swb";
+
+ final backup = await SWB.createStackWalletJSON(
+ secureStorage: ref.read(secureStoreProvider),
+ );
+
+ final encryptedDataString = await SWB
+ .encryptStackWalletWithPassphrase(passphrase, jsonEncode(backup));
+
+ if (Platform.isAndroid) {
+ // TODO SAF
+ File(
+ fileToSavePath,
+ ).writeAsStringSync(encryptedDataString, flush: true);
+ } else {
+ File(
+ fileToSavePath,
+ ).writeAsStringSync(encryptedDataString, flush: true);
+ }
+
+ return fileToSavePath;
+ }(),
+ context: context,
+ message: "Encrypting backup",
+ subMessage: "This shouldn't take long",
+ delay: const Duration(seconds: 1),
+ onException: (e) => ex = e,
+ );
+
+ if (mounted) {
+ if (savedPath != null) {
+ await showDialog<dynamic>(
+ context: context,
+ barrierDismissible: false,
+ builder: (_) => !Util.isDesktop
+ ? StackOkDialog(title: "Backup saved to:", message: savedPath)
+ : DesktopDialog(
+ maxHeight: double.infinity,
+ maxWidth: 500,
+ child: Padding(
+ padding: const EdgeInsets.only(
+ left: 32,
+ right: 32,
+ bottom: 32,
+ ),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const SizedBox(height: 26),
+ Text(
+ "${AppConfig.prefix} backup saved to: \n",
+ style: STextStyles.desktopH3(context),
+ ),
+ Text(
+ savedPath,
+ style: STextStyles.desktopTextExtraExtraSmall(
+ context,
+ ),
+ ),
+ const SizedBox(height: 40),
+ Row(
+ children: [
+ Expanded(
+ child: PrimaryButton(
+ label: "Ok",
+ buttonHeight: ButtonHeight.l,
+ onPressed: Navigator.of(context).pop,
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ passwordController.text = "";
+ passwordRepeatController.text = "";
+ if (mounted) {
+ setState(() {});
+ }
+ } else {
+ await showDialog<dynamic>(
+ context: context,
+ barrierDismissible: false,
+ builder: (_) => StackOkDialog(
+ title: "Backup creation failed",
+ message: ex?.toString() ?? "Unexpected error",
+ ),
+ );
+ }
+ }
+ }
+ }
+
@override
void initState() {
stackFileSystem = SWBFileSystem();
@@ -82,7 +197,7 @@ class _RestoreFromFileViewState extends State<CreateBackupView> {
passwordFocusNode = FocusNode();
passwordRepeatFocusNode = FocusNode();
- if (Platform.isAndroid || Platform.isIOS) {
+ if (Platform.isIOS) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
final dir = await stackFileSystem.prepareStorage();
if (mounted) {
@@ -129,7 +244,7 @@ class _RestoreFromFileViewState extends State<CreateBackupView> {
const Duration(milliseconds: 75),
);
}
- if (mounted) {
+ if (context.mounted) {
Navigator.of(context).pop();
}
},
@@ -184,7 +299,7 @@ class _RestoreFromFileViewState extends State<CreateBackupView> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
- if (!Platform.isAndroid && !Platform.isIOS)
+ if (!Platform.isIOS)
Consumer(
builder: (context, ref, __) {
return Container(
@@ -192,13 +307,13 @@ class _RestoreFromFileViewState extends State<CreateBackupView> {
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
- onTap: Platform.isAndroid || Platform.isIOS
+ onTap: Platform.isIOS
? null
: () async {
try {
await stackFileSystem.prepareStorage();
- if (mounted) {
+ if (context.mounted) {
await stackFileSystem.pickDir(context);
}
@@ -456,135 +571,19 @@ class _RestoreFromFileViewState extends State<CreateBackupView> {
const SizedBox(height: 16),
if (!isDesktop) const Spacer(),
!isDesktop
- ? Consumer(
- builder: (context, ref, __) {
- return TextButton(
- style: shouldEnableCreate
- ? Theme.of(context)
- .extension<StackColors>()!
- .getPrimaryEnabledButtonStyle(context)
- : Theme.of(context)
- .extension<StackColors>()!
- .getPrimaryDisabledButtonStyle(context),
- onPressed: !shouldEnableCreate
- ? null
- : () async {
- final String pathToSave =
- fileLocationController.text;
- final String passphrase =
- passwordController.text;
- final String repeatPassphrase =
- passwordRepeatController.text;
-
- if (pathToSave.isEmpty) {
- unawaited(
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: "Directory not chosen",
- context: context,
- ),
- );
- return;
- }
- if (!(await Directory(pathToSave).exists())) {
- unawaited(
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: "Directory does not exist",
- context: context,
- ),
- );
- return;
- }
- if (passphrase.isEmpty) {
- unawaited(
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: "A passphrase is required",
- context: context,
- ),
- );
- return;
- }
- if (passphrase != repeatPassphrase) {
- unawaited(
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: "Passphrase does not match",
- context: context,
- ),
- );
- return;
- }
-
- unawaited(
- showDialog<dynamic>(
- context: context,
- barrierDismissible: false,
- builder: (_) => const StackDialog(
- title: "Encrypting backup",
- message: "This shouldn't take long",
- ),
- ),
- );
- // make sure the dialog is able to be displayed for at least 1 second
- await Future<void>.delayed(
- const Duration(seconds: 1),
- );
-
- final DateTime now = DateTime.now();
- final String fileToSave =
- "$pathToSave/stackbackup_${now.year}_${now.month}_${now.day}_${now.hour}_${now.minute}_${now.second}.swb";
-
- final backup = await SWB.createStackWalletJSON(
- secureStorage: ref.read(secureStoreProvider),
- );
-
- final bool result = await SWB
- .encryptStackWalletWithPassphrase(
- fileToSave,
- passphrase,
- jsonEncode(backup),
- );
-
- if (mounted) {
- // pop encryption progress dialog
- if (!isDesktop) Navigator.of(context).pop();
-
- if (result) {
- await showDialog<dynamic>(
- context: context,
- barrierDismissible: false,
- builder: (_) => Platform.isAndroid
- ? StackOkDialog(
- title: "Backup saved to:",
- message: fileToSave,
- )
- : const StackOkDialog(
- title:
- "Backup creation succeeded",
- ),
- );
- passwordController.text = "";
- passwordRepeatController.text = "";
- setState(() {});
- } else {
- await showDialog<dynamic>(
- context: context,
- barrierDismissible: false,
- builder: (_) => const StackOkDialog(
- title: "Backup creation failed",
- ),
- );
- }
- }
- },
- child: Text(
- "Create backup",
- style: STextStyles.button(context),
- ),
- );
- },
+ ? TextButton(
+ style: shouldEnableCreate
+ ? Theme.of(context)
+ .extension<StackColors>()!
+ .getPrimaryEnabledButtonStyle(context)
+ : Theme.of(context)
+ .extension<StackColors>()!
+ .getPrimaryDisabledButtonStyle(context),
+ onPressed: !shouldEnableCreate ? null : _createBackup,
+ child: Text(
+ "Create backup",
+ style: STextStyles.button(context),
+ ),
)
: Row(
children: [
@@ -597,238 +596,7 @@ class _RestoreFromFileViewState extends State<CreateBackupView> {
enabled: shouldEnableCreate,
onPressed: !shouldEnableCreate
? null
- : () async {
- final String pathToSave =
- fileLocationController.text;
- final String passphrase =
- passwordController.text;
- final String repeatPassphrase =
- passwordRepeatController.text;
-
- if (pathToSave.isEmpty) {
- unawaited(
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: "Directory not chosen",
- context: context,
- ),
- );
- return;
- }
- if (!(await Directory(
- pathToSave,
- ).exists())) {
- unawaited(
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: "Directory does not exist",
- context: context,
- ),
- );
- return;
- }
- if (passphrase.isEmpty) {
- unawaited(
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: "A passphrase is required",
- context: context,
- ),
- );
- return;
- }
- if (passphrase != repeatPassphrase) {
- unawaited(
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: "Passphrase does not match",
- context: context,
- ),
- );
- return;
- }
-
- unawaited(
- showDialog<dynamic>(
- context: context,
- barrierDismissible: false,
- builder: (_) {
- if (Util.isDesktop) {
- return DesktopDialog(
- maxHeight: double.infinity,
- maxWidth: 450,
- child: Padding(
- padding: const EdgeInsets.all(
- 32,
- ),
- child: Column(
- mainAxisSize:
- MainAxisSize.min,
- crossAxisAlignment:
- CrossAxisAlignment.start,
- children: [
- Text(
- "Encrypting initial backup",
- style:
- STextStyles.desktopH3(
- context,
- ),
- ),
- const SizedBox(height: 40),
- Text(
- "This shouldn't take long",
- style:
- STextStyles.desktopTextExtraExtraSmall(
- context,
- ),
- ),
- ],
- ),
- ),
- );
- } else {
- return const StackDialog(
- title:
- "Encrypting initial backup",
- message:
- "This shouldn't take long",
- );
- }
- },
- ),
- );
-
- await Future<void>.delayed(
- const Duration(seconds: 1),
- );
-
- // make sure the dialog is able to be displayed for at least 1 second
- final fut = Future<void>.delayed(
- const Duration(seconds: 1),
- );
-
- final DateTime now = DateTime.now();
- final String fileToSave =
- "$pathToSave/stackbackup_${now.year}_${now.month}_${now.day}_${now.hour}_${now.minute}_${now.second}.swb";
-
- final backup = await SWB
- .createStackWalletJSON(
- secureStorage: ref.read(
- secureStoreProvider,
- ),
- );
-
- final bool result = await SWB
- .encryptStackWalletWithPassphrase(
- fileToSave,
- passphrase,
- jsonEncode(backup),
- );
-
- await Future.wait([fut]);
-
- if (mounted) {
- // pop encryption progress dialog
- if (!isDesktop)
- Navigator.of(context).pop();
-
- if (result) {
- await showDialog<dynamic>(
- context: context,
- barrierDismissible: false,
- builder: (context) {
- if (Platform.isAndroid) {
- return StackOkDialog(
- title: "Backup saved to:",
- message: fileToSave,
- );
- } else if (isDesktop) {
- return DesktopDialog(
- maxHeight: double.infinity,
- maxWidth: 500,
- child: Padding(
- padding:
- const EdgeInsets.only(
- left: 32,
- right: 32,
- bottom: 32,
- ),
- child: Column(
- mainAxisSize:
- MainAxisSize.min,
- crossAxisAlignment:
- CrossAxisAlignment
- .start,
- children: [
- const SizedBox(
- height: 26,
- ),
- Text(
- "${AppConfig.prefix} backup saved to: \n",
- style:
- STextStyles.desktopH3(
- context,
- ),
- ),
- Text(
- fileToSave,
- style:
- STextStyles.desktopTextExtraExtraSmall(
- context,
- ),
- ),
- const SizedBox(
- height: 40,
- ),
- Row(
- children: [
- // const Spacer(),
- Expanded(
- child: PrimaryButton(
- label: "Ok",
- buttonHeight:
- ButtonHeight
- .l,
- onPressed: () {
- int count = 0;
- Navigator.of(
- context,
- ).popUntil(
- (_) =>
- count++ >=
- 2,
- );
- },
- ),
- ),
- ],
- ),
- ],
- ),
- ),
- );
- } else {
- return const StackOkDialog(
- title:
- "Backup creation succeeded",
- );
- }
- },
- );
- passwordController.text = "";
- passwordRepeatController.text = "";
- setState(() {});
- } else {
- await showDialog<dynamic>(
- context: context,
- barrierDismissible: false,
- builder: (_) => const StackOkDialog(
- title: "Backup creation failed",
- ),
- );
- }
- }
- },
+ : _createBackup,
);
},
),
diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart
index 5ad7eab..e1d5fd3 100644
--- a/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart
+++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart
@@ -8,7 +8,6 @@
*
*/
-import 'dart:async';
import 'dart:convert';
import 'dart:io';
@@ -21,7 +20,6 @@ import 'package:stack_wallet_backup/stack_wallet_backup.dart';
import 'package:zxcvbn/zxcvbn.dart';
import '../../../../app_config.dart';
-import '../../../../notifications/show_flush_bar.dart';
import '../../../../providers/global/prefs_provider.dart';
import '../../../../providers/global/secure_store_provider.dart';
import '../../../../themes/stack_colors.dart';
@@ -31,6 +29,7 @@ import '../../../../utilities/enums/backup_frequency_type.dart';
import '../../../../utilities/flutter_secure_storage_interface.dart';
import '../../../../utilities/format.dart';
import '../../../../utilities/logger.dart';
+import '../../../../utilities/show_loading.dart';
import '../../../../utilities/text_styles.dart';
import '../../../../utilities/util.dart';
import '../../../../widgets/background.dart';
@@ -95,158 +94,101 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> {
final String passphrase = passwordController.text;
final String repeatPassphrase = passwordRepeatController.text;
- if (pathToSave.isEmpty) {
- unawaited(
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: "Directory not chosen",
- context: context,
- ),
- );
- return;
- }
- if (!(await Directory(pathToSave).exists())) {
- unawaited(
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: "Directory does not exist",
- context: context,
- ),
- );
- return;
- }
- if (passphrase.isEmpty) {
- unawaited(
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: "A passphrase is required",
- context: context,
- ),
- );
- return;
- }
- if (passphrase != repeatPassphrase) {
- unawaited(
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: "Passphrase does not match",
- context: context,
- ),
- );
- return;
- }
+ if (validateFail(context, pathToSave, passphrase, repeatPassphrase)) return;
- unawaited(
- showDialog<dynamic>(
- context: context,
- barrierDismissible: false,
- builder:
- (_) => const StackDialog(
- title: "Updating Auto Backup",
- message: "This shouldn't take long",
- ),
- ),
- );
- // make sure the dialog is able to be displayed for at least 1 second
- final fut = Future<void>.delayed(const Duration(seconds: 1));
-
- String adkString;
- int adkVersion;
- try {
- final adk = await compute(generateAdk, passphrase);
- adkString = Format.uint8listToString(adk.item2);
- adkVersion = adk.item1;
- } on Exception catch (e, s) {
- final String err = getErrorMessageFromSWBException(e);
- Logging.instance.e("$err\n$s", error: e, stackTrace: s);
- // pop encryption progress dialog
- Navigator.of(context).pop();
- unawaited(
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: err,
- context: context,
- ),
- );
- return;
- } catch (e, s) {
- Logging.instance.e("$e\n$s", error: e, stackTrace: s);
- // pop encryption progress dialog
- Navigator.of(context).pop();
- unawaited(
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: "$e",
- context: context,
- ),
- );
- return;
- }
+ if (mounted) {
+ final now = DateTime.now();
+ Exception? ex;
+ final savedPath = await showLoading(
+ whileFuture: () async {
+ String adkString;
+ int adkVersion;
+ try {
+ final adk = await compute(generateAdk, passphrase);
+ adkString = Format.uint8listToString(adk.item2);
+ adkVersion = adk.item1;
+ } on Exception catch (e, s) {
+ final String err = getErrorMessageFromSWBException(e);
+ Logging.instance.e(err, error: e, stackTrace: s);
+ rethrow;
+ }
- await secureStore.write(key: "auto_adk_string", value: adkString);
- await secureStore.write(
- key: "auto_adk_version_string",
- value: adkVersion.toString(),
- );
+ await secureStore.write(key: "auto_adk_string", value: adkString);
+ await secureStore.write(
+ key: "auto_adk_version_string",
+ value: adkVersion.toString(),
+ );
+
+ final fileToSavePath = createAutoBackupFilename(pathToSave, now);
+
+ final backup = await SWB.createStackWalletJSON(
+ secureStorage: secureStore,
+ );
+
+ final encryptedDataString = await SWB.encryptStackWalletWithADK(
+ adkString,
+ jsonEncode(backup),
+ adkVersion,
+ );
+
+ if (Platform.isAndroid) {
+ // TODO SAF
+ File(
+ fileToSavePath,
+ ).writeAsStringSync(encryptedDataString, flush: true);
+ } else {
+ File(
+ fileToSavePath,
+ ).writeAsStringSync(encryptedDataString, flush: true);
+ }
- final DateTime now = DateTime.now();
- final String fileToSave = createAutoBackupFilename(pathToSave, now);
+ return fileToSavePath;
+ }(),
+ context: context,
+ message: "Updating Auto Backup",
+ subMessage: "This shouldn't take long",
+ delay: const Duration(seconds: 1),
+ onException: (e) => ex = e,
+ );
- final backup = await SWB.createStackWalletJSON(
- secureStorage: ref.read(secureStoreProvider),
- );
+ if (mounted) {
+ // pop encryption progress dialog
+ Navigator.of(context).pop();
- final bool result = await SWB.encryptStackWalletWithADK(
- fileToSave,
- adkString,
- jsonEncode(backup),
- adkVersion,
- );
+ if (savedPath != null) {
+ ref.read(prefsChangeNotifierProvider).autoBackupLocation = pathToSave;
+ ref.read(prefsChangeNotifierProvider).lastAutoBackup = now;
- // this future should already be complete unless there was an error encrypting
- await Future.wait([fut]);
+ ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled = true;
- if (mounted) {
- // pop encryption progress dialog
- Navigator.of(context).pop();
-
- if (result) {
- ref.read(prefsChangeNotifierProvider).autoBackupLocation = pathToSave;
- ref.read(prefsChangeNotifierProvider).lastAutoBackup = now;
-
- ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled = true;
-
- await showDialog<dynamic>(
- context: context,
- barrierDismissible: false,
- builder:
- (_) =>
- Platform.isAndroid
- ? StackOkDialog(
- title: "${AppConfig.prefix} Auto Backup saved to:",
- message: fileToSave,
- )
- : const StackOkDialog(
- title: "${AppConfig.prefix} Auto Backup saved",
- ),
- );
- if (mounted) {
- passwordController.text = "";
- passwordRepeatController.text = "";
+ await showDialog<dynamic>(
+ context: context,
+ barrierDismissible: false,
+ builder: (_) => StackOkDialog(
+ title: "${AppConfig.prefix} Auto Backup saved to:",
+ message: savedPath,
+ ),
+ );
+ if (mounted) {
+ passwordController.text = "";
+ passwordRepeatController.text = "";
- if (!Util.isDesktop) {
- Navigator.of(
- context,
- ).popUntil(ModalRoute.withName(AutoBackupView.routeName));
+ if (!Util.isDesktop) {
+ Navigator.of(
+ context,
+ ).popUntil(ModalRoute.withName(AutoBackupView.routeName));
+ }
}
+ } else {
+ await showDialog<dynamic>(
+ context: context,
+ barrierDismissible: false,
+ builder: (_) => StackOkDialog(
+ title: "Failed to update Auto Backup",
+ message: ex?.toString(),
+ ),
+ );
}
- } else {
- await showDialog<dynamic>(
- context: context,
- barrierDismissible: false,
- builder:
- (_) => const StackOkDialog(title: "Failed to update Auto Backup"),
- );
}
}
}
@@ -262,13 +204,14 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> {
fileLocationController.text =
ref.read(prefsChangeNotifierProvider).autoBackupLocation ?? "";
- _currentDropDownValue =
- ref.read(prefsChangeNotifierProvider).backupFrequencyType;
+ _currentDropDownValue = ref
+ .read(prefsChangeNotifierProvider)
+ .backupFrequencyType;
passwordFocusNode = FocusNode();
passwordRepeatFocusNode = FocusNode();
- if (Platform.isAndroid || Platform.isIOS) {
+ if (Platform.isIOS) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
final dir = await stackFileSystem.prepareStorage();
if (mounted) {
@@ -302,44 +245,45 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> {
return ConditionalParent(
condition: !isDesktop,
- builder:
- (child) => Background(
- child: Scaffold(
- backgroundColor:
- Theme.of(context).extension<StackColors>()!.background,
- appBar: AppBar(
- leading: AppBarBackButton(
- onPressed: () {
- Navigator.of(context).pop();
- },
- ),
- title: Text(
- "Edit Auto Backup",
- style: STextStyles.navBarTitle(context),
- ),
- ),
- body: SafeArea(
- child: Padding(
- padding: const EdgeInsets.all(16),
- child: LayoutBuilder(
- builder: (context, constraints) {
- return SingleChildScrollView(
- child: ConstrainedBox(
- constraints: BoxConstraints(
- minHeight: constraints.maxHeight,
- ),
- child: IntrinsicHeight(child: child),
- ),
- );
- },
- ),
- ),
+ builder: (child) => Background(
+ child: Scaffold(
+ backgroundColor: Theme.of(
+ context,
+ ).extension<StackColors>()!.background,
+ appBar: AppBar(
+ leading: AppBarBackButton(
+ onPressed: () {
+ Navigator.of(context).pop();
+ },
+ ),
+ title: Text(
+ "Edit Auto Backup",
+ style: STextStyles.navBarTitle(context),
+ ),
+ ),
+ body: SafeArea(
+ child: Padding(
+ padding: const EdgeInsets.all(16),
+ child: LayoutBuilder(
+ builder: (context, constraints) {
+ return SingleChildScrollView(
+ child: ConstrainedBox(
+ constraints: BoxConstraints(
+ minHeight: constraints.maxHeight,
+ ),
+ child: IntrinsicHeight(child: child),
+ ),
+ );
+ },
),
),
),
+ ),
+ ),
child: Column(
- crossAxisAlignment:
- isDesktop ? CrossAxisAlignment.start : CrossAxisAlignment.stretch,
+ crossAxisAlignment: isDesktop
+ ? CrossAxisAlignment.start
+ : CrossAxisAlignment.stretch,
children: [
if (!isDesktop)
Text("Create your backup", style: STextStyles.smallMed12(context)),
@@ -352,31 +296,30 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> {
textAlign: TextAlign.left,
),
const SizedBox(height: 10),
- if (!Platform.isAndroid && !Platform.isIOS)
+ if (!Platform.isIOS)
TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
- onTap:
- Platform.isAndroid || Platform.isIOS
- ? null
- : () async {
- try {
- await stackFileSystem.prepareStorage();
-
- if (mounted) {
- await stackFileSystem.pickDir(context);
- }
-
- if (mounted) {
- setState(() {
- fileLocationController.text =
- stackFileSystem.dirPath ?? "";
- });
- }
- } catch (e, s) {
- Logging.instance.e("$e\n$s", error: e, stackTrace: s);
+ onTap: Platform.isIOS
+ ? null
+ : () async {
+ try {
+ await stackFileSystem.prepareStorage();
+
+ if (context.mounted) {
+ await stackFileSystem.pickDir(context);
+ }
+
+ if (mounted) {
+ setState(() {
+ fileLocationController.text =
+ stackFileSystem.dirPath ?? "";
+ });
}
- },
+ } catch (e, s) {
+ Logging.instance.e("$e\n$s", error: e, stackTrace: s);
+ }
+ },
controller: fileLocationController,
style: STextStyles.field(context),
decoration: InputDecoration(
@@ -388,10 +331,9 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> {
const SizedBox(width: 16),
SvgPicture.asset(
Assets.svg.folder,
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.textDark3,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textDark3,
width: 16,
height: 16,
),
@@ -419,8 +361,7 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> {
),
textAlign: TextAlign.left,
),
- if (!Platform.isAndroid && !Platform.isIOS)
- const SizedBox(height: 10),
+ if (!Platform.isIOS) const SizedBox(height: 10),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
@@ -433,40 +374,44 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> {
obscureText: hidePassword,
enableSuggestions: false,
autocorrect: false,
- decoration: standardInputDecoration(
- "Create passphrase",
- passwordFocusNode,
- context,
- ).copyWith(
- labelStyle: isDesktop ? STextStyles.fieldLabel(context) : null,
- suffixIcon: UnconstrainedBox(
- child: Row(
- children: [
- const SizedBox(width: 16),
- GestureDetector(
- key: const Key(
- "createBackupPasswordFieldShowPasswordButtonKey",
- ),
- onTap: () async {
- setState(() {
- hidePassword = !hidePassword;
- });
- },
- child: SvgPicture.asset(
- hidePassword ? Assets.svg.eye : Assets.svg.eyeSlash,
- color:
- Theme.of(
+ decoration:
+ standardInputDecoration(
+ "Create passphrase",
+ passwordFocusNode,
+ context,
+ ).copyWith(
+ labelStyle: isDesktop
+ ? STextStyles.fieldLabel(context)
+ : null,
+ suffixIcon: UnconstrainedBox(
+ child: Row(
+ children: [
+ const SizedBox(width: 16),
+ GestureDetector(
+ key: const Key(
+ "createBackupPasswordFieldShowPasswordButtonKey",
+ ),
+ onTap: () async {
+ setState(() {
+ hidePassword = !hidePassword;
+ });
+ },
+ child: SvgPicture.asset(
+ hidePassword
+ ? Assets.svg.eye
+ : Assets.svg.eyeSlash,
+ color: Theme.of(
context,
).extension<StackColors>()!.textDark3,
- width: 16,
- height: 16,
- ),
+ width: 16,
+ height: 16,
+ ),
+ ),
+ const SizedBox(width: 12),
+ ],
),
- const SizedBox(width: 12),
- ],
+ ),
),
- ),
- ),
onChanged: (newValue) {
if (newValue.isEmpty) {
setState(() {
@@ -513,13 +458,12 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> {
right: 12,
top: passwordFeedback.isNotEmpty ? 4 : 0,
),
- child:
- passwordFeedback.isNotEmpty
- ? Text(
- passwordFeedback,
- style: STextStyles.infoSmall(context),
- )
- : null,
+ child: passwordFeedback.isNotEmpty
+ ? Text(
+ passwordFeedback,
+ style: STextStyles.infoSmall(context),
+ )
+ : null,
),
if (passwordFocusNode.hasFocus ||
passwordRepeatFocusNode.hasFocus ||
@@ -528,27 +472,22 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> {
padding: const EdgeInsets.only(left: 12, right: 12, top: 10),
child: ProgressBar(
key: const Key("createStackBackUpProgressBar"),
- width:
- isDesktop
- ? 492
- : MediaQuery.of(context).size.width - 32 - 24,
+ width: isDesktop
+ ? 492
+ : MediaQuery.of(context).size.width - 32 - 24,
height: 5,
- fillColor:
- passwordStrength < 0.51
- ? Theme.of(
- context,
- ).extension<StackColors>()!.accentColorRed
- : passwordStrength < 1
- ? Theme.of(
- context,
- ).extension<StackColors>()!.accentColorYellow
- : Theme.of(
- context,
- ).extension<StackColors>()!.accentColorGreen,
- backgroundColor:
- Theme.of(
- context,
- ).extension<StackColors>()!.buttonBackSecondary,
+ fillColor: passwordStrength < 0.51
+ ? Theme.of(context).extension<StackColors>()!.accentColorRed
+ : passwordStrength < 1
+ ? Theme.of(
+ context,
+ ).extension<StackColors>()!.accentColorYellow
+ : Theme.of(
+ context,
+ ).extension<StackColors>()!.accentColorGreen,
+ backgroundColor: Theme.of(
+ context,
+ ).extension<StackColors>()!.buttonBackSecondary,
percent: passwordStrength < 0.25 ? 0.03 : passwordStrength,
),
),
@@ -565,40 +504,44 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> {
obscureText: hidePassword,
enableSuggestions: false,
autocorrect: false,
- decoration: standardInputDecoration(
- "Confirm passphrase",
- passwordRepeatFocusNode,
- context,
- ).copyWith(
- labelStyle: isDesktop ? STextStyles.fieldLabel(context) : null,
- suffixIcon: UnconstrainedBox(
- child: Row(
- children: [
- const SizedBox(width: 16),
- GestureDetector(
- key: const Key(
- "createBackupPasswordFieldShowPasswordButtonKey",
- ),
- onTap: () async {
- setState(() {
- hidePassword = !hidePassword;
- });
- },
- child: SvgPicture.asset(
- hidePassword ? Assets.svg.eye : Assets.svg.eyeSlash,
- color:
- Theme.of(
+ decoration:
+ standardInputDecoration(
+ "Confirm passphrase",
+ passwordRepeatFocusNode,
+ context,
+ ).copyWith(
+ labelStyle: isDesktop
+ ? STextStyles.fieldLabel(context)
+ : null,
+ suffixIcon: UnconstrainedBox(
+ child: Row(
+ children: [
+ const SizedBox(width: 16),
+ GestureDetector(
+ key: const Key(
+ "createBackupPasswordFieldShowPasswordButtonKey",
+ ),
+ onTap: () async {
+ setState(() {
+ hidePassword = !hidePassword;
+ });
+ },
+ child: SvgPicture.asset(
+ hidePassword
+ ? Assets.svg.eye
+ : Assets.svg.eyeSlash,
+ color: Theme.of(
context,
).extension<StackColors>()!.textDark3,
- width: 16,
- height: 16,
- ),
+ width: 16,
+ height: 16,
+ ),
+ ),
+ const SizedBox(width: 12),
+ ],
),
- const SizedBox(width: 12),
- ],
+ ),
),
- ),
- ),
onChanged: (newValue) {
setState(() {});
// TODO: ? check if passwords match?
@@ -608,13 +551,13 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> {
SizedBox(height: isDesktop ? 24 : 32),
Text(
"Auto Backup frequency",
- style:
- isDesktop
- ? STextStyles.desktopTextExtraSmall(context).copyWith(
- color:
- Theme.of(context).extension<StackColors>()!.textDark3,
- )
- : STextStyles.smallMed12(context),
+ style: isDesktop
+ ? STextStyles.desktopTextExtraSmall(context).copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textDark3,
+ )
+ : STextStyles.smallMed12(context),
),
const SizedBox(height: 10),
if (isDesktop)
@@ -653,8 +596,9 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> {
.backupFrequencyType !=
value) {
ref
- .read(prefsChangeNotifierProvider)
- .backupFrequencyType = value;
+ .read(prefsChangeNotifierProvider)
+ .backupFrequencyType =
+ value;
}
setState(() {
_currentDropDownValue = value;
@@ -666,18 +610,18 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> {
Assets.svg.chevronDown,
width: 10,
height: 5,
- color:
- Theme.of(context).extension<StackColors>()!.textDark3,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textDark3,
),
),
dropdownStyleData: DropdownStyleData(
offset: const Offset(0, -10),
elevation: 0,
decoration: BoxDecoration(
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.textFieldDefaultBG,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textFieldDefaultBG,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
@@ -699,8 +643,9 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> {
),
Positioned.fill(
child: RawMaterialButton(
- splashColor:
- Theme.of(context).extension<StackColors>()!.highlight,
+ splashColor: Theme.of(
+ context,
+ ).extension<StackColors>()!.highlight,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
@@ -737,10 +682,9 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> {
padding: const EdgeInsets.only(right: 4.0),
child: SvgPicture.asset(
Assets.svg.chevronDown,
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.textSubtitle2,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textSubtitle2,
width: 12,
height: 6,
),
@@ -777,14 +721,13 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> {
),
if (!isDesktop)
TextButton(
- style:
- shouldEnableCreate
- ? Theme.of(context)
- .extension<StackColors>()!
- .getPrimaryEnabledButtonStyle(context)
- : Theme.of(context)
- .extension<StackColors>()!
- .getPrimaryDisabledButtonStyle(context),
+ style: shouldEnableCreate
+ ? Theme.of(context)
+ .extension<StackColors>()!
+ .getPrimaryEnabledButtonStyle(context)
+ : Theme.of(context)
+ .extension<StackColors>()!
+ .getPrimaryDisabledButtonStyle(context),
onPressed: !shouldEnableCreate ? null : onSavePressed,
child: Text("Save", style: STextStyles.button(context)),
),
diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart
index 355fc66..58333ed 100644
--- a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart
+++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart
@@ -13,6 +13,7 @@ import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
+import 'package:flutter/material.dart';
import 'package:isar_community/isar.dart';
import 'package:stack_wallet_backup/stack_wallet_backup.dart';
import 'package:tuple/tuple.dart';
@@ -31,6 +32,7 @@ import '../../../../../models/node_model.dart';
import '../../../../../models/stack_restoring_ui_state.dart';
import '../../../../../models/trade_wallet_lookup.dart';
import '../../../../../models/wallet_restore_state.dart';
+import '../../../../../notifications/show_flush_bar.dart';
import '../../../../../services/address_book_service.dart';
import '../../../../../services/node_service.dart';
import '../../../../../services/trade_notes_service.dart';
@@ -92,6 +94,35 @@ String createAutoBackupFilename(String dirPath, DateTime date) {
"_${date.minute}_${date.second}.swb";
}
+bool validateFail(
+ BuildContext context,
+ String pathToSave,
+ String passphrase,
+ String repeatPassphrase,
+) {
+ for (final e in [
+ [pathToSave.isEmpty, "Directory not chosen"],
+ [!(Directory(pathToSave).existsSync()), "Directory does not exist"],
+ [passphrase.isEmpty, "A passphrase is required"],
+ [passphrase != repeatPassphrase, "Passphrase does not match"],
+ ]) {
+ if (e[0] as bool) {
+ if (context.mounted) {
+ unawaited(
+ showFloatingFlushBar(
+ type: FlushBarType.warning,
+ message: e[1] as String,
+ context: context,
+ ),
+ );
+ }
+ return true;
+ }
+ }
+
+ return false;
+}
+
abstract class SWB {
static Completer<void>? _cancelCompleter;
@@ -131,88 +162,42 @@ abstract class SWB {
}
}
- static Future<bool> encryptStackWalletWithPassphrase(
- String fileToSave,
+ static Future<String> encryptStackWalletWithPassphrase(
String passphrase,
String plaintext,
) async {
- try {
- final File backupFile = File(fileToSave);
- if (!backupFile.existsSync()) {
- final String jsonBackup = plaintext;
- final Uint8List content = Uint8List.fromList(utf8.encode(jsonBackup));
- final Uint8List encryptedContent = await encryptWithPassphrase(
- passphrase,
- content,
- );
- backupFile.writeAsStringSync(
- Format.uint8listToString(encryptedContent),
- );
- }
- Logging.instance.d(backupFile.absolute);
- return true;
- } catch (e, s) {
- Logging.instance.e("$e\n$s", error: e, stackTrace: s);
- return false;
- }
+ final String jsonBackup = plaintext;
+ final Uint8List content = Uint8List.fromList(utf8.encode(jsonBackup));
+ final Uint8List encryptedContent = await encryptWithPassphrase(
+ passphrase,
+ content,
+ );
+ return Format.uint8listToString(encryptedContent);
}
- static Future<bool> encryptStackWalletWithADK(
- String fileToSave,
+ static Future<String> encryptStackWalletWithADK(
String adk,
String plaintext,
int adkVersion,
) async {
- try {
- final File backupFile = File(fileToSave);
- if (!backupFile.existsSync()) {
- final String jsonBackup = plaintext;
- final Uint8List content = Uint8List.fromList(utf8.encode(jsonBackup));
- final Uint8List encryptedContent = await encryptWithAdk(
- Format.stringToUint8List(adk),
- content,
- version: adkVersion,
- );
- backupFile.writeAsStringSync(
- Format.uint8listToString(encryptedContent),
- );
- }
- Logging.instance.d(backupFile.absolute);
- return true;
- } catch (e, s) {
- Logging.instance.e("$e\n$s", error: e, stackTrace: s);
- return false;
- }
- }
-
- static Future<String?> decryptStackWalletWithPassphrase(
- Tuple2<String, String> data,
- ) async {
- try {
- final String fileToRestore = data.item1;
- final String passphrase = data.item2;
- final File backupFile = File(fileToRestore);
- final String encryptedText = await backupFile.readAsString();
- return await decryptStackWalletStringWithPassphrase(
- Tuple2(encryptedText, passphrase),
- );
- } catch (e, s) {
- Logging.instance.e("$e\n$s", error: e, stackTrace: s);
- return null;
- }
+ final String jsonBackup = plaintext;
+ final Uint8List content = Uint8List.fromList(utf8.encode(jsonBackup));
+ final Uint8List encryptedContent = await encryptWithAdk(
+ Format.stringToUint8List(adk),
+ content,
+ version: adkVersion,
+ );
+ return Format.uint8listToString(encryptedContent);
}
static Future<String?> decryptStackWalletStringWithPassphrase(
- Tuple2<String, String> data,
+ ({String passphrase, String encryptedText}) data,
) async {
try {
- final encryptedText = data.item1;
- final passphrase = data.item2;
-
- final encryptedBytes = Format.stringToUint8List(encryptedText);
+ final encryptedBytes = Format.stringToUint8List(data.encryptedText);
final decryptedContent = await decryptWithPassphrase(
- passphrase,
+ data.passphrase,
encryptedBytes,
);
diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_encrypted_string_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_encrypted_string_view.dart
index 5aded8e..77ed42e 100644
--- a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_encrypted_string_view.dart
+++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_encrypted_string_view.dart
@@ -12,7 +12,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
-import 'package:tuple/tuple.dart';
import '../../../../app_config.dart';
import '../../../../notifications/show_flush_bar.dart';
@@ -73,8 +72,9 @@ class _RestoreFromEncryptedStringViewState
onWillPop: _onWillPop,
child: Background(
child: Scaffold(
- backgroundColor:
- Theme.of(context).extension<StackColors>()!.background,
+ backgroundColor: Theme.of(
+ context,
+ ).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
@@ -120,41 +120,41 @@ class _RestoreFromEncryptedStringViewState
obscureText: hidePassword,
enableSuggestions: false,
autocorrect: false,
- decoration: standardInputDecoration(
- "Enter password",
- passwordFocusNode,
- context,
- ).copyWith(
- suffixIcon: UnconstrainedBox(
- child: Row(
- children: [
- const SizedBox(width: 16),
- GestureDetector(
- key: const Key(
- "restoreFromFilePasswordFieldShowPasswordButtonKey",
- ),
- onTap: () async {
- setState(() {
- hidePassword = !hidePassword;
- });
- },
- child: SvgPicture.asset(
- hidePassword
- ? Assets.svg.eye
- : Assets.svg.eyeSlash,
- color:
- Theme.of(context)
+ decoration:
+ standardInputDecoration(
+ "Enter password",
+ passwordFocusNode,
+ context,
+ ).copyWith(
+ suffixIcon: UnconstrainedBox(
+ child: Row(
+ children: [
+ const SizedBox(width: 16),
+ GestureDetector(
+ key: const Key(
+ "restoreFromFilePasswordFieldShowPasswordButtonKey",
+ ),
+ onTap: () async {
+ setState(() {
+ hidePassword = !hidePassword;
+ });
+ },
+ child: SvgPicture.asset(
+ hidePassword
+ ? Assets.svg.eye
+ : Assets.svg.eyeSlash,
+ color: Theme.of(context)
.extension<StackColors>()!
.textDark3,
- width: 16,
- height: 16,
- ),
+ width: 16,
+ height: 16,
+ ),
+ ),
+ const SizedBox(width: 12),
+ ],
),
- const SizedBox(width: 12),
- ],
+ ),
),
- ),
- ),
onChanged: (newValue) {
setState(() {});
},
@@ -163,114 +163,108 @@ class _RestoreFromEncryptedStringViewState
const SizedBox(height: 16),
const Spacer(),
TextButton(
- style:
- passwordController.text.isEmpty
- ? Theme.of(context)
- .extension<StackColors>()!
- .getPrimaryEnabledButtonStyle(context)
- : Theme.of(context)
- .extension<StackColors>()!
- .getPrimaryDisabledButtonStyle(
- context,
- ),
- onPressed:
- passwordController.text.isEmpty
- ? null
- : () async {
- final String passphrase =
- passwordController.text;
+ style: passwordController.text.isEmpty
+ ? Theme.of(context)
+ .extension<StackColors>()!
+ .getPrimaryEnabledButtonStyle(context)
+ : Theme.of(context)
+ .extension<StackColors>()!
+ .getPrimaryDisabledButtonStyle(context),
+ onPressed: passwordController.text.isEmpty
+ ? null
+ : () async {
+ final String passphrase =
+ passwordController.text;
- if (FocusScope.of(context).hasFocus) {
- FocusScope.of(context).unfocus();
- await Future<void>.delayed(
- const Duration(milliseconds: 75),
- );
- }
+ if (FocusScope.of(context).hasFocus) {
+ FocusScope.of(context).unfocus();
+ await Future<void>.delayed(
+ const Duration(milliseconds: 75),
+ );
+ }
- bool shouldPop = false;
- showDialog<dynamic>(
- barrierDismissible: false,
- context: context,
- builder:
- (_) => WillPopScope(
- onWillPop: () async {
- return shouldPop;
- },
- child: Column(
- crossAxisAlignment:
- CrossAxisAlignment
- .stretch,
- mainAxisAlignment:
- MainAxisAlignment.center,
- children: [
- Material(
- color: Colors.transparent,
- child: Center(
- child: Text(
- "Decrypting ${AppConfig.prefix} backup file",
- style: STextStyles.pageTitleH2(
- context,
- ).copyWith(
- color:
- Theme.of(
- context,
- )
- .extension<
- StackColors
- >()!
- .textWhite,
- ),
+ bool shouldPop = false;
+ showDialog<dynamic>(
+ barrierDismissible: false,
+ context: context,
+ builder: (_) => WillPopScope(
+ onWillPop: () async {
+ return shouldPop;
+ },
+ child: Column(
+ crossAxisAlignment:
+ CrossAxisAlignment.stretch,
+ mainAxisAlignment:
+ MainAxisAlignment.center,
+ children: [
+ Material(
+ color: Colors.transparent,
+ child: Center(
+ child: Text(
+ "Decrypting ${AppConfig.prefix} backup file",
+ style:
+ STextStyles.pageTitleH2(
+ context,
+ ).copyWith(
+ color:
+ Theme.of(context)
+ .extension<
+ StackColors
+ >()!
+ .textWhite,
),
- ),
- ),
- const SizedBox(height: 64),
- const Center(
- child: LoadingIndicator(
- width: 100,
- ),
- ),
- ],
+ ),
),
),
- );
-
- final String?
- jsonString = await compute(
- SWB.decryptStackWalletStringWithPassphrase,
- Tuple2(widget.encrypted, passphrase),
- debugLabel:
- "stack wallet decryption compute",
- );
+ const SizedBox(height: 64),
+ const Center(
+ child: LoadingIndicator(
+ width: 100,
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
- if (mounted) {
- // pop LoadingIndicator
- shouldPop = true;
- Navigator.of(context).pop();
+ final String? jsonString = await compute(
+ SWB.decryptStackWalletStringWithPassphrase,
+ (
+ encryptedText: widget.encrypted,
+ passphrase: passphrase,
+ ),
+ debugLabel:
+ "stack wallet decryption compute",
+ );
- passwordController.text = "";
+ if (mounted) {
+ // pop LoadingIndicator
+ shouldPop = true;
+ Navigator.of(context).pop();
- if (jsonString == null) {
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message:
- "Failed to decrypt backup file",
- context: context,
- );
- return;
- }
+ passwordController.text = "";
- Navigator.of(context).push(
- RouteGenerator.getRoute(
- builder:
- (_) =>
- StackRestoreProgressView(
- jsonString: jsonString,
- fromFile: true,
- ),
- ),
+ if (jsonString == null) {
+ showFloatingFlushBar(
+ type: FlushBarType.warning,
+ message:
+ "Failed to decrypt backup file",
+ context: context,
);
+ return;
}
- },
+
+ Navigator.of(context).push(
+ RouteGenerator.getRoute(
+ builder: (_) =>
+ StackRestoreProgressView(
+ jsonString: jsonString,
+ fromFile: true,
+ ),
+ ),
+ );
+ }
+ },
child: Text(
"Restore",
style: STextStyles.button(context),
diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart
index d0ce73d..3a7224a 100644
--- a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart
+++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart
@@ -15,7 +15,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
-import 'package:tuple/tuple.dart';
import '../../../../app_config.dart';
import '../../../../notifications/show_flush_bar.dart';
@@ -89,8 +88,9 @@ class _RestoreFromFileViewState extends ConsumerState<RestoreFromFileView> {
builder: (child) {
return Background(
child: Scaffold(
- backgroundColor:
- Theme.of(context).extension<StackColors>()!.background,
+ backgroundColor: Theme.of(
+ context,
+ ).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
@@ -140,12 +140,12 @@ class _RestoreFromFileViewState extends ConsumerState<RestoreFromFileView> {
padding: const EdgeInsets.only(bottom: 10.0),
child: Text(
"Choose file location",
- style: STextStyles.desktopTextExtraExtraSmall(
- context,
- ).copyWith(
- color:
- Theme.of(context).extension<StackColors>()!.textDark3,
- ),
+ style: STextStyles.desktopTextExtraExtraSmall(context)
+ .copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textDark3,
+ ),
textAlign: TextAlign.left,
),
),
@@ -187,10 +187,9 @@ class _RestoreFromFileViewState extends ConsumerState<RestoreFromFileView> {
const SizedBox(width: 16),
SvgPicture.asset(
Assets.svg.folder,
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.textDark3,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textDark3,
width: 16,
height: 16,
),
@@ -215,12 +214,12 @@ class _RestoreFromFileViewState extends ConsumerState<RestoreFromFileView> {
padding: const EdgeInsets.only(bottom: 10.0),
child: Text(
"Enter passphrase",
- style: STextStyles.desktopTextExtraExtraSmall(
- context,
- ).copyWith(
- color:
- Theme.of(context).extension<StackColors>()!.textDark3,
- ),
+ style: STextStyles.desktopTextExtraExtraSmall(context)
+ .copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textDark3,
+ ),
textAlign: TextAlign.left,
),
),
@@ -236,41 +235,44 @@ class _RestoreFromFileViewState extends ConsumerState<RestoreFromFileView> {
obscureText: hidePassword,
enableSuggestions: false,
autocorrect: false,
- decoration: standardInputDecoration(
- "Enter passphrase",
- passwordFocusNode,
- context,
- ).copyWith(
- labelStyle:
- isDesktop ? STextStyles.fieldLabel(context) : null,
- suffixIcon: UnconstrainedBox(
- child: Row(
- children: [
- const SizedBox(width: 16),
- GestureDetector(
- key: const Key(
- "restoreFromFilePasswordFieldShowPasswordButtonKey",
- ),
- onTap: () async {
- setState(() {
- hidePassword = !hidePassword;
- });
- },
- child: SvgPicture.asset(
- hidePassword ? Assets.svg.eye : Assets.svg.eyeSlash,
- color:
- Theme.of(
+ decoration:
+ standardInputDecoration(
+ "Enter passphrase",
+ passwordFocusNode,
+ context,
+ ).copyWith(
+ labelStyle: isDesktop
+ ? STextStyles.fieldLabel(context)
+ : null,
+ suffixIcon: UnconstrainedBox(
+ child: Row(
+ children: [
+ const SizedBox(width: 16),
+ GestureDetector(
+ key: const Key(
+ "restoreFromFilePasswordFieldShowPasswordButtonKey",
+ ),
+ onTap: () async {
+ setState(() {
+ hidePassword = !hidePassword;
+ });
+ },
+ child: SvgPicture.asset(
+ hidePassword
+ ? Assets.svg.eye
+ : Assets.svg.eyeSlash,
+ color: Theme.of(
context,
).extension<StackColors>()!.textDark3,
- width: 16,
- height: 16,
- ),
+ width: 16,
+ height: 16,
+ ),
+ ),
+ const SizedBox(width: 12),
+ ],
),
- const SizedBox(width: 12),
- ],
+ ),
),
- ),
- ),
onChanged: (newValue) {
setState(() {});
},
@@ -280,20 +282,20 @@ class _RestoreFromFileViewState extends ConsumerState<RestoreFromFileView> {
if (!isDesktop) const Spacer(),
!isDesktop
? TextButton(
- style:
- passwordController.text.isEmpty ||
- fileLocationController.text.isEmpty
- ? Theme.of(context)
+ style:
+ passwordController.text.isEmpty ||
+ fileLocationController.text.isEmpty
+ ? Theme.of(context)
.extension<StackColors>()!
.getPrimaryDisabledButtonStyle(context)
- : Theme.of(context)
+ : Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonStyle(context),
- onPressed:
- passwordController.text.isEmpty ||
- fileLocationController.text.isEmpty
- ? null
- : () async {
+ onPressed:
+ passwordController.text.isEmpty ||
+ fileLocationController.text.isEmpty
+ ? null
+ : () async {
final String fileToRestore =
fileLocationController.text;
final String passphrase = passwordController.text;
@@ -319,48 +321,51 @@ class _RestoreFromFileViewState extends ConsumerState<RestoreFromFileView> {
showDialog<dynamic>(
barrierDismissible: false,
context: context,
- builder:
- (_) => WillPopScope(
- onWillPop: () async {
- return shouldPop;
- },
- child: Column(
- crossAxisAlignment:
- CrossAxisAlignment.stretch,
- mainAxisAlignment:
- MainAxisAlignment.center,
- children: [
- Material(
- color: Colors.transparent,
- child: Center(
- child: Text(
- "Decrypting ${AppConfig.prefix} backup file",
- style: STextStyles.pageTitleH2(
+ builder: (_) => WillPopScope(
+ onWillPop: () async {
+ return shouldPop;
+ },
+ child: Column(
+ crossAxisAlignment:
+ CrossAxisAlignment.stretch,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Material(
+ color: Colors.transparent,
+ child: Center(
+ child: Text(
+ "Decrypting ${AppConfig.prefix} backup file",
+ style:
+ STextStyles.pageTitleH2(
context,
).copyWith(
- color:
- Theme.of(context)
- .extension<
- StackColors
- >()!
- .textWhite,
+ color: Theme.of(context)
+ .extension<StackColors>()!
+ .textWhite,
),
- ),
- ),
- ),
- const SizedBox(height: 64),
- const Center(
- child: LoadingIndicator(width: 100),
),
- ],
+ ),
),
- ),
+ const SizedBox(height: 64),
+ const Center(
+ child: LoadingIndicator(width: 100),
+ ),
+ ],
+ ),
+ ),
),
);
+ final encryptedText = await File(
+ fileToRestore,
+ ).readAsString();
+
final String? jsonString = await compute(
- SWB.decryptStackWalletWithPassphrase,
- Tuple2(fileToRestore, passphrase),
+ SWB.decryptStackWalletStringWithPassphrase,
+ (
+ encryptedText: encryptedText,
+ passphrase: passphrase,
+ ),
debugLabel: "stack wallet decryption compute",
);
@@ -382,31 +387,30 @@ class _RestoreFromFileViewState extends ConsumerState<RestoreFromFileView> {
await Navigator.of(context).push(
RouteGenerator.getRoute(
- builder:
- (_) => StackRestoreProgressView(
- jsonString: jsonString,
- shouldPushToHome: true,
- ),
+ builder: (_) => StackRestoreProgressView(
+ jsonString: jsonString,
+ shouldPushToHome: true,
+ ),
),
);
}
},
- child: Text("Restore", style: STextStyles.button(context)),
- )
+ child: Text("Restore", style: STextStyles.button(context)),
+ )
: Row(
- children: [
- PrimaryButton(
- width: 183,
- buttonHeight: ButtonHeight.m,
- label: "Restore",
- enabled:
- !(passwordController.text.isEmpty ||
- fileLocationController.text.isEmpty),
- onPressed:
- passwordController.text.isEmpty ||
- fileLocationController.text.isEmpty
- ? null
- : () async {
+ children: [
+ PrimaryButton(
+ width: 183,
+ buttonHeight: ButtonHeight.m,
+ label: "Restore",
+ enabled:
+ !(passwordController.text.isEmpty ||
+ fileLocationController.text.isEmpty),
+ onPressed:
+ passwordController.text.isEmpty ||
+ fileLocationController.text.isEmpty
+ ? null
+ : () async {
final String fileToRestore =
fileLocationController.text;
final String passphrase =
@@ -433,55 +437,58 @@ class _RestoreFromFileViewState extends ConsumerState<RestoreFromFileView> {
showDialog<dynamic>(
barrierDismissible: false,
context: context,
- builder:
- (_) => WillPopScope(
- onWillPop: () async {
- return shouldPop;
- },
- child: Column(
- crossAxisAlignment:
- CrossAxisAlignment.stretch,
- mainAxisAlignment:
- MainAxisAlignment.center,
- children: [
- Material(
- color: Colors.transparent,
- child: Center(
- child: Text(
- "Decrypting ${AppConfig.prefix} backup file",
- style:
- STextStyles.pageTitleH2(
- context,
- ).copyWith(
- color:
- Theme.of(context)
- .extension<
- StackColors
- >()!
- .textWhite,
- ),
- ),
- ),
- ),
- const SizedBox(height: 64),
- const Center(
- child: LoadingIndicator(
- width: 100,
- ),
+ builder: (_) => WillPopScope(
+ onWillPop: () async {
+ return shouldPop;
+ },
+ child: Column(
+ crossAxisAlignment:
+ CrossAxisAlignment.stretch,
+ mainAxisAlignment:
+ MainAxisAlignment.center,
+ children: [
+ Material(
+ color: Colors.transparent,
+ child: Center(
+ child: Text(
+ "Decrypting ${AppConfig.prefix} backup file",
+ style:
+ STextStyles.pageTitleH2(
+ context,
+ ).copyWith(
+ color: Theme.of(context)
+ .extension<
+ StackColors
+ >()!
+ .textWhite,
+ ),
),
- ],
+ ),
),
- ),
+ const SizedBox(height: 64),
+ const Center(
+ child: LoadingIndicator(width: 100),
+ ),
+ ],
+ ),
+ ),
),
);
+ final encryptedText = await File(
+ fileToRestore,
+ ).readAsString();
+
final String? jsonString = await compute(
- SWB.decryptStackWalletWithPassphrase,
- Tuple2(fileToRestore, passphrase),
+ SWB.decryptStackWalletStringWithPassphrase,
+ (
+ encryptedText: encryptedText,
+ passphrase: passphrase,
+ ),
debugLabel: "stack wallet decryption compute",
);
- if (mounted) {
+ if (context.mounted) {
// pop LoadingIndicator
shouldPop = true;
Navigator.of(
@@ -571,16 +578,16 @@ class _RestoreFromFileViewState extends ConsumerState<RestoreFromFileView> {
);
}
},
- ),
- const SizedBox(width: 16),
- SecondaryButton(
- width: 183,
- buttonHeight: ButtonHeight.m,
- label: "Cancel",
- onPressed: () {},
- ),
- ],
- ),
+ ),
+ const SizedBox(width: 16),
+ SecondaryButton(
+ width: 183,
+ buttonHeight: ButtonHeight.m,
+ label: "Cancel",
+ onPressed: () {},
+ ),
+ ],
+ ),
],
),
),
diff --git a/lib/pages_desktop_specific/password/forgotten_passphrase_restore_from_swb.dart b/lib/pages_desktop_specific/password/forgotten_passphrase_restore_from_swb.dart
index 161459e..1d672c5 100644
--- a/lib/pages_desktop_specific/password/forgotten_passphrase_restore_from_swb.dart
+++ b/lib/pages_desktop_specific/password/forgotten_passphrase_restore_from_swb.dart
@@ -15,7 +15,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
-import 'package:tuple/tuple.dart';
import '../../app_config.dart';
import '../../db/hive/db.dart';
@@ -96,29 +95,26 @@ class _ForgottenPassphraseRestoreFromSWBState
child: Text(
"Decrypting ${AppConfig.prefix} backup file",
style: STextStyles.pageTitleH2(context).copyWith(
- color:
- Theme.of(context).extension<StackColors>()!.textWhite,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textWhite,
),
),
),
),
- const SizedBox(
- height: 64,
- ),
- const Center(
- child: LoadingIndicator(
- width: 100,
- ),
- ),
+ const SizedBox(height: 64),
+ const Center(child: LoadingIndicator(width: 100)),
],
),
),
),
);
+ final content = await File(fileToRestore).readAsString();
+
final String? jsonString = await compute(
- SWB.decryptStackWalletWithPassphrase,
- Tuple2(fileToRestore, passphrase),
+ SWB.decryptStackWalletStringWithPassphrase,
+ (encryptedText: content, passphrase: passphrase),
debugLabel: "${AppConfig.appName} decryption compute",
);
@@ -161,9 +157,7 @@ class _ForgottenPassphraseRestoreFromSWBState
),
],
),
- const SizedBox(
- height: 44,
- ),
+ const SizedBox(height: 44),
Flexible(
child: StackRestoreProgressView(
jsonString: jsonString,
@@ -220,8 +214,9 @@ class _ForgottenPassphraseRestoreFromSWBState
ref.refresh(storageCryptoHandlerProvider);
await DB.instance.init();
if (mounted) {
- Navigator.of(context)
- .popUntil(ModalRoute.withName(CreatePasswordView.routeName));
+ Navigator.of(
+ context,
+ ).popUntil(ModalRoute.withName(CreatePasswordView.routeName));
Navigator.of(context).pop();
}
},
@@ -241,21 +236,17 @@ class _ForgottenPassphraseRestoreFromSWBState
"Restore from backup",
style: STextStyles.desktopH1(context),
),
- const SizedBox(
- height: 32,
- ),
+ const SizedBox(height: 32),
Text(
"Use your ${AppConfig.prefix} backup file to restore your wallets, address book, and wallet preferences.",
textAlign: TextAlign.center,
style: STextStyles.desktopTextSmall(context).copyWith(
- color: Theme.of(context)
- .extension<StackColors>()!
- .textSubtitle1,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textSubtitle1,
),
),
- const SizedBox(
- height: 40,
- ),
+ const SizedBox(height: 40),
GestureDetector(
onTap: () async {
try {
@@ -290,20 +281,16 @@ class _ForgottenPassphraseRestoreFromSWBState
child: UnconstrainedBox(
child: Row(
children: [
- const SizedBox(
- width: 24,
- ),
+ const SizedBox(width: 24),
SvgPicture.asset(
Assets.svg.folder,
- color: Theme.of(context)
- .extension<StackColors>()!
- .textDark3,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textDark3,
width: 24,
height: 24,
),
- const SizedBox(
- width: 12,
- ),
+ const SizedBox(width: 12),
],
),
),
@@ -321,16 +308,14 @@ class _ForgottenPassphraseRestoreFromSWBState
setState(() {
_enableButton =
passwordController.text.isNotEmpty &&
- fileLocationController.text.isNotEmpty;
+ fileLocationController.text.isNotEmpty;
});
},
),
),
),
),
- const SizedBox(
- height: 16,
- ),
+ const SizedBox(height: 16),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
@@ -339,67 +324,63 @@ class _ForgottenPassphraseRestoreFromSWBState
key: const Key("restoreFromFilePasswordFieldKey"),
focusNode: passwordFocusNode,
controller: passwordController,
- style: STextStyles.desktopTextMedium(context).copyWith(
- height: 2,
- ),
+ style: STextStyles.desktopTextMedium(
+ context,
+ ).copyWith(height: 2),
obscureText: hidePassword,
enableSuggestions: false,
autocorrect: false,
- decoration: standardInputDecoration(
- "Enter passphrase",
- passwordFocusNode,
- context,
- ).copyWith(
- suffixIcon: UnconstrainedBox(
- child: SizedBox(
- height: 70,
- child: Row(
- children: [
- const SizedBox(
- width: 24,
- ),
- GestureDetector(
- key: const Key(
- "restoreFromFilePasswordFieldShowPasswordButtonKey",
- ),
- onTap: () async {
- setState(() {
- hidePassword = !hidePassword;
- });
- },
- child: MouseRegion(
- cursor: SystemMouseCursors.click,
- child: SvgPicture.asset(
- hidePassword
- ? Assets.svg.eye
- : Assets.svg.eyeSlash,
- color: Theme.of(context)
- .extension<StackColors>()!
- .textDark3,
- width: 24,
- height: 24,
+ decoration:
+ standardInputDecoration(
+ "Enter passphrase",
+ passwordFocusNode,
+ context,
+ ).copyWith(
+ suffixIcon: UnconstrainedBox(
+ child: SizedBox(
+ height: 70,
+ child: Row(
+ children: [
+ const SizedBox(width: 24),
+ GestureDetector(
+ key: const Key(
+ "restoreFromFilePasswordFieldShowPasswordButtonKey",
+ ),
+ onTap: () async {
+ setState(() {
+ hidePassword = !hidePassword;
+ });
+ },
+ child: MouseRegion(
+ cursor: SystemMouseCursors.click,
+ child: SvgPicture.asset(
+ hidePassword
+ ? Assets.svg.eye
+ : Assets.svg.eyeSlash,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textDark3,
+ width: 24,
+ height: 24,
+ ),
+ ),
),
- ),
- ),
- const SizedBox(
- width: 12,
+ const SizedBox(width: 12),
+ ],
),
- ],
+ ),
),
),
- ),
- ),
onChanged: (newValue) {
setState(() {
- _enableButton = passwordController.text.isNotEmpty &&
+ _enableButton =
+ passwordController.text.isNotEmpty &&
fileLocationController.text.isNotEmpty;
});
},
),
),
- const SizedBox(
- height: 24,
- ),
+ const SizedBox(height: 24),
PrimaryButton(
label: "Restore",
enabled: _enableButton,
@@ -407,9 +388,7 @@ class _ForgottenPassphraseRestoreFromSWBState
restore();
},
),
- const SizedBox(
- height: kDesktopAppBarHeight,
- ),
+ const SizedBox(height: kDesktopAppBarHeight),
],
),
),
diff --git a/lib/pages_desktop_specific/settings/settings_menu/backup_and_restore/create_auto_backup.dart b/lib/pages_desktop_specific/settings/settings_menu/backup_and_restore/create_auto_backup.dart
index 3ffea65..ce19167 100644
--- a/lib/pages_desktop_specific/settings/settings_menu/backup_and_restore/create_auto_backup.dart
+++ b/lib/pages_desktop_specific/settings/settings_menu/backup_and_restore/create_auto_backup.dart
@@ -21,7 +21,6 @@ import 'package:stack_wallet_backup/stack_wallet_backup.dart';
import 'package:zxcvbn/zxcvbn.dart';
import '../../../../app_config.dart';
-import '../../../../notifications/show_flush_bar.dart';
import '../../../../pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart';
import '../../../../pages/settings_views/global_settings_view/stack_backup_views/helpers/swb_file_system.dart';
import '../../../../providers/global/prefs_provider.dart';
@@ -33,6 +32,7 @@ import '../../../../utilities/enums/backup_frequency_type.dart';
import '../../../../utilities/flutter_secure_storage_interface.dart';
import '../../../../utilities/format.dart';
import '../../../../utilities/logger.dart';
+import '../../../../utilities/show_loading.dart';
import '../../../../utilities/text_styles.dart';
import '../../../../utilities/util.dart';
import '../../../../widgets/desktop/desktop_dialog.dart';
@@ -44,9 +44,7 @@ import '../../../../widgets/stack_dialog.dart';
import '../../../../widgets/stack_text_field.dart';
class CreateAutoBackup extends ConsumerStatefulWidget {
- const CreateAutoBackup({
- super.key,
- });
+ const CreateAutoBackup({super.key});
@override
ConsumerState<CreateAutoBackup> createState() => _CreateAutoBackup();
@@ -89,6 +87,146 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> {
BackupFrequencyType.afterClosingAWallet,
];
+ Future<void> _enableAutoBackup() async {
+ final String pathToSave = fileLocationController.text;
+ final String passphrase = passphraseController.text;
+ final String repeatPassphrase = passphraseRepeatController.text;
+
+ if (validateFail(context, pathToSave, passphrase, repeatPassphrase)) return;
+
+ if (mounted) {
+ final now = DateTime.now();
+ Exception? ex;
+ final savedPath = await showLoading(
+ whileFuture: () async {
+ String adkString;
+ int adkVersion;
+ try {
+ final adk = await compute(generateAdk, passphrase);
+ adkString = Format.uint8listToString(adk.item2);
+ adkVersion = adk.item1;
+ } on Exception catch (e, s) {
+ final String err = getErrorMessageFromSWBException(e);
+ Logging.instance.e(err, error: e, stackTrace: s);
+ rethrow;
+ }
+
+ await secureStore.write(key: "auto_adk_string", value: adkString);
+ await secureStore.write(
+ key: "auto_adk_version_string",
+ value: adkVersion.toString(),
+ );
+
+ final fileToSavePath = createAutoBackupFilename(pathToSave, now);
+
+ final backup = await SWB.createStackWalletJSON(
+ secureStorage: secureStore,
+ );
+
+ final encryptedDataString = await SWB.encryptStackWalletWithADK(
+ adkString,
+ jsonEncode(backup),
+ adkVersion,
+ );
+
+ if (Platform.isAndroid) {
+ // TODO SAF
+ File(
+ fileToSavePath,
+ ).writeAsStringSync(encryptedDataString, flush: true);
+ } else {
+ File(
+ fileToSavePath,
+ ).writeAsStringSync(encryptedDataString, flush: true);
+ }
+
+ return fileToSavePath;
+ }(),
+ context: context,
+ message: "Encrypting initial backup",
+ subMessage: "This shouldn't take long",
+ delay: const Duration(seconds: 1),
+ onException: (e) => ex = e,
+ );
+
+ if (mounted) {
+ // pop encryption progress dialog
+ Navigator.of(context).pop();
+
+ if (savedPath != null) {
+ ref.read(prefsChangeNotifierProvider).autoBackupLocation = pathToSave;
+ ref.read(prefsChangeNotifierProvider).lastAutoBackup = now;
+
+ ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled = true;
+
+ await showDialog<dynamic>(
+ context: context,
+ barrierDismissible: false,
+ builder: (context) {
+ return DesktopDialog(
+ maxHeight: double.infinity,
+ maxWidth: 500,
+ child: Padding(
+ padding: const EdgeInsets.only(
+ left: 32,
+ right: 32,
+ bottom: 32,
+ ),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ "${AppConfig.prefix} Auto Backup enabled!",
+ style: STextStyles.desktopH3(context),
+ ),
+ const DesktopDialogCloseButton(),
+ ],
+ ),
+ const SizedBox(height: 40),
+ Row(
+ children: [
+ const Spacer(),
+ Expanded(
+ child: PrimaryButton(
+ label: "Ok",
+ buttonHeight: ButtonHeight.l,
+ onPressed: () {
+ Navigator.of(context).pop();
+ },
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ );
+ },
+ );
+ if (mounted) {
+ passphraseController.text = "";
+ passphraseRepeatController.text = "";
+
+ Navigator.of(context).pop();
+ }
+ } else {
+ await showDialog<dynamic>(
+ context: context,
+ barrierDismissible: false,
+ builder: (_) => StackOkDialog(
+ title: "Failed to enable Auto Backup",
+ message: ex?.toString(),
+ ),
+ );
+ }
+ }
+ }
+ }
+
@override
void initState() {
secureStore = ref.read(secureStoreProvider);
@@ -101,7 +239,7 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> {
passphraseFocusNode = FocusNode();
passphraseRepeatFocusNode = FocusNode();
- if (Platform.isAndroid || Platform.isIOS) {
+ if (Platform.isIOS) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
final dir = await stackFileSystem.prepareStorage();
if (mounted) {
@@ -154,9 +292,7 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> {
const DesktopDialogCloseButton(),
],
),
- const SizedBox(
- height: 30,
- ),
+ const SizedBox(height: 30),
Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(left: 32),
@@ -168,15 +304,13 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> {
textAlign: TextAlign.left,
),
),
- const SizedBox(
- height: 10,
- ),
+ const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
- if (!Platform.isAndroid && !Platform.isIOS)
+ if (!Platform.isIOS)
Consumer(
builder: (context, ref, __) {
return Container(
@@ -184,7 +318,7 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> {
child: TextField(
autocorrect: false,
enableSuggestions: false,
- onTap: Platform.isAndroid || Platform.isIOS
+ onTap: Platform.isIOS
? null
: () async {
try {
@@ -216,20 +350,16 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> {
suffixIcon: UnconstrainedBox(
child: Row(
children: [
- const SizedBox(
- width: 16,
- ),
+ const SizedBox(width: 16),
SvgPicture.asset(
Assets.svg.folder,
- color: Theme.of(context)
- .extension<StackColors>()!
- .textDark3,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textDark3,
width: 16,
height: 16,
),
- const SizedBox(
- width: 12,
- ),
+ const SizedBox(width: 12),
],
),
),
@@ -249,21 +379,18 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> {
);
},
),
- if (!Platform.isAndroid && !Platform.isIOS)
- const SizedBox(
- height: 24,
- ),
+ if (!Platform.isIOS) const SizedBox(height: 24),
if (isDesktop)
Padding(
padding: const EdgeInsets.only(bottom: 10.0),
child: Text(
"Create a passphrase",
- style:
- STextStyles.desktopTextExtraSmall(context).copyWith(
- color: Theme.of(context)
- .extension<StackColors>()!
- .textDark3,
- ),
+ style: STextStyles.desktopTextExtraSmall(context)
+ .copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textDark3,
+ ),
textAlign: TextAlign.left,
),
),
@@ -279,46 +406,44 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> {
obscureText: hidePassword,
enableSuggestions: false,
autocorrect: false,
- decoration: standardInputDecoration(
- "Create passphrase",
- passphraseFocusNode,
- context,
- ).copyWith(
- labelStyle:
- isDesktop ? STextStyles.fieldLabel(context) : null,
- suffixIcon: UnconstrainedBox(
- child: Row(
- children: [
- const SizedBox(
- width: 16,
- ),
- GestureDetector(
- key: const Key(
- "createBackupPasswordFieldShowPasswordButtonKey",
- ),
- onTap: () async {
- setState(() {
- hidePassword = !hidePassword;
- });
- },
- child: SvgPicture.asset(
- hidePassword
- ? Assets.svg.eye
- : Assets.svg.eyeSlash,
- color: Theme.of(context)
- .extension<StackColors>()!
- .textDark3,
- width: 16,
- height: 16,
- ),
- ),
- const SizedBox(
- width: 12,
+ decoration:
+ standardInputDecoration(
+ "Create passphrase",
+ passphraseFocusNode,
+ context,
+ ).copyWith(
+ labelStyle: isDesktop
+ ? STextStyles.fieldLabel(context)
+ : null,
+ suffixIcon: UnconstrainedBox(
+ child: Row(
+ children: [
+ const SizedBox(width: 16),
+ GestureDetector(
+ key: const Key(
+ "createBackupPasswordFieldShowPasswordButtonKey",
+ ),
+ onTap: () async {
+ setState(() {
+ hidePassword = !hidePassword;
+ });
+ },
+ child: SvgPicture.asset(
+ hidePassword
+ ? Assets.svg.eye
+ : Assets.svg.eyeSlash,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textDark3,
+ width: 16,
+ height: 16,
+ ),
+ ),
+ const SizedBox(width: 12),
+ ],
),
- ],
+ ),
),
- ),
- ),
onChanged: (newValue) {
if (newValue.isEmpty) {
setState(() {
@@ -386,26 +511,25 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> {
width: 512,
height: 5,
fillColor: passwordStrength < 0.51
- ? Theme.of(context)
- .extension<StackColors>()!
- .accentColorRed
+ ? Theme.of(
+ context,
+ ).extension<StackColors>()!.accentColorRed
: passwordStrength < 1
- ? Theme.of(context)
- .extension<StackColors>()!
- .accentColorYellow
- : Theme.of(context)
- .extension<StackColors>()!
- .accentColorGreen,
- backgroundColor: Theme.of(context)
- .extension<StackColors>()!
- .buttonBackSecondary,
- percent:
- passwordStrength < 0.25 ? 0.03 : passwordStrength,
+ ? Theme.of(
+ context,
+ ).extension<StackColors>()!.accentColorYellow
+ : Theme.of(
+ context,
+ ).extension<StackColors>()!.accentColorGreen,
+ backgroundColor: Theme.of(
+ context,
+ ).extension<StackColors>()!.buttonBackSecondary,
+ percent: passwordStrength < 0.25
+ ? 0.03
+ : passwordStrength,
),
),
- const SizedBox(
- height: 16,
- ),
+ const SizedBox(height: 16),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
@@ -418,45 +542,42 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> {
obscureText: hidePassword,
enableSuggestions: false,
autocorrect: false,
- decoration: standardInputDecoration(
- "Confirm passphrase",
- passphraseRepeatFocusNode,
- context,
- ).copyWith(
- labelStyle: STextStyles.fieldLabel(context),
- suffixIcon: UnconstrainedBox(
- child: Row(
- children: [
- const SizedBox(
- width: 16,
- ),
- GestureDetector(
- key: const Key(
- "createBackupPasswordFieldShowPasswordButtonKey",
- ),
- onTap: () async {
- setState(() {
- hidePassword = !hidePassword;
- });
- },
- child: SvgPicture.asset(
- hidePassword
- ? Assets.svg.eye
- : Assets.svg.eyeSlash,
- color: Theme.of(context)
- .extension<StackColors>()!
- .textDark3,
- width: 16,
- height: 16,
- ),
- ),
- const SizedBox(
- width: 12,
+ decoration:
+ standardInputDecoration(
+ "Confirm passphrase",
+ passphraseRepeatFocusNode,
+ context,
+ ).copyWith(
+ labelStyle: STextStyles.fieldLabel(context),
+ suffixIcon: UnconstrainedBox(
+ child: Row(
+ children: [
+ const SizedBox(width: 16),
+ GestureDetector(
+ key: const Key(
+ "createBackupPasswordFieldShowPasswordButtonKey",
+ ),
+ onTap: () async {
+ setState(() {
+ hidePassword = !hidePassword;
+ });
+ },
+ child: SvgPicture.asset(
+ hidePassword
+ ? Assets.svg.eye
+ : Assets.svg.eyeSlash,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textDark3,
+ width: 16,
+ height: 16,
+ ),
+ ),
+ const SizedBox(width: 12),
+ ],
),
- ],
+ ),
),
- ),
- ),
onChanged: (newValue) {
setState(() {});
// TODO: ? check if passwords match?
@@ -466,9 +587,7 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> {
],
),
),
- const SizedBox(
- height: 24,
- ),
+ const SizedBox(height: 24),
Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(left: 32),
@@ -480,47 +599,39 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> {
textAlign: TextAlign.left,
),
),
- const SizedBox(
- height: 10,
- ),
+ const SizedBox(height: 10),
Padding(
- padding: const EdgeInsets.only(
- left: 32,
- right: 32,
- ),
+ padding: const EdgeInsets.only(left: 32, right: 32),
child: isDesktop
? DropdownButtonHideUnderline(
child: DropdownButton2(
isExpanded: true,
value: _currentDropDownValue,
items: [
- ..._dropDownItems.map(
- (e) {
- String message = "";
- switch (e) {
- case BackupFrequencyType.everyTenMinutes:
- message = "Every 10 minutes";
- break;
- case BackupFrequencyType.everyAppStart:
- message = "Every app startup";
- break;
- case BackupFrequencyType.afterClosingAWallet:
- message =
- "After closing a cryptocurrency wallet";
- break;
- }
-
- return DropdownMenuItem(
- value: e,
- child: Text(
- message,
- style: STextStyles.desktopTextExtraExtraSmall(
- context,
- ),
+ ..._dropDownItems.map((e) {
+ String message = "";
+ switch (e) {
+ case BackupFrequencyType.everyTenMinutes:
+ message = "Every 10 minutes";
+ break;
+ case BackupFrequencyType.everyAppStart:
+ message = "Every app startup";
+ break;
+ case BackupFrequencyType.afterClosingAWallet:
+ message = "After closing a cryptocurrency wallet";
+ break;
+ }
+
+ return DropdownMenuItem(
+ value: e,
+ child: Text(
+ message,
+ style: STextStyles.desktopTextExtraExtraSmall(
+ context,
),
- );
- },
- ),
+ ),
+ );
+ }),
],
onChanged: (value) {
if (value is BackupFrequencyType) {
@@ -529,8 +640,9 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> {
.backupFrequencyType !=
value) {
ref
- .read(prefsChangeNotifierProvider)
- .backupFrequencyType = value;
+ .read(prefsChangeNotifierProvider)
+ .backupFrequencyType =
+ value;
}
setState(() {
_currentDropDownValue = value;
@@ -542,18 +654,18 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> {
Assets.svg.chevronDown,
width: 10,
height: 5,
- color: Theme.of(context)
- .extension<StackColors>()!
- .textDark3,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textDark3,
),
),
dropdownStyleData: DropdownStyleData(
offset: const Offset(0, -10),
elevation: 0,
decoration: BoxDecoration(
- color: Theme.of(context)
- .extension<StackColors>()!
- .textFieldDefaultBG,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textFieldDefaultBG,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
@@ -581,292 +693,13 @@ class _CreateAutoBackup extends ConsumerState<CreateAutoBackup> {
onPressed: Navigator.of(context).pop,
),
),
- const SizedBox(
- width: 16,
- ),
+ const SizedBox(width: 16),
Expanded(
child: PrimaryButton(
buttonHeight: ButtonHeight.l,
label: "Enable Auto Backup",
enabled: shouldEnableCreate,
- onPressed: !shouldEnableCreate
- ? null
- : () async {
- final String pathToSave =
- fileLocationController.text;
- final String passphrase = passphraseController.text;
- final String repeatPassphrase =
- passphraseRepeatController.text;
-
- if (pathToSave.isEmpty) {
- unawaited(
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: "Directory not chosen",
- context: context,
- ),
- );
- return;
- }
- if (!(await Directory(pathToSave).exists())) {
- unawaited(
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: "Directory does not exist",
- context: context,
- ),
- );
- return;
- }
- if (passphrase.isEmpty) {
- unawaited(
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: "A passphrase is required",
- context: context,
- ),
- );
- return;
- }
- if (passphrase != repeatPassphrase) {
- unawaited(
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: "Passphrase does not match",
- context: context,
- ),
- );
- return;
- }
-
- unawaited(
- showDialog<dynamic>(
- context: context,
- barrierDismissible: false,
- builder: (_) {
- if (Util.isDesktop) {
- return DesktopDialog(
- maxHeight: double.infinity,
- maxWidth: 450,
- child: Padding(
- padding: const EdgeInsets.all(
- 32,
- ),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment:
- CrossAxisAlignment.start,
- children: [
- Text(
- "Encrypting initial backup",
- style: STextStyles.desktopH3(
- context,
- ),
- ),
- const SizedBox(
- height: 40,
- ),
- Text(
- "This shouldn't take long",
- style: STextStyles
- .desktopTextExtraExtraSmall(
- context,
- ),
- ),
- ],
- ),
- ),
- );
- } else {
- return const StackDialog(
- title: "Encrypting initial backup",
- message: "This shouldn't take long",
- );
- }
- },
- ),
- );
-
- // make sure the dialog is able to be displayed for at least some time
- final fut = Future<void>.delayed(
- const Duration(milliseconds: 300),
- );
-
- String adkString;
- int adkVersion;
- try {
- final adk =
- await compute(generateAdk, passphrase);
- adkString = Format.uint8listToString(adk.item2);
- adkVersion = adk.item1;
- } on Exception catch (e, s) {
- final String err =
- getErrorMessageFromSWBException(e);
- Logging.instance.e(
- err,
- error: e,
- stackTrace: s,
- );
- // pop encryption progress dialog
- Navigator.of(context).pop();
- unawaited(
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: err,
- context: context,
- ),
- );
- return;
- } catch (e, s) {
- Logging.instance.e(
- "",
- error: e,
- stackTrace: s,
- );
- // pop encryption progress dialog
- Navigator.of(context).pop();
- unawaited(
- showFloatingFlushBar(
- type: FlushBarType.warning,
- message: "$e",
- context: context,
- ),
- );
- return;
- }
-
- await secureStore.write(
- key: "auto_adk_string",
- value: adkString,
- );
- await secureStore.write(
- key: "auto_adk_version_string",
- value: adkVersion.toString(),
- );
-
- final DateTime now = DateTime.now();
- final String fileToSave =
- createAutoBackupFilename(pathToSave, now);
-
- final backup = await SWB.createStackWalletJSON(
- secureStorage: secureStore,
- );
-
- final bool result =
- await SWB.encryptStackWalletWithADK(
- fileToSave,
- adkString,
- jsonEncode(backup),
- adkVersion,
- );
-
- // this future should already be complete unless there was an error encrypting
- await Future.wait([fut]);
-
- if (mounted) {
- // pop encryption progress dialog
- Navigator.of(context).pop();
-
- if (result) {
- ref
- .read(prefsChangeNotifierProvider)
- .autoBackupLocation = pathToSave;
- ref
- .read(prefsChangeNotifierProvider)
- .lastAutoBackup = now;
-
- ref
- .read(prefsChangeNotifierProvider)
- .isAutoBackupEnabled = true;
-
- await showDialog<dynamic>(
- context: context,
- barrierDismissible: false,
- builder: (context) {
- if (Platform.isAndroid) {
- return StackOkDialog(
- title:
- "${AppConfig.prefix} Auto Backup enabled and saved to:",
- message: fileToSave,
- );
- } else if (Util.isDesktop) {
- return DesktopDialog(
- maxHeight: double.infinity,
- maxWidth: 500,
- child: Padding(
- padding: const EdgeInsets.only(
- left: 32,
- right: 32,
- bottom: 32,
- ),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment:
- CrossAxisAlignment.start,
- children: [
- Row(
- mainAxisAlignment:
- MainAxisAlignment
- .spaceBetween,
- children: [
- Text(
- "${AppConfig.prefix} Auto Backup enabled!",
- style:
- STextStyles.desktopH3(
- context,
- ),
- ),
- const DesktopDialogCloseButton(),
- ],
- ),
- const SizedBox(
- height: 40,
- ),
- Row(
- children: [
- const Spacer(),
- Expanded(
- child: PrimaryButton(
- label: "Ok",
- buttonHeight:
- ButtonHeight.l,
- onPressed: () {
- Navigator.of(context)
- .pop();
- },
- ),
- ),
- ],
- ),
- ],
- ),
- ),
- );
- } else {
- return const StackOkDialog(
- title:
- "${AppConfig.prefix} Auto Backup enabled!",
- );
- }
- },
- );
- if (mounted) {
- passphraseController.text = "";
- passphraseRepeatController.text = "";
-
- Navigator.of(context).pop();
- }
- } else {
- await showDialog<dynamic>(
- context: context,
- barrierDismissible: false,
- builder: (_) => const StackOkDialog(
- title: "Failed to enable Auto Backup",
- ),
- );
- }
- }
- },
+ onPressed: !shouldEnableCreate ? null : _enableAutoBackup,
),
),
],
diff --git a/lib/services/auto_swb_service.dart b/lib/services/auto_swb_service.dart
index 24f58d0..39b080a 100644
--- a/lib/services/auto_swb_service.dart
+++ b/lib/services/auto_swb_service.dart
@@ -20,11 +20,7 @@ import '../utilities/flutter_secure_storage_interface.dart';
import '../utilities/logger.dart';
import '../utilities/prefs.dart';
-enum AutoSWBStatus {
- idle,
- backingUp,
- error,
-}
+enum AutoSWBStatus { idle, backingUp, error }
class AutoSWBService extends ChangeNotifier {
Timer? _timer;
@@ -74,27 +70,28 @@ class AutoSWBService extends ChangeNotifier {
);
final jsonString = jsonEncode(json);
- final adkString =
- await secureStorageInterface.read(key: "auto_adk_string");
+ final adkString = await secureStorageInterface.read(
+ key: "auto_adk_string",
+ );
- final adkVersionString =
- await secureStorageInterface.read(key: "auto_adk_version_string");
+ final adkVersionString = await secureStorageInterface.read(
+ key: "auto_adk_version_string",
+ );
final int adkVersion = int.parse(adkVersionString!);
final DateTime now = DateTime.now();
- final String fileToSave =
- createAutoBackupFilename(autoBackupDirectoryPath, now);
+ final String fileToSave = createAutoBackupFilename(
+ autoBackupDirectoryPath,
+ now,
+ );
- final result = await SWB.encryptStackWalletWithADK(
- fileToSave,
+ final content = await SWB.encryptStackWalletWithADK(
adkString!,
jsonString,
adkVersion,
);
- if (!result) {
- throw Exception("stack auto backup service failed to create a backup");
- }
+ await File(fileToSave).writeAsString(content, flush: true);
Prefs.instance.lastAutoBackup = now;
Why this scored 34/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.