From 7e7f095719fbcfd543da179c15e75756e8697def Mon Sep 17 00:00:00 2001 From: redDwarf03 Date: Fri, 28 Jun 2024 15:20:34 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=84=20Change=20password=20mangement=20?= =?UTF-8?q?UX?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/l10n/intl_en.arb | 1 + lib/l10n/intl_fr.arb | 1 + .../views/authenticate/password_screen.dart | 134 +++-- lib/ui/views/settings/set_password.dart | 480 +++++++++++------- 4 files changed, 398 insertions(+), 218 deletions(-) diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 9fc5f1118..64dabcfe2 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -243,6 +243,7 @@ "enterPasswordHint": "Enter your password", "passwordsDontMatch": "Passwords do not match", "passwordBlank": "Password cannot be empty", + "passwordConfirmationBlank": "Password confirmation cannot be empty", "welcomeDisclaimerChoice": "I have read and agree to the privacy policy", "welcomeDisclaimerLink": "Terms of use", "showBalances": "Show balances", diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index de21bad33..32543421a 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -233,6 +233,7 @@ "enterPasswordHint": "Saisissez votre mot de passe", "passwordsDontMatch": "Les mots de passe ne correspondent pas", "passwordBlank": "Le mot de passe est obligatoire", + "passwordConfirmationBlank": "La confirmation du mot de passe est obligatoire", "passwordStrengthWeak": "Votre mot de passe est faible.", "passwordStrengthAlright": "Votre mot de passe est bon.", "passwordStrengthStrong": "Votre mot de passe est fort.", diff --git a/lib/ui/views/authenticate/password_screen.dart b/lib/ui/views/authenticate/password_screen.dart index 4e2e7b9af..4964d7948 100644 --- a/lib/ui/views/authenticate/password_screen.dart +++ b/lib/ui/views/authenticate/password_screen.dart @@ -16,6 +16,8 @@ import 'package:aewallet/ui/widgets/components/sheet_skeleton.dart'; import 'package:aewallet/ui/widgets/components/sheet_skeleton_interface.dart'; import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_gen/gen_l10n/localizations.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; @@ -171,59 +173,108 @@ class _PasswordScreenState extends ConsumerState @override Widget getSheetContent(BuildContext context, WidgetRef ref) { - final localizations = AppLocalizations.of(context)!; - final passwordAuthentication = ref.watch( AuthenticationProviders.passwordAuthentication, ); return Column( - children: [ - AppTextField( - topMargin: 30, - padding: const EdgeInsetsDirectional.only( - start: 16, - end: 16, - ), - focusNode: enterPasswordFocusNode, - controller: enterPasswordController, - textInputAction: TextInputAction.done, - autocorrect: false, - autofocus: true, - onChanged: (String newText) { - setState(() { - if (passwordError != null) { - passwordError = null; - } - }); - }, - onSubmitted: (value) async { - _unfocus(); - await _verifyPassword(); - }, - labelText: localizations.enterPasswordHint, - keyboardType: TextInputType.text, - obscureText: !enterPasswordVisible!, - style: ArchethicThemeStyles.textStyleSize16W700Primary, - suffixButton: TextFieldButton( - icon: enterPasswordVisible! - ? Symbols.visibility - : Symbols.visibility_off, - onPressed: () { - setState(() { - enterPasswordVisible = !enterPasswordVisible!; - }); - }, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 5), + child: Text( + AppLocalizations.of(context)!.enterPasswordHint, ), ), + Row( + children: [ + SizedBox( + width: MediaQuery.of(context).size.width - 80, + child: Row( + children: [ + Expanded( + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + ), + child: Row( + children: [ + Expanded( + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + 10, + ), + border: Border.all( + color: Theme.of(context) + .colorScheme + .primaryContainer, + width: 0.5, + ), + gradient: + ArchethicTheme.gradientInputFormBackground, + ), + child: TextField( + style: const TextStyle( + fontSize: 14, + ), + autocorrect: false, + controller: enterPasswordController, + obscureText: !enterPasswordVisible!, + onChanged: (String newText) { + setState(() { + if (passwordError != null) { + passwordError = null; + } + }); + }, + onSubmitted: (value) async { + _unfocus(); + await _verifyPassword(); + }, + focusNode: enterPasswordFocusNode, + textInputAction: TextInputAction.next, + keyboardType: TextInputType.text, + inputFormatters: [ + LengthLimitingTextInputFormatter( + 20, + ), + ], + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.only(left: 10), + ), + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + TextFieldButton( + icon: enterPasswordVisible! + ? Symbols.visibility + : Symbols.visibility_off, + onPressed: () { + setState(() { + enterPasswordVisible = !enterPasswordVisible!; + }); + }, + ), + ], + ), if (passwordAuthentication.failedAttemptsCount > 0) Container( + alignment: Alignment.center, margin: const EdgeInsets.symmetric( horizontal: 40, vertical: 10, ), child: AutoSizeText( - '${localizations.attempt}${passwordAuthentication.failedAttemptsCount}/${passwordAuthentication.maxAttemptsCount}', + '${AppLocalizations.of(context)!.attempt}${passwordAuthentication.failedAttemptsCount}/${passwordAuthentication.maxAttemptsCount}', style: ArchethicThemeStyles.textStyleSize14W200Primary, textAlign: TextAlign.center, maxLines: 1, @@ -239,6 +290,9 @@ class _PasswordScreenState extends ConsumerState ), ), ], - ); + ) + .animate() + .fade(duration: const Duration(milliseconds: 200)) + .scale(duration: const Duration(milliseconds: 200)); } } diff --git a/lib/ui/views/settings/set_password.dart b/lib/ui/views/settings/set_password.dart index 733787ff4..570961ba3 100755 --- a/lib/ui/views/settings/set_password.dart +++ b/lib/ui/views/settings/set_password.dart @@ -12,6 +12,8 @@ import 'package:aewallet/ui/widgets/components/app_text_field.dart'; import 'package:aewallet/ui/widgets/components/sheet_skeleton.dart'; import 'package:aewallet/ui/widgets/components/sheet_skeleton_interface.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_gen/gen_l10n/localizations.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; @@ -36,30 +38,34 @@ class SetPassword extends ConsumerStatefulWidget { class _SetPasswordState extends ConsumerState implements SheetSkeletonInterface { - FocusNode? setPasswordFocusNode; - TextEditingController? setPasswordController; - FocusNode? confirmPasswordFocusNode; - TextEditingController? confirmPasswordController; - bool? animationOpen; - double passwordStrength = 0; - + late TextEditingController pwdController; + late FocusNode pwdFocusNode; + late TextEditingController pwdConfirmController; + late FocusNode pwdConfirmFocusNode; String? passwordError; - bool? passwordsMatch; bool? setPasswordVisible; - bool? confirmPasswordVisible; + bool? passwordsMatch; + double passwordStrength = 0; bool isProcessing = false; @override void initState() { super.initState(); setPasswordVisible = false; - confirmPasswordVisible = false; + pwdFocusNode = FocusNode(); + pwdController = TextEditingController(); + pwdConfirmFocusNode = FocusNode(); + pwdConfirmController = TextEditingController(); passwordsMatch = false; - setPasswordFocusNode = FocusNode(); - confirmPasswordFocusNode = FocusNode(); - setPasswordController = TextEditingController(); - confirmPasswordController = TextEditingController(); - animationOpen = false; + } + + @override + void dispose() { + pwdFocusNode.dispose(); + pwdController.dispose(); + pwdConfirmController.dispose(); + pwdConfirmFocusNode.dispose(); + super.dispose(); } @override @@ -105,177 +111,290 @@ class _SetPasswordState extends ConsumerState @override Widget getSheetContent(BuildContext context, WidgetRef ref) { - final localizations = AppLocalizations.of(context)!; - return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - AppTextField( - topMargin: 30, - cursorColor: ArchethicTheme.text, - focusNode: setPasswordFocusNode, - controller: setPasswordController, - textInputAction: TextInputAction.next, - autocorrect: false, - onChanged: (String newText) async { - passwordStrength = estimatePasswordStrength( - setPasswordController!.text, - ); - if (passwordError != null) { - setState(() { - passwordError = null; - }); - } + Column( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 5), + child: Text( + AppLocalizations.of(context)!.createPasswordHint, + ), + ), + Row( + children: [ + SizedBox( + width: MediaQuery.of(context).size.width - 130, + child: Row( + children: [ + Expanded( + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + ), + child: Row( + children: [ + Expanded( + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + 10, + ), + border: Border.all( + color: Theme.of(context) + .colorScheme + .primaryContainer, + width: 0.5, + ), + gradient: ArchethicTheme + .gradientInputFormBackground, + ), + child: TextField( + style: const TextStyle( + fontSize: 14, + ), + autocorrect: false, + controller: pwdController, + obscureText: !setPasswordVisible!, + onChanged: (text) async { + passwordStrength = + estimatePasswordStrength( + pwdController.text, + ); + if (passwordError != null) { + setState(() { + passwordError = null; + }); + } - if (confirmPasswordController!.text == - setPasswordController!.text) { - if (mounted) { - setState(() { - passwordsMatch = true; - }); - } - } else { - if (mounted) { - setState(() { - passwordsMatch = false; - }); - } - } - }, - labelText: localizations.createPasswordHint, - keyboardType: TextInputType.text, - obscureText: !setPasswordVisible!, - style: ArchethicThemeStyles.textStyleSize16W700Primary, - onSubmitted: (text) { - confirmPasswordFocusNode!.requestFocus(); - }, - prefixButton: TextFieldButton( - icon: Symbols.shuffle, - onPressed: () { - setPasswordController!.text = ''; - final passwordLength = Random().nextInt(8) + 10; + if (pwdConfirmController.text == + pwdController.text) { + if (mounted) { + setState(() { + passwordsMatch = true; + }); + } + } else { + if (mounted) { + setState(() { + passwordsMatch = false; + }); + } + } + }, + focusNode: pwdFocusNode, + textInputAction: TextInputAction.next, + keyboardType: TextInputType.text, + inputFormatters: [ + LengthLimitingTextInputFormatter( + 20, + ), + ], + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.only(left: 10), + ), + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + TextFieldButton( + icon: Symbols.shuffle, + onPressed: () { + pwdController.text = ''; + final passwordLength = Random().nextInt(8) + 10; - const allowedChars = - r'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ023456789@#=+!£$%&?[](){}'; - var i = 0; - while (i < passwordLength) { - final random = Random.secure().nextInt(allowedChars.length); - setPasswordController!.text += allowedChars[random]; - i++; - } + const allowedChars = + r'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ023456789@#=+!£$%&?[](){}'; + var i = 0; + while (i < passwordLength) { + final random = + Random.secure().nextInt(allowedChars.length); + pwdController.text += allowedChars[random]; + i++; + } - setState(() { - setPasswordVisible = true; - passwordStrength = estimatePasswordStrength( - setPasswordController!.text, - ); - }); - }, - ), - suffixButton: TextFieldButton( - icon: setPasswordVisible! - ? Symbols.visibility - : Symbols.visibility_off, - onPressed: () { - setState(() { - setPasswordVisible = !setPasswordVisible!; - }); - }, - ), - ), - Align( - alignment: Alignment.centerRight, - child: Container( - padding: EdgeInsets.only( - top: 10, - right: MediaQuery.of(context).size.width * 0.105, + setState(() { + setPasswordVisible = true; + passwordStrength = estimatePasswordStrength( + pwdController.text, + ); + }); + }, + ), + TextFieldButton( + icon: setPasswordVisible! + ? Symbols.visibility + : Symbols.visibility_off, + onPressed: () { + setState(() { + setPasswordVisible = !setPasswordVisible!; + }); + }, + ), + ], + ), + ], + ) + .animate() + .fade(duration: const Duration(milliseconds: 200)) + .scale(duration: const Duration(milliseconds: 200)), + Align( + alignment: Alignment.centerRight, + child: Container( + padding: const EdgeInsets.only( + top: 10, + right: 5, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SizedBox( + width: 150, + child: LinearProgressIndicator( + value: passwordStrength, + backgroundColor: Colors.grey[300], + color: passwordStrength <= 0.25 + ? Colors.red + : passwordStrength <= 0.6 + ? Colors.orange + : passwordStrength <= 0.8 + ? Colors.yellow + : Colors.green, + minHeight: 5, + ), + ), + if (passwordStrength <= 0.25) + Text( + AppLocalizations.of(context)!.passwordStrengthWeak, + textAlign: TextAlign.end, + style: ArchethicThemeStyles.textStyleSize12W100Primary, + ) + else + passwordStrength <= 0.8 + ? Text( + AppLocalizations.of(context)! + .passwordStrengthAlright, + textAlign: TextAlign.end, + style: ArchethicThemeStyles + .textStyleSize12W100Primary, + ) + : Text( + AppLocalizations.of(context)! + .passwordStrengthStrong, + textAlign: TextAlign.end, + style: ArchethicThemeStyles + .textStyleSize12W100Primary, + ), + ], + ), + ), + ), + const SizedBox( + height: 10, ), - child: Column( - mainAxisAlignment: MainAxisAlignment.end, + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox( - width: 150, - child: LinearProgressIndicator( - value: passwordStrength, - backgroundColor: Colors.grey[300], - color: passwordStrength <= 0.25 - ? Colors.red - : passwordStrength <= 0.6 - ? Colors.orange - : passwordStrength <= 0.8 - ? Colors.yellow - : Colors.green, - minHeight: 5, + Padding( + padding: const EdgeInsets.only(bottom: 5), + child: Text( + AppLocalizations.of(context)!.confirmPasswordHint, ), ), - if (passwordStrength <= 0.25) - Text( - localizations.passwordStrengthWeak, - textAlign: TextAlign.end, - style: ArchethicThemeStyles.textStyleSize12W100Primary, - ) - else - passwordStrength <= 0.8 - ? Text( - localizations.passwordStrengthAlright, - textAlign: TextAlign.end, - style: - ArchethicThemeStyles.textStyleSize12W100Primary, - ) - : Text( - localizations.passwordStrengthStrong, - textAlign: TextAlign.end, - style: - ArchethicThemeStyles.textStyleSize12W100Primary, + SizedBox( + width: MediaQuery.of(context).size.width, + child: Row( + children: [ + Expanded( + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + ), + child: Row( + children: [ + Expanded( + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + 10, + ), + border: Border.all( + color: Theme.of(context) + .colorScheme + .primaryContainer, + width: 0.5, + ), + gradient: ArchethicTheme + .gradientInputFormBackground, + ), + child: TextField( + style: const TextStyle( + fontSize: 14, + ), + autocorrect: false, + controller: pwdConfirmController, + obscureText: !setPasswordVisible!, + onChanged: (text) async { + if (passwordError != null) { + setState(() { + passwordError = null; + }); + } + if (pwdConfirmController.text == + pwdController.text) { + if (mounted) { + setState(() { + passwordsMatch = true; + }); + } + } else { + if (mounted) { + setState(() { + passwordsMatch = false; + }); + } + } + }, + focusNode: pwdConfirmFocusNode, + textInputAction: TextInputAction.next, + keyboardType: TextInputType.text, + inputFormatters: [ + LengthLimitingTextInputFormatter( + 20, + ), + ], + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.only(left: 10), + ), + ), + ), + ), + ], + ), ), + ), + ], + ), + ), ], - ), - ), - ), - AppTextField( - topMargin: 20, - focusNode: confirmPasswordFocusNode, - controller: confirmPasswordController, - textInputAction: TextInputAction.done, - autocorrect: false, - onSubmitted: (_) async { - await _validateRequest(); - }, - onChanged: (String newText) { - if (passwordError != null) { - setState(() { - passwordError = null; - }); - } - if (confirmPasswordController!.text == - setPasswordController!.text) { - if (mounted) { - setState(() { - passwordsMatch = true; - }); - } - } else { - if (mounted) { - setState(() { - passwordsMatch = false; - }); - } - } - }, - labelText: localizations.confirmPasswordHint, - keyboardType: TextInputType.text, - obscureText: !confirmPasswordVisible!, - style: ArchethicThemeStyles.textStyleSize16W700Primary, - suffixButton: TextFieldButton( - icon: confirmPasswordVisible! - ? Symbols.visibility - : Symbols.visibility_off, - onPressed: () { - setState(() { - confirmPasswordVisible = !confirmPasswordVisible!; - }); - }, - ), + ) + .animate() + .fade(duration: const Duration(milliseconds: 200)) + .scale(duration: const Duration(milliseconds: 200)), + ], ), const SizedBox(height: 20), // Error Text @@ -315,14 +434,19 @@ class _SetPasswordState extends ConsumerState Future _validateRequest() async { if (isProcessing) return; - if (setPasswordController!.text.isEmpty || - confirmPasswordController!.text.isEmpty) { + if (pwdController.text.isEmpty) { setState(() { passwordError = AppLocalizations.of(context)!.passwordBlank; }); return; } - if (setPasswordController!.text != confirmPasswordController!.text) { + if (pwdConfirmController.text.isEmpty) { + setState(() { + passwordError = AppLocalizations.of(context)!.passwordConfirmationBlank; + }); + return; + } + if (pwdController.text != pwdConfirmController.text) { setState(() { passwordError = AppLocalizations.of(context)!.passwordsDontMatch; }); @@ -342,7 +466,7 @@ class _SetPasswordState extends ConsumerState ); await ref .read(AuthenticationProviders.passwordAuthentication.notifier) - .setPassword(setPasswordController!.text); + .setPassword(pwdController.text); context.pop(true); } }