From fe9133c2064506f3b61de07842c3a59555d2cda9 Mon Sep 17 00:00:00 2001 From: Arseni Date: Wed, 4 Feb 2026 16:07:40 +0300 Subject: [PATCH] clear instuctions for password in signup --- .../pweb/lib/pages/signup/form/feilds.dart | 89 +++++------- .../signup/form/password_ui_controller.dart | 135 ++++++++++++++++++ 2 files changed, 174 insertions(+), 50 deletions(-) create mode 100644 frontend/pweb/lib/pages/signup/form/password_ui_controller.dart diff --git a/frontend/pweb/lib/pages/signup/form/feilds.dart b/frontend/pweb/lib/pages/signup/form/feilds.dart index 750ad917..afd3fc2d 100644 --- a/frontend/pweb/lib/pages/signup/form/feilds.dart +++ b/frontend/pweb/lib/pages/signup/form/feilds.dart @@ -3,9 +3,8 @@ import 'package:flutter/material.dart'; import 'package:pweb/pages/signup/form/controllers.dart'; import 'package:pweb/pages/signup/form/description.dart'; import 'package:pweb/pages/signup/form/email.dart'; +import 'package:pweb/pages/signup/form/password_ui_controller.dart'; import 'package:pweb/pages/signup/header.dart'; -import 'package:pweb/widgets/password/hint/short.dart'; -import 'package:pweb/widgets/password/password.dart'; import 'package:pweb/widgets/password/verify.dart'; import 'package:pweb/widgets/text_field.dart'; import 'package:pweb/widgets/vspacer.dart'; @@ -16,55 +15,45 @@ import 'package:pweb/generated/i18n/app_localizations.dart'; class SignUpFormFields extends StatelessWidget { final SignUpFormControllers controllers; - const SignUpFormFields({ - required this.controllers, - super.key, - }); + const SignUpFormFields({required this.controllers, super.key}); @override Widget build(BuildContext context) => Column( - children: [ - const SignUpHeader(), - const VSpacer(), - NotEmptyTextFormField( - controller: controllers.companyName, - labelText: AppLocalizations.of(context)!.companyName, - readOnly: false, - error: AppLocalizations.of(context)!.companynameRequired, - ), - const VSpacer(), - DescriptionField( - controller: controllers.description, - ), - const VSpacer(), - NotEmptyTextFormField( - controller: controllers.firstName, - labelText: AppLocalizations.of(context)!.firstName, - readOnly: false, - error: AppLocalizations.of(context)!.enterFirstName, - ), - const VSpacer(), - NotEmptyTextFormField( - controller: controllers.lastName, - labelText: AppLocalizations.of(context)!.lastName, - readOnly: false, - error: AppLocalizations.of(context)!.enterLastName, - ), - const VSpacer(), - EmailField(controller: controllers.email), - const VSpacer(), - defaulRulesPasswordField( - context, - controller: controllers.password, - validationRuleBuilder: (rules, value) => - shortValidation(context, rules, value), - ), - const VSpacer(multiplier: 2.0), - VerifyPasswordField( - controller: controllers.passwordConfirm, - externalPasswordController: controllers.password, - ), - const VSpacer(multiplier: 2.0), - ], - ); + children: [ + const SignUpHeader(), + const VSpacer(), + NotEmptyTextFormField( + controller: controllers.companyName, + labelText: AppLocalizations.of(context)!.companyName, + readOnly: false, + error: AppLocalizations.of(context)!.companynameRequired, + ), + const VSpacer(), + DescriptionField(controller: controllers.description), + const VSpacer(), + NotEmptyTextFormField( + controller: controllers.firstName, + labelText: AppLocalizations.of(context)!.firstName, + readOnly: false, + error: AppLocalizations.of(context)!.enterFirstName, + ), + const VSpacer(), + NotEmptyTextFormField( + controller: controllers.lastName, + labelText: AppLocalizations.of(context)!.lastName, + readOnly: false, + error: AppLocalizations.of(context)!.enterLastName, + ), + const VSpacer(), + EmailField(controller: controllers.email), + const VSpacer(), + SignUpPasswordUiController(controller: controllers.password), + const VSpacer(multiplier: 2.0), + VerifyPasswordField( + controller: controllers.passwordConfirm, + externalPasswordController: controllers.password, + ), + const VSpacer(multiplier: 2.0), + ], + ); } diff --git a/frontend/pweb/lib/pages/signup/form/password_ui_controller.dart b/frontend/pweb/lib/pages/signup/form/password_ui_controller.dart new file mode 100644 index 00000000..ad2bbcc4 --- /dev/null +++ b/frontend/pweb/lib/pages/signup/form/password_ui_controller.dart @@ -0,0 +1,135 @@ +import 'package:flutter/material.dart'; + +import 'package:fancy_password_field/fancy_password_field.dart'; + +import 'package:pweb/config/constants.dart'; +import 'package:pweb/widgets/password/password.dart'; + +import 'package:pweb/generated/i18n/app_localizations.dart'; + + +class SignUpPasswordUiController extends StatefulWidget { + final TextEditingController controller; + + const SignUpPasswordUiController({required this.controller, super.key}); + + @override + State createState() => + _SignUpPasswordUiControllerState(); +} + +class _SignUpPasswordUiControllerState + extends State { + @override + void initState() { + super.initState(); + widget.controller.addListener(_onPasswordChanged); + } + + @override + void dispose() { + widget.controller.removeListener(_onPasswordChanged); + super.dispose(); + } + + void _onPasswordChanged() => setState(() {}); + + @override + Widget build(BuildContext context) { + final loc = AppLocalizations.of(context)!; + final specialRule = _SpecialCharacterValidationRule( + customText: loc.passwordValidationRuleSpecialCharacter, + ); + final value = widget.controller.text; + final missing = _allRules(context, specialRule) + .where((rule) => !rule.validate(value)) + .map((rule) => rule.name) + .toList(growable: false); + final hasMissingRules = value.isNotEmpty && missing.isNotEmpty; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Theme( + data: hasMissingRules ? _invalidTheme(context) : Theme.of(context), + child: defaulRulesPasswordField( + context, + controller: widget.controller, + additionalRules: {specialRule}, + validationRuleBuilder: (_, _) => const SizedBox.shrink(), + ), + ), + if (hasMissingRules) ...[ + const SizedBox(height: 8), + ...missing.map( + (ruleText) => Padding( + padding: const EdgeInsets.only(bottom: 2), + child: Text( + '• $ruleText', + style: TextStyle(color: Theme.of(context).colorScheme.error), + ), + ), + ), + ], + ], + ); + } + + List _allRules( + BuildContext context, + ValidationRule specialRule, + ) { + final loc = AppLocalizations.of(context)!; + return [ + DigitValidationRule(customText: loc.passwordValidationRuleDigit), + UppercaseValidationRule(customText: loc.passwordValidationRuleUpperCase), + LowercaseValidationRule(customText: loc.passwordValidationRuleLowerCase), + MinCharactersValidationRule( + Constants.minPasswordCharacters, + customText: loc.passwordValidationRuleMinCharacters( + Constants.minPasswordCharacters, + ), + ), + specialRule, + ]; + } + + ThemeData _invalidTheme(BuildContext context) { + final theme = Theme.of(context); + final errorColor = theme.colorScheme.error; + final border = OutlineInputBorder( + borderRadius: BorderRadius.circular(4), + borderSide: BorderSide(color: errorColor, width: 1.2), + ); + + return theme.copyWith( + inputDecorationTheme: theme.inputDecorationTheme.copyWith( + enabledBorder: border, + focusedBorder: border, + errorBorder: border, + focusedErrorBorder: border, + ), + ); + } +} + +class _SpecialCharacterValidationRule extends ValidationRule { + final String customText; + + _SpecialCharacterValidationRule({required this.customText}); + + @override + String get name => customText; + + @override + bool get showName => true; + + @override + bool validate(String value) => value.runes.any(_isAsciiSpecialCharacter); + + bool _isAsciiSpecialCharacter(int code) => + (code >= 33 && code <= 47) || + (code >= 58 && code <= 64) || + (code >= 91 && code <= 96) || + (code >= 123 && code <= 126); +} -- 2.49.1