Compare commits
	
		
			No commits in common. "94b0ff01ad5045332d3d172f35bb40fe4359705e" and "9203df94a01e8951469f394a01f1fa96cc108c0d" have entirely different histories.
		
	
	
		
			94b0ff01ad
			...
			9203df94a0
		
	
		
| @ -30,25 +30,21 @@ Form Bloc for Dart & Flutter. | |||||||
| ## Features | ## Features | ||||||
| 
 | 
 | ||||||
| - Form | - Form | ||||||
|     * FormInputValidator: |     * FormInput: *atom of a form* | ||||||
|         - Store data |         - Store data | ||||||
|         - Validate this data |         - Validate this data | ||||||
|     * FormInputMetadata: |     * FormEntry: *shell of this atom* | ||||||
|         - Store infos and options about an input. |  | ||||||
|     * FormEntry: |  | ||||||
|         - Associate a key to an input |         - Associate a key to an input | ||||||
|         - Configure form attribute (exportation, name...) |         - 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 `FormInputValidator` for validation |         - You can use several pre configured FormInput for validation | ||||||
|         - You can use updateFormData() to change `FormData` during runtime (intersection, union, difference or replace) |         - You can use updateFormData() to change FormData and validators during runtime (intersection, union, difference or replace) | ||||||
| 
 | 
 | ||||||
| - Consistent | - Consistent | ||||||
|     * Every class have same naming convention |     * Every class have same naming convention | ||||||
| @ -63,120 +59,4 @@ import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; | |||||||
| 
 | 
 | ||||||
| ## Usage | ## Usage | ||||||
| 
 | 
 | ||||||
| Firstly, you have to create your form inputs. | todo | ||||||
| 
 |  | ||||||
