diff --git a/packages/wyatt_form_bloc/analysis_options.yaml b/packages/wyatt_form_bloc/analysis_options.yaml index 2f6092c4..cc27c8ca 100644 --- a/packages/wyatt_form_bloc/analysis_options.yaml +++ b/packages/wyatt_form_bloc/analysis_options.yaml @@ -1 +1,4 @@ -include: package:wyatt_analysis/analysis_options.yaml \ No newline at end of file +include: package:wyatt_analysis/analysis_options.yaml + +analyzer: + exclude: "!example/**" \ No newline at end of file diff --git a/packages/wyatt_form_bloc/example/lib/app/app.dart b/packages/wyatt_form_bloc/example/lib/app/app.dart index cc8691b4..611f20ac 100644 --- a/packages/wyatt_form_bloc/example/lib/app/app.dart +++ b/packages/wyatt_form_bloc/example/lib/app/app.dart @@ -61,10 +61,10 @@ class App extends StatelessWidget { @override Widget build(BuildContext context) { - FormDataCubit _formCubit = CustomFormCubit(inputs: getNormalFormData()); + FormDataCubit formCubit = CustomFormCubit(inputs: getNormalFormData()); return BlocProvider( - create: (context) => _formCubit, + create: (context) => formCubit, child: const WidgetTree(), ); } diff --git a/packages/wyatt_form_bloc/example/lib/sign_up/widgets/sign_up_form.dart b/packages/wyatt_form_bloc/example/lib/sign_up/widgets/sign_up_form.dart index 6d611a5d..947faf6d 100644 --- a/packages/wyatt_form_bloc/example/lib/sign_up/widgets/sign_up_form.dart +++ b/packages/wyatt_form_bloc/example/lib/sign_up/widgets/sign_up_form.dart @@ -187,9 +187,9 @@ class _CheckListInput extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - final _input = + final input = state.data.validatorOf>(formFieldList); - final _options = _input.value; + final options = input.value; return Column( mainAxisSize: MainAxisSize.min, @@ -197,33 +197,33 @@ class _CheckListInput extends StatelessWidget { ListTile( title: const Text('Checkbox1'), trailing: Checkbox( - value: _options.contains('checkbox1'), + value: options.contains('checkbox1'), onChanged: (_) { context.read().dataChanged( formFieldList, - _input.select('checkbox1'), + input.select('checkbox1'), ); }), ), ListTile( title: const Text('Checkbox2'), trailing: Checkbox( - value: _options.contains('checkbox2'), + value: options.contains('checkbox2'), onChanged: (_) { context.read().dataChanged( formFieldList, - _input.select('checkbox2'), + input.select('checkbox2'), ); }), ), ListTile( title: const Text('Checkbox3 (default)'), trailing: Checkbox( - value: _options.contains('checkbox3'), + value: options.contains('checkbox3'), onChanged: (_) { context.read().dataChanged( formFieldList, - _input.select('checkbox3'), + input.select('checkbox3'), ); }), ), @@ -239,7 +239,7 @@ class _RadioListInput extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - final _input = state.data.validatorOf(formFieldRadio); + final input = state.data.validatorOf(formFieldRadio); return Column( mainAxisSize: MainAxisSize.min, @@ -248,7 +248,7 @@ class _RadioListInput extends StatelessWidget { title: const Text('Radio1'), trailing: Radio( groupValue: true, - value: _input.value == 'radio1', + value: input.value == 'radio1', onChanged: (_) { context.read().dataChanged( formFieldRadio, @@ -260,7 +260,7 @@ class _RadioListInput extends StatelessWidget { title: const Text('Radio2'), trailing: Radio( groupValue: true, - value: _input.value == 'radio2', + value: input.value == 'radio2', onChanged: (_) { context.read().dataChanged( formFieldRadio, @@ -272,7 +272,7 @@ class _RadioListInput extends StatelessWidget { title: const Text('Radio3'), trailing: Radio( groupValue: true, - value: _input.value == 'radio3', + value: input.value == 'radio3', onChanged: (_) { context.read().dataChanged( formFieldRadio, @@ -378,6 +378,20 @@ class _DebugButton extends StatelessWidget { } } +class _ResetButton extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return ElevatedButton( + onPressed: () => context.read().resetForm(), + child: const Text('RESET'), + ); + }, + ); + } +} + class SignUpForm extends StatelessWidget { const SignUpForm({Key? key}) : super(key: key); @@ -431,6 +445,8 @@ class SignUpForm extends StatelessWidget { _SignUpButton(), const SizedBox(height: 8), _DebugButton(), + const SizedBox(height: 8), + _ResetButton(), ], ), ), diff --git a/packages/wyatt_form_bloc/lib/src/cubit/form_data_cubit.dart b/packages/wyatt_form_bloc/lib/src/cubit/form_data_cubit.dart index 1be75738..5434acac 100644 --- a/packages/wyatt_form_bloc/lib/src/cubit/form_data_cubit.dart +++ b/packages/wyatt_form_bloc/lib/src/cubit/form_data_cubit.dart @@ -18,40 +18,56 @@ import 'dart:async'; import 'package:bloc/bloc.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/form/form.dart'; +import 'package:wyatt_form_bloc/src/validators/form/every_input_validator.dart'; part 'form_data_state.dart'; abstract class FormDataCubit extends Cubit { - FormDataCubit({required FormData inputs}) - : super(FormDataState(data: inputs)); + FormValidator validationStrategy; + FormData formCopy; + + FormDataCubit({ + required FormData inputs, + FormValidator validator = const EveryInputValidator(), + }) : formCopy = inputs.clone(), + validationStrategy = validator, + super(FormDataState(data: inputs)); /// Change value of a field. - /// + /// /// Inputs: /// - `field`: The key of the field to change. /// - `dirtyValue`: The new value of the field. (Wrapped in a dirty validator) - void dataChanged(String field, FormInputValidator dirtyValue) { - final _form = state.data.clone(); + void dataChanged( + String field, + FormInputValidator dirtyValue, + ) { + final form = state.data.clone(); - if (_form.contains(field)) { - _form.updateValidator(field, dirtyValue); + if (form.contains(field)) { + form.updateValidator(field, dirtyValue); } else { throw Exception('Form field $field not found'); } emit( state.copyWith( - data: _form, - status: _form.validate(), + data: form, + 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. - /// + /// /// Inputs: /// - `data`: The new entries list. /// - `operation`: The operation to perform on the entries set. @@ -59,31 +75,36 @@ abstract class FormDataCubit extends Cubit { FormData data, { SetOperation operation = SetOperation.replace, }) { - FormData _form = data; + FormData form = data; switch (operation) { case SetOperation.replace: - _form = data; + form = data; break; case SetOperation.difference: - _form = state.data.difference(data); + form = state.data.difference(data); break; case SetOperation.intersection: - _form = state.data.intersection(data); + form = state.data.intersection(data); break; case SetOperation.union: - _form = state.data.union(data); + form = state.data.union(data); break; } emit( state.copyWith( - data: _form, - status: _form.validate(), + data: form, + status: validationStrategy.validate(form), ), ); } + /// Reset all form inputs + void resetForm() { + emit(FormDataState(data: formCopy)); + } + /// Submit the form. Future submitForm(); } diff --git a/packages/wyatt_form_bloc/lib/src/cubit/form_data_state.dart b/packages/wyatt_form_bloc/lib/src/cubit/form_data_state.dart index 20db50b0..4629b0e6 100644 --- a/packages/wyatt_form_bloc/lib/src/cubit/form_data_state.dart +++ b/packages/wyatt_form_bloc/lib/src/cubit/form_data_state.dart @@ -16,7 +16,6 @@ part of 'form_data_cubit.dart'; -@immutable class FormDataState extends Equatable { /// Global status of a form. final FormStatus status; @@ -37,13 +36,11 @@ class FormDataState extends Equatable { FormStatus? status, FormData? data, String? errorMessage, - }) { - return FormDataState( + }) => FormDataState( status: status ?? this.status, data: data ?? this.data, errorMessage: errorMessage ?? this.errorMessage, ); - } @override bool? get stringify => true; diff --git a/packages/wyatt_form_bloc/lib/src/enums/form_status.dart b/packages/wyatt_form_bloc/lib/src/enums/form_status.dart index 01870220..c4316e65 100644 --- a/packages/wyatt_form_bloc/lib/src/enums/form_status.dart +++ b/packages/wyatt_form_bloc/lib/src/enums/form_status.dart @@ -14,8 +14,6 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -import 'package:wyatt_form_bloc/src/form/form.dart'; - const Set _validatedFormStatuses = { FormStatus.valid, FormStatus.submissionInProgress, @@ -51,6 +49,8 @@ enum FormStatus { bool get isPure => this == FormStatus.pure; /// Indicates whether the form is completely validated. + /// This means the [FormStatus] is strictly: + /// * `FormStatus.valid` bool get isValid => this == FormStatus.valid; /// Indicates whether the form has been validated successfully. @@ -75,23 +75,4 @@ enum FormStatus { /// Indicates whether the form submission has been canceled. bool get isSubmissionCanceled => this == FormStatus.submissionCanceled; - - /// Validate a list of inputs by processing them in `validate` as validators. - static FormStatus validateInputs(List inputs) { - return validate( - inputs - .map((FormInput input) => input.validator) - .toList(), - ); - } - - /// Validate a list of validators. - static FormStatus validate(List validators) { - return validators.every((FormInputValidator validator) => validator.pure) - ? FormStatus.pure - : validators - .any((FormInputValidator validator) => validator.valid == false) - ? FormStatus.invalid - : FormStatus.valid; - } } diff --git a/packages/wyatt_form_bloc/lib/src/enums/validation_error.dart b/packages/wyatt_form_bloc/lib/src/enums/validation_error.dart index d5ae6088..5a0b436e 100644 --- a/packages/wyatt_form_bloc/lib/src/enums/validation_error.dart +++ b/packages/wyatt_form_bloc/lib/src/enums/validation_error.dart @@ -14,7 +14,14 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -enum ValidationError { - /// Generic invalid error. - invalid +// enum ValidationError { +// /// Generic invalid error. +// invalid +// } + +abstract class ValidationError {} + +enum ValidationStandardError implements ValidationError { + invalid, + empty } diff --git a/packages/wyatt_form_bloc/lib/src/form/form.dart b/packages/wyatt_form_bloc/lib/src/form/form.dart index 2a131d1b..041263fc 100644 --- a/packages/wyatt_form_bloc/lib/src/form/form.dart +++ b/packages/wyatt_form_bloc/lib/src/form/form.dart @@ -14,12 +14,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +import 'dart:convert'; + 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_status.dart'; +import 'package:wyatt_form_bloc/src/enums/validation_error.dart'; +part 'form_data.dart'; part 'form_input.dart'; part 'form_input_metadata.dart'; part 'form_input_validator.dart'; -part 'form_data.dart'; +part 'form_validator.dart'; diff --git a/packages/wyatt_form_bloc/lib/src/form/form_data.dart b/packages/wyatt_form_bloc/lib/src/form/form_data.dart index 7e874580..86e67940 100644 --- a/packages/wyatt_form_bloc/lib/src/form/form_data.dart +++ b/packages/wyatt_form_bloc/lib/src/form/form_data.dart @@ -16,7 +16,6 @@ part of 'form.dart'; -@immutable class FormData extends Equatable { final List _inputs; @@ -29,7 +28,7 @@ class FormData extends Equatable { /// Returns the input for the associated key FormInput inputOf(String key) { if (contains(key)) { - return _inputs.firstWhere((FormInput input) => input.key == key); + return _inputs.firstWhere((input) => input.key == key); } else { 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 - List> validators() { - return _inputs - .map>( - (FormInput input) => input.validator as FormInputValidator, - ) - .toList(); - } + List> validators() => + _inputs + .map>( + (input) => input.validator as FormInputValidator, + ) + .toList(); /// A [FormInputValidator] represents the value of a single form input field. /// It contains information about the [FormInputStatus], value, as well /// as validation status. - T validatorOf(String key) { - return inputOf(key).validator as T; - } + T validatorOf(String key) => inputOf(key).validator as T; /// Updates validator of a given input. (perform copyWith) - void updateValidator(String key, FormInputValidator dirtyValue) { + void updateValidator( + String key, + FormInputValidator dirtyValue, + ) { if (contains(key)) { final index = _inputs.indexOf( inputOf(key), @@ -73,38 +72,31 @@ class FormData extends Equatable { /// Returns a validation error if the [FormInputValidator] is invalid. /// Returns null if the [FormInputValidator] is valid. - E? errorOf(String key) { - return (inputOf(key).validator as FormInputValidator).error; - } + E? errorOf(String key) => + (inputOf(key).validator as FormInputValidator).error; /// The value of the associated [FormInputValidator]. For example, /// if you have a FormInputValidator for FirstName, the value could be 'Joe'. - V valueOf(String key) { - return (inputOf(key).validator as FormInputValidator).value; - } + V valueOf(String key) => + (inputOf(key).validator as FormInputValidator).value; /// Returns `true` if the [FormInputValidator] is not valid. /// Same as `E? errorOf(String key) != null` - bool isNotValid(String key) { - return !inputOf(key).validator.valid; - } + bool isNotValid(String key) => !inputOf(key).validator.valid; /// Returns all associated metadata as a list - List> metadata() { - return _inputs - .map>( - (FormInput input) => input.metadata as FormInputMetadata, - ) - .toList(); - } + List> metadata() => _inputs + .map>( + (input) => input.metadata as FormInputMetadata, + ) + .toList(); /// Returns the metadata associated. With `M` the type of extra data. - FormInputMetadata metadataOf(String key) { - return inputOf(key).metadata as FormInputMetadata; - } + FormInputMetadata metadataOf(String key) => + inputOf(key).metadata as FormInputMetadata; /// Updates metadata of a given input. (perform copyWith) - void updateMetadata(String key, FormInputMetadata meta) { + void updateMetadata(String key, FormInputMetadata meta) { if (contains(key)) { final index = _inputs.indexOf( 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. - bool contains(String key) { - return _inputs.any((FormInput input) => input.key == key); - } + bool contains(String key) => _inputs.any((input) => input.key == key); /// Makes an intersection set operation and returns newly created [FormData] FormData intersection(FormData other) { @@ -173,11 +158,9 @@ class FormData extends Equatable { } /// Deeply copy this. - FormData clone() { - return FormData( - _inputs.map((FormInput input) => input.clone()).toList(), - ); - } + FormData clone() => FormData( + _inputs.map((input) => input.clone()).toList(), + ); /// Export this to [Map] format. Map toMap() { @@ -190,6 +173,9 @@ class FormData extends Equatable { return map; } + /// Export this to [String] format. + String toJson() => jsonEncode(toMap()); + @override bool? get stringify => true; diff --git a/packages/wyatt_form_bloc/lib/src/form/form_input.dart b/packages/wyatt_form_bloc/lib/src/form/form_input.dart index 58a2e506..6ab60d99 100644 --- a/packages/wyatt_form_bloc/lib/src/form/form_input.dart +++ b/packages/wyatt_form_bloc/lib/src/form/form_input.dart @@ -17,10 +17,11 @@ part of 'form.dart'; -class FormInput extends Equatable { +class FormInput> + extends Equatable { final String key; - final FormInputValidator validator; - final FormInputMetadata metadata; + final Validator validator; + final FormInputMetadata metadata; String get name => metadata._name ?? key; @@ -31,29 +32,25 @@ class FormInput extends Equatable { this.metadata = const FormInputMetadata(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 bool? get stringify => true; @override List 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, - ); - } } diff --git a/packages/wyatt_form_bloc/lib/src/form/form_input_metadata.dart b/packages/wyatt_form_bloc/lib/src/form/form_input_metadata.dart index e8921ef1..bed46368 100644 --- a/packages/wyatt_form_bloc/lib/src/form/form_input_metadata.dart +++ b/packages/wyatt_form_bloc/lib/src/form/form_input_metadata.dart @@ -17,7 +17,7 @@ part of 'form.dart'; -class FormInputMetadata extends Equatable { +class FormInputMetadata extends Equatable { final bool export; final String? _name; final T? extra; @@ -27,30 +27,27 @@ class FormInputMetadata extends Equatable { this.extra, String? name, }) : _name = name; - - @override - bool? get stringify => true; - - @override - List get props => [export, _name, extra]; FormInputMetadata copyWith({ bool? export, String? name, T? extra, - }) { - return FormInputMetadata( - export: export ?? this.export, - name: name ?? _name, - extra: extra ?? this.extra, - ); - } + }) => + FormInputMetadata( + export: export ?? this.export, + name: name ?? _name, + extra: extra ?? this.extra, + ); - FormInputMetadata clone() { - return copyWith( - export: export, - name: _name, - extra: extra, - ); - } + FormInputMetadata clone() => copyWith( + export: export, + name: _name, + extra: extra, + ); + + @override + bool? get stringify => true; + + @override + List get props => [export, _name, extra]; } diff --git a/packages/wyatt_form_bloc/lib/src/form/form_input_validator.dart b/packages/wyatt_form_bloc/lib/src/form/form_input_validator.dart index 3207762a..61f47cf3 100644 --- a/packages/wyatt_form_bloc/lib/src/form/form_input_validator.dart +++ b/packages/wyatt_form_bloc/lib/src/form/form_input_validator.dart @@ -16,7 +16,7 @@ part of 'form.dart'; -/// {@template form_input} +/// {@template form_input_validator} /// A [FormInputValidator] represents the value of a single form input field. /// It contains information about the [FormInputStatus], [value], as well /// as validation status. @@ -25,26 +25,28 @@ part of 'form.dart'; /// [FormInputValidator] instances. /// /// ```dart -/// enum FirstNameError { empty } -/// class FirstName extends FormInputValidator { -/// const FirstName.pure({String value = ''}) : super.pure(value); -/// const FirstName.dirty({String value = ''}) : super.dirty(value); +/// class Name extends FormInputValidator { +/// const Name.pure({String value = ''}) : super.pure(value); +/// const Name.dirty({String value = ''}) : super.dirty(value); /// /// @override -/// FirstNameError? validator(String value) { -/// return value.isEmpty ? FirstNameError.empty : null; +/// ValidationStandardError? validator(String? value) { +/// if (value?.isEmpty ?? false) { +/// return ValidationStandardError.empty +/// } +/// return value == null ? ValidationStandardError.invalid : null; /// } /// } /// ``` /// {@endtemplate} -@immutable -abstract class FormInputValidator extends Equatable { +abstract class FormInputValidator + extends Equatable { const FormInputValidator._(this.value, [this.pure = true]); /// Constructor which create a `pure` [FormInputValidator] with a given 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. const FormInputValidator.dirty(V value) : this._(value, false); diff --git a/packages/wyatt_form_bloc/lib/src/form/form_validator.dart b/packages/wyatt_form_bloc/lib/src/form/form_validator.dart new file mode 100644 index 00000000..15cdc16a --- /dev/null +++ b/packages/wyatt_form_bloc/lib/src/form/form_validator.dart @@ -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 . + +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() + .every((validator) => validator.pure); + + FormStatus validate(FormData form); +} diff --git a/packages/wyatt_form_bloc/lib/src/validators/form/every_input_validator.dart b/packages/wyatt_form_bloc/lib/src/validators/form/every_input_validator.dart new file mode 100644 index 00000000..dccc622c --- /dev/null +++ b/packages/wyatt_form_bloc/lib/src/validators/form/every_input_validator.dart @@ -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 . + +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() + .any((validator) => validator.valid == false)) { + return FormStatus.invalid; + } + + return FormStatus.valid; + } +} diff --git a/packages/wyatt_form_bloc/lib/src/validators/inputs/base/regex_validator.dart b/packages/wyatt_form_bloc/lib/src/validators/inputs/base/regex_validator.dart new file mode 100644 index 00000000..16d41ab2 --- /dev/null +++ b/packages/wyatt_form_bloc/lib/src/validators/inputs/base/regex_validator.dart @@ -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 . + +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 + extends FormInputValidator { + 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; + } +} diff --git a/packages/wyatt_form_bloc/lib/src/validators/inputs/base/text_validator.dart b/packages/wyatt_form_bloc/lib/src/validators/inputs/base/text_validator.dart new file mode 100644 index 00000000..c57697f1 --- /dev/null +++ b/packages/wyatt_form_bloc/lib/src/validators/inputs/base/text_validator.dart @@ -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 . + +import 'package:wyatt_form_bloc/src/enums/validation_error.dart'; +import 'package:wyatt_form_bloc/src/form/form.dart'; + +abstract class TextValidator + extends FormInputValidator { + 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; + } +} diff --git a/packages/wyatt_form_bloc/lib/src/validators/boolean.dart b/packages/wyatt_form_bloc/lib/src/validators/inputs/boolean.dart similarity index 85% rename from packages/wyatt_form_bloc/lib/src/validators/boolean.dart rename to packages/wyatt_form_bloc/lib/src/validators/inputs/boolean.dart index 30349320..e28f5ad1 100644 --- a/packages/wyatt_form_bloc/lib/src/validators/boolean.dart +++ b/packages/wyatt_form_bloc/lib/src/validators/inputs/boolean.dart @@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart'; /// {@template boolean} /// Form input for a bool input /// {@endtemplate} -class Boolean extends FormInputValidator { +class Boolean extends FormInputValidator { /// {@macro boolean} const Boolean.pure({bool? defaultValue = false}) : super.pure(defaultValue ?? false); @@ -29,7 +29,6 @@ class Boolean extends FormInputValidator { const Boolean.dirty({bool value = false}) : super.dirty(value); @override - ValidationError? validator(bool? value) { - return value != null ? null : ValidationError.invalid; - } + ValidationStandardError? validator(bool? value) => + value != null ? null : ValidationStandardError.invalid; } diff --git a/packages/wyatt_form_bloc/lib/src/validators/confirmed_password.dart b/packages/wyatt_form_bloc/lib/src/validators/inputs/confirmed_password.dart similarity index 87% rename from packages/wyatt_form_bloc/lib/src/validators/confirmed_password.dart rename to packages/wyatt_form_bloc/lib/src/validators/inputs/confirmed_password.dart index 158fef70..54899cf2 100644 --- a/packages/wyatt_form_bloc/lib/src/validators/confirmed_password.dart +++ b/packages/wyatt_form_bloc/lib/src/validators/inputs/confirmed_password.dart @@ -21,7 +21,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart'; /// Form input for a confirmed password input. /// {@endtemplate} class ConfirmedPassword - extends FormInputValidator { + extends FormInputValidator { /// {@macro confirmed_password} const ConfirmedPassword.pure({this.password = ''}) : super.pure(''); @@ -33,7 +33,6 @@ class ConfirmedPassword final String password; @override - ValidationError? validator(String? value) { - return password == value ? null : ValidationError.invalid; - } + ValidationStandardError? validator(String? value) => + password == value ? null : ValidationStandardError.invalid; } diff --git a/packages/wyatt_form_bloc/lib/src/validators/email.dart b/packages/wyatt_form_bloc/lib/src/validators/inputs/email.dart similarity index 64% rename from packages/wyatt_form_bloc/lib/src/validators/email.dart rename to packages/wyatt_form_bloc/lib/src/validators/inputs/email.dart index a8192ca6..4961c672 100644 --- a/packages/wyatt_form_bloc/lib/src/validators/email.dart +++ b/packages/wyatt_form_bloc/lib/src/validators/inputs/email.dart @@ -15,24 +15,25 @@ // along with this program. If not, see . 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} /// Form input for an email input. /// {@endtemplate} -class Email extends FormInputValidator { +class Email extends RegexValidator { /// {@macro email} - const Email.pure() : super.pure(''); + const Email.pure() : super.pure(); /// {@macro email} - const Email.dirty([String value = '']) : super.dirty(value); - - static final RegExp _emailRegExp = RegExp( - r'[^@ \t\r\n]+@[^@ \t\r\n]+\.[^@ \t\r\n]+', - ); + const Email.dirty([super.value = '']) : super.dirty(); @override - ValidationError? validator(String? value) { - return _emailRegExp.hasMatch(value ?? '') ? null : ValidationError.invalid; - } + ValidationStandardError get onEmpty => ValidationStandardError.empty; + @override + ValidationStandardError get onError => ValidationStandardError.invalid; + + @override + RegExp get regex => RegExp( + r'[^@ \t\r\n]+@[^@ \t\r\n]+\.[^@ \t\r\n]+', + ); } diff --git a/packages/wyatt_form_bloc/lib/src/validators/inputs/enum_validator.dart b/packages/wyatt_form_bloc/lib/src/validators/inputs/enum_validator.dart new file mode 100644 index 00000000..63c3ef60 --- /dev/null +++ b/packages/wyatt_form_bloc/lib/src/validators/inputs/enum_validator.dart @@ -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 . + +import 'package:wyatt_form_bloc/src/enums/validation_error.dart'; +import 'package:wyatt_form_bloc/src/validators/inputs/base/text_validator.dart'; + +class EnumValidator extends TextValidator { + /// {@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; +} diff --git a/packages/wyatt_form_bloc/lib/src/validators/iban.dart b/packages/wyatt_form_bloc/lib/src/validators/inputs/iban.dart similarity index 50% rename from packages/wyatt_form_bloc/lib/src/validators/iban.dart rename to packages/wyatt_form_bloc/lib/src/validators/inputs/iban.dart index 64b98665..74bef3e3 100644 --- a/packages/wyatt_form_bloc/lib/src/validators/iban.dart +++ b/packages/wyatt_form_bloc/lib/src/validators/inputs/iban.dart @@ -15,24 +15,25 @@ // along with this program. If not, see . 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} /// Form input for an IBAN input. /// {@endtemplate} -class Iban extends FormInputValidator { +class Iban extends RegexValidator { /// {@macro iban} - const Iban.pure() : super.pure(''); + const Iban.pure() : super.pure(); /// {@macro iban} - const Iban.dirty([String value = '']) : super.dirty(value); - - 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}))$', - ); + const Iban.dirty([super.value = '']) : super.dirty(); @override - ValidationError? validator(String? value) { - return _regExp.hasMatch(value ?? '') ? null : ValidationError.invalid; - } + ValidationStandardError get onEmpty => ValidationStandardError.empty; + @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}))$', + ); } diff --git a/packages/wyatt_form_bloc/lib/src/validators/list_option.dart b/packages/wyatt_form_bloc/lib/src/validators/inputs/list_option.dart similarity index 84% rename from packages/wyatt_form_bloc/lib/src/validators/list_option.dart rename to packages/wyatt_form_bloc/lib/src/validators/inputs/list_option.dart index d021223a..00ddd74b 100644 --- a/packages/wyatt_form_bloc/lib/src/validators/list_option.dart +++ b/packages/wyatt_form_bloc/lib/src/validators/inputs/list_option.dart @@ -20,7 +20,8 @@ import 'package:wyatt_form_bloc/src/form/form.dart'; /// {@template list_option} /// Form input for a list input /// {@endtemplate} -class ListOption extends FormInputValidator, ValidationError> { +class ListOption + extends FormInputValidator, ValidationStandardError> { /// {@macro list_option} const ListOption.pure({List? defaultValue}) : super.pure(defaultValue ?? const []); @@ -28,7 +29,7 @@ class ListOption extends FormInputValidator, ValidationError> { /// {@macro list_option} const ListOption.dirty({List? value}) : super.dirty(value ?? const []); - ListOption select(T? v) { + ListOption select(T? v) { if (v == null) { return this; } @@ -42,7 +43,8 @@ class ListOption extends FormInputValidator, ValidationError> { } @override - ValidationError? validator(List? value) { - return value?.isNotEmpty == true ? null : ValidationError.invalid; - } + ValidationStandardError? validator(List? value) => + value?.isNotEmpty ?? false == true + ? null + : ValidationStandardError.invalid; } diff --git a/packages/wyatt_form_bloc/lib/src/validators/name.dart b/packages/wyatt_form_bloc/lib/src/validators/inputs/name.dart similarity index 65% rename from packages/wyatt_form_bloc/lib/src/validators/name.dart rename to packages/wyatt_form_bloc/lib/src/validators/inputs/name.dart index 860a3b48..cf3b6558 100644 --- a/packages/wyatt_form_bloc/lib/src/validators/name.dart +++ b/packages/wyatt_form_bloc/lib/src/validators/inputs/name.dart @@ -15,22 +15,23 @@ // along with this program. If not, see . 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} /// Form input for a name input. /// {@endtemplate} -class Name extends FormInputValidator { +class Name extends RegexValidator { /// {@macro name} - const Name.pure() : super.pure(''); + const Name.pure() : super.pure(); /// {@macro name} - const Name.dirty([String value = '']) : super.dirty(value); - - static final RegExp _nameRegExp = RegExp(r"^([ \u00c0-\u01ffa-zA-Z'\-])+$"); + const Name.dirty([super.value = '']) : super.dirty(); @override - ValidationError? validator(String? value) { - return _nameRegExp.hasMatch(value ?? '') ? null : ValidationError.invalid; - } + ValidationStandardError get onEmpty => ValidationStandardError.empty; + @override + ValidationStandardError get onError => ValidationStandardError.invalid; + + @override + RegExp get regex => RegExp(r"^([ \u00c0-\u01ffa-zA-Z'\-])+$"); } diff --git a/packages/wyatt_form_bloc/lib/src/validators/password.dart b/packages/wyatt_form_bloc/lib/src/validators/inputs/password.dart similarity index 65% rename from packages/wyatt_form_bloc/lib/src/validators/password.dart rename to packages/wyatt_form_bloc/lib/src/validators/inputs/password.dart index b9ee5fe1..8a6beab0 100644 --- a/packages/wyatt_form_bloc/lib/src/validators/password.dart +++ b/packages/wyatt_form_bloc/lib/src/validators/inputs/password.dart @@ -15,25 +15,23 @@ // along with this program. If not, see . 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} /// Form input for a password input. /// {@endtemplate} -class Password extends FormInputValidator { +class Password extends RegexValidator { /// {@macro password} - const Password.pure() : super.pure(''); + const Password.pure() : super.pure(); /// {@macro password} - const Password.dirty([String value = '']) : super.dirty(value); - - static final RegExp _passwordRegExp = - RegExp(r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$'); + const Password.dirty([super.value = '']) : super.dirty(); @override - ValidationError? validator(String? value) { - return _passwordRegExp.hasMatch(value ?? '') - ? null - : ValidationError.invalid; - } + ValidationStandardError get onEmpty => ValidationStandardError.empty; + @override + ValidationStandardError get onError => ValidationStandardError.invalid; + + @override + RegExp get regex => RegExp(r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$'); } diff --git a/packages/wyatt_form_bloc/lib/src/validators/phone.dart b/packages/wyatt_form_bloc/lib/src/validators/inputs/phone.dart similarity index 69% rename from packages/wyatt_form_bloc/lib/src/validators/phone.dart rename to packages/wyatt_form_bloc/lib/src/validators/inputs/phone.dart index 8a63cc2e..9f4dd1af 100644 --- a/packages/wyatt_form_bloc/lib/src/validators/phone.dart +++ b/packages/wyatt_form_bloc/lib/src/validators/inputs/phone.dart @@ -15,23 +15,24 @@ // along with this program. If not, see . 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} /// Form input for a phone input. /// {@endtemplate} -class Phone extends FormInputValidator { +class Phone extends RegexValidator { /// {@macro phone} - const Phone.pure() : super.pure(''); + const Phone.pure() : super.pure(); /// {@macro phone} - const Phone.dirty([String value = '']) : super.dirty(value); - - static final RegExp _phoneRegExp = - RegExp(r'^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$'); + const Phone.dirty([super.value = '']) : super.dirty(); @override - ValidationError? validator(String? value) { - return _phoneRegExp.hasMatch(value ?? '') ? null : ValidationError.invalid; - } + ValidationStandardError get onEmpty => ValidationStandardError.empty; + @override + ValidationStandardError get onError => ValidationStandardError.invalid; + + @override + RegExp get regex => + RegExp(r'^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$'); } diff --git a/packages/wyatt_form_bloc/lib/src/validators/siren.dart b/packages/wyatt_form_bloc/lib/src/validators/inputs/siren.dart similarity index 65% rename from packages/wyatt_form_bloc/lib/src/validators/siren.dart rename to packages/wyatt_form_bloc/lib/src/validators/inputs/siren.dart index 811fb4bd..798c33f4 100644 --- a/packages/wyatt_form_bloc/lib/src/validators/siren.dart +++ b/packages/wyatt_form_bloc/lib/src/validators/inputs/siren.dart @@ -15,22 +15,23 @@ // along with this program. If not, see . 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} /// Form input for a SIREN input. /// {@endtemplate} -class Siren extends FormInputValidator { +class Siren extends RegexValidator { /// {@macro siren} - const Siren.pure() : super.pure(''); + const Siren.pure() : super.pure(); /// {@macro siren} - const Siren.dirty([String value = '']) : super.dirty(value); - - static final RegExp _regExp = RegExp(r'(\d{9}|\d{3}[ ]\d{3}[ ]\d{3})$'); + const Siren.dirty([super.value = '']) : super.dirty(); @override - ValidationError? validator(String? value) { - return _regExp.hasMatch(value ?? '') ? null : ValidationError.invalid; - } + ValidationStandardError get onEmpty => ValidationStandardError.empty; + @override + ValidationStandardError get onError => ValidationStandardError.invalid; + + @override + RegExp get regex => RegExp(r'(\d{9}|\d{3}[ ]\d{3}[ ]\d{3})$'); } diff --git a/packages/wyatt_form_bloc/lib/src/validators/text_string.dart b/packages/wyatt_form_bloc/lib/src/validators/inputs/text_string.dart similarity index 69% rename from packages/wyatt_form_bloc/lib/src/validators/text_string.dart rename to packages/wyatt_form_bloc/lib/src/validators/inputs/text_string.dart index 230f69e4..61d0def3 100644 --- a/packages/wyatt_form_bloc/lib/src/validators/text_string.dart +++ b/packages/wyatt_form_bloc/lib/src/validators/inputs/text_string.dart @@ -15,20 +15,21 @@ // along with this program. If not, see . 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} /// Form input for a text input /// {@endtemplate} -class TextString extends FormInputValidator { +class TextString extends TextValidator { /// {@macro text_string} - const TextString.pure() : super.pure(''); + const TextString.pure() : super.pure(); /// {@macro text_string} - const TextString.dirty([String value = '']) : super.dirty(value); + const TextString.dirty([super.value = '']) : super.dirty(); @override - ValidationError? validator(String? value) { - return (value?.isNotEmpty ?? false) ? null : ValidationError.invalid; - } + ValidationStandardError get onEmpty => ValidationStandardError.empty; + + @override + ValidationStandardError get onNull => ValidationStandardError.invalid; } diff --git a/packages/wyatt_form_bloc/lib/src/validators/validators.dart b/packages/wyatt_form_bloc/lib/src/validators/validators.dart index 170a7993..ae73b127 100644 --- a/packages/wyatt_form_bloc/lib/src/validators/validators.dart +++ b/packages/wyatt_form_bloc/lib/src/validators/validators.dart @@ -14,13 +14,16 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -export 'boolean.dart'; -export 'confirmed_password.dart'; -export 'email.dart'; -export 'iban.dart'; -export 'list_option.dart'; -export 'name.dart'; -export 'password.dart'; -export 'phone.dart'; -export 'siren.dart'; -export 'text_string.dart'; +export 'inputs/base/regex_validator.dart'; +export 'inputs/base/text_validator.dart'; +export 'inputs/boolean.dart'; +export 'inputs/confirmed_password.dart'; +export 'inputs/email.dart'; +export 'inputs/enum_validator.dart'; +export 'inputs/iban.dart'; +export 'inputs/list_option.dart'; +export 'inputs/name.dart'; +export 'inputs/password.dart'; +export 'inputs/phone.dart'; +export 'inputs/siren.dart'; +export 'inputs/text_string.dart'; diff --git a/packages/wyatt_form_bloc/pubspec.yaml b/packages/wyatt_form_bloc/pubspec.yaml index 6fd29c0b..6fbe876a 100644 --- a/packages/wyatt_form_bloc/pubspec.yaml +++ b/packages/wyatt_form_bloc/pubspec.yaml @@ -9,7 +9,6 @@ environment: dependencies: bloc: ^8.0.3 equatable: ^2.0.3 - meta: ^1.7.0 dev_dependencies: test: ^1.21.4