From d0f1d07363a503f92e3674c30c0b92ecf7f8db72 Mon Sep 17 00:00:00 2001 From: Hugo Pointcheval Date: Sun, 10 Jul 2022 21:13:13 +0200 Subject: [PATCH] feat: add metadata on inputs --- .../lib/src/cubit/form_data_cubit.dart | 15 +- .../lib/src/cubit/form_data_state.dart | 23 +- .../lib/src/enums/form_status.dart | 32 ++- .../wyatt_form_bloc/lib/src/form/form.dart | 18 +- .../lib/src/form/form_data.dart | 231 ++++++++++-------- .../lib/src/form/form_entry.dart | 79 ------ .../lib/src/form/form_input.dart | 118 +++------ .../lib/src/form/form_input_metadata.dart | 56 +++++ .../lib/src/form/form_input_validator.dart | 104 ++++++++ .../lib/src/utils/list_equals.dart | 25 -- .../lib/src/validators/boolean.dart | 2 +- .../src/validators/confirmed_password.dart | 2 +- .../lib/src/validators/email.dart | 4 +- .../lib/src/validators/iban.dart | 2 +- .../lib/src/validators/list_option.dart | 2 +- .../lib/src/validators/name.dart | 2 +- .../lib/src/validators/password.dart | 2 +- .../lib/src/validators/phone.dart | 2 +- .../lib/src/validators/siren.dart | 2 +- .../lib/src/validators/text_string.dart | 2 +- 20 files changed, 379 insertions(+), 344 deletions(-) delete mode 100644 packages/wyatt_form_bloc/lib/src/form/form_entry.dart create mode 100644 packages/wyatt_form_bloc/lib/src/form/form_input_metadata.dart create mode 100644 packages/wyatt_form_bloc/lib/src/form/form_input_validator.dart delete mode 100644 packages/wyatt_form_bloc/lib/src/utils/list_equals.dart 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 728c239d..1be75738 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 @@ -17,6 +17,7 @@ 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'; @@ -24,19 +25,19 @@ import 'package:wyatt_form_bloc/src/form/form.dart'; part 'form_data_state.dart'; abstract class FormDataCubit extends Cubit { - FormDataCubit({required FormData entries}) - : super(FormDataState(data: entries)); + FormDataCubit({required FormData inputs}) + : 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. - void dataChanged(String field, FormInput dirtyValue) { + /// - `dirtyValue`: The new value of the field. (Wrapped in a dirty validator) + void dataChanged(String field, FormInputValidator dirtyValue) { final _form = state.data.clone(); if (_form.contains(field)) { - _form.update(field, dirtyValue); + _form.updateValidator(field, dirtyValue); } else { throw Exception('Form field $field not found'); } @@ -44,7 +45,7 @@ abstract class FormDataCubit extends Cubit { emit( state.copyWith( data: _form, - status: _form.selfValidate(), + status: _form.validate(), ), ); } @@ -78,7 +79,7 @@ abstract class FormDataCubit extends Cubit { emit( state.copyWith( data: _form, - status: _form.selfValidate(), + status: _form.validate(), ), ); } 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 d8f5ec82..2d03e217 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 @@ -17,7 +17,7 @@ part of 'form_data_cubit.dart'; @immutable -class FormDataState { +class FormDataState extends Equatable { final FormStatus status; final FormData data; final String? errorMessage; @@ -41,23 +41,8 @@ class FormDataState { } @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is FormDataState && - other.status == status && - other.data == data && - other.errorMessage == errorMessage; - } - + bool? get stringify => true; + @override - int get hashCode { - return status.hashCode ^ data.hashCode ^ errorMessage.hashCode; - } - - @override - String toString() { - return 'FormDataState(status: $status, data: $data, ' - 'errorMessage: $errorMessage)'; - } + List get props => [status, data, errorMessage]; } 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 8e0709be..b5701ef6 100644 --- a/packages/wyatt_form_bloc/lib/src/enums/form_status.dart +++ b/packages/wyatt_form_bloc/lib/src/enums/form_status.dart @@ -14,6 +14,16 @@ // 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, + FormStatus.submissionSuccess, + FormStatus.submissionFailure, + FormStatus.submissionCanceled, +}; + /// Enum representing the status of a form at any given point in time. enum FormStatus { /// The form has not been touched. @@ -35,19 +45,8 @@ enum FormStatus { submissionFailure, /// The form submission has been canceled. - submissionCanceled -} + submissionCanceled; -const Set _validatedFormStatuses = { - FormStatus.valid, - FormStatus.submissionInProgress, - FormStatus.submissionSuccess, - FormStatus.submissionFailure, - FormStatus.submissionCanceled, -}; - -/// Useful extensions on [FormStatus] -extension FormStatusX on FormStatus { /// Indicates whether the form is untouched. bool get isPure => this == FormStatus.pure; @@ -76,4 +75,13 @@ extension FormStatusX on FormStatus { /// Indicates whether the form submission has been canceled. bool get isSubmissionCanceled => this == FormStatus.submissionCanceled; + + /// Validate a list of inputs + static FormStatus validate(List inputs) { + return inputs.every((FormInput input) => input.validator.pure) + ? FormStatus.pure + : inputs.any((FormInput input) => input.validator.valid == false) + ? FormStatus.invalid + : FormStatus.valid; + } } diff --git a/packages/wyatt_form_bloc/lib/src/form/form.dart b/packages/wyatt_form_bloc/lib/src/form/form.dart index a7ab3277..2a131d1b 100644 --- a/packages/wyatt_form_bloc/lib/src/form/form.dart +++ b/packages/wyatt_form_bloc/lib/src/form/form.dart @@ -1,19 +1,25 @@ // 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 . -export 'form_data.dart'; -export 'form_entry.dart'; -export 'form_input.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_status.dart'; + +part 'form_input.dart'; +part 'form_input_metadata.dart'; +part 'form_input_validator.dart'; +part 'form_data.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 6bad8487..8c978cf9 100644 --- a/packages/wyatt_form_bloc/lib/src/form/form_data.dart +++ b/packages/wyatt_form_bloc/lib/src/form/form_data.dart @@ -14,133 +14,164 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -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/utils/list_equals.dart'; +part of 'form.dart'; @immutable -class FormData { - const FormData(this._entries); +class FormData extends Equatable { + final List _inputs; - FormData.empty() : this([]); + const FormData(this._inputs); + const FormData.empty() : this(const []); - final List _entries; - - List get entries => _entries; - - List> inputs() { - return _entries - .map((FormEntry entry) => entry.input as FormInput) - .toList(); - } - - static FormStatus validate(List inputs) { - return inputs.every((FormInput element) => element.pure) - ? FormStatus.pure - : inputs.any((FormInput input) => input.valid == false) - ? FormStatus.invalid - : FormStatus.valid; - } - - FormStatus selfValidate() { - return validate(inputs()); - } - - FormInput input(String key) { + /// Returns the input for the associated key + FormInput inputByKey(String key) { if (contains(key)) { - return _entries.firstWhere((FormEntry entry) => entry.key == key).input - as FormInput; + return _inputs.firstWhere((FormInput input) => input.key == key); } else { throw Exception('FormInput with key `$key` does not exist in form'); } } - bool contains(String key) { - return _entries.any((FormEntry entry) => entry.key == key); - } - - FormData intersection(FormData other) { - final List entries = []; - - for (final FormEntry entry in _entries) { - if (other.contains(entry.key)) { - entries.add(entry); - } - } - - return FormData(entries); - } - - FormData difference(FormData other) { - final List entries = []; - - for (final FormEntry otherEntry in other._entries) { - if (!contains(otherEntry.key)) { - entries.add(otherEntry); - } - } - - for (final FormEntry entry in _entries) { - if (!other.contains(entry.key)) { - entries.add(entry); - } - } - - return FormData(entries); - } - - FormData union(FormData other) { - final List entries = []; - - for (final FormEntry entry in _entries) { - entries.add(entry); - } - - for (final FormEntry otherEntry in other._entries) { - if (!contains(otherEntry.key)) { - entries.add(otherEntry); - } - } - - return FormData(entries); - } - - void update(String key, FormInput input) { + /// Updates a input (perform a replace at index). + void updateInput(String key, FormInput input) { if (contains(key)) { - final index = _entries.indexOf( - _entries.firstWhere((FormEntry entry) => entry.key == key), + final index = _inputs.indexOf( + inputByKey(key), ); - _entries[index] = _entries[index].copyWith(input: input); + _inputs[index] = input; } } + /// Updates validator of a given input. (perform copyWith) + void updateValidator(String key, FormInputValidator dirtyValue) { + if (contains(key)) { + final index = _inputs.indexOf( + inputByKey(key), + ); + _inputs[index] = _inputs[index].copyWith(validator: dirtyValue); + } + } + + /// Updates metadata of a given input. (perform copyWith) + void updateMetadata(String key, FormInputMetadata meta) { + if (contains(key)) { + final index = _inputs.indexOf( + inputByKey(key), + ); + _inputs[index] = _inputs[index].copyWith(metadata: meta); + } + } + + /// 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 inputByKey(key).validator as T; + } + + /// Returns a validation error if the [FormInputValidator] is invalid. + /// Returns null if the [FormInputValidator] is valid. + E? errorOf(String key) { + return (inputByKey(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 (inputByKey(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 !inputByKey(key).validator.valid; + } + + /// Returns the metadata associated. With `M` the type of extra data. + FormInputMetadata metadataOf(String key) { + return inputByKey(key).metadata as FormInputMetadata; + } + + /// Validate self inputs. + FormStatus validate() { + return FormStatus.validate(_inputs); + } + + /// Check if this contains an input with the given key. + bool contains(String key) { + return _inputs.any((FormInput input) => input.key == key); + } + + /// Makes an intersection set operation and returns newly created [FormData] + FormData intersection(FormData other) { + final List inputs = []; + + for (final FormInput i in _inputs) { + if (other.contains(i.key)) { + inputs.add(i); + } + } + + return FormData(inputs); + } + + /// Makes a difference set operation and returns newly created [FormData] + FormData difference(FormData other) { + final List inputs = []; + + for (final FormInput i in other._inputs) { + if (!contains(i.key)) { + inputs.add(i); + } + } + + for (final FormInput i in _inputs) { + if (!other.contains(i.key)) { + inputs.add(i); + } + } + + return FormData(inputs); + } + + /// Makes an union set operation and returns newly created [FormData] + FormData union(FormData other) { + final List inputs = []; + + for (final FormInput i in _inputs) { + inputs.add(i); + } + + for (final FormInput i in other._inputs) { + if (!contains(i.key)) { + inputs.add(i); + } + } + + return FormData(inputs); + } + + /// Deeply copy this. FormData clone() { return FormData( - _entries.map((FormEntry entry) => entry.clone()).toList(), + _inputs.map((FormInput input) => input.clone()).toList(), ); } + /// Export this to [Map] format. Map toMap() { final map = {}; - for (final entry in _entries) { - if (entry.export) { - map[entry.name] = entry.input.value; + for (final input in _inputs) { + if (input.metadata.export) { + map[input.name] = input.validator.value; } } return map; } @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is FormData && listEquals(other._entries, _entries); - } - + bool? get stringify => true; + @override - int get hashCode => _entries.hashCode; - - @override - String toString() => 'FormData(entries: $_entries)'; + List get props => _inputs; } diff --git a/packages/wyatt_form_bloc/lib/src/form/form_entry.dart b/packages/wyatt_form_bloc/lib/src/form/form_entry.dart deleted file mode 100644 index 017da7c6..00000000 --- a/packages/wyatt_form_bloc/lib/src/form/form_entry.dart +++ /dev/null @@ -1,79 +0,0 @@ -// 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:meta/meta.dart'; -import 'package:wyatt_form_bloc/src/form/form.dart'; - -@immutable -class FormEntry { - const FormEntry(this.key, this.input, {this.export = true, String? name}) - : _field = name ?? key; - - final String key; - final FormInput input; - final bool export; - final String _field; - - String get name => _field; - - FormEntry copyWith({ - String? key, - FormInput? input, - bool? export, - String? name, - }) { - return FormEntry( - key ?? this.key, - input ?? this.input, - export: export ?? this.export, - name: name, - ); - } - - FormEntry clone() { - return FormEntry( - key, - input, - export: export, - name: name, - ); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is FormEntry && - other.key == key && - other.input == input && - other.export == export && - other.name == name; - } - - @override - int get hashCode { - return key.hashCode ^ - input.hashCode ^ - export.hashCode ^ - name.hashCode; - } - - @override - String toString() { - return 'FormEntry(key: $key, input: $input, ' - 'export: $export, name: $name)'; - } -} 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 d438e4ab..58a2e506 100644 --- a/packages/wyatt_form_bloc/lib/src/form/form_input.dart +++ b/packages/wyatt_form_bloc/lib/src/form/form_input.dart @@ -1,3 +1,4 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first // Copyright (C) 2022 WYATT GROUP // Please see the AUTHORS file for details. // @@ -14,98 +15,45 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -import 'package:meta/meta.dart'; -import 'package:wyatt_form_bloc/src/enums/enums.dart'; +part of 'form.dart'; -/// {@template form_input} -/// A [FormInput] represents the value of a single form input field. -/// It contains information about the [FormInputStatus], [value], as well -/// as validation status. -/// -/// [FormInput] should be extended to define custom [FormInput] instances. -/// -/// ```dart -/// enum FirstNameError { empty } -/// class FirstName extends FormInput { -/// const FirstName.pure({String value = ''}) : super.pure(value); -/// const FirstName.dirty({String value = ''}) : super.dirty(value); -/// -/// @override -/// FirstNameError? validator(String value) { -/// return value.isEmpty ? FirstNameError.empty : null; -/// } -/// } -/// ``` -/// {@endtemplate} -@immutable -abstract class FormInput { - const FormInput._(this.value, [this.pure = true]); +class FormInput extends Equatable { + final String key; + final FormInputValidator validator; + final FormInputMetadata metadata; - /// Constructor which create a `pure` [FormInput] with a given value. - const FormInput.pure(T value) : this._(value); + String get name => metadata._name ?? key; - /// Constructor which create a `dirty` [FormInput] with a given value. - const FormInput.dirty(T value) : this._(value, false); - - /// The value of the given [FormInput]. - /// For example, if you have a `FormInput` for `FirstName`, - /// the value could be 'Joe'. - final T value; - - /// If the [FormInput] is pure (has been touched/modified). - /// Typically when the `FormInput` is initially created, - /// it is created using the `FormInput.pure` constructor to - /// signify that the user has not modified it. - /// - /// For subsequent changes (in response to user input), the - /// `FormInput.dirty` constructor should be used to signify that - /// the `FormInput` has been manipulated. - final bool pure; - - /// The [FormInputStatus] which can be one of the following: - /// * [FormInputStatus.pure] - /// - if the input has not been modified. - /// * [FormInputStatus.invalid] - /// - if the input has been modified and validation failed. - /// * [FormInputStatus.valid] - /// - if the input has been modified and validation succeeded. - FormInputStatus get status => pure - ? FormInputStatus.pure - : valid - ? FormInputStatus.valid - : FormInputStatus.invalid; - - /// Returns a validation error if the [FormInput] is invalid. - /// Returns `null` if the [FormInput] is valid. - E? get error => validator(value); - - /// Whether the [FormInput] value is valid according to the - /// overridden `validator`. - /// - /// Returns `true` if `validator` returns `null` for the - /// current [FormInput] value and `false` otherwise. - bool get valid => validator(value) == null; - - /// Whether the [FormInput] value is not valid. - /// A value is invalid when the overridden `validator` - /// returns an error (non-null value). - bool get invalid => status == FormInputStatus.invalid; - - /// A function that must return a validation error if the provided - /// [value] is invalid and `null` otherwise. - E? validator(T value); + const FormInput( + this.key, + this.validator, { + // ignore: avoid_redundant_argument_values + this.metadata = const FormInputMetadata(export: true), + }); @override - int get hashCode => value.hashCode ^ pure.hashCode; + bool? get stringify => true; @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) return false; - return other is FormInput && - other.value == value && - other.pure == pure; + 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, + ); } - @override - String toString() => 'FormInput<$runtimeType>(value: $value, pure: $pure)'; + 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 new file mode 100644 index 00000000..e8921ef1 --- /dev/null +++ b/packages/wyatt_form_bloc/lib/src/form/form_input_metadata.dart @@ -0,0 +1,56 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +// 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'; + +class FormInputMetadata extends Equatable { + final bool export; + final String? _name; + final T? extra; + + const FormInputMetadata({ + this.export = true, + 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 clone() { + return copyWith( + export: export, + name: _name, + extra: 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 new file mode 100644 index 00000000..3207762a --- /dev/null +++ b/packages/wyatt_form_bloc/lib/src/form/form_input_validator.dart @@ -0,0 +1,104 @@ +// 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_input} +/// A [FormInputValidator] represents the value of a single form input field. +/// It contains information about the [FormInputStatus], [value], as well +/// as validation status. +/// +/// [FormInputValidator] should be extended to define custom +/// [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); +/// +/// @override +/// FirstNameError? validator(String value) { +/// return value.isEmpty ? FirstNameError.empty : null; +/// } +/// } +/// ``` +/// {@endtemplate} +@immutable +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 + /// given value. + const FormInputValidator.dirty(V value) : this._(value, false); + + /// The value of the given [FormInputValidator]. + /// For example, if you have a `FormInputValidator` for `FirstName`, + /// the value could be 'Joe'. + final V value; + + /// If the [FormInputValidator] is pure (has been touched/modified). + /// Typically when the `FormInputValidator` is initially created, + /// it is created using the `FormInputValidator.pure` constructor to + /// signify that the user has not modified it. + /// + /// For subsequent changes (in response to user input), the + /// `FormInputValidator.dirty` constructor should be used to signify that + /// the `FormInputValidator` has been manipulated. + final bool pure; + + /// The [FormInputStatus] which can be one of the following: + /// * [FormInputStatus.pure] + /// - if the input has not been modified. + /// * [FormInputStatus.invalid] + /// - if the input has been modified and validation failed. + /// * [FormInputStatus.valid] + /// - if the input has been modified and validation succeeded. + FormInputStatus get status => pure + ? FormInputStatus.pure + : valid + ? FormInputStatus.valid + : FormInputStatus.invalid; + + /// Returns a validation error if the [FormInputValidator] is invalid. + /// Returns `null` if the [FormInputValidator] is valid. + E? get error => validator(value); + + /// Whether the [FormInputValidator] value is valid according to the + /// overridden `validator`. + /// + /// Returns `true` if `validator` returns `null` for the + /// current [FormInputValidator] value and `false` otherwise. + bool get valid => validator(value) == null; + + /// Whether the [FormInputValidator] value is not valid. + /// A value is invalid when the overridden `validator` + /// returns an error (non-null value). + bool get invalid => status == FormInputStatus.invalid; + + /// A function that must return a validation error if the provided + /// [value] is invalid and `null` otherwise. + E? validator(V value); + + @override + bool? get stringify => true; + + @override + List get props => [value, pure]; +} diff --git a/packages/wyatt_form_bloc/lib/src/utils/list_equals.dart b/packages/wyatt_form_bloc/lib/src/utils/list_equals.dart deleted file mode 100644 index e7d1989c..00000000 --- a/packages/wyatt_form_bloc/lib/src/utils/list_equals.dart +++ /dev/null @@ -1,25 +0,0 @@ -// 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 . - -bool listEquals(List? a, List? b) { - if (a == null) return b == null; - if (b == null || a.length != b.length) return false; - if (identical(a, b)) return true; - for (int index = 0; index < a.length; index += 1) { - if (a[index] != b[index]) return false; - } - return true; -} diff --git a/packages/wyatt_form_bloc/lib/src/validators/boolean.dart b/packages/wyatt_form_bloc/lib/src/validators/boolean.dart index db03ffe6..30349320 100644 --- a/packages/wyatt_form_bloc/lib/src/validators/boolean.dart +++ b/packages/wyatt_form_bloc/lib/src/validators/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 FormInput { +class Boolean extends FormInputValidator { /// {@macro boolean} const Boolean.pure({bool? defaultValue = false}) : super.pure(defaultValue ?? false); diff --git a/packages/wyatt_form_bloc/lib/src/validators/confirmed_password.dart b/packages/wyatt_form_bloc/lib/src/validators/confirmed_password.dart index 3637a5ee..158fef70 100644 --- a/packages/wyatt_form_bloc/lib/src/validators/confirmed_password.dart +++ b/packages/wyatt_form_bloc/lib/src/validators/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 FormInput { + extends FormInputValidator { /// {@macro confirmed_password} const ConfirmedPassword.pure({this.password = ''}) : super.pure(''); diff --git a/packages/wyatt_form_bloc/lib/src/validators/email.dart b/packages/wyatt_form_bloc/lib/src/validators/email.dart index 9de7c449..a8192ca6 100644 --- a/packages/wyatt_form_bloc/lib/src/validators/email.dart +++ b/packages/wyatt_form_bloc/lib/src/validators/email.dart @@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart'; /// {@template email} /// Form input for an email input. /// {@endtemplate} -class Email extends FormInput { +class Email extends FormInputValidator { /// {@macro email} const Email.pure() : super.pure(''); @@ -28,7 +28,7 @@ class Email extends FormInput { const Email.dirty([String value = '']) : super.dirty(value); static final RegExp _emailRegExp = RegExp( - r'^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$', + r'[^@ \t\r\n]+@[^@ \t\r\n]+\.[^@ \t\r\n]+', ); @override diff --git a/packages/wyatt_form_bloc/lib/src/validators/iban.dart b/packages/wyatt_form_bloc/lib/src/validators/iban.dart index 301c1bcc..64b98665 100644 --- a/packages/wyatt_form_bloc/lib/src/validators/iban.dart +++ b/packages/wyatt_form_bloc/lib/src/validators/iban.dart @@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart'; /// {@template iban} /// Form input for an IBAN input. /// {@endtemplate} -class Iban extends FormInput { +class Iban extends FormInputValidator { /// {@macro iban} const Iban.pure() : super.pure(''); diff --git a/packages/wyatt_form_bloc/lib/src/validators/list_option.dart b/packages/wyatt_form_bloc/lib/src/validators/list_option.dart index 13c7dc5c..d021223a 100644 --- a/packages/wyatt_form_bloc/lib/src/validators/list_option.dart +++ b/packages/wyatt_form_bloc/lib/src/validators/list_option.dart @@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart'; /// {@template list_option} /// Form input for a list input /// {@endtemplate} -class ListOption extends FormInput, ValidationError> { +class ListOption extends FormInputValidator, ValidationError> { /// {@macro list_option} const ListOption.pure({List? defaultValue}) : super.pure(defaultValue ?? const []); diff --git a/packages/wyatt_form_bloc/lib/src/validators/name.dart b/packages/wyatt_form_bloc/lib/src/validators/name.dart index 952c6353..860a3b48 100644 --- a/packages/wyatt_form_bloc/lib/src/validators/name.dart +++ b/packages/wyatt_form_bloc/lib/src/validators/name.dart @@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart'; /// {@template name} /// Form input for a name input. /// {@endtemplate} -class Name extends FormInput { +class Name extends FormInputValidator { /// {@macro name} const Name.pure() : super.pure(''); diff --git a/packages/wyatt_form_bloc/lib/src/validators/password.dart b/packages/wyatt_form_bloc/lib/src/validators/password.dart index f897c9f6..b9ee5fe1 100644 --- a/packages/wyatt_form_bloc/lib/src/validators/password.dart +++ b/packages/wyatt_form_bloc/lib/src/validators/password.dart @@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart'; /// {@template password} /// Form input for a password input. /// {@endtemplate} -class Password extends FormInput { +class Password extends FormInputValidator { /// {@macro password} const Password.pure() : super.pure(''); diff --git a/packages/wyatt_form_bloc/lib/src/validators/phone.dart b/packages/wyatt_form_bloc/lib/src/validators/phone.dart index ba009760..8a63cc2e 100644 --- a/packages/wyatt_form_bloc/lib/src/validators/phone.dart +++ b/packages/wyatt_form_bloc/lib/src/validators/phone.dart @@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart'; /// {@template phone} /// Form input for a phone input. /// {@endtemplate} -class Phone extends FormInput { +class Phone extends FormInputValidator { /// {@macro phone} const Phone.pure() : super.pure(''); diff --git a/packages/wyatt_form_bloc/lib/src/validators/siren.dart b/packages/wyatt_form_bloc/lib/src/validators/siren.dart index e0cea358..811fb4bd 100644 --- a/packages/wyatt_form_bloc/lib/src/validators/siren.dart +++ b/packages/wyatt_form_bloc/lib/src/validators/siren.dart @@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart'; /// {@template siren} /// Form input for a SIREN input. /// {@endtemplate} -class Siren extends FormInput { +class Siren extends FormInputValidator { /// {@macro siren} const Siren.pure() : super.pure(''); diff --git a/packages/wyatt_form_bloc/lib/src/validators/text_string.dart b/packages/wyatt_form_bloc/lib/src/validators/text_string.dart index 89ce6427..230f69e4 100644 --- a/packages/wyatt_form_bloc/lib/src/validators/text_string.dart +++ b/packages/wyatt_form_bloc/lib/src/validators/text_string.dart @@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart'; /// {@template text_string} /// Form input for a text input /// {@endtemplate} -class TextString extends FormInput { +class TextString extends FormInputValidator { /// {@macro text_string} const TextString.pure() : super.pure('');