From fe5fa692f775ac2dfd8955b06be535807c50d3e0 Mon Sep 17 00:00:00 2001 From: Hugo Pointcheval Date: Mon, 2 May 2022 14:41:24 +0200 Subject: [PATCH] feat(form): add list option validator --- .../wyatt_form_bloc/example/lib/app/app.dart | 44 ++--- .../example/lib/constants.dart | 2 + .../example/lib/cubit/custom_form_cubit.dart | 33 ++++ .../example/lib/cubit/custom_form_state.dart | 22 +++ .../lib/sign_up/widgets/sign_up_form.dart | 182 ++++++++++++++---- .../lib/src/cubit/form_data_cubit.dart | 34 ++-- .../lib/src/validators/list_option.dart | 48 +++++ .../lib/src/validators/validators.dart | 1 + 8 files changed, 287 insertions(+), 79 deletions(-) create mode 100644 packages/wyatt_form_bloc/example/lib/cubit/custom_form_cubit.dart create mode 100644 packages/wyatt_form_bloc/example/lib/cubit/custom_form_state.dart create mode 100644 packages/wyatt_form_bloc/lib/src/validators/list_option.dart diff --git a/packages/wyatt_form_bloc/example/lib/app/app.dart b/packages/wyatt_form_bloc/example/lib/app/app.dart index 3f47e228..60d99e66 100644 --- a/packages/wyatt_form_bloc/example/lib/app/app.dart +++ b/packages/wyatt_form_bloc/example/lib/app/app.dart @@ -14,50 +14,48 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -import 'dart:developer'; - import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:form_bloc_example/constants.dart'; +import 'package:form_bloc_example/cubit/custom_form_cubit.dart'; import 'package:form_bloc_example/sign_up/sign_up_page.dart'; import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; class App extends StatelessWidget { const App({Key? key}) : super(key: key); - static FormData getNormalFormData() { - return const FormData([ + static List getNormalEntries() { + return const [ FormEntry(formFieldName, Name.pure()), FormEntry(formFieldEmail, Email.pure()), FormEntry(formFieldPhone, Phone.pure()), + FormEntry( + formFieldList, ListOption.pure(defaultValue: ['checkbox3'])), + FormEntry(formFieldRadio, TextString.pure()), FormEntry(formFieldPro, Boolean.pure(), name: 'business'), FormEntry(formFieldHidden, Boolean.pure(), export: false), - ]); + ]; + } + + static List getBusinessEntries() { + const entries = [ + FormEntry(formFieldSiren, Siren.pure()), + FormEntry(formFieldIban, Iban.pure()), + ]; + return getNormalEntries() + entries; + } + + static FormData getNormalFormData() { + return FormData(getNormalEntries()); } static FormData getProFormData() { - return const FormData([ - FormEntry(formFieldName, Name.pure()), - FormEntry(formFieldEmail, Email.pure()), - FormEntry(formFieldPhone, Phone.pure()), - FormEntry(formFieldPro, Boolean.pure(), name: 'business'), - FormEntry(formFieldHidden, Boolean.pure(), export: false), - FormEntry(formFieldSiren, Siren.pure()), - FormEntry(formFieldIban, Iban.pure()), - ]); - } - - Future onSubmit(FormDataState state) async { - log(state.data.toMap().toString()); - return true; + return FormData(getBusinessEntries()); } @override Widget build(BuildContext context) { - FormDataCubit _formCubit = FormDataCubit( - entries: getNormalFormData(), - onSubmit: onSubmit, - ); + FormDataCubit _formCubit = CustomFormCubit(entries: getNormalFormData()); return BlocProvider( create: (context) => _formCubit, diff --git a/packages/wyatt_form_bloc/example/lib/constants.dart b/packages/wyatt_form_bloc/example/lib/constants.dart index d3bd1850..c1c52795 100644 --- a/packages/wyatt_form_bloc/example/lib/constants.dart +++ b/packages/wyatt_form_bloc/example/lib/constants.dart @@ -19,6 +19,8 @@ const String formFieldPhone = 'phone'; const String formFieldEmail = 'email'; const String formFieldSiren = 'siren'; const String formFieldIban = 'iban'; +const String formFieldList = 'list'; +const String formFieldRadio = 'radio'; const String formFieldHidden = 'hidden'; const String formFieldPro = 'isPro'; \ No newline at end of file diff --git a/packages/wyatt_form_bloc/example/lib/cubit/custom_form_cubit.dart b/packages/wyatt_form_bloc/example/lib/cubit/custom_form_cubit.dart new file mode 100644 index 00000000..6cdae37d --- /dev/null +++ b/packages/wyatt_form_bloc/example/lib/cubit/custom_form_cubit.dart @@ -0,0 +1,33 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'dart:developer'; + +import 'package:meta/meta.dart'; +import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; + +part 'custom_form_state.dart'; + +class CustomFormCubit extends FormDataCubit { + CustomFormCubit({required FormData entries}) : super(entries: entries); + + @override + Future submitForm() { + log(state.data.toMap().toString()); + + return Future.value(); + } +} diff --git a/packages/wyatt_form_bloc/example/lib/cubit/custom_form_state.dart b/packages/wyatt_form_bloc/example/lib/cubit/custom_form_state.dart new file mode 100644 index 00000000..f6993044 --- /dev/null +++ b/packages/wyatt_form_bloc/example/lib/cubit/custom_form_state.dart @@ -0,0 +1,22 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +part of 'custom_form_cubit.dart'; + +@immutable +abstract class CustomFormState {} + +class CustomFormInitial extends CustomFormState {} diff --git a/packages/wyatt_form_bloc/example/lib/sign_up/widgets/sign_up_form.dart b/packages/wyatt_form_bloc/example/lib/sign_up/widgets/sign_up_form.dart index 99fa68ae..82d28ed2 100644 --- a/packages/wyatt_form_bloc/example/lib/sign_up/widgets/sign_up_form.dart +++ b/packages/wyatt_form_bloc/example/lib/sign_up/widgets/sign_up_form.dart @@ -135,6 +135,112 @@ class _IbanInput extends StatelessWidget { } } +class _CheckListInput extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + final _input = + state.data.input>(formFieldList) as ListOption; + final _options = _input.value; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + title: const Text('Checkbox1'), + trailing: Checkbox( + value: _options.contains('checkbox1'), + onChanged: (_) { + context.read().dataChanged( + formFieldList, + _input.select('checkbox1'), + ); + }), + ), + ListTile( + title: const Text('Checkbox2'), + trailing: Checkbox( + value: _options.contains('checkbox2'), + onChanged: (_) { + context.read().dataChanged( + formFieldList, + _input.select('checkbox2'), + ); + }), + ), + ListTile( + title: const Text('Checkbox3 (default)'), + trailing: Checkbox( + value: _options.contains('checkbox3'), + onChanged: (_) { + context.read().dataChanged( + formFieldList, + _input.select('checkbox3'), + ); + }), + ), + ], + ); + }, + ); + } +} + +class _RadioListInput extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + final _input = + state.data.input(formFieldRadio) as TextString; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + title: const Text('Radio1'), + trailing: Radio( + groupValue: true, + value: _input.value == 'radio1', + onChanged: (_) { + context.read().dataChanged( + formFieldRadio, + const TextString.dirty('radio1'), + ); + }), + ), + ListTile( + title: const Text('Radio2'), + trailing: Radio( + groupValue: true, + value: _input.value == 'radio2', + onChanged: (_) { + context.read().dataChanged( + formFieldRadio, + const TextString.dirty('radio2'), + ); + }), + ), + ListTile( + title: const Text('Radio3'), + trailing: Radio( + groupValue: true, + value: _input.value == 'radio3', + onChanged: (_) { + context.read().dataChanged( + formFieldRadio, + const TextString.dirty('radio3'), + ); + }), + ), + ], + ); + }, + ); + } +} + class _CheckHiddenInput extends StatelessWidget { @override Widget build(BuildContext context) { @@ -201,8 +307,7 @@ class _SignUpButton extends StatelessWidget { ? const CircularProgressIndicator() : ElevatedButton( onPressed: state.status.isValidated - ? () => - context.read().submitForm() + ? () => context.read().submitForm() : null, child: const Text('SIGN UP'), ); @@ -244,42 +349,43 @@ class SignUpForm extends StatelessWidget { ); } }, - child: SizedBox( - height: MediaQuery.of(context).size.height, - child: Align( - alignment: const Alignment(0, -1 / 3), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - _NameInput(), - const SizedBox(height: 8), - _EmailInput(), - const SizedBox(height: 8), - _PhoneInput(), - const SizedBox(height: 8), - _CheckHiddenInput(), - const SizedBox(height: 8), - _CheckIsProInput(), - const SizedBox(height: 8), - BlocBuilder( - builder: (context, state) { - if (state.data.input(formFieldPro).value) { - return Column(children: [ - _SirenInput(), - const SizedBox(height: 8), - _IbanInput(), - const SizedBox(height: 8), - ]); - } - return const SizedBox.shrink(); - }, - ), - const SizedBox(height: 8), - _SignUpButton(), - const SizedBox(height: 8), - _DebugButton(), - ], - ), + child: Align( + alignment: const Alignment(0, -1 / 3), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _NameInput(), + const SizedBox(height: 8), + _EmailInput(), + const SizedBox(height: 8), + _PhoneInput(), + const SizedBox(height: 8), + _CheckListInput(), + const SizedBox(height: 8), + _RadioListInput(), + const SizedBox(height: 8), + _CheckHiddenInput(), + const SizedBox(height: 8), + _CheckIsProInput(), + const SizedBox(height: 8), + BlocBuilder( + builder: (context, state) { + if (state.data.input(formFieldPro).value) { + return Column(children: [ + _SirenInput(), + const SizedBox(height: 8), + _IbanInput(), + const SizedBox(height: 8), + ]); + } + return const SizedBox.shrink(); + }, + ), + const SizedBox(height: 8), + _SignUpButton(), + const SizedBox(height: 8), + _DebugButton(), + ], ), ), ); diff --git a/packages/wyatt_form_bloc/lib/src/cubit/form_data_cubit.dart b/packages/wyatt_form_bloc/lib/src/cubit/form_data_cubit.dart index 65706184..728c239d 100644 --- a/packages/wyatt_form_bloc/lib/src/cubit/form_data_cubit.dart +++ b/packages/wyatt_form_bloc/lib/src/cubit/form_data_cubit.dart @@ -23,16 +23,16 @@ import 'package:wyatt_form_bloc/src/form/form.dart'; part 'form_data_state.dart'; -class FormDataCubit extends Cubit { - final Future Function(FormDataState state)? _onSubmit; +abstract class FormDataCubit extends Cubit { + FormDataCubit({required FormData entries}) + : super(FormDataState(data: entries)); - FormDataCubit({ - required FormData entries, - Future Function(FormDataState state)? onSubmit, - }) : _onSubmit = onSubmit, - super(FormDataState(data: entries)); - - void dataChanged(String field, FormInput dirtyValue) { + /// Change value of a field. + /// + /// Inputs: + /// - `field`: The key of the field to change. + /// - `dirtyValue`: The new value of the field. + void dataChanged(String field, FormInput dirtyValue) { final _form = state.data.clone(); if (_form.contains(field)) { @@ -49,6 +49,11 @@ class FormDataCubit extends Cubit { ); } + /// Update entries list. + /// + /// Inputs: + /// - `data`: The new entries list. + /// - `operation`: The operation to perform on the entries set. void updateFormData( FormData data, { SetOperation operation = SetOperation.replace, @@ -78,13 +83,6 @@ class FormDataCubit extends Cubit { ); } - Future submitForm() async { - unawaited( - _onSubmit?.call(state).then((bool reemit) { - if (reemit) { - emit(state); - } - }), - ); - } + /// Submit the form. + Future submitForm(); } diff --git a/packages/wyatt_form_bloc/lib/src/validators/list_option.dart b/packages/wyatt_form_bloc/lib/src/validators/list_option.dart new file mode 100644 index 00000000..13c7dc5c --- /dev/null +++ b/packages/wyatt_form_bloc/lib/src/validators/list_option.dart @@ -0,0 +1,48 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:wyatt_form_bloc/src/enums/enums.dart'; +import 'package:wyatt_form_bloc/src/form/form.dart'; + +/// {@template list_option} +/// Form input for a list input +/// {@endtemplate} +class ListOption extends FormInput, ValidationError> { + /// {@macro list_option} + const ListOption.pure({List? defaultValue}) + : super.pure(defaultValue ?? const []); + + /// {@macro list_option} + const ListOption.dirty({List? value}) : super.dirty(value ?? const []); + + ListOption select(T? v) { + if (v == null) { + return this; + } + if (value.contains(v)) { + final List newValue = List.from(value)..remove(v); + return ListOption.dirty(value: newValue); + } else { + final List newValue = List.from(value)..add(v); + return ListOption.dirty(value: newValue); + } + } + + @override + ValidationError? validator(List? value) { + return value?.isNotEmpty == true ? null : ValidationError.invalid; + } +} diff --git a/packages/wyatt_form_bloc/lib/src/validators/validators.dart b/packages/wyatt_form_bloc/lib/src/validators/validators.dart index 809c0005..170a7993 100644 --- a/packages/wyatt_form_bloc/lib/src/validators/validators.dart +++ b/packages/wyatt_form_bloc/lib/src/validators/validators.dart @@ -18,6 +18,7 @@ export 'boolean.dart'; export 'confirmed_password.dart'; export 'email.dart'; export 'iban.dart'; +export 'list_option.dart'; export 'name.dart'; export 'password.dart'; export 'phone.dart';