feat!(form): migrate to wyatt architecture

This commit is contained in:
Hugo Pointcheval 2022-11-09 22:35:41 -05:00
parent 79fdd3c837
commit b89ef3de8a
Signed by: hugo
GPG Key ID: A9E8E9615379254F
62 changed files with 1387 additions and 818 deletions

View File

@ -16,16 +16,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
# Dart - Form BLoC
# Flutter - Form BLoC
<p align="left">
<a href="https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_analysis">
<img src="https://img.shields.io/badge/Style-Wyatt%20Analysis-blue.svg?style=flat-square" alt="Style: Wyatt Analysis" />
</a>
<img src="https://img.shields.io/badge/SDK-Dart%20%7C%20Flutter-blue?style=flat-square" alt="SDK: Dart & Flutter" />
<img src="https://img.shields.io/badge/SDK-Flutter-blue?style=flat-square" alt="SDK: Flutter" />
</p>
Form Bloc for Dart & Flutter.
Form Bloc for Flutter.
## Features

View File

@ -1,4 +1,4 @@
include: package:wyatt_analysis/analysis_options.yaml
include: package:wyatt_analysis/analysis_options.flutter.yaml
analyzer:
exclude: "!example/**"

View File

@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>9.0</string>
<string>11.0</string>
</dict>
</plist>

View File

@ -272,7 +272,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@ -349,7 +349,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@ -398,7 +398,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;

View File

@ -43,5 +43,7 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
</dict>
</plist>

View File

