feat(form): add list option validator
This commit is contained in:
parent
5ea5c510cb
commit
fe5fa692f7
@ -14,50 +14,48 @@
|
|||||||
// 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 '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/constants.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: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';
|
||||||
|
|
||||||
class App extends StatelessWidget {
|
class App extends StatelessWidget {
|
||||||
const App({Key? key}) : super(key: key);
|
const App({Key? key}) : super(key: key);
|
||||||
|
|
||||||
static FormData getNormalFormData() {
|
static List<FormEntry> getNormalEntries() {
|
||||||
return const FormData([
|
return const [
|
||||||
FormEntry(formFieldName, Name.pure()),
|
FormEntry(formFieldName, Name.pure()),
|
||||||
FormEntry(formFieldEmail, Email.pure()),
|
FormEntry(formFieldEmail, Email.pure()),
|
||||||
FormEntry(formFieldPhone, Phone.pure()),
|
FormEntry(formFieldPhone, Phone.pure()),
|
||||||
|
FormEntry(
|
||||||
|
formFieldList, ListOption<String>.pure(defaultValue: ['checkbox3'])),
|
||||||
|
FormEntry(formFieldRadio, TextString.pure()),
|
||||||
FormEntry(formFieldPro, Boolean.pure(), name: 'business'),
|
FormEntry(formFieldPro, Boolean.pure(), name: 'business'),
|
||||||
FormEntry(formFieldHidden, Boolean.pure(), export: false),
|
FormEntry(formFieldHidden, Boolean.pure(), export: false),
|
||||||
]);
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<FormEntry> getBusinessEntries() {
|
||||||
|
const entries = [
|
||||||
|
FormEntry(formFieldSiren, Siren.pure()),
|
||||||
|
FormEntry(formFieldIban, Iban.pure()),
|
||||||
|
];
|
||||||
|
return getNormalEntries() + entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FormData getNormalFormData() {
|
||||||
|
return FormData(getNormalEntries());
|
||||||
}
|
}
|
||||||
|
|
||||||
static FormData getProFormData() {
|
static FormData getProFormData() {
|
||||||
return const FormData([
|
return FormData(getBusinessEntries());
|
||||||
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<bool> onSubmit(FormDataState state) async {
|
|
||||||
log(state.data.toMap().toString());
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
FormDataCubit _formCubit = FormDataCubit(
|
FormDataCubit _formCubit = CustomFormCubit(entries: getNormalFormData());
|
||||||
entries: getNormalFormData(),
|
|
||||||
onSubmit: onSubmit,
|
|
||||||
);
|
|
||||||
|
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => _formCubit,
|
create: (context) => _formCubit,
|
||||||
|
@ -19,6 +19,8 @@ const String formFieldPhone = 'phone';
|
|||||||
const String formFieldEmail = 'email';
|
const String formFieldEmail = 'email';
|
||||||
const String formFieldSiren = 'siren';
|
const String formFieldSiren = 'siren';
|
||||||
const String formFieldIban = 'iban';
|
const String formFieldIban = 'iban';
|
||||||
|
const String formFieldList = 'list';
|
||||||
|
const String formFieldRadio = 'radio';
|
||||||
const String formFieldHidden = 'hidden';
|
const String formFieldHidden = 'hidden';
|
||||||
|
|
||||||
const String formFieldPro = 'isPro';
|
const String formFieldPro = 'isPro';
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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<void> submitForm() {
|
||||||
|
log(state.data.toMap().toString());
|
||||||
|
|
||||||
|
return Future.value();
|
||||||
|
}
|
||||||
|
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
part of 'custom_form_cubit.dart';
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
abstract class CustomFormState {}
|
||||||
|
|
||||||
|
class CustomFormInitial extends CustomFormState {}
|
@ -135,6 +135,112 @@ class _IbanInput extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _CheckListInput extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<FormDataCubit, FormDataState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final _input =
|
||||||
|
state.data.input<List<String>>(formFieldList) as ListOption<String>;
|
||||||
|
final _options = _input.value;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
title: const Text('Checkbox1'),
|
||||||
|
trailing: Checkbox(
|
||||||
|
value: _options.contains('checkbox1'),
|
||||||
|
onChanged: (_) {
|
||||||
|
context.read<FormDataCubit>().dataChanged(
|
||||||
|
formFieldList,
|
||||||
|
_input.select('checkbox1'),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: const Text('Checkbox2'),
|
||||||
|
trailing: Checkbox(
|
||||||
|
value: _options.contains('checkbox2'),
|
||||||
|
onChanged: (_) {
|
||||||
|
context.read<FormDataCubit>().dataChanged(
|
||||||
|
formFieldList,
|
||||||
|
_input.select('checkbox2'),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: const Text('Checkbox3 (default)'),
|
||||||
|
trailing: Checkbox(
|
||||||
|
value: _options.contains('checkbox3'),
|
||||||
|
onChanged: (_) {
|
||||||
|
context.read<FormDataCubit>().dataChanged(
|
||||||
|
formFieldList,
|
||||||
|
_input.select('checkbox3'),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RadioListInput extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<FormDataCubit, FormDataState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final _input =
|
||||||
|
state.data.input<String>(formFieldRadio) as TextString;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
title: const Text('Radio1'),
|
||||||
|
trailing: Radio<bool>(
|
||||||
|
groupValue: true,
|
||||||
|
value: _input.value == 'radio1',
|
||||||
|
onChanged: (_) {
|
||||||
|
context.read<FormDataCubit>().dataChanged(
|
||||||
|
formFieldRadio,
|
||||||
|
const TextString.dirty('radio1'),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: const Text('Radio2'),
|
||||||
|
trailing: Radio<bool>(
|
||||||
|
groupValue: true,
|
||||||
|
value: _input.value == 'radio2',
|
||||||
|
onChanged: (_) {
|
||||||
|
context.read<FormDataCubit>().dataChanged(
|
||||||
|
formFieldRadio,
|
||||||
|
const TextString.dirty('radio2'),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: const Text('Radio3'),
|
||||||
|
trailing: Radio<bool>(
|
||||||
|
groupValue: true,
|
||||||
|
value: _input.value == 'radio3',
|
||||||
|
onChanged: (_) {
|
||||||
|
context.read<FormDataCubit>().dataChanged(
|
||||||
|
formFieldRadio,
|
||||||
|
const TextString.dirty('radio3'),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _CheckHiddenInput extends StatelessWidget {
|
class _CheckHiddenInput extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -201,8 +307,7 @@ class _SignUpButton extends StatelessWidget {
|
|||||||
? const CircularProgressIndicator()
|
? const CircularProgressIndicator()
|
||||||
: ElevatedButton(
|
: ElevatedButton(
|
||||||
onPressed: state.status.isValidated
|
onPressed: state.status.isValidated
|
||||||
? () =>
|
? () => context.read<FormDataCubit>().submitForm()
|
||||||
context.read<FormDataCubit>().submitForm()
|
|
||||||
: null,
|
: null,
|
||||||
child: const Text('SIGN UP'),
|
child: const Text('SIGN UP'),
|
||||||
);
|
);
|
||||||
@ -244,8 +349,6 @@ class SignUpForm extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: SizedBox(
|
|
||||||
height: MediaQuery.of(context).size.height,
|
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: const Alignment(0, -1 / 3),
|
alignment: const Alignment(0, -1 / 3),
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -257,6 +360,10 @@ class SignUpForm extends StatelessWidget {
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_PhoneInput(),
|
_PhoneInput(),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
_CheckListInput(),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
_RadioListInput(),
|
||||||
|
const SizedBox(height: 8),
|
||||||
_CheckHiddenInput(),
|
_CheckHiddenInput(),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_CheckIsProInput(),
|
_CheckIsProInput(),
|
||||||
@ -281,7 +388,6 @@ class SignUpForm extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,16 +23,16 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
|
|||||||
|
|
||||||
part 'form_data_state.dart';
|
part 'form_data_state.dart';
|
||||||
|
|
||||||
class FormDataCubit extends Cubit<FormDataState> {
|
abstract class FormDataCubit extends Cubit<FormDataState> {
|
||||||
final Future<bool> Function(FormDataState state)? _onSubmit;
|
FormDataCubit({required FormData entries})
|
||||||
|
: super(FormDataState(data: entries));
|
||||||
|
|
||||||
FormDataCubit({
|
/// Change value of a field.
|
||||||
required FormData entries,
|
///
|
||||||
Future<bool> Function(FormDataState state)? onSubmit,
|
/// Inputs:
|
||||||
}) : _onSubmit = onSubmit,
|
/// - `field`: The key of the field to change.
|
||||||
super(FormDataState(data: entries));
|
/// - `dirtyValue`: The new value of the field.
|
||||||
|
void dataChanged(String field, FormInput dirtyValue) {
|
||||||
void dataChanged<T>(String field, FormInput dirtyValue) {
|
|
||||||
final _form = state.data.clone();
|
final _form = state.data.clone();
|
||||||
|
|
||||||
if (_form.contains(field)) {
|
if (_form.contains(field)) {
|
||||||
@ -49,6 +49,11 @@ class FormDataCubit extends Cubit<FormDataState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update entries list.
|
||||||
|
///
|
||||||
|
/// Inputs:
|
||||||
|
/// - `data`: The new entries list.
|
||||||
|
/// - `operation`: The operation to perform on the entries set.
|
||||||
void updateFormData(
|
void updateFormData(
|
||||||
FormData data, {
|
FormData data, {
|
||||||
SetOperation operation = SetOperation.replace,
|
SetOperation operation = SetOperation.replace,
|
||||||
@ -78,13 +83,6 @@ class FormDataCubit extends Cubit<FormDataState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> submitForm() async {
|
/// Submit the form.
|
||||||
unawaited(
|
Future<void> submitForm();
|
||||||
_onSubmit?.call(state).then((bool reemit) {
|
|
||||||
if (reemit) {
|
|
||||||
emit(state);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
48
packages/wyatt_form_bloc/lib/src/validators/list_option.dart
Normal file
48
packages/wyatt_form_bloc/lib/src/validators/list_option.dart
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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<T> extends FormInput<List<T>, ValidationError> {
|
||||||
|
/// {@macro list_option}
|
||||||
|
const ListOption.pure({List<T>? defaultValue})
|
||||||
|
: super.pure(defaultValue ?? const []);
|
||||||
|
|
||||||
|
/// {@macro list_option}
|
||||||
|
const ListOption.dirty({List<T>? value}) : super.dirty(value ?? const []);
|
||||||
|
|
||||||
|
ListOption select(T? v) {
|
||||||
|
if (v == null) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
if (value.contains(v)) {
|
||||||
|
final List<T> newValue = List.from(value)..remove(v);
|
||||||
|
return ListOption<T>.dirty(value: newValue);
|
||||||
|
} else {
|
||||||
|
final List<T> newValue = List.from(value)..add(v);
|
||||||
|
return ListOption<T>.dirty(value: newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ValidationError? validator(List<T>? value) {
|
||||||
|
return value?.isNotEmpty == true ? null : ValidationError.invalid;
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,7 @@ export 'boolean.dart';
|
|||||||
export 'confirmed_password.dart';
|
export 'confirmed_password.dart';
|
||||||
export 'email.dart';
|
export 'email.dart';
|
||||||
export 'iban.dart';
|
export 'iban.dart';
|
||||||
|
export 'list_option.dart';
|
||||||
export 'name.dart';
|
export 'name.dart';
|
||||||
export 'password.dart';
|
export 'password.dart';
|
||||||
export 'phone.dart';
|
export 'phone.dart';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user