feat!(form): add repository feature, and controller for inputs

This commit is contained in:
Hugo Pointcheval 2022-11-10 01:05:12 -05:00
parent b89ef3de8a
commit 7a056ac38e
Signed by: hugo
GPG Key ID: A9E8E9615379254F
18 changed files with 233 additions and 32 deletions

View File

@ -32,3 +32,4 @@ export 'input_validators/name.dart';
export 'input_validators/password.dart';
export 'input_validators/phone.dart';
export 'input_validators/text_string.dart';
export 'repositories/form_repository_impl.dart';

View File

@ -30,6 +30,7 @@ class WyattFormImpl extends WyattForm {
FormInput<dynamic, FormInputValidator<dynamic, ValidationError>,
dynamic>> _inputs;
final FormValidator _validator;
final String _name;
late List<
FormInput<dynamic, FormInputValidator<dynamic, ValidationError>,
@ -37,8 +38,10 @@ class WyattFormImpl extends WyattForm {
WyattFormImpl(
this._inputs, {
required String name,
FormValidator validationStrategy = const EveryInputValidator(),
}) : _validator = validationStrategy {
}) : _name = name,
_validator = validationStrategy {
_inputsInitial = _inputs.map((input) => input.clone()).toList();
}
@ -50,6 +53,9 @@ class WyattFormImpl extends WyattForm {
@override
FormValidator get formValidationStrategy => _validator;
@override
String get name => _name;
@override
bool containsKey(String key) => inputs.any((input) => input.key == key);
@ -94,22 +100,24 @@ class WyattFormImpl extends WyattForm {
FormStatus validate() => formValidationStrategy.validate(this);
@override
WyattForm clone() => WyattFormImpl(
WyattForm clone() {
final clone = WyattFormImpl(
_inputs.map((input) => input.clone()).toList(),
name: _name,
validationStrategy: formValidationStrategy,
);
).._inputsInitial = _inputsInitial;
return clone;
}
@override
WyattForm reset() => WyattFormImpl(
WyattForm reset() {
final newForm = WyattFormImpl(
_inputsInitial,
name: _name,
validationStrategy: formValidationStrategy,
);
@override
bool? get stringify => true;
@override
List<Object?> get props => _inputs;
).._inputsInitial = _inputsInitial;
return newForm;
}
@override
void updateMetadata<Extra>(String key, FormInputMetadata<Extra> metadata) {
@ -133,4 +141,11 @@ class WyattFormImpl extends WyattForm {
@override
WyattForm operationWith(FormOperation operation, WyattForm other) =>
operation.call(this, other);
@override
List<Object?> get props => [_inputs, _name, _validator];
@override
String toString() =>
'WyattForm(name: $name, validation: ${_validator.runtimeType}, inputs: $inputs)';
}

View File

@ -44,6 +44,7 @@ class FormDifference extends FormOperation {
return WyattFormImpl(
inputs,
name: a.name,
validationStrategy: a.formValidationStrategy,
);
}

View File

@ -38,6 +38,7 @@ class FormIntersection extends FormOperation {
return WyattFormImpl(
inputs,
name: a.name,
validationStrategy: a.formValidationStrategy,
);
}

View File

@ -42,6 +42,7 @@ class FormUnion extends FormOperation {
return WyattFormImpl(
inputs,
name: a.name,
validationStrategy: a.formValidationStrategy,
);
}

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/>.
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_form_bloc/src/domain/form/wyatt_form.dart';
import 'package:wyatt_form_bloc/src/domain/repositories/form_repository.dart';
class FormRepositoryImpl extends FormRepository {
final Map<String, WyattForm> _runtimeForms = {};
@override
Map<String, WyattForm> get runtimeForms => _runtimeForms;
@override
WyattForm accessForm(String formName) {
if (_runtimeForms.containsKey(formName)) {
return _runtimeForms[formName]!;
} else {
throw ClientException(
'Form $formName is not registered. Just use '
'`FormRepository.register(yourForm);` before any operation on it.',
);
}
}
@override
void registerForm(WyattForm form) {
_runtimeForms[form.name] = form;
}
@override
void updateForm(WyattForm form) {
if (_runtimeForms.containsKey(form.name)) {
_runtimeForms[form.name] = form;
} else {
throw ClientException(
'Form ${form.name} is not registered. Just use '
'`FormRepository.register(yourForm);` before any operation on it.',
);
}
}
@override
void unregisterForm(String formName) {
_runtimeForms.remove(formName);
}
}

View File

@ -21,3 +21,4 @@ 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';
export 'repositories/form_repository.dart';

View File

@ -59,8 +59,9 @@ class FormInput<
);
@override
bool? get stringify => true;
List<Object?> get props => [key, validator, metadata];
@override
List<Object?> get props => [key, validator, metadata];
String toString() =>
'FormInput(name: $name, value: ${validator.value}, status: ${validator.status.name}';
}

View File

@ -28,6 +28,7 @@ import 'package:wyatt_form_bloc/src/domain/input_validators/form_input_validator
abstract class WyattForm extends Equatable {
List<FormInput> get inputs;
FormValidator get formValidationStrategy;
String get name;
bool containsKey(String key);

View File

@ -19,5 +19,6 @@ import 'package:wyatt_form_bloc/src/domain/form/wyatt_form.dart';
// ignore: one_member_abstracts
abstract class FormOperation {
const FormOperation();
// TODO(hpcl): handle operation on `initialInputs`
WyattForm call(WyattForm a, WyattForm b);
}

View File

@ -18,11 +18,11 @@ 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 'any_validator.dart';
part 'equality_validator.dart';
part 'nullable_validator.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.

View File

@ -0,0 +1,27 @@
// 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_architecture/wyatt_architecture.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
abstract class FormRepository extends BaseRepository {
Map<String, WyattForm> get runtimeForms;
void registerForm(WyattForm form);
void updateForm(WyattForm form);
WyattForm accessForm(String formName);
void unregisterForm(String formName);
}

View File

@ -29,6 +29,8 @@ part 'form_data_state.dart';
abstract class FormDataCubit<State extends FormDataState> extends Cubit<State> {
FormDataCubit(super.initialState) : super();
String get formName;
FutureOr<void> dataChanged<Value>(
String key,
FormInputValidator<Value, ValidationError> dirtyValue,

View File

@ -21,22 +21,31 @@ 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/domain/repositories/form_repository.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));
final FormRepository _formRepository;
final String _formName;
FormDataCubitImpl(this._formRepository, this._formName)
: super(FormDataStateImpl(form: _formRepository.accessForm(_formName)));
@override
String get formName => _formName;
@override
FutureOr<void> dataChanged<Value>(
String key,
FormInputValidator<Value, ValidationError> dirtyValue,
) {
final form = state.form.clone();
final form = _formRepository.accessForm(_formName).clone();
try {
form.updateValidator(key, dirtyValue);
_formRepository.updateForm(form);
} catch (e) {
rethrow;
}
@ -54,7 +63,9 @@ abstract class FormDataCubitImpl extends FormDataCubit<FormDataStateImpl> {
WyattForm form, {
SetOperation operation = SetOperation.replace,
}) {
final WyattForm newForm = operation.operation.call(state.form, form);
final WyattForm current = _formRepository.accessForm(_formName).clone();
final WyattForm newForm = operation.operation.call(current, form);
_formRepository.updateForm(newForm);
emit(
state.copyWith(
@ -67,6 +78,7 @@ abstract class FormDataCubitImpl extends FormDataCubit<FormDataStateImpl> {
@override
FutureOr<void> reset() {
final form = state.form.reset();
_formRepository.updateForm(form);
emit(
state.copyWith(
form: form,

View File

@ -35,8 +35,9 @@ class FormDataStateImpl extends FormDataState {
);
@override
bool? get stringify => true;
List<Object?> get props => [status, form, errorMessage];
@override
List<Object?> get props => [status, form, errorMessage];
String toString() =>
'FormDataSate(status: ${status.name} ${(errorMessage != null) ? " [$errorMessage]" : ""}, $form';
}

View File

@ -35,7 +35,7 @@ class InputBuilder<Cubit extends FormDataCubit> extends StatelessWidget {
Widget build(BuildContext context) =>
BlocBuilder<Cubit, FormDataState>(
builder: (context, state) {
final cubit = context.read<Cubit>();
final cubit = context.watch<Cubit>();
final inputValid = state.form.validatorOf(field).valid;
return builder.call(context, cubit, state, field, inputValid);
},

View File

@ -0,0 +1,75 @@
// 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/domain/repositories/form_repository.dart';
import 'package:wyatt_form_bloc/src/presentation/features/form_data/form_data_cubit.dart';
class InputBuilderTextController<Cubit extends FormDataCubit, S extends String?,
Extra> extends StatelessWidget {
InputBuilderTextController({
required this.field,
required this.builder,
super.key,
});
final String field;
final Widget Function(
BuildContext context,
Cubit cubit,
FormDataState state,
String field,
bool inputValid,
TextEditingController textEditingController,
FormInputMetadata<Extra>? metadata,
) builder;
final TextEditingController _controller = TextEditingController();
@override
Widget build(BuildContext context) {
final formName = context.watch<Cubit>().formName;
final value =
context.read<FormRepository>().accessForm(formName).valueOf<S>(field);
_controller
..text = value ?? ''
..selection = TextSelection.fromPosition(
TextPosition(
offset: _controller.text.length,
),
);
return 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,
_controller,
metadata,
);
},
);
}
}

View File

@ -18,4 +18,5 @@ 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/input_builder_text_controller.dart';
export 'features/widgets/submit_builder.dart';