desktop create password view

This commit is contained in:
julian 2022-09-15 13:48:48 -06:00
parent 28916a9b93
commit c4b546236c
2 changed files with 419 additions and 0 deletions

View file

@ -0,0 +1,409 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/cfcolors.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/progress_bar.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:zxcvbn/zxcvbn.dart';
import '../../notifications/show_flush_bar.dart';
import '../../utilities/enums/flush_bar_type.dart';
class CreatePasswordView extends StatefulWidget {
const CreatePasswordView({
Key? key,
this.secureStore = const SecureStorageWrapper(
FlutterSecureStorage(),
),
}) : super(key: key);
static const String routeName = "/createPasswordDesktop";
final FlutterSecureStorageInterface secureStore;
@override
State<CreatePasswordView> createState() => _CreatePasswordViewState();
}
class _CreatePasswordViewState extends State<CreatePasswordView> {
late final TextEditingController passwordController;
late final TextEditingController passwordRepeatController;
late final FocusNode passwordFocusNode;
late final FocusNode passwordRepeatFocusNode;
final zxcvbn = Zxcvbn();
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.";
bool shouldShowPasswordHint = true;
bool hidePassword = true;
double passwordStrength = 0.0;
bool get nextEnabled =>
passwordController.text.isNotEmpty &&
passwordRepeatController.text.isNotEmpty;
bool get fieldsMatch =>
passwordController.text == passwordRepeatController.text;
void onNextPressed() async {
final String passphrase = passwordController.text;
final String repeatPassphrase = passwordRepeatController.text;
if (passphrase.isEmpty) {
unawaited(showFloatingFlushBar(
type: FlushBarType.warning,
message: "A password is required",
context: context,
));
return;
}
if (passphrase != repeatPassphrase) {
unawaited(showFloatingFlushBar(
type: FlushBarType.warning,
message: "Password does not match",
context: context,
));
return;
}
await widget.secureStore
.write(key: "stackDesktopPassword", value: passphrase);
unawaited(showFloatingFlushBar(
type: FlushBarType.success,
message: "Your password is set up",
context: context,
));
}
@override
void initState() {
passwordController = TextEditingController();
passwordRepeatController = TextEditingController();
passwordFocusNode = FocusNode();
passwordRepeatFocusNode = FocusNode();
super.initState();
}
@override
void dispose() {
passwordController.dispose();
passwordRepeatController.dispose();
passwordFocusNode.dispose();
passwordRepeatFocusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType ");
return Material(
child: Column(
children: [
Row(
children: [
AppBarBackButton(
onPressed: () async {
if (mounted) {
Navigator.of(context).pop();
}
},
),
const Spacer(),
SizedBox(
height: 56,
child: TextButton(
style: CFColors.getSecondaryEnabledButtonColor(context),
onPressed: () {
//
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 30,
),
child: Text(
"Exit to My Stack",
style: STextStyles.desktopButtonSecondaryEnabled,
),
),
),
),
const SizedBox(
width: 24,
),
],
),
Expanded(
child: Center(
child: SizedBox(
width: 480,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Create a password",
style: STextStyles.desktopH2,
),
const SizedBox(
height: 16,
),
Text(
"Protect your funds with a strong password",
style: STextStyles.desktopSubtitleH2,
),
const SizedBox(
height: 40,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
key: const Key("createBackupPasswordFieldKey1"),
focusNode: passwordFocusNode,
controller: passwordController,
style: STextStyles.desktopTextMedium.copyWith(
height: 2,
),
obscureText: hidePassword,
enableSuggestions: false,
autocorrect: false,
decoration: standardInputDecoration(
"Create password",
passwordFocusNode,
).copyWith(
suffixIcon: UnconstrainedBox(
child: SizedBox(
height: 70,
child: Row(
children: [
GestureDetector(
key: const Key(
"createDesktopPasswordFieldShowPasswordButtonKey"),
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: CFColors.neutral50,
width: 24,
height: 19,
),
),
),
),
const SizedBox(
width: 10,
),
],
),
),
),
),
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(
top: passwordFeedback.isNotEmpty ? 4 : 8,
),
child: passwordFeedback.isNotEmpty
? Text(
passwordFeedback,
style:
STextStyles.desktopTextExtraSmall.copyWith(
color: CFColors.textSubtitle1,
),
)
: null,
),
if (passwordFocusNode.hasFocus ||
passwordRepeatFocusNode.hasFocus ||
passwordController.text.isNotEmpty)
Padding(
padding: const EdgeInsets.only(
top: 10,
),
child: ProgressBar(
key: const Key("createDesktopPasswordProgressBar"),
width: 458,
height: 8,
fillColor: passwordStrength < 0.51
? CFColors.stackRed
: passwordStrength < 1
? CFColors.stackYellow
: CFColors.stackGreen,
backgroundColor: CFColors.buttonGray,
percent:
passwordStrength < 0.25 ? 0.03 : passwordStrength,
),
),
const SizedBox(
height: 16,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
key: const Key("createDesktopPasswordFieldKey2"),
focusNode: passwordRepeatFocusNode,
controller: passwordRepeatController,
style: STextStyles.desktopTextMedium.copyWith(
height: 2,
),
obscureText: hidePassword,
enableSuggestions: false,
autocorrect: false,
decoration: standardInputDecoration(
"Confirm password",
passwordRepeatFocusNode,
).copyWith(
suffixIcon: UnconstrainedBox(
child: SizedBox(
height: 70,
child: Row(
children: [
GestureDetector(
key: const Key(
"createDesktopPasswordFieldShowPasswordButtonKey2"),
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(
fieldsMatch && passwordStrength == 1
? Assets.svg.checkCircle
: hidePassword
? Assets.svg.eye
: Assets.svg.eyeSlash,
color: fieldsMatch &&
passwordStrength == 1
? CFColors.stackGreen
: CFColors.neutral50,
width: 24,
height: 19,
),
),
),
),
const SizedBox(
width: 10,
),
],
),
),
),
),
onChanged: (newValue) {
setState(() {});
},
),
),
const SizedBox(
height: 32,
),
SizedBox(
width: 480,
height: 70,
child: TextButton(
style: nextEnabled
? CFColors.getPrimaryEnabledButtonColor(context)
: CFColors.getPrimaryDisabledButtonColor(context),
onPressed: nextEnabled ? onNextPressed : null,
child: Text(
"Next",
style: nextEnabled
? STextStyles.desktopButtonEnabled
: STextStyles.desktopButtonDisabled,
),
),
),
],
),
),
),
),
const SizedBox(
// balance out height of "appbar"
// 56 = height of app bar buttons
// 20 = top and bottom padding
height: 56 + 20 + 20,
),
],
),
);
}
}

View file

@ -75,6 +75,7 @@ import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_deta
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_search_filter_view.dart';
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
import 'package:stackwallet/pages/wallets_view/wallets_view.dart';
import 'package:stackwallet/pages_desktop_specific/create_password/create_password_view.dart';
import 'package:stackwallet/services/coins/manager.dart';
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
@ -876,6 +877,15 @@ class RouteGenerator {
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
// == Desktop specific routes ============================================
case CreatePasswordView.routeName:
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => const CreatePasswordView(),
settings: RouteSettings(name: settings.name));
// == End of desktop specific routes =======================================
default:
return _routeError("");
}