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
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
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<FormEntry> getNormalEntries() {
|
||||
return const [
|
||||
FormEntry(formFieldName, Name.pure()),
|
||||
FormEntry(formFieldEmail, Email.pure()),
|
||||
FormEntry(formFieldPhone, Phone.pure()),
|
||||
FormEntry(
|
||||
formFieldList, ListOption<String>.pure(defaultValue: ['checkbox3'])),
|
||||
FormEntry(formFieldRadio, TextString.pure()),
|
||||
FormEntry(formFieldPro, Boolean.pure(), name: 'business'),
|
||||
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() {
|
||||
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<bool> 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,
|
||||
|
@ -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';
|
@ -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 {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -201,8 +307,7 @@ class _SignUpButton extends StatelessWidget {
|
||||
? const CircularProgressIndicator()
|
||||
: ElevatedButton(
|
||||
onPressed: state.status.isValidated
|
||||
? () =>
|
||||
context.read<FormDataCubit>().submitForm()
|
||||
? () => context.read<FormDataCubit>().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<FormDataCubit, FormDataState>(
|
||||
builder: (context, state) {
|
||||
if (state.data.input<bool>(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<FormDataCubit, FormDataState>(
|
||||
builder: (context, state) {
|
||||
if (state.data.input<bool>(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(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -23,16 +23,16 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
|
||||
|
||||
part 'form_data_state.dart';
|
||||
|
||||
class FormDataCubit extends Cubit<FormDataState> {
|
||||
final Future<bool> Function(FormDataState state)? _onSubmit;
|
||||
abstract class FormDataCubit extends Cubit<FormDataState> {
|
||||
FormDataCubit({required FormData entries})
|
||||
: super(FormDataState(data: entries));
|
||||
|
||||
FormDataCubit({
|
||||
required FormData entries,
|
||||
Future<bool> Function(FormDataState state)? onSubmit,
|
||||
}) : _onSubmit = onSubmit,
|
||||
super(FormDataState(data: entries));
|
||||
|
||||
void dataChanged<T>(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<FormDataState> {
|
||||
);
|
||||
}
|
||||
|
||||
/// 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<FormDataState> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> submitForm() async {
|
||||
unawaited(
|
||||
_onSubmit?.call(state).then((bool reemit) {
|
||||
if (reemit) {
|
||||
emit(state);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
/// Submit the form.
|
||||
Future<void> submitForm();
|
||||
}
|
||||
|
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 'email.dart';
|
||||
export 'iban.dart';
|
||||
export 'list_option.dart';
|
||||
export 'name.dart';
|
||||
export 'password.dart';
|
||||
export 'phone.dart';
|
||||
|
Loading…
x
Reference in New Issue
Block a user