feat: add validation to disable next button

This commit is contained in:
Freek van de Ven 2024-02-09 18:36:22 +01:00
parent 15aa74ebda
commit 277b38f39e
7 changed files with 57 additions and 19 deletions

View file

@ -35,9 +35,8 @@ class AuthScreen extends StatefulWidget {
final String previousBtnTitle;
final AppBar? customAppBar;
final Color? customBackgroundColor;
final Widget Function(
Future<void> Function() onPressed, String label, int step)?
nextButtonBuilder;
final Widget Function(Future<void> Function()? onPressed, String label,
int step, bool enabled)? nextButtonBuilder;
final Widget? Function(VoidCallback onPressed, String label, int step)?
previousButtonBuilder;
final Widget? titleWidget;
@ -52,6 +51,7 @@ class _AuthScreenState extends State<AuthScreen> {
final _pageController = PageController();
final _animationDuration = const Duration(milliseconds: 300);
final _animationCurve = Curves.ease;
bool _formValid = false;
AppBar get _appBar =>
widget.customAppBar ??
@ -60,6 +60,7 @@ class _AuthScreenState extends State<AuthScreen> {
);
void onPrevious() {
_validate(_pageController.page!.toInt() - 1);
_pageController.previousPage(
duration: _animationDuration,
curve: _animationCurve,
@ -95,6 +96,7 @@ class _AuthScreenState extends State<AuthScreen> {
return;
} else {
_validate(_pageController.page!.toInt() + 1);
_pageController.nextPage(
duration: _animationDuration,
curve: _animationCurve,
@ -102,6 +104,29 @@ class _AuthScreenState extends State<AuthScreen> {
}
}
void _validate(int currentPage) {
bool isStepValid = true;
// Loop through each field in the current step
for (var field in widget.steps[currentPage].fields) {
for (var validator in field.validators) {
String? validationResult = validator(field.value);
if (validationResult != null) {
// If any validator returns an error, mark step as invalid and break
isStepValid = false;
break;
}
}
if (!isStepValid) {
break; // No need to check further fields if one is already invalid
}
}
setState(() {
_formValid = isStepValid;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -136,7 +161,9 @@ class _AuthScreenState extends State<AuthScreen> {
if (field.title != null) ...[
field.title!,
],
field.build(),
field.build(context, () {
_validate(i);
}),
],
),
)
@ -197,18 +224,23 @@ class _AuthScreenState extends State<AuthScreen> {
.call(onPrevious, widget.previousBtnTitle, i)!
],
widget.nextButtonBuilder?.call(
() async {
await onNext(widget.steps[i]);
},
!_formValid
? null
: () async {
await onNext(widget.steps[i]);
},
widget.steps.last == widget.steps[i]
? widget.submitBtnTitle
: widget.nextBtnTitle,
i,
_formValid,
) ??
ElevatedButton(
onPressed: () async {
await onNext(widget.steps[i]);
},
onPressed: !_formValid
? null
: () async {
await onNext(widget.steps[i]);
},
child: Row(
children: [
Text(

View file

@ -28,9 +28,8 @@ class RegistrationOptions {
final VoidCallback afterRegistration;
final RegistrationRepository registrationRepository;
final AppBar Function(String title)? customAppbarBuilder;
final Widget Function(
Future<void> Function() onPressed, String label, int step)?
nextButtonBuilder;
final Widget Function(Future<void> Function()? onPressed, String label,
int step, bool enabled)? nextButtonBuilder;
final Widget? Function(VoidCallback onPressed, String label, int step)?
previousButtonBuilder;
final Color? backgroundColor;

View file

@ -24,12 +24,13 @@ class AuthBoolField extends AuthField {
final Function(String value)? onChange;
@override
Widget build() {
Widget build(BuildContext context, Function onValueChanged) {
return FlutterFormInputBool(
widgetType: widgetType,
onChanged: (v) {
value = v;
onChange?.call(value);
onValueChanged();
},
validator: (value) {
for (var validator in validators) {

View file

@ -24,7 +24,7 @@ class AuthDropdownField extends AuthField {
final Icon icon;
@override
Widget build() {
Widget build(BuildContext context, Function onValueChanged) {
return Padding(
padding: padding,
child: DropdownButtonFormField<String>(
@ -41,6 +41,7 @@ class AuthDropdownField extends AuthField {
onChanged: (newValue) {
selectedValue = newValue;
onChanged(newValue);
onValueChanged();
},
validator: (value) {
if (validators.isNotEmpty) {

View file

@ -7,9 +7,10 @@ import 'package:flutter/material.dart';
abstract class AuthField<T> {
AuthField({
required this.name,
required this.value,
this.onValueChanged,
this.title,
this.validators = const [],
required this.value,
});
final String name;
@ -17,5 +18,7 @@ abstract class AuthField<T> {
List<String? Function(T?)> validators;
T value;
Widget build();
final Function(T)? onValueChanged; // Callback for value changes
Widget build(BuildContext context, Function onValueChanged);
}

View file

@ -27,7 +27,7 @@ class AuthPassField extends AuthField {
final EdgeInsets padding;
@override
Widget build() {
Widget build(BuildContext context, Function onValueChanged) {
return Padding(
padding: padding,
child: FlutterFormInputPassword(
@ -37,6 +37,7 @@ class AuthPassField extends AuthField {
onChanged: (v) {
value = v;
onChange?.call(value);
onValueChanged();
},
validator: (value) {
for (var validator in validators) {

View file

@ -28,7 +28,7 @@ class AuthTextField extends AuthField {
final EdgeInsets padding;
@override
Widget build() {
Widget build(BuildContext context, Function onValueChanged) {
return Padding(
padding: padding,
child: TextFormField(
@ -38,6 +38,7 @@ class AuthTextField extends AuthField {
onChanged: (v) {
value = v;
onChange?.call(value);
onValueChanged();
},
validator: (value) {
for (var validator in validators) {