mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-03-26 00:58:50 +00:00
added textfield functionality to desktop create auto backup
This commit is contained in:
parent
8af1350b95
commit
95716bd0f6
1 changed files with 306 additions and 183 deletions
|
@ -1,14 +1,23 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/log_level_enum.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
|
||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||
import 'package:stackwallet/widgets/progress_bar.dart';
|
||||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
||||
import 'package:zxcvbn/zxcvbn.dart';
|
||||
|
||||
class CreateAutoBackup extends StatefulWidget {
|
||||
const CreateAutoBackup({Key? key}) : super(key: key);
|
||||
|
@ -22,13 +31,24 @@ class _CreateAutoBackup extends State<CreateAutoBackup> {
|
|||
late final TextEditingController passphraseController;
|
||||
late final TextEditingController passphraseRepeatController;
|
||||
|
||||
late final FocusNode chooseFileLocation;
|
||||
late final StackFileSystem stackFileSystem;
|
||||
late final FocusNode passphraseFocusNode;
|
||||
late final FocusNode passphraseRepeatFocusNode;
|
||||
final zxcvbn = Zxcvbn();
|
||||
|
||||
bool shouldShowPasswordHint = true;
|
||||
bool hidePassword = true;
|
||||
|
||||
String passwordFeedback =
|
||||
"Add another word or two. Uncommon words are better. Use a few words, avoid common phrases. No need for symbols, digits, or uppercase letters.";
|
||||
double passwordStrength = 0.0;
|
||||
|
||||
bool get shouldEnableCreate {
|
||||
return fileLocationController.text.isNotEmpty &&
|
||||
passphraseController.text.isNotEmpty &&
|
||||
passphraseRepeatController.text.isNotEmpty;
|
||||
}
|
||||
|
||||
bool get fieldsMatch =>
|
||||
passphraseController.text == passphraseRepeatController.text;
|
||||
|
||||
|
@ -42,14 +62,26 @@ class _CreateAutoBackup extends State<CreateAutoBackup> {
|
|||
|
||||
@override
|
||||
void initState() {
|
||||
stackFileSystem = StackFileSystem();
|
||||
|
||||
fileLocationController = TextEditingController();
|
||||
passphraseController = TextEditingController();
|
||||
passphraseRepeatController = TextEditingController();
|
||||
|
||||
chooseFileLocation = FocusNode();
|
||||
passphraseFocusNode = FocusNode();
|
||||
passphraseRepeatFocusNode = FocusNode();
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||
final dir = await stackFileSystem.prepareStorage();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
fileLocationController.text = dir.path;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
@ -59,7 +91,6 @@ class _CreateAutoBackup extends State<CreateAutoBackup> {
|
|||
passphraseController.dispose();
|
||||
passphraseRepeatController.dispose();
|
||||
|
||||
chooseFileLocation.dispose();
|
||||
passphraseFocusNode.dispose();
|
||||
passphraseRepeatFocusNode.dispose();
|
||||
|
||||
|
@ -71,9 +102,9 @@ class _CreateAutoBackup extends State<CreateAutoBackup> {
|
|||
debugPrint("BUILD: $runtimeType ");
|
||||
|
||||
String? selectedItem = "Every 10 minutes";
|
||||
|
||||
final isDesktop = Util.isDesktop;
|
||||
return DesktopDialog(
|
||||
maxHeight: 650,
|
||||
maxHeight: 680,
|
||||
maxWidth: 600,
|
||||
child: Column(
|
||||
children: [
|
||||
|
@ -127,198 +158,289 @@ class _CreateAutoBackup extends State<CreateAutoBackup> {
|
|||
height: 10,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
right: 32,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
key: const Key("backupChooseFileLocation"),
|
||||
focusNode: chooseFileLocation,
|
||||
controller: fileLocationController,
|
||||
style: STextStyles.desktopTextMedium(context).copyWith(
|
||||
height: 2,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
enableSuggestions: false,
|
||||
autocorrect: false,
|
||||
decoration: standardInputDecoration(
|
||||
"Save to...",
|
||||
chooseFileLocation,
|
||||
context,
|
||||
).copyWith(
|
||||
labelStyle:
|
||||
STextStyles.desktopTextExtraExtraSmall(context).copyWith(
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.textDark3,
|
||||
),
|
||||
suffixIcon: Container(
|
||||
decoration: BoxDecoration(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (!Platform.isAndroid)
|
||||
Consumer(builder: (context, ref, __) {
|
||||
return Container(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(1000),
|
||||
),
|
||||
height: 32,
|
||||
width: 32,
|
||||
child: Center(
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.folder,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
width: 20,
|
||||
height: 17.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: const EdgeInsets.only(left: 32),
|
||||
child: Text(
|
||||
"Create a passphrase",
|
||||
style: STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color: Theme.of(context).extension<StackColors>()!.textDark3,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
right: 32,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
key: const Key("createBackupPassphrase"),
|
||||
focusNode: passphraseFocusNode,
|
||||
controller: passphraseController,
|
||||
style: STextStyles.desktopTextMedium(context).copyWith(
|
||||
height: 2,
|
||||
),
|
||||
obscureText: hidePassword,
|
||||
enableSuggestions: false,
|
||||
autocorrect: false,
|
||||
decoration: standardInputDecoration(
|
||||
"Create passphrase",
|
||||
passphraseFocusNode,
|
||||
context,
|
||||
).copyWith(
|
||||
labelStyle:
|
||||
STextStyles.desktopTextExtraExtraSmall(context).copyWith(
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.textDark3,
|
||||
),
|
||||
suffixIcon: UnconstrainedBox(
|
||||
child: GestureDetector(
|
||||
key: const Key(
|
||||
"createDesktopAutoBackupShowPassphraseButton1"),
|
||||
onTap: () async {
|
||||
setState(() {
|
||||
hidePassword = !hidePassword;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(1000),
|
||||
),
|
||||
height: 32,
|
||||
width: 32,
|
||||
child: Center(
|
||||
child: SvgPicture.asset(
|
||||
hidePassword ? Assets.svg.eye : Assets.svg.eyeSlash,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
width: 20,
|
||||
height: 17.5,
|
||||
child: TextField(
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
onTap: Platform.isAndroid
|
||||
? null
|
||||
: () async {
|
||||
try {
|
||||
await stackFileSystem.prepareStorage();
|
||||
|
||||
if (mounted) {
|
||||
await stackFileSystem.pickDir(context);
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
fileLocationController.text =
|
||||
stackFileSystem.dirPath ?? "";
|
||||
});
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance
|
||||
.log("$e\n$s", level: LogLevel.Error);
|
||||
}
|
||||
},
|
||||
controller: fileLocationController,
|
||||
style: STextStyles.field(context),
|
||||
decoration: InputDecoration(
|
||||
hintText: "Save to...",
|
||||
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(
|
||||
"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: 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),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
right: 32,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
key: const Key("createBackupPassphrase"),
|
||||
focusNode: passphraseRepeatFocusNode,
|
||||
controller: passphraseRepeatController,
|
||||
style: STextStyles.desktopTextMedium(context).copyWith(
|
||||
height: 2,
|
||||
),
|
||||
obscureText: hidePassword,
|
||||
enableSuggestions: false,
|
||||
autocorrect: false,
|
||||
decoration: standardInputDecoration(
|
||||
"Confirm passphrase",
|
||||
passphraseRepeatFocusNode,
|
||||
context,
|
||||
).copyWith(
|
||||
labelStyle:
|
||||
STextStyles.desktopTextExtraExtraSmall(context).copyWith(
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.textDark3,
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
suffixIcon: UnconstrainedBox(
|
||||
child: GestureDetector(
|
||||
key: const Key(
|
||||
"createDesktopAutoBackupShowPassphraseButton2"),
|
||||
onTap: () async {
|
||||
child: TextField(
|
||||
key: const Key("createBackupPasswordFieldKey1"),
|
||||
focusNode: passphraseFocusNode,
|
||||
controller: passphraseController,
|
||||
style: STextStyles.field(context),
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
onChanged: (newValue) {
|
||||
if (newValue.isEmpty) {
|
||||
setState(() {
|
||||
hidePassword = !hidePassword;
|
||||
passwordFeedback = "";
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(1000),
|
||||
),
|
||||
height: 32,
|
||||
width: 32,
|
||||
child: Center(
|
||||
child: SvgPicture.asset(
|
||||
hidePassword ? Assets.svg.eye : Assets.svg.eyeSlash,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
width: 20,
|
||||
height: 17.5,
|
||||
),
|
||||
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 (passphraseFocusNode.hasFocus ||
|
||||
passphraseRepeatFocusNode.hasFocus ||
|
||||
passphraseController.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 (passphraseFocusNode.hasFocus ||
|
||||
passphraseRepeatFocusNode.hasFocus ||
|
||||
passphraseController.text.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 12,
|
||||
right: 12,
|
||||
top: 10,
|
||||
),
|
||||
child: ProgressBar(
|
||||
key: const Key("createStackBackUpProgressBar"),
|
||||
width: 510,
|
||||
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: passphraseRepeatFocusNode,
|
||||
controller: passphraseRepeatController,
|
||||
style: STextStyles.field(context),
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
onChanged: (newValue) {
|
||||
setState(() {});
|
||||
// TODO: ? check if passwords match?
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
|
@ -376,6 +498,7 @@ class _CreateAutoBackup extends State<CreateAutoBackup> {
|
|||
},
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Row(
|
||||
|
|
Loading…
Reference in a new issue