added conditional for desktop manual and restore backup

This commit is contained in:
ryleedavis 2022-11-04 14:18:54 -06:00
parent 9231c3a2a5
commit a6c380592e
2 changed files with 695 additions and 663 deletions

View file

@ -15,6 +15,7 @@ import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/progress_bar.dart'; import 'package:stackwallet/widgets/progress_bar.dart';
import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:stackwallet/widgets/stack_dialog.dart';
@ -93,426 +94,436 @@ class _RestoreFromFileViewState extends State<CreateBackupView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( final isDesktop = Util.isDesktop;
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
title: Text(
"Create backup",
style: STextStyles.navBarTitle(context),
),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (!Platform.isAndroid)
Consumer(builder: (context, ref, __) {
return Container(
color: Colors.transparent,
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
onTap: Platform.isAndroid
? null
: () async {
try {
await stackFileSystem.prepareStorage();
if (mounted) { return ConditionalParent(
await stackFileSystem condition: !isDesktop,
.pickDir(context); builder: (child) {
} return Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
title: Text(
"Create backup",
style: STextStyles.navBarTitle(context),
),
),
body: 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: ConditionalParent(
condition: isDesktop,
builder: (child) {
return Column(
children: [
Text(
"Choose file location",
style: STextStyles.desktopTextExtraSmall(context).copyWith(
color:
Theme.of(context).extension<StackColors>()!.textDark3),
),
// child,
],
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (!Platform.isAndroid)
Consumer(builder: (context, ref, __) {
return Container(
color: Colors.transparent,
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
onTap: Platform.isAndroid
? null
: () async {
try {
await stackFileSystem.prepareStorage();
if (mounted) { if (mounted) {
setState(() { await stackFileSystem.pickDir(context);
fileLocationController.text = }
stackFileSystem.dirPath ?? "";
}); if (mounted) {
} setState(() {
} catch (e, s) { fileLocationController.text =
Logging.instance.log("$e\n$s", stackFileSystem.dirPath ?? "";
level: LogLevel.Error); });
} }
}, } catch (e, s) {
controller: fileLocationController, Logging.instance
style: STextStyles.field(context), .log("$e\n$s", level: LogLevel.Error);
decoration: InputDecoration( }
hintText: "Save to...", },
hintStyle: STextStyles.fieldLabel(context), controller: fileLocationController,
suffixIcon: UnconstrainedBox( style: STextStyles.field(context),
child: Row( decoration: InputDecoration(
children: [ hintText: "Save to...",
const SizedBox( hintStyle: STextStyles.fieldLabel(context),
width: 16, suffixIcon: UnconstrainedBox(
), child: Row(
SvgPicture.asset( children: [
Assets.svg.folder, const SizedBox(
color: Theme.of(context) width: 16,
.extension<StackColors>()!
.textDark3,
width: 16,
height: 16,
),
const SizedBox(
width: 12,
),
],
),
),
),
key: const Key(
"createBackupSaveToFileLocationTextFieldKey"),
readOnly: true,
toolbarOptions: const ToolbarOptions(
copy: true,
cut: false,
paste: false,
selectAll: false,
),
onChanged: (newValue) {
// ref.read(addressEntryDataProvider(widget.id)).address = newValue;
},
), ),
); SvgPicture.asset(
}), Assets.svg.folder,
if (!Platform.isAndroid) color: Theme.of(context)
.extension<StackColors>()!
.textDark3,
width: 16,
height: 16,
),
const SizedBox(
width: 12,
),
],
),
),
),
key:
const Key("createBackupSaveToFileLocationTextFieldKey"),
readOnly: true,
toolbarOptions: const ToolbarOptions(
copy: true,
cut: false,
paste: false,
selectAll: false,
),
onChanged: (newValue) {
// ref.read(addressEntryDataProvider(widget.id)).address = newValue;
},
),
);
}),
if (!Platform.isAndroid)
const SizedBox(
height: 8,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
key: const Key("createBackupPasswordFieldKey1"),
focusNode: passwordFocusNode,
controller: passwordController,
style: STextStyles.field(context),
obscureText: hidePassword,
enableSuggestions: false,
autocorrect: false,
decoration: standardInputDecoration(
"Create passphrase",
passwordFocusNode,
context,
).copyWith(
suffixIcon: UnconstrainedBox(
child: Row(
children: [
const SizedBox( const SizedBox(
height: 8, width: 16,
), ),
ClipRRect( GestureDetector(
borderRadius: BorderRadius.circular( key: const Key(
Constants.size.circularBorderRadius, "createBackupPasswordFieldShowPasswordButtonKey"),
), onTap: () async {
child: TextField(
key: const Key("createBackupPasswordFieldKey1"),
focusNode: passwordFocusNode,
controller: passwordController,
style: STextStyles.field(context),
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)
.extension<StackColors>()!
.textDark3,
width: 16,
height: 16,
),
),
const SizedBox(
width: 12,
),
],
),
),
),
onChanged: (newValue) {
if (newValue.isEmpty) {
setState(() {
passwordFeedback = "";
});
return;
}
final result = zxcvbn.evaluate(newValue);
String suggestionsAndTips = "";
for (var sug
in result.feedback.suggestions!.toSet()) {
suggestionsAndTips += "$sug\n";
}
suggestionsAndTips += result.feedback.warning!;
String feedback =
// "Password Strength: ${((result.score! / 4.0) * 100).toInt()}%\n"
suggestionsAndTips;
passwordStrength = result.score! / 4;
// hack fix to format back string returned from zxcvbn
if (feedback.contains("phrasesNo need")) {
feedback = feedback.replaceFirst(
"phrasesNo need", "phrases\nNo need");
}
if (feedback.endsWith("\n")) {
feedback =
feedback.substring(0, feedback.length - 2);
}
setState(() { setState(() {
passwordFeedback = feedback; hidePassword = !hidePassword;
}); });
}, },
), child: SvgPicture.asset(
), hidePassword ? Assets.svg.eye : Assets.svg.eyeSlash,
if (passwordFocusNode.hasFocus || color: Theme.of(context)
passwordRepeatFocusNode.hasFocus ||
passwordController.text.isNotEmpty)
Padding(
padding: EdgeInsets.only(
left: 12,
right: 12,
top: passwordFeedback.isNotEmpty ? 4 : 0,
),
child: passwordFeedback.isNotEmpty
? Text(
passwordFeedback,
style: STextStyles.infoSmall(context),
)
: null,
),
if (passwordFocusNode.hasFocus ||
passwordRepeatFocusNode.hasFocus ||
passwordController.text.isNotEmpty)
Padding(
padding: const EdgeInsets.only(
left: 12,
right: 12,
top: 10,
),
child: ProgressBar(
key: const Key("createStackBackUpProgressBar"),
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>()! .extension<StackColors>()!
.buttonBackSecondary, .textDark3,
percent: passwordStrength < 0.25 width: 16,
? 0.03 height: 16,
: passwordStrength,
), ),
), ),
const SizedBox( const SizedBox(
height: 10, width: 12,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
), ),
child: TextField( ],
key: const Key("createBackupPasswordFieldKey2"), ),
focusNode: passwordRepeatFocusNode,
controller: passwordRepeatController,
style: STextStyles.field(context),
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)
.extension<StackColors>()!
.textDark3,
width: 16,
height: 16,
),
),
const SizedBox(
width: 12,
),
],
),
),
),
onChanged: (newValue) {
setState(() {});
// TODO: ? check if passwords match?
},
),
),
const SizedBox(
height: 16,
),
const Spacer(),
TextButton(
style: shouldEnableCreate
? Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context)
: Theme.of(context)
.extension<StackColors>()!
.getPrimaryDisabledButtonColor(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();
bool result =
await SWB.encryptStackWalletWithPassphrase(
fileToSave,
passphrase,
jsonEncode(backup),
);
if (mounted) {
// pop encryption progress dialog
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),
),
),
],
), ),
), ),
onChanged: (newValue) {
if (newValue.isEmpty) {
setState(() {
passwordFeedback = "";
});
return;
}
final result = zxcvbn.evaluate(newValue);
String suggestionsAndTips = "";
for (var sug in result.feedback.suggestions!.toSet()) {
suggestionsAndTips += "$sug\n";
}
suggestionsAndTips += result.feedback.warning!;
String feedback =
// "Password Strength: ${((result.score! / 4.0) * 100).toInt()}%\n"
suggestionsAndTips;
passwordStrength = result.score! / 4;
// hack fix to format back string returned from zxcvbn
if (feedback.contains("phrasesNo need")) {
feedback = feedback.replaceFirst(
"phrasesNo need", "phrases\nNo need");
}
if (feedback.endsWith("\n")) {
feedback = feedback.substring(0, feedback.length - 2);
}
setState(() {
passwordFeedback = feedback;
});
},
), ),
); ),
}, if (passwordFocusNode.hasFocus ||
passwordRepeatFocusNode.hasFocus ||
passwordController.text.isNotEmpty)
Padding(
padding: EdgeInsets.only(
left: 12,
right: 12,
top: passwordFeedback.isNotEmpty ? 4 : 0,
),
child: passwordFeedback.isNotEmpty
? Text(
passwordFeedback,
style: STextStyles.infoSmall(context),
)
: null,
),
if (passwordFocusNode.hasFocus ||
passwordRepeatFocusNode.hasFocus ||
passwordController.text.isNotEmpty)
Padding(
padding: const EdgeInsets.only(
left: 12,
right: 12,
top: 10,
),
child: ProgressBar(
key: const Key("createStackBackUpProgressBar"),
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,
),
),
const SizedBox(
height: 10,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
key: const Key("createBackupPasswordFieldKey2"),
focusNode: passwordRepeatFocusNode,
controller: passwordRepeatController,
style: STextStyles.field(context),
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)
.extension<StackColors>()!
.textDark3,
width: 16,
height: 16,
),
),
const SizedBox(
width: 12,
),
],
),
),
),
onChanged: (newValue) {
setState(() {});
// TODO: ? check if passwords match?
},
),
),
const SizedBox(
height: 16,
),
const Spacer(),
TextButton(
style: shouldEnableCreate
? Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context)
: Theme.of(context)
.extension<StackColors>()!
.getPrimaryDisabledButtonColor(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();
bool result = await SWB.encryptStackWalletWithPassphrase(
fileToSave,
passphrase,
jsonEncode(backup),
);
if (mounted) {
// pop encryption progress dialog
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),
),
),
],
), ),
), ),
); );