@ -18,7 +18,7 @@ import 'package:flutter/material.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/cubit/custom_form_cubit.dart';
import 'package:form_bloc_example/simple_custom_form_cubit/simple_custom_form_cubit.dart';
import 'package:form_bloc_example/sign_up/sign_up_page.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
@ -29,41 +29,43 @@ class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
static List<FormInput> getNormalEntries() {
return const [
FormInput(formFieldName, Name.pure(), metadata: FormInputMetadata<Metadata>(extra: blue)),
FormInput(formFieldEmail, Email.pure(), metadata: FormInputMetadata<Metadata>(extra: blue)),
FormInput(formFieldPhone, Phone.pure(), metadata: FormInputMetadata<Metadata>(extra: red)),
FormInput(
formFieldList, ListOption<String>.pure(defaultValue: ['checkbox3'])),
FormInput(formFieldRadio, TextString.pure()),
FormInput(formFieldPro, Boolean.pure(),
metadata: FormInputMetadata<Metadata>(name: 'business')),
FormInput(formFieldHidden, Boolean.pure(),
metadata: FormInputMetadata<Metadata>(export: false, extra: blue)),
return [
FormInput(formFieldName, const Name.pure(),
metadata: const FormInputMetadata<Metadata>(extra: blue)),
FormInput(formFieldEmail, const Email.pure(),
metadata: const FormInputMetadata<Metadata>(extra: blue)),
FormInput(formFieldList,
const ListOption<String>.pure(defaultValue: 'c3')),
FormInput(formFieldRadio, const TextString.pure()),
FormInput(formFieldPro, const Boolean.pure(),
metadata: const FormInputMetadata<Metadata>(name: 'business')),
FormInput(formFieldHidden, const Boolean.pure(),
metadata:
const FormInputMetadata<Metadata>(export: false, extra: blue)),
];
}
static List<FormInput> getBusinessEntries() {
const entries = [
FormInput(formFieldSiren, Siren.pure()),
FormInput(formFieldIban, Iban.pure()),
final entries = [
FormInput(formFieldPhone, const Phone.pure(),
metadata: const FormInputMetadata<Metadata>(extra: red)),
];
return getNormalEntries() + entries;
}
static FormData getNormalFormData() {
return FormData(getNormalEntries());
static WyattForm getNormalFormData() {
return WyattFormImpl(getNormalEntries());
}
static FormData getProFormData() {
return FormData(getBusinessEntries());
static WyattForm getProFormData() {
return WyattFormImpl(getBusinessEntries());
}
@override
Widget build(BuildContext context) {
FormDataCubit formCubit = CustomFormCubit(inputs: getNormalFormData());
SimpleCustomFormCubit formCubit = SimpleCustomFormCubit(getNormalFormData());
return BlocProvider(
return BlocProvider<SimpleCustomFormCubit>(
create: (context) => formCubit,
child: const WidgetTree(),
);

View File

@ -28,7 +28,5 @@ class Metadata {
});
@override
String toString() {
return category.toString();
}
String toString() => 'Metadata(category: $category)';
}

View File

@ -17,8 +17,6 @@
const String formFieldName = 'name';
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';

View File

@ -14,39 +14,33 @@
// 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/app/app.dart';
import 'package:form_bloc_example/app/metadata.dart';
import 'package:form_bloc_example/constants.dart';
import 'package:form_bloc_example/simple_custom_form_cubit/simple_custom_form_cubit.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) {
Color computeColor(Metadata? meta) {
switch (meta?.category) {
case Category.perso:
return Colors.blue;
case Category.business:
return Colors.red;
case null:
return Colors.green;
}
}
}
class _NameInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<FormDataCubit, FormDataState>(
builder: ((context, state) {
final meta = state.data.metadataOf(field).extra as Metadata;
return InputBuilderMetadata<SimpleCustomFormCubit, Metadata>(
field: formFieldName,
builder: (context, cubit, state, field, inputValid, metadata) {
final meta = state.form.metadataOf<Metadata>(field).extra;
final color = computeColor(meta);
return Row(
children: [
Container(
@ -57,57 +51,23 @@ class CategoryIndicator extends StatelessWidget {
borderRadius: BorderRadius.circular(8),
),
),
const SizedBox(width: 20,),
const SizedBox(
width: 20,
),
SizedBox(
width: MediaQuery.of(context).size.width - 45,
child: builder(context, state),
)
],
);
}),
);
}
}
class _NameInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CategoryIndicator(
field: formFieldName,
builder: (context, state) {
return TextField(
onChanged: (name) => context
.read<FormDataCubit>()
.dataChanged(formFieldName, Name.dirty(name)),
child: TextField(
onChanged: (value) =>
cubit.dataChanged(field, Name.dirty(value)),
keyboardType: TextInputType.name,
decoration: InputDecoration(
labelText: 'name',
helperText: '',
errorText:
state.data.isNotValid(formFieldName) ? 'invalid name' : null,
errorText: !inputValid ? 'invalid name' : null,
),
);
});
}
}
class _EmailInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CategoryIndicator(
field: formFieldEmail,
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,
),
)
],
);
},
);
@ -117,79 +77,70 @@ class _EmailInput extends StatelessWidget {
class _PhoneInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CategoryIndicator(
return InputBuilderMetadata<SimpleCustomFormCubit, Metadata>(
field: formFieldPhone,
builder: (context, state) {
return TextField(
onChanged: (phone) => context
.read<FormDataCubit>()
.dataChanged(formFieldPhone, Phone.dirty(phone)),
builder: (context, cubit, state, field, inputValid, metadata) {
final meta = state.form.metadataOf<Metadata>(field).extra;
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: TextField(
onChanged: (value) =>
cubit.dataChanged(field, Phone.dirty(value)),
keyboardType: TextInputType.phone,
decoration: InputDecoration(
labelText: 'phone',
helperText: '',
errorText:
state.data.isNotValid(formFieldPhone) ? 'invalid phone' : null,
errorText: !inputValid ? 'invalid phone' : null,
),
),
)
],
);
},
);
}
}
class _SirenInput extends StatelessWidget {
class _EmailInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CategoryIndicator(
field: formFieldName,
builder: (context, state) {
return TextField(
onChanged: (siren) => context
.read<FormDataCubit>()
.dataChanged(formFieldSiren, Siren.dirty(siren)),
keyboardType: TextInputType.number,
return InputBuilder<SimpleCustomFormCubit>(
field: formFieldEmail,
builder: (context, cubit, state, field, inputValid) => TextField(
onChanged: (value) => cubit.dataChanged(field, Email.dirty(value)),
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'siren',
labelText: 'email',
helperText: '',
errorText:
state.data.isNotValid(formFieldSiren) ? 'invalid SIREN' : null,
errorText: !inputValid ? 'invalid email' : null,
),
);
},
);
}
}
class _IbanInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<FormDataCubit, FormDataState>(
builder: (context, state) {
return TextField(
onChanged: (iban) => context
.read<FormDataCubit>()
.dataChanged(formFieldIban, Iban.dirty(iban)),
keyboardType: TextInputType.text,
decoration: InputDecoration(
labelText: 'iban',
helperText: '',
errorText:
state.data.isNotValid(formFieldIban) ? 'invalid IBAN' : null,
),
);
},
);
}
}
class _CheckListInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<FormDataCubit, FormDataState>(
builder: (context, state) {
final input =
state.data.validatorOf<ListOption<String>>(formFieldList);
final options = input.value;
return InputBuilder<SimpleCustomFormCubit>(
field: formFieldList,
builder: (context, cubit, state, field, inputValid) {
final input = state.form.validatorOf<ListOption<String>>(field);
final choices = ['c1', 'c2', 'c3'];
return Column(
mainAxisSize: MainAxisSize.min,
@ -197,33 +148,33 @@ class _CheckListInput extends StatelessWidget {
ListTile(
title: const Text('Checkbox1'),
trailing: Checkbox(
value: options.contains('checkbox1'),
value: input.value == choices[0],
onChanged: (_) {
context.read<FormDataCubit>().dataChanged(
formFieldList,
input.select('checkbox1'),
cubit.dataChanged(
field,
ListOption.dirty(choices: choices, value: choices[0]),
);
}),
),
ListTile(
title: const Text('Checkbox2'),
trailing: Checkbox(
value: options.contains('checkbox2'),
value: input.value == choices[1],
onChanged: (_) {
context.read<FormDataCubit>().dataChanged(
formFieldList,
input.select('checkbox2'),
cubit.dataChanged(
field,
ListOption.dirty(choices: choices, value: choices[1]),
);
}),
),
ListTile(
title: const Text('Checkbox3 (default)'),
trailing: Checkbox(
value: options.contains('checkbox3'),
value: input.value == choices[2],
onChanged: (_) {
context.read<FormDataCubit>().dataChanged(
formFieldList,
input.select('checkbox3'),
cubit.dataChanged(
field,
ListOption.dirty(choices: choices, value: choices[2]),
);
}),
),
@ -237,9 +188,10 @@ class _CheckListInput extends StatelessWidget {
class _RadioListInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<FormDataCubit, FormDataState>(
builder: (context, state) {
final input = state.data.validatorOf<TextString>(formFieldRadio);
return InputBuilder<SimpleCustomFormCubit>(
field: formFieldRadio,
builder: ((context, cubit, state, field, inputValid) {
final input = state.form.validatorOf<TextString>(field);
return Column(
mainAxisSize: MainAxisSize.min,
@ -250,8 +202,8 @@ class _RadioListInput extends StatelessWidget {
groupValue: true,
value: input.value == 'radio1',
onChanged: (_) {
context.read<FormDataCubit>().dataChanged(
formFieldRadio,
cubit.dataChanged(
field,
const TextString.dirty('radio1'),
);
}),
@ -262,8 +214,8 @@ class _RadioListInput extends StatelessWidget {
groupValue: true,
value: input.value == 'radio2',
onChanged: (_) {
context.read<FormDataCubit>().dataChanged(
formFieldRadio,
cubit.dataChanged(
field,
const TextString.dirty('radio2'),
);
}),
@ -274,15 +226,15 @@ class _RadioListInput extends StatelessWidget {
groupValue: true,
value: input.value == 'radio3',
onChanged: (_) {
context.read<FormDataCubit>().dataChanged(
formFieldRadio,
cubit.dataChanged(
field,
const TextString.dirty('radio3'),
);
}),
),
],
);
},
}),
);
}
}
@ -292,15 +244,16 @@ class _CheckHiddenInput extends StatelessWidget {
Widget build(BuildContext context) {
return ListTile(
title: const Text('This input is not exported'),
trailing: BlocBuilder<FormDataCubit, FormDataState>(
builder: (context, state) {
trailing: InputBuilder<SimpleCustomFormCubit>(
field: formFieldHidden,
builder: (context, cubit, state, field, inputValid) {
return Checkbox(
value: state.data.validatorOf<Boolean>(formFieldHidden).value,
value: state.form.validatorOf<Boolean>(field).value,
onChanged: (v) {
final value = v!; // state is false, so value can't be null
context.read<FormDataCubit>().dataChanged(
formFieldHidden,
cubit.dataChanged(
field,
Boolean.dirty(value: value),
);
});
@ -315,25 +268,24 @@ class _CheckIsProInput extends StatelessWidget {
Widget build(BuildContext context) {
return ListTile(
title: const Text('Are you a pro?'),
trailing: BlocBuilder<FormDataCubit, FormDataState>(
builder: (context, state) {
trailing: InputBuilder<SimpleCustomFormCubit>(
field: formFieldPro,
builder: (context, cubit, state, field, inputValid) {
return Checkbox(
value: state.data.validatorOf<Boolean>(formFieldPro).value,
onChanged: (isPro) {
final value = isPro!; // state is false, so value can't be null
value: state.form.validatorOf<Boolean>(field).value,
onChanged: (v) {
final value = v!; // state is false, so value can't be null
context.read<FormDataCubit>().dataChanged(
formFieldPro,
cubit.dataChanged(
field,
Boolean.dirty(value: value),
);
if (value) {
context.read<FormDataCubit>().updateFormData(
App.getProFormData(),
cubit.update(App.getProFormData(),
operation: SetOperation.union);
} else {
context.read<FormDataCubit>().updateFormData(
App.getNormalFormData(),
cubit.update(App.getNormalFormData(),
operation: SetOperation.intersection);
}
});
@ -346,49 +298,28 @@ class _CheckIsProInput extends StatelessWidget {
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
return SubmitBuilder<SimpleCustomFormCubit>(
builder: (context, cubit, status) {
return status.isSubmissionInProgress
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: state.status.isValidated
? () => context.read<FormDataCubit>().submitForm()
: null,
onPressed: status.isValidated ? () => cubit.submit() : null,
child: const Text('SIGN UP'),
);
},
);
}
}
class _DebugButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<FormDataCubit, FormDataState>(
builder: (context, state) {
return ElevatedButton(
onPressed: () {
log(state.toString());
},
child: const Text('DEBUG'),
);
},
);
});
}
}
class _ResetButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<FormDataCubit, FormDataState>(
builder: (context, state) {
return SubmitBuilder<SimpleCustomFormCubit>(
builder: (context, cubit, status) {
return ElevatedButton(
onPressed: () => context.read<FormDataCubit>().resetForm(),
onPressed: () => cubit.reset(),
child: const Text('RESET'),
);
},
);
});
}
}
@ -397,7 +328,7 @@ class SignUpForm extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocListener<FormDataCubit, FormDataState>(
return BlocListener<SimpleCustomFormCubit, FormDataState>(
listener: (context, state) {
if (state.status.isSubmissionSuccess) {
Navigator.of(context).pop(); // Never happens here
@ -418,8 +349,6 @@ class SignUpForm extends StatelessWidget {
const SizedBox(height: 8),
_EmailInput(),
const SizedBox(height: 8),
_PhoneInput(),
const SizedBox(height: 8),
_CheckListInput(),
const SizedBox(height: 8),
_RadioListInput(),
@ -428,13 +357,12 @@ class SignUpForm extends StatelessWidget {
const SizedBox(height: 8),
_CheckIsProInput(),
const SizedBox(height: 8),
BlocBuilder<FormDataCubit, FormDataState>(
BlocBuilder<SimpleCustomFormCubit, FormDataState>(
builder: (context, state) {
if (state.data.validatorOf<Boolean>(formFieldPro).value) {
if (state.form.validatorOf<Boolean>(formFieldPro).value ??
false) {
return Column(children: [
_SirenInput(),
const SizedBox(height: 8),
_IbanInput(),
_PhoneInput(),
const SizedBox(height: 8),
]);
}
@ -444,8 +372,6 @@ class SignUpForm extends StatelessWidget {
const SizedBox(height: 8),
_SignUpButton(),
const SizedBox(height: 8),
_DebugButton(),
const SizedBox(height: 8),
_ResetButton(),
],
),

View File

@ -18,12 +18,13 @@ import 'dart:developer';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
class CustomFormCubit extends FormDataCubit {
CustomFormCubit({required FormData inputs}) : super(inputs: inputs);
class SimpleCustomFormCubit extends FormDataCubitImpl {
SimpleCustomFormCubit(super.form) : super();
@override
Future<void> submitForm() {
log(state.data.toMap().toString());
Future<void> submit() {
final value = FormJsonEncoder().encode(state.form);
log(value);
return Future.value();
}

View File

@ -14,17 +14,4 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'form/every_input_validator.dart';
export 'inputs/base/regex_validator.dart';
export 'inputs/base/text_validator.dart';
export 'inputs/boolean.dart';
export 'inputs/confirmed_password.dart';
export 'inputs/email.dart';
export 'inputs/enum_validator.dart';
export 'inputs/iban.dart';
export 'inputs/list_option.dart';
export 'inputs/name.dart';
export 'inputs/password.dart';
export 'inputs/phone.dart';
export 'inputs/siren.dart';
export 'inputs/text_string.dart';
export 'enums/enums.dart';

View File

@ -0,0 +1,39 @@
// 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/data/form_operations/form_difference.dart';
import 'package:wyatt_form_bloc/src/data/form_operations/form_intersection.dart';
import 'package:wyatt_form_bloc/src/data/form_operations/form_replace.dart';
import 'package:wyatt_form_bloc/src/data/form_operations/form_union.dart';
import 'package:wyatt_form_bloc/src/domain/form_operations/form_operation.dart';
enum SetOperation {
/// Replace entire set with new set.
replace(FormReplace()),
/// Keep common elements between sets.
intersection(FormIntersection()),
/// Remove common elements between sets.
difference(FormDifference()),
/// Add new elements to set.
union(FormUnion());
final FormOperation operation;
const SetOperation(this.operation);
}

View File

@ -23,5 +23,6 @@ abstract class ValidationError {}
enum ValidationStandardError implements ValidationError {
invalid,
empty
empty,
notEqual,
}

View File

@ -1,110 +0,0 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.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/validators/form/every_input_validator.dart';
part 'form_data_state.dart';
abstract class FormDataCubit extends Cubit<FormDataState> {
FormValidator validationStrategy;
FormData formCopy;
FormDataCubit({
required FormData inputs,
FormValidator validator = const EveryInputValidator(),
}) : formCopy = inputs.clone(),
validationStrategy = validator,
super(FormDataState(data: inputs));
/// Change value of a field.
///
/// Inputs:
/// - `field`: The key of the field to change.
/// - `dirtyValue`: The new value of the field. (Wrapped in a dirty validator)
void dataChanged<V>(
String field,
FormInputValidator<V, ValidationError> dirtyValue,
) {
final form = state.data.clone();
if (form.contains(field)) {
form.updateValidator(field, dirtyValue);
} else {
throw Exception('Form field $field not found');
}
emit(
state.copyWith(
data: form,
status: validationStrategy.validate(form),
),
);
}
/// Just validate the form manually. (Useful if you just update the strategy)
void validate() {
final form = state.data;
emit(state.copyWith(status: validationStrategy.validate(form)));
}
/// 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,
}) {
FormData form = data;
switch (operation) {
case SetOperation.replace:
form = data;
break;
case SetOperation.difference:
form = state.data.difference(data);
break;
case SetOperation.intersection:
form = state.data.intersection(data);
break;
case SetOperation.union:
form = state.data.union(data);
break;
}
emit(
state.copyWith(
data: form,
status: validationStrategy.validate(form),
),
);
}
/// Reset all form inputs
void resetForm() {
emit(FormDataState(data: formCopy));
}
/// Submit the form.
Future<void> submitForm();
}

View File

@ -0,0 +1,34 @@
// 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/>.
export 'form/wyatt_form_impl.dart';
export 'form_encoders/form_json_encoder.dart';
export 'form_encoders/form_map_encoder.dart';
export 'form_operations/form_difference.dart';
export 'form_operations/form_intersection.dart';
export 'form_operations/form_replace.dart';
export 'form_operations/form_union.dart';
export 'form_validators/every_input_validator.dart';
export 'form_validators/only_required_input_validator.dart';
export 'input_validators/boolean.dart';
export 'input_validators/confirmed_password.dart';
export 'input_validators/email.dart';
export 'input_validators/enum_option.dart';
export 'input_validators/list_option.dart';
export 'input_validators/name.dart';
export 'input_validators/password.dart';
export 'input_validators/phone.dart';
export 'input_validators/text_string.dart';

View File

@ -0,0 +1,136 @@
// 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/core/enums/form_status.dart';
import 'package:wyatt_form_bloc/src/core/enums/validation_error.dart';
import 'package:wyatt_form_bloc/src/data/form_validators/every_input_validator.dart';
import 'package:wyatt_form_bloc/src/domain/entities/form_input.dart';
import 'package:wyatt_form_bloc/src/domain/entities/form_input_metadata.dart';
import 'package:wyatt_form_bloc/src/domain/form/wyatt_form.dart';
import 'package:wyatt_form_bloc/src/domain/form_operations/form_operation.dart';
import 'package:wyatt_form_bloc/src/domain/form_validators/form_validator.dart';
import 'package:wyatt_form_bloc/src/domain/input_validators/form_input_validator.dart';
// ignore: must_be_immutable
class WyattFormImpl extends WyattForm {
final List<
FormInput<dynamic, FormInputValidator<dynamic, ValidationError>,
dynamic>> _inputs;
final FormValidator _validator;
late List<
FormInput<dynamic, FormInputValidator<dynamic, ValidationError>,
dynamic>> _inputsInitial;
WyattFormImpl(
this._inputs, {
FormValidator validationStrategy = const EveryInputValidator(),
}) : _validator = validationStrategy {
_inputsInitial = _inputs.map((input) => input.clone()).toList();
}
@override
List<
FormInput<dynamic, FormInputValidator<dynamic, ValidationError>,
dynamic>> get inputs => _inputs;
@override
FormValidator get formValidationStrategy => _validator;
@override
bool containsKey(String key) => inputs.any((input) => input.key == key);
@override
List<FormInputValidator<Value, Error>>
asValidatorList<Value, Error extends ValidationError>() => inputs
.map<FormInputValidator<Value, Error>>(
(input) => input.validator as FormInputValidator<Value, Error>,
)
.toList();
@override
FormInput<dynamic, FormInputValidator<dynamic, ValidationError>, dynamic>
inputOf(String key) {
if (containsKey(key)) {
return inputs.firstWhere((input) => input.key == key);
} else {
throw Exception('FormInput with key `$key` does not exist in form');
}
}
@override
Error? errorOf<Error extends ValidationError>(String key) =>
(inputOf(key).validator as FormInputValidator<dynamic, Error>).error;
@override
FormInputMetadata<Extra> metadataOf<Extra>(String key) =>
inputOf(key).metadata as FormInputMetadata<Extra>;
@override
Validator validatorOf<
Validator extends FormInputValidator<dynamic, ValidationError>>(
String key,
) =>
inputOf(key).validator as Validator;
@override
Value? valueOf<Value>(String key) =>
(inputOf(key).validator as FormInputValidator<Value, dynamic>).value;
@override
FormStatus validate() => formValidationStrategy.validate(this);
@override
WyattForm clone() => WyattFormImpl(
_inputs.map((input) => input.clone()).toList(),
validationStrategy: formValidationStrategy,
);
@override
WyattForm reset() => WyattFormImpl(
_inputsInitial,
validationStrategy: formValidationStrategy,
);
@override
bool? get stringify => true;
@override
List<Object?> get props => _inputs;
@override
void updateMetadata<Extra>(String key, FormInputMetadata<Extra> metadata) {
final index = _inputs.indexOf(
inputOf(key),
);
_inputs[index] = _inputs[index].copyWith(metadata: metadata);
}
@override
void updateValidator<Value>(
String key,
FormInputValidator<Value?, ValidationError> dirtyValue,
) {
final index = _inputs.indexOf(
inputOf(key),
);
_inputs[index] = _inputs[index].copyWith(validator: dirtyValue);
}
@override
WyattForm operationWith(FormOperation operation, WyattForm other) =>
operation.call(this, other);
}

View File

@ -0,0 +1,30 @@
// 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:convert';
import 'package:wyatt_form_bloc/src/data/form_encoders/form_map_encoder.dart';
import 'package:wyatt_form_bloc/src/domain/form/wyatt_form.dart';
import 'package:wyatt_form_bloc/src/domain/form_encoders/form_encoder.dart';
class FormJsonEncoder extends FormEncoder<String> {
@override
String encode(WyattForm form) {
final mapEncoder = FormMapEncoder();
final map = mapEncoder.encode(form);
return jsonEncode(map);
}
}

View File

@ -0,0 +1,32 @@
// 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/domain/form/wyatt_form.dart';
import 'package:wyatt_form_bloc/src/domain/form_encoders/form_encoder.dart';
class FormMapEncoder extends FormEncoder<Map<String, dynamic>> {
@override
Map<String, dynamic> encode(WyattForm form) {
final inputs = form.inputs;
final map = <String, dynamic>{};
for (final input in inputs) {
if (input.metadata.export) {
map[input.name] = input.validator.value;
}
}
return map;
}
}

View File

@ -0,0 +1,50 @@
// 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/core/enums/validation_error.dart';
import 'package:wyatt_form_bloc/src/data/form/wyatt_form_impl.dart';
import 'package:wyatt_form_bloc/src/domain/entities/form_input.dart';
import 'package:wyatt_form_bloc/src/domain/form/wyatt_form.dart';
import 'package:wyatt_form_bloc/src/domain/form_operations/form_operation.dart';
import 'package:wyatt_form_bloc/src/domain/input_validators/form_input_validator.dart';
class FormDifference extends FormOperation {
const FormDifference();
@override
WyattForm call(WyattForm a, WyattForm b) {
final inputs = <
FormInput<dynamic, FormInputValidator<dynamic, ValidationError>,
dynamic>>[];
for (final i in b.inputs) {
if (!a.containsKey(i.key)) {
inputs.add(i);
}
}
for (final i in a.inputs) {
if (!b.containsKey(i.key)) {
inputs.add(i);
}
}
return WyattFormImpl(
inputs,
validationStrategy: a.formValidationStrategy,
);
}
}

View File

@ -0,0 +1,44 @@
// 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/core/enums/validation_error.dart';
import 'package:wyatt_form_bloc/src/data/form/wyatt_form_impl.dart';
import 'package:wyatt_form_bloc/src/domain/entities/form_input.dart';
import 'package:wyatt_form_bloc/src/domain/form/wyatt_form.dart';
import 'package:wyatt_form_bloc/src/domain/form_operations/form_operation.dart';
import 'package:wyatt_form_bloc/src/domain/input_validators/form_input_validator.dart';
class FormIntersection extends FormOperation {
const FormIntersection();
@override
WyattForm call(WyattForm a, WyattForm b) {
final inputs = <
FormInput<dynamic, FormInputValidator<dynamic, ValidationError>,
dynamic>>[];
for (final i in a.inputs) {
if (b.containsKey(i.key)) {
inputs.add(i);
}
}
return WyattFormImpl(
inputs,
validationStrategy: a.formValidationStrategy,
);
}
}

View File

@ -14,15 +14,12 @@
// 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:convert';
import 'package:wyatt_form_bloc/src/domain/form/wyatt_form.dart';
import 'package:wyatt_form_bloc/src/domain/form_operations/form_operation.dart';
import 'package:equatable/equatable.dart';
import 'package:wyatt_form_bloc/src/enums/form_input_status.dart';
import 'package:wyatt_form_bloc/src/enums/form_status.dart';
import 'package:wyatt_form_bloc/src/enums/validation_error.dart';
class FormReplace extends FormOperation {
const FormReplace();
part 'form_data.dart';
part 'form_input.dart';
part 'form_input_metadata.dart';
part 'form_input_validator.dart';
part 'form_validator.dart';
@override
WyattForm call(WyattForm a, WyattForm b) => b;
}

View 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/core/enums/validation_error.dart';
import 'package:wyatt_form_bloc/src/data/form/wyatt_form_impl.dart';
import 'package:wyatt_form_bloc/src/domain/entities/form_input.dart';
import 'package:wyatt_form_bloc/src/domain/form/wyatt_form.dart';
import 'package:wyatt_form_bloc/src/domain/form_operations/form_operation.dart';
import 'package:wyatt_form_bloc/src/domain/input_validators/form_input_validator.dart';
class FormUnion extends FormOperation {
const FormUnion();
@override
WyattForm call(WyattForm a, WyattForm b) {
final inputs = <
FormInput<dynamic, FormInputValidator<dynamic, ValidationError>,
dynamic>>[];
for (final i in a.inputs) {
inputs.add(i);
}
for (final i in b.inputs) {
if (!a.containsKey(i.key)) {
inputs.add(i);
}
}
return WyattFormImpl(
inputs,
validationStrategy: a.formValidationStrategy,
);
}
}

View File

@ -14,7 +14,10 @@
// 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/wyatt_form_bloc.dart';
import 'package:wyatt_form_bloc/src/core/enums/form_status.dart';
import 'package:wyatt_form_bloc/src/core/enums/validation_error.dart';
import 'package:wyatt_form_bloc/src/domain/form/wyatt_form.dart';
import 'package:wyatt_form_bloc/src/domain/form_validators/form_validator.dart';
/// {@template every}
/// Check and validate every input of a form
@ -24,18 +27,13 @@ class EveryInputValidator extends FormValidator {
const EveryInputValidator() : super();
@override
FormStatus validate(FormData form) {
FormStatus validate(WyattForm form) {
if (isPure(form)) {
return FormStatus.pure;
}
return rawValidate(form.validators<dynamic, ValidationError>());
}
final validators = form.asValidatorList<dynamic, ValidationError>();
@override
FormStatus rawValidate(
List<FormInputValidator<dynamic, ValidationError>> validators,
) {
if (validators.any((validator) => validator.valid == false)) {
return FormStatus.invalid;
}

View File

@ -0,0 +1,29 @@
// 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/core/enums/form_status.dart';
import 'package:wyatt_form_bloc/src/domain/form/wyatt_form.dart';
import 'package:wyatt_form_bloc/src/domain/form_validators/form_validator.dart';
class OnlyRequiredInputValidator extends FormValidator {
const OnlyRequiredInputValidator() : super();
@override
FormStatus validate(WyattForm form) {
// TODO(hpcl): use metadata to check if input is required or not.
return FormStatus.valid;
}
}

View File

@ -14,13 +14,13 @@
// 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';
import 'package:wyatt_form_bloc/src/core/enums/validation_error.dart';
import 'package:wyatt_form_bloc/src/domain/input_validators/form_input_validator.dart';
/// {@template boolean}
/// Form input for a bool input
/// {@endtemplate}
class Boolean extends FormInputValidator<bool, ValidationStandardError> {
class Boolean extends NullableValidator<bool, ValidationStandardError> {
/// {@macro boolean}
const Boolean.pure({bool? defaultValue = false})
: super.pure(defaultValue ?? false);
@ -28,7 +28,11 @@ class Boolean extends FormInputValidator<bool, ValidationStandardError> {
/// {@macro boolean}
const Boolean.dirty({bool value = false}) : super.dirty(value);
@override
ValidationStandardError get onNull => ValidationStandardError.invalid;
@override
ValidationStandardError? validator(bool? value) =>
value != null ? null : ValidationStandardError.invalid;
value != null ? null : onNull;
}

View File

@ -14,25 +14,31 @@
// 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';
import 'package:wyatt_form_bloc/src/core/enums/validation_error.dart';
import 'package:wyatt_form_bloc/src/domain/input_validators/form_input_validator.dart';
/// {@template confirmed_password}
/// Form input for a confirmed password input.
/// {@endtemplate}
class ConfirmedPassword
extends FormInputValidator<String, ValidationStandardError> {
extends EqualityValidator<String, ValidationStandardError> {
/// {@macro confirmed_password}
const ConfirmedPassword.pure({this.password = ''}) : super.pure('');
const ConfirmedPassword.pure({this.password = '', String? defaultValue})
: super.pure(defaultValue);
/// {@macro confirmed_password}
const ConfirmedPassword.dirty({required this.password, String value = ''})
const ConfirmedPassword.dirty({required this.password, String? value})
: super.dirty(value);
/// The original password.
final String password;
@override
ValidationStandardError? validator(String? value) =>
password == value ? null : ValidationStandardError.invalid;
String get another => password;
@override
ValidationStandardError get onNotEqual => ValidationStandardError.notEqual;
@override
ValidationStandardError get onNull => ValidationStandardError.invalid;
}

View File

@ -14,15 +14,15 @@
// 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/validators/inputs/base/regex_validator.dart';
import 'package:wyatt_form_bloc/src/core/enums/validation_error.dart';
import 'package:wyatt_form_bloc/src/domain/input_validators/form_input_validator.dart';
/// {@template email}
/// Form input for an email input.
/// {@endtemplate}
class Email extends RegexValidator<ValidationStandardError> {
/// {@macro email}
const Email.pure() : super.pure();
const Email.pure([super.value]) : super.pure();
/// {@macro email}
const Email.dirty([super.value = '']) : super.dirty();

View File

@ -14,24 +14,27 @@
// 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/validators/inputs/base/regex_validator.dart';
import 'package:wyatt_form_bloc/src/core/enums/validation_error.dart';
import 'package:wyatt_form_bloc/src/domain/input_validators/form_input_validator.dart';
/// {@template siren}
/// Form input for a SIREN input.
/// {@endtemplate}
class Siren extends RegexValidator<ValidationStandardError> {
/// {@macro siren}
const Siren.pure() : super.pure();
class EnumOption<T> extends AnyValidator<T, List<T>, ValidationError> {
const EnumOption.pure({this.enums = const [], T? defaultValue})
: super.pure(defaultValue);
/// {@macro siren}
const Siren.dirty([super.value = '']) : super.dirty();
const EnumOption.dirty({required this.enums, T? value})
: super.dirty(value);
final List<T> enums;
@override
ValidationStandardError get onEmpty => ValidationStandardError.empty;
@override
ValidationStandardError get onError => ValidationStandardError.invalid;
List<T> get allChoices => enums;
@override
RegExp get regex => RegExp(r'(\d{9}|\d{3}[ ]\d{3}[ ]\d{3})$');
ValidationError get onError => ValidationStandardError.invalid;
@override
ValidationError get onNull => ValidationStandardError.invalid;
@override
bool test(T? element, T value) => element == value;
}

View File

@ -0,0 +1,40 @@
// 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/core/enums/validation_error.dart';
import 'package:wyatt_form_bloc/src/domain/input_validators/form_input_validator.dart';
class ListOption<T> extends AnyValidator<T, Iterable<T>, ValidationError> {
const ListOption.pure({this.choices = const [], T? defaultValue})
: super.pure(defaultValue);
const ListOption.dirty({required this.choices, T? value})
: super.dirty(value);
final Iterable<T> choices;
@override
Iterable<T> get allChoices => choices;
@override
ValidationError get onError => ValidationStandardError.invalid;
@override
ValidationError get onNull => ValidationStandardError.invalid;
@override
bool test(T? element, T value) => element == value;
}

View File

@ -14,15 +14,15 @@
// 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/validators/inputs/base/regex_validator.dart';
import 'package:wyatt_form_bloc/src/core/enums/validation_error.dart';
import 'package:wyatt_form_bloc/src/domain/input_validators/form_input_validator.dart';
/// {@template name}
/// Form input for a name input.
/// {@endtemplate}
class Name extends RegexValidator<ValidationStandardError> {
/// {@macro name}
const Name.pure() : super.pure();
const Name.pure([super.value]) : super.pure();
/// {@macro name}
const Name.dirty([super.value = '']) : super.dirty();

View File

@ -14,18 +14,18 @@
// 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/validators/inputs/base/regex_validator.dart';
import 'package:wyatt_form_bloc/src/core/enums/validation_error.dart';
import 'package:wyatt_form_bloc/src/domain/input_validators/form_input_validator.dart';
/// {@template password}
/// Form input for a password input.
/// {@endtemplate}
class Password extends RegexValidator<ValidationStandardError> {
/// {@macro password}
const Password.pure() : super.pure();
const Password.pure([super.value]) : super.pure();
/// {@macro password}
const Password.dirty([super.value = '']) : super.dirty();
const Password.dirty([super.value]) : super.dirty();
@override
ValidationStandardError get onEmpty => ValidationStandardError.empty;

View File

@ -14,15 +14,15 @@
// 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/validators/inputs/base/regex_validator.dart';
import 'package:wyatt_form_bloc/src/core/enums/validation_error.dart';
import 'package:wyatt_form_bloc/src/domain/input_validators/form_input_validator.dart';
/// {@template phone}
/// Form input for a phone input.
/// {@endtemplate}
class Phone extends RegexValidator<ValidationStandardError> {
/// {@macro phone}
const Phone.pure() : super.pure();
const Phone.pure([super.value]) : super.pure();
/// {@macro phone}
const Phone.dirty([super.value = '']) : super.dirty();

View File

@ -14,15 +14,15 @@
// 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/validators/inputs/base/text_validator.dart';
import 'package:wyatt_form_bloc/src/core/enums/validation_error.dart';
import 'package:wyatt_form_bloc/src/domain/input_validators/form_input_validator.dart';
/// {@template text_string}
/// Form input for a text input
/// {@endtemplate}
class TextString extends TextValidator<ValidationStandardError> {
/// {@macro text_string}
const TextString.pure() : super.pure();
const TextString.pure([super.value]) : super.pure();
/// {@macro text_string}
const TextString.dirty([super.value = '']) : super.dirty();

View File

@ -0,0 +1,23 @@
// 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/>.
export 'entities/form_input.dart';
export 'entities/form_input_metadata.dart';
export 'form/wyatt_form.dart';
export 'form_encoders/form_encoder.dart';
export 'form_operations/form_operation.dart';
export 'form_validators/form_validator.dart';
export 'input_validators/form_input_validator.dart';

View File

@ -15,27 +15,36 @@
// 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';
import 'package:equatable/equatable.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_form_bloc/src/core/enums/validation_error.dart';
import 'package:wyatt_form_bloc/src/domain/entities/form_input_metadata.dart';
import 'package:wyatt_form_bloc/src/domain/input_validators/form_input_validator.dart';
class FormInput<Validator extends FormInputValidator<dynamic, ValidationError>>
extends Equatable {
// ignore: must_be_immutable
class FormInput<
Value,
Validator extends FormInputValidator<Value, ValidationError>,
Extra> extends Equatable implements Entity {
final String key;
final Validator validator;
final FormInputMetadata<dynamic> metadata;
late FormInputMetadata<Extra> metadata;
String get name => metadata._name ?? key;
String get name => metadata.name ?? key;
const FormInput(
FormInput(
this.key,
this.validator, {
// ignore: avoid_redundant_argument_values
this.metadata = const FormInputMetadata<void>(export: true),
});
FormInputMetadata<Extra>? metadata,
}) {
this.metadata = metadata ?? FormInputMetadata<Extra>();
}
FormInput copyWith({
FormInput<Value, Validator, Extra> copyWith({
String? key,
Validator? validator,
FormInputMetadata? metadata,
FormInputMetadata<Extra>? metadata,
}) =>
FormInput(
key ?? this.key,
@ -43,11 +52,12 @@ class FormInput<Validator extends FormInputValidator<dynamic, ValidationError>>
metadata: metadata ?? this.metadata,
);
FormInput clone() => copyWith(
FormInput<Value, Validator, Extra> clone() => copyWith(
key: key,
validator: validator,
metadata: metadata,
);
@override
bool? get stringify => true;

View File

@ -15,33 +15,35 @@
// 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';
import 'package:equatable/equatable.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';
class FormInputMetadata<T extends Object?> extends Equatable {
class FormInputMetadata<Extra> extends Equatable
implements Entity {
final bool export;
final String? _name;
final T? extra;
final String? name;
final Extra? extra;
const FormInputMetadata({
this.export = true,
this.name,
this.extra,
String? name,
}) : _name = name;
});
FormInputMetadata<T> copyWith({
FormInputMetadata<Extra> copyWith({
bool? export,
String? name,
T? extra,
Extra? extra,
}) =>
FormInputMetadata<T>(
FormInputMetadata<Extra>(
export: export ?? this.export,
name: name ?? _name,
name: name ?? this.name,
extra: extra ?? this.extra,
);
FormInputMetadata<T> clone() => copyWith(
FormInputMetadata<Extra> clone() => copyWith(
export: export,
name: _name,
name: name,
extra: extra,
);
@ -49,5 +51,5 @@ class FormInputMetadata<T extends Object?> extends Equatable {
bool? get stringify => true;
@override
List<Object?> get props => [export, _name, extra];
List<Object?> get props => [export, name, extra];
}

View File

@ -0,0 +1,60 @@
// 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/>.
// ignore_for_file: strict_raw_type
import 'package:equatable/equatable.dart';
import 'package:wyatt_form_bloc/src/core/enums/form_status.dart';
import 'package:wyatt_form_bloc/src/core/enums/validation_error.dart';
import 'package:wyatt_form_bloc/src/domain/entities/form_input.dart';
import 'package:wyatt_form_bloc/src/domain/entities/form_input_metadata.dart';
import 'package:wyatt_form_bloc/src/domain/form_operations/form_operation.dart';
import 'package:wyatt_form_bloc/src/domain/form_validators/form_validator.dart';
import 'package:wyatt_form_bloc/src/domain/input_validators/form_input_validator.dart';
abstract class WyattForm extends Equatable {
List<FormInput> get inputs;
FormValidator get formValidationStrategy;
bool containsKey(String key);
FormInput inputOf(String key);
Validator validatorOf<
Validator extends FormInputValidator<dynamic, ValidationError>>(
String key,
);
Error? errorOf<Error extends ValidationError>(String key);
Value? valueOf<Value>(String key);
FormInputMetadata<Extra> metadataOf<Extra>(String key);
List<FormInputValidator<Value, Error>>
asValidatorList<Value, Error extends ValidationError>();
void updateValidator<Value>(
String key,
FormInputValidator<Value?, ValidationError> dirtyValue,
);
void updateMetadata<Extra>(
String key,
FormInputMetadata<Extra> metadata,
);
FormStatus validate();
WyattForm clone();
WyattForm operationWith(FormOperation operation, WyattForm other);
WyattForm reset();
}

View File

@ -14,16 +14,9 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
enum SetOperation {
/// Replace entire set with new set.
replace,
import 'package:wyatt_form_bloc/src/domain/form/wyatt_form.dart';
/// Keep common elements between sets.
intersection,
/// Remove common elements between sets.
difference,
/// Add new elements to set.
union
// ignore: one_member_abstracts
abstract class FormEncoder<Output> {
Output encode(WyattForm form);
}

View File

@ -0,0 +1,23 @@
// 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/domain/form/wyatt_form.dart';
// ignore: one_member_abstracts
abstract class FormOperation {
const FormOperation();
WyattForm call(WyattForm a, WyattForm b);
}

View File

@ -14,7 +14,9 @@
// 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';
import 'package:wyatt_form_bloc/src/core/enums/form_status.dart';
import 'package:wyatt_form_bloc/src/core/enums/validation_error.dart';
import 'package:wyatt_form_bloc/src/domain/form/wyatt_form.dart';
/// {@template form_validator}
/// A [FormValidator] represents the global validaton state of a Form.
@ -23,13 +25,9 @@ abstract class FormValidator {
/// {@macro form_validator}
const FormValidator();
bool isPure(FormData form) => form
.validators<dynamic, ValidationError>()
bool isPure(WyattForm form) => form
.asValidatorList<dynamic, ValidationError>()
.every((validator) => validator.pure);
FormStatus validate(FormData form);
FormStatus rawValidate(
List<FormInputValidator<dynamic, ValidationError>> validators,
);
FormStatus validate(WyattForm form);
}

View File

@ -0,0 +1,45 @@
// 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_input_validator.dart';
abstract class AnyValidator<O, I extends Iterable<O?>,
E extends ValidationError> extends FormInputValidator<O?, E> {
const AnyValidator.pure(super.value) : super.pure();
const AnyValidator.dirty(super.value) : super.dirty();
I get allChoices;
bool test(O? element, O value);
E get onError;
/// If value is null.
E get onNull;
@override
E? validator(O? value) {
if (value == null) {
return onNull;
}
if (allChoices.any(
(element) => test(element, value),
)) {
return null;
}
return onError;
}
}

View File

@ -0,0 +1,45 @@
// 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_input_validator.dart';
/// {@template equality_validator}
/// Abstract equality validator validating a value when equals to another.
/// {@endtemplate}
abstract class EqualityValidator<O extends Object, E extends ValidationError>
extends FormInputValidator<O?, E> {
const EqualityValidator.pure(super.value) : super.pure();
const EqualityValidator.dirty(super.value) : super.dirty();
O get another;
/// If values are different.
E get onNotEqual;
/// If value is null.
E get onNull;
@override
E? validator(O? value) {
if (value == null) {
return onNull;
}
if (value != another) {
return onNotEqual;
}
return null;
}
}

View File

@ -14,7 +14,15 @@
// 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';
import 'package:equatable/equatable.dart';
import 'package:wyatt_form_bloc/src/core/enums/form_input_status.dart';
import 'package:wyatt_form_bloc/src/core/enums/validation_error.dart';
part 'regex_validator.dart';
part 'text_validator.dart';
part 'nullable_validator.dart';
part 'equality_validator.dart';
part 'any_validator.dart';
/// {@template form_input_validator}
/// A [FormInputValidator] represents the value of a single form input field.
@ -39,21 +47,21 @@ part of 'form.dart';
/// }
/// ```
/// {@endtemplate}
abstract class FormInputValidator<V, E extends ValidationError>
abstract class FormInputValidator<Value, Error extends ValidationError>
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);
const FormInputValidator.pure(Value value) : this._(value);
/// Constructor which create a `dirty` [FormInputValidator] with a
/// given value.
const FormInputValidator.dirty(V value) : this._(value, false);
const FormInputValidator.dirty(Value 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;
final Value? value;
/// If the [FormInputValidator] is pure (has been touched/modified).
/// Typically when the `FormInputValidator` is initially created,
@ -80,7 +88,7 @@ abstract class FormInputValidator<V, E extends ValidationError>
/// Returns a validation error if the [FormInputValidator] is invalid.
/// Returns `null` if the [FormInputValidator] is valid.
E? get error => validator(value);
Error? get error => validator(value);
/// Whether the [FormInputValidator] value is valid according to the
/// overridden `validator`.
@ -96,7 +104,7 @@ abstract class FormInputValidator<V, E extends ValidationError>
/// A function that must return a validation error if the provided
/// [value] is invalid and `null` otherwise.
E? validator(V value);
Error? validator(Value? value);
@override
bool? get stringify => true;

View File

@ -14,19 +14,24 @@
// 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/validation_error.dart';
import 'package:wyatt_form_bloc/src/validators/inputs/base/text_validator.dart';
part of 'form_input_validator.dart';
class EnumValidator<E> extends TextValidator<ValidationStandardError> {
/// {@macro text_string}
const EnumValidator.pure() : super.pure();
/// {@template nullable_validator}
/// Abstract nullable validator not validating null values.
/// {@endtemplate}
abstract class NullableValidator<O extends Object, E extends ValidationError>
extends FormInputValidator<O?, E> {
const NullableValidator.pure(super.value) : super.pure();
const NullableValidator.dirty(super.value) : super.dirty();
/// {@macro text_string}
EnumValidator.dirty(E value) : super.dirty(value.toString());
/// If value is null.
E get onNull;
@override
ValidationStandardError get onEmpty => ValidationStandardError.empty;
@override
ValidationStandardError get onNull => ValidationStandardError.invalid;
E? validator(O? value) {
if (value == null) {
return onNull;
}
return null;
}
}

View File

@ -14,20 +14,21 @@
// 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/validation_error.dart';
import 'package:wyatt_form_bloc/src/form/form.dart';
part of 'form_input_validator.dart';
/// {@template regex_validator}
/// Abstract regex validator for form input.
/// {@endtemplate}
abstract class RegexValidator<E extends ValidationError>
extends FormInputValidator<String, E> {
const RegexValidator.pure() : super.pure('');
const RegexValidator.dirty([super.value = '']) : super.dirty();
extends FormInputValidator<String?, E> {
const RegexValidator.pure(super.value) : super.pure();
const RegexValidator.dirty(super.value) : super.dirty();
RegExp get regex;
/// If the value is **not** null, but empty.
E get onEmpty;
/// If value does not conform to regex.
E get onError;

View File

@ -14,13 +14,12 @@
// 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/validation_error.dart';
import 'package:wyatt_form_bloc/src/form/form.dart';
part of 'form_input_validator.dart';
abstract class TextValidator<E extends ValidationError>
extends FormInputValidator<String, E> {
const TextValidator.pure() : super.pure('');
const TextValidator.dirty([super.value = '']) : super.dirty();
extends FormInputValidator<String?, E> {
const TextValidator.pure(super.value) : super.pure();
const TextValidator.dirty(super.value) : super.dirty();
/// If the value is **not** null, but empty.
E get onEmpty;

View File

@ -1,184 +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';
class FormData extends Equatable {
final List<FormInput> _inputs;
const FormData(this._inputs);
const FormData.empty() : this(const <FormInput>[]);
/// Returns all inputs as a list
List<FormInput> inputs() => _inputs;
/// Returns the input for the associated key
FormInput inputOf(String key) {
if (contains(key)) {
return _inputs.firstWhere((input) => input.key == key);
} else {
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(
inputOf(key),
);
_inputs[index] = input;
}
}
/// Returns all associated validators as a list
List<FormInputValidator<V, E>> validators<V, E extends ValidationError>() =>
_inputs
.map<FormInputValidator<V, E>>(
(input) => input.validator as FormInputValidator<V, E>,
)
.toList();
/// 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) => inputOf(key).validator as T;
/// Updates validator of a given input. (perform copyWith)
void updateValidator<V>(
String key,
FormInputValidator<V, ValidationError> dirtyValue,
) {
if (contains(key)) {
final index = _inputs.indexOf(
inputOf(key),
);
_inputs[index] = _inputs[index].copyWith(validator: dirtyValue);
}
}
/// Returns a validation error if the [FormInputValidator] is invalid.
/// Returns null if the [FormInputValidator] is valid.
E? errorOf<E extends ValidationError>(String key) =>
(inputOf(key).validator as FormInputValidator<dynamic, 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>(String key) =>
(inputOf(key).validator as FormInputValidator<V, dynamic>).value;
/// Returns `true` if the [FormInputValidator] is not valid.
/// Same as `E? errorOf<V, E>(String key) != null`
bool isNotValid(String key) => !inputOf(key).validator.valid;
/// Returns all associated metadata as a list
List<FormInputMetadata<M>> metadata<M>() => _inputs
.map<FormInputMetadata<M>>(
(input) => input.metadata as FormInputMetadata<M>,
)
.toList();
/// Returns the metadata associated. With `M` the type of extra data.
FormInputMetadata<M> metadataOf<M>(String key) =>
inputOf(key).metadata as FormInputMetadata<M>;
/// Updates metadata of a given input. (perform copyWith)
void updateMetadata<M>(String key, FormInputMetadata<M> meta) {
if (contains(key)) {
final index = _inputs.indexOf(
inputOf(key),
);
_inputs[index] = _inputs[index].copyWith(metadata: meta);
}
}
/// Check if this contains an input with the given key.
bool contains(String key) => _inputs.any((input) => input.key == key);
/// Makes an intersection set operation and returns newly created [FormData]
FormData intersection(FormData other) {
final List<FormInput> inputs = <FormInput>[];
for (final FormInput i in _inputs) {
if (other.contains(i.key)) {
inputs.add(i);
}
}
return FormData(inputs);
}
/// Makes a difference set operation and returns newly created [FormData]
FormData difference(FormData other) {
final List<FormInput> inputs = <FormInput>[];
for (final FormInput i in other._inputs) {
if (!contains(i.key)) {
inputs.add(i);
}
}
for (final FormInput i in _inputs) {
if (!other.contains(i.key)) {
inputs.add(i);
}
}
return FormData(inputs);
}
/// Makes an union set operation and returns newly created [FormData]
FormData union(FormData other) {
final List<FormInput> inputs = <FormInput>[];
for (final FormInput i in _inputs) {
inputs.add(i);
}
for (final FormInput i in other._inputs) {
if (!contains(i.key)) {
inputs.add(i);
}
}
return FormData(inputs);
}
/// Deeply copy this.
FormData clone() => FormData(
_inputs.map((input) => input.clone()).toList(),
);
/// Export this to [Map] format.
Map<String, dynamic> toMap() {
final map = <String, dynamic>{};
for (final input in _inputs) {
if (input.metadata.export) {
map[input.name] = input.validator.value;
}
}
return map;
}
/// Export this to [String] format.
String toJson() => jsonEncode(toMap());
@override
bool? get stringify => true;
@override
List<Object?> get props => _inputs;
}

View File

@ -0,0 +1,47 @@
// 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:async';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_form_bloc/src/core/enums/form_status.dart';
import 'package:wyatt_form_bloc/src/core/enums/set_operations.dart';
import 'package:wyatt_form_bloc/src/core/enums/validation_error.dart';
import 'package:wyatt_form_bloc/src/domain/form/wyatt_form.dart';
import 'package:wyatt_form_bloc/src/domain/input_validators/form_input_validator.dart';
part 'form_data_state.dart';
abstract class FormDataCubit<State extends FormDataState> extends Cubit<State> {
FormDataCubit(super.initialState) : super();
FutureOr<void> dataChanged<Value>(
String key,
FormInputValidator<Value, ValidationError> dirtyValue,
);
FutureOr<void> update(
WyattForm form, {
SetOperation operation = SetOperation.replace,
});
FutureOr<void> validate();
FutureOr<void> reset();
FutureOr<void> submit();
}

View File

@ -16,35 +16,19 @@
part of 'form_data_cubit.dart';
class FormDataState extends Equatable {
abstract class FormDataState extends Equatable {
/// Global status of a form.
final FormStatus status;
/// FormData with all inputs, and associated metadata.
final FormData data;
final WyattForm form;
/// Optional error message.
final String? errorMessage;
const FormDataState({
required this.data,
required this.form,
this.status = FormStatus.pure,
this.errorMessage,
});
FormDataState copyWith({
FormStatus? status,
FormData? data,
String? errorMessage,
}) => FormDataState(
status: status ?? this.status,
data: data ?? this.data,
errorMessage: errorMessage ?? this.errorMessage,
);
@override
bool? get stringify => true;
@override
List<Object?> get props => [status, data, errorMessage];
}

View File

@ -0,0 +1,86 @@
// 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:async';
import 'package:wyatt_form_bloc/src/core/enums/form_status.dart';
import 'package:wyatt_form_bloc/src/core/enums/set_operations.dart';
import 'package:wyatt_form_bloc/src/core/enums/validation_error.dart';
import 'package:wyatt_form_bloc/src/domain/form/wyatt_form.dart';
import 'package:wyatt_form_bloc/src/domain/input_validators/form_input_validator.dart';
import 'package:wyatt_form_bloc/src/presentation/features/form_data/form_data_cubit.dart';
part 'form_data_state_impl.dart';
abstract class FormDataCubitImpl extends FormDataCubit<FormDataStateImpl> {
FormDataCubitImpl(WyattForm form) : super(FormDataStateImpl(form: form));
@override
FutureOr<void> dataChanged<Value>(
String key,
FormInputValidator<Value, ValidationError> dirtyValue,
) {
final form = state.form.clone();
try {
form.updateValidator(key, dirtyValue);
} catch (e) {
rethrow;
}
emit(
state.copyWith(
form: form,
status: form.validate(),
),
);
}
@override
FutureOr<void> update(
WyattForm form, {
SetOperation operation = SetOperation.replace,
}) {
final WyattForm newForm = operation.operation.call(state.form, form);
emit(
state.copyWith(
form: newForm,
status: newForm.validate(),
),
);
}
@override
FutureOr<void> reset() {
final form = state.form.reset();
emit(
state.copyWith(
form: form,
status: form.validate(),
),
);
}
@override
FutureOr<void> validate() {
emit(
state.copyWith(
status: state.form.validate(),
),
);
}
}

View File

@ -0,0 +1,42 @@
// 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_data_cubit_impl.dart';
class FormDataStateImpl extends FormDataState {
const FormDataStateImpl({
required super.form,
super.status = FormStatus.pure,
super.errorMessage,
});
FormDataStateImpl copyWith({
FormStatus? status,
WyattForm? form,
String? errorMessage,
}) =>
FormDataStateImpl(
status: status ?? this.status,
form: form ?? this.form,
errorMessage: errorMessage ?? this.errorMessage,
);
@override
bool? get stringify => true;
@override
List<Object?> get props => [status, form, errorMessage];
}

View File

@ -0,0 +1,43 @@
// 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:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
class InputBuilder<Cubit extends FormDataCubit> extends StatelessWidget {
const InputBuilder({required this.field, required this.builder, super.key});
final String field;
final Widget Function(
BuildContext context,
Cubit cubit,
FormDataState state,
String field,
bool inputValid,
) builder;
@override
Widget build(BuildContext context) =>
BlocBuilder<Cubit, FormDataState>(
builder: (context, state) {
final cubit = context.read<Cubit>();
final inputValid = state.form.validatorOf(field).valid;
return builder.call(context, cubit, state, field, inputValid);
},
);
}

View File

@ -0,0 +1,57 @@
// 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:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_form_bloc/src/domain/entities/form_input_metadata.dart';
import 'package:wyatt_form_bloc/src/presentation/presentation.dart';
class InputBuilderMetadata<Cubit extends FormDataCubit, Extra>
extends StatelessWidget {
const InputBuilderMetadata({
required this.field,
required this.builder,
super.key,
});
final String field;
final Widget Function(
BuildContext context,
Cubit cubit,
FormDataState state,
String field,
bool inputValid,
FormInputMetadata<Extra>? metadata,
) builder;
@override
Widget build(BuildContext context) => BlocBuilder<Cubit, FormDataState>(
builder: (context, state) {
final cubit = context.read<Cubit>();
final inputValid = state.form.validatorOf(field).valid;
final metadata = state.form.metadataOf<Extra>(field);
return builder.call(
context,
cubit,
state,
field,
inputValid,
metadata,
);
},
);
}

View File

@ -0,0 +1,37 @@
// 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:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_form_bloc/src/core/enums/form_status.dart';
import 'package:wyatt_form_bloc/src/presentation/features/form_data/form_data_cubit.dart';
class SubmitBuilder<Cubit extends FormDataCubit> extends StatelessWidget {
const SubmitBuilder({required this.builder, super.key});
final Widget Function(BuildContext context, Cubit cubit, FormStatus status)
builder;
@override
Widget build(BuildContext context) => BlocBuilder<Cubit, FormDataState>(
buildWhen: (previous, current) => previous.status != current.status,
builder: (context, state) => builder.call(
context,
context.read<Cubit>(),
state.status,
),
);
}

View File

@ -0,0 +1,21 @@
// 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/>.
export 'features/form_data/form_data_cubit.dart';
export 'features/form_data_impl/form_data_cubit_impl.dart';
export 'features/widgets/input_builder.dart';
export 'features/widgets/input_builder_metadata.dart';
export 'features/widgets/submit_builder.dart';

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'cubit/form_data_cubit.dart';
export 'enums/enums.dart';
export 'form/form.dart';
export 'validators/validators.dart';
export 'core/core.dart';
export 'data/data.dart';
export 'domain/domain.dart';
export 'presentation/presentation.dart';

View File

@ -1,39 +0,0 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_form_bloc/src/enums/enums.dart';
import 'package:wyatt_form_bloc/src/validators/inputs/base/regex_validator.dart';
/// {@template iban}
/// Form input for an IBAN input.
/// {@endtemplate}
class Iban extends RegexValidator<ValidationStandardError> {
/// {@macro iban}
const Iban.pure() : super.pure();
/// {@macro iban}
const Iban.dirty([super.value = '']) : super.dirty();
@override
ValidationStandardError get onEmpty => ValidationStandardError.empty;
@override
ValidationStandardError get onError => ValidationStandardError.invalid;
@override
RegExp get regex => RegExp(
r'^(?:((?:IT|SM)\d{2}[A-Z]{1}\d{22})|(NL\d{2}[A-Z]{4}\d{10})|(LV\d{2}[A-Z]{4}\d{13})|((?:BG|GB|IE)\d{2}[A-Z]{4}\d{14})|(GI\d{2}[A-Z]{4}\d{15})|(RO\d{2}[A-Z]{4}\d{16})|(MT\d{2}[A-Z]{4}\d{23})|(NO\d{13})|((?:DK|FI)\d{16})|((?:SI)\d{17})|((?:AT|EE|LU|LT)\d{18})|((?:HR|LI|CH)\d{19})|((?:DE|VA)\d{20})|((?:AD|CZ|ES|MD|SK|SE)\d{22})|(PT\d{23})|((?:IS)\d{24})|((?:BE)\d{14})|((?:FR|MC|GR)\d{25})|((?:PL|HU|CY)\d{26}))$',
);
}

View File

@ -1,50 +0,0 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package: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 FormInputValidator<List<T>, ValidationStandardError> {
/// {@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<T> 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
ValidationStandardError? validator(List<T>? value) =>
value?.isNotEmpty ?? false == true
? null
: ValidationStandardError.invalid;
}

View File

@ -3,15 +3,33 @@ description: Manage forms in Dart & Flutter with Bloc
repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_form_bloc
version: 0.0.6
publish_to: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub
environment:
sdk: '>=2.17.2 <3.0.0'
sdk: ">=2.17.2 <3.0.0"
dependencies:
bloc: ^8.1.0
flutter:
sdk: flutter
flutter_bloc: ^8.1.0
equatable: ^2.0.5
wyatt_architecture:
git:
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
ref: wyatt_architecture-v0.0.2-dev.0
path: packages/wyatt_architecture
wyatt_type_utils:
git:
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
ref: wyatt_type_utils-v0.0.3+1
path: packages/wyatt_type_utils
dev_dependencies:
test: ^1.21.4
flutter_test:
sdk: flutter
wyatt_analysis:
git: