form/feature/add_metadata #10
| @ -30,21 +30,22 @@ Form Bloc for Dart & Flutter. | |||||||
| ## Features | ## Features | ||||||
| 
 | 
 | ||||||
| - Form | - Form | ||||||
|     * FormInput: *atom of a form* |     * FormInputValidator: | ||||||
|         - Store data |         - Store data | ||||||
|         - Validate this data |         - Validate this data | ||||||
|     * FormEntry: *shell of this atom* |     * FormInputMetadata: | ||||||
|         - Associate a key to an input |         - Store infos and options about an input. | ||||||
|         - Configure form attribute (exportation, name...) |     * FormInput: | ||||||
|     * FormData: *collection of entries* |         - Associaite a key, to a validator and metadata. | ||||||
|         - Contain all entries |     * FormData: *collection of inputs* | ||||||
|  |         - Contain all inputs | ||||||
|         - Basic set operation |         - Basic set operation | ||||||
| 
 | 
 | ||||||
| - FormDataCubit | - FormDataCubit | ||||||
|     * Data management behind a form. |     * Data management behind a form. | ||||||
|         - Use entries to pass a FormData object |         - Use entries to pass a FormData object | ||||||
|         - You can use several pre configured FormInput for validation |         - You can use several pre configured `FormInputValidator` for validation | ||||||
|         - You can use updateFormData() to change FormData and validators during runtime (intersection, union, difference or replace) |         - You can use updateFormData() to change `FormData` during runtime (intersection, union, difference or replace) | ||||||
| 
 | 
 | ||||||
| - Consistent | - Consistent | ||||||
|     * Every class have same naming convention |     * Every class have same naming convention | ||||||
| @ -59,4 +60,120 @@ import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; | |||||||
| 
 | 
 | ||||||
| ## Usage | ## Usage | ||||||
| 
 | 
 | ||||||
| todo | Firstly, you have to create your form inputs. | ||||||
|  | 
 | ||||||
