Skip to content

Commit 0287c50

Browse files
committed
Implement Form Validation for Login and Register Screens
- Added `form_builder_validators` dependency for form validation. - Implemented validation for email, username, password, and confirm password fields. - Added a custom regex for password validation. - Added error messages for invalid passwords and mismatched passwords. - Updated `CustomTextFormField` and `PasswordFieldWidget` to support validators. - Updated the `LoginViewBody` and `RegisterViewBody` to use forms and validate inputs. - Added loggers to indicate the validity of forms. - Refactored `StringsManager` to include new validation strings. - Refactored `UiConstants` to include the password regex. - Updated the flutter and intl SDKs version.
1 parent b1894b0 commit 0287c50

File tree

8 files changed

+205
-59
lines changed

8 files changed

+205
-59
lines changed

lib/core/res/strings_manager.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,7 @@ abstract class AppStrings {
2424
static const String username = "Username";
2525
static const String email = "Email";
2626
static const String confirmPassword = "Confirm Password";
27+
static const String invalidMatchePassword = "Passwords do not match";
28+
static const String invalidPassword =
29+
"Your password must be a minimum of 8 characters, at least one uppercase letter, one lowercase letter, one number, and one special character.";
2730
}

lib/core/res/ui_constants.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
abstract class UiConstants {
22
static const int pageAnimationDuration = 300;
3+
static RegExp passwordRegex =
4+
RegExp(r'^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$');
35
}

