added textfield functionality to desktop create auto backup

This commit is contained in:
ryleedavis 2022-11-08 12:15:31 -07:00
parent 8af1350b95
commit 95716bd0f6

View file

@ -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(