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

View File

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

View File

@ -42,6 +42,7 @@ class FormUnion extends FormOperation {
return WyattFormImpl( return WyattFormImpl(
inputs, inputs,
name: a.name,
validationStrategy: a.formValidationStrategy, 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_operations/form_operation.dart';
export 'form_validators/form_validator.dart'; export 'form_validators/form_validator.dart';
export 'input_validators/form_input_validator.dart'; export 'input_validators/form_input_validator.dart';
export 'repositories/form_repository.dart';

View File

@ -59,8 +59,9 @@ class FormInput<
); );
@override @override
bool? get stringify => true; List<Object?> get props => [key, validator, metadata];
@override @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 { abstract class WyattForm extends Equatable {
List<FormInput> get inputs; List<FormInput> get inputs;
FormValidator get formValidationStrategy; FormValidator get formValidationStrategy;
String get name;
bool containsKey(String key); 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 // ignore: one_member_abstracts
abstract class FormOperation { abstract class FormOperation {
const FormOperation(); const FormOperation();
// TODO(hpcl): handle operation on `initialInputs`
WyattForm call(WyattForm a, WyattForm b); 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/form_input_status.dart';
import 'package:wyatt_form_bloc/src/core/enums/validation_error.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 'regex_validator.dart';
part 'text_validator.dart'; part 'text_validator.dart';
part 'nullable_validator.dart';
part 'equality_validator.dart';
part 'any_validator.dart';
/// {@template form_input_validator} /// {@template form_input_validator}
/// A [FormInputValidator] represents the value of a single form input field. /// 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> { abstract class FormDataCubit<State extends FormDataState> extends Cubit<State> {
FormDataCubit(super.initialState) : super(); FormDataCubit(super.initialState) : super();
String get formName;
FutureOr<void> dataChanged<Value>( FutureOr<void> dataChanged<Value>(
String key, String key,
FormInputValidator<Value, ValidationError> dirtyValue, 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/core/enums/validation_error.dart';
import 'package:wyatt_form_bloc/src/domain/form/wyatt_form.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/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'; import 'package:wyatt_form_bloc/src/presentation/features/form_data/form_data_cubit.dart';
part 'form_data_state_impl.dart'; part 'form_data_state_impl.dart';
abstract class FormDataCubitImpl extends FormDataCubit<FormDataStateImpl> { 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 @override
FutureOr<void> dataChanged<Value>( FutureOr<void> dataChanged<Value>(
String key, String key,
FormInputValidator<Value, ValidationError> dirtyValue, FormInputValidator<Value, ValidationError> dirtyValue,
) { ) {
final form = state.form.clone(); final form = _formRepository.accessForm(_formName).clone();
try { try {
form.updateValidator(key, dirtyValue); form.updateValidator(key, dirtyValue);
_formRepository.updateForm(form);
} catch (e) { } catch (e) {
rethrow; rethrow;
} }
@ -54,7 +63,9 @@ abstract class FormDataCubitImpl extends FormDataCubit<FormDataStateImpl> {
WyattForm form, { WyattForm form, {
SetOperation operation = SetOperation.replace, 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( emit(
state.copyWith( state.copyWith(
@ -67,6 +78,7 @@ abstract class FormDataCubitImpl extends FormDataCubit<FormDataStateImpl> {
@override @override
FutureOr<void> reset() { FutureOr<void> reset() {
final form = state.form.reset(); final form = state.form.reset();
_formRepository.updateForm(form);
emit( emit(
state.copyWith( state.copyWith(
form: form, form: form,

View File

@ -35,8 +35,9 @@ class FormDataStateImpl extends FormDataState {
); );
@override @override
bool? get stringify => true; List<Object?> get props => [status, form, errorMessage];
@override @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) => Widget build(BuildContext context) =>
BlocBuilder<Cubit, FormDataState>( BlocBuilder<Cubit, FormDataState>(
builder: (context, state) { builder: (context, state) {
final cubit = context.read<Cubit>(); final cubit = context.watch<Cubit>();
final inputValid = state.form.validatorOf(field).valid; final inputValid = state.form.validatorOf(field).valid;
return builder.call(context, cubit, state, field, inputValid); 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/form_data_impl/form_data_cubit_impl.dart';
export 'features/widgets/input_builder.dart'; export 'features/widgets/input_builder.dart';
export 'features/widgets/input_builder_metadata.dart'; export 'features/widgets/input_builder_metadata.dart';
export 'features/widgets/input_builder_text_controller.dart';
export 'features/widgets/submit_builder.dart'; export 'features/widgets/submit_builder.dart';