feat(from): add form validator class with strategy design pattern #12
| @ -1 +1,4 @@ | |||||||
| include: package:wyatt_analysis/analysis_options.yaml | include: package:wyatt_analysis/analysis_options.yaml | ||||||
|  | 
 | ||||||
|  | analyzer: | ||||||
|  |   exclude: "!example/**" | ||||||
| @ -61,10 +61,10 @@ class App extends StatelessWidget { | |||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     FormDataCubit _formCubit = CustomFormCubit(inputs: getNormalFormData()); |     FormDataCubit formCubit = CustomFormCubit(inputs: getNormalFormData()); | ||||||
| 
 | 
 | ||||||
|     return BlocProvider( |     return BlocProvider( | ||||||
|       create: (context) => _formCubit, |       create: (context) => formCubit, | ||||||
|       child: const WidgetTree(), |       child: const WidgetTree(), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -187,9 +187,9 @@ class _CheckListInput extends StatelessWidget { | |||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return BlocBuilder<FormDataCubit, FormDataState>( |     return BlocBuilder<FormDataCubit, FormDataState>( | ||||||
|       builder: (context, state) { |       builder: (context, state) { | ||||||
|         final _input = |         final input = | ||||||
|             state.data.validatorOf<ListOption<String>>(formFieldList); |             state.data.validatorOf<ListOption<String>>(formFieldList); | ||||||
|         final _options = _input.value; |         final options = input.value; | ||||||
| 
 | 
 | ||||||
|         return Column( |         return Column( | ||||||
|           mainAxisSize: MainAxisSize.min, |           mainAxisSize: MainAxisSize.min, | ||||||
| @ -197,33 +197,33 @@ class _CheckListInput extends StatelessWidget { | |||||||
|             ListTile( |             ListTile( | ||||||
|               title: const Text('Checkbox1'), |               title: const Text('Checkbox1'), | ||||||
|               trailing: Checkbox( |               trailing: Checkbox( | ||||||
|                   value: _options.contains('checkbox1'), |                   value: options.contains('checkbox1'), | ||||||
|                   onChanged: (_) { |                   onChanged: (_) { | ||||||
|                     context.read<FormDataCubit>().dataChanged( |                     context.read<FormDataCubit>().dataChanged( | ||||||
|                           formFieldList, |                           formFieldList, | ||||||
|                           _input.select('checkbox1'), |                           input.select('checkbox1'), | ||||||
|                         ); |                         ); | ||||||
|                   }), |                   }), | ||||||
|             ), |             ), | ||||||
|             ListTile( |             ListTile( | ||||||
|               title: const Text('Checkbox2'), |               title: const Text('Checkbox2'), | ||||||
|               trailing: Checkbox( |               trailing: Checkbox( | ||||||
|                   value: _options.contains('checkbox2'), |                   value: options.contains('checkbox2'), | ||||||
|                   onChanged: (_) { |                   onChanged: (_) { | ||||||
|                     context.read<FormDataCubit>().dataChanged( |                     context.read<FormDataCubit>().dataChanged( | ||||||
|                           formFieldList, |                           formFieldList, | ||||||
|                           _input.select('checkbox2'), |                           input.select('checkbox2'), | ||||||
|                         ); |                         ); | ||||||
|                   }), |                   }), | ||||||
|             ), |             ), | ||||||
|             ListTile( |             ListTile( | ||||||
|               title: const Text('Checkbox3 (default)'), |               title: const Text('Checkbox3 (default)'), | ||||||
|               trailing: Checkbox( |               trailing: Checkbox( | ||||||
|                   value: _options.contains('checkbox3'), |                   value: options.contains('checkbox3'), | ||||||
|                   onChanged: (_) { |                   onChanged: (_) { | ||||||
|                     context.read<FormDataCubit>().dataChanged( |                     context.read<FormDataCubit>().dataChanged( | ||||||
|                           formFieldList, |                           formFieldList, | ||||||
|                           _input.select('checkbox3'), |                           input.select('checkbox3'), | ||||||
|                         ); |                         ); | ||||||
|                   }), |                   }), | ||||||
|             ), |             ), | ||||||
| @ -239,7 +239,7 @@ class _RadioListInput extends StatelessWidget { | |||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return BlocBuilder<FormDataCubit, FormDataState>( |     return BlocBuilder<FormDataCubit, FormDataState>( | ||||||
|       builder: (context, state) { |       builder: (context, state) { | ||||||
|         final _input = state.data.validatorOf<TextString>(formFieldRadio); |         final input = state.data.validatorOf<TextString>(formFieldRadio); | ||||||
| 
 | 
 | ||||||
|         return Column( |         return Column( | ||||||
|           mainAxisSize: MainAxisSize.min, |           mainAxisSize: MainAxisSize.min, | ||||||
| @ -248,7 +248,7 @@ class _RadioListInput extends StatelessWidget { | |||||||
|               title: const Text('Radio1'), |               title: const Text('Radio1'), | ||||||
|               trailing: Radio<bool>( |               trailing: Radio<bool>( | ||||||
|                   groupValue: true, |                   groupValue: true, | ||||||
|                   value: _input.value == 'radio1', |                   value: input.value == 'radio1', | ||||||
|                   onChanged: (_) { |                   onChanged: (_) { | ||||||
|                     context.read<FormDataCubit>().dataChanged( |                     context.read<FormDataCubit>().dataChanged( | ||||||
|                           formFieldRadio, |                           formFieldRadio, | ||||||
| @ -260,7 +260,7 @@ class _RadioListInput extends StatelessWidget { | |||||||
|               title: const Text('Radio2'), |               title: const Text('Radio2'), | ||||||
|               trailing: Radio<bool>( |               trailing: Radio<bool>( | ||||||
|                   groupValue: true, |                   groupValue: true, | ||||||
|                   value: _input.value == 'radio2', |                   value: input.value == 'radio2', | ||||||
|                   onChanged: (_) { |                   onChanged: (_) { | ||||||
|                     context.read<FormDataCubit>().dataChanged( |                     context.read<FormDataCubit>().dataChanged( | ||||||
|                           formFieldRadio, |                           formFieldRadio, | ||||||
| @ -272,7 +272,7 @@ class _RadioListInput extends StatelessWidget { | |||||||
|               title: const Text('Radio3'), |               title: const Text('Radio3'), | ||||||
|               trailing: Radio<bool>( |               trailing: Radio<bool>( | ||||||
|                   groupValue: true, |                   groupValue: true, | ||||||
|                   value: _input.value == 'radio3', |                   value: input.value == 'radio3', | ||||||
|                   onChanged: (_) { |                   onChanged: (_) { | ||||||
|                     context.read<FormDataCubit>().dataChanged( |                     context.read<FormDataCubit>().dataChanged( | ||||||
|                           formFieldRadio, |                           formFieldRadio, | ||||||
| @ -378,6 +378,20 @@ class _DebugButton extends StatelessWidget { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | class _ResetButton extends StatelessWidget { | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return BlocBuilder<FormDataCubit, FormDataState>( | ||||||
|  |       builder: (context, state) { | ||||||
|  |         return ElevatedButton( | ||||||
|  |           onPressed: () => context.read<FormDataCubit>().resetForm(), | ||||||
|  |           child: const Text('RESET'), | ||||||
|  |         ); | ||||||
|  |       }, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| class SignUpForm extends StatelessWidget { | class SignUpForm extends StatelessWidget { | ||||||
|   const SignUpForm({Key? key}) : super(key: key); |   const SignUpForm({Key? key}) : super(key: key); | ||||||
| 
 | 
 | ||||||
| @ -431,6 +445,8 @@ class SignUpForm extends StatelessWidget { | |||||||
|             _SignUpButton(), |             _SignUpButton(), | ||||||
|             const SizedBox(height: 8), |             const SizedBox(height: 8), | ||||||
|             _DebugButton(), |             _DebugButton(), | ||||||
|  |             const SizedBox(height: 8), | ||||||
|  |             _ResetButton(), | ||||||
|           ], |           ], | ||||||
|         ), |         ), | ||||||
|       ), |       ), | ||||||
|  | |||||||
| @ -18,40 +18,56 @@ import 'dart:async'; | |||||||
| 
 | 
 | ||||||
| import 'package:bloc/bloc.dart'; | import 'package:bloc/bloc.dart'; | ||||||
| import 'package:equatable/equatable.dart'; | import 'package:equatable/equatable.dart'; | ||||||
| import 'package:meta/meta.dart'; |  | ||||||
| import 'package:wyatt_form_bloc/src/enums/enums.dart'; | import 'package:wyatt_form_bloc/src/enums/enums.dart'; | ||||||
| import 'package:wyatt_form_bloc/src/form/form.dart'; | import 'package:wyatt_form_bloc/src/form/form.dart'; | ||||||
|  | import 'package:wyatt_form_bloc/src/validators/form/every_input_validator.dart'; | ||||||
| 
 | 
 | ||||||
| part 'form_data_state.dart'; | part 'form_data_state.dart'; | ||||||
| 
 | 
 | ||||||
| abstract class FormDataCubit extends Cubit<FormDataState> { | abstract class FormDataCubit extends Cubit<FormDataState> { | ||||||
|   FormDataCubit({required FormData inputs}) |   FormValidator validationStrategy; | ||||||
|       : super(FormDataState(data: inputs)); |   FormData formCopy; | ||||||
|  | 
 | ||||||
|  |   FormDataCubit({ | ||||||
|  |     required FormData inputs, | ||||||
|  |     FormValidator validator = const EveryInputValidator(), | ||||||
|  |   })  : formCopy = inputs.clone(), | ||||||
|  |         validationStrategy = validator, | ||||||
|  |         super(FormDataState(data: inputs)); | ||||||
| 
 | 
 | ||||||
|   /// Change value of a field. |   /// Change value of a field. | ||||||
|   ///  |   /// | ||||||
|   /// Inputs: |   /// Inputs: | ||||||
|   /// - `field`: The key of the field to change. |   /// - `field`: The key of the field to change. | ||||||
|   /// - `dirtyValue`: The new value of the field. (Wrapped in a dirty validator) |   /// - `dirtyValue`: The new value of the field. (Wrapped in a dirty validator) | ||||||
|   void dataChanged(String field, FormInputValidator dirtyValue) { |   void dataChanged<V>( | ||||||
|     final _form = state.data.clone(); |     String field, | ||||||
|  |     FormInputValidator<V, ValidationError> dirtyValue, | ||||||
|  |   ) { | ||||||
|  |     final form = state.data.clone(); | ||||||
| 
 | 
 | ||||||
|     if (_form.contains(field)) { |     if (form.contains(field)) { | ||||||
|       _form.updateValidator(field, dirtyValue); |       form.updateValidator(field, dirtyValue); | ||||||
|     } else { |     } else { | ||||||
|       throw Exception('Form field $field not found'); |       throw Exception('Form field $field not found'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     emit( |     emit( | ||||||
|       state.copyWith( |       state.copyWith( | ||||||
|         data: _form, |         data: form, | ||||||
|         status: _form.validate(), |         status: validationStrategy.validate(form), | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /// Just validate the form manually. (Useful if you just update the strategy) | ||||||
|  |   void validate() { | ||||||
|  |     final form = state.data; | ||||||
|  |     emit(state.copyWith(status: validationStrategy.validate(form))); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   /// Update entries list. |   /// Update entries list. | ||||||
|   ///  |   /// | ||||||
|   /// Inputs: |   /// Inputs: | ||||||
|   /// - `data`: The new entries list. |   /// - `data`: The new entries list. | ||||||
|   /// - `operation`: The operation to perform on the entries set. |   /// - `operation`: The operation to perform on the entries set. | ||||||
| @ -59,31 +75,36 @@ abstract class FormDataCubit extends Cubit<FormDataState> { | |||||||
|     FormData data, { |     FormData data, { | ||||||
|     SetOperation operation = SetOperation.replace, |     SetOperation operation = SetOperation.replace, | ||||||
|   }) { |   }) { | ||||||
|     FormData _form = data; |     FormData form = data; | ||||||
| 
 | 
 | ||||||
|     switch (operation) { |     switch (operation) { | ||||||
|       case SetOperation.replace: |       case SetOperation.replace: | ||||||
|         _form = data; |         form = data; | ||||||
|         break; |         break; | ||||||
|       case SetOperation.difference: |       case SetOperation.difference: | ||||||
|         _form = state.data.difference(data); |         form = state.data.difference(data); | ||||||
|         break; |         break; | ||||||
|       case SetOperation.intersection: |       case SetOperation.intersection: | ||||||
|         _form = state.data.intersection(data); |         form = state.data.intersection(data); | ||||||
|         break; |         break; | ||||||
|       case SetOperation.union: |       case SetOperation.union: | ||||||
|         _form = state.data.union(data); |         form = state.data.union(data); | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     emit( |     emit( | ||||||
|       state.copyWith( |       state.copyWith( | ||||||
|         data: _form, |         data: form, | ||||||
|         status: _form.validate(), |         status: validationStrategy.validate(form), | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /// Reset all form inputs | ||||||
|  |   void resetForm() { | ||||||
|  |     emit(FormDataState(data: formCopy)); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   /// Submit the form. |   /// Submit the form. | ||||||
|   Future<void> submitForm(); |   Future<void> submitForm(); | ||||||
| } | } | ||||||
|  | |||||||
| @ -16,7 +16,6 @@ | |||||||
| 
 | 
 | ||||||
| part of 'form_data_cubit.dart'; | part of 'form_data_cubit.dart'; | ||||||
| 
 | 
 | ||||||
| @immutable |  | ||||||
| class FormDataState extends Equatable { | class FormDataState extends Equatable { | ||||||
|   /// Global status of a form. |   /// Global status of a form. | ||||||
|   final FormStatus status; |   final FormStatus status; | ||||||
| @ -37,13 +36,11 @@ class FormDataState extends Equatable { | |||||||
|     FormStatus? status, |     FormStatus? status, | ||||||
|     FormData? data, |     FormData? data, | ||||||
|     String? errorMessage, |     String? errorMessage, | ||||||
|   }) { |   }) => FormDataState( | ||||||
|     return FormDataState( |  | ||||||
|       status: status ?? this.status, |       status: status ?? this.status, | ||||||
|       data: data ?? this.data, |       data: data ?? this.data, | ||||||
|       errorMessage: errorMessage ?? this.errorMessage, |       errorMessage: errorMessage ?? this.errorMessage, | ||||||
|     ); |     ); | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   bool? get stringify => true; |   bool? get stringify => true; | ||||||
|  | |||||||
| @ -14,8 +14,6 @@ | |||||||
| // You should have received a copy of the GNU General Public License | // You should have received a copy of the GNU General Public License | ||||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||||
| 
 | 
 | ||||||
| import 'package:wyatt_form_bloc/src/form/form.dart'; |  | ||||||
| 
 |  | ||||||
| const Set<FormStatus> _validatedFormStatuses = <FormStatus>{ | const Set<FormStatus> _validatedFormStatuses = <FormStatus>{ | ||||||
|   FormStatus.valid, |   FormStatus.valid, | ||||||
|   FormStatus.submissionInProgress, |   FormStatus.submissionInProgress, | ||||||
| @ -51,6 +49,8 @@ enum FormStatus { | |||||||
|   bool get isPure => this == FormStatus.pure; |   bool get isPure => this == FormStatus.pure; | ||||||
| 
 | 
 | ||||||
|   /// Indicates whether the form is completely validated. |   /// Indicates whether the form is completely validated. | ||||||
|  |   /// This means the [FormStatus] is strictly: | ||||||
|  |   /// * `FormStatus.valid` | ||||||
|   bool get isValid => this == FormStatus.valid; |   bool get isValid => this == FormStatus.valid; | ||||||
| 
 | 
 | ||||||
|   /// Indicates whether the form has been validated successfully. |   /// Indicates whether the form has been validated successfully. | ||||||
| @ -75,23 +75,4 @@ enum FormStatus { | |||||||
| 
 | 
 | ||||||
|   /// Indicates whether the form submission has been canceled. |   /// Indicates whether the form submission has been canceled. | ||||||
|   bool get isSubmissionCanceled => this == FormStatus.submissionCanceled; |   bool get isSubmissionCanceled => this == FormStatus.submissionCanceled; | ||||||
| 
 |  | ||||||
|   /// Validate a list of inputs by processing them in `validate` as validators. |  | ||||||
|   static FormStatus validateInputs(List<FormInput> inputs) { |  | ||||||
|     return validate( |  | ||||||
|       inputs |  | ||||||
|           .map<FormInputValidator>((FormInput input) => input.validator) |  | ||||||
|           .toList(), |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   /// Validate a list of validators. |  | ||||||
|   static FormStatus validate(List<FormInputValidator> validators) { |  | ||||||
|     return validators.every((FormInputValidator validator) => validator.pure) |  | ||||||
|         ? FormStatus.pure |  | ||||||
|         : validators |  | ||||||
|                 .any((FormInputValidator validator) => validator.valid == false) |  | ||||||
|             ? FormStatus.invalid |  | ||||||
|             : FormStatus.valid; |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -14,7 +14,14 @@ | |||||||
| // You should have received a copy of the GNU General Public License | // You should have received a copy of the GNU General Public License | ||||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||||
| 
 | 
 | ||||||
| enum ValidationError { | // enum ValidationError { | ||||||
|   /// Generic invalid error. | //   /// Generic invalid error. | ||||||
|   invalid | //   invalid | ||||||
|  | // } | ||||||
|  | 
 | ||||||
|  | abstract class ValidationError {} | ||||||
|  | 
 | ||||||
|  | enum ValidationStandardError implements ValidationError { | ||||||
|  |   invalid, | ||||||
|  |   empty | ||||||
| } | } | ||||||
|  | |||||||
| @ -14,12 +14,15 @@ | |||||||
| // You should have received a copy of the GNU General Public License | // You should have received a copy of the GNU General Public License | ||||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||||
| 
 | 
 | ||||||
|  | import 'dart:convert'; | ||||||
|  | 
 | ||||||
| import 'package:equatable/equatable.dart'; | import 'package:equatable/equatable.dart'; | ||||||
| import 'package:meta/meta.dart'; |  | ||||||
| import 'package:wyatt_form_bloc/src/enums/form_input_status.dart'; | import 'package:wyatt_form_bloc/src/enums/form_input_status.dart'; | ||||||
| import 'package:wyatt_form_bloc/src/enums/form_status.dart'; | import 'package:wyatt_form_bloc/src/enums/form_status.dart'; | ||||||
|  | import 'package:wyatt_form_bloc/src/enums/validation_error.dart'; | ||||||
| 
 | 
 | ||||||
|  | part 'form_data.dart'; | ||||||
| part 'form_input.dart'; | part 'form_input.dart'; | ||||||
| part 'form_input_metadata.dart'; | part 'form_input_metadata.dart'; | ||||||
| part 'form_input_validator.dart'; | part 'form_input_validator.dart'; | ||||||
| part 'form_data.dart'; | part 'form_validator.dart'; | ||||||
|  | |||||||
| @ -16,7 +16,6 @@ | |||||||
| 
 | 
 | ||||||
| part of 'form.dart'; | part of 'form.dart'; | ||||||
| 
 | 
 | ||||||
| @immutable |  | ||||||
| class FormData extends Equatable { | class FormData extends Equatable { | ||||||
|   final List<FormInput> _inputs; |   final List<FormInput> _inputs; | ||||||
| 
 | 
 | ||||||
| @ -29,7 +28,7 @@ class FormData extends Equatable { | |||||||
|   /// Returns the input for the associated key |   /// Returns the input for the associated key | ||||||
|   FormInput inputOf(String key) { |   FormInput inputOf(String key) { | ||||||
|     if (contains(key)) { |     if (contains(key)) { | ||||||
|       return _inputs.firstWhere((FormInput input) => input.key == key); |       return _inputs.firstWhere((input) => input.key == key); | ||||||
|     } else { |     } else { | ||||||
|       throw Exception('FormInput with key `$key` does not exist in form'); |       throw Exception('FormInput with key `$key` does not exist in form'); | ||||||
|     } |     } | ||||||
| @ -46,23 +45,23 @@ class FormData extends Equatable { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Returns all associated validators as a list |   /// Returns all associated validators as a list | ||||||
|   List<FormInputValidator<V, E>> validators<V, E>() { |   List<FormInputValidator<V, E>> validators<V, E extends ValidationError>() => | ||||||
|     return _inputs |       _inputs | ||||||
|         .map<FormInputValidator<V, E>>( |           .map<FormInputValidator<V, E>>( | ||||||
|           (FormInput input) => input.validator as FormInputValidator<V, E>, |             (input) => input.validator as FormInputValidator<V, E>, | ||||||
|         ) |           ) | ||||||
|         .toList(); |           .toList(); | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   /// A [FormInputValidator] represents the value of a single form input field. |   /// A [FormInputValidator] represents the value of a single form input field. | ||||||
|   /// It contains information about the [FormInputStatus], value, as well |   /// It contains information about the [FormInputStatus], value, as well | ||||||
|   /// as validation status. |   /// as validation status. | ||||||
|   T validatorOf<T>(String key) { |   T validatorOf<T>(String key) => inputOf(key).validator as T; | ||||||
|     return inputOf(key).validator as T; |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   /// Updates validator of a given input. (perform copyWith) |   /// Updates validator of a given input. (perform copyWith) | ||||||
|   void updateValidator(String key, FormInputValidator dirtyValue) { |   void updateValidator<V>( | ||||||
|  |     String key, | ||||||
|  |     FormInputValidator<V, ValidationError> dirtyValue, | ||||||
|  |   ) { | ||||||
|     if (contains(key)) { |     if (contains(key)) { | ||||||
|       final index = _inputs.indexOf( |       final index = _inputs.indexOf( | ||||||
|         inputOf(key), |         inputOf(key), | ||||||
| @ -73,38 +72,31 @@ class FormData extends Equatable { | |||||||
| 
 | 
 | ||||||
|   /// Returns a validation error if the [FormInputValidator] is invalid. |   /// Returns a validation error if the [FormInputValidator] is invalid. | ||||||
|   /// Returns null if the [FormInputValidator] is valid. |   /// Returns null if the [FormInputValidator] is valid. | ||||||
|   E? errorOf<V, E>(String key) { |   E? errorOf<E extends ValidationError>(String key) => | ||||||
|     return (inputOf(key).validator as FormInputValidator<V, E>).error; |       (inputOf(key).validator as FormInputValidator<dynamic, E>).error; | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   /// The value of the associated [FormInputValidator]. For example, |   /// The value of the associated [FormInputValidator]. For example, | ||||||
|   /// if you have a FormInputValidator for FirstName, the value could be 'Joe'. |   /// if you have a FormInputValidator for FirstName, the value could be 'Joe'. | ||||||
|   V valueOf<V, E>(String key) { |   V valueOf<V>(String key) => | ||||||
|     return (inputOf(key).validator as FormInputValidator<V, E>).value; |       (inputOf(key).validator as FormInputValidator<V, dynamic>).value; | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   /// Returns `true` if the [FormInputValidator] is not valid. |   /// Returns `true` if the [FormInputValidator] is not valid. | ||||||
|   /// Same as `E? errorOf<V, E>(String key) != null` |   /// Same as `E? errorOf<V, E>(String key) != null` | ||||||
|   bool isNotValid(String key) { |   bool isNotValid(String key) => !inputOf(key).validator.valid; | ||||||
|     return !inputOf(key).validator.valid; |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   /// Returns all associated metadata as a list |   /// Returns all associated metadata as a list | ||||||
|   List<FormInputMetadata<M>> metadata<M>() { |   List<FormInputMetadata<M>> metadata<M>() => _inputs | ||||||
|     return _inputs |       .map<FormInputMetadata<M>>( | ||||||
|         .map<FormInputMetadata<M>>( |         (input) => input.metadata as FormInputMetadata<M>, | ||||||
|           (FormInput input) => input.metadata as FormInputMetadata<M>, |       ) | ||||||
|         ) |       .toList(); | ||||||
|         .toList(); |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   /// Returns the metadata associated. With `M` the type of extra data. |   /// Returns the metadata associated. With `M` the type of extra data. | ||||||
|   FormInputMetadata<M> metadataOf<M>(String key) { |   FormInputMetadata<M> metadataOf<M>(String key) => | ||||||
|     return inputOf(key).metadata as FormInputMetadata<M>; |       inputOf(key).metadata as FormInputMetadata<M>; | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   /// Updates metadata of a given input. (perform copyWith) |   /// Updates metadata of a given input. (perform copyWith) | ||||||
|   void updateMetadata(String key, FormInputMetadata meta) { |   void updateMetadata<M>(String key, FormInputMetadata<M> meta) { | ||||||
|     if (contains(key)) { |     if (contains(key)) { | ||||||
|       final index = _inputs.indexOf( |       final index = _inputs.indexOf( | ||||||
|         inputOf(key), |         inputOf(key), | ||||||
| @ -113,15 +105,8 @@ class FormData extends Equatable { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Validate self inputs. |  | ||||||
|   FormStatus validate() { |  | ||||||
|     return FormStatus.validateInputs(_inputs); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   /// Check if this contains an input with the given key. |   /// Check if this contains an input with the given key. | ||||||
|   bool contains(String key) { |   bool contains(String key) => _inputs.any((input) => input.key == key); | ||||||
|     return _inputs.any((FormInput input) => input.key == key); |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   /// Makes an intersection set operation and returns newly created [FormData] |   /// Makes an intersection set operation and returns newly created [FormData] | ||||||
|   FormData intersection(FormData other) { |   FormData intersection(FormData other) { | ||||||
| @ -173,11 +158,9 @@ class FormData extends Equatable { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Deeply copy this. |   /// Deeply copy this. | ||||||
|   FormData clone() { |   FormData clone() => FormData( | ||||||
|     return FormData( |         _inputs.map((input) => input.clone()).toList(), | ||||||
|       _inputs.map((FormInput input) => input.clone()).toList(), |       ); | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   /// Export this to [Map] format. |   /// Export this to [Map] format. | ||||||
|   Map<String, dynamic> toMap() { |   Map<String, dynamic> toMap() { | ||||||
| @ -190,6 +173,9 @@ class FormData extends Equatable { | |||||||
|     return map; |     return map; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /// Export this to [String] format. | ||||||
|  |   String toJson() => jsonEncode(toMap()); | ||||||
|  | 
 | ||||||
|   @override |   @override | ||||||
|   bool? get stringify => true; |   bool? get stringify => true; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -17,10 +17,11 @@ | |||||||
| 
 | 
 | ||||||
| part of 'form.dart'; | part of 'form.dart'; | ||||||
| 
 | 
 | ||||||
| class FormInput extends Equatable { | class FormInput<Validator extends FormInputValidator<dynamic, ValidationError>> | ||||||
|  |     extends Equatable { | ||||||
|   final String key; |   final String key; | ||||||
|   final FormInputValidator validator; |   final Validator validator; | ||||||
|   final FormInputMetadata metadata; |   final FormInputMetadata<dynamic> metadata; | ||||||
| 
 | 
 | ||||||
|   String get name => metadata._name ?? key; |   String get name => metadata._name ?? key; | ||||||
| 
 | 
 | ||||||
| @ -31,29 +32,25 @@ class FormInput extends Equatable { | |||||||
|     this.metadata = const FormInputMetadata<void>(export: true), |     this.metadata = const FormInputMetadata<void>(export: true), | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|  |   FormInput copyWith({ | ||||||
|  |     String? key, | ||||||
|  |     Validator? validator, | ||||||
|  |     FormInputMetadata? metadata, | ||||||
|  |   }) => | ||||||
|  |       FormInput( | ||||||
|  |         key ?? this.key, | ||||||
|  |         validator ?? this.validator, | ||||||
|  |         metadata: metadata ?? this.metadata, | ||||||
|  |       ); | ||||||
|  | 
 | ||||||
|  |   FormInput clone() => copyWith( | ||||||
|  |         key: key, | ||||||
|  |         validator: validator, | ||||||
|  |         metadata: metadata, | ||||||
|  |       ); | ||||||
|   @override |   @override | ||||||
|   bool? get stringify => true; |   bool? get stringify => true; | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   List<Object?> get props => [key, validator, metadata]; |   List<Object?> get props => [key, validator, metadata]; | ||||||
| 
 |  | ||||||
|   FormInput copyWith({ |  | ||||||
|     String? key, |  | ||||||
|     FormInputValidator? validator, |  | ||||||
|     FormInputMetadata? metadata, |  | ||||||
|   }) { |  | ||||||
|     return FormInput( |  | ||||||
|       key ?? this.key, |  | ||||||
|       validator ?? this.validator, |  | ||||||
|       metadata: metadata ?? this.metadata, |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   FormInput clone() { |  | ||||||
|     return copyWith( |  | ||||||
|       key: key, |  | ||||||
|       validator: validator, |  | ||||||
|       metadata: metadata, |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -17,7 +17,7 @@ | |||||||
| 
 | 
 | ||||||
| part of 'form.dart'; | part of 'form.dart'; | ||||||
| 
 | 
 | ||||||
| class FormInputMetadata<T> extends Equatable { | class FormInputMetadata<T extends Object?> extends Equatable { | ||||||
|   final bool export; |   final bool export; | ||||||
|   final String? _name; |   final String? _name; | ||||||
|   final T? extra; |   final T? extra; | ||||||
| @ -27,30 +27,27 @@ class FormInputMetadata<T> extends Equatable { | |||||||
|     this.extra, |     this.extra, | ||||||
|     String? name, |     String? name, | ||||||
|   }) : _name = name; |   }) : _name = name; | ||||||
|    |  | ||||||
|   @override |  | ||||||
|   bool? get stringify => true; |  | ||||||
| 
 |  | ||||||
|   @override |  | ||||||
|   List<Object?> get props => [export, _name, extra]; |  | ||||||
| 
 | 
 | ||||||
|   FormInputMetadata<T> copyWith({ |   FormInputMetadata<T> copyWith({ | ||||||
|     bool? export, |     bool? export, | ||||||
|     String? name, |     String? name, | ||||||
|     T? extra, |     T? extra, | ||||||
|   }) { |   }) => | ||||||
|     return FormInputMetadata<T>( |       FormInputMetadata<T>( | ||||||
|       export: export ?? this.export, |         export: export ?? this.export, | ||||||
|       name: name ?? _name, |         name: name ?? _name, | ||||||
|       extra: extra ?? this.extra, |         extra: extra ?? this.extra, | ||||||
|     ); |       ); | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   FormInputMetadata<T> clone() { |   FormInputMetadata<T> clone() => copyWith( | ||||||
|     return copyWith( |         export: export, | ||||||
|       export: export, |         name: _name, | ||||||
|       name: _name, |         extra: extra, | ||||||
|       extra: extra, |       ); | ||||||
|     ); | 
 | ||||||
|   } |   @override | ||||||
|  |   bool? get stringify => true; | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   List<Object?> get props => [export, _name, extra]; | ||||||
| } | } | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ | |||||||
| 
 | 
 | ||||||
| part of 'form.dart'; | part of 'form.dart'; | ||||||
| 
 | 
 | ||||||
| /// {@template form_input} | /// {@template form_input_validator} | ||||||
| /// A [FormInputValidator] represents the value of a single form input field. | /// A [FormInputValidator] represents the value of a single form input field. | ||||||
| /// It contains information about the [FormInputStatus], [value], as well | /// It contains information about the [FormInputStatus], [value], as well | ||||||
| /// as validation status. | /// as validation status. | ||||||
| @ -25,26 +25,28 @@ part of 'form.dart'; | |||||||
| /// [FormInputValidator] instances. | /// [FormInputValidator] instances. | ||||||
| /// | /// | ||||||
| /// ```dart | /// ```dart | ||||||
| /// enum FirstNameError { empty } | /// class Name extends FormInputValidator<String, ValidationStandardError> { | ||||||
| /// class FirstName extends FormInputValidator<String, FirstNameError> { | ///   const Name.pure({String value = ''}) : super.pure(value); | ||||||
| ///   const FirstName.pure({String value = ''}) : super.pure(value); | ///   const Name.dirty({String value = ''}) : super.dirty(value); | ||||||
| ///   const FirstName.dirty({String value = ''}) : super.dirty(value); |  | ||||||
| /// | /// | ||||||
| ///   @override | ///   @override | ||||||
| ///   FirstNameError? validator(String value) { | ///   ValidationStandardError? validator(String? value) { | ||||||
| ///     return value.isEmpty ? FirstNameError.empty : null; | ///     if (value?.isEmpty ?? false) { | ||||||
|  | ///       return ValidationStandardError.empty | ||||||
|  | ///     } | ||||||
|  | ///     return value == null ? ValidationStandardError.invalid : null; | ||||||
| ///   } | ///   } | ||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
| /// {@endtemplate} | /// {@endtemplate} | ||||||
| @immutable | abstract class FormInputValidator<V, E extends ValidationError> | ||||||
| abstract class FormInputValidator<V, E> extends Equatable { |     extends Equatable { | ||||||
|   const FormInputValidator._(this.value, [this.pure = true]); |   const FormInputValidator._(this.value, [this.pure = true]); | ||||||
| 
 | 
 | ||||||
|   /// Constructor which create a `pure` [FormInputValidator] with a given value. |   /// Constructor which create a `pure` [FormInputValidator] with a given value. | ||||||
|   const FormInputValidator.pure(V value) : this._(value); |   const FormInputValidator.pure(V value) : this._(value); | ||||||
| 
 | 
 | ||||||
|   /// Constructor which create a `dirty` [FormInputValidator] with a  |   /// Constructor which create a `dirty` [FormInputValidator] with a | ||||||
|   /// given value. |   /// given value. | ||||||
|   const FormInputValidator.dirty(V value) : this._(value, false); |   const FormInputValidator.dirty(V value) : this._(value, false); | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										31
									
								
								packages/wyatt_form_bloc/lib/src/form/form_validator.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								packages/wyatt_form_bloc/lib/src/form/form_validator.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | |||||||
|  | // Copyright (C) 2022 WYATT GROUP | ||||||
|  | // Please see the AUTHORS file for details. | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||||
|  | 
 | ||||||
|  | part of 'form.dart'; | ||||||
|  | 
 | ||||||
|  | /// {@template form_validator} | ||||||
|  | /// A [FormValidator] represents the global validaton state of a Form. | ||||||
|  | /// {@endtemplate} | ||||||
|  | abstract class FormValidator { | ||||||
|  |   /// {@macro form_validator} | ||||||
|  |   const FormValidator(); | ||||||
|  | 
 | ||||||
|  |   bool isPure(FormData form) => form | ||||||
|  |       .validators<dynamic, ValidationError>() | ||||||
|  |       .every((validator) => validator.pure); | ||||||
|  | 
 | ||||||
|  |   FormStatus validate(FormData form); | ||||||
|  | } | ||||||
| @ -0,0 +1,40 @@ | |||||||
|  | // Copyright (C) 2022 WYATT GROUP | ||||||
|  | // Please see the AUTHORS file for details. | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||||
|  | 
 | ||||||
|  | import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; | ||||||
|  | 
 | ||||||
|  | /// {@template every} | ||||||
|  | /// Check and validate every input of a form | ||||||
|  | /// {@endtemplate} | ||||||
|  | class EveryInputValidator extends FormValidator { | ||||||
|  |   /// {@macro every} | ||||||
|  |   const EveryInputValidator() : super(); | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   FormStatus validate(FormData form) { | ||||||
|  |     if (isPure(form)) { | ||||||
|  |       return FormStatus.pure; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (form | ||||||
|  |         .validators<dynamic, ValidationError>() | ||||||
|  |         .any((validator) => validator.valid == false)) { | ||||||
|  |       return FormStatus.invalid; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return FormStatus.valid; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -0,0 +1,41 @@ | |||||||
|  | // Copyright (C) 2022 WYATT GROUP | ||||||
|  | // Please see the AUTHORS file for details. | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||||
|  | 
 | ||||||
|  | import 'package:wyatt_form_bloc/src/enums/validation_error.dart'; | ||||||
|  | import 'package:wyatt_form_bloc/src/form/form.dart'; | ||||||
|  | 
 | ||||||
|  | /// {@template regex_validator} | ||||||
|  | /// Abstract regex validator for form input. | ||||||
|  | /// {@endtemplate} | ||||||
|  | abstract class RegexValidator<E extends ValidationError> | ||||||
|  |     extends FormInputValidator<String, E> { | ||||||
|  |   const RegexValidator.pure() : super.pure(''); | ||||||
|  |   const RegexValidator.dirty([super.value = '']) : super.dirty(); | ||||||
|  | 
 | ||||||
|  |   RegExp get regex; | ||||||
|  |   /// If the value is **not** null, but empty. | ||||||
|  |   E get onEmpty; | ||||||
|  |   /// If value does not conform to regex. | ||||||
|  |   E get onError; | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   E? validator(String? value) { | ||||||
|  |     if (value?.isEmpty ?? false) { | ||||||
|  |       return onEmpty; | ||||||
|  |     } | ||||||
|  |     return regex.hasMatch(value ?? '') ? null : onError; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -0,0 +1,38 @@ | |||||||
|  | // Copyright (C) 2022 WYATT GROUP | ||||||
|  | // Please see the AUTHORS file for details. | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||||
|  | 
 | ||||||
|  | import 'package:wyatt_form_bloc/src/enums/validation_error.dart'; | ||||||
|  | import 'package:wyatt_form_bloc/src/form/form.dart'; | ||||||
|  | 
 | ||||||
|  | abstract class TextValidator<E extends ValidationError> | ||||||
|  |     extends FormInputValidator<String, E> { | ||||||
|  |   const TextValidator.pure() : super.pure(''); | ||||||
|  |   const TextValidator.dirty([super.value = '']) : super.dirty(); | ||||||
|  | 
 | ||||||
|  |   /// If the value is **not** null, but empty. | ||||||
|  |   E get onEmpty; | ||||||
|  | 
 | ||||||
|  |   /// If value is null. | ||||||
|  |   E get onNull; | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   E? validator(String? value) { | ||||||
|  |     if (value?.isEmpty ?? false) { | ||||||
|  |       return onEmpty; | ||||||
|  |     } | ||||||
|  |     return value != null ? null : onNull; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart'; | |||||||
| /// {@template boolean} | /// {@template boolean} | ||||||
| /// Form input for a bool input | /// Form input for a bool input | ||||||
| /// {@endtemplate} | /// {@endtemplate} | ||||||
| class Boolean extends FormInputValidator<bool, ValidationError> { | class Boolean extends FormInputValidator<bool, ValidationStandardError> { | ||||||
|   /// {@macro boolean} |   /// {@macro boolean} | ||||||
|   const Boolean.pure({bool? defaultValue = false}) |   const Boolean.pure({bool? defaultValue = false}) | ||||||
|       : super.pure(defaultValue ?? false); |       : super.pure(defaultValue ?? false); | ||||||
| @ -29,7 +29,6 @@ class Boolean extends FormInputValidator<bool, ValidationError> { | |||||||
|   const Boolean.dirty({bool value = false}) : super.dirty(value); |   const Boolean.dirty({bool value = false}) : super.dirty(value); | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   ValidationError? validator(bool? value) { |   ValidationStandardError? validator(bool? value) => | ||||||
|     return value != null ? null : ValidationError.invalid; |       value != null ? null : ValidationStandardError.invalid; | ||||||
|   } |  | ||||||
| } | } | ||||||
| @ -21,7 +21,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart'; | |||||||
| /// Form input for a confirmed password input. | /// Form input for a confirmed password input. | ||||||
| /// {@endtemplate} | /// {@endtemplate} | ||||||
| class ConfirmedPassword | class ConfirmedPassword | ||||||
|     extends FormInputValidator<String, ValidationError> { |     extends FormInputValidator<String, ValidationStandardError> { | ||||||
|   /// {@macro confirmed_password} |   /// {@macro confirmed_password} | ||||||
|   const ConfirmedPassword.pure({this.password = ''}) : super.pure(''); |   const ConfirmedPassword.pure({this.password = ''}) : super.pure(''); | ||||||
| 
 | 
 | ||||||
| @ -33,7 +33,6 @@ class ConfirmedPassword | |||||||
|   final String password; |   final String password; | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   ValidationError? validator(String? value) { |   ValidationStandardError? validator(String? value) => | ||||||
|     return password == value ? null : ValidationError.invalid; |       password == value ? null : ValidationStandardError.invalid; | ||||||
|   } |  | ||||||
| } | } | ||||||
| @ -15,24 +15,25 @@ | |||||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||||
| 
 | 
 | ||||||
| import 'package:wyatt_form_bloc/src/enums/enums.dart'; | import 'package:wyatt_form_bloc/src/enums/enums.dart'; | ||||||
| import 'package:wyatt_form_bloc/src/form/form.dart'; | import 'package:wyatt_form_bloc/src/validators/inputs/base/regex_validator.dart'; | ||||||
| 
 | 
 | ||||||
| /// {@template email} | /// {@template email} | ||||||
| /// Form input for an email input. | /// Form input for an email input. | ||||||
| /// {@endtemplate} | /// {@endtemplate} | ||||||
| class Email extends FormInputValidator<String, ValidationError> { | class Email extends RegexValidator<ValidationStandardError> { | ||||||
|   /// {@macro email} |   /// {@macro email} | ||||||
|   const Email.pure() : super.pure(''); |   const Email.pure() : super.pure(); | ||||||
| 
 | 
 | ||||||
|   /// {@macro email} |   /// {@macro email} | ||||||
|   const Email.dirty([String value = '']) : super.dirty(value); |   const Email.dirty([super.value = '']) : super.dirty(); | ||||||
| 
 |  | ||||||
|   static final RegExp _emailRegExp = RegExp( |  | ||||||
|     r'[^@ \t\r\n]+@[^@ \t\r\n]+\.[^@ \t\r\n]+', |  | ||||||
|   ); |  | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   ValidationError? validator(String? value) { |   ValidationStandardError get onEmpty => ValidationStandardError.empty; | ||||||
|     return _emailRegExp.hasMatch(value ?? '') ? null : ValidationError.invalid; |   @override | ||||||
|   } |   ValidationStandardError get onError => ValidationStandardError.invalid; | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   RegExp get regex => RegExp( | ||||||
|  |         r'[^@ \t\r\n]+@[^@ \t\r\n]+\.[^@ \t\r\n]+', | ||||||
|  |       ); | ||||||
| } | } | ||||||
| @ -0,0 +1,32 @@ | |||||||
|  | // Copyright (C) 2022 WYATT GROUP | ||||||
|  | // Please see the AUTHORS file for details. | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||||
|  | 
 | ||||||
|  | import 'package:wyatt_form_bloc/src/enums/validation_error.dart'; | ||||||
|  | import 'package:wyatt_form_bloc/src/validators/inputs/base/text_validator.dart'; | ||||||
|  | 
 | ||||||
|  | class EnumValidator<E> extends TextValidator<ValidationStandardError> { | ||||||
|  |   /// {@macro text_string} | ||||||
|  |   const EnumValidator.pure() : super.pure(); | ||||||
|  | 
 | ||||||
|  |   /// {@macro text_string} | ||||||
|  |   EnumValidator.dirty(E value) : super.dirty(value.toString()); | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   ValidationStandardError get onEmpty => ValidationStandardError.empty; | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   ValidationStandardError get onNull => ValidationStandardError.invalid; | ||||||
|  | } | ||||||
| @ -15,24 +15,25 @@ | |||||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||||
| 
 | 
 | ||||||
| import 'package:wyatt_form_bloc/src/enums/enums.dart'; | import 'package:wyatt_form_bloc/src/enums/enums.dart'; | ||||||
| import 'package:wyatt_form_bloc/src/form/form.dart'; | import 'package:wyatt_form_bloc/src/validators/inputs/base/regex_validator.dart'; | ||||||
| 
 | 
 | ||||||
| /// {@template iban} | /// {@template iban} | ||||||
| /// Form input for an IBAN input. | /// Form input for an IBAN input. | ||||||
| /// {@endtemplate} | /// {@endtemplate} | ||||||
| class Iban extends FormInputValidator<String, ValidationError> { | class Iban extends RegexValidator<ValidationStandardError> { | ||||||
|   /// {@macro iban} |   /// {@macro iban} | ||||||
|   const Iban.pure() : super.pure(''); |   const Iban.pure() : super.pure(); | ||||||
| 
 | 
 | ||||||
|   /// {@macro iban} |   /// {@macro iban} | ||||||
|   const Iban.dirty([String value = '']) : super.dirty(value); |   const Iban.dirty([super.value = '']) : super.dirty(); | ||||||
| 
 |  | ||||||
|   static final RegExp _regExp = RegExp( |  | ||||||
|     r'^(?:((?:IT|SM)\d{2}[A-Z]{1}\d{22})|(NL\d{2}[A-Z]{4}\d{10})|(LV\d{2}[A-Z]{4}\d{13})|((?:BG|GB|IE)\d{2}[A-Z]{4}\d{14})|(GI\d{2}[A-Z]{4}\d{15})|(RO\d{2}[A-Z]{4}\d{16})|(MT\d{2}[A-Z]{4}\d{23})|(NO\d{13})|((?:DK|FI)\d{16})|((?:SI)\d{17})|((?:AT|EE|LU|LT)\d{18})|((?:HR|LI|CH)\d{19})|((?:DE|VA)\d{20})|((?:AD|CZ|ES|MD|SK|SE)\d{22})|(PT\d{23})|((?:IS)\d{24})|((?:BE)\d{14})|((?:FR|MC|GR)\d{25})|((?:PL|HU|CY)\d{26}))$', |  | ||||||
|   ); |  | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   ValidationError? validator(String? value) { |   ValidationStandardError get onEmpty => ValidationStandardError.empty; | ||||||
|     return _regExp.hasMatch(value ?? '') ? null : ValidationError.invalid; |   @override | ||||||
|   } |   ValidationStandardError get onError => ValidationStandardError.invalid; | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   RegExp get regex => RegExp( | ||||||
|  |         r'^(?:((?:IT|SM)\d{2}[A-Z]{1}\d{22})|(NL\d{2}[A-Z]{4}\d{10})|(LV\d{2}[A-Z]{4}\d{13})|((?:BG|GB|IE)\d{2}[A-Z]{4}\d{14})|(GI\d{2}[A-Z]{4}\d{15})|(RO\d{2}[A-Z]{4}\d{16})|(MT\d{2}[A-Z]{4}\d{23})|(NO\d{13})|((?:DK|FI)\d{16})|((?:SI)\d{17})|((?:AT|EE|LU|LT)\d{18})|((?:HR|LI|CH)\d{19})|((?:DE|VA)\d{20})|((?:AD|CZ|ES|MD|SK|SE)\d{22})|(PT\d{23})|((?:IS)\d{24})|((?:BE)\d{14})|((?:FR|MC|GR)\d{25})|((?:PL|HU|CY)\d{26}))$', | ||||||
|  |       ); | ||||||
| } | } | ||||||
| @ -20,7 +20,8 @@ import 'package:wyatt_form_bloc/src/form/form.dart'; | |||||||
| /// {@template list_option} | /// {@template list_option} | ||||||
| /// Form input for a list input | /// Form input for a list input | ||||||
| /// {@endtemplate} | /// {@endtemplate} | ||||||
| class ListOption<T> extends FormInputValidator<List<T>, ValidationError> { | class ListOption<T> | ||||||
|  |     extends FormInputValidator<List<T>, ValidationStandardError> { | ||||||
|   /// {@macro list_option} |   /// {@macro list_option} | ||||||
|   const ListOption.pure({List<T>? defaultValue}) |   const ListOption.pure({List<T>? defaultValue}) | ||||||
|       : super.pure(defaultValue ?? const []); |       : super.pure(defaultValue ?? const []); | ||||||
| @ -28,7 +29,7 @@ class ListOption<T> extends FormInputValidator<List<T>, ValidationError> { | |||||||
|   /// {@macro list_option} |   /// {@macro list_option} | ||||||
|   const ListOption.dirty({List<T>? value}) : super.dirty(value ?? const []); |   const ListOption.dirty({List<T>? value}) : super.dirty(value ?? const []); | ||||||
| 
 | 
 | ||||||
|   ListOption select(T? v) { |   ListOption<T> select(T? v) { | ||||||
|     if (v == null) { |     if (v == null) { | ||||||
|       return this; |       return this; | ||||||
|     } |     } | ||||||
| @ -42,7 +43,8 @@ class ListOption<T> extends FormInputValidator<List<T>, ValidationError> { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   ValidationError? validator(List<T>? value) { |   ValidationStandardError? validator(List<T>? value) => | ||||||
|     return value?.isNotEmpty == true ? null : ValidationError.invalid; |       value?.isNotEmpty ?? false == true | ||||||
|   } |           ? null | ||||||
|  |           : ValidationStandardError.invalid; | ||||||
| } | } | ||||||
| @ -15,22 +15,23 @@ | |||||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||||
| 
 | 
 | ||||||
| import 'package:wyatt_form_bloc/src/enums/enums.dart'; | import 'package:wyatt_form_bloc/src/enums/enums.dart'; | ||||||
| import 'package:wyatt_form_bloc/src/form/form.dart'; | import 'package:wyatt_form_bloc/src/validators/inputs/base/regex_validator.dart'; | ||||||
| 
 | 
 | ||||||
| /// {@template name} | /// {@template name} | ||||||
| /// Form input for a name input. | /// Form input for a name input. | ||||||
| /// {@endtemplate} | /// {@endtemplate} | ||||||
| class Name extends FormInputValidator<String, ValidationError> { | class Name extends RegexValidator<ValidationStandardError> { | ||||||
|   /// {@macro name} |   /// {@macro name} | ||||||
|   const Name.pure() : super.pure(''); |   const Name.pure() : super.pure(); | ||||||
| 
 | 
 | ||||||
|   /// {@macro name} |   /// {@macro name} | ||||||
|   const Name.dirty([String value = '']) : super.dirty(value); |   const Name.dirty([super.value = '']) : super.dirty(); | ||||||
| 
 |  | ||||||
|   static final RegExp _nameRegExp = RegExp(r"^([ \u00c0-\u01ffa-zA-Z'\-])+$"); |  | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   ValidationError? validator(String? value) { |   ValidationStandardError get onEmpty => ValidationStandardError.empty; | ||||||
|     return _nameRegExp.hasMatch(value ?? '') ? null : ValidationError.invalid; |   @override | ||||||
|   } |   ValidationStandardError get onError => ValidationStandardError.invalid; | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   RegExp get regex => RegExp(r"^([ \u00c0-\u01ffa-zA-Z'\-])+$"); | ||||||
| } | } | ||||||
| @ -15,25 +15,23 @@ | |||||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||||
| 
 | 
 | ||||||
| import 'package:wyatt_form_bloc/src/enums/enums.dart'; | import 'package:wyatt_form_bloc/src/enums/enums.dart'; | ||||||
| import 'package:wyatt_form_bloc/src/form/form.dart'; | import 'package:wyatt_form_bloc/src/validators/inputs/base/regex_validator.dart'; | ||||||
| 
 | 
 | ||||||
| /// {@template password} | /// {@template password} | ||||||
| /// Form input for a password input. | /// Form input for a password input. | ||||||
| /// {@endtemplate} | /// {@endtemplate} | ||||||
| class Password extends FormInputValidator<String, ValidationError> { | class Password extends RegexValidator<ValidationStandardError> { | ||||||
|   /// {@macro password} |   /// {@macro password} | ||||||
|   const Password.pure() : super.pure(''); |   const Password.pure() : super.pure(); | ||||||
| 
 | 
 | ||||||
|   /// {@macro password} |   /// {@macro password} | ||||||
|   const Password.dirty([String value = '']) : super.dirty(value); |   const Password.dirty([super.value = '']) : super.dirty(); | ||||||
| 
 |  | ||||||
|   static final RegExp _passwordRegExp = |  | ||||||
|       RegExp(r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$'); |  | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   ValidationError? validator(String? value) { |   ValidationStandardError get onEmpty => ValidationStandardError.empty; | ||||||
|     return _passwordRegExp.hasMatch(value ?? '') |   @override | ||||||
|         ? null |   ValidationStandardError get onError => ValidationStandardError.invalid; | ||||||
|         : ValidationError.invalid; | 
 | ||||||
|   } |   @override | ||||||
|  |   RegExp get regex => RegExp(r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$'); | ||||||
| } | } | ||||||
| @ -15,23 +15,24 @@ | |||||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||||
| 
 | 
 | ||||||
| import 'package:wyatt_form_bloc/src/enums/enums.dart'; | import 'package:wyatt_form_bloc/src/enums/enums.dart'; | ||||||
| import 'package:wyatt_form_bloc/src/form/form.dart'; | import 'package:wyatt_form_bloc/src/validators/inputs/base/regex_validator.dart'; | ||||||
| 
 | 
 | ||||||
| /// {@template phone} | /// {@template phone} | ||||||
| /// Form input for a phone input. | /// Form input for a phone input. | ||||||
| /// {@endtemplate} | /// {@endtemplate} | ||||||
| class Phone extends FormInputValidator<String, ValidationError> { | class Phone extends RegexValidator<ValidationStandardError> { | ||||||
|   /// {@macro phone} |   /// {@macro phone} | ||||||
|   const Phone.pure() : super.pure(''); |   const Phone.pure() : super.pure(); | ||||||
| 
 | 
 | ||||||
|   /// {@macro phone} |   /// {@macro phone} | ||||||
|   const Phone.dirty([String value = '']) : super.dirty(value); |   const Phone.dirty([super.value = '']) : super.dirty(); | ||||||
| 
 |  | ||||||
|   static final RegExp _phoneRegExp = |  | ||||||
|       RegExp(r'^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$'); |  | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   ValidationError? validator(String? value) { |   ValidationStandardError get onEmpty => ValidationStandardError.empty; | ||||||
|     return _phoneRegExp.hasMatch(value ?? '') ? null : ValidationError.invalid; |   @override | ||||||
|   } |   ValidationStandardError get onError => ValidationStandardError.invalid; | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   RegExp get regex => | ||||||
|  |       RegExp(r'^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$'); | ||||||
| } | } | ||||||
| @ -15,22 +15,23 @@ | |||||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||||
| 
 | 
 | ||||||
| import 'package:wyatt_form_bloc/src/enums/enums.dart'; | import 'package:wyatt_form_bloc/src/enums/enums.dart'; | ||||||
| import 'package:wyatt_form_bloc/src/form/form.dart'; | import 'package:wyatt_form_bloc/src/validators/inputs/base/regex_validator.dart'; | ||||||
| 
 | 
 | ||||||
| /// {@template siren} | /// {@template siren} | ||||||
| /// Form input for a SIREN input. | /// Form input for a SIREN input. | ||||||
| /// {@endtemplate} | /// {@endtemplate} | ||||||
| class Siren extends FormInputValidator<String, ValidationError> { | class Siren extends RegexValidator<ValidationStandardError> { | ||||||
|   /// {@macro siren} |   /// {@macro siren} | ||||||
|   const Siren.pure() : super.pure(''); |   const Siren.pure() : super.pure(); | ||||||
| 
 | 
 | ||||||
|   /// {@macro siren} |   /// {@macro siren} | ||||||
|   const Siren.dirty([String value = '']) : super.dirty(value); |   const Siren.dirty([super.value = '']) : super.dirty(); | ||||||
| 
 |  | ||||||
|   static final RegExp _regExp = RegExp(r'(\d{9}|\d{3}[ ]\d{3}[ ]\d{3})$'); |  | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   ValidationError? validator(String? value) { |   ValidationStandardError get onEmpty => ValidationStandardError.empty; | ||||||
|     return _regExp.hasMatch(value ?? '') ? null : ValidationError.invalid; |   @override | ||||||
|   } |   ValidationStandardError get onError => ValidationStandardError.invalid; | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   RegExp get regex => RegExp(r'(\d{9}|\d{3}[ ]\d{3}[ ]\d{3})$'); | ||||||
| } | } | ||||||
| @ -15,20 +15,21 @@ | |||||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||||
| 
 | 
 | ||||||
| import 'package:wyatt_form_bloc/src/enums/enums.dart'; | import 'package:wyatt_form_bloc/src/enums/enums.dart'; | ||||||
| import 'package:wyatt_form_bloc/src/form/form.dart'; | import 'package:wyatt_form_bloc/src/validators/inputs/base/text_validator.dart'; | ||||||
| 
 | 
 | ||||||
| /// {@template text_string} | /// {@template text_string} | ||||||
| /// Form input for a text input | /// Form input for a text input | ||||||
| /// {@endtemplate} | /// {@endtemplate} | ||||||
| class TextString extends FormInputValidator<String, ValidationError> { | class TextString extends TextValidator<ValidationStandardError> { | ||||||
|   /// {@macro text_string} |   /// {@macro text_string} | ||||||
|   const TextString.pure() : super.pure(''); |   const TextString.pure() : super.pure(); | ||||||
| 
 | 
 | ||||||
|   /// {@macro text_string} |   /// {@macro text_string} | ||||||
|   const TextString.dirty([String value = '']) : super.dirty(value); |   const TextString.dirty([super.value = '']) : super.dirty(); | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   ValidationError? validator(String? value) { |   ValidationStandardError get onEmpty => ValidationStandardError.empty; | ||||||
|     return (value?.isNotEmpty ?? false) ? null : ValidationError.invalid; | 
 | ||||||
|   } |   @override | ||||||
|  |   ValidationStandardError get onNull => ValidationStandardError.invalid; | ||||||
| } | } | ||||||
| @ -14,13 +14,16 @@ | |||||||
| // You should have received a copy of the GNU General Public License | // You should have received a copy of the GNU General Public License | ||||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||||
| 
 | 
 | ||||||
| export 'boolean.dart'; | export 'inputs/base/regex_validator.dart'; | ||||||
| export 'confirmed_password.dart'; | export 'inputs/base/text_validator.dart'; | ||||||
| export 'email.dart'; | export 'inputs/boolean.dart'; | ||||||
| export 'iban.dart'; | export 'inputs/confirmed_password.dart'; | ||||||
| export 'list_option.dart'; | export 'inputs/email.dart'; | ||||||
| export 'name.dart'; | export 'inputs/enum_validator.dart'; | ||||||
| export 'password.dart'; | export 'inputs/iban.dart'; | ||||||
| export 'phone.dart'; | export 'inputs/list_option.dart'; | ||||||
| export 'siren.dart'; | export 'inputs/name.dart'; | ||||||
| export 'text_string.dart'; | export 'inputs/password.dart'; | ||||||
|  | export 'inputs/phone.dart'; | ||||||
|  | export 'inputs/siren.dart'; | ||||||
|  | export 'inputs/text_string.dart'; | ||||||
|  | |||||||
| @ -9,7 +9,6 @@ environment: | |||||||
| dependencies: | dependencies: | ||||||
|   bloc: ^8.0.3 |   bloc: ^8.0.3 | ||||||
|   equatable: ^2.0.3 |   equatable: ^2.0.3 | ||||||
|   meta: ^1.7.0 |  | ||||||
| 
 | 
 | ||||||
| dev_dependencies: | dev_dependencies: | ||||||
|   test: ^1.21.4 |   test: ^1.21.4 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user