View file

@ -15,13 +15,13 @@ import 'package:stackwallet/utilities/enums/flush_bar_type.dart';
import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/loading_indicator.dart';
import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import 'package:stackwallet/utilities/util.dart';
class RestoreFromFileView extends ConsumerStatefulWidget { class RestoreFromFileView extends ConsumerStatefulWidget {
const RestoreFromFileView({Key? key}) : super(key: key); const RestoreFromFileView({Key? key}) : super(key: key);
@ -65,275 +65,296 @@ class _RestoreFromFileViewState extends ConsumerState<RestoreFromFileView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( final isDesktop = Util.isDesktop;
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
title: Text(
"Restore from file",
style: STextStyles.navBarTitle(context),
),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
onTap: () async {
try {
await stackFileSystem.prepareStorage();
if (mounted) {
await stackFileSystem.openFile(context);
}
if (mounted) { return ConditionalParent(
condition: !isDesktop,
builder: (child) {
return Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 75));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
title: Text(
"Restore from file",
style: STextStyles.navBarTitle(context),
),
),
body: 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: ConditionalParent(
condition: isDesktop,
builder: (child) {
return Column(
children: [
Text(
"Choose file location",
style: STextStyles.desktopTextExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark3),
),
// child,
],
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
onTap: () async {
try {
await stackFileSystem.prepareStorage();
if (mounted) {
await stackFileSystem.openFile(context);
}
if (mounted) {
setState(() {
fileLocationController.text =
stackFileSystem.filePath ?? "";
});
}
} catch (e, s) {
Logging.instance.log("$e\n$s", level: LogLevel.Error);
}
},
controller: fileLocationController,
style: STextStyles.field(context),
decoration: InputDecoration(
hintText: "Choose file...",
hintStyle: STextStyles.fieldLabel(context),
suffixIcon: UnconstrainedBox(
child: Row(
children: [
const SizedBox(
width: 16,
),
SvgPicture.asset(
Assets.svg.folder,
color: Theme.of(context)
.extension<StackColors>()!
.textDark3,
width: 16,
height: 16,
),
const SizedBox(
width: 12,
),
],
),
),
),
key: const Key("restoreFromFileLocationTextFieldKey"),
readOnly: true,
toolbarOptions: const ToolbarOptions(
copy: true,
cut: false,
paste: false,
selectAll: false,
),
onChanged: (newValue) {},
),
const SizedBox(
height: 8,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
key: const Key("restoreFromFilePasswordFieldKey"),
focusNode: passwordFocusNode,
controller: passwordController,
style: STextStyles.field(context),
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(() { setState(() {
fileLocationController.text = hidePassword = !hidePassword;
stackFileSystem.filePath ?? "";
}); });
} },
} catch (e, s) { child: SvgPicture.asset(
Logging.instance hidePassword
.log("$e\n$s", level: LogLevel.Error); ? Assets.svg.eye
} : Assets.svg.eyeSlash,
}, color: Theme.of(context)
controller: fileLocationController, .extension<StackColors>()!
style: STextStyles.field(context), .textDark3,
decoration: InputDecoration( width: 16,
hintText: "Choose file...", height: 16,
hintStyle: STextStyles.fieldLabel(context), ),
suffixIcon: UnconstrainedBox( ),
child: Row( const SizedBox(
width: 12,
),
],
),
),
),
onChanged: (newValue) {
setState(() {});
},
),
),
const SizedBox(
height: 16,
),
const Spacer(),
TextButton(
style: passwordController.text.isEmpty ||
fileLocationController.text.isEmpty
? Theme.of(context)
.extension<StackColors>()!
.getPrimaryDisabledButtonColor(context)
: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context),
onPressed: passwordController.text.isEmpty ||
fileLocationController.text.isEmpty
? null
: () async {
final String fileToRestore =
fileLocationController.text;
final String passphrase = passwordController.text;
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 75));
}
if (!(await File(fileToRestore).exists())) {
showFloatingFlushBar(
type: FlushBarType.warning,
message: "Backup file does not exist",
context: context,
);
return;
}
bool shouldPop = false;
showDialog<dynamic>(
barrierDismissible: false,
context: context,
builder: (_) => WillPopScope(
onWillPop: () async {
return shouldPop;
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const SizedBox( Material(
width: 16, color: Colors.transparent,
), child: Center(
SvgPicture.asset( child: Text(
Assets.svg.folder, "Decrypting Stack backup file",
color: Theme.of(context) style: STextStyles.pageTitleH2(context)
.extension<StackColors>()! .copyWith(
.textDark3, color: Theme.of(context)
width: 16, .extension<StackColors>()!
height: 16, .textWhite,
),
),
),
), ),
const SizedBox( const SizedBox(
width: 12, height: 64,
),
const Center(
child: LoadingIndicator(
width: 100,
),
), ),
], ],
), ),
), ),
), );
key: const Key("restoreFromFileLocationTextFieldKey"),
readOnly: true, final String? jsonString = await compute(
toolbarOptions: const ToolbarOptions( SWB.decryptStackWalletWithPassphrase,
copy: true, Tuple2(fileToRestore, passphrase),
cut: false, debugLabel: "stack wallet decryption compute",
paste: false, );
selectAll: false,
), if (mounted) {
onChanged: (newValue) {}, // pop LoadingIndicator
), shouldPop = true;
const SizedBox( Navigator.of(context).pop();
height: 8,
), passwordController.text = "";
ClipRRect(
borderRadius: BorderRadius.circular( if (jsonString == null) {
Constants.size.circularBorderRadius, showFloatingFlushBar(
), type: FlushBarType.warning,
child: TextField( message: "Failed to decrypt backup file",
key: const Key("restoreFromFilePasswordFieldKey"), context: context,
focusNode: passwordFocusNode, );
controller: passwordController, return;
style: STextStyles.field(context), }
obscureText: hidePassword,
enableSuggestions: false, Navigator.of(context).push(
autocorrect: false, RouteGenerator.getRoute(
decoration: standardInputDecoration( builder: (_) => StackRestoreProgressView(
"Enter password", jsonString: jsonString,
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,
),
),
const SizedBox(
width: 12,
),
],
), ),
), ),
), );
onChanged: (newValue) { }
setState(() {}); },
}, child: Text(
), "Restore",
), style: STextStyles.button(context),
const SizedBox(
height: 16,
),
const Spacer(),
TextButton(
style: passwordController.text.isEmpty ||
fileLocationController.text.isEmpty
? Theme.of(context)
.extension<StackColors>()!
.getPrimaryDisabledButtonColor(context)
: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context),
onPressed: passwordController.text.isEmpty ||
fileLocationController.text.isEmpty
? null
: () async {
final String fileToRestore =
fileLocationController.text;
final String passphrase =
passwordController.text;
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 75));
}
if (!(await File(fileToRestore).exists())) {
showFloatingFlushBar(
type: FlushBarType.warning,
message: "Backup file does not exist",
context: context,
);
return;
}
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 Stack 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.decryptStackWalletWithPassphrase,
Tuple2(fileToRestore, passphrase),
debugLabel: "stack wallet decryption compute",
);
if (mounted) {
// pop LoadingIndicator
shouldPop = true;
Navigator.of(context).pop();
passwordController.text = "";
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,
),
),
);
}
},
child: Text(
"Restore",
style: STextStyles.button(context),
),
),
],
),
), ),
), ),
); ],
}, ),
), ));
),
);
} }
} }