lib/core/widgets/custom_form_field.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class CustomTextFormField extends StatelessWidget {
3030
decoration: InputDecoration(
3131
prefixIcon: _buildPrefixIcon,
3232
suffixIcon: _buildSuffixIcon,
33-
33+
errorMaxLines: 3,
3434
hintText: hintText,
3535
border: OutlineInputBorder(),
3636
),
Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_svg/flutter_svg.dart';
3+
import 'package:form_builder_validators/form_builder_validators.dart';
34
import 'package:graduation_project/core/res/assets_manager.dart';
45
import 'package:graduation_project/core/res/strings_manager.dart';
56
import 'package:graduation_project/core/res/values_manager.dart';
7+
import 'package:graduation_project/core/utils/logger.dart';
68
import 'package:graduation_project/core/widgets/custom_elevated_button.dart';
79
import 'package:graduation_project/core/widgets/custom_form_field.dart';
810
import 'package:graduation_project/features/auth/presentation/widgets/auth_logo_widget.dart';
@@ -11,33 +13,81 @@ import 'package:graduation_project/features/auth/presentation/widgets/create_acc
1113
import 'package:graduation_project/features/auth/presentation/widgets/forget_password_button.dart';
1214
import 'package:graduation_project/features/auth/presentation/widgets/password_field_widget.dart';
1315

14-
class LoginViewBody extends StatelessWidget {
16+
class LoginViewBody extends StatefulWidget {
1517
const LoginViewBody({super.key});
1618

19+
@override
20+
State<LoginViewBody> createState() => _LoginViewBodyState();
21+
}
22+
23+
class _LoginViewBodyState extends State<LoginViewBody> {
24+
late final GlobalKey<FormState> _formKey;
25+
late final TextEditingController _identifierController;
26+
late final TextEditingController _passwordController;
27+
28+
@override
29+
void initState() {
30+
super.initState();
31+
_formKey = GlobalKey<FormState>();
32+
_identifierController = TextEditingController();
33+
_passwordController = TextEditingController();
34+
}
35+
1736
@override
1837
Widget build(BuildContext context) {
19-
return Column(
20-
crossAxisAlignment: CrossAxisAlignment.start,
21-
children: [
22-
const AuthHeaderWidget(),
23-
const AuthTextWidget(
24-
title: AppStrings.loginTitle,
25-
subtitle: AppStrings.loginSubtitle,
26-
),
27-
const SizedBox(height: AppSize.s30),
28-
CustomTextFormField(
29-
prefixIcon: SvgPicture.asset(AssetsManager.iconsMail),
30-
hintText: AppStrings.emailOrUsername,
31-
),
32-
const SizedBox(height: AppSize.s20),
33-
PasswordFieldWidget(),
34-
const SizedBox(height: AppSize.s40),
35-
CustomElevatedButton(text: AppStrings.login, onPressed: () {}),
36-
const SizedBox(height: AppSize.s8),
37-
ForgetPasswordButton(),
38-
const SizedBox(height: AppSize.s18),
39-
const CreateAccountOptionsWidget(),
40-
],
38+
return Form(
39+
key: _formKey,
40+
child: Column(
41+
crossAxisAlignment: CrossAxisAlignment.start,
42+
children: [
43+
const AuthHeaderWidget(),
44+
const AuthTextWidget(
45+
title: AppStrings.loginTitle,
46+
subtitle: AppStrings.loginSubtitle,
47+
),
48+
const SizedBox(height: AppSize.s30),
49+
CustomTextFormField(
50+
controller: _identifierController,
51+
prefixIcon: SvgPicture.asset(AssetsManager.iconsMail),
52+
validator: FormBuilderValidators.compose([
53+
FormBuilderValidators.required(),
54+
FormBuilderValidators.or([
55+
FormBuilderValidators.username(),
56+
FormBuilderValidators.email(),
57+
]),
58+
]),
59+
hintText: AppStrings.emailOrUsername,
60+
),
61+
const SizedBox(height: AppSize.s20),
62+
PasswordFieldWidget(
63+
controller: _passwordController,
64+
validator: FormBuilderValidators.compose([
65+
FormBuilderValidators.required(),
66+
FormBuilderValidators.password(),]),
67+
),
68+
const SizedBox(height: AppSize.s40),
69+
CustomElevatedButton(text: AppStrings.login, onPressed: _submit),
70+
const SizedBox(height: AppSize.s8),
71+
ForgetPasswordButton(),
72+
const SizedBox(height: AppSize.s18),
73+
const CreateAccountOptionsWidget(),
74+
],
75+
),
4176
);
4277
}
78+
79+
void _submit() {
80+
if (_formKey.currentState!.validate()) {
81+
Logger.success("Login form is valid");
82+
} else {
83+
Logger.success("Login form is invalid");
84+
}
85+
}
86+
87+
@override
88+
void dispose() {
89+
_identifierController.dispose();
90+
_passwordController.dispose();
91+
super.dispose();
92+
}
4393
}

lib/features/auth/presentation/widgets/password_field_widget.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ class PasswordFieldWidget extends StatefulWidget {
1010
super.key,
1111
this.controller,
1212
this.isConfirmPassword = false,
13+
this.validator,
1314
});
1415

1516
final TextEditingController? controller;
1617
final bool isConfirmPassword;
18+
final String? Function(String?)? validator;
1719

1820
@override
1921
State<PasswordFieldWidget> createState() => _PasswordFieldWidgetState();
@@ -26,6 +28,7 @@ class _PasswordFieldWidgetState extends State<PasswordFieldWidget> {
2628
Widget build(BuildContext context) {
2729
return CustomTextFormField(
2830
isObscure: isObscure,
31+
validator: widget.validator,
2932
controller: widget.controller,
3033
hintText: _hintText,
3134
prefixIcon: SvgPicture.asset(AssetsManager.iconsPassword),
Lines changed: 100 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,120 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_svg/flutter_svg.dart';
3+
import 'package:form_builder_validators/form_builder_validators.dart';
34
import 'package:go_router/go_router.dart';
45
import 'package:graduation_project/core/res/assets_manager.dart';
56
import 'package:graduation_project/core/res/strings_manager.dart';
7+
import 'package:graduation_project/core/res/ui_constants.dart';
68
import 'package:graduation_project/core/res/values_manager.dart';
9+
import 'package:graduation_project/core/utils/logger.dart';
710
import 'package:graduation_project/core/widgets/custom_elevated_button.dart';
811
import 'package:graduation_project/core/widgets/custom_form_field.dart';
912
import 'package:graduation_project/features/auth/presentation/widgets/auth_action_text_widget.dart';
1013
import 'package:graduation_project/features/auth/presentation/widgets/auth_logo_widget.dart';
1114
import 'package:graduation_project/features/auth/presentation/widgets/auth_text_widget.dart';
1215
import 'package:graduation_project/features/auth/presentation/widgets/password_field_widget.dart';
1316

14-
class RegisterViewBody extends StatelessWidget {
17+
class RegisterViewBody extends StatefulWidget {
1518
const RegisterViewBody({super.key});
1619

20+
@override
21+
State<RegisterViewBody> createState() => _RegisterViewBodyState();
22+
}
23+
24+
class _RegisterViewBodyState extends State<RegisterViewBody> {
25+
late final GlobalKey<FormState> _formKey;
26+
late final TextEditingController _usernameController;
27+
late final TextEditingController _emailController;
28+
late final TextEditingController _passwordController;
29+
30+
@override
31+
void initState() {
32+
super.initState();
33+
_formKey = GlobalKey<FormState>();
34+
_usernameController = TextEditingController();
35+
_emailController = TextEditingController();
36+
_passwordController = TextEditingController();
37+
}
38+
1739
@override
1840
Widget build(BuildContext context) {
19-
return Column(
20-
crossAxisAlignment: CrossAxisAlignment.start,
21-
children: [
22-
const AuthHeaderWidget(),
23-
const AuthTextWidget(
24-
title: AppStrings.registerTitle,
25-
subtitle: AppStrings.registerSubtitle,
26-
),
27-
const SizedBox(height: AppSize.s30),
28-
CustomTextFormField(
29-
prefixIcon: SvgPicture.asset(AssetsManager.iconsUser),
30-
hintText: AppStrings.username,
31-
),
32-
const SizedBox(height: AppSize.s20),
33-
CustomTextFormField(
34-
prefixIcon: SvgPicture.asset(AssetsManager.iconsMail),
35-
hintText: AppStrings.email,
36-
),
37-
const SizedBox(height: AppSize.s20),
38-
const PasswordFieldWidget(),
39-
const SizedBox(height: AppSize.s20),
40-
const PasswordFieldWidget(isConfirmPassword: true),
41-
const SizedBox(height: AppSize.s40),
42-
CustomElevatedButton(text: AppStrings.signUp, onPressed: () {}),
43-
const SizedBox(height: AppSize.s18),
44-
AuthActionTextWidget(
45-
textBeforeAction: AppStrings.alreadyHaveAnAccount,
46-
actionText: AppStrings.login,
47-
onActionPressed: () {
48-
context.pop();
49-
},
50-
),
51-
],
41+
return Form(
42+
key: _formKey,
43+
child: Column(
44+
crossAxisAlignment: CrossAxisAlignment.start,
45+
children: [
46+
const AuthHeaderWidget(),
47+
const AuthTextWidget(
48+
title: AppStrings.registerTitle,
49+
subtitle: AppStrings.registerSubtitle,
50+
),
51+
const SizedBox(height: AppSize.s30),
52+
CustomTextFormField(
53+
controller: _usernameController,
54+
prefixIcon: SvgPicture.asset(AssetsManager.iconsUser),
55+
validator: FormBuilderValidators.compose([
56+
FormBuilderValidators.required(),
57+
FormBuilderValidators.username(),
58+
]),
59+
hintText: AppStrings.username,
60+
),
61+
const SizedBox(height: AppSize.s20),
62+
CustomTextFormField(
63+
controller: _emailController,
64+
prefixIcon: SvgPicture.asset(AssetsManager.iconsMail),
65+
validator: FormBuilderValidators.compose([
66+
FormBuilderValidators.required(),
67+
FormBuilderValidators.email(),
68+
]),
69+
hintText: AppStrings.email,
70+
),
71+
const SizedBox(height: AppSize.s20),
72+
PasswordFieldWidget(
73+
controller: _passwordController,
74+
validator: FormBuilderValidators.match(
75+
UiConstants.passwordRegex,
76+
errorText: AppStrings.invalidPassword,
77+
),
78+
),
79+
const SizedBox(height: AppSize.s20),
80+
PasswordFieldWidget(
81+
isConfirmPassword: true,
82+
validator: FormBuilderValidators.compose([
83+
FormBuilderValidators.required(),
84+
FormBuilderValidators.equal(
85+
_passwordController.text,
86+
errorText: AppStrings.invalidMatchePassword,
87+
),
88+
]),
89+
),
90+
const SizedBox(height: AppSize.s40),
91+
CustomElevatedButton(text: AppStrings.signUp, onPressed: _submit),
92+
const SizedBox(height: AppSize.s18),
93+
AuthActionTextWidget(
94+
textBeforeAction: AppStrings.alreadyHaveAnAccount,
95+
actionText: AppStrings.login,
96+
onActionPressed: () {
97+
context.pop();
98+
},
99+
),
100+
],
101+
),
52102
);
53103
}
104+
105+
void _submit() {
106+
if (_formKey.currentState!.validate()) {
107+
Logger.success("Registration form is valid");
108+
} else {
109+
Logger.error("Registration form is invalid");
110+
}
111+
}
112+
113+
@override
114+
void dispose() {
115+
_usernameController.dispose();
116+
_emailController.dispose();
117+
_passwordController.dispose();
118+
super.dispose();
119+
}
54120
}

pubspec.lock

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,11 @@ packages:
262262
url: "https://pub.dev"
263263
source: hosted
264264
version: "5.0.0"
265+
flutter_localizations:
266+
dependency: transitive
267+
description: flutter
268+
source: sdk
269+
version: "0.0.0"
265270
flutter_svg:
266271
dependency: "direct main"
267272
description:
@@ -280,6 +285,14 @@ packages:
280285
description: flutter
281286
source: sdk
282287
version: "0.0.0"
288+
form_builder_validators:
289+
dependency: "direct main"
290+
description:
291+
name: form_builder_validators
292+
sha256: cd617fa346250293ff3e2709961d0faf7b80e6e4f0ff7b500126b28d7422dd67
293+
url: "https://pub.dev"
294+
source: hosted
295+
version: "11.1.2"
283296
freezed:
284297
dependency: "direct dev"
285298
description:
@@ -376,6 +389,14 @@ packages:
376389
url: "https://pub.dev"
377390
source: hosted
378391
version: "2.7.0"
392+
intl:
393+
dependency: transitive
394+
description:
395+
name: intl
396+
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
397+
url: "https://pub.dev"
398+
source: hosted
399+
version: "0.19.0"
379400
io:
380401
dependency: transitive
381402
description:
@@ -799,4 +820,4 @@ packages:
799820
version: "3.1.3"
800821
sdks:
801822
dart: ">=3.7.0 <4.0.0"
802-
flutter: ">=3.22.0"
823+
flutter: ">=3.27.0"

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ dependencies:
4848
injectable: ^2.5.0
4949
freezed_annotation: ^3.0.0
5050
smooth_page_indicator: ^1.2.1
51+
form_builder_validators: ^11.1.2
5152

5253

5354

0 commit comments

Comments
 (0)