|  | ```dart | ||||||
|  | static List<FormInput> getNormalEntries() { | ||||||
|  |     return const [ | ||||||
|  |         FormInput(formFieldName, Name.pure()), | ||||||
|  |         FormInput(formFieldEmail, Email.pure()), | ||||||
|  |         FormInput(formFieldPhone, Phone.pure()), | ||||||
|  |         FormInput(formFieldList, ListOption<String>.pure(defaultValue: ['checkbox3'])), | ||||||
|  |         FormInput(formFieldRadio, TextString.pure()), | ||||||
|  |         FormInput(formFieldPro, Boolean.pure(), metadata: FormInputMetadata<void>(name: 'business')), | ||||||
|  |         FormInput(formFieldHidden, Boolean.pure(), metadata: FormInputMetadata<void>(export: false)), | ||||||
|  |     ]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static List<FormInput> getBusinessEntries() { | ||||||
|  |     const entries = [ | ||||||
|  |         FormInput(formFieldSiren, Siren.pure()), | ||||||
|  |         FormInput(formFieldIban, Iban.pure()), | ||||||
|  |     ]; | ||||||
|  |     return getNormalEntries() + entries; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static FormData getNormalFormData() { | ||||||
|  |     return FormData(getNormalEntries()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static FormData getProFormData() { | ||||||
|  |     return FormData(getBusinessEntries()); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | > Let's create functions to enable dynamic changes in runtime. | ||||||
|  | 
 | ||||||
|  | > When creating metadata with `FormInputMetadata<T>`, `T` is the type of `extra` object. | ||||||
|  | 
 | ||||||
|  | Then a `FormData` is a collection of inputs. You can `validate`, `contains`, `intersection`, `difference`, `union`, or `clone` this data. | ||||||
|  | 
 | ||||||
|  | After that, you have to extends `FormDataCubit`. | ||||||
|  | 
 | ||||||
|  | ```dart | ||||||
|  | class CustomFormCubit extends FormDataCubit { | ||||||
|  |   CustomFormCubit({required FormData inputs}) : super(inputs: inputs); | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   Future<void> submitForm() { | ||||||
|  |     log(state.data.toMap().toString()); | ||||||
|  |     // Handle all your logic here. | ||||||
|  |     emit(...) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | > The submited form is in `state.data` ! | ||||||
|  | 
 | ||||||
|  | Don't forget to create and provide it ! | ||||||
|  | 
 | ||||||
|  | ```dart | ||||||
|  | FormDataCubit _formCubit = CustomFormCubit(inputs: getNormalFormData()); | ||||||
|  | 
 | ||||||
|  | return BlocProvider( | ||||||
|  |     create: (context) => _formCubit, | ||||||
|  |     child: const WidgetTree(), | ||||||
|  | ); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | You can now create the first form input ! | ||||||
|  | 
 | ||||||
|  | ```dart | ||||||
|  | class _EmailInput extends StatelessWidget { | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return BlocBuilder<FormDataCubit, FormDataState>( | ||||||
|  |       builder: (context, state) { | ||||||
|  |         return TextField( | ||||||
|  |           onChanged: (email) => context | ||||||
|  |               .read<FormDataCubit>() | ||||||
|  |               .dataChanged(formFieldEmail, Email.dirty(email)), | ||||||
|  |           keyboardType: TextInputType.emailAddress, | ||||||
|  |           decoration: InputDecoration( | ||||||
|  |             labelText: 'email', | ||||||
|  |             helperText: '', | ||||||
|  |             errorText: state.data.isNotValid(formFieldEmail) | ||||||
|  |                 ? 'invalid email' | ||||||
|  |                 : null, | ||||||
|  |           ), | ||||||
|  |         ); | ||||||
|  |       }, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Then, you can create the trigger | ||||||
|  | 
 | ||||||
|  | ```dart | ||||||
|  | class _SignUpButton extends StatelessWidget { | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return BlocBuilder<FormDataCubit, FormDataState>( | ||||||
|  |       buildWhen: (previous, current) => previous.status != current.status, | ||||||
|  |       builder: (context, state) { | ||||||
|  |         return state.status.isSubmissionInProgress | ||||||
|  |             ? const CircularProgressIndicator() | ||||||
|  |             : ElevatedButton( | ||||||
|  |                 onPressed: state.status.isValidated | ||||||
|  |                     ? () => context.read<FormDataCubit>().submitForm() | ||||||
|  |                     : null, | ||||||
|  |                 child: const Text('SIGN UP'), | ||||||
|  |               ); | ||||||
|  |       }, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | > With `state.status` you can access the status of the form to check if there is an error or if it's in submission. | ||||||
| @ -1,10 +1,30 @@ | |||||||
| # This file tracks properties of this Flutter project. | # This file tracks properties of this Flutter project. | ||||||
| # Used by Flutter tool to assess capabilities and perform upgrades etc. | # Used by Flutter tool to assess capabilities and perform upgrades etc. | ||||||
| # | # | ||||||
| # This file should be version controlled and should not be manually edited. | # This file should be version controlled. | ||||||
| 
 | 
 | ||||||
| version: | version: | ||||||
|   revision: 5464c5bac742001448fe4fc0597be939379f88ea |   revision: 85684f9300908116a78138ea4c6036c35c9a1236 | ||||||
|   channel: stable |   channel: stable | ||||||
| 
 | 
 | ||||||
| project_type: app | project_type: app | ||||||
|  | 
 | ||||||
|  | # Tracks metadata for the flutter migrate command | ||||||
|  | migration: | ||||||
|  |   platforms: | ||||||
|  |     - platform: root | ||||||
|  |       create_revision: 85684f9300908116a78138ea4c6036c35c9a1236 | ||||||
|  |       base_revision: 85684f9300908116a78138ea4c6036c35c9a1236 | ||||||
|  |     - platform: web | ||||||
|  |       create_revision: 85684f9300908116a78138ea4c6036c35c9a1236 | ||||||
|  |       base_revision: 85684f9300908116a78138ea4c6036c35c9a1236 | ||||||
|  | 
 | ||||||
|  |   # User provided section | ||||||
|  | 
 | ||||||
|  |   # List of Local paths (relative to this file) that should be | ||||||
|  |   # ignored by the migrate tool. | ||||||
|  |   # | ||||||
|  |   # Files that are not part of the templates will be ignored by default. | ||||||
|  |   unmanaged_files: | ||||||
|  |     - 'lib/main.dart' | ||||||
|  |     - 'ios/Runner.xcodeproj/project.pbxproj' | ||||||
|  | |||||||
| @ -16,31 +16,37 @@ | |||||||
| 
 | 
 | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter_bloc/flutter_bloc.dart'; | import 'package:flutter_bloc/flutter_bloc.dart'; | ||||||
|  | import 'package:form_bloc_example/app/metadata.dart'; | ||||||
| import 'package:form_bloc_example/constants.dart'; | import 'package:form_bloc_example/constants.dart'; | ||||||
| import 'package:form_bloc_example/cubit/custom_form_cubit.dart'; | import 'package:form_bloc_example/cubit/custom_form_cubit.dart'; | ||||||
| import 'package:form_bloc_example/sign_up/sign_up_page.dart'; | import 'package:form_bloc_example/sign_up/sign_up_page.dart'; | ||||||
| import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; | import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; | ||||||
| 
 | 
 | ||||||
|  | const blue = Metadata(category: Category.perso); | ||||||
|  | const red = Metadata(category: Category.business); | ||||||
|  | 
 | ||||||
| class App extends StatelessWidget { | class App extends StatelessWidget { | ||||||
|   const App({Key? key}) : super(key: key); |   const App({Key? key}) : super(key: key); | ||||||
| 
 | 
 | ||||||
|   static List<FormEntry> getNormalEntries() { |   static List<FormInput> getNormalEntries() { | ||||||
|     return const [ |     return const [ | ||||||
|       FormEntry(formFieldName, Name.pure()), |       FormInput(formFieldName, Name.pure(), metadata: FormInputMetadata<Metadata>(extra: blue)), | ||||||
|       FormEntry(formFieldEmail, Email.pure()), |       FormInput(formFieldEmail, Email.pure(), metadata: FormInputMetadata<Metadata>(extra: blue)), | ||||||
|       FormEntry(formFieldPhone, Phone.pure()), |       FormInput(formFieldPhone, Phone.pure(), metadata: FormInputMetadata<Metadata>(extra: red)), | ||||||
|       FormEntry( |       FormInput( | ||||||
|           formFieldList, ListOption<String>.pure(defaultValue: ['checkbox3'])), |           formFieldList, ListOption<String>.pure(defaultValue: ['checkbox3'])), | ||||||
|       FormEntry(formFieldRadio, TextString.pure()), |       FormInput(formFieldRadio, TextString.pure()), | ||||||
|       FormEntry(formFieldPro, Boolean.pure(), name: 'business'), |       FormInput(formFieldPro, Boolean.pure(), | ||||||
|       FormEntry(formFieldHidden, Boolean.pure(), export: false), |           metadata: FormInputMetadata<Metadata>(name: 'business')), | ||||||
|  |       FormInput(formFieldHidden, Boolean.pure(), | ||||||
|  |           metadata: FormInputMetadata<Metadata>(export: false, extra: blue)), | ||||||
|     ]; |     ]; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   static List<FormEntry> getBusinessEntries() { |   static List<FormInput> getBusinessEntries() { | ||||||
|     const entries = [ |     const entries = [ | ||||||
|       FormEntry(formFieldSiren, Siren.pure()), |       FormInput(formFieldSiren, Siren.pure()), | ||||||
|       FormEntry(formFieldIban, Iban.pure()), |       FormInput(formFieldIban, Iban.pure()), | ||||||
|     ]; |     ]; | ||||||
|     return getNormalEntries() + entries; |     return getNormalEntries() + entries; | ||||||
|   } |   } | ||||||
| @ -55,7 +61,7 @@ class App extends StatelessWidget { | |||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     FormDataCubit _formCubit = CustomFormCubit(entries: getNormalFormData()); |     FormDataCubit _formCubit = CustomFormCubit(inputs: getNormalFormData()); | ||||||
| 
 | 
 | ||||||
|     return BlocProvider( |     return BlocProvider( | ||||||
|       create: (context) => _formCubit, |       create: (context) => _formCubit, | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | // ignore_for_file: public_member_api_docs, sort_constructors_first | ||||||
| // Copyright (C) 2022 WYATT GROUP | // Copyright (C) 2022 WYATT GROUP | ||||||
| // Please see the AUTHORS file for details. | // Please see the AUTHORS file for details. | ||||||
| // | // | ||||||
| @ -14,9 +15,20 @@ | |||||||
| // 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/>. | ||||||
| 
 | 
 | ||||||
| part of 'custom_form_cubit.dart'; | enum Category { | ||||||
|  |   perso, | ||||||
|  |   business | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| @immutable | class Metadata { | ||||||
| abstract class CustomFormState {} |   final Category category; | ||||||
| 
 | 
 | ||||||
| class CustomFormInitial extends CustomFormState {} |   const Metadata({ | ||||||
|  |     required this.category, | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   String toString() { | ||||||
|  |     return category.toString(); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -16,13 +16,10 @@ | |||||||
| 
 | 
 | ||||||
| import 'dart:developer'; | import 'dart:developer'; | ||||||
| 
 | 
 | ||||||
| import 'package:meta/meta.dart'; |  | ||||||
| import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; | import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; | ||||||
| 
 | 
 | ||||||
| part 'custom_form_state.dart'; |  | ||||||
| 
 |  | ||||||
| class CustomFormCubit extends FormDataCubit { | class CustomFormCubit extends FormDataCubit { | ||||||
|   CustomFormCubit({required FormData entries}) : super(entries: entries); |   CustomFormCubit({required FormData inputs}) : super(inputs: inputs); | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   Future<void> submitForm() { |   Future<void> submitForm() { | ||||||
|  | |||||||
| @ -19,13 +19,61 @@ import 'dart:developer'; | |||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter_bloc/flutter_bloc.dart'; | import 'package:flutter_bloc/flutter_bloc.dart'; | ||||||
| import 'package:form_bloc_example/app/app.dart'; | import 'package:form_bloc_example/app/app.dart'; | ||||||
|  | import 'package:form_bloc_example/app/metadata.dart'; | ||||||
| import 'package:form_bloc_example/constants.dart'; | import 'package:form_bloc_example/constants.dart'; | ||||||
| import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; | import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; | ||||||
| 
 | 
 | ||||||
|  | class CategoryIndicator extends StatelessWidget { | ||||||
|  |   const CategoryIndicator( | ||||||
|  |       {Key? key, required this.field, required this.builder}) | ||||||
|  |       : super(key: key); | ||||||
|  | 
 | ||||||
|  |   final String field; | ||||||
|  |   final Function(BuildContext context, FormDataState state) builder; | ||||||
|  | 
 | ||||||
|  |   Color computeColor(Metadata meta) { | ||||||
|  |     switch (meta.category) { | ||||||
|  |       case Category.perso: | ||||||
|  |         return Colors.blue; | ||||||
|  |       case Category.business: | ||||||
|  |         return Colors.red; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return BlocBuilder<FormDataCubit, FormDataState>( | ||||||
|  |       builder: ((context, state) { | ||||||
|  |         final meta = state.data.metadataOf(field).extra as Metadata; | ||||||
|  |         final color = computeColor(meta); | ||||||
|  | 
 | ||||||
|  |         return Row( | ||||||
|  |           children: [ | ||||||
|  |             Container( | ||||||
|  |               height: 8, | ||||||
|  |               width: 8, | ||||||
|  |               decoration: BoxDecoration( | ||||||
|  |                 color: color, | ||||||
|  |                 borderRadius: BorderRadius.circular(8), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |             const SizedBox(width: 20,), | ||||||
|  |             SizedBox( | ||||||
|  |               width: MediaQuery.of(context).size.width - 45, | ||||||
|  |               child: builder(context, state), | ||||||
|  |             ) | ||||||
|  |           ], | ||||||
|  |         ); | ||||||
|  |       }), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| class _NameInput extends StatelessWidget { | class _NameInput extends StatelessWidget { | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return BlocBuilder<FormDataCubit, FormDataState>( |     return CategoryIndicator( | ||||||
|  |         field: formFieldName, | ||||||
|         builder: (context, state) { |         builder: (context, state) { | ||||||
|           return TextField( |           return TextField( | ||||||
|             onChanged: (name) => context |             onChanged: (name) => context | ||||||
| @ -36,18 +84,18 @@ class _NameInput extends StatelessWidget { | |||||||
|               labelText: 'name', |               labelText: 'name', | ||||||
|               helperText: '', |               helperText: '', | ||||||
|               errorText: |               errorText: | ||||||
|                 state.data.input(formFieldName).invalid ? 'invalid name' : null, |                   state.data.isNotValid(formFieldName) ? 'invalid name' : null, | ||||||
|             ), |             ), | ||||||
|           ); |           ); | ||||||
|       }, |         }); | ||||||
|     ); |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| class _EmailInput extends StatelessWidget { | class _EmailInput extends StatelessWidget { | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return BlocBuilder<FormDataCubit, FormDataState>( |     return CategoryIndicator( | ||||||
|  |         field: formFieldEmail, | ||||||
|       builder: (context, state) { |       builder: (context, state) { | ||||||
|         return TextField( |         return TextField( | ||||||
|           onChanged: (email) => context |           onChanged: (email) => context | ||||||
| @ -57,9 +105,8 @@ class _EmailInput extends StatelessWidget { | |||||||
|           decoration: InputDecoration( |           decoration: InputDecoration( | ||||||
|             labelText: 'email', |             labelText: 'email', | ||||||
|             helperText: '', |             helperText: '', | ||||||
|             errorText: state.data.input(formFieldEmail).invalid |             errorText: | ||||||
|                 ? 'invalid email' |                 state.data.isNotValid(formFieldEmail) ? 'invalid email' : null, | ||||||
|                 : null, |  | ||||||
|           ), |           ), | ||||||
|         ); |         ); | ||||||
|       }, |       }, | ||||||
| @ -70,7 +117,8 @@ class _EmailInput extends StatelessWidget { | |||||||
| class _PhoneInput extends StatelessWidget { | class _PhoneInput extends StatelessWidget { | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return BlocBuilder<FormDataCubit, FormDataState>( |     return CategoryIndicator( | ||||||
|  |         field: formFieldPhone, | ||||||
|       builder: (context, state) { |       builder: (context, state) { | ||||||
|         return TextField( |         return TextField( | ||||||
|           onChanged: (phone) => context |           onChanged: (phone) => context | ||||||
| @ -80,9 +128,8 @@ class _PhoneInput extends StatelessWidget { | |||||||
|           decoration: InputDecoration( |           decoration: InputDecoration( | ||||||
|             labelText: 'phone', |             labelText: 'phone', | ||||||
|             helperText: '', |             helperText: '', | ||||||
|             errorText: state.data.input(formFieldPhone).invalid |             errorText: | ||||||
|                 ? 'invalid phone' |                 state.data.isNotValid(formFieldPhone) ? 'invalid phone' : null, | ||||||
|                 : null, |  | ||||||
|           ), |           ), | ||||||
|         ); |         ); | ||||||
|       }, |       }, | ||||||
| @ -93,7 +140,8 @@ class _PhoneInput extends StatelessWidget { | |||||||
| class _SirenInput extends StatelessWidget { | class _SirenInput extends StatelessWidget { | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return BlocBuilder<FormDataCubit, FormDataState>( |     return CategoryIndicator( | ||||||
|  |         field: formFieldName, | ||||||
|       builder: (context, state) { |       builder: (context, state) { | ||||||
|         return TextField( |         return TextField( | ||||||
|           onChanged: (siren) => context |           onChanged: (siren) => context | ||||||
| @ -103,9 +151,8 @@ class _SirenInput extends StatelessWidget { | |||||||
|           decoration: InputDecoration( |           decoration: InputDecoration( | ||||||
|             labelText: 'siren', |             labelText: 'siren', | ||||||
|             helperText: '', |             helperText: '', | ||||||
|             errorText: state.data.input(formFieldSiren).invalid |             errorText: | ||||||
|                 ? 'invalid SIREN' |                 state.data.isNotValid(formFieldSiren) ? 'invalid SIREN' : null, | ||||||
|                 : null, |  | ||||||
|           ), |           ), | ||||||
|         ); |         ); | ||||||
|       }, |       }, | ||||||
| @ -127,7 +174,7 @@ class _IbanInput extends StatelessWidget { | |||||||
|             labelText: 'iban', |             labelText: 'iban', | ||||||
|             helperText: '', |             helperText: '', | ||||||
|             errorText: |             errorText: | ||||||
|                 state.data.input(formFieldIban).invalid ? 'invalid IBAN' : null, |                 state.data.isNotValid(formFieldIban) ? 'invalid IBAN' : null, | ||||||
|           ), |           ), | ||||||
|         ); |         ); | ||||||
|       }, |       }, | ||||||
| @ -141,7 +188,7 @@ class _CheckListInput extends StatelessWidget { | |||||||
|     return BlocBuilder<FormDataCubit, FormDataState>( |     return BlocBuilder<FormDataCubit, FormDataState>( | ||||||
|       builder: (context, state) { |       builder: (context, state) { | ||||||
|         final _input = |         final _input = | ||||||
|             state.data.input<List<String>>(formFieldList) as ListOption<String>; |             state.data.validatorOf<ListOption<String>>(formFieldList); | ||||||
|         final _options = _input.value; |         final _options = _input.value; | ||||||
| 
 | 
 | ||||||
|         return Column( |         return Column( | ||||||
| @ -192,8 +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 = |         final _input = state.data.validatorOf<TextString>(formFieldRadio); | ||||||
|             state.data.input<String>(formFieldRadio) as TextString; |  | ||||||
| 
 | 
 | ||||||
|         return Column( |         return Column( | ||||||
|           mainAxisSize: MainAxisSize.min, |           mainAxisSize: MainAxisSize.min, | ||||||
| @ -249,7 +295,7 @@ class _CheckHiddenInput extends StatelessWidget { | |||||||
|       trailing: BlocBuilder<FormDataCubit, FormDataState>( |       trailing: BlocBuilder<FormDataCubit, FormDataState>( | ||||||
|         builder: (context, state) { |         builder: (context, state) { | ||||||
|           return Checkbox( |           return Checkbox( | ||||||
|               value: state.data.input<bool>(formFieldHidden).value, |               value: state.data.validatorOf<Boolean>(formFieldHidden).value, | ||||||
|               onChanged: (v) { |               onChanged: (v) { | ||||||
|                 final value = v!; // state is false, so value can't be null |                 final value = v!; // state is false, so value can't be null | ||||||
| 
 | 
 | ||||||
| @ -272,7 +318,7 @@ class _CheckIsProInput extends StatelessWidget { | |||||||
|       trailing: BlocBuilder<FormDataCubit, FormDataState>( |       trailing: BlocBuilder<FormDataCubit, FormDataState>( | ||||||
|         builder: (context, state) { |         builder: (context, state) { | ||||||
|           return Checkbox( |           return Checkbox( | ||||||
|               value: state.data.input<bool>(formFieldPro).value, |               value: state.data.validatorOf<Boolean>(formFieldPro).value, | ||||||
|               onChanged: (isPro) { |               onChanged: (isPro) { | ||||||
|                 final value = isPro!; // state is false, so value can't be null |                 final value = isPro!; // state is false, so value can't be null | ||||||
| 
 | 
 | ||||||
| @ -370,7 +416,7 @@ class SignUpForm extends StatelessWidget { | |||||||
|             const SizedBox(height: 8), |             const SizedBox(height: 8), | ||||||
|             BlocBuilder<FormDataCubit, FormDataState>( |             BlocBuilder<FormDataCubit, FormDataState>( | ||||||
|               builder: (context, state) { |               builder: (context, state) { | ||||||
|                 if (state.data.input<bool>(formFieldPro).value) { |                 if (state.data.validatorOf<Boolean>(formFieldPro).value) { | ||||||
|                   return Column(children: [ |                   return Column(children: [ | ||||||
|                     _SirenInput(), |                     _SirenInput(), | ||||||
|                     const SizedBox(height: 8), |                     const SizedBox(height: 8), | ||||||
|  | |||||||
| @ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev | |||||||
| version: 1.0.0+1 | version: 1.0.0+1 | ||||||
| 
 | 
 | ||||||
| environment: | environment: | ||||||
|   sdk: ">=2.16.2 <3.0.0" |   sdk: ">=2.17.2 <3.0.0" | ||||||
| 
 | 
 | ||||||
| # Dependencies specify other packages that your package needs in order to work. | # Dependencies specify other packages that your package needs in order to work. | ||||||
| # To automatically upgrade your package dependencies to the latest versions | # To automatically upgrade your package dependencies to the latest versions | ||||||
|  | |||||||
							
								
								
									
										
											BIN
										
									
								
								packages/wyatt_form_bloc/example/web/favicon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								packages/wyatt_form_bloc/example/web/favicon.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 917 B | 
							
								
								
									
										
											BIN
										
									
								
								packages/wyatt_form_bloc/example/web/icons/Icon-192.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								packages/wyatt_form_bloc/example/web/icons/Icon-192.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 5.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								packages/wyatt_form_bloc/example/web/icons/Icon-512.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								packages/wyatt_form_bloc/example/web/icons/Icon-512.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 8.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								packages/wyatt_form_bloc/example/web/icons/Icon-maskable-192.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								packages/wyatt_form_bloc/example/web/icons/Icon-maskable-192.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 5.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								packages/wyatt_form_bloc/example/web/icons/Icon-maskable-512.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								packages/wyatt_form_bloc/example/web/icons/Icon-maskable-512.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 20 KiB | 
							
								
								
									
										58
									
								
								packages/wyatt_form_bloc/example/web/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								packages/wyatt_form_bloc/example/web/index.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,58 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html> | ||||||
|  | <head> | ||||||
|  |   <!-- | ||||||
|  |     If you are serving your web app in a path other than the root, change the | ||||||
|  |     href value below to reflect the base path you are serving from. | ||||||
|  | 
 | ||||||
|  |     The path provided below has to start and end with a slash "/" in order for | ||||||
|  |     it to work correctly. | ||||||
|  | 
 | ||||||
|  |     For more details: | ||||||
|  |     * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base | ||||||
|  | 
 | ||||||
|  |     This is a placeholder for base href that will be replaced by the value of | ||||||
|  |     the `--base-href` argument provided to `flutter build`. | ||||||
|  |   --> | ||||||
|  |   <base href="$FLUTTER_BASE_HREF"> | ||||||
|  | 
 | ||||||
|  |   <meta charset="UTF-8"> | ||||||
|  |   <meta content="IE=Edge" http-equiv="X-UA-Compatible"> | ||||||
|  |   <meta name="description" content="A new Flutter project."> | ||||||
|  | 
 | ||||||
|  |   <!-- iOS meta tags & icons --> | ||||||
|  |   <meta name="apple-mobile-web-app-capable" content="yes"> | ||||||
|  |   <meta name="apple-mobile-web-app-status-bar-style" content="black"> | ||||||
|  |   <meta name="apple-mobile-web-app-title" content="example"> | ||||||
|  |   <link rel="apple-touch-icon" href="icons/Icon-192.png"> | ||||||
|  | 
 | ||||||
|  |   <!-- Favicon --> | ||||||
|  |   <link rel="icon" type="image/png" href="favicon.png"/> | ||||||
|  | 
 | ||||||
|  |   <title>example</title> | ||||||
|  |   <link rel="manifest" href="manifest.json"> | ||||||
|  | 
 | ||||||
|  |   <script> | ||||||
|  |     // The value below is injected by flutter build, do not touch. | ||||||
|  |     var serviceWorkerVersion = null; | ||||||
|  |   </script> | ||||||
|  |   <!-- This script adds the flutter initialization JS code --> | ||||||
|  |   <script src="flutter.js" defer></script> | ||||||
|  | </head> | ||||||
|  | <body> | ||||||
|  |   <script> | ||||||
|  |     window.addEventListener('load', function(ev) { | ||||||
|  |       // Download main.dart.js | ||||||
|  |       _flutter.loader.loadEntrypoint({ | ||||||
|  |         serviceWorker: { | ||||||
|  |           serviceWorkerVersion: serviceWorkerVersion, | ||||||
|  |         } | ||||||
|  |       }).then(function(engineInitializer) { | ||||||
|  |         return engineInitializer.initializeEngine(); | ||||||
|  |       }).then(function(appRunner) { | ||||||
|  |         return appRunner.runApp(); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   </script> | ||||||
|  | </body> | ||||||
|  | </html> | ||||||
							
								
								
									
										35
									
								
								packages/wyatt_form_bloc/example/web/manifest.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								packages/wyatt_form_bloc/example/web/manifest.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | |||||||
|  | { | ||||||
|  |     "name": "example", | ||||||
|  |     "short_name": "example", | ||||||
|  |     "start_url": ".", | ||||||
|  |     "display": "standalone", | ||||||
|  |     "background_color": "#0175C2", | ||||||
|  |     "theme_color": "#0175C2", | ||||||
|  |     "description": "A new Flutter project.", | ||||||
|  |     "orientation": "portrait-primary", | ||||||
|  |     "prefer_related_applications": false, | ||||||
|  |     "icons": [ | ||||||
|  |         { | ||||||
|  |             "src": "icons/Icon-192.png", | ||||||
|  |             "sizes": "192x192", | ||||||
|  |             "type": "image/png" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "src": "icons/Icon-512.png", | ||||||
|  |             "sizes": "512x512", | ||||||
|  |             "type": "image/png" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "src": "icons/Icon-maskable-192.png", | ||||||
|  |             "sizes": "192x192", | ||||||
|  |             "type": "image/png", | ||||||
|  |             "purpose": "maskable" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "src": "icons/Icon-maskable-512.png", | ||||||
|  |             "sizes": "512x512", | ||||||
|  |             "type": "image/png", | ||||||
|  |             "purpose": "maskable" | ||||||
|  |         } | ||||||
|  |     ] | ||||||
|  | } | ||||||
| @ -17,6 +17,7 @@ | |||||||
| import 'dart:async'; | import 'dart:async'; | ||||||
| 
 | 
 | ||||||
| import 'package:bloc/bloc.dart'; | import 'package:bloc/bloc.dart'; | ||||||
|  | import 'package:equatable/equatable.dart'; | ||||||
| import 'package:meta/meta.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'; | ||||||
| @ -24,19 +25,19 @@ import 'package:wyatt_form_bloc/src/form/form.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 entries}) |   FormDataCubit({required FormData inputs}) | ||||||
|       : super(FormDataState(data: entries)); |       : 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. |   /// - `dirtyValue`: The new value of the field. (Wrapped in a dirty validator) | ||||||
|   void dataChanged(String field, FormInput dirtyValue) { |   void dataChanged(String field, FormInputValidator dirtyValue) { | ||||||
|     final _form = state.data.clone(); |     final _form = state.data.clone(); | ||||||
| 
 | 
 | ||||||
|     if (_form.contains(field)) { |     if (_form.contains(field)) { | ||||||
|       _form.update(field, dirtyValue); |       _form.updateValidator(field, dirtyValue); | ||||||
|     } else { |     } else { | ||||||
|       throw Exception('Form field $field not found'); |       throw Exception('Form field $field not found'); | ||||||
|     } |     } | ||||||
| @ -44,7 +45,7 @@ abstract class FormDataCubit extends Cubit<FormDataState> { | |||||||
|     emit( |     emit( | ||||||
|       state.copyWith( |       state.copyWith( | ||||||
|         data: _form, |         data: _form, | ||||||
|         status: _form.selfValidate(), |         status: _form.validate(), | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| @ -78,7 +79,7 @@ abstract class FormDataCubit extends Cubit<FormDataState> { | |||||||
|     emit( |     emit( | ||||||
|       state.copyWith( |       state.copyWith( | ||||||
|         data: _form, |         data: _form, | ||||||
|         status: _form.selfValidate(), |         status: _form.validate(), | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -17,7 +17,7 @@ | |||||||
| part of 'form_data_cubit.dart'; | part of 'form_data_cubit.dart'; | ||||||
| 
 | 
 | ||||||
| @immutable | @immutable | ||||||
| class FormDataState { | class FormDataState extends Equatable { | ||||||
|   final FormStatus status; |   final FormStatus status; | ||||||
|   final FormData data; |   final FormData data; | ||||||
|   final String? errorMessage; |   final String? errorMessage; | ||||||
| @ -41,23 +41,8 @@ class FormDataState { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   bool operator ==(Object other) { |   bool? get stringify => true; | ||||||
|     if (identical(this, other)) return true; |  | ||||||
| 
 |  | ||||||
|     return other is FormDataState && |  | ||||||
|         other.status == status && |  | ||||||
|         other.data == data && |  | ||||||
|         other.errorMessage == errorMessage; |  | ||||||
|   } |  | ||||||
|    |    | ||||||
|   @override |   @override | ||||||
|   int get hashCode { |   List<Object?> get props => [status, data, errorMessage]; | ||||||
|     return status.hashCode ^ data.hashCode ^ errorMessage.hashCode; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   @override |  | ||||||
|   String toString() { |  | ||||||
|     return 'FormDataState(status: $status, data: $data, ' |  | ||||||
|         'errorMessage: $errorMessage)'; |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -14,6 +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/>. | ||||||
| 
 | 
 | ||||||
|  | import 'package:wyatt_form_bloc/src/form/form.dart'; | ||||||
|  | 
 | ||||||
|  | const Set<FormStatus> _validatedFormStatuses = <FormStatus>{ | ||||||
|  |   FormStatus.valid, | ||||||
|  |   FormStatus.submissionInProgress, | ||||||
|  |   FormStatus.submissionSuccess, | ||||||
|  |   FormStatus.submissionFailure, | ||||||
|  |   FormStatus.submissionCanceled, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| /// Enum representing the status of a form at any given point in time. | /// Enum representing the status of a form at any given point in time. | ||||||
| enum FormStatus { | enum FormStatus { | ||||||
|   /// The form has not been touched. |   /// The form has not been touched. | ||||||
| @ -35,19 +45,8 @@ enum FormStatus { | |||||||
|   submissionFailure, |   submissionFailure, | ||||||
| 
 | 
 | ||||||
|   /// The form submission has been canceled. |   /// The form submission has been canceled. | ||||||
|   submissionCanceled |   submissionCanceled; | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| const Set<FormStatus> _validatedFormStatuses = <FormStatus>{ |  | ||||||
|   FormStatus.valid, |  | ||||||
|   FormStatus.submissionInProgress, |  | ||||||
|   FormStatus.submissionSuccess, |  | ||||||
|   FormStatus.submissionFailure, |  | ||||||
|   FormStatus.submissionCanceled, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| /// Useful extensions on [FormStatus] |  | ||||||
| extension FormStatusX on FormStatus { |  | ||||||
|   /// Indicates whether the form is untouched. |   /// Indicates whether the form is untouched. | ||||||
|   bool get isPure => this == FormStatus.pure; |   bool get isPure => this == FormStatus.pure; | ||||||
| 
 | 
 | ||||||
| @ -76,4 +75,13 @@ extension FormStatusX on 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 | ||||||
|  |   static FormStatus validate(List<FormInput> inputs) { | ||||||
|  |     return inputs.every((FormInput input) => input.validator.pure) | ||||||
|  |         ? FormStatus.pure | ||||||
|  |         : inputs.any((FormInput input) => input.validator.valid == false) | ||||||
|  |             ? FormStatus.invalid | ||||||
|  |             : FormStatus.valid; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -14,6 +14,12 @@ | |||||||
| // 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 'form_data.dart'; | import 'package:equatable/equatable.dart'; | ||||||
| export 'form_entry.dart'; | import 'package:meta/meta.dart'; | ||||||
| export 'form_input.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'; | ||||||
|  | |||||||
| @ -14,133 +14,164 @@ | |||||||
| // 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:meta/meta.dart'; | part of 'form.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'; |  | ||||||
| 
 | 
 | ||||||
| @immutable | @immutable | ||||||
| class FormData { | class FormData extends Equatable { | ||||||
|   const FormData(this._entries); |   final List<FormInput> _inputs; | ||||||
| 
 | 
 | ||||||
|   FormData.empty() : this(<FormEntry>[]); |   const FormData(this._inputs); | ||||||
|  |   const FormData.empty() : this(const <FormInput>[]); | ||||||
| 
 | 
 | ||||||
|   final List<FormEntry> _entries; |   /// Returns the input for the associated key | ||||||
| 
 |   FormInput inputByKey(String key) { | ||||||
|   List<FormEntry> get entries => _entries; |  | ||||||
| 
 |  | ||||||
|   List<FormInput<T, ValidationError>> inputs<T>() { |  | ||||||
|     return _entries |  | ||||||
|         .map((FormEntry entry) => entry.input as FormInput<T, ValidationError>) |  | ||||||
|         .toList(); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   static FormStatus validate(List<FormInput> 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<dynamic>()); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   FormInput<T, ValidationError> input<T>(String key) { |  | ||||||
|     if (contains(key)) { |     if (contains(key)) { | ||||||
|       return _entries.firstWhere((FormEntry entry) => entry.key == key).input |       return _inputs.firstWhere((FormInput input) => input.key == key); | ||||||
|           as FormInput<T, ValidationError>; |  | ||||||
|     } else { |     } else { | ||||||
|       throw Exception('FormInput with key `$key` does not exist in form'); |       throw Exception('FormInput with key `$key` does not exist in form'); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   bool contains(String key) { |   /// Updates a input (perform a replace at index). | ||||||
|     return _entries.any((FormEntry entry) => entry.key == key); |   void updateInput(String key, FormInput input) { | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   FormData intersection(FormData other) { |  | ||||||
|     final List<FormEntry> entries = <FormEntry>[]; |  | ||||||
| 
 |  | ||||||
|     for (final FormEntry entry in _entries) { |  | ||||||
|       if (other.contains(entry.key)) { |  | ||||||
|         entries.add(entry); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return FormData(entries); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   FormData difference(FormData other) { |  | ||||||
|     final List<FormEntry> entries = <FormEntry>[]; |  | ||||||
| 
 |  | ||||||
|     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<FormEntry> entries = <FormEntry>[]; |  | ||||||
| 
 |  | ||||||
|     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) { |  | ||||||
|     if (contains(key)) { |     if (contains(key)) { | ||||||
|       final index = _entries.indexOf( |       final index = _inputs.indexOf( | ||||||
|         _entries.firstWhere((FormEntry entry) => entry.key == key), |         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<T>(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<V, E>(String key) { | ||||||
|  |     return (inputByKey(key).validator as FormInputValidator<V, E>).error; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// The value of the associated [FormInputValidator]. For example,  | ||||||
|  |   /// if you have a FormInputValidator for FirstName, the value could be 'Joe'. | ||||||
|  |   V valueOf<V, E>(String key) { | ||||||
|  |     return (inputByKey(key).validator as FormInputValidator<V, E>).value; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// Returns `true` if the [FormInputValidator] is not valid. | ||||||
|  |   /// Same as `E? errorOf<V, E>(String key) != null` | ||||||
|  |   bool isNotValid(String key) { | ||||||
|  |     return !inputByKey(key).validator.valid; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// Returns the metadata associated. With `M` the type of extra data. | ||||||
|  |   FormInputMetadata<M> metadataOf<M>(String key) { | ||||||
|  |     return inputByKey(key).metadata as FormInputMetadata<M>; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// 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<FormInput> inputs = <FormInput>[]; | ||||||
|  | 
 | ||||||
|  |     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<FormInput> inputs = <FormInput>[]; | ||||||
|  | 
 | ||||||
|  |     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<FormInput> inputs = <FormInput>[]; | ||||||
|  | 
 | ||||||
|  |     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() { |   FormData clone() { | ||||||
|     return FormData( |     return FormData( | ||||||
|       _entries.map((FormEntry entry) => entry.clone()).toList(), |       _inputs.map((FormInput input) => input.clone()).toList(), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /// Export this to [Map] format. | ||||||
|   Map<String, dynamic> toMap() { |   Map<String, dynamic> toMap() { | ||||||
|     final map = <String, dynamic>{}; |     final map = <String, dynamic>{}; | ||||||
|     for (final entry in _entries) { |     for (final input in _inputs) { | ||||||
|       if (entry.export) { |       if (input.metadata.export) { | ||||||
|         map[entry.name] = entry.input.value; |         map[input.name] = input.validator.value; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     return map; |     return map; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   bool operator ==(Object other) { |   bool? get stringify => true; | ||||||
|     if (identical(this, other)) return true; |  | ||||||
| 
 |  | ||||||
|     return other is FormData && listEquals(other._entries, _entries); |  | ||||||
|   } |  | ||||||
|    |    | ||||||
|   @override |   @override | ||||||
|   int get hashCode => _entries.hashCode; |   List<Object?> get props => _inputs; | ||||||
| 
 |  | ||||||
|   @override |  | ||||||
|   String toString() => 'FormData(entries: $_entries)'; |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -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 <https://www.gnu.org/licenses/>. |  | ||||||
| 
 |  | ||||||
| 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)'; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -1,3 +1,4 @@ | |||||||
|  | // ignore_for_file: public_member_api_docs, sort_constructors_first | ||||||
| // Copyright (C) 2022 WYATT GROUP | // Copyright (C) 2022 WYATT GROUP | ||||||
| // Please see the AUTHORS file for details. | // Please see the AUTHORS file for details. | ||||||
| // | // | ||||||
| @ -14,98 +15,45 @@ | |||||||
| // 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:meta/meta.dart'; | part of 'form.dart'; | ||||||
| import 'package:wyatt_form_bloc/src/enums/enums.dart'; |  | ||||||
| 
 | 
 | ||||||
| /// {@template form_input} | class FormInput extends Equatable { | ||||||
| /// A [FormInput] represents the value of a single form input field. |   final String key; | ||||||
| /// It contains information about the [FormInputStatus], [value], as well |   final FormInputValidator validator; | ||||||
| /// as validation status. |   final FormInputMetadata metadata; | ||||||
| /// |  | ||||||
| /// [FormInput] should be extended to define custom [FormInput] instances. |  | ||||||
| /// |  | ||||||
| /// ```dart |  | ||||||
| /// enum FirstNameError { empty } |  | ||||||
| /// class FirstName extends FormInput<String, FirstNameError> { |  | ||||||
| ///   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<T, E> { |  | ||||||
|   const FormInput._(this.value, [this.pure = true]); |  | ||||||
| 
 | 
 | ||||||
|   /// Constructor which create a `pure` [FormInput] with a given value. |   String get name => metadata._name ?? key; | ||||||
|   const FormInput.pure(T value) : this._(value); |  | ||||||
| 
 | 
 | ||||||
|   /// Constructor which create a `dirty` [FormInput] with a given value. |   const FormInput( | ||||||
|   const FormInput.dirty(T value) : this._(value, false); |     this.key, | ||||||
| 
 |     this.validator, { | ||||||
|   /// The value of the given [FormInput]. |     // ignore: avoid_redundant_argument_values | ||||||
|   /// For example, if you have a `FormInput` for `FirstName`, |     this.metadata = const FormInputMetadata<void>(export: true), | ||||||
|   /// 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); |  | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   int get hashCode => value.hashCode ^ pure.hashCode; |   bool? get stringify => true; | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   bool operator ==(Object other) { |   List<Object?> get props => [key, validator, metadata]; | ||||||
|     if (other.runtimeType != runtimeType) return false; | 
 | ||||||
|     return other is FormInput<T, E> && |   FormInput copyWith({ | ||||||
|         other.value == value && |     String? key, | ||||||
|         other.pure == pure; |     FormInputValidator? validator, | ||||||
|  |     FormInputMetadata? metadata, | ||||||
|  |   }) { | ||||||
|  |     return FormInput( | ||||||
|  |       key ?? this.key, | ||||||
|  |       validator ?? this.validator, | ||||||
|  |       metadata: metadata ?? this.metadata, | ||||||
|  |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   @override |   FormInput clone() { | ||||||
|   String toString() => 'FormInput<$runtimeType>(value: $value, pure: $pure)'; |     return copyWith( | ||||||
|  |       key: key, | ||||||
|  |       validator: validator, | ||||||
|  |       metadata: metadata, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -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 <https://www.gnu.org/licenses/>. | ||||||
|  | 
 | ||||||
|  | part of 'form.dart'; | ||||||
|  | 
 | ||||||
|  | class FormInputMetadata<T> 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<Object?> get props => [export, _name, extra]; | ||||||
|  | 
 | ||||||
|  |   FormInputMetadata<T> copyWith({ | ||||||
|  |     bool? export, | ||||||
|  |     String? name, | ||||||
|  |     T? extra, | ||||||
|  |   }) { | ||||||
|  |     return FormInputMetadata<T>( | ||||||
|  |       export: export ?? this.export, | ||||||
|  |       name: name ?? _name, | ||||||
|  |       extra: extra ?? this.extra, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   FormInputMetadata<T> clone() { | ||||||
|  |     return copyWith( | ||||||
|  |       export: export, | ||||||
|  |       name: _name, | ||||||
|  |       extra: extra, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										104
									
								
								packages/wyatt_form_bloc/lib/src/form/form_input_validator.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								packages/wyatt_form_bloc/lib/src/form/form_input_validator.dart
									
									
									
									
									
										Normal file
									
								
							| @ -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 <https://www.gnu.org/licenses/>. | ||||||
|  | 
 | ||||||
|  | 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<String, FirstNameError> { | ||||||
|  | ///   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<V, E> 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<Object?> get props => [value, pure]; | ||||||
|  | } | ||||||
| @ -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 <https://www.gnu.org/licenses/>. |  | ||||||
| 
 |  | ||||||
| bool listEquals<T>(List<T>? a, List<T>? 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; |  | ||||||
| } |  | ||||||
| @ -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 FormInput<bool, ValidationError> { | class Boolean extends FormInputValidator<bool, ValidationError> { | ||||||
|   /// {@macro boolean} |   /// {@macro boolean} | ||||||
|   const Boolean.pure({bool? defaultValue = false}) |   const Boolean.pure({bool? defaultValue = false}) | ||||||
|       : super.pure(defaultValue ?? false); |       : super.pure(defaultValue ?? false); | ||||||
|  | |||||||
| @ -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 FormInput<String, ValidationError> { |     extends FormInputValidator<String, ValidationError> { | ||||||
|   /// {@macro confirmed_password} |   /// {@macro confirmed_password} | ||||||
|   const ConfirmedPassword.pure({this.password = ''}) : super.pure(''); |   const ConfirmedPassword.pure({this.password = ''}) : super.pure(''); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart'; | |||||||
| /// {@template email} | /// {@template email} | ||||||
| /// Form input for an email input. | /// Form input for an email input. | ||||||
| /// {@endtemplate} | /// {@endtemplate} | ||||||
| class Email extends FormInput<String, ValidationError> { | class Email extends FormInputValidator<String, ValidationError> { | ||||||
|   /// {@macro email} |   /// {@macro email} | ||||||
|   const Email.pure() : super.pure(''); |   const Email.pure() : super.pure(''); | ||||||
| 
 | 
 | ||||||
| @ -28,7 +28,7 @@ class Email extends FormInput<String, ValidationError> { | |||||||
|   const Email.dirty([String value = '']) : super.dirty(value); |   const Email.dirty([String value = '']) : super.dirty(value); | ||||||
| 
 | 
 | ||||||
|   static final RegExp _emailRegExp = RegExp( |   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 |   @override | ||||||
|  | |||||||
| @ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart'; | |||||||
| /// {@template iban} | /// {@template iban} | ||||||
| /// Form input for an IBAN input. | /// Form input for an IBAN input. | ||||||
| /// {@endtemplate} | /// {@endtemplate} | ||||||
| class Iban extends FormInput<String, ValidationError> { | class Iban extends FormInputValidator<String, ValidationError> { | ||||||
|   /// {@macro iban} |   /// {@macro iban} | ||||||
|   const Iban.pure() : super.pure(''); |   const Iban.pure() : super.pure(''); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -20,7 +20,7 @@ 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 FormInput<List<T>, ValidationError> { | class ListOption<T> extends FormInputValidator<List<T>, ValidationError> { | ||||||
|   /// {@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 []); | ||||||
|  | |||||||
| @ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart'; | |||||||
| /// {@template name} | /// {@template name} | ||||||
| /// Form input for a name input. | /// Form input for a name input. | ||||||
| /// {@endtemplate} | /// {@endtemplate} | ||||||
| class Name extends FormInput<String, ValidationError> { | class Name extends FormInputValidator<String, ValidationError> { | ||||||
|   /// {@macro name} |   /// {@macro name} | ||||||
|   const Name.pure() : super.pure(''); |   const Name.pure() : super.pure(''); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart'; | |||||||
| /// {@template password} | /// {@template password} | ||||||
| /// Form input for a password input. | /// Form input for a password input. | ||||||
| /// {@endtemplate} | /// {@endtemplate} | ||||||
| class Password extends FormInput<String, ValidationError> { | class Password extends FormInputValidator<String, ValidationError> { | ||||||
|   /// {@macro password} |   /// {@macro password} | ||||||
|   const Password.pure() : super.pure(''); |   const Password.pure() : super.pure(''); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart'; | |||||||
| /// {@template phone} | /// {@template phone} | ||||||
| /// Form input for a phone input. | /// Form input for a phone input. | ||||||
| /// {@endtemplate} | /// {@endtemplate} | ||||||
| class Phone extends FormInput<String, ValidationError> { | class Phone extends FormInputValidator<String, ValidationError> { | ||||||
|   /// {@macro phone} |   /// {@macro phone} | ||||||
|   const Phone.pure() : super.pure(''); |   const Phone.pure() : super.pure(''); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart'; | |||||||
| /// {@template siren} | /// {@template siren} | ||||||
| /// Form input for a SIREN input. | /// Form input for a SIREN input. | ||||||
| /// {@endtemplate} | /// {@endtemplate} | ||||||
| class Siren extends FormInput<String, ValidationError> { | class Siren extends FormInputValidator<String, ValidationError> { | ||||||
|   /// {@macro siren} |   /// {@macro siren} | ||||||
|   const Siren.pure() : super.pure(''); |   const Siren.pure() : super.pure(''); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart'; | |||||||
| /// {@template text_string} | /// {@template text_string} | ||||||
| /// Form input for a text input | /// Form input for a text input | ||||||
| /// {@endtemplate} | /// {@endtemplate} | ||||||
| class TextString extends FormInput<String, ValidationError> { | class TextString extends FormInputValidator<String, ValidationError> { | ||||||
|   /// {@macro text_string} |   /// {@macro text_string} | ||||||
|   const TextString.pure() : super.pure(''); |   const TextString.pure() : super.pure(''); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -4,14 +4,15 @@ repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/mas | |||||||
| version: 0.0.2 | version: 0.0.2 | ||||||
| 
 | 
 | ||||||
| environment: | environment: | ||||||
|   sdk: '>=2.16.2 <3.0.0' |   sdk: '>=2.17.2 <3.0.0' | ||||||
| 
 | 
 | ||||||
| dependencies: | dependencies: | ||||||
|   bloc: ^8.0.3 |   bloc: ^8.0.3 | ||||||
|  |   equatable: ^2.0.3 | ||||||
|   meta: ^1.7.0 |   meta: ^1.7.0 | ||||||
| 
 | 
 | ||||||
| dev_dependencies: | dev_dependencies: | ||||||
|   test: ^1.21.0 |   test: ^1.21.4 | ||||||
| 
 | 
 | ||||||
|   wyatt_analysis: |   wyatt_analysis: | ||||||
|     git: |     git: | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user