| ```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,30 +1,10 @@ | |||||||
| # 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. | # This file should be version controlled and should not be manually edited. | ||||||
| 
 | 
 | ||||||
| version: | version: | ||||||
|   revision: 85684f9300908116a78138ea4c6036c35c9a1236 |   revision: 5464c5bac742001448fe4fc0597be939379f88ea | ||||||
|   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,37 +16,31 @@ | |||||||
| 
 | 
 | ||||||
| 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<FormInput> getNormalEntries() { |   static List<FormEntry> getNormalEntries() { | ||||||
|     return const [ |     return const [ | ||||||
|       FormInput(formFieldName, Name.pure(), metadata: FormInputMetadata<Metadata>(extra: blue)), |       FormEntry(formFieldName, Name.pure()), | ||||||
|       FormInput(formFieldEmail, Email.pure(), metadata: FormInputMetadata<Metadata>(extra: blue)), |       FormEntry(formFieldEmail, Email.pure()), | ||||||
|       FormInput(formFieldPhone, Phone.pure(), metadata: FormInputMetadata<Metadata>(extra: red)), |       FormEntry(formFieldPhone, Phone.pure()), | ||||||
|       FormInput( |       FormEntry( | ||||||
|           formFieldList, ListOption<String>.pure(defaultValue: ['checkbox3'])), |           formFieldList, ListOption<String>.pure(defaultValue: ['checkbox3'])), | ||||||
|       FormInput(formFieldRadio, TextString.pure()), |       FormEntry(formFieldRadio, TextString.pure()), | ||||||
|       FormInput(formFieldPro, Boolean.pure(), |       FormEntry(formFieldPro, Boolean.pure(), name: 'business'), | ||||||
|           metadata: FormInputMetadata<Metadata>(name: 'business')), |       FormEntry(formFieldHidden, Boolean.pure(), export: false), | ||||||
|       FormInput(formFieldHidden, Boolean.pure(), |  | ||||||
|           metadata: FormInputMetadata<Metadata>(export: false, extra: blue)), |  | ||||||
|     ]; |     ]; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   static List<FormInput> getBusinessEntries() { |   static List<FormEntry> getBusinessEntries() { | ||||||
|     const entries = [ |     const entries = [ | ||||||
|       FormInput(formFieldSiren, Siren.pure()), |       FormEntry(formFieldSiren, Siren.pure()), | ||||||
|       FormInput(formFieldIban, Iban.pure()), |       FormEntry(formFieldIban, Iban.pure()), | ||||||
|     ]; |     ]; | ||||||
|     return getNormalEntries() + entries; |     return getNormalEntries() + entries; | ||||||
|   } |   } | ||||||
| @ -61,7 +55,7 @@ class App extends StatelessWidget { | |||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     FormDataCubit _formCubit = CustomFormCubit(inputs: getNormalFormData()); |     FormDataCubit _formCubit = CustomFormCubit(entries: getNormalFormData()); | ||||||
| 
 | 
 | ||||||
|     return BlocProvider( |     return BlocProvider( | ||||||
|       create: (context) => _formCubit, |       create: (context) => _formCubit, | ||||||
|  | |||||||
| @ -16,10 +16,13 @@ | |||||||
| 
 | 
 | ||||||
| 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 inputs}) : super(inputs: inputs); |   CustomFormCubit({required FormData entries}) : super(entries: entries); | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   Future<void> submitForm() { |   Future<void> submitForm() { | ||||||
|  | |||||||
| @ -1,4 +1,3 @@ | |||||||
| // 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. | ||||||
| // | // | ||||||
| @ -15,20 +14,9 @@ | |||||||
| // You should have received a copy of the GNU General Public License | // You should have received a copy of the GNU General Public License | ||||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||||
| 
 | 
 | ||||||
| enum Category { | part of 'custom_form_cubit.dart'; | ||||||
|   perso, |  | ||||||
|   business |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| class Metadata { | @immutable | ||||||
|   final Category category; | abstract class CustomFormState {} | ||||||
| 
 | 
 | ||||||
|   const Metadata({ | class CustomFormInitial extends CustomFormState {} | ||||||
|     required this.category, |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   @override |  | ||||||
|   String toString() { |  | ||||||
|     return category.toString(); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -19,83 +19,35 @@ 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 CategoryIndicator( |     return BlocBuilder<FormDataCubit, FormDataState>( | ||||||
|         field: formFieldName, |       builder: (context, state) { | ||||||
|         builder: (context, state) { |         return TextField( | ||||||
|           return TextField( |           onChanged: (name) => context | ||||||
|             onChanged: (name) => context |               .read<FormDataCubit>() | ||||||
|                 .read<FormDataCubit>() |               .dataChanged(formFieldName, Name.dirty(name)), | ||||||
|                 .dataChanged(formFieldName, Name.dirty(name)), |           keyboardType: TextInputType.name, | ||||||
|             keyboardType: TextInputType.name, |           decoration: InputDecoration( | ||||||
|             decoration: InputDecoration( |             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 CategoryIndicator( |     return BlocBuilder<FormDataCubit, FormDataState>( | ||||||
|         field: formFieldEmail, |  | ||||||
|       builder: (context, state) { |       builder: (context, state) { | ||||||
|         return TextField( |         return TextField( | ||||||
|           onChanged: (email) => context |           onChanged: (email) => context | ||||||
| @ -105,8 +57,9 @@ class _EmailInput extends StatelessWidget { | |||||||
|           decoration: InputDecoration( |           decoration: InputDecoration( | ||||||
|             labelText: 'email', |             labelText: 'email', | ||||||
|             helperText: '', |             helperText: '', | ||||||
|             errorText: |             errorText: state.data.input(formFieldEmail).invalid | ||||||
|                 state.data.isNotValid(formFieldEmail) ? 'invalid email' : null, |                 ? 'invalid email' | ||||||
|  |                 : null, | ||||||
|           ), |           ), | ||||||
|         ); |         ); | ||||||
|       }, |       }, | ||||||
| @ -117,8 +70,7 @@ class _EmailInput extends StatelessWidget { | |||||||
| class _PhoneInput extends StatelessWidget { | class _PhoneInput extends StatelessWidget { | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return CategoryIndicator( |     return BlocBuilder<FormDataCubit, FormDataState>( | ||||||
|         field: formFieldPhone, |  | ||||||
|       builder: (context, state) { |       builder: (context, state) { | ||||||
|         return TextField( |         return TextField( | ||||||
|           onChanged: (phone) => context |           onChanged: (phone) => context | ||||||
| @ -128,8 +80,9 @@ class _PhoneInput extends StatelessWidget { | |||||||
|           decoration: InputDecoration( |           decoration: InputDecoration( | ||||||
|             labelText: 'phone', |             labelText: 'phone', | ||||||
|             helperText: '', |             helperText: '', | ||||||
|             errorText: |             errorText: state.data.input(formFieldPhone).invalid | ||||||
|                 state.data.isNotValid(formFieldPhone) ? 'invalid phone' : null, |                 ? 'invalid phone' | ||||||
|  |                 : null, | ||||||
|           ), |           ), | ||||||
|         ); |         ); | ||||||
|       }, |       }, | ||||||
| @ -140,8 +93,7 @@ class _PhoneInput extends StatelessWidget { | |||||||
| class _SirenInput extends StatelessWidget { | class _SirenInput extends StatelessWidget { | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return CategoryIndicator( |     return BlocBuilder<FormDataCubit, FormDataState>( | ||||||
|         field: formFieldName, |  | ||||||
|       builder: (context, state) { |       builder: (context, state) { | ||||||
|         return TextField( |         return TextField( | ||||||
|           onChanged: (siren) => context |           onChanged: (siren) => context | ||||||
| @ -151,8 +103,9 @@ class _SirenInput extends StatelessWidget { | |||||||
|           decoration: InputDecoration( |           decoration: InputDecoration( | ||||||
|             labelText: 'siren', |             labelText: 'siren', | ||||||
|             helperText: '', |             helperText: '', | ||||||
|             errorText: |             errorText: state.data.input(formFieldSiren).invalid | ||||||
|                 state.data.isNotValid(formFieldSiren) ? 'invalid SIREN' : null, |                 ? 'invalid SIREN' | ||||||
|  |                 : null, | ||||||
|           ), |           ), | ||||||
|         ); |         ); | ||||||
|       }, |       }, | ||||||
| @ -174,7 +127,7 @@ class _IbanInput extends StatelessWidget { | |||||||
|             labelText: 'iban', |             labelText: 'iban', | ||||||
|             helperText: '', |             helperText: '', | ||||||
|             errorText: |             errorText: | ||||||
|                 state.data.isNotValid(formFieldIban) ? 'invalid IBAN' : null, |                 state.data.input(formFieldIban).invalid ? 'invalid IBAN' : null, | ||||||
|           ), |           ), | ||||||
|         ); |         ); | ||||||
|       }, |       }, | ||||||
| @ -188,7 +141,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.validatorOf<ListOption<String>>(formFieldList); |             state.data.input<List<String>>(formFieldList) as ListOption<String>; | ||||||
|         final _options = _input.value; |         final _options = _input.value; | ||||||
| 
 | 
 | ||||||
|         return Column( |         return Column( | ||||||
| @ -239,7 +192,8 @@ class _RadioListInput extends StatelessWidget { | |||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return BlocBuilder<FormDataCubit, FormDataState>( |     return BlocBuilder<FormDataCubit, FormDataState>( | ||||||
|       builder: (context, state) { |       builder: (context, state) { | ||||||
|         final _input = state.data.validatorOf<TextString>(formFieldRadio); |         final _input = | ||||||
|  |             state.data.input<String>(formFieldRadio) as TextString; | ||||||
| 
 | 
 | ||||||
|         return Column( |         return Column( | ||||||
|           mainAxisSize: MainAxisSize.min, |           mainAxisSize: MainAxisSize.min, | ||||||
| @ -295,7 +249,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.validatorOf<Boolean>(formFieldHidden).value, |               value: state.data.input<bool>(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 | ||||||
| 
 | 
 | ||||||
| @ -318,7 +272,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.validatorOf<Boolean>(formFieldPro).value, |               value: state.data.input<bool>(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 | ||||||
| 
 | 
 | ||||||
| @ -416,7 +370,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.validatorOf<Boolean>(formFieldPro).value) { |                 if (state.data.input<bool>(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.17.2 <3.0.0" |   sdk: ">=2.16.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 | ||||||
|  | |||||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 917 B | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 5.2 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 8.1 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 5.5 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 20 KiB | 
| @ -1,58 +0,0 @@ | |||||||
| <!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> |  | ||||||
| @ -1,35 +0,0 @@ | |||||||
| { |  | ||||||
|     "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,7 +17,6 @@ | |||||||
| 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'; | ||||||
| @ -25,19 +24,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 inputs}) |   FormDataCubit({required FormData entries}) | ||||||
|       : super(FormDataState(data: inputs)); |       : super(FormDataState(data: entries)); | ||||||
| 
 | 
 | ||||||
|   /// Change value of a field. |   /// Change value of a field. | ||||||
|   ///  |   ///  | ||||||
|   /// Inputs: |   /// Inputs: | ||||||
|   /// - `field`: The key of the field to change. |   /// - `field`: The key of the field to change. | ||||||
|   /// - `dirtyValue`: The new value of the field. (Wrapped in a dirty validator) |   /// - `dirtyValue`: The new value of the field. | ||||||
|   void dataChanged(String field, FormInputValidator dirtyValue) { |   void dataChanged(String field, FormInput dirtyValue) { | ||||||
|     final _form = state.data.clone(); |     final _form = state.data.clone(); | ||||||
| 
 | 
 | ||||||
|     if (_form.contains(field)) { |     if (_form.contains(field)) { | ||||||
|       _form.updateValidator(field, dirtyValue); |       _form.update(field, dirtyValue); | ||||||
|     } else { |     } else { | ||||||
|       throw Exception('Form field $field not found'); |       throw Exception('Form field $field not found'); | ||||||
|     } |     } | ||||||
| @ -45,7 +44,7 @@ abstract class FormDataCubit extends Cubit<FormDataState> { | |||||||
|     emit( |     emit( | ||||||
|       state.copyWith( |       state.copyWith( | ||||||
|         data: _form, |         data: _form, | ||||||
|         status: _form.validate(), |         status: _form.selfValidate(), | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| @ -79,7 +78,7 @@ abstract class FormDataCubit extends Cubit<FormDataState> { | |||||||
|     emit( |     emit( | ||||||
|       state.copyWith( |       state.copyWith( | ||||||
|         data: _form, |         data: _form, | ||||||
|         status: _form.validate(), |         status: _form.selfValidate(), | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -17,7 +17,7 @@ | |||||||
| part of 'form_data_cubit.dart'; | part of 'form_data_cubit.dart'; | ||||||
| 
 | 
 | ||||||
| @immutable | @immutable | ||||||
| class FormDataState extends Equatable { | class FormDataState { | ||||||
|   final FormStatus status; |   final FormStatus status; | ||||||
|   final FormData data; |   final FormData data; | ||||||
|   final String? errorMessage; |   final String? errorMessage; | ||||||
| @ -41,8 +41,23 @@ class FormDataState extends Equatable { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   bool? get stringify => true; |   bool operator ==(Object other) { | ||||||
|  |     if (identical(this, other)) return true; | ||||||
|  | 
 | ||||||
|  |     return other is FormDataState && | ||||||
|  |         other.status == status && | ||||||
|  |         other.data == data && | ||||||
|  |         other.errorMessage == errorMessage; | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   List<Object?> get props => [status, data, errorMessage]; |   int get hashCode { | ||||||
|  |     return status.hashCode ^ data.hashCode ^ errorMessage.hashCode; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   String toString() { | ||||||
|  |     return 'FormDataState(status: $status, data: $data, ' | ||||||
|  |         'errorMessage: $errorMessage)'; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -14,16 +14,6 @@ | |||||||
| // You should have received a copy of the GNU General Public License | // You should have received a copy of the GNU General Public License | ||||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||||
| 
 | 
 | ||||||
| import 'package:wyatt_form_bloc/src/form/form.dart'; |  | ||||||
| 
 |  | ||||||
| const Set<FormStatus> _validatedFormStatuses = <FormStatus>{ |  | ||||||
|   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. | ||||||
| @ -45,8 +35,19 @@ 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; | ||||||
| 
 | 
 | ||||||
| @ -75,13 +76,4 @@ enum FormStatus { | |||||||
| 
 | 
 | ||||||
|   /// Indicates whether the form submission has been canceled. |   /// Indicates whether the form submission has been canceled. | ||||||
|   bool get isSubmissionCanceled => this == FormStatus.submissionCanceled; |   bool get isSubmissionCanceled => this == FormStatus.submissionCanceled; | ||||||
| 
 |  | ||||||
|   /// Validate a list of inputs |  | ||||||
|   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,12 +14,6 @@ | |||||||
| // You should have received a copy of the GNU General Public License | // You should have received a copy of the GNU General Public License | ||||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||||
| 
 | 
 | ||||||
| import 'package:equatable/equatable.dart'; | export 'form_data.dart'; | ||||||
| import 'package:meta/meta.dart'; | export 'form_entry.dart'; | ||||||
| import 'package:wyatt_form_bloc/src/enums/form_input_status.dart'; | export 'form_input.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,164 +14,133 @@ | |||||||
| // 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 'form.dart'; | import 'package:meta/meta.dart'; | ||||||
|  | import 'package:wyatt_form_bloc/src/enums/enums.dart'; | ||||||
|  | import 'package:wyatt_form_bloc/src/form/form.dart'; | ||||||
|  | import 'package:wyatt_form_bloc/src/utils/list_equals.dart'; | ||||||
| 
 | 
 | ||||||
| @immutable | @immutable | ||||||
| class FormData extends Equatable { | class FormData { | ||||||
|   final List<FormInput> _inputs; |   const FormData(this._entries); | ||||||
| 
 | 
 | ||||||
|   const FormData(this._inputs); |   FormData.empty() : this(<FormEntry>[]); | ||||||
|   const FormData.empty() : this(const <FormInput>[]); |  | ||||||
| 
 | 
 | ||||||
|   /// Returns the input for the associated key |   final List<FormEntry> _entries; | ||||||
|   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 _inputs.firstWhere((FormInput input) => input.key == key); |       return _entries.firstWhere((FormEntry entry) => entry.key == key).input | ||||||
|  |           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'); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Updates a input (perform a replace at index). |  | ||||||
|   void updateInput(String key, FormInput input) { |  | ||||||
|     if (contains(key)) { |  | ||||||
|       final index = _inputs.indexOf( |  | ||||||
|         inputByKey(key), |  | ||||||
|       ); |  | ||||||
|       _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) { |   bool contains(String key) { | ||||||
|     return _inputs.any((FormInput input) => input.key == key); |     return _entries.any((FormEntry entry) => entry.key == key); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Makes an intersection set operation and returns newly created [FormData] |  | ||||||
|   FormData intersection(FormData other) { |   FormData intersection(FormData other) { | ||||||
|     final List<FormInput> inputs = <FormInput>[]; |     final List<FormEntry> entries = <FormEntry>[]; | ||||||
| 
 | 
 | ||||||
|     for (final FormInput i in _inputs) { |     for (final FormEntry entry in _entries) { | ||||||
|       if (other.contains(i.key)) { |       if (other.contains(entry.key)) { | ||||||
|         inputs.add(i); |         entries.add(entry); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return FormData(inputs); |     return FormData(entries); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Makes a difference set operation and returns newly created [FormData] |  | ||||||
|   FormData difference(FormData other) { |   FormData difference(FormData other) { | ||||||
|     final List<FormInput> inputs = <FormInput>[]; |     final List<FormEntry> entries = <FormEntry>[]; | ||||||
| 
 | 
 | ||||||
|     for (final FormInput i in other._inputs) { |     for (final FormEntry otherEntry in other._entries) { | ||||||
|       if (!contains(i.key)) { |       if (!contains(otherEntry.key)) { | ||||||
|         inputs.add(i); |         entries.add(otherEntry); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     for (final FormInput i in _inputs) { |     for (final FormEntry entry in _entries) { | ||||||
|       if (!other.contains(i.key)) { |       if (!other.contains(entry.key)) { | ||||||
|         inputs.add(i); |         entries.add(entry); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return FormData(inputs); |     return FormData(entries); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Makes an union set operation and returns newly created [FormData] |  | ||||||
|   FormData union(FormData other) { |   FormData union(FormData other) { | ||||||
|     final List<FormInput> inputs = <FormInput>[]; |     final List<FormEntry> entries = <FormEntry>[]; | ||||||
| 
 | 
 | ||||||
|     for (final FormInput i in _inputs) { |     for (final FormEntry entry in _entries) { | ||||||
|       inputs.add(i); |       entries.add(entry); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     for (final FormInput i in other._inputs) { |     for (final FormEntry otherEntry in other._entries) { | ||||||
|       if (!contains(i.key)) { |       if (!contains(otherEntry.key)) { | ||||||
|         inputs.add(i); |         entries.add(otherEntry); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return FormData(inputs); |     return FormData(entries); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   void update(String key, FormInput input) { | ||||||
|  |     if (contains(key)) { | ||||||
|  |       final index = _entries.indexOf( | ||||||
|  |         _entries.firstWhere((FormEntry entry) => entry.key == key), | ||||||
|  |       ); | ||||||
|  |       _entries[index] = _entries[index].copyWith(input: input); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Deeply copy this. |  | ||||||
|   FormData clone() { |   FormData clone() { | ||||||
|     return FormData( |     return FormData( | ||||||
|       _inputs.map((FormInput input) => input.clone()).toList(), |       _entries.map((FormEntry entry) => entry.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 input in _inputs) { |     for (final entry in _entries) { | ||||||
|       if (input.metadata.export) { |       if (entry.export) { | ||||||
|         map[input.name] = input.validator.value; |         map[entry.name] = entry.input.value; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     return map; |     return map; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   bool? get stringify => true; |   bool operator ==(Object other) { | ||||||
|  |     if (identical(this, other)) return true; | ||||||
|  | 
 | ||||||
|  |     return other is FormData && listEquals(other._entries, _entries); | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   List<Object?> get props => _inputs; |   int get hashCode => _entries.hashCode; | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   String toString() => 'FormData(entries: $_entries)'; | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										79
									
								
								packages/wyatt_form_bloc/lib/src/form/form_entry.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								packages/wyatt_form_bloc/lib/src/form/form_entry.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,79 @@ | |||||||
|  | // 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,4 +1,3 @@ | |||||||
| // 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. | ||||||
| // | // | ||||||
| @ -15,45 +14,98 @@ | |||||||
| // 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 'form.dart'; | import 'package:meta/meta.dart'; | ||||||
|  | import 'package:wyatt_form_bloc/src/enums/enums.dart'; | ||||||
| 
 | 
 | ||||||
| class FormInput extends Equatable { | /// {@template form_input} | ||||||
|   final String key; | /// A [FormInput] represents the value of a single form input field. | ||||||
|   final FormInputValidator validator; | /// It contains information about the [FormInputStatus], [value], as well | ||||||
|   final FormInputMetadata metadata; | /// as validation status. | ||||||
|  | /// | ||||||
|  | /// [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]); | ||||||
| 
 | 
 | ||||||
|   String get name => metadata._name ?? key; |   /// Constructor which create a `pure` [FormInput] with a given value. | ||||||
|  |   const FormInput.pure(T value) : this._(value); | ||||||
| 
 | 
 | ||||||
|   const FormInput( |   /// Constructor which create a `dirty` [FormInput] with a given value. | ||||||
|     this.key, |   const FormInput.dirty(T value) : this._(value, false); | ||||||
|     this.validator, { | 
 | ||||||
|     // ignore: avoid_redundant_argument_values |   /// The value of the given [FormInput]. | ||||||
|     this.metadata = const FormInputMetadata<void>(export: true), |   /// 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); | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   bool? get stringify => true; |   int get hashCode => value.hashCode ^ pure.hashCode; | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   List<Object?> get props => [key, validator, metadata]; |   bool operator ==(Object other) { | ||||||
| 
 |     if (other.runtimeType != runtimeType) return false; | ||||||
|   FormInput copyWith({ |     return other is FormInput<T, E> && | ||||||
|     String? key, |         other.value == value && | ||||||
|     FormInputValidator? validator, |         other.pure == pure; | ||||||
|     FormInputMetadata? metadata, |  | ||||||
|   }) { |  | ||||||
|     return FormInput( |  | ||||||
|       key ?? this.key, |  | ||||||
|       validator ?? this.validator, |  | ||||||
|       metadata: metadata ?? this.metadata, |  | ||||||
|     ); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   FormInput clone() { |   @override | ||||||
|     return copyWith( |   String toString() => 'FormInput<$runtimeType>(value: $value, pure: $pure)'; | ||||||
|       key: key, |  | ||||||
|       validator: validator, |  | ||||||
|       metadata: metadata, |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,56 +0,0 @@ | |||||||
| // 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, |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -1,104 +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/>. |  | ||||||
| 
 |  | ||||||
| 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]; |  | ||||||
| } |  | ||||||
							
								
								
									
										25
									
								
								packages/wyatt_form_bloc/lib/src/utils/list_equals.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								packages/wyatt_form_bloc/lib/src/utils/list_equals.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +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 <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 FormInputValidator<bool, ValidationError> { | class Boolean extends FormInput<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 FormInputValidator<String, ValidationError> { |     extends FormInput<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 FormInputValidator<String, ValidationError> { | class Email extends FormInput<String, ValidationError> { | ||||||
|   /// {@macro email} |   /// {@macro email} | ||||||
|   const Email.pure() : super.pure(''); |   const Email.pure() : super.pure(''); | ||||||
| 
 | 
 | ||||||
| @ -28,7 +28,7 @@ class Email extends FormInputValidator<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'[^@ \t\r\n]+@[^@ \t\r\n]+\.[^@ \t\r\n]+', |     r'^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$', | ||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
|   @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 FormInputValidator<String, ValidationError> { | class Iban extends FormInput<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 FormInputValidator<List<T>, ValidationError> { | class ListOption<T> extends FormInput<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 FormInputValidator<String, ValidationError> { | class Name extends FormInput<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 FormInputValidator<String, ValidationError> { | class Password extends FormInput<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 FormInputValidator<String, ValidationError> { | class Phone extends FormInput<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 FormInputValidator<String, ValidationError> { | class Siren extends FormInput<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 FormInputValidator<String, ValidationError> { | class TextString extends FormInput<String, ValidationError> { | ||||||
|   /// {@macro text_string} |   /// {@macro text_string} | ||||||
|   const TextString.pure() : super.pure(''); |   const TextString.pure() : super.pure(''); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -4,15 +4,14 @@ 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.17.2 <3.0.0' |   sdk: '>=2.16.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.4 |   test: ^1.21.0 | ||||||
| 
 | 
 | ||||||
|   wyatt_analysis: |   wyatt_analysis: | ||||||
|     git: |     git: | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user