clear instuctions for password in signup #413
@@ -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),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
135
frontend/pweb/lib/pages/signup/form/password_ui_controller.dart
Normal file
135
frontend/pweb/lib/pages/signup/form/password_ui_controller.dart
Normal 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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user