clear instuctions for password in signup #413

Merged
tech merged 1 commits from SEND042 into main 2026-02-04 17:31:25 +00:00
2 changed files with 174 additions and 50 deletions
Showing only changes of commit fe9133c206 - Show all commits

View File

@@ -3,9 +3,8 @@ import 'package:flutter/material.dart';
import 'package:pweb/pages/signup/form/controllers.dart'; import 'package:pweb/pages/signup/form/controllers.dart';
import 'package:pweb/pages/signup/form/description.dart'; import 'package:pweb/pages/signup/form/description.dart';
import 'package:pweb/pages/signup/form/email.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/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/password/verify.dart';
import 'package:pweb/widgets/text_field.dart'; import 'package:pweb/widgets/text_field.dart';
import 'package:pweb/widgets/vspacer.dart'; import 'package:pweb/widgets/vspacer.dart';
@@ -16,55 +15,45 @@ import 'package:pweb/generated/i18n/app_localizations.dart';
class SignUpFormFields extends StatelessWidget { class SignUpFormFields extends StatelessWidget {
final SignUpFormControllers controllers; final SignUpFormControllers controllers;
const SignUpFormFields({ const SignUpFormFields({required this.controllers, super.key});
required this.controllers,
super.key,
});
@override @override
Widget build(BuildContext context) => Column( Widget build(BuildContext context) => Column(
children: [ children: [
const SignUpHeader(), const SignUpHeader(),
const VSpacer(), const VSpacer(),
NotEmptyTextFormField( NotEmptyTextFormField(
controller: controllers.companyName, controller: controllers.companyName,
labelText: AppLocalizations.of(context)!.companyName, labelText: AppLocalizations.of(context)!.companyName,
readOnly: false, readOnly: false,
error: AppLocalizations.of(context)!.companynameRequired, error: AppLocalizations.of(context)!.companynameRequired,
), ),
const VSpacer(), const VSpacer(),
DescriptionField( DescriptionField(controller: controllers.description),
controller: controllers.description, const VSpacer(),
), NotEmptyTextFormField(
const VSpacer(), controller: controllers.firstName,
NotEmptyTextFormField( labelText: AppLocalizations.of(context)!.firstName,
controller: controllers.firstName, readOnly: false,
labelText: AppLocalizations.of(context)!.firstName, error: AppLocalizations.of(context)!.enterFirstName,
readOnly: false, ),
error: AppLocalizations.of(context)!.enterFirstName, const VSpacer(),
), NotEmptyTextFormField(
const VSpacer(), controller: controllers.lastName,
NotEmptyTextFormField( labelText: AppLocalizations.of(context)!.lastName,
controller: controllers.lastName, readOnly: false,
labelText: AppLocalizations.of(context)!.lastName, error: AppLocalizations.of(context)!.enterLastName,
readOnly: false, ),
error: AppLocalizations.of(context)!.enterLastName, const VSpacer(),
), EmailField(controller: controllers.email),
const VSpacer(), const VSpacer(),
EmailField(controller: controllers.email), SignUpPasswordUiController(controller: controllers.password),
const VSpacer(), const VSpacer(multiplier: 2.0),
defaulRulesPasswordField( VerifyPasswordField(
context, controller: controllers.passwordConfirm,
controller: controllers.password, externalPasswordController: controllers.password,
validationRuleBuilder: (rules, value) => ),
shortValidation(context, rules, value), const VSpacer(multiplier: 2.0),
), ],
const VSpacer(multiplier: 2.0), );
VerifyPasswordField(
controller: controllers.passwordConfirm,
externalPasswordController: controllers.password,
),
const VSpacer(multiplier: 2.0),
],
);
} }

View File

@@ -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<SignUpPasswordUiController> createState() =>
_SignUpPasswordUiControllerState();
}
class _SignUpPasswordUiControllerState
extends State<SignUpPasswordUiController> {
@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<ValidationRule> _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);
}