From 4ea5625e95a3f84fe3358cc85211863b4e7a09cf Mon Sep 17 00:00:00 2001 From: BreX900 Date: Sun, 15 May 2022 11:10:46 +0200 Subject: [PATCH 1/3] fix example android build feat a field no longer needs to be inside a form bloc to be visible feat now you can animate the appearance of a field even if it is only within a list fix removed the name field from the block fields. Now the organization of the form values is similar to a json fix now form bloc only manages the steps and not lists of field blocs fix removed the form bloc field in the bloc fields to have no dependency on the form and to be able to use any field at will feat Added validate method to form bloc. Validate the form without having to submit feat now the package no longer depends on flutter_bloc, use whatever package you want: get_it, riverpod, getx or singletons ;) feat added builders for lists and groups feat now the relatives have the status of the children. Fantastic isn't it? Remember to avoid unnecessary widget rebuilds --- .../lib/examples/all_fields_form.dart | 8 +- .../examples/async_field_validation_form.dart | 7 +- .../lib/examples/conditional_fields_form.dart | 201 ++++---- .../lib/examples/list_fields_form.dart | 104 ++-- .../loading_and_initializing_form.dart | 14 +- .../lib/examples/serialized_form.dart | 14 +- form_bloc_web/lib/examples/simple_form.dart | 8 +- .../submission_error_to_field_form.dart | 11 +- .../examples/submission_progress_form.dart | 11 +- .../validation_based_on_other_field.dart | 12 +- form_bloc_web/lib/examples/wizard_form.dart | 28 +- form_bloc_web/lib/main.dart | 1 + .../lib/widgets/app_form_bloc_provider.dart | 32 ++ form_bloc_web/pubspec.yaml | 7 +- .../example/android/app/build.gradle | 9 +- .../android/app/src/main/AndroidManifest.xml | 5 +- .../com/example/example/MainActivity.kt | 6 - .../example/form_bloc_example/MainActivity.kt | 11 +- .../example/android/build.gradle | 4 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../flutter_form_bloc/example/lib/main.dart | 40 +- .../flutter_form_bloc/example/pubspec.yaml | 2 + .../lib/flutter_form_bloc.dart | 2 +- .../lib/src/checkbox_field_bloc_builder.dart | 102 ++-- .../chip/choice_chip_field_bloc_builder.dart | 135 +++-- .../chip/filter_chip_field_bloc_builder.dart | 148 +++--- .../lib/src/cubit_consumer.dart | 128 +++++ .../date_time_field_bloc_builder_base.dart | 130 +++-- .../lib/src/dropdown_field_bloc_builder.dart | 134 +++-- .../appear/can_show_field_bloc_builder.dart | 24 +- .../features/appear/form_bloc_provider.dart | 22 + .../scroll/scrollable_field_bloc_target.dart | 11 +- .../scroll/scrollable_form_bloc_manager.dart | 6 +- .../src/fields/list_field_bloc_consumer.dart | 62 +++ .../src/fields/simple_field_bloc_builder.dart | 150 +++++- .../lib/src/form_bloc_listener.dart | 86 ++-- .../checkbox_group_field_bloc_builder.dart | 49 +- .../fields/radio_button_group_field_bloc.dart | 54 +- .../src/slider/slider_field_bloc_builder.dart | 71 ++- .../stepper/stepper_form_bloc_builder.dart | 19 +- .../suffix_button_bloc_builder.dart | 18 +- .../lib/src/switch_field_bloc_builder.dart | 95 ++-- .../lib/src/text_field_bloc_builder.dart | 62 +-- .../lib/src/utils/functions.dart | 57 ++- packages/flutter_form_bloc/pubspec.yaml | 9 +- packages/form_bloc/example/main.dart | 12 +- .../boolean_field/boolean_field_bloc.dart | 2 - .../boolean_field/boolean_field_state.dart | 10 +- .../lib/src/blocs/field/field_bloc.dart | 183 +------ .../lib/src/blocs/field/field_state.dart | 105 ++-- .../lib/src/blocs/form/form_bloc.dart | 261 +++++----- .../lib/src/blocs/form/form_bloc_utils.dart | 74 ++- .../lib/src/blocs/form/form_state.dart | 471 +++++++++--------- .../blocs/group_field/group_field_bloc.dart | 93 ++-- .../blocs/input_field/input_field_bloc.dart | 6 +- .../blocs/input_field/input_field_state.dart | 10 +- .../src/blocs/list_field/list_field_bloc.dart | 125 ++--- .../multi_select_field_bloc.dart | 18 +- .../multi_select_field_state.dart | 10 +- .../blocs/select_field/select_field_bloc.dart | 6 +- .../select_field/select_field_state.dart | 13 +- .../src/blocs/text_field/text_field_bloc.dart | 6 +- .../blocs/text_field/text_field_state.dart | 10 +- .../lib/src/extension/extension.dart | 17 +- .../boolean_field_bloc_test.dart | 9 +- .../boolean_field_state_test.dart | 5 - .../test/field_bloc/field_bloc_test.dart | 43 -- .../field_bloc/multi_field_bloc_test.dart | 23 +- .../test/form_bloc/form_bloc_test.dart | 28 +- .../test/form_bloc/form_bloc_utils_test.dart | 87 ++-- .../text_field/text_field_state_test.dart | 5 - 71 files changed, 1843 insertions(+), 1900 deletions(-) create mode 100644 form_bloc_web/lib/widgets/app_form_bloc_provider.dart delete mode 100644 packages/flutter_form_bloc/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt create mode 100644 packages/flutter_form_bloc/lib/src/cubit_consumer.dart create mode 100644 packages/flutter_form_bloc/lib/src/features/appear/form_bloc_provider.dart create mode 100644 packages/flutter_form_bloc/lib/src/fields/list_field_bloc_consumer.dart diff --git a/form_bloc_web/lib/examples/all_fields_form.dart b/form_bloc_web/lib/examples/all_fields_form.dart index eddd32e8..ee9882c2 100644 --- a/form_bloc_web/lib/examples/all_fields_form.dart +++ b/form_bloc_web/lib/examples/all_fields_form.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_bloc/flutter_form_bloc.dart'; void main() => runApp(const App()); @@ -44,7 +45,7 @@ class AllFieldsFormBloc extends FormBloc { final time1 = InputFieldBloc(initialValue: null); AllFieldsFormBloc() { - addFieldBlocs(fieldBlocs: [ + addStep(ListFieldBloc(fieldBlocs: [ text1, boolean1, boolean2, @@ -54,7 +55,7 @@ class AllFieldsFormBloc extends FormBloc { date1, dateAndTime1, time1, - ]); + ])); } @override @@ -90,7 +91,8 @@ class AllFieldsForm extends StatelessWidget { ), child: Scaffold( appBar: AppBar(title: const Text('Built-in Widgets')), - body: FormBlocListener( + body: FormBlocListener( + formBloc: formBloc, onSubmitting: (context, state) { LoadingDialog.show(context); }, diff --git a/form_bloc_web/lib/examples/async_field_validation_form.dart b/form_bloc_web/lib/examples/async_field_validation_form.dart index c7204201..c01faa6b 100644 --- a/form_bloc_web/lib/examples/async_field_validation_form.dart +++ b/form_bloc_web/lib/examples/async_field_validation_form.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_bloc/flutter_form_bloc.dart'; void main() => runApp(const App()); @@ -25,7 +26,7 @@ class AsyncFieldValidationFormBloc extends FormBloc { ); AsyncFieldValidationFormBloc() { - addFieldBlocs(fieldBlocs: [username]); + addStep(username); username.addAsyncValidators( [_checkUsername], @@ -75,8 +76,8 @@ class AsyncFieldValidationForm extends StatelessWidget { return Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar(title: const Text('Async Field Validation')), - body: - FormBlocListener( + body: FormBlocListener( + formBloc: formBloc, onSubmitting: (context, state) { LoadingDialog.show(context); }, diff --git a/form_bloc_web/lib/examples/conditional_fields_form.dart b/form_bloc_web/lib/examples/conditional_fields_form.dart index 818eff9f..501fed61 100644 --- a/form_bloc_web/lib/examples/conditional_fields_form.dart +++ b/form_bloc_web/lib/examples/conditional_fields_form.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_form_bloc/flutter_form_bloc.dart'; +import 'package:form_bloc_web/widgets/app_form_bloc_provider.dart'; void main() => runApp(const App()); @@ -16,6 +17,8 @@ class App extends StatelessWidget { } class ConditionalFieldsFormBloc extends FormBloc { + final _step = ListFieldBloc(); + final doYouLikeFormBloc = SelectFieldBloc( validators: [FieldBlocValidators.required], items: ['No', 'Yes'], @@ -32,38 +35,30 @@ class ConditionalFieldsFormBloc extends FormBloc { ); ConditionalFieldsFormBloc() { - addFieldBlocs( - fieldBlocs: [ - doYouLikeFormBloc, - ], - ); + addStep(_step..addFieldBloc(doYouLikeFormBloc)); showSecretField.onValueChanges( onData: (previous, current) async* { if (current.value) { - addFieldBlocs(fieldBlocs: [secretField]); + _step.addFieldBloc(secretField); } else { - removeFieldBlocs(fieldBlocs: [secretField]); + _step.removeFieldBloc(secretField); } }, ); doYouLikeFormBloc.onValueChanges( onData: (previous, current) async* { - removeFieldBlocs( - fieldBlocs: [ - whyNotYouLikeFormBloc, - showSecretField, - secretField, - ], - ); + _step.removeFieldBlocs([ + whyNotYouLikeFormBloc, + showSecretField, + secretField, + ]); if (current.value == 'No') { - addFieldBlocs(fieldBlocs: [ - whyNotYouLikeFormBloc, - ]); + _step.addFieldBloc(whyNotYouLikeFormBloc); } else if (current.value == 'Yes') { - addFieldBlocs(fieldBlocs: [ + _step.addFieldBlocs([ showSecretField, if (showSecretField.value) secretField, ]); @@ -88,13 +83,9 @@ class ConditionalFieldsFormBloc extends FormBloc { debugPrint(showSecretField.value.toString()); debugPrint(secretField.value); - try { - await Future.delayed(const Duration(milliseconds: 500)); + await Future.delayed(const Duration(milliseconds: 500)); - emitSuccess(); - } catch (e) { - emitFailure(); - } + emitSuccess(); } } @@ -103,98 +94,97 @@ class ConditionalFieldsForm extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocProvider( + return AppFormBlocProvider( create: (context) => ConditionalFieldsFormBloc(), - child: Builder( - builder: (context) { - final formBloc = BlocProvider.of(context); - - return Theme( - data: Theme.of(context).copyWith( - inputDecorationTheme: InputDecorationTheme( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(20), - ), + builder: (context, formBloc) { + return Theme( + data: Theme.of(context).copyWith( + inputDecorationTheme: InputDecorationTheme( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), ), ), - child: Scaffold( - appBar: AppBar(title: const Text('Conditional Fields')), - body: FormBlocListener( - onSubmitting: (context, state) { - LoadingDialog.show(context); - }, - onSubmissionFailed: (context, state) { - LoadingDialog.hide(context); - }, - onSuccess: (context, state) { - LoadingDialog.hide(context); - - Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (_) => const SuccessScreen())); - }, - onFailure: (context, state) { - LoadingDialog.hide(context); - - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(state.failureResponse!))); - }, - child: SingleChildScrollView( - physics: const ClampingScrollPhysics(), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - RadioButtonGroupFieldBlocBuilder( - selectFieldBloc: formBloc.doYouLikeFormBloc, - itemBuilder: (context, dynamic value) => - FieldItem(child: Text(value)), - decoration: const InputDecoration( - labelText: 'Do you like form bloc?', - prefixIcon: SizedBox(), - ), - ), - TextFieldBlocBuilder( - textFieldBloc: formBloc.whyNotYouLikeFormBloc, - keyboardType: TextInputType.multiline, - maxLines: 1, - decoration: const InputDecoration( - labelText: 'Why?', - prefixIcon: Icon(Icons.sentiment_very_dissatisfied), - ), + ), + child: Scaffold( + appBar: AppBar(title: const Text('Conditional Fields')), + body: FormBlocListener( + formBloc: formBloc, + onSubmitting: (context, state) { + LoadingDialog.show(context); + }, + onSubmissionFailed: (context, state) { + LoadingDialog.hide(context); + }, + onSuccess: (context, state) { + LoadingDialog.hide(context); + + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (_) => const SuccessScreen())); + }, + onFailure: (context, state) { + LoadingDialog.hide(context); + + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text('${state.failureResponse}'), + )); + }, + child: SingleChildScrollView( + physics: const ClampingScrollPhysics(), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + RadioButtonGroupFieldBlocBuilder( + selectFieldBloc: formBloc.doYouLikeFormBloc, + itemBuilder: (context, dynamic value) => + FieldItem(child: Text(value)), + decoration: const InputDecoration( + labelText: 'Do you like form bloc?', + prefixIcon: SizedBox(), ), - SizedBox( - width: 200, - child: CheckboxFieldBlocBuilder( - booleanFieldBloc: formBloc.showSecretField, - controlAffinity: - FieldBlocBuilderControlAffinity.trailing, - body: Container( - alignment: Alignment.center, - child: const Text('Do you want to see a secret field?'), - ), - ), + ), + TextFieldBlocBuilder( + textFieldBloc: formBloc.whyNotYouLikeFormBloc, + keyboardType: TextInputType.multiline, + maxLines: 1, + decoration: const InputDecoration( + labelText: 'Why?', + prefixIcon: Icon(Icons.sentiment_very_dissatisfied), ), - TextFieldBlocBuilder( - textFieldBloc: formBloc.secretField, - keyboardType: TextInputType.multiline, - decoration: const InputDecoration( - labelText: 'Secret field', - prefixIcon: Icon(Icons.sentiment_very_satisfied), + ), + SizedBox( + width: 200, + child: CheckboxFieldBlocBuilder( + booleanFieldBloc: formBloc.showSecretField, + controlAffinity: + FieldBlocBuilderControlAffinity.trailing, + body: Container( + alignment: Alignment.center, + child: const Text( + 'Do you want to see a secret field?'), ), ), - ElevatedButton( - onPressed: formBloc.submit, - child: const Text('SUBMIT'), + ), + TextFieldBlocBuilder( + textFieldBloc: formBloc.secretField, + keyboardType: TextInputType.multiline, + decoration: const InputDecoration( + labelText: 'Secret field', + prefixIcon: Icon(Icons.sentiment_very_satisfied), ), - ], - ), + ), + ElevatedButton( + onPressed: formBloc.submit, + child: const Text('SUBMIT'), + ), + ], ), ), ), ), - ); - }, - ), + ), + ); + }, ); } } @@ -249,7 +239,8 @@ class SuccessScreen extends StatelessWidget { const SizedBox(height: 10), ElevatedButton.icon( onPressed: () => Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (_) => const ConditionalFieldsForm())), + MaterialPageRoute( + builder: (_) => const ConditionalFieldsForm())), icon: const Icon(Icons.replay), label: const Text('AGAIN'), ), diff --git a/form_bloc_web/lib/examples/list_fields_form.dart b/form_bloc_web/lib/examples/list_fields_form.dart index 031f740b..e6b7c88d 100644 --- a/form_bloc_web/lib/examples/list_fields_form.dart +++ b/form_bloc_web/lib/examples/list_fields_form.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_bloc/flutter_form_bloc.dart'; void main() => runApp(const App()); @@ -19,25 +20,22 @@ class App extends StatelessWidget { } class ListFieldFormBloc extends FormBloc { - final clubName = TextFieldBloc(name: 'clubName'); + final clubName = TextFieldBloc(); - final members = ListFieldBloc(name: 'members'); + final members = ListFieldBloc(); ListFieldFormBloc() { - addFieldBlocs( - fieldBlocs: [ - clubName, - members, - ], - ); + addStep(ListFieldBloc(fieldBlocs: [ + clubName, + members, + ])); } void addMember() { members.addFieldBloc(MemberFieldBloc( - name: 'member', - firstName: TextFieldBloc(name: 'firstName'), - lastName: TextFieldBloc(name: 'lastName'), - hobbies: ListFieldBloc(name: 'hobbies'), + firstName: TextFieldBloc(), + lastName: TextFieldBloc(), + hobbies: ListFieldBloc(), )); } @@ -82,7 +80,7 @@ class ListFieldFormBloc extends FormBloc { debugPrint(clubV1.toJson().toString()); // With Serialization - final clubV2 = Club.fromJson(state.toJson()); + final clubV2 = Club.fromJson(state.toJson()[0]!); debugPrint('clubV2'); debugPrint(clubV2.toJson().toString()); @@ -105,35 +103,34 @@ class MemberFieldBloc extends GroupFieldBloc { required this.firstName, required this.lastName, required this.hobbies, - String? name, - }) : super(name: name, fieldBlocs: [firstName, lastName, hobbies]); + }) : super( + fieldBlocs: { + 'firstName': firstName, + 'lastName': lastName, + 'hobbies': hobbies, + }, + ); } class Club { - String? clubName; - List? members; - - Club({this.clubName, this.members}); - - Club.fromJson(Map json) { - clubName = json['clubName']; - if (json['members'] != null) { - members = []; - json['members'].forEach((v) { - members!.add(Member.fromJson(v)); - }); - } - } + final String clubName; + final List members; + + Club({required this.clubName, required this.members}); Map toJson() { - final Map data = {}; - data['clubName'] = clubName; - if (members != null) { - data['members'] = members!.map((v) => v.toJson()).toList(); - } - return data; + return { + 'clubName': clubName, + 'members': members, + }; } + factory Club.fromJson(Map map) { + return Club( + clubName: map['clubName'] as String, + members: map['members'] as List, + ); + } @override String toString() => '''Club { clubName: $clubName, @@ -142,24 +139,30 @@ class Club { } class Member { - String? firstName; - String? lastName; - List? hobbies; + final String firstName; + final String lastName; + final List hobbies; - Member({this.firstName, this.lastName, this.hobbies}); + Member({ + required this.firstName, + required this.lastName, + required this.hobbies, + }); - Member.fromJson(Map json) { - firstName = json['firstName']; - lastName = json['lastName']; - hobbies = json['hobbies'].cast(); + Map toJson() { + return { + 'firstName': firstName, + 'lastName': lastName, + 'hobbies': hobbies, + }; } - Map toJson() { - final Map data = {}; - data['firstName'] = firstName; - data['lastName'] = lastName; - data['hobbies'] = hobbies; - return data; + factory Member.fromJson(Map map) { + return Member( + firstName: map['firstName'] as String, + lastName: map['lastName'] as String, + hobbies: map['hobbies'] as List, + ); } @override @@ -196,7 +199,8 @@ class ListFieldsForm extends StatelessWidget { onPressed: formBloc.submit, child: const Icon(Icons.send), ), - body: FormBlocListener( + body: FormBlocListener( + formBloc: formBloc, onSubmitting: (context, state) { LoadingDialog.show(context); }, diff --git a/form_bloc_web/lib/examples/loading_and_initializing_form.dart b/form_bloc_web/lib/examples/loading_and_initializing_form.dart index f561a655..8e292919 100644 --- a/form_bloc_web/lib/examples/loading_and_initializing_form.dart +++ b/form_bloc_web/lib/examples/loading_and_initializing_form.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_bloc/flutter_form_bloc.dart'; void main() => runApp(const App()); @@ -21,9 +22,9 @@ class LoadingFormBloc extends FormBloc { final select = SelectFieldBloc(); LoadingFormBloc() : super(isLoading: true) { - addFieldBlocs( + addStep(ListFieldBloc( fieldBlocs: [text, select], - ); + )); } var _throwException = true; @@ -88,7 +89,8 @@ class LoadingForm extends StatelessWidget { ), child: Scaffold( appBar: AppBar(title: const Text('Loading and Initializing')), - body: FormBlocListener( + body: FormBlocListener( + formBloc: loadingFormBloc, onSubmitting: (context, state) { LoadingDialog.show(context); }, @@ -116,10 +118,12 @@ class LoadingForm extends StatelessWidget { child: SingleChildScrollView( child: Column( children: [ - const Icon(Icons.sentiment_dissatisfied, size: 70), + const Icon(Icons.sentiment_dissatisfied, + size: 70), const SizedBox(height: 20), Container( - padding: const EdgeInsets.symmetric(horizontal: 12), + padding: + const EdgeInsets.symmetric(horizontal: 12), alignment: Alignment.center, child: Text( state.failureResponse ?? diff --git a/form_bloc_web/lib/examples/serialized_form.dart b/form_bloc_web/lib/examples/serialized_form.dart index 40264e8d..e949f6c6 100644 --- a/form_bloc_web/lib/examples/serialized_form.dart +++ b/form_bloc_web/lib/examples/serialized_form.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_bloc/flutter_form_bloc.dart'; void main() => runApp(const App()); @@ -18,29 +19,25 @@ class App extends StatelessWidget { } class SerializedFormBloc extends FormBloc { - final name = TextFieldBloc( - name: 'name', - ); + final name = TextFieldBloc(); final gender = SelectFieldBloc( - name: 'gender', items: ['male', 'female'], ); final birthDate = InputFieldBloc( - name: 'birthDate', initialValue: null, toJson: (value) => value!.toUtc().toIso8601String(), ); SerializedFormBloc() { - addFieldBlocs( + addStep(ListFieldBloc( fieldBlocs: [ name, gender, birthDate, ], - ); + )); } @override @@ -80,7 +77,8 @@ class SerializedForm extends StatelessWidget { onPressed: formBloc.submit, child: const Icon(Icons.send), ), - body: FormBlocListener( + body: FormBlocListener( + formBloc: formBloc, onSuccess: (context, state) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text(state.successResponse!), diff --git a/form_bloc_web/lib/examples/simple_form.dart b/form_bloc_web/lib/examples/simple_form.dart index a688ee5c..901a539d 100644 --- a/form_bloc_web/lib/examples/simple_form.dart +++ b/form_bloc_web/lib/examples/simple_form.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_bloc/flutter_form_bloc.dart'; void main() => runApp(const App()); @@ -32,13 +33,13 @@ class LoginFormBloc extends FormBloc { final showSuccessResponse = BooleanFieldBloc(); LoginFormBloc() { - addFieldBlocs( + addStep(ListFieldBloc( fieldBlocs: [ email, password, showSuccessResponse, ], - ); + )); } @override @@ -71,7 +72,8 @@ class LoginForm extends StatelessWidget { return Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar(title: const Text('Login')), - body: FormBlocListener( + body: FormBlocListener( + formBloc: loginFormBloc, onSubmitting: (context, state) { LoadingDialog.show(context); }, diff --git a/form_bloc_web/lib/examples/submission_error_to_field_form.dart b/form_bloc_web/lib/examples/submission_error_to_field_form.dart index 728c7b7b..503e7327 100644 --- a/form_bloc_web/lib/examples/submission_error_to_field_form.dart +++ b/form_bloc_web/lib/examples/submission_error_to_field_form.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_bloc/flutter_form_bloc.dart'; void main() => runApp(const App()); @@ -19,11 +20,7 @@ class SubmissionErrorToFieldFormBloc extends FormBloc { final username = TextFieldBloc(); SubmissionErrorToFieldFormBloc() { - addFieldBlocs( - fieldBlocs: [ - username, - ], - ); + addStep(username); } @override @@ -69,8 +66,8 @@ class SubmissionErrorToFieldForm extends StatelessWidget { ), child: Scaffold( appBar: AppBar(title: const Text('Submission Error to Field')), - body: FormBlocListener( + body: FormBlocListener( + formBloc: formBloc, onSubmitting: (context, state) { LoadingDialog.show(context); }, diff --git a/form_bloc_web/lib/examples/submission_progress_form.dart b/form_bloc_web/lib/examples/submission_progress_form.dart index 68a9e2ff..fa47edb1 100644 --- a/form_bloc_web/lib/examples/submission_progress_form.dart +++ b/form_bloc_web/lib/examples/submission_progress_form.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_bloc/flutter_form_bloc.dart'; import 'package:liquid_progress_indicator/liquid_progress_indicator.dart'; import 'package:rxdart/rxdart.dart'; @@ -26,9 +27,7 @@ class SubmissionProgressFormBloc extends FormBloc { ); SubmissionProgressFormBloc() { - addFieldBlocs( - fieldBlocs: [username], - ); + addStep(username); } final List _fakeUploads = []; @@ -120,7 +119,8 @@ class SubmissionProgressForm extends StatelessWidget { return Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar(title: const Text('Submission Progress')), - body: FormBlocListener( + body: FormBlocListener( + formBloc: formBloc, onSuccess: (context, state) { Navigator.of(context).pushReplacement( MaterialPageRoute(builder: (_) => const SuccessScreen())); @@ -298,7 +298,8 @@ class SuccessScreen extends StatelessWidget { const SizedBox(height: 10), ElevatedButton.icon( onPressed: () => Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (_) => const SubmissionProgressForm())), + MaterialPageRoute( + builder: (_) => const SubmissionProgressForm())), icon: const Icon(Icons.replay), label: const Text('AGAIN'), ), diff --git a/form_bloc_web/lib/examples/validation_based_on_other_field.dart b/form_bloc_web/lib/examples/validation_based_on_other_field.dart index a7c95472..76047961 100644 --- a/form_bloc_web/lib/examples/validation_based_on_other_field.dart +++ b/form_bloc_web/lib/examples/validation_based_on_other_field.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_bloc/flutter_form_bloc.dart'; void main() => runApp(const App()); @@ -35,9 +36,9 @@ class ValidationBasedOnOtherFieldFormBloc extends FormBloc { } ValidationBasedOnOtherFieldFormBloc() { - addFieldBlocs( + addStep(ListFieldBloc( fieldBlocs: [password, confirmPassword], - ); + )); confirmPassword ..addValidators([_confirmPassword(password)]) @@ -74,9 +75,10 @@ class ValidationBasedOnOtherFieldForm extends StatelessWidget { return Scaffold( resizeToAvoidBottomInset: false, - appBar: AppBar(title: const Text('Validation based on other field')), - body: FormBlocListener( + appBar: + AppBar(title: const Text('Validation based on other field')), + body: FormBlocListener( + formBloc: loginFormBloc, onSubmitting: (context, state) { LoadingDialog.show(context); }, diff --git a/form_bloc_web/lib/examples/wizard_form.dart b/form_bloc_web/lib/examples/wizard_form.dart index eb229135..cbd9f55e 100644 --- a/form_bloc_web/lib/examples/wizard_form.dart +++ b/form_bloc_web/lib/examples/wizard_form.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_bloc/flutter_form_bloc.dart'; void main() => runApp(const App()); @@ -54,18 +55,15 @@ class WizardFormBloc extends FormBloc { final facebook = TextFieldBloc(); WizardFormBloc() { - addFieldBlocs( - step: 0, + addStep(ListFieldBloc( fieldBlocs: [username, email, password], - ); - addFieldBlocs( - step: 1, + )); + addStep(ListFieldBloc( fieldBlocs: [firstName, lastName, gender, birthDate], - ); - addFieldBlocs( - step: 2, + )); + addStep(ListFieldBloc( fieldBlocs: [github, twitter, facebook], - ); + )); } bool _showEmailTakenError = true; @@ -120,6 +118,8 @@ class _WizardFormState extends State { create: (context) => WizardFormBloc(), child: Builder( builder: (context) { + final formBloc = context.read(); + return Theme( data: Theme.of(context).copyWith( inputDecorationTheme: InputDecorationTheme( @@ -141,15 +141,17 @@ class _WizardFormState extends State { ], ), body: SafeArea( - child: FormBlocListener( + child: FormBlocListener( + formBloc: formBloc, onSubmitting: (context, state) => LoadingDialog.show(context), - onSubmissionFailed: (context, state) => LoadingDialog.hide(context), + onSubmissionFailed: (context, state) => + LoadingDialog.hide(context), onSuccess: (context, state) { LoadingDialog.hide(context); if (state.stepCompleted == state.lastStep) { - Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (_) => const SuccessScreen())); + Navigator.of(context).pushReplacement(MaterialPageRoute( + builder: (_) => const SuccessScreen())); } }, onFailure: (context, state) { diff --git a/form_bloc_web/lib/main.dart b/form_bloc_web/lib/main.dart index 1dcb2311..e9fd6c2b 100644 --- a/form_bloc_web/lib/main.dart +++ b/form_bloc_web/lib/main.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_bloc/flutter_form_bloc.dart'; import 'package:form_bloc_web/pages/home_page.dart'; import 'package:form_bloc_web/routes.dart'; diff --git a/form_bloc_web/lib/widgets/app_form_bloc_provider.dart b/form_bloc_web/lib/widgets/app_form_bloc_provider.dart new file mode 100644 index 00000000..d1704619 --- /dev/null +++ b/form_bloc_web/lib/widgets/app_form_bloc_provider.dart @@ -0,0 +1,32 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_form_bloc/flutter_form_bloc.dart'; +import 'package:provider/provider.dart'; + +class AppFormBlocProvider extends StatelessWidget { + final Create create; + final Widget Function(BuildContext context, TFormBloc formBloc) builder; + + const AppFormBlocProvider({ + Key? key, + required this.create, + required this.builder, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: create, + child: Builder( + builder: (context) { + final formBloc = context.select((e) => e); + + return FormBlocProvider( + formBloc: formBloc, + child: builder(context, formBloc), + ); + }, + ), + ); + } +} diff --git a/form_bloc_web/pubspec.yaml b/form_bloc_web/pubspec.yaml index e07d02ad..aef054f3 100644 --- a/form_bloc_web/pubspec.yaml +++ b/form_bloc_web/pubspec.yaml @@ -10,9 +10,10 @@ dependencies: sdk: flutter # form_bloc: # path: ../packages/form_bloc -# flutter_form_bloc: -# path: ../packages/flutter_form_bloc - flutter_form_bloc: ^0.30.1 + flutter_form_bloc: + path: ../packages/flutter_form_bloc +# flutter_form_bloc: ^0.30.1 + flutter_bloc: ^8.0.1 animations: ^2.0.2 flutter_syntax_view: ^4.0.0 flash: ^1.5.2 diff --git a/packages/flutter_form_bloc/example/android/app/build.gradle b/packages/flutter_form_bloc/example/android/app/build.gradle index 79c473f4..98de8876 100644 --- a/packages/flutter_form_bloc/example/android/app/build.gradle +++ b/packages/flutter_form_bloc/example/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 28 + compileSdkVersion 31 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -39,8 +39,8 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.form_bloc_example" - minSdkVersion 16 - targetSdkVersion 28 + minSdkVersion 24 + targetSdkVersion 31 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -61,7 +61,4 @@ flutter { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test:runner:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } diff --git a/packages/flutter_form_bloc/example/android/app/src/main/AndroidManifest.xml b/packages/flutter_form_bloc/example/android/app/src/main/AndroidManifest.xml index c73176e1..98a5c543 100644 --- a/packages/flutter_form_bloc/example/android/app/src/main/AndroidManifest.xml +++ b/packages/flutter_form_bloc/example/android/app/src/main/AndroidManifest.xml @@ -6,7 +6,7 @@ additional functionality it is fine to subclass or reimplement FlutterApplication and put your custom class here. --> + android:windowSoftInputMode="adjustResize" + android:exported="true"> diff --git a/packages/flutter_form_bloc/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/packages/flutter_form_bloc/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt deleted file mode 100644 index e793a000..00000000 --- a/packages/flutter_form_bloc/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.example.example - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity: FlutterActivity() { -} diff --git a/packages/flutter_form_bloc/example/android/app/src/main/kotlin/com/example/form_bloc_example/MainActivity.kt b/packages/flutter_form_bloc/example/android/app/src/main/kotlin/com/example/form_bloc_example/MainActivity.kt index 19b4902f..30fed964 100644 --- a/packages/flutter_form_bloc/example/android/app/src/main/kotlin/com/example/form_bloc_example/MainActivity.kt +++ b/packages/flutter_form_bloc/example/android/app/src/main/kotlin/com/example/form_bloc_example/MainActivity.kt @@ -1,12 +1,5 @@ package com.example.form_bloc_example -import androidx.annotation.NonNull; -import io.flutter.embedding.android.FlutterActivity -import io.flutter.embedding.engine.FlutterEngine -import io.flutter.plugins.GeneratedPluginRegistrant +import io.flutter.embedding.android.FlutterActivity; -class MainActivity: FlutterActivity() { - override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { - GeneratedPluginRegistrant.registerWith(flutterEngine); - } -} +class MainActivity: FlutterActivity() {} diff --git a/packages/flutter_form_bloc/example/android/build.gradle b/packages/flutter_form_bloc/example/android/build.gradle index 3100ad2d..a57760a8 100644 --- a/packages/flutter_form_bloc/example/android/build.gradle +++ b/packages/flutter_form_bloc/example/android/build.gradle @@ -1,12 +1,12 @@ buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = '1.6.21' repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' + classpath 'com.android.tools.build:gradle:4.2.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/packages/flutter_form_bloc/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/flutter_form_bloc/example/android/gradle/wrapper/gradle-wrapper.properties index 296b146b..939efa29 100644 --- a/packages/flutter_form_bloc/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/flutter_form_bloc/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip diff --git a/packages/flutter_form_bloc/example/lib/main.dart b/packages/flutter_form_bloc/example/lib/main.dart index 37f26b17..d02d35a0 100644 --- a/packages/flutter_form_bloc/example/lib/main.dart +++ b/packages/flutter_form_bloc/example/lib/main.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_bloc/flutter_form_bloc.dart'; void main() { @@ -40,6 +41,21 @@ class App extends StatelessWidget { } class AllFieldsFormBloc extends FormBloc { + late final step = ListFieldBloc( + fieldBlocs: [ + text1, + boolean1, + boolean2, + select1, + select2, + multiSelect1, + date1, + dateAndTime1, + time1, + double1, + ], + ); + final text1 = TextFieldBloc(); final boolean1 = BooleanFieldBloc(); @@ -78,18 +94,7 @@ class AllFieldsFormBloc extends FormBloc { ); AllFieldsFormBloc() : super(autoValidate: false) { - addFieldBlocs(fieldBlocs: [ - text1, - boolean1, - boolean2, - select1, - select2, - multiSelect1, - date1, - dateAndTime1, - time1, - double1, - ]); + addStep(step); } void addErrors() { @@ -147,7 +152,8 @@ class AllFieldsForm extends StatelessWidget { ), ], ), - body: FormBlocListener( + body: FormBlocListener( + formBloc: formBloc, onSubmitting: (context, state) { LoadingDialog.show(context); }, @@ -251,13 +257,13 @@ class AllFieldsForm extends StatelessWidget { Row( children: [ IconButton( - onPressed: () => formBloc.addFieldBloc( - fieldBloc: formBloc.select1), + onPressed: () => + formBloc.step.addFieldBloc(formBloc.select1), icon: const Icon(Icons.add), ), IconButton( - onPressed: () => formBloc.removeFieldBloc( - fieldBloc: formBloc.select1), + onPressed: () => + formBloc.step.removeFieldBloc(formBloc.select1), icon: const Icon(Icons.delete), ), ], diff --git a/packages/flutter_form_bloc/example/pubspec.yaml b/packages/flutter_form_bloc/example/pubspec.yaml index 4fbb5270..e917e5f5 100644 --- a/packages/flutter_form_bloc/example/pubspec.yaml +++ b/packages/flutter_form_bloc/example/pubspec.yaml @@ -9,6 +9,8 @@ environment: dependencies: flutter: sdk: flutter + + flutter_bloc: flutter_form_bloc: path: ../ diff --git a/packages/flutter_form_bloc/lib/flutter_form_bloc.dart b/packages/flutter_form_bloc/lib/flutter_form_bloc.dart index 4420ee81..6e78562e 100644 --- a/packages/flutter_form_bloc/lib/flutter_form_bloc.dart +++ b/packages/flutter_form_bloc/lib/flutter_form_bloc.dart @@ -1,6 +1,5 @@ library flutter_form_bloc; -export 'package:flutter_bloc/flutter_bloc.dart'; export 'package:form_bloc/form_bloc.dart'; export 'src/checkbox_field_bloc_builder.dart'; @@ -11,6 +10,7 @@ export 'src/date_time/date_time_field_bloc_builder.dart'; export 'src/date_time/time_field_bloc_builder.dart'; export 'src/dropdown_field_bloc_builder.dart'; export 'src/features/appear/can_show_field_bloc_builder.dart'; +export 'src/features/appear/form_bloc_provider.dart'; export 'src/features/scroll/scrollable_field_bloc_target.dart'; export 'src/features/scroll/scrollable_form_bloc_manager.dart'; export 'src/field_bloc_builder.dart'; diff --git a/packages/flutter_form_bloc/lib/src/checkbox_field_bloc_builder.dart b/packages/flutter_form_bloc/lib/src/checkbox_field_bloc_builder.dart index 1ce2408c..10027f63 100644 --- a/packages/flutter_form_bloc/lib/src/checkbox_field_bloc_builder.dart +++ b/packages/flutter_form_bloc/lib/src/checkbox_field_bloc_builder.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_form_bloc/src/features/appear/can_show_field_bloc_builder.dart'; +import 'package:flutter_form_bloc/src/fields/simple_field_bloc_builder.dart'; import 'package:flutter_form_bloc/src/theme/field_theme_resolver.dart'; import 'package:flutter_form_bloc/src/theme/form_bloc_theme.dart'; import 'package:flutter_form_bloc/src/utils/utils.dart'; @@ -127,68 +126,65 @@ class CheckboxFieldBlocBuilder extends StatelessWidget { data: Theme.of(context).copyWith( checkboxTheme: fieldTheme.checkboxTheme, ), - child: CanShowFieldBlocBuilder( + child: SimpleFieldBlocBuilder( fieldBloc: booleanFieldBloc, - animate: animateWhenCanShow, - builder: (_, __) { - return BlocBuilder( - bloc: booleanFieldBloc, - builder: (context, state) { - final isEnabled = fieldBlocIsEnabled( - isEnabled: this.isEnabled, - enableOnlyWhenFormBlocCanSubmit: - enableOnlyWhenFormBlocCanSubmit, - fieldBlocState: state, - ); - - return DefaultFieldBlocBuilderPadding( - padding: padding, - child: InputDecorator( - decoration: Style.inputDecorationWithoutBorder.copyWith( - prefixIcon: fieldTheme.controlAffinity! == - FieldBlocBuilderControlAffinity.leading - ? _buildCheckbox(context, state) - : null, - suffixIcon: fieldTheme.controlAffinity! == - FieldBlocBuilderControlAffinity.trailing - ? _buildCheckbox(context, state) - : null, - errorText: Style.getErrorText( - context: context, - errorBuilder: errorBuilder, - fieldBlocState: state, - fieldBloc: booleanFieldBloc, - ), - ), - child: DefaultTextStyle( - style: Style.resolveTextStyle( - isEnabled: isEnabled, - style: fieldTheme.textStyle!, - color: fieldTheme.textColor!, - ), - child: Container( - constraints: const BoxConstraints( - minHeight: kMinInteractiveDimension, - ), - alignment: alignment, - child: body, - ), + animateWhenCanShow: animateWhenCanShow, + enableOnlyWhenFormBlocCanSubmit: enableOnlyWhenFormBlocCanSubmit, + isEnabled: isEnabled, + // TODO: Implement readOnly + readOnly: false, + nextFocusNode: nextFocusNode, + builder: (context, state, data) { + final isEnabled = data.isEnabled; + + return DefaultFieldBlocBuilderPadding( + padding: padding, + child: InputDecorator( + decoration: Style.inputDecorationWithoutBorder.copyWith( + prefixIcon: fieldTheme.controlAffinity! == + FieldBlocBuilderControlAffinity.leading + ? _buildCheckbox(context, state, data) + : null, + suffixIcon: fieldTheme.controlAffinity! == + FieldBlocBuilderControlAffinity.trailing + ? _buildCheckbox(context, state, data) + : null, + errorText: Style.getErrorText( + context: context, + errorBuilder: errorBuilder, + fieldBlocState: state, + fieldBloc: booleanFieldBloc, + ), + ), + child: DefaultTextStyle( + style: Style.resolveTextStyle( + isEnabled: isEnabled, + style: fieldTheme.textStyle!, + color: fieldTheme.textColor!, + ), + child: Container( + constraints: const BoxConstraints( + minHeight: kMinInteractiveDimension, ), + alignment: alignment, + child: body, ), - ); - }, + ), + ), ); }, ), ); } - Checkbox _buildCheckbox(BuildContext context, BooleanFieldBlocState state) { + Checkbox _buildCheckbox( + BuildContext context, + BooleanFieldBlocState state, + FieldBlocBuilderData data, + ) { return Checkbox( value: state.value, - onChanged: fieldBlocBuilderOnChange( - isEnabled: isEnabled, - nextFocusNode: nextFocusNode, + onChanged: data.buildOnChange( onChanged: booleanFieldBloc.changeValue as void Function(bool?), ), ); diff --git a/packages/flutter_form_bloc/lib/src/chip/choice_chip_field_bloc_builder.dart b/packages/flutter_form_bloc/lib/src/chip/choice_chip_field_bloc_builder.dart index eae9b5d3..14d4924d 100644 --- a/packages/flutter_form_bloc/lib/src/chip/choice_chip_field_bloc_builder.dart +++ b/packages/flutter_form_bloc/lib/src/chip/choice_chip_field_bloc_builder.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_bloc/src/chip/chip_field_item_builder.dart'; import 'package:flutter_form_bloc/src/fields/simple_field_bloc_builder.dart'; import 'package:flutter_form_bloc/src/theme/form_bloc_theme.dart'; @@ -191,78 +190,70 @@ class ChoiceChipFieldBlocBuilder extends StatelessWidget { Widget build(BuildContext context) { final fieldTheme = themeOf(context); - final current = SimpleFieldBlocBuilder( - singleFieldBloc: selectFieldBloc, + final current = SimpleFieldBlocBuilder>( + fieldBloc: selectFieldBloc, animateWhenCanShow: animateWhenCanShow, - builder: (context, canShow) { - return BlocBuilder, - SelectFieldBlocState>( - bloc: selectFieldBloc, - builder: (context, state) { - final isEnabled = fieldBlocIsEnabled( - isEnabled: this.isEnabled, - enableOnlyWhenFormBlocCanSubmit: enableOnlyWhenFormBlocCanSubmit, - fieldBlocState: state, - ); - - final value = state.value; - final items = state.items; - - return DefaultFieldBlocBuilderPadding( - padding: padding, - child: InputDecorator( - decoration: _buildDecoration(context, state, isEnabled), - isEmpty: false, - child: Wrap( - direction: direction, - alignment: alignment, - spacing: fieldTheme.wrapTheme.spacing!, - runAlignment: runAlignment, - runSpacing: fieldTheme.wrapTheme.runSpacing!, - crossAxisAlignment: crossAxisAlignment, - textDirection: textDirection, - verticalDirection: verticalDirection, - children: items.map((item) { - final fieldItem = itemBuilder(context, item); - - return ChoiceChip( - focusNode: focusNode, - autofocus: autofocus, - selected: value == item, - onSelected: fieldBlocBuilderOnChange( - isEnabled: isEnabled && fieldItem.isEnabled, - readOnly: readOnly, - nextFocusNode: nextFocusNode, - onChanged: (isSelected) { - selectFieldBloc.changeValue(isSelected ? item : null); - fieldItem.onTap?.call(); - }, - ), - labelStyle: labelStyle, - labelPadding: labelPadding, - pressElevation: pressElevation, - disabledColor: disabledColor, - selectedColor: selectedColor, - side: side, - shape: shape, - clipBehavior: clipBehavior, - backgroundColor: backgroundColor, - padding: chipPadding, - visualDensity: visualDensity, - materialTapTargetSize: materialTapTargetSize, - elevation: elevation, - shadowColor: shadowColor, - selectedShadowColor: selectedShadowColor, - avatarBorder: avatarBorder, - tooltip: fieldItem.tooltip, - avatar: fieldItem.avatar, - label: fieldItem.label, - ); - }).toList(), - ), - ), - ); - }, + enableOnlyWhenFormBlocCanSubmit: enableOnlyWhenFormBlocCanSubmit, + isEnabled: isEnabled, + readOnly: readOnly, + nextFocusNode: nextFocusNode, + builder: (context, state, data) { + final isEnabled = data.isEnabled; + + final value = state.value; + final items = state.items; + + return DefaultFieldBlocBuilderPadding( + padding: padding, + child: InputDecorator( + decoration: _buildDecoration(context, state, isEnabled), + isEmpty: false, + child: Wrap( + direction: direction, + alignment: alignment, + spacing: fieldTheme.wrapTheme.spacing!, + runAlignment: runAlignment, + runSpacing: fieldTheme.wrapTheme.runSpacing!, + crossAxisAlignment: crossAxisAlignment, + textDirection: textDirection, + verticalDirection: verticalDirection, + children: items.map((item) { + final fieldItem = itemBuilder(context, item); + + return ChoiceChip( + focusNode: focusNode, + autofocus: autofocus, + selected: value == item, + onSelected: data.buildOnChange( + isEnabled: fieldItem.isEnabled, + onChanged: (isSelected) { + selectFieldBloc.changeValue(isSelected ? item : null); + fieldItem.onTap?.call(); + }, + ), + labelStyle: labelStyle, + labelPadding: labelPadding, + pressElevation: pressElevation, + disabledColor: disabledColor, + selectedColor: selectedColor, + side: side, + shape: shape, + clipBehavior: clipBehavior, + backgroundColor: backgroundColor, + padding: chipPadding, + visualDensity: visualDensity, + materialTapTargetSize: materialTapTargetSize, + elevation: elevation, + shadowColor: shadowColor, + selectedShadowColor: selectedShadowColor, + avatarBorder: avatarBorder, + tooltip: fieldItem.tooltip, + avatar: fieldItem.avatar, + label: fieldItem.label, + ); + }).toList(), + ), + ), ); }, ); diff --git a/packages/flutter_form_bloc/lib/src/chip/filter_chip_field_bloc_builder.dart b/packages/flutter_form_bloc/lib/src/chip/filter_chip_field_bloc_builder.dart index 8fdd2d41..55bcaddd 100644 --- a/packages/flutter_form_bloc/lib/src/chip/filter_chip_field_bloc_builder.dart +++ b/packages/flutter_form_bloc/lib/src/chip/filter_chip_field_bloc_builder.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_bloc/src/chip/chip_field_item_builder.dart'; import 'package:flutter_form_bloc/src/fields/simple_field_bloc_builder.dart'; import 'package:flutter_form_bloc/src/theme/form_bloc_theme.dart'; @@ -186,84 +185,77 @@ class FilterChipFieldBlocBuilder extends StatelessWidget { Widget build(BuildContext context) { final fieldTheme = themeOf(context); - final current = SimpleFieldBlocBuilder( - singleFieldBloc: multiSelectFieldBloc, + final current = + SimpleFieldBlocBuilder>( + fieldBloc: multiSelectFieldBloc, animateWhenCanShow: animateWhenCanShow, - builder: (_, __) { - return BlocBuilder, - MultiSelectFieldBlocState>( - bloc: multiSelectFieldBloc, - builder: (context, state) { - final isEnabled = fieldBlocIsEnabled( - isEnabled: this.isEnabled, - enableOnlyWhenFormBlocCanSubmit: enableOnlyWhenFormBlocCanSubmit, - fieldBlocState: state, - ); - - final values = state.value; - final items = state.items; - - return DefaultFieldBlocBuilderPadding( - padding: padding, - child: InputDecorator( - decoration: _buildDecoration(context, state, isEnabled), - isEmpty: false, - child: Wrap( - direction: direction, - alignment: alignment, - spacing: fieldTheme.wrapTheme.spacing!, - runAlignment: runAlignment, - runSpacing: fieldTheme.wrapTheme.runSpacing!, - crossAxisAlignment: crossAxisAlignment, - textDirection: textDirection, - verticalDirection: verticalDirection, - children: items.map((item) { - final fieldItem = itemBuilder(context, item); - - return FilterChip( - focusNode: focusNode, - autofocus: autofocus, - selected: values.contains(item), - onSelected: fieldBlocBuilderOnChange( - isEnabled: isEnabled && fieldItem.isEnabled, - readOnly: readOnly, - nextFocusNode: nextFocusNode, - onChanged: (isSelected) { - if (isSelected) { - multiSelectFieldBloc.select(item); - } else { - multiSelectFieldBloc.deselect(item); - } - fieldItem.onTap?.call(); - }, - ), - labelStyle: labelStyle, - labelPadding: labelPadding, - pressElevation: pressElevation, - disabledColor: disabledColor, - selectedColor: selectedColor, - side: side, - shape: shape, - clipBehavior: clipBehavior, - backgroundColor: backgroundColor, - padding: chipPadding, - visualDensity: visualDensity, - materialTapTargetSize: materialTapTargetSize, - elevation: elevation, - shadowColor: shadowColor, - selectedShadowColor: selectedShadowColor, - showCheckmark: showCheckmark, - checkmarkColor: checkmarkColor, - avatarBorder: avatarBorder, - tooltip: fieldItem.tooltip, - avatar: fieldItem.avatar, - label: fieldItem.label, - ); - }).toList(), - ), - ), - ); - }, + enableOnlyWhenFormBlocCanSubmit: enableOnlyWhenFormBlocCanSubmit, + isEnabled: isEnabled, + readOnly: readOnly, + nextFocusNode: nextFocusNode, + builder: (context, state, data) { + final isEnabled = data.isEnabled; + + final values = state.value; + final items = state.items; + + return DefaultFieldBlocBuilderPadding( + padding: padding, + child: InputDecorator( + decoration: _buildDecoration(context, state, isEnabled), + isEmpty: false, + child: Wrap( + direction: direction, + alignment: alignment, + spacing: fieldTheme.wrapTheme.spacing!, + runAlignment: runAlignment, + runSpacing: fieldTheme.wrapTheme.runSpacing!, + crossAxisAlignment: crossAxisAlignment, + textDirection: textDirection, + verticalDirection: verticalDirection, + children: items.map((item) { + final fieldItem = itemBuilder(context, item); + + return FilterChip( + focusNode: focusNode, + autofocus: autofocus, + selected: values.contains(item), + onSelected: data.buildOnChange( + isEnabled: fieldItem.isEnabled, + onChanged: (isSelected) { + if (isSelected) { + multiSelectFieldBloc.select(item); + } else { + multiSelectFieldBloc.deselect(item); + } + fieldItem.onTap?.call(); + }, + ), + labelStyle: labelStyle, + labelPadding: labelPadding, + pressElevation: pressElevation, + disabledColor: disabledColor, + selectedColor: selectedColor, + side: side, + shape: shape, + clipBehavior: clipBehavior, + backgroundColor: backgroundColor, + padding: chipPadding, + visualDensity: visualDensity, + materialTapTargetSize: materialTapTargetSize, + elevation: elevation, + shadowColor: shadowColor, + selectedShadowColor: selectedShadowColor, + showCheckmark: showCheckmark, + checkmarkColor: checkmarkColor, + avatarBorder: avatarBorder, + tooltip: fieldItem.tooltip, + avatar: fieldItem.avatar, + label: fieldItem.label, + ); + }).toList(), + ), + ), ); }, ); diff --git a/packages/flutter_form_bloc/lib/src/cubit_consumer.dart b/packages/flutter_form_bloc/lib/src/cubit_consumer.dart new file mode 100644 index 00000000..a7e27472 --- /dev/null +++ b/packages/flutter_form_bloc/lib/src/cubit_consumer.dart @@ -0,0 +1,128 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:flutter/widgets.dart'; + +typedef CubitListener = void Function( + BuildContext context, TState state); + +typedef CubitBuilder = Widget Function( + BuildContext context, TState state, Widget child); + +typedef CubitCondition = bool Function(TState prev, TState curr); + +class SourceConsumer extends StatefulWidget { + final StateStreamable source; + final CubitCondition? listenWhen; + final CubitListener? listener; + final CubitCondition? buildWhen; + final Widget? child; + final CubitBuilder? builder; + + const SourceConsumer({ + Key? key, + required this.source, + this.listenWhen, + this.listener, + this.buildWhen, + this.child, + this.builder, + }) : super(key: key); + + bool _when(CubitCondition? condition, TState prev, TState curr) { + final source = this.source; + // Simulated the behavior of BlocBase + if (source is _StateStreamableSelector) { + if (prev == curr) return false; + } + return condition?.call(prev, curr) ?? true; + } + + @override + _SourceConsumerState createState() => _SourceConsumerState(); +} + +class _SourceConsumerState extends State> { + late StreamSubscription _sub; + late TState _listenState; + late TState _buildState; + + @override + void initState() { + super.initState(); + _initListener(); + } + + @override + void didUpdateWidget(covariant SourceConsumer oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.source != oldWidget.source) { + _sub.cancel(); + _initListener(); + } + } + + @override + void dispose() { + _sub.cancel(); + super.dispose(); + } + + void _initListener() { + _listenState = widget.source.state; + _buildState = widget.source.state; + + _sub = widget.source.stream.listen((state) { + if (widget._when(widget.listenWhen, _listenState, state)) { + _listenState = state; + widget.listener?.call(context, state); + } + + if (widget._when(widget.buildWhen, _buildState, state)) { + _buildState = state; + if (widget.builder != null) { + setState(() { + _buildState = state; + }); + } + } + }); + } + + @override + Widget build(BuildContext context) { + final child = widget.child ?? const SizedBox.shrink(); + + return widget.builder?.call(context, _listenState, child) ?? child; + } +} + +class _StateStreamableSelector + implements StateStreamable { + final StateStreamable source; + final TResult Function(TState state) selector; + + _StateStreamableSelector(this.source, this.selector); + + @override + TResult get state => selector(source.state); + + @override + Stream get stream => source.stream.map(selector); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is _StateStreamableSelector && + runtimeType == other.runtimeType && + source == other.source; + + @override + int get hashCode => source.hashCode; +} + +extension SelectStateStreamableExtension on StateStreamable { + StateStreamable select(R Function(TState state) selector) { + return _StateStreamableSelector(this, selector); + } +} diff --git a/packages/flutter_form_bloc/lib/src/date_time/date_time_field_bloc_builder_base.dart b/packages/flutter_form_bloc/lib/src/date_time/date_time_field_bloc_builder_base.dart index f3181d24..7727c5f2 100644 --- a/packages/flutter_form_bloc/lib/src/date_time/date_time_field_bloc_builder_base.dart +++ b/packages/flutter_form_bloc/lib/src/date_time/date_time_field_bloc_builder_base.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_bloc/src/fields/simple_field_bloc_builder.dart'; import 'package:flutter_form_bloc/src/suffix_buttons/clear_suffix_button.dart'; import 'package:flutter_form_bloc/src/theme/field_theme_resolver.dart'; @@ -162,6 +161,8 @@ class _DateTimeFieldBlocBuilderBaseState } } + late FieldBlocBuilderData _data; + void _showPicker(BuildContext context) async { FocusScope.of(context).requestFocus(FocusNode()); dynamic result; @@ -177,16 +178,10 @@ class _DateTimeFieldBlocBuilderBaseState } else if (widget.type == DateTimeFieldBlocBuilderBaseType.time) { result = await _showTimePicker(context); } - if (result != null) { - fieldBlocBuilderOnChange( - isEnabled: widget.isEnabled, - nextFocusNode: widget.nextFocusNode, - onChanged: (value) { - widget.dateTimeFieldBloc.changeValue(value); - // Used for hide keyboard - // FocusScope.of(context).requestFocus(FocusNode()); - }, - )!(result); + if (result != null && _data.canChange(isEnabled: widget.isEnabled)) { + widget.dateTimeFieldBloc.changeValue(result); + // Used for hide keyboard + // FocusScope.of(context).requestFocus(FocusNode()); } } @@ -196,66 +191,61 @@ class _DateTimeFieldBlocBuilderBaseState return Focus( focusNode: _effectiveFocusNode, - child: SimpleFieldBlocBuilder( - singleFieldBloc: widget.dateTimeFieldBloc, + child: SimpleFieldBlocBuilder>( + fieldBloc: widget.dateTimeFieldBloc, animateWhenCanShow: widget.animateWhenCanShow, - builder: (_, __) { - return BlocBuilder, - InputFieldBlocState>( - bloc: widget.dateTimeFieldBloc, - builder: (context, state) { - final isEnabled = fieldBlocIsEnabled( - isEnabled: widget.isEnabled, - enableOnlyWhenFormBlocCanSubmit: - widget.enableOnlyWhenFormBlocCanSubmit, - fieldBlocState: state, - ); - - Widget child; - - if (state.value == null && widget.decoration.hintText != null) { - child = Text( - widget.decoration.hintText!, - maxLines: widget.decoration.hintMaxLines, - overflow: TextOverflow.ellipsis, - textAlign: fieldTheme.textAlign, - style: Style.resolveTextStyle( - isEnabled: isEnabled, - style: widget.decoration.hintStyle ?? fieldTheme.textStyle!, - color: fieldTheme.textColor!, - ), - ); - } else { - child = Text( - state.value != null - ? _tryFormat(state.value, widget.format) - : '', - maxLines: 1, - overflow: TextOverflow.ellipsis, - softWrap: true, - textAlign: fieldTheme.textAlign, - style: Style.resolveTextStyle( - isEnabled: isEnabled, - style: fieldTheme.textStyle!, - color: fieldTheme.textColor!, - ), - ); - } - - return DefaultFieldBlocBuilderPadding( - padding: widget.padding, - child: GestureDetector( - onTap: !isEnabled ? null : () => _showPicker(context), - child: InputDecorator( - decoration: - _buildDecoration(context, fieldTheme, state, isEnabled), - isEmpty: state.value == null && - widget.decoration.hintText == null, - child: child, - ), - ), - ); - }, + enableOnlyWhenFormBlocCanSubmit: widget.enableOnlyWhenFormBlocCanSubmit, + isEnabled: widget.isEnabled, + // TODO: Implement readOnly + readOnly: false, + nextFocusNode: widget.nextFocusNode, + builder: (context, state, data) { + // TODO: Improve the handling of the data field + _data = data; + + final isEnabled = data.isEnabled; + + Widget child; + + if (state.value == null && widget.decoration.hintText != null) { + child = Text( + widget.decoration.hintText!, + maxLines: widget.decoration.hintMaxLines, + overflow: TextOverflow.ellipsis, + textAlign: fieldTheme.textAlign, + style: Style.resolveTextStyle( + isEnabled: isEnabled, + style: widget.decoration.hintStyle ?? fieldTheme.textStyle!, + color: fieldTheme.textColor!, + ), + ); + } else { + child = Text( + state.value != null ? _tryFormat(state.value, widget.format) : '', + maxLines: 1, + overflow: TextOverflow.ellipsis, + softWrap: true, + textAlign: fieldTheme.textAlign, + style: Style.resolveTextStyle( + isEnabled: isEnabled, + style: fieldTheme.textStyle!, + color: fieldTheme.textColor!, + ), + ); + } + + return DefaultFieldBlocBuilderPadding( + padding: widget.padding, + child: GestureDetector( + onTap: !isEnabled ? null : () => _showPicker(context), + child: InputDecorator( + decoration: + _buildDecoration(context, fieldTheme, state, isEnabled), + isEmpty: + state.value == null && widget.decoration.hintText == null, + child: child, + ), + ), ); }, ), diff --git a/packages/flutter_form_bloc/lib/src/dropdown_field_bloc_builder.dart b/packages/flutter_form_bloc/lib/src/dropdown_field_bloc_builder.dart index 5bf34df5..83988f80 100644 --- a/packages/flutter_form_bloc/lib/src/dropdown_field_bloc_builder.dart +++ b/packages/flutter_form_bloc/lib/src/dropdown_field_bloc_builder.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_bloc/src/fields/simple_field_bloc_builder.dart'; import 'package:flutter_form_bloc/src/flutter_typeahead.dart'; import 'package:flutter_form_bloc/src/theme/field_theme_resolver.dart'; @@ -155,79 +154,72 @@ class DropdownFieldBlocBuilder extends StatelessWidget { Widget build(BuildContext context) { final fieldTheme = themeStyleOf(context); - return SimpleFieldBlocBuilder( - singleFieldBloc: selectFieldBloc, + return SimpleFieldBlocBuilder>( + fieldBloc: selectFieldBloc, animateWhenCanShow: animateWhenCanShow, - builder: (context, canShow) { - return BlocBuilder, - SelectFieldBlocState>( - bloc: selectFieldBloc, - builder: (context, fieldState) { - final isEnabled = fieldBlocIsEnabled( - isEnabled: this.isEnabled, - enableOnlyWhenFormBlocCanSubmit: enableOnlyWhenFormBlocCanSubmit, - fieldBlocState: fieldState, - ); - - final decoration = - _buildDecoration(context, fieldTheme, fieldState, isEnabled); - - return DefaultFieldBlocBuilderPadding( - padding: padding, - child: InputDecorator( - decoration: decoration, - textAlign: textAlign, - isEmpty: - fieldState.value == null && decoration.hintText == null, - child: DropdownButtonHideUnderline( - child: DropdownButton( - value: fieldState.value, - focusNode: focusNode, - hint: hint, - isExpanded: isExpanded, - isDense: true, - disabledHint: disabledHint ?? - (decoration.hintText != null - ? DefaultTextStyle( - style: Style.resolveTextStyle( - isEnabled: isEnabled, - style: decoration.hintStyle ?? - fieldTheme.textStyle!, - color: fieldTheme.textColor!, - ), - child: Text(decoration.hintText!), - ) - : null), - onChanged: fieldBlocBuilderOnChange( - isEnabled: isEnabled, - nextFocusNode: nextFocusNode, - onChanged: (value) { - selectFieldBloc.changeValue(value); - onChanged?.call(value); - }, - ), - items: _buildItems( - context: context, - fieldTheme: fieldTheme, - items: fieldState.items, - isEnabled: isEnabled, - isSelected: false, - ), - selectedItemBuilder: (context) => _buildItems( - context: context, - fieldTheme: fieldTheme, - items: fieldState.items, - isEnabled: isEnabled, - isSelected: true, - ), - icon: this.decoration.suffixIcon ?? - fieldTheme.moreIcon ?? - const Icon(Icons.arrow_drop_down), - ), + enableOnlyWhenFormBlocCanSubmit: enableOnlyWhenFormBlocCanSubmit, + isEnabled: isEnabled, + // TODO: implement readOnly + readOnly: false, + nextFocusNode: nextFocusNode, + builder: (context, fieldState, data) { + final isEnabled = data.isEnabled; + + final decoration = + _buildDecoration(context, fieldTheme, fieldState, isEnabled); + + return DefaultFieldBlocBuilderPadding( + padding: padding, + child: InputDecorator( + decoration: decoration, + textAlign: textAlign, + isEmpty: fieldState.value == null && decoration.hintText == null, + child: DropdownButtonHideUnderline( + child: DropdownButton( + value: fieldState.value, + focusNode: focusNode, + hint: hint, + isExpanded: isExpanded, + isDense: true, + disabledHint: disabledHint ?? + (decoration.hintText != null + ? DefaultTextStyle( + style: Style.resolveTextStyle( + isEnabled: isEnabled, + style: + decoration.hintStyle ?? fieldTheme.textStyle!, + color: fieldTheme.textColor!, + ), + child: Text(decoration.hintText!), + ) + : null), + onChanged: data.buildOnChange( + isEnabled: isEnabled, + onChanged: (value) { + selectFieldBloc.changeValue(value); + onChanged?.call(value); + }, + ), + items: _buildItems( + context: context, + fieldTheme: fieldTheme, + items: fieldState.items, + isEnabled: isEnabled, + isSelected: false, + ), + selectedItemBuilder: (context) => _buildItems( + context: context, + fieldTheme: fieldTheme, + items: fieldState.items, + isEnabled: isEnabled, + isSelected: true, ), + icon: this.decoration.suffixIcon ?? + fieldTheme.moreIcon ?? + const Icon(Icons.arrow_drop_down), ), - ); - }, + ), + ), ); }, ); diff --git a/packages/flutter_form_bloc/lib/src/features/appear/can_show_field_bloc_builder.dart b/packages/flutter_form_bloc/lib/src/features/appear/can_show_field_bloc_builder.dart index ab580d0e..b9f6c8f3 100644 --- a/packages/flutter_form_bloc/lib/src/features/appear/can_show_field_bloc_builder.dart +++ b/packages/flutter_form_bloc/lib/src/features/appear/can_show_field_bloc_builder.dart @@ -1,16 +1,18 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_form_bloc/src/cubit_consumer.dart'; import 'package:form_bloc/form_bloc.dart'; class CanShowFieldBlocBuilder extends StatefulWidget { const CanShowFieldBlocBuilder({ Key? key, + required this.formBloc, required this.fieldBloc, required this.builder, this.animate = true, }) : super(key: key); - final FieldBloc fieldBloc; + final FieldBloc formBloc; + final FieldBloc fieldBloc; final bool animate; final Widget Function(BuildContext context, bool canShow) builder; @@ -36,7 +38,7 @@ class _CanShowFieldBlocBuilderState extends State duration: const Duration(milliseconds: 300), ); - if (widget.fieldBloc.state.hasFormBloc) { + if (widget.formBloc.state.contains(widget.fieldBloc)) { _showOnFirstFrame = true; _initVisibility(); } else { @@ -59,8 +61,8 @@ class _CanShowFieldBlocBuilderState extends State if (widget.animate != oldWidget.animate) { _initVisibility(); } - if (widget.fieldBloc.state.hasFormBloc != - oldWidget.fieldBloc.state.hasFormBloc) { + if (widget.formBloc.state.contains(widget.fieldBloc) != + widget.formBloc.state.contains(oldWidget.fieldBloc)) { _initVisibility(); } } @@ -72,7 +74,7 @@ class _CanShowFieldBlocBuilderState extends State } void _initVisibility() { - final canShow = widget.fieldBloc.state.hasFormBloc; + final canShow = widget.formBloc.state.contains(widget.fieldBloc); _canShow = canShow; @@ -126,10 +128,12 @@ class _CanShowFieldBlocBuilderState extends State ); } - return BlocListener( - bloc: widget.fieldBloc, - listenWhen: (prev, curr) => prev.hasFormBloc != curr.hasFormBloc, - listener: (context, state) => _changeVisibility(state.hasFormBloc), + return SourceConsumer( + source: widget.formBloc, + listenWhen: (prev, curr) => + prev.contains(widget.fieldBloc) != curr.contains(widget.fieldBloc), + listener: (context, state) => + _changeVisibility(state.contains(widget.fieldBloc)), child: child, ); } diff --git a/packages/flutter_form_bloc/lib/src/features/appear/form_bloc_provider.dart b/packages/flutter_form_bloc/lib/src/features/appear/form_bloc_provider.dart new file mode 100644 index 00000000..17eaadec --- /dev/null +++ b/packages/flutter_form_bloc/lib/src/features/appear/form_bloc_provider.dart @@ -0,0 +1,22 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_form_bloc/flutter_form_bloc.dart'; + +class FormBlocProvider extends InheritedWidget { + final FieldBloc formBloc; + + const FormBlocProvider({ + Key? key, + required this.formBloc, + required Widget child, + }) : super(key: key, child: child); + + static FieldBloc? maybeOf(BuildContext context) { + return context + .dependOnInheritedWidgetOfExactType() + ?.formBloc; + } + + @override + bool updateShouldNotify(FormBlocProvider oldWidget) => + formBloc != oldWidget.formBloc; +} diff --git a/packages/flutter_form_bloc/lib/src/features/scroll/scrollable_field_bloc_target.dart b/packages/flutter_form_bloc/lib/src/features/scroll/scrollable_field_bloc_target.dart index 8ae6c6e7..7785f1bf 100644 --- a/packages/flutter_form_bloc/lib/src/features/scroll/scrollable_field_bloc_target.dart +++ b/packages/flutter_form_bloc/lib/src/features/scroll/scrollable_field_bloc_target.dart @@ -1,10 +1,10 @@ import 'package:flutter/widgets.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_form_bloc/src/cubit_consumer.dart'; import 'package:form_bloc/form_bloc.dart'; /// Mark the widget as a possible scroll target class ScrollableFieldBlocTarget extends StatefulWidget { - final SingleFieldBloc singleFieldBloc; + final FieldBloc singleFieldBloc; /// Enable auto scroll when the field has an error final bool canScroll; @@ -43,8 +43,7 @@ class ScrollableFieldBlocTarget extends StatefulWidget { } @override - State createState() => - ScrollableFieldBlocTargetState(); + State createState() => ScrollableFieldBlocTargetState(); } class ScrollableFieldBlocTargetState extends State { @@ -56,8 +55,8 @@ class ScrollableFieldBlocTargetState extends State { @override Widget build(BuildContext context) { - return BlocListener( - bloc: widget.singleFieldBloc, + return SourceConsumer( + source: widget.singleFieldBloc, listenWhen: (prev, curr) => prev.hasError != curr.hasError, listener: (context, state) { _hasError = state.hasError; diff --git a/packages/flutter_form_bloc/lib/src/features/scroll/scrollable_form_bloc_manager.dart b/packages/flutter_form_bloc/lib/src/features/scroll/scrollable_form_bloc_manager.dart index a42897e9..3bb923b9 100644 --- a/packages/flutter_form_bloc/lib/src/features/scroll/scrollable_form_bloc_manager.dart +++ b/packages/flutter_form_bloc/lib/src/features/scroll/scrollable_form_bloc_manager.dart @@ -1,5 +1,5 @@ import 'package:flutter/widgets.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_form_bloc/src/cubit_consumer.dart'; import 'package:flutter_form_bloc/src/features/scroll/scrollable_field_bloc_target.dart'; import 'package:flutter_form_bloc/src/theme/form_bloc_theme.dart'; import 'package:form_bloc/form_bloc.dart'; @@ -62,8 +62,8 @@ class ScrollableFormBlocManager extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocListener( - bloc: formBloc, + return SourceConsumer( + source: formBloc, listenWhen: (prev, curr) => prev.runtimeType != curr.runtimeType, listener: _onFormBlocState, child: child, diff --git a/packages/flutter_form_bloc/lib/src/fields/list_field_bloc_consumer.dart b/packages/flutter_form_bloc/lib/src/fields/list_field_bloc_consumer.dart new file mode 100644 index 00000000..788e08a4 --- /dev/null +++ b/packages/flutter_form_bloc/lib/src/fields/list_field_bloc_consumer.dart @@ -0,0 +1,62 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_form_bloc/src/cubit_consumer.dart'; +import 'package:form_bloc/form_bloc.dart'; + +class MultiFieldBlocConsumer extends StatelessWidget { + final MultiFieldBloc multiFieldBloc; + final CubitCondition? listenWhen; + final CubitListener? listener; + final CubitCondition? buildWhen; + final Widget? child; + final CubitBuilder? builder; + + const MultiFieldBlocConsumer({ + Key? key, + required this.multiFieldBloc, + this.listenWhen, + this.listener, + this.buildWhen, + this.child, + this.builder, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return SourceConsumer( + source: multiFieldBloc, + listenWhen: listenWhen, + listener: listener, + buildWhen: buildWhen, + child: child, + builder: builder, + ); + } +} + +class ListFieldBlocConsumer + extends MultiFieldBlocConsumer> { + const ListFieldBlocConsumer({ + Key? key, + required ListFieldBloc listFieldBloc, + CubitCondition>? listenWhen, + CubitListener>? listener, + CubitCondition>? buildWhen = + fieldBlocsChanges, + Widget? child, + CubitBuilder>? builder, + }) : super( + key: key, + multiFieldBloc: listFieldBloc, + listener: listener, + builder: builder, + child: child, + ); + + static bool fieldBlocsChanges( + ListFieldBlocState prev, ListFieldBlocState curr) { + return prev.fieldBlocs.equals(curr.fieldBlocs); + } +} diff --git a/packages/flutter_form_bloc/lib/src/fields/simple_field_bloc_builder.dart b/packages/flutter_form_bloc/lib/src/fields/simple_field_bloc_builder.dart index 2b044004..b1e25f7e 100644 --- a/packages/flutter_form_bloc/lib/src/fields/simple_field_bloc_builder.dart +++ b/packages/flutter_form_bloc/lib/src/fields/simple_field_bloc_builder.dart @@ -1,41 +1,159 @@ +import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_form_bloc/src/cubit_consumer.dart'; import 'package:flutter_form_bloc/src/features/appear/can_show_field_bloc_builder.dart'; +import 'package:flutter_form_bloc/src/features/appear/form_bloc_provider.dart'; import 'package:flutter_form_bloc/src/features/scroll/scrollable_field_bloc_target.dart'; import 'package:form_bloc/form_bloc.dart'; +class FieldBlocBuilderData extends Equatable { + final bool canShow; + final bool isReadonly; + final bool isEnabled; + final FocusNode? nextFocusNode; + + const FieldBlocBuilderData({ + required this.canShow, + required this.isReadonly, + required this.isEnabled, + required this.nextFocusNode, + }); + + bool canChange({bool isEnabled = true}) => + !isReadonly && this.isEnabled && isEnabled; + + ValueChanged? buildOnChange({ + bool isEnabled = true, + required void Function(T value) onChanged, + }) { + if (isReadonly) return null; + if (!this.isEnabled || !isEnabled) return null; + final nextFocusNode = this.nextFocusNode; + if (nextFocusNode != null) { + return (value) { + onChanged(value); + nextFocusNode.nextFocus(); + }; + } + return onChanged; + } + + @override + List get props => [canShow, isReadonly, isEnabled]; +} + /// Use these widgets: /// - [CanShowFieldBlocBuilder] /// - [ScrollableFieldBlocTarget] -class SimpleFieldBlocBuilder extends StatelessWidget { - final SingleFieldBloc singleFieldBloc; +class SimpleFieldBlocBuilder + extends StatelessWidget { + final FieldBloc fieldBloc; final bool animateWhenCanShow; final bool focusOnValidationFailed; - final Widget Function(BuildContext context, bool canShow) builder; + final bool enableOnlyWhenFormBlocCanSubmit; + final bool isEnabled; + final bool readOnly; + final FocusNode? nextFocusNode; + final Widget Function( + BuildContext context, + TState state, + FieldBlocBuilderData data, + ) builder; const SimpleFieldBlocBuilder({ Key? key, - required this.singleFieldBloc, + required this.fieldBloc, this.animateWhenCanShow = true, this.focusOnValidationFailed = true, + this.enableOnlyWhenFormBlocCanSubmit = true, + this.isEnabled = true, + this.readOnly = false, + this.nextFocusNode, required this.builder, }) : super(key: key); @override Widget build(BuildContext context) { - return CanShowFieldBlocBuilder( - fieldBloc: singleFieldBloc, - animate: animateWhenCanShow, - builder: (context, canShow) { - final field = builder(context, canShow); - - if (!canShow) { - return field; + Widget _buildField({ + bool canShow = true, + bool isSubmitting = false, + required TState state, + }) { + final data = FieldBlocBuilderData( + canShow: canShow, + isReadonly: readOnly || isSubmitting, + isEnabled: isEnabled, + nextFocusNode: nextFocusNode, + ); + final child = builder(context, state, data); + + if (!canShow) { + return child; + } + + return ScrollableFieldBlocTarget( + singleFieldBloc: fieldBloc, + canScroll: focusOnValidationFailed, + child: child, + ); + } + + Widget _buildAnimatedField({ + required FieldBloc formBloc, + bool isSubmitting = false, + required TState state, + }) { + return CanShowFieldBlocBuilder( + formBloc: formBloc, + fieldBloc: fieldBloc, + animate: animateWhenCanShow, + builder: (context, canShow) { + return _buildField( + canShow: canShow, + isSubmitting: isSubmitting, + state: state, + ); + }, + ); + } + + Widget _buildLockableField({ + required FormBloc formBloc, + required TState state, + }) { + return SourceConsumer( + source: formBloc.select((state) => state is FormBlocSubmitting), + builder: (context, isSubmitting, _) { + return _buildAnimatedField( + formBloc: formBloc, + isSubmitting: isSubmitting, + state: state, + ); + }, + ); + } + + return SourceConsumer( + source: fieldBloc, + builder: (context, state, _) { + final formBloc = FormBlocProvider.maybeOf(context); + + if (formBloc == null) { + return _buildField( + state: state, + ); + } + + if (!enableOnlyWhenFormBlocCanSubmit || formBloc is! FormBloc) { + return _buildAnimatedField( + formBloc: formBloc, + state: state, + ); } - return ScrollableFieldBlocTarget( - singleFieldBloc: singleFieldBloc, - canScroll: focusOnValidationFailed, - child: field, + return _buildLockableField( + formBloc: formBloc, + state: state, ); }, ); diff --git a/packages/flutter_form_bloc/lib/src/form_bloc_listener.dart b/packages/flutter_form_bloc/lib/src/form_bloc_listener.dart index c159292e..1b34b16b 100644 --- a/packages/flutter_form_bloc/lib/src/form_bloc_listener.dart +++ b/packages/flutter_form_bloc/lib/src/form_bloc_listener.dart @@ -1,6 +1,7 @@ import 'package:flutter/widgets.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_form_bloc/src/cubit_consumer.dart'; import 'package:form_bloc/form_bloc.dart' as form_bloc; +import 'package:form_bloc/form_bloc.dart'; typedef FormBlocListenerCallback< FormBlocState extends form_bloc @@ -10,18 +11,13 @@ typedef FormBlocListenerCallback< = void Function(BuildContext context, FormBlocState state); /// [BlocListener] that reacts to the state changes of the FormBloc. -class FormBlocListener< - FormBloc extends form_bloc.FormBloc, - SuccessResponse, - ErrorResponse> - extends BlocListener> { +class FormBlocListener extends SourceConsumer< + form_bloc.FormBlocState> { /// [BlocListener] that reacts to the state changes of the FormBloc. /// {@macro bloclistener} FormBlocListener({ Key? key, - this.formBloc, - Widget? child, + required this.formBloc, this.onLoading, this.onLoaded, this.onLoadFailed, @@ -33,59 +29,49 @@ class FormBlocListener< this.onDeleting, this.onDeleteFailed, this.onDeleteSuccessful, + required Widget child, }) : super( key: key, - child: child, - bloc: formBloc, + source: formBloc, listenWhen: (previousState, state) => previousState.runtimeType != state.runtimeType, listener: (context, state) { - if (state is form_bloc - .FormBlocLoading && - onLoading != null) { - onLoading(context, state); - } else if (state is form_bloc - .FormBlocLoaded && - onLoaded != null) { - onLoaded(context, state); + if (state + is form_bloc.FormBlocLoading) { + onLoading?.call(context, state); + } else if (state + is form_bloc.FormBlocLoaded) { + onLoaded?.call(context, state); } else if (state is form_bloc - .FormBlocLoadFailed && - onLoadFailed != null) { - onLoadFailed(context, state); + .FormBlocLoadFailed) { + onLoadFailed?.call(context, state); } else if (state is form_bloc - .FormBlocSubmitting && - onSubmitting != null) { - onSubmitting(context, state); + .FormBlocSubmitting) { + onSubmitting?.call(context, state); + } else if (state + is form_bloc.FormBlocSuccess) { + onSuccess?.call(context, state); + } else if (state + is form_bloc.FormBlocFailure) { + onFailure?.call(context, state); } else if (state is form_bloc - .FormBlocSuccess && - onSuccess != null) { - onSuccess(context, state); + .FormBlocSubmissionCancelled) { + onSubmissionCancelled?.call(context, state); } else if (state is form_bloc - .FormBlocFailure && - onFailure != null) { - onFailure(context, state); - } else if (state is form_bloc.FormBlocSubmissionCancelled< - SuccessResponse, ErrorResponse> && - onSubmissionCancelled != null) { - onSubmissionCancelled(context, state); + .FormBlocSubmissionFailed) { + onSubmissionFailed?.call(context, state); + } else if (state + is form_bloc.FormBlocDeleting) { + onDeleting?.call(context, state); } else if (state is form_bloc - .FormBlocSubmissionFailed && - onSubmissionFailed != null) { - onSubmissionFailed(context, state); + .FormBlocDeleteFailed) { + onDeleteFailed?.call(context, state); } else if (state is form_bloc - .FormBlocDeleting && - onDeleting != null) { - onDeleting(context, state); - } else if (state is form_bloc - .FormBlocDeleteFailed && - onDeleteFailed != null) { - onDeleteFailed(context, state); - } else if (state is form_bloc - .FormBlocDeleteSuccessful && - onDeleteSuccessful != null) { - onDeleteSuccessful(context, state); + .FormBlocDeleteSuccessful) { + onDeleteSuccessful?.call(context, state); } }, + child: child, ); /// {@macro form_bloc.form_state.FormBlocLoading} @@ -157,7 +143,7 @@ class FormBlocListener< /// If the [formBloc] parameter is omitted, [FormBlocListener] /// will automatically perform a lookup using /// [BlocProvider].of<[FormBloc]> and the current [BuildContext]. - final FormBloc? formBloc; + final FormBloc formBloc; /// The [Widget] which will be rendered as a descendant of the [BlocListener]. @override diff --git a/packages/flutter_form_bloc/lib/src/groups/fields/checkbox_group_field_bloc_builder.dart b/packages/flutter_form_bloc/lib/src/groups/fields/checkbox_group_field_bloc_builder.dart index 823d47f2..9003b188 100644 --- a/packages/flutter_form_bloc/lib/src/groups/fields/checkbox_group_field_bloc_builder.dart +++ b/packages/flutter_form_bloc/lib/src/groups/fields/checkbox_group_field_bloc_builder.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_bloc/src/fields/simple_field_bloc_builder.dart'; import 'package:flutter_form_bloc/src/groups/widgets/group_view.dart'; import 'package:flutter_form_bloc/src/groups/widgets/item_group_tile.dart'; @@ -130,31 +129,21 @@ class CheckboxGroupFieldBlocBuilder extends StatelessWidget { data: Theme.of(context).copyWith( checkboxTheme: fieldTheme.checkboxTheme, ), - child: SimpleFieldBlocBuilder( - singleFieldBloc: multiSelectFieldBloc, + child: SimpleFieldBlocBuilder>( + fieldBloc: multiSelectFieldBloc, animateWhenCanShow: animateWhenCanShow, - builder: (_, __) { - return BlocBuilder, - MultiSelectFieldBlocState>( - bloc: multiSelectFieldBloc, - builder: (context, state) { - final isEnabled = fieldBlocIsEnabled( - isEnabled: this.isEnabled, - enableOnlyWhenFormBlocCanSubmit: - enableOnlyWhenFormBlocCanSubmit, - fieldBlocState: state, - ); - - return DefaultFieldBlocBuilderPadding( - padding: padding, - child: GroupInputDecorator( - decoration: - _buildDecoration(context, state, isEnabled, fieldTheme), - child: - _buildCheckboxes(context, state, isEnabled, fieldTheme), - ), - ); - }, + enableOnlyWhenFormBlocCanSubmit: enableOnlyWhenFormBlocCanSubmit, + isEnabled: isEnabled, + // TODO: Implement readOnly + readOnly: false, + nextFocusNode: nextFocusNode, + builder: (context, state, data) { + return DefaultFieldBlocBuilderPadding( + padding: padding, + child: GroupInputDecorator( + decoration: _buildDecoration(context, state, data, fieldTheme), + child: _buildCheckboxes(context, state, data, fieldTheme), + ), ); }, ), @@ -164,7 +153,7 @@ class CheckboxGroupFieldBlocBuilder extends StatelessWidget { Widget _buildCheckboxes( BuildContext context, MultiSelectFieldBlocState state, - bool isFieldEnabled, + FieldBlocBuilderData data, CheckboxFieldTheme fieldTheme, ) { return DefaultTextStyle( @@ -183,11 +172,9 @@ class CheckboxGroupFieldBlocBuilder extends StatelessWidget { builder: (context, index) { final item = state.items[index]; final fieldItem = itemBuilder(context, item); - final isEnabled = isFieldEnabled && fieldItem.isEnabled; - final onChanged = fieldBlocBuilderOnChange( + final onChanged = data.buildOnChange( isEnabled: isEnabled, - nextFocusNode: nextFocusNode, onChanged: (isChecked) { if (!isChecked!) { multiSelectFieldBloc.deselect(item); @@ -220,13 +207,13 @@ class CheckboxGroupFieldBlocBuilder extends StatelessWidget { InputDecoration _buildDecoration( BuildContext context, MultiSelectFieldBlocState state, - bool isEnabled, + FieldBlocBuilderData data, CheckboxFieldTheme fieldTheme, ) { var decoration = this.decoration; decoration = decoration.copyWith( - enabled: isEnabled, + enabled: data.isEnabled, errorText: Style.getErrorText( context: context, errorBuilder: errorBuilder, diff --git a/packages/flutter_form_bloc/lib/src/groups/fields/radio_button_group_field_bloc.dart b/packages/flutter_form_bloc/lib/src/groups/fields/radio_button_group_field_bloc.dart index a84c6546..38f07497 100644 --- a/packages/flutter_form_bloc/lib/src/groups/fields/radio_button_group_field_bloc.dart +++ b/packages/flutter_form_bloc/lib/src/groups/fields/radio_button_group_field_bloc.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_form_bloc/src/features/appear/can_show_field_bloc_builder.dart'; +import 'package:flutter_form_bloc/src/fields/simple_field_bloc_builder.dart'; import 'package:flutter_form_bloc/src/groups/widgets/group_view.dart'; import 'package:flutter_form_bloc/src/groups/widgets/item_group_tile.dart'; import 'package:flutter_form_bloc/src/theme/field_theme_resolver.dart'; @@ -122,31 +121,21 @@ class RadioButtonGroupFieldBlocBuilder extends StatelessWidget { data: Theme.of(context).copyWith( radioTheme: fieldTheme.radioTheme, ), - child: CanShowFieldBlocBuilder( + child: SimpleFieldBlocBuilder>( fieldBloc: selectFieldBloc, - animate: animateWhenCanShow, - builder: (_, __) { - return BlocBuilder, - SelectFieldBlocState>( - bloc: selectFieldBloc, - builder: (context, state) { - final isEnabled = fieldBlocIsEnabled( - isEnabled: this.isEnabled, - enableOnlyWhenFormBlocCanSubmit: - enableOnlyWhenFormBlocCanSubmit, - fieldBlocState: state, - ); - - return DefaultFieldBlocBuilderPadding( - padding: padding, - child: GroupInputDecorator( - decoration: - _buildDecoration(context, fieldTheme, state, isEnabled), - child: - _buildRadioButtons(context, state, fieldTheme, isEnabled), - ), - ); - }, + animateWhenCanShow: animateWhenCanShow, + enableOnlyWhenFormBlocCanSubmit: enableOnlyWhenFormBlocCanSubmit, + isEnabled: isEnabled, + // TODO: Implement readOnly + readOnly: false, + nextFocusNode: nextFocusNode, + builder: (context, state, data) { + return DefaultFieldBlocBuilderPadding( + padding: padding, + child: GroupInputDecorator( + decoration: _buildDecoration(context, state, data, fieldTheme), + child: _buildRadioButtons(context, state, data, fieldTheme), + ), ); }, ), @@ -156,8 +145,8 @@ class RadioButtonGroupFieldBlocBuilder extends StatelessWidget { Widget _buildRadioButtons( BuildContext context, SelectFieldBlocState state, + FieldBlocBuilderData data, RadioFieldTheme fieldTheme, - bool isFieldEnabled, ) { return DefaultTextStyle( style: Style.resolveTextStyle( @@ -175,11 +164,8 @@ class RadioButtonGroupFieldBlocBuilder extends StatelessWidget { builder: (context, index) { final item = state.items[index]; final fieldItem = itemBuilder(context, item); - final isEnabled = isFieldEnabled && fieldItem.isEnabled; - final onChanged = fieldBlocBuilderOnChange( - isEnabled: isEnabled, - nextFocusNode: nextFocusNode, + final onChanged = data.buildOnChange( onChanged: (value) { selectFieldBloc.changeValue(value); fieldItem.onTap?.call(); @@ -213,14 +199,14 @@ class RadioButtonGroupFieldBlocBuilder extends StatelessWidget { InputDecoration _buildDecoration( BuildContext context, - RadioFieldTheme fieldTheme, SelectFieldBlocState state, - bool isEnable, + FieldBlocBuilderData data, + RadioFieldTheme fieldTheme, ) { var decoration = this.decoration; decoration = decoration.copyWith( - enabled: isEnable, + enabled: data.isEnabled, errorText: Style.getErrorText( context: context, errorBuilder: errorBuilder, diff --git a/packages/flutter_form_bloc/lib/src/slider/slider_field_bloc_builder.dart b/packages/flutter_form_bloc/lib/src/slider/slider_field_bloc_builder.dart index 1bb2e6ae..fddd5ddb 100644 --- a/packages/flutter_form_bloc/lib/src/slider/slider_field_bloc_builder.dart +++ b/packages/flutter_form_bloc/lib/src/slider/slider_field_bloc_builder.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_bloc/src/fields/simple_field_bloc_builder.dart'; import 'package:flutter_form_bloc/src/theme/form_bloc_theme.dart'; import 'package:flutter_form_bloc/src/utils/utils.dart'; @@ -96,47 +95,37 @@ class SliderFieldBlocBuilder extends StatelessWidget { data: Theme.of(context).copyWith( sliderTheme: fieldTheme.sliderTheme, ), - child: SimpleFieldBlocBuilder( - singleFieldBloc: inputFieldBloc, + child: SimpleFieldBlocBuilder>( + fieldBloc: inputFieldBloc, animateWhenCanShow: animateWhenCanShow, - builder: (context, _) { - return BlocBuilder, - InputFieldBlocState>( - bloc: inputFieldBloc, - builder: (context, state) { - final isEnabled = fieldBlocIsEnabled( - isEnabled: this.isEnabled, - enableOnlyWhenFormBlocCanSubmit: - enableOnlyWhenFormBlocCanSubmit, - fieldBlocState: state, - ); - final value = state.value; - - return DefaultFieldBlocBuilderPadding( - padding: padding, - child: InputDecorator( - decoration: _buildDecoration(context, state, isEnabled), - isEmpty: false, - child: Slider( - value: value, - min: min, - max: max, - focusNode: focusNode, - divisions: divisions, - onChanged: fieldBlocBuilderOnChange( - isEnabled: isEnabled, - readOnly: readOnly, - nextFocusNode: nextFocusNode, - onChanged: inputFieldBloc.changeValue, - ), - label: labelBuilder?.call(context, value), - activeColor: activeColor, - inactiveColor: inactiveColor, - mouseCursor: mouseCursor, - ), + enableOnlyWhenFormBlocCanSubmit: enableOnlyWhenFormBlocCanSubmit, + isEnabled: isEnabled, + readOnly: readOnly, + nextFocusNode: nextFocusNode, + builder: (context, state, data) { + final value = state.value; + + return DefaultFieldBlocBuilderPadding( + padding: padding, + child: InputDecorator( + decoration: _buildDecoration(context, state, data), + isEmpty: false, + child: Slider( + value: value, + min: min, + max: max, + focusNode: focusNode, + divisions: divisions, + onChanged: data.buildOnChange( + isEnabled: isEnabled, + onChanged: inputFieldBloc.changeValue, ), - ); - }, + label: labelBuilder?.call(context, value), + activeColor: activeColor, + inactiveColor: inactiveColor, + mouseCursor: mouseCursor, + ), + ), ); }, ), @@ -146,7 +135,7 @@ class SliderFieldBlocBuilder extends StatelessWidget { InputDecoration _buildDecoration( BuildContext context, FieldBlocState state, - bool isEnabled, + FieldBlocBuilderData data, ) { return decoration.copyWith( enabled: isEnabled, diff --git a/packages/flutter_form_bloc/lib/src/stepper/stepper_form_bloc_builder.dart b/packages/flutter_form_bloc/lib/src/stepper/stepper_form_bloc_builder.dart index ce78d7c8..fba225db 100644 --- a/packages/flutter_form_bloc/lib/src/stepper/stepper_form_bloc_builder.dart +++ b/packages/flutter_form_bloc/lib/src/stepper/stepper_form_bloc_builder.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart' hide Stepper, Step; -import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_form_bloc/src/cubit_consumer.dart'; import 'package:flutter_form_bloc/src/stepper/stepper.dart'; import 'package:form_bloc/form_bloc.dart'; @@ -51,10 +51,11 @@ class FormBlocStep { final bool? isActive; } -class StepperFormBlocBuilder extends StatelessWidget { +class StepperFormBlocBuilder + extends StatelessWidget { const StepperFormBlocBuilder({ Key? key, - this.formBloc, + required this.formBloc, required this.stepsBuilder, this.physics, this.type = StepperType.vertical, @@ -64,12 +65,12 @@ class StepperFormBlocBuilder extends StatelessWidget { this.controlsBuilder, }) : super(key: key); - final T? formBloc; + final TFormBloc formBloc; /// The steps of the stepper whose titles, subtitles, icons always get shown. /// /// The length of [stepsBuilder] must not change. - final List Function(T? formBloc) stepsBuilder; + final List Function(TFormBloc? formBloc) stepsBuilder; /// How the stepper's scroll view should respond to user input. /// @@ -158,12 +159,12 @@ class StepperFormBlocBuilder extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocBuilder( - bloc: formBloc, + return SourceConsumer( + source: formBloc, buildWhen: (p, c) => p.numberOfSteps != c.numberOfSteps || p.currentStep != c.currentStep, - builder: (context, state) { - final formBloc = this.formBloc ?? context.read(); + builder: (context, state, _) { + final formBloc = this.formBloc; final formBlocSteps = stepsBuilder(formBloc); return Stepper( diff --git a/packages/flutter_form_bloc/lib/src/suffix_buttons/suffix_button_bloc_builder.dart b/packages/flutter_form_bloc/lib/src/suffix_buttons/suffix_button_bloc_builder.dart index 597c0747..6865d5c7 100644 --- a/packages/flutter_form_bloc/lib/src/suffix_buttons/suffix_button_bloc_builder.dart +++ b/packages/flutter_form_bloc/lib/src/suffix_buttons/suffix_button_bloc_builder.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_form_bloc/src/utils/functions.dart'; +import 'package:flutter_form_bloc/src/cubit_consumer.dart'; +import 'package:flutter_form_bloc/src/features/appear/form_bloc_provider.dart'; import 'package:form_bloc/form_bloc.dart'; typedef BlocChildBuilder = Widget Function( @@ -33,10 +33,10 @@ class SuffixButtonBuilderBase extends StatelessWidget { } Widget _buildButton(BuildContext context, FieldBlocState state) { - final isEnabled = fieldBlocIsEnabled( - isEnabled: this.isEnabled, - fieldBlocState: state, - ); + final formBloc = FormBlocProvider.maybeOf(context); + + final isEnabled = + this.isEnabled && !(formBloc?.state.isValidating ?? false); return InkWell( borderRadius: const BorderRadius.all(Radius.circular(25.0)), @@ -48,9 +48,9 @@ class SuffixButtonBuilderBase extends StatelessWidget { @override Widget build(BuildContext context) { return ExcludeFocus( - child: BlocBuilder( - bloc: singleFieldBloc, - builder: (context, state) { + child: SourceConsumer( + source: singleFieldBloc, + builder: (context, state, _) { Widget current = _buildButton(context, state); if (!visibleWithoutValue) { diff --git a/packages/flutter_form_bloc/lib/src/switch_field_bloc_builder.dart b/packages/flutter_form_bloc/lib/src/switch_field_bloc_builder.dart index 37a3880b..eb870c52 100644 --- a/packages/flutter_form_bloc/lib/src/switch_field_bloc_builder.dart +++ b/packages/flutter_form_bloc/lib/src/switch_field_bloc_builder.dart @@ -1,6 +1,5 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_bloc/src/fields/simple_field_bloc_builder.dart'; import 'package:flutter_form_bloc/src/theme/field_theme_resolver.dart'; import 'package:flutter_form_bloc/src/theme/form_bloc_theme.dart'; @@ -136,56 +135,51 @@ class SwitchFieldBlocBuilder extends StatelessWidget { data: Theme.of(context).copyWith( switchTheme: fieldTheme.switchTheme!, ), - child: SimpleFieldBlocBuilder( - singleFieldBloc: booleanFieldBloc, + child: SimpleFieldBlocBuilder( + fieldBloc: booleanFieldBloc, animateWhenCanShow: animateWhenCanShow, - builder: (_, __) { - return BlocBuilder( - bloc: booleanFieldBloc, - builder: (context, state) { - final isEnabled = fieldBlocIsEnabled( - isEnabled: this.isEnabled, - enableOnlyWhenFormBlocCanSubmit: - enableOnlyWhenFormBlocCanSubmit, - fieldBlocState: state, - ); - - return DefaultFieldBlocBuilderPadding( - padding: padding, - child: InputDecorator( - decoration: Style.inputDecorationWithoutBorder.copyWith( - prefixIcon: fieldTheme.controlAffinity == - FieldBlocBuilderControlAffinity.leading - ? _buildSwitch(context, state) - : null, - suffixIcon: fieldTheme.controlAffinity == - FieldBlocBuilderControlAffinity.trailing - ? _buildSwitch(context, state) - : null, - errorText: Style.getErrorText( - context: context, - errorBuilder: errorBuilder, - fieldBlocState: state, - fieldBloc: booleanFieldBloc, - ), - ), - child: DefaultTextStyle( - style: Style.resolveTextStyle( - isEnabled: isEnabled, - style: fieldTheme.textStyle!, - color: fieldTheme.textColor!, - ), - child: Container( - constraints: const BoxConstraints( - minHeight: kMinInteractiveDimension, - ), - alignment: alignment, - child: body, - ), + enableOnlyWhenFormBlocCanSubmit: enableOnlyWhenFormBlocCanSubmit, + isEnabled: isEnabled, + // TODO: Implement readOnly + readOnly: false, + nextFocusNode: nextFocusNode, + builder: (context, state, data) { + final isEnabled = data.isEnabled; + + return DefaultFieldBlocBuilderPadding( + padding: padding, + child: InputDecorator( + decoration: Style.inputDecorationWithoutBorder.copyWith( + prefixIcon: fieldTheme.controlAffinity == + FieldBlocBuilderControlAffinity.leading + ? _buildSwitch(context, state, data) + : null, + suffixIcon: fieldTheme.controlAffinity == + FieldBlocBuilderControlAffinity.trailing + ? _buildSwitch(context, state, data) + : null, + errorText: Style.getErrorText( + context: context, + errorBuilder: errorBuilder, + fieldBlocState: state, + fieldBloc: booleanFieldBloc, + ), + ), + child: DefaultTextStyle( + style: Style.resolveTextStyle( + isEnabled: isEnabled, + style: fieldTheme.textStyle!, + color: fieldTheme.textColor!, + ), + child: Container( + constraints: const BoxConstraints( + minHeight: kMinInteractiveDimension, ), + alignment: alignment, + child: body, ), - ); - }, + ), + ), ); }, ), @@ -195,12 +189,11 @@ class SwitchFieldBlocBuilder extends StatelessWidget { Switch _buildSwitch( BuildContext context, BooleanFieldBlocState state, + FieldBlocBuilderData data, ) { return Switch( value: state.value, - onChanged: fieldBlocBuilderOnChange( - isEnabled: isEnabled, - nextFocusNode: nextFocusNode, + onChanged: data.buildOnChange( onChanged: booleanFieldBloc.changeValue, ), activeThumbImage: activeThumbImage, diff --git a/packages/flutter_form_bloc/lib/src/text_field_bloc_builder.dart b/packages/flutter_form_bloc/lib/src/text_field_bloc_builder.dart index ed01c7a0..908eb2d0 100644 --- a/packages/flutter_form_bloc/lib/src/text_field_bloc_builder.dart +++ b/packages/flutter_form_bloc/lib/src/text_field_bloc_builder.dart @@ -1,9 +1,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_form_bloc/flutter_form_bloc.dart'; -import 'package:flutter_form_bloc/src/features/appear/can_show_field_bloc_builder.dart'; +import 'package:flutter_form_bloc/src/fields/simple_field_bloc_builder.dart'; import 'package:flutter_form_bloc/src/flutter_typeahead.dart'; import 'package:flutter_form_bloc/src/suffix_buttons/clear_suffix_button.dart'; import 'package:flutter_form_bloc/src/suffix_buttons/obscure_suffix_button.dart'; @@ -770,12 +768,13 @@ class _TextFieldBlocBuilderState extends State { super.dispose(); } + late FieldBlocBuilderData _data; + /// Disable editing when the state of the FormBloc is [FormBlocSubmitting]. void _textControllerListener() { - if (widget.textFieldBloc.state.formBloc?.state is FormBlocSubmitting) { - if (_controller.text != (widget.textFieldBloc.value)) { - _fixControllerTextValue(widget.textFieldBloc.value); - } + if (!_data.isReadonly) return; + if (_controller.text != (widget.textFieldBloc.value)) { + _fixControllerTextValue(widget.textFieldBloc.value); } } @@ -807,33 +806,26 @@ class _TextFieldBlocBuilderState extends State { Widget build(BuildContext context) { final fieldTheme = widget.themeStyleOf(context); - return SimpleFieldBlocBuilder( - singleFieldBloc: widget.textFieldBloc, + return SimpleFieldBlocBuilder( + fieldBloc: widget.textFieldBloc, animateWhenCanShow: widget.animateWhenCanShow, focusOnValidationFailed: widget.focusOnValidationFailed, - builder: (_, __) { - return BlocBuilder( - bloc: widget.textFieldBloc, - builder: (context, state) { - final isEnabled = fieldBlocIsEnabled( - isEnabled: widget.isEnabled, - enableOnlyWhenFormBlocCanSubmit: - widget.enableOnlyWhenFormBlocCanSubmit, - fieldBlocState: state, - ); - - if (_controller.text != state.value) { - _fixControllerTextValue(state.value); - } - return DefaultFieldBlocBuilderPadding( - padding: widget.padding, - child: _buildTextField( - state: state, - isEnabled: isEnabled, - fieldTheme: fieldTheme, - ), - ); - }, + enableOnlyWhenFormBlocCanSubmit: widget.enableOnlyWhenFormBlocCanSubmit, + isEnabled: widget.isEnabled, + readOnly: widget.readOnly, + nextFocusNode: widget.nextFocusNode, + builder: (context, state, data) { + _data = data; + if (_controller.text != state.value) { + _fixControllerTextValue(state.value); + } + return DefaultFieldBlocBuilderPadding( + padding: widget.padding, + child: _buildTextField( + state: state, + data: data, + fieldTheme: fieldTheme, + ), ); }, ); @@ -913,7 +905,7 @@ class _TextFieldBlocBuilderState extends State { Widget _buildTextField({ required TextFieldTheme fieldTheme, required TextFieldBlocState state, - required bool isEnabled, + required FieldBlocBuilderData data, }) { return TypeAheadField( textFieldConfiguration: TextFieldConfiguration( @@ -925,7 +917,7 @@ class _TextFieldBlocBuilderState extends State { (widget.nextFocusNode != null ? TextInputAction.next : null), textCapitalization: widget.textCapitalization, style: Style.resolveTextStyle( - isEnabled: isEnabled, + isEnabled: data.isEnabled, style: fieldTheme.textStyle!, color: fieldTheme.textColor!, ), @@ -947,7 +939,7 @@ class _TextFieldBlocBuilderState extends State { onEditingComplete: widget.onEditingComplete, onSubmitted: _onSubmitted, inputFormatters: widget.inputFormatters, - enabled: isEnabled, + enabled: data.isEnabled, cursorWidth: widget.cursorWidth, cursorRadius: widget.cursorRadius, cursorColor: widget.cursorColor, diff --git a/packages/flutter_form_bloc/lib/src/utils/functions.dart b/packages/flutter_form_bloc/lib/src/utils/functions.dart index 8963fdff..2a393fa7 100644 --- a/packages/flutter_form_bloc/lib/src/utils/functions.dart +++ b/packages/flutter_form_bloc/lib/src/utils/functions.dart @@ -1,36 +1,35 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:form_bloc/form_bloc.dart'; -ValueChanged? fieldBlocBuilderOnChange({ - required bool isEnabled, - bool readOnly = false, - required FocusNode? nextFocusNode, - required void Function(T value) onChanged, -}) { - if (isEnabled) { - return (T value) { - if (readOnly) return; - onChanged(value); - if (nextFocusNode != null) { - nextFocusNode.requestFocus(); - } - }; - } - return null; -} +// ValueChanged? fieldBlocBuilderOnChange({ +// required bool isEnabled, +// bool readOnly = false, +// required FocusNode? nextFocusNode, +// required void Function(T value) onChanged, +// }) { +// if (isEnabled) { +// return (T value) { +// if (readOnly) return; +// onChanged(value); +// if (nextFocusNode != null) { +// nextFocusNode.requestFocus(); +// } +// }; +// } +// return null; +// } -bool fieldBlocIsEnabled({ - required bool isEnabled, - bool? enableOnlyWhenFormBlocCanSubmit, - required FieldBlocState fieldBlocState, -}) { - return isEnabled - ? (enableOnlyWhenFormBlocCanSubmit ?? false) - ? fieldBlocState.formBloc?.state.canSubmit ?? true - : true - : false; -} +// bool fieldBlocIsEnabled({ +// required bool isEnabled, +// bool? enableOnlyWhenFormBlocCanSubmit, +// required FieldBlocState fieldBlocState, +// }) { +// return isEnabled +// ? (enableOnlyWhenFormBlocCanSubmit ?? false) +// ? fieldBlocState.formBloc?.state.canSubmit ?? true +// : true +// : false; +// } Widget widgetBasedOnPlatform({ required Widget mobile, diff --git a/packages/flutter_form_bloc/pubspec.yaml b/packages/flutter_form_bloc/pubspec.yaml index c473c489..9130a4f7 100644 --- a/packages/flutter_form_bloc/pubspec.yaml +++ b/packages/flutter_form_bloc/pubspec.yaml @@ -12,10 +12,11 @@ dependencies: flutter: sdk: flutter - flutter_bloc: ^8.0.1 -# form_bloc: -# path: ../form_bloc - form_bloc: ^0.30.0 + bloc: ^8.0.3 +# flutter_bloc: ^8.0.1 + form_bloc: + path: ../form_bloc +# form_bloc: ^0.30.0 equatable: ^2.0.3 rxdart: ^0.27.3 flutter_keyboard_visibility: ^5.0.2 diff --git a/packages/form_bloc/example/main.dart b/packages/form_bloc/example/main.dart index 1ba9b2f4..dc44c0d5 100644 --- a/packages/form_bloc/example/main.dart +++ b/packages/form_bloc/example/main.dart @@ -15,11 +15,13 @@ class LoginFormBloc extends FormBloc { ); LoginFormBloc() { - addFieldBlocs( - fieldBlocs: [ - email, - password, - ], + addStep( + fieldBloc: ListFieldBloc( + fieldBlocs: [ + email, + password, + ], + ), ); } diff --git a/packages/form_bloc/lib/src/blocs/boolean_field/boolean_field_bloc.dart b/packages/form_bloc/lib/src/blocs/boolean_field/boolean_field_bloc.dart index c383baee..07daea39 100644 --- a/packages/form_bloc/lib/src/blocs/boolean_field/boolean_field_bloc.dart +++ b/packages/form_bloc/lib/src/blocs/boolean_field/boolean_field_bloc.dart @@ -35,7 +35,6 @@ class BooleanFieldBloc extends SingleFieldBloc>? validators, List>? asyncValidators, @@ -69,7 +68,6 @@ class BooleanFieldBloc extends SingleFieldBloc value, extraData: extraData, ), diff --git a/packages/form_bloc/lib/src/blocs/boolean_field/boolean_field_state.dart b/packages/form_bloc/lib/src/blocs/boolean_field/boolean_field_state.dart index b96e659d..92ce6319 100644 --- a/packages/form_bloc/lib/src/blocs/boolean_field/boolean_field_state.dart +++ b/packages/form_bloc/lib/src/blocs/boolean_field/boolean_field_state.dart @@ -1,7 +1,6 @@ part of '../field/field_bloc.dart'; -class BooleanFieldBlocState - extends FieldBlocState { +class BooleanFieldBlocState extends FieldBlocState { BooleanFieldBlocState({ required bool isValueChanged, required bool initialValue, @@ -12,8 +11,6 @@ class BooleanFieldBlocState required Suggestions? suggestions, required bool isValidated, required bool isValidating, - FormBloc? formBloc, - required String name, List additionalProps = const [], dynamic Function(bool value)? toJson, ExtraData? extraData, @@ -27,8 +24,6 @@ class BooleanFieldBlocState suggestions: suggestions, isValidated: isValidated, isValidating: isValidating, - formBloc: formBloc, - name: name, toJson: toJson, extraData: extraData, ); @@ -44,7 +39,6 @@ class BooleanFieldBlocState Param?>? suggestions, bool? isValidated, bool? isValidating, - Param? formBloc, Param? extraData, }) { return BooleanFieldBlocState( @@ -57,8 +51,6 @@ class BooleanFieldBlocState isDirty: isDirty ?? this.isDirty, isValidated: isValidated ?? this.isValidated, isValidating: isValidating ?? this.isValidating, - formBloc: formBloc == null ? this.formBloc : formBloc.value, - name: name, toJson: _toJson, extraData: extraData == null ? this.extraData : extraData.value, ); diff --git a/packages/form_bloc/lib/src/blocs/field/field_bloc.dart b/packages/form_bloc/lib/src/blocs/field/field_bloc.dart index 67b0b40c..6d41cc8a 100644 --- a/packages/form_bloc/lib/src/blocs/field/field_bloc.dart +++ b/packages/form_bloc/lib/src/blocs/field/field_bloc.dart @@ -10,7 +10,6 @@ import 'package:form_bloc/src/extension/extension.dart'; import 'package:form_bloc/src/utils.dart'; import 'package:meta/meta.dart'; import 'package:rxdart/rxdart.dart'; -import 'package:uuid/uuid.dart'; part '../boolean_field/boolean_field_bloc.dart'; part '../boolean_field/boolean_field_state.dart'; @@ -52,23 +51,13 @@ typedef Suggestions = Future> Function(String pattern); /// * [GroupFieldBloc]. /// * [ListFieldBloc]. mixin FieldBloc on BlocBase { - String get name => state.name; - /// {@template form_bloc.FieldBloc.validate} /// Validate the field. If it contains more fields, it validates all children. /// Returns `true` if the field is valid otherwise `false` /// {@endtemplate} Future validate(); - /// {@template form_bloc.FieldBloc.updateFormBloc} - /// Update the [formBloc] and [autoValidate] to the fieldBloc - /// {@endtemplate} - void updateFormBloc(FormBloc formBloc, {bool autoValidate = false}); - - /// {@template form_bloc.FieldBloc.removeFormBloc} - /// Remove the [formBloc] to the fieldBloc - /// {@endtemplate} - void removeFormBloc(FormBloc formBloc); + void updateAutoValidation(bool isEnabled); } /// The base class with the common behavior @@ -84,7 +73,7 @@ abstract class SingleFieldBloc< Suggestion, State extends FieldBlocState, ExtraData> extends Cubit with FieldBloc { - bool _autoValidate = true; + bool _autoValidate; List> _validators; @@ -108,11 +97,13 @@ abstract class SingleFieldBloc< SingleFieldBloc({ Equality equality = const DefaultEquality(), + bool autoValidate = true, required List>? validators, required List>? asyncValidators, required Duration asyncValidatorDebounceTime, required State initialState, - }) : _validators = validators ?? [], + }) : _autoValidate = autoValidate, + _validators = validators ?? [], _asyncValidators = asyncValidators ?? [], _asyncValidatorDebounceTime = asyncValidatorDebounceTime, super(initialState) { @@ -327,16 +318,12 @@ abstract class SingleFieldBloc< _revalidateFieldBlocsSubscription?.cancel(); // TODO: When async validation is in progress and auto validate is off this method is broken // because it emit a completed async validation - // TODO: It does not manage MultiFieldBloc fields if (fieldBlocs.isNotEmpty) { - _revalidateFieldBlocsSubscription = Rx.combineLatest( - fieldBlocs.whereType().toList().map( - (state) { - return state.stream.map((state) => state.value).distinct(); - }, - ), - (_) {}, - ).listen((_) { + _revalidateFieldBlocsSubscription = Rx.merge(fieldBlocs.map((e) { + return e.stream.map((event) { + event.value; + }).distinct(DeepCollectionEquality().equals); + })).listen((_) { if (_autoValidate) { _validate(); } else { @@ -546,34 +533,13 @@ abstract class SingleFieldBloc< } } - /// {@macro form_bloc.FieldBloc.updateFormBloc} - /// See [FieldBloc.updateFormBloc] + // Launch validate if previous auto validation is disable and current is enabled @override - void updateFormBloc(FormBloc formBloc, {bool autoValidate = false}) { - _autoValidate = autoValidate; - if (!_autoValidate) { - emit(state.copyWith( - error: Param(null), - isValidated: false, - isValidating: false, - formBloc: Param(formBloc), - ) as State); - } else { - emit(state.copyWith( - formBloc: Param(formBloc), - ) as State); - } - } - - /// {@macro form_bloc.FieldBloc.removeFormBloc} - /// See [FieldBloc.removeFormBloc] - @override - void removeFormBloc(FormBloc formBloc) { - if (state.formBloc == formBloc) { - emit(state.copyWith( - formBloc: Param(null), - ) as State); - } + void updateAutoValidation(bool isEnabled) { + if (_autoValidate == isEnabled) return; + _autoValidate = isEnabled; + if (!_autoValidate || state.isValidated) return; + _validate(); } /// {@template form_bloc.field_bloc.itemsWithoutDuplicates} @@ -624,42 +590,13 @@ abstract class SingleFieldBloc< } } -class ValidationStatus extends Equatable { - final bool isValidating; - final bool isValid; - - const ValidationStatus({ - required this.isValidating, - required this.isValid, - }); - - @override - List get props => [isValidating, isValid]; - - @override - String toString() { - return 'ValidationStatus{isValidating: $isValidating, isValid: $isValid}'; - } -} - class MultiFieldBloc> extends Cubit with FieldBloc { - late final StreamSubscription _onValidationStatus; - - bool _autoValidate = false; + bool _autoValidate; - bool get autoValidate => _autoValidate; - - MultiFieldBloc(TState initialState) : super(initialState) { - _onValidationStatus = stream.switchMap((state) { - return MultiFieldBloc.onValidationStatus(state.flatFieldBlocs); - }).listen((validationStatus) { - emit(state.copyWith( - isValidating: validationStatus.isValidating, - isValid: validationStatus.isValid, - ) as TState); - }); - } + MultiFieldBloc(TState initialState, {required bool autoValidate}) + : _autoValidate = autoValidate, + super(initialState); Iterable get flatFieldBlocs => state.flatFieldBlocs; @@ -701,59 +638,21 @@ class MultiFieldBloc> // INTERNAL // =========================================================================== - /// See [FieldBloc.updateFormBloc] @override - void updateFormBloc(FormBloc formBloc, {bool autoValidate = false}) { - _autoValidate = autoValidate; - - emit(state.copyWith( - formBloc: Param(formBloc), - ) as TState); - - FormBlocUtils.updateFormBloc( - fieldBlocs: state.flatFieldBlocs, - formBloc: formBloc, - autoValidate: _autoValidate, - ); - } - - /// See [FieldBloc.removeFormBloc] - @override - void removeFormBloc(FormBloc formBloc) { - if (state.formBloc == formBloc) { - emit(state.copyWith( - formBloc: Param(null), - ) as TState); - - FormBlocUtils.removeFormBloc( - fieldBlocs: state.flatFieldBlocs, - formBloc: formBloc, - ); + void updateAutoValidation(bool isEnabled) { + if (_autoValidate == isEnabled) return; + _autoValidate = isEnabled; + for (final fieldBloc in state.flatFieldBlocs) { + fieldBloc.updateAutoValidation(isEnabled); } } @override - Future close() { - _onValidationStatus.cancel(); - for (final fieldBloc in state.flatFieldBlocs) { - fieldBloc.close(); - } + Future close() async { + await Future.wait(state.flatFieldBlocs.map((e) => e.close())); return super.close(); } - static Stream onValidationStatus( - Iterable fieldBlocs, - ) { - return Rx.combineLatestList(fieldBlocs.map((fieldBloc) { - return Rx.merge([Stream.value(fieldBloc.state), fieldBloc.stream]); - })).map((fieldStates) { - return ValidationStatus( - isValidating: fieldStates.any((fieldState) => fieldState.isValidating), - isValid: fieldStates.every((fieldState) => fieldState.isValid), - ); - }).distinct(); - } - static Future validateAll(Iterable fieldBlocs) async { // Force validation if field bloc is not valid fieldBlocs = fieldBlocs.where((element) => !element.state.isValid); @@ -766,32 +665,4 @@ class MultiFieldBloc> return areValid.every((isValid) => isValid); }); } - - static bool deepContains(Iterable fieldBlocs, FieldBloc target) { - if (fieldBlocs.isEmpty) return false; - - for (final fieldBloc in fieldBlocs) { - if (fieldBloc is MultiFieldBloc) { - final contains = - MultiFieldBloc.deepContains(fieldBloc.state.flatFieldBlocs, target); - if (contains) { - return true; - } - } else if (fieldBloc.state.name == target.state.name) { - return true; - } - } - return false; - } - - static bool areFieldBlocsValid(Iterable fieldBlocs) => - fieldBlocs.every(_isFieldBlocValid); - - static bool areFieldBlocsValidating(Iterable fieldBlocs) => - fieldBlocs.every(_isFieldBlocValidating); - - static bool _isFieldBlocValid(FieldBloc field) => field.state.isValid; - - static bool _isFieldBlocValidating(FieldBloc field) => - field.state.isValidating; } diff --git a/packages/form_bloc/lib/src/blocs/field/field_state.dart b/packages/form_bloc/lib/src/blocs/field/field_state.dart index 951c1b87..ec5e1c4b 100644 --- a/packages/form_bloc/lib/src/blocs/field/field_state.dart +++ b/packages/form_bloc/lib/src/blocs/field/field_state.dart @@ -2,25 +2,34 @@ part of 'field_bloc.dart'; /// The common state interface of all field blocs mixin FieldBlocStateBase { - // TODO: Rename to `FieldBlocState` - /// It is the string that identifies the [FieldBloc]. - String get name; + // TODO: Set value Type + dynamic get value; - bool get isValidating; + bool get isValueChanged; + bool get hasInitialValue; + bool get hasUpdatedValue; + + bool get hasError; + bool get isDirty; + // TODO: Implement autoValidate + bool get isValidating; bool get isValid; - /// Identifies whether the FieldBloc has been added to the FormBloc - FormBloc? get formBloc; + // TODO: Implement isReadOnly. You can't call changeValue method - bool get hasFormBloc => formBloc != null; + bool contains(FieldBloc fieldBloc) => false; + + dynamic toJson(); } +// TODO: Implement disabled values/items abstract class FieldBlocState extends Equatable with FieldBlocStateBase { /// {@template flutter_field_bloc.FieldBloc.isValueChanged} /// Returns true when the value has been changed by [FieldBloc.changeValue]. /// {@endtemplate} + @override final bool isValueChanged; final Value initialValue; @@ -37,19 +46,13 @@ abstract class FieldBlocState extends Equatable /// Indicate if this field was [value] updated by [SingleFieldBloc.changeValue] / [SingleFieldBloc.updateValue] /// or receive a external validation by [SingleFieldBloc.validate]. + @override final bool isDirty; - @Deprecated('In favour of [isValueChanged]') - bool get isInitial => - !hasInitialValue && (isValueChanged || !hasUpdatedValue); /// Function that returns a list of suggestions /// which can be used to update the value. final Suggestions? suggestions; - /// It is the string that identifies the [FieldBloc]. - @override - final String name; - /// Indicate if [value] was checked with the validators /// of the [FieldBloc]. final bool isValidated; @@ -59,16 +62,13 @@ abstract class FieldBlocState extends Equatable @override final bool isValidating; - /// The current [FormBloc] that contains this `FieldBloc`. - @override - final FormBloc? formBloc; - /// Transform [value] in a JSON value. /// By default returns [value], but you can /// set in the constructor of the `FieldBloc` /// /// This method is called when you use [FormBlocState.toJson] - Object? toJson() => value == null ? null : _toJson(value); + @override + dynamic toJson() => _toJson(value); /// Implementation of [toJson] final dynamic Function(Value value) _toJson; @@ -88,8 +88,6 @@ abstract class FieldBlocState extends Equatable required this.suggestions, required this.isValidated, required this.isValidating, - required this.formBloc, - required this.name, required dynamic Function(Value value)? toJson, required this.extraData, }) : _toJson = toJson ?? ((value) => value); @@ -102,6 +100,7 @@ abstract class FieldBlocState extends Equatable bool get isValid => !hasError && !isValidating && isValidated; /// Indicates if [error] is not `null`. + @override bool get hasError => error != null; /// Indicates if [value] is not `null`. @@ -110,28 +109,30 @@ abstract class FieldBlocState extends Equatable /// {@template flutter_field_bloc.FieldBloc.hasInitialValue} /// Indicate if this field has [value] from [FieldBloc.updateInitialValue] method. /// {@endtemplate} + @override bool get hasInitialValue => initialValue == value; /// {@template flutter_field_bloc.FieldBloc.hasUpdatedValue} /// Indicate if this field has [value] from [FieldBloc.updateValue] method. /// {@endtemplate} + @override bool get hasUpdatedValue => updatedValue == value; - bool get hasDirt => isDirty || isValueChanged || !hasInitialValue; - /// Indicates if this state has error and is not initial. /// /// Used for not show the error when [hasInitialValue] is `false`. /// Which mean that [value] was updated /// after be instantiate by the [FieldBloc] and has an error. - bool get canShowError => hasDirt && hasError; + bool get canShowError => _hasDirt && hasError; /// Indicates if this state is validating and is not initial. /// /// Used for not show the is validating when [isDirty] is `true`. /// Which mean that [value] was updated /// after be instantiate by the [FieldBloc] and is validating. - bool get canShowIsValidating => hasDirt && isValidating; + bool get canShowIsValidating => _hasDirt && isValidating; + + bool get _hasDirt => isDirty || isValueChanged || !hasInitialValue; /// Returns a copy of the current state by changing /// the values that are passed as parameters. @@ -145,7 +146,6 @@ abstract class FieldBlocState extends Equatable Param?>? suggestions, bool? isValidated, bool? isValidating, - Param formBloc, Param? extraData, }); @@ -154,7 +154,6 @@ abstract class FieldBlocState extends Equatable var _toString = ''; _toString += '$runtimeType {'; - _toString += '\n name: $name'; _toString += ',\n isValueChanged: $isValueChanged'; _toString += ',\n updatedValue: $updatedValue'; _toString += ',\n initialValue: $initialValue'; @@ -166,7 +165,6 @@ abstract class FieldBlocState extends Equatable _toString += ',\n isValid: $isValid'; _toString += ',\n extraData: $extraData'; _toString += extra; - _toString += ',\n formBloc: $formBloc'; _toString += '\n}'; return _toString; @@ -184,51 +182,64 @@ abstract class FieldBlocState extends Equatable isValidated, isValidating, extraData, - formBloc, ]; } abstract class MultiFieldBlocState extends Equatable with FieldBlocStateBase { + final ExtraData? extraData; + + MultiFieldBlocState({ + required this.extraData, + }); + @override - final FormBloc? formBloc; + late final bool isValueChanged = + flatFieldStates.any((fs) => fs.isValueChanged); @override - final String name; + late final bool hasInitialValue = + flatFieldStates.every((fs) => fs.hasInitialValue); @override - final bool isValidating; + late final bool hasUpdatedValue = + flatFieldStates.every((fs) => fs.hasUpdatedValue); @override - final bool isValid; + late final bool hasError = flatFieldStates.any((fs) => fs.hasError); - final ExtraData? extraData; + @override + late final bool isDirty = flatFieldStates.any((fs) => fs.isDirty); - const MultiFieldBlocState({ - required this.formBloc, - required this.name, - required this.isValidating, - required this.isValid, - required this.extraData, - }); + @override + late final bool isValid = flatFieldStates.every((fs) => fs.isValid); + + @override + late final bool isValidating = flatFieldStates.any((fs) => fs.isValidating); + + /// Returns `true` if the [FormBloc] contains [fieldBloc] + @override + bool contains(FieldBloc fieldBloc) { + if (flatFieldBlocs.contains(fieldBloc)) return true; + return flatFieldStates.any((fb) => fb.contains(fieldBloc)); + } + @internal Iterable get flatFieldBlocs; + @internal + Iterable get flatFieldStates; + MultiFieldBlocState copyWith({ - Param? formBloc, - bool? isValidating, - bool? isValid, Param? extraData, }); @override - List get props => [formBloc, name, isValidating, isValid, extraData]; + List get props => [isValidating, isValid, extraData]; @override String toString([Object? other]) { return '$runtimeType {' - ',\n formBloc: $formBloc' - ',\n name: $name' ',\n isValidating: $isValidating' ',\n isValid: $isValid' ',\n extraData: $extraData' diff --git a/packages/form_bloc/lib/src/blocs/form/form_bloc.dart b/packages/form_bloc/lib/src/blocs/form/form_bloc.dart index 661e7372..6aca68b9 100644 --- a/packages/form_bloc/lib/src/blocs/form/form_bloc.dart +++ b/packages/form_bloc/lib/src/blocs/form/form_bloc.dart @@ -1,8 +1,10 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; +import 'package:collection/collection.dart'; import 'package:equatable/equatable.dart'; import 'package:form_bloc/src/blocs/field/field_bloc.dart'; +import 'package:form_bloc/src/extension/extension.dart'; import 'package:rxdart/rxdart.dart'; part 'form_state.dart'; @@ -11,8 +13,9 @@ part 'form_state.dart'; /// /// See complex examples here: https://github.com/GiancarloCode/form_bloc/tree/master/packages/flutter_form_bloc/example/lib/forms abstract class FormBloc - extends Cubit> { - /// See: [_setupStepValidationSubs]. + extends Cubit> + implements FieldBloc> { + /// See: [_mapFieldStates]. /// Each [FormBlocState.currentStep] has its own subscription final Map _stepValidationSubs = {}; @@ -32,7 +35,7 @@ abstract class FormBloc bool _canSubmit = true; /// Indicates if the [_fieldBlocs] must be autoValidated. - final bool _autoValidate; + bool _autoValidate; late final StreamSubscription _setupAreAllFieldsValidSubscriptionSubscription; @@ -59,7 +62,7 @@ abstract class FormBloc _formBlocStateSubscription?.cancel(); _isValidDone = null; - for (final fieldBloc in state.flatFieldBlocs()!) { + for (final fieldBloc in state.fieldBlocs.values) { fieldBloc.close(); } @@ -70,34 +73,32 @@ abstract class FormBloc void _initStepValidationSubs() { _setupAreAllFieldsValidSubscriptionSubscription = stream - .map((state) => state._flatFieldBlocsStepped()) + .map((state) => state._fieldBlocs) .debounceTime(const Duration(milliseconds: 5)) - .distinct() - .listen(_setupStepValidationSubs); + .distinct(const MapEquality().equals) + .switchMap(_mapFieldStates) + .listen((fieldStates) { + emit(state._copyWith( + fieldStates: fieldStates, + )); + }); } /// Init the subscription to the state of each /// `fieldBloc` in [FieldBlocs] to update [FormBlocState._isValidByStep] /// when any `fieldBloc` changes it state. - void _setupStepValidationSubs( - Map> allFieldBlocs, - ) { - for (final sub in _stepValidationSubs.values) { - sub.cancel(); + Stream> _mapFieldStates( + Map fieldBlocs, + ) async* { + final fieldStatesStream = Rx.combineLatestList(fieldBlocs.entries.map((e) { + final step = e.key; + final fb = e.value; + return fb.hotStream.map((fs) => MapEntry(step, fs)); + })).skip(1); + + await for (final fieldStates in fieldStatesStream) { + yield Map.fromEntries(fieldStates.map((e) => MapEntry(e.key, e.value))); } - - allFieldBlocs.forEach((step, fieldBlocs) { - _stepValidationSubs[step] = - MultiFieldBloc.onValidationStatus(fieldBlocs).listen((status) { - if (_autoValidate) { - _canSubmit = !status.isValidating; - } - _updateValidStep( - isValid: status.isValid, - step: step, - ); - }); - }); } /// If [isLoading] is `true`, [OnLoading] @@ -158,6 +159,11 @@ abstract class FormBloc /// [onSubmitting] will be called. void submit() => _onSubmit(); + @override + Future validate() async { + return await state.fieldBloc(state.currentStep).validate(); + } + /// Call `clear` method for each [FieldBloc] in [FieldBlocs]. void clear() => _onClearFormBloc(); @@ -178,15 +184,22 @@ abstract class FormBloc /// Adds [fieldBloc] to the [FormBloc]. /// - /// You can set [step] of this fields, by default is `0`. - void addFieldBloc({int step = 0, required FieldBloc fieldBloc}) => - _onAddFieldBlocs(step: step, fieldBlocs: [fieldBloc]); + /// TODO: Fix + /// You can set [insertAt] of this fields, by default is `0`. + void addStep(FieldBloc fieldBloc, {int? insertAt}) => + _onAddStep(step: insertAt, fieldBlocs: fieldBloc); - /// Adds [fieldBlocs] to the [FormBloc]. + /// Adds [fieldBloc] to the [FormBloc]. /// - /// You can set [step] of this fields, by default is `0`. - void addFieldBlocs({int step = 0, required List fieldBlocs}) => - _onAddFieldBlocs(step: step, fieldBlocs: fieldBlocs); + /// You can set [at] of this fields, by default is `0`. + void updateStep(int at, FieldBloc fieldBloc) => + _onUpdateStep(step: at, fieldBlocs: fieldBloc); + + // /// Adds [fieldBlocs] to the [FormBloc]. + // /// + // /// You can set [step] of this fields, by default is `0`. + // void addFieldBlocs({int step = 0, required List fieldBlocs}) => + // _onAddFieldBlocs(step: step, fieldBlocs: fieldBlocs); void previousStep() => _onPreviousStep(); @@ -195,12 +208,19 @@ abstract class FormBloc void updateCurrentStep(int step) => _onUpdateCurrentStep(step: step); /// Removes a [FieldBloc] from the [FormBloc] - void removeFieldBloc({int? step, required FieldBloc fieldBloc}) => - _onRemoveFieldBlocs(step: step, fieldBlocs: [fieldBloc]); + void removeStep(int at) => _onRemoveStep(step: at); - /// Removes a [FieldBlocs] from the [FormBloc] - void removeFieldBlocs({int? step, required List fieldBlocs}) => - _onRemoveFieldBlocs(step: step, fieldBlocs: fieldBlocs); + /// Removes a [FieldBloc] from the [FormBloc] + void removeStepBy(FieldBloc fieldBloc, {int? at}) => + _onRemoveStepBy(at: at, fieldBlocs: fieldBloc); + + @override + void updateAutoValidation(bool isEnabled) { + if (_autoValidate == isEnabled) return; + for (final fieldBloc in state._fieldBlocs.values) { + fieldBloc.updateAutoValidation(isEnabled); + } + } // =========================================================================== // METHODS TO UPDATE STATE @@ -298,19 +318,19 @@ abstract class FormBloc /// Check if all field blocs and their children have undergone a change in values /// {@endtemplate} bool isValuesChanged({int? step}) => - FormBlocUtils.isValuesChanged(state.flatFieldBlocs(step) ?? const []); + FormBlocUtils.isValuesChanged(state.fieldBlocs.values); /// {@template form_bloc.hasInitialValues} /// Check if all field blocs and their children have initial values /// {@endtemplate} bool hasInitialValues({int? step}) => - FormBlocUtils.hasInitialValues(state.flatFieldBlocs(step) ?? const []); + FormBlocUtils.hasInitialValues(state.fieldBlocs.values); /// {@template form_bloc.hasUpdatedValues} /// Check if all field blocs and their children have updated values /// {@endtemplate} bool hasUpdatedValues({int? step}) => - FormBlocUtils.hasUpdatedValues(state.flatFieldBlocs(step) ?? const []); + FormBlocUtils.hasUpdatedValues(state.fieldBlocs.values); // =========================================================================== // toString @@ -331,14 +351,10 @@ abstract class FormBloc notValidStep != null && notValidStep != state.lastStep) { // go to the first step invalid - emit(FormBlocSubmissionFailed( - isValidByStep: state._isValidByStep, - isEditing: state.isEditing, - fieldBlocs: state._fieldBlocs, - currentStep: state.currentStep, - )); - emit(state.toLoaded()._copyWith(currentStep: notValidStep)); - } else if (_autoValidate && state.isValid(state.currentStep)) { + + emit(state.toSubmissionFailed()); + emit(state.toLoaded()); + } else if (_autoValidate && state.isValidStep(state.currentStep)) { // Auto validate is enabled, required validate all field blocs // if step isn't valid to show a error (if value is initial) @@ -354,54 +370,30 @@ abstract class FormBloc void _validateAndSubmit() async { // get field blocs of the current step and validate - final currentFieldBlocs = state.fieldBlocs(state.currentStep)?.values ?? []; - final isValidDone = - _isValidDone = MultiFieldBloc.validateAll(currentFieldBlocs); + _isValidDone = state.fieldBloc(state.currentStep).validate(); final isValid = await isValidDone; if (_isValidDone != isValidDone) return; if (!isValid) { - emit(FormBlocSubmissionFailed( - isValidByStep: { - ...state._isValidByStep, - state.currentStep: false, - }, - isEditing: state.isEditing, + emit(state.toFailure( fieldBlocs: state._fieldBlocs, - currentStep: state.currentStep, )); emit(state.toLoaded()); return; } emit(state._copyWith( - isValidByStep: { - ...state._isValidByStep, - state.currentStep: true, - }, + fieldBlocs: state._fieldBlocs, )); _callInBlocContext(onSubmitting); } - void _updateValidStep({ - required bool isValid, - required int step, - }) async { - emit(state._copyWith( - isValidByStep: { - ...state._isValidByStep, - step: isValid, - }, - fieldBlocs: state._fieldBlocs, - )); - } - void _onClearFormBloc() { final allSingleFieldBlocs = - FormBlocUtils.getAllSingleFieldBlocs(state.fieldBlocs()!.values); + FormBlocUtils.getAllSingleFieldBlocs(state.fieldBlocs.values); for (var fieldBloc in allSingleFieldBlocs) { fieldBloc.clear(); @@ -413,13 +405,8 @@ abstract class FormBloc if (stateSnapshot is FormBlocSubmitting && !stateSnapshot.isCanceling) { _isValidDone = null; - emit(FormBlocSubmitting( - isCanceling: true, - isValidByStep: stateSnapshot._isValidByStep, - progress: stateSnapshot.progress, - isEditing: stateSnapshot.isEditing, - fieldBlocs: stateSnapshot._fieldBlocs, - currentStep: stateSnapshot.currentStep, + emit(state.toSubmitting( + isCancelling: true, )); _callInBlocContext(onCancelingSubmission); @@ -427,13 +414,7 @@ abstract class FormBloc } void _onDeleteFormBloc() { - emit(FormBlocDeleting( - isValidByStep: state._isValidByStep, - isEditing: state.isEditing, - fieldBlocs: state._fieldBlocs, - currentStep: state.currentStep, - progress: 0.0, - )); + emit(state.toDeleting()); _callInBlocContext(onDeleting); } @@ -450,84 +431,64 @@ abstract class FormBloc } } - void _onAddFieldBlocs({ - int step = 0, - required Iterable fieldBlocs, + void _onAddStep({ + int? step, + required FieldBloc fieldBlocs, }) { - fieldBlocs = fieldBlocs.where((fieldBloc) { - return !state.contains(fieldBloc, step: step, deep: false); - }); - - if (fieldBlocs.isEmpty) return; + assert(step == null || step > 0 && step <= state._fieldBlocs.length); - for (final fieldBloc in fieldBlocs) { - fieldBloc.updateFormBloc(this, autoValidate: _autoValidate); - } + if (state._fieldBlocs[step] == fieldBlocs) return; emit(state._copyWith( fieldBlocs: { ...state._fieldBlocs, - step: { - ...?state._fieldBlocs[step], - for (final fieldBloc in fieldBlocs) fieldBloc.state.name: fieldBloc, - }, + step ?? state._fieldStates.length: fieldBlocs, }, - isValidByStep: { - ...state._isValidByStep, - step: MultiFieldBloc.areFieldBlocsValid( - fieldBlocs.followedBy(state.flatFieldBlocs(step) ?? const [])), + )); + } + + void _onUpdateStep({ + required int step, + required FieldBloc fieldBlocs, + }) { + assert(step > 0 && step <= state._fieldBlocs.length); + + if (state._fieldBlocs[step] == fieldBlocs) return; + + emit(state._copyWith( + fieldBlocs: { + ...state._fieldBlocs, + step: fieldBlocs, }, )); } - void _onRemoveFieldBlocs({ - int? step, - required Iterable fieldBlocs, - }) async { - fieldBlocs = fieldBlocs.where((fieldBloc) { - return state.contains(fieldBloc, step: step, deep: false); - }); + void _onRemoveStep({required int step}) async { + if (!state._fieldBlocs.containsKey(step)) return; - if (fieldBlocs.isEmpty) return; + final nextFieldBlocs = state._fieldBlocs.entries + .where((element) => element.key != step) + .map((e) => e.value) + .toList() + .asMap(); - for (final fieldBloc in fieldBlocs) { - fieldBloc.removeFormBloc(this); - } + emit(state._copyWith( + fieldBlocs: nextFieldBlocs, + )); + } - Map> nextFieldBlocs; - if (step == null) { - nextFieldBlocs = { - ...state._fieldBlocs, - for (final step in state._fieldBlocs.keys) - step: { - for (final fieldBloc in state.flatFieldBlocs(step)!) - if (!fieldBlocs - .any((fb) => fieldBloc.state.name == fb.state.name)) - fieldBloc.state.name: fieldBloc, - }, - }; - } else { - final fieldBlocsInStep = state.flatFieldBlocs(step); - if (fieldBlocsInStep == null) return; - nextFieldBlocs = { - ...state._fieldBlocs, - step: { - for (final fieldBloc in fieldBlocsInStep) - if (!fieldBlocs.any((fb) => fieldBloc.state.name == fb.state.name)) - fieldBloc.state.name: fieldBloc, - }, - }; - } + void _onRemoveStepBy({ + int? at, + required FieldBloc fieldBlocs, + }) async { + if (!state._fieldBlocs.containsValue(fieldBlocs)) return; - // Remove empty steps - nextFieldBlocs.removeWhere((_, fieldBlocs) => fieldBlocs.isEmpty); + final nextFieldBlocs = state._fieldBlocs.where((key, value) { + return !((at == null || at == key) && fieldBlocs == value); + }); emit(state._copyWith( fieldBlocs: nextFieldBlocs, - isValidByStep: { - for (final e in nextFieldBlocs.entries) - e.key: MultiFieldBloc.areFieldBlocsValid(e.value.values), - }, )); } } diff --git a/packages/form_bloc/lib/src/blocs/form/form_bloc_utils.dart b/packages/form_bloc/lib/src/blocs/form/form_bloc_utils.dart index 00144123..bc0de651 100644 --- a/packages/form_bloc/lib/src/blocs/form/form_bloc_utils.dart +++ b/packages/form_bloc/lib/src/blocs/form/form_bloc_utils.dart @@ -3,8 +3,7 @@ part of '../field/field_bloc.dart'; class FormBlocUtils { FormBlocUtils._(); - static Iterable getAllSingleFieldBlocs( - Iterable fieldBlocs) { + static Iterable getAllSingleFieldBlocs(Iterable fieldBlocs) { return fieldBlocs.expand((fieldBloc) { if (fieldBloc is SingleFieldBloc) { return [fieldBloc]; @@ -21,8 +20,7 @@ class FormBlocUtils { if (fieldBloc is SingleFieldBloc) { return [fieldBloc]; } else if (fieldBloc is MultiFieldBloc) { - return [fieldBloc] - .followedBy(getAllFieldBlocs(fieldBloc.flatFieldBlocs)); + return [fieldBloc].followedBy(getAllFieldBlocs(fieldBloc.flatFieldBlocs)); } } return const []; @@ -33,7 +31,7 @@ class FormBlocUtils { /// if it does not exist, return `null`. static FieldBloc? getFieldBlocFromPath({ required String path, - required Map fieldBlocs, + required Map fieldBlocs, }) { var names = path.split('/'); @@ -189,8 +187,7 @@ class FormBlocUtils { } } */ - static Map fieldBlocsStatesToJson( - Map fieldBlocsStates) { + static Map fieldBlocsStatesToJson(Map fieldBlocsStates) { final json = {}; fieldBlocsStates.forEach((name, dynamic fieldBlocState) { @@ -206,8 +203,7 @@ class FormBlocUtils { return json; } - static List fieldBlocsStatesListToJsonList( - List fieldBlocsStatesList) { + static List fieldBlocsStatesListToJsonList(List fieldBlocsStatesList) { final list = []; for (var fieldBlocState in fieldBlocsStatesList) { @@ -222,8 +218,7 @@ class FormBlocUtils { return list; } - static Map fieldBlocsToFieldBlocsStates( - Map fieldBlocs) { + static Map fieldBlocsToFieldBlocsStates(Map fieldBlocs) { final json = {}; fieldBlocs.forEach((name, fieldBloc) { @@ -239,8 +234,7 @@ class FormBlocUtils { return json; } - static List fieldBlocListToFieldBlocsStatesList( - ListFieldBloc fieldBlocList) { + static List fieldBlocListToFieldBlocsStatesList(ListFieldBloc fieldBlocList) { final list = []; for (var fieldBloc in fieldBlocList.state.fieldBlocs) { @@ -255,8 +249,7 @@ class FormBlocUtils { return list; } - static Iterable flattenFieldBlocsStateList( - Iterable fieldBlocStateList) { + static Iterable flattenFieldBlocsStateList(Iterable fieldBlocStateList) { final list = []; for (var fieldBlocState in fieldBlocStateList) { @@ -306,8 +299,7 @@ class FormBlocUtils { currentFieldBlocState = fieldBlocsStates[name]; } } else { - if (currentFieldBlocState == null || - currentFieldBlocState is FieldBlocState) { + if (currentFieldBlocState == null || currentFieldBlocState is FieldBlocState) { return null; } @@ -338,8 +330,8 @@ class FormBlocUtils { required String path, required Map fieldBlocsStates, }) { - final dynamic fieldBlocState = FormBlocUtils.getFieldBlocStateFromPath( - path: path, fieldBlocsStates: fieldBlocsStates); + final dynamic fieldBlocState = + FormBlocUtils.getFieldBlocStateFromPath(path: path, fieldBlocsStates: fieldBlocsStates); if (fieldBlocState is FieldBlocState) { return fieldBlocState.value; @@ -352,28 +344,28 @@ class FormBlocUtils { } } - static void updateFormBloc({ - required Iterable fieldBlocs, - required FormBloc? formBloc, - bool autoValidate = false, - }) { - if (formBloc == null) return; - - for (final fieldBloc in fieldBlocs) { - fieldBloc.updateFormBloc(formBloc, autoValidate: autoValidate); - } - } - - static void removeFormBloc({ - required Iterable fieldBlocs, - required FormBloc? formBloc, - }) { - if (formBloc == null) return; - - for (final fieldBloc in fieldBlocs) { - fieldBloc.removeFormBloc(formBloc); - } - } + // static void updateFormBloc({ + // required Iterable fieldBlocs, + // required FormBloc? formBloc, + // bool autoValidate = false, + // }) { + // if (formBloc == null) return; + // + // for (final fieldBloc in fieldBlocs) { + // fieldBloc.updateFormBloc(formBloc, autoValidate: autoValidate); + // } + // } + // + // static void removeFormBloc({ + // required Iterable fieldBlocs, + // required FormBloc? formBloc, + // }) { + // if (formBloc == null) return; + // + // for (final fieldBloc in fieldBlocs) { + // fieldBloc.removeFormBloc(formBloc); + // } + // } /// See [FormBloc.isValuesChanged] static bool isValuesChanged( diff --git a/packages/form_bloc/lib/src/blocs/form/form_state.dart b/packages/form_bloc/lib/src/blocs/form/form_state.dart index 95b88b8e..9774d13f 100644 --- a/packages/form_bloc/lib/src/blocs/form/form_state.dart +++ b/packages/form_bloc/lib/src/blocs/form/form_state.dart @@ -13,59 +13,8 @@ part of 'form_bloc.dart'; /// * [FormBlocDeleteFailed] /// * [FormBlocDeleteSuccessful] /// * [FormBlocUpdatingFields] -abstract class FormBlocState - extends Equatable { - /// Indicates if each [FieldBloc] in [FormBloc._fieldBlocs] is valid. - final Map _isValidByStep; - - bool isValid([int? step]) { - if (step == null) { - return _isValidByStep.values.every((e) => e); - } else { - return _isValidByStep[step] ?? false; - } - } - - @Deprecated( - 'In favour of [FormBloc.hasInitialValues] or [FormBloc.isValuesChanged].') - bool isInitial([int? step]) { - return FormBlocUtils.hasInitialValues(flatFieldBlocs(step) ?? const []); - } - - /// It is usually used in forms that are used as CRUD, - /// so when it is true it means that you can - /// perform the update operation. - final bool isEditing; - - final int currentStep; - - int get numberOfSteps { - if (_fieldBlocs.isNotEmpty) { - return _fieldBlocs.length; - } - return 1; - } - - bool get isLastStep => currentStep == lastStep; - - int get lastStep => numberOfSteps - 1; - - bool get isFirstStep => currentStep == 0; - - // Returns the first step that is not valid. - int? get notValidStep { - final invalidSteps = []; - if (_isValidByStep.isNotEmpty) { - for (var i = 0; i < _isValidByStep.length - 1; i++) { - if (!_isValidByStep[i]!) { - invalidSteps.add(i); - } - } - } - - return invalidSteps.isNotEmpty ? invalidSteps.first : null; - } - +abstract class FormBlocState extends Equatable + implements FieldBlocStateBase { /// Map containing all the [name]s. /// /// The `key` of each [FieldBloc] will be @@ -79,133 +28,134 @@ abstract class FormBlocState /// * [multiSelectFieldBlocOf] /// * [groupFieldBlocOf] /// * [listFieldBlocOf] - final Map> _fieldBlocs; + final Map _fieldBlocs; - Map? fieldBlocs([int? step]) { - if (step == null) { - return _allFieldBlocsMap; - } else { - return _fieldBlocs[step]; - } - } + final Map _fieldStates; - Map get _allFieldBlocsMap { - final map = {}; - _fieldBlocs.forEach((key, value) => map.addAll(value)); - return map; - } + Map get fieldBlocs => Map.unmodifiable(_fieldBlocs); - Iterable? flatFieldBlocs([int? step]) { - if (step == null) return _fieldBlocs.values.expand((e) => e.values); - return _fieldBlocs[step]?.values; - } + Map get fieldStates => + Map.unmodifiable(_fieldStates); - Map> _flatFieldBlocsStepped() { - return _fieldBlocs.map((key, fbs) => MapEntry(key, fbs.values)); + FieldBloc fieldBloc(int step) { + final fieldBloc = _fieldBlocs[step]; + if (fieldBloc == null) throw 'Not exist $step'; + return fieldBloc; } - /// States by step - late final Map> _fieldBlocsStatesByStepMap = - _fieldBlocs.map((key, value) { - return MapEntry(key, FormBlocUtils.fieldBlocsToFieldBlocsStates(value)); + @override + late final Map value = + _fieldStates.map((step, state) { + return MapEntry(step, state.value); }); - /// All states of all steps - late final Map _fieldBlocsStates = - FormBlocUtils.fieldBlocsToFieldBlocsStates({ - for (final stepFieldBlocs in _fieldBlocs.values) ...stepFieldBlocs, + @override + late final bool isValueChanged = _fieldStates.values.any((fs) { + return fs.isValueChanged; }); - /// Returns `true` if the [FormBloc] contains [fieldBloc] - bool contains(FieldBloc fieldBloc, {int? step, bool deep = true}) { - final fieldBlocs = (flatFieldBlocs(step) ?? const []); - if (deep) { - return fieldBlocs.any((fb) => fb == fieldBloc); - } - return MultiFieldBloc.deepContains(fieldBlocs, fieldBloc); - } + @override + late final bool hasInitialValue = _fieldStates.values.every((fs) { + return fs.hasInitialValue; + }); - /// Returns the value of [FieldBloc] that has this [name]. - T? valueOf(String name) { - return FormBlocUtils.getValueOfFieldBlocsStates( - path: name, - fieldBlocsStates: _fieldBlocsStates, - ) as T?; - } + @override + late final bool hasUpdatedValue = _fieldStates.values.every((fs) { + return fs.hasUpdatedValue; + }); - List? valueListOf(String name) { - return (FormBlocUtils.getValueOfFieldBlocsStates( - path: name, - fieldBlocsStates: _fieldBlocsStates, - ) as List?) - ?.cast(); - } + @override + late final bool hasError = _fieldStates.values.any((fs) => fs.hasError); - List>? valueMapListOf(String name) { - return valueListOf>(name); - } + @override + late final bool isDirty = _fieldStates.values.any((fs) => fs.isDirty); - Map? valueMapOf(String name) { - return (FormBlocUtils.getValueOfFieldBlocsStates( - path: name, - fieldBlocsStates: _fieldBlocsStates, - ) as Map?) - ?.cast(); - } + @override + late final bool isValid = _fieldStates.values.every((fs) => fs.isValid); - T? _fieldBlocOf(String name) { - final fieldBloc = FormBlocUtils.getFieldBlocFromPath( - path: name, - fieldBlocs: _allFieldBlocsMap, - ); - return fieldBloc as T?; - } + @override + late final bool isValidating = + _fieldStates.values.any((fs) => fs.isValidating) && + this is! FormBlocSubmitting; + + bool isValidStep(int step) => _fieldStates[step]?.isValid ?? false; + + /// It is usually used in forms that are used as CRUD, + /// so when it is true it means that you can + /// perform the update operation. + final bool isEditing; + + final int currentStep; - Map toJson([int? step]) { - if (step == null) { - return FormBlocUtils.fieldBlocsStatesToJson(_fieldBlocsStates); + int get numberOfSteps { + if (_fieldBlocs.isNotEmpty) { + return _fieldBlocs.length; } + return 1; + } + + bool get isLastStep => currentStep == lastStep; + + int get lastStep => numberOfSteps - 1; + + bool get isFirstStep => currentStep == 0; - if (_fieldBlocs.containsKey(step)) { - return FormBlocUtils.fieldBlocsStatesToJson( - _fieldBlocsStatesByStepMap[step]!); + // Returns the first step that is not valid. + int? get notValidStep { + return _fieldStates.entries + .firstWhereOrNull((entry) => entry.value.isValid) + ?.key; + } + + /// Returns `true` if the [FormBloc] contains [fieldBloc] + @override + bool contains(FieldBloc fieldBloc, {int? step}) { + if (step != null) { + if (_fieldBlocs[step] == fieldBloc) return true; + return _fieldStates[step]!.contains(fieldBloc); } + if (_fieldBlocs.containsValue(fieldBloc)) return true; + return _fieldStates.values.any((fs) => fs.contains(fieldBloc)); + } - return const {}; + @override + Map toJson() { + return _fieldStates.map( + (key, value) => MapEntry(key, value.toJson())); } - T? _singleFieldBlocOf(String path) { + T? _fieldBlocOf(String path) { final fieldBloc = FormBlocUtils.getFieldBlocFromPath( path: path, - fieldBlocs: _allFieldBlocsMap, + fieldBlocs: _fieldBlocs, ); return fieldBloc as T?; } InputFieldBloc? inputFieldBlocOf( - String name) => - _singleFieldBlocOf(name); + String path) => + _fieldBlocOf(path); - TextFieldBloc? textFieldBlocOf(String name) => - _singleFieldBlocOf(name); + TextFieldBloc? textFieldBlocOf(String path) => + _fieldBlocOf(path); - BooleanFieldBloc? booleanFieldBlocOf(String name) => - _singleFieldBlocOf(name); + BooleanFieldBloc? booleanFieldBlocOf(String path) => + _fieldBlocOf(path); SelectFieldBloc? selectFieldBlocOf( - String name) => - _singleFieldBlocOf(name); + String path) => + _fieldBlocOf(path); MultiSelectFieldBloc? - multiSelectFieldBlocOf(String name) => - _singleFieldBlocOf(name); + multiSelectFieldBlocOf(String path) => + _fieldBlocOf(path); GroupFieldBloc? groupFieldBlocOf(String name) => _fieldBlocOf(name); ListFieldBloc? listFieldBlocOf( - String name) => - _fieldBlocOf>(name); + String path) => + _fieldBlocOf>(path); /// Returns `true` if the state is /// [FormBlocLoaded] or [FormBlocFailure] or @@ -241,12 +191,12 @@ abstract class FormBlocState } FormBlocState({ - required Map isValidByStep, required this.isEditing, - required Map> fieldBlocs, + required Map fieldBlocs, + required Map fieldStates, required this.currentStep, - }) : _isValidByStep = isValidByStep, - _fieldBlocs = fieldBlocs; + }) : _fieldBlocs = fieldBlocs, + _fieldStates = fieldStates; /// Returns a [FormBlocLoading] /// {@template form_bloc.copy_to_form_bloc_state} @@ -260,9 +210,9 @@ abstract class FormBlocState }) { final state = this; return FormBlocLoading( - isValidByStep: _isValidByStep, isEditing: isEditing, fieldBlocs: _fieldBlocs, + fieldStates: _fieldStates, currentStep: currentStep, progress: progress ?? (state is FormBlocLoading @@ -279,13 +229,13 @@ abstract class FormBlocState {FailureResponse? failureResponse}) { final state = this; return FormBlocLoadFailed( - isValidByStep: _isValidByStep, isEditing: isEditing, failureResponse: failureResponse ?? (state is FormBlocLoadFailed ? state.failureResponse : null), fieldBlocs: _fieldBlocs, + fieldStates: _fieldStates, currentStep: currentStep, ); } @@ -295,9 +245,9 @@ abstract class FormBlocState /// /// {@macro form_bloc.form_state.FormBlocLoaded} FormBlocState toLoaded() => FormBlocLoaded( - isValidByStep: _isValidByStep, isEditing: isEditing, fieldBlocs: _fieldBlocs, + fieldStates: _fieldStates, currentStep: currentStep, ); @@ -310,20 +260,32 @@ abstract class FormBlocState /// /// * If [progress] is less than 0, it will become 0.0 /// * If [progress] is greater than 1, it will become 1.0 - FormBlocState toSubmitting( - {double? progress}) { + FormBlocState toSubmitting({ + bool? isCancelling, + double? progress, + }) { final state = this; return FormBlocSubmitting( - isValidByStep: _isValidByStep, isEditing: isEditing, progress: progress ?? (state is FormBlocSubmitting ? state.progress : 0.0), - isCanceling: state is FormBlocSubmitting - ? state.isCanceling - : false, + isCanceling: isCancelling ?? + (state is FormBlocSubmitting + ? state.isCanceling + : false), fieldBlocs: _fieldBlocs, + fieldStates: _fieldStates, + currentStep: currentStep, + ); + } + + FormBlocState toSubmissionFailed() { + return FormBlocSubmissionFailed( + isEditing: isEditing, + fieldBlocs: _fieldBlocs, + fieldStates: _fieldStates, currentStep: currentStep, ); } @@ -338,12 +300,12 @@ abstract class FormBlocState bool? isEditing, }) { return FormBlocSuccess( - isValidByStep: _isValidByStep, isEditing: isEditing ?? this.isEditing, successResponse: successResponse, canSubmitAgain: currentStep < (numberOfSteps - 1) ? true : (canSubmitAgain ?? false), fieldBlocs: _fieldBlocs, + fieldStates: _fieldStates, currentStep: currentStep < (numberOfSteps - 1) ? currentStep + 1 : currentStep, stepCompleted: currentStep, @@ -356,16 +318,18 @@ abstract class FormBlocState /// {@macro form_bloc.form_state.FormBlocFailure} FormBlocState toFailure({ FailureResponse? failureResponse, + Map? fieldBlocs, }) { final state = this; return FormBlocFailure( - isValidByStep: _isValidByStep, isEditing: isEditing, failureResponse: failureResponse ?? (state is FormBlocFailure ? state.failureResponse : null), - fieldBlocs: _fieldBlocs, + fieldBlocs: fieldBlocs ?? _fieldBlocs, + fieldStates: fieldBlocs?.map((step, fb) => MapEntry(step, fb.state)) ?? + _fieldStates, currentStep: currentStep, ); } @@ -376,12 +340,22 @@ abstract class FormBlocState /// {@macro form_bloc.form_state.FormBlocSubmissionCancelled} FormBlocState toSubmissionCancelled() => FormBlocSubmissionCancelled( - isValidByStep: _isValidByStep, isEditing: isEditing, fieldBlocs: _fieldBlocs, + fieldStates: _fieldStates, currentStep: currentStep, ); + FormBlocState toDeleting() { + return FormBlocDeleting( + isEditing: isEditing, + fieldBlocs: _fieldBlocs, + fieldStates: _fieldStates, + currentStep: currentStep, + progress: 0.0, + ); + } + /// Returns a [FormBlocDeleteFailed] /// {@macro form_bloc.copy_to_form_bloc_state} /// @@ -390,13 +364,13 @@ abstract class FormBlocState {FailureResponse? failureResponse}) { final state = this; return FormBlocDeleteFailed( - isValidByStep: _isValidByStep, isEditing: isEditing, failureResponse: failureResponse ?? (state is FormBlocDeleteFailed ? state.failureResponse : null), fieldBlocs: _fieldBlocs, + fieldStates: _fieldStates, currentStep: currentStep, ); } @@ -409,13 +383,13 @@ abstract class FormBlocState {SuccessResponse? successResponse}) { final state = this; return FormBlocDeleteSuccessful( - isValidByStep: _isValidByStep, isEditing: isEditing, successResponse: successResponse ?? (state is FormBlocDeleteSuccessful ? state.successResponse : null), fieldBlocs: _fieldBlocs, + fieldStates: _fieldStates, currentStep: currentStep, ); } @@ -428,9 +402,9 @@ abstract class FormBlocState {double? progress}) { final state = this; return FormBlocUpdatingFields( - isValidByStep: _isValidByStep, isEditing: isEditing, fieldBlocs: _fieldBlocs, + fieldStates: _fieldStates, currentStep: currentStep, progress: progress ?? (state is FormBlocUpdatingFields @@ -442,108 +416,113 @@ abstract class FormBlocState /// Returns a copy of the current state by changing [currentStep]. FormBlocState _copyWith({ int? currentStep, - Map>? fieldBlocs, - Map? isValidByStep, + Map? fieldBlocs, + Map? fieldStates, }) { final state = this; + final _fieldStates = + fieldBlocs?.map((step, fb) => MapEntry(step, fb.state)) ?? + fieldStates ?? + this._fieldStates; + if (state is FormBlocLoading) { return FormBlocLoading( - isValidByStep: isValidByStep ?? _isValidByStep, isEditing: isEditing, fieldBlocs: fieldBlocs ?? _fieldBlocs, + fieldStates: _fieldStates, currentStep: currentStep ?? this.currentStep, progress: state.progress, ); } else if (state is FormBlocLoadFailed) { return FormBlocLoadFailed( - isValidByStep: isValidByStep ?? _isValidByStep, isEditing: isEditing, failureResponse: state.failureResponse, fieldBlocs: fieldBlocs ?? _fieldBlocs, + fieldStates: _fieldStates, currentStep: currentStep ?? this.currentStep, ); } else if (state is FormBlocLoaded) { return FormBlocLoaded( - isValidByStep: isValidByStep ?? _isValidByStep, isEditing: isEditing, fieldBlocs: fieldBlocs ?? _fieldBlocs, + fieldStates: _fieldStates, currentStep: currentStep ?? this.currentStep, ); } else if (state is FormBlocSubmitting) { return FormBlocSubmitting( - isValidByStep: isValidByStep ?? _isValidByStep, isEditing: isEditing, progress: state.progress, isCanceling: state.isCanceling, fieldBlocs: fieldBlocs ?? _fieldBlocs, + fieldStates: _fieldStates, currentStep: currentStep ?? this.currentStep, ); } else if (state is FormBlocSuccess) { return FormBlocSuccess( - isValidByStep: isValidByStep ?? _isValidByStep, isEditing: isEditing, successResponse: state.successResponse, canSubmitAgain: state.canSubmitAgain, fieldBlocs: fieldBlocs ?? _fieldBlocs, + fieldStates: _fieldStates, currentStep: currentStep ?? this.currentStep, ); } else if (state is FormBlocFailure) { return FormBlocFailure( - isValidByStep: isValidByStep ?? _isValidByStep, isEditing: isEditing, failureResponse: state.failureResponse, fieldBlocs: fieldBlocs ?? _fieldBlocs, + fieldStates: _fieldStates, currentStep: currentStep ?? this.currentStep, ); } else if (state is FormBlocSubmissionFailed) { return FormBlocSubmissionFailed( - isValidByStep: isValidByStep ?? _isValidByStep, isEditing: isEditing, fieldBlocs: fieldBlocs ?? _fieldBlocs, + fieldStates: _fieldStates, currentStep: currentStep ?? this.currentStep, ); } else if (state is FormBlocSubmissionCancelled) { return FormBlocSubmissionCancelled( - isValidByStep: isValidByStep ?? _isValidByStep, isEditing: isEditing, fieldBlocs: fieldBlocs ?? _fieldBlocs, + fieldStates: _fieldStates, currentStep: currentStep ?? this.currentStep, ); } else if (state is FormBlocDeleteSuccessful) { return FormBlocDeleteSuccessful( - isValidByStep: isValidByStep ?? _isValidByStep, isEditing: isEditing, successResponse: state.successResponse, fieldBlocs: fieldBlocs ?? _fieldBlocs, + fieldStates: _fieldStates, currentStep: currentStep ?? this.currentStep, ); } else if (state is FormBlocDeleteFailed) { return FormBlocDeleteFailed( - isValidByStep: isValidByStep ?? _isValidByStep, isEditing: isEditing, failureResponse: state.failureResponse, fieldBlocs: fieldBlocs ?? _fieldBlocs, + fieldStates: _fieldStates, currentStep: currentStep ?? this.currentStep, ); } else if (state is FormBlocDeleting) { return FormBlocDeleting( - isValidByStep: isValidByStep ?? _isValidByStep, isEditing: isEditing, fieldBlocs: fieldBlocs ?? _fieldBlocs, + fieldStates: _fieldStates, currentStep: currentStep ?? this.currentStep, progress: state.progress, ); } else if (state is FormBlocUpdatingFields) { return FormBlocUpdatingFields( - isValidByStep: isValidByStep ?? _isValidByStep, isEditing: isEditing, fieldBlocs: fieldBlocs ?? _fieldBlocs, + fieldStates: _fieldStates, currentStep: currentStep ?? this.currentStep, progress: state.progress, ); @@ -556,27 +535,27 @@ abstract class FormBlocState String toString() => _toStringWith(); String _toStringWith([String? extra]) { - String _allStepsToJson() { - var string = ''; - if (numberOfSteps > 1) { - _fieldBlocs.forEach((key, value) { - string += ',\n step $key - toJson($key): ${toJson(key)}'; - }); - } - - return string; - } - - String _allStepsIsValid() { - var string = ''; - if (numberOfSteps > 1) { - _isValidByStep.forEach((key, value) { - string += ',\n step $key - isValid($key): ${isValid(key)}'; - }); - } - - return string; - } + // String _allStepsToJson() { + // var string = ''; + // if (numberOfSteps > 1) { + // _fieldBlocs.forEach((key, value) { + // string += ',\n step $key - toJson($key): ${toJson(key)}'; + // }); + // } + // + // return string; + // } + + // String _allStepsIsValid() { + // var string = ''; + // if (numberOfSteps > 1) { + // _isValidByStep.forEach((key, value) { + // string += ',\n step $key - isValid($key): ${isValid(key)}'; + // }); + // } + // + // return string; + // } var _toString = '$runtimeType {'; @@ -589,9 +568,9 @@ abstract class FormBlocState _toString += ',\n currentStep: $currentStep'; _toString += ',\n numberOfSteps: $numberOfSteps'; - _toString += _allStepsIsValid(); - _toString += ',\n isValid(): ${isValid()}'; - _toString += _allStepsToJson(); + // _toString += _allStepsIsValid(); + // _toString += ',\n isValid(): ${isValid()}'; + // _toString += _allStepsToJson(); _toString += ',\n toJson(): ${toJson()}'; _toString += ',\n fieldBlocs: $_fieldBlocs'; _toString += '\n}'; @@ -615,25 +594,25 @@ class FormBlocLoading final double progress; FormBlocLoading({ - Map isValidByStep = const {}, bool isEditing = false, - Map> fieldBlocs = const {}, + Map fieldBlocs = const {}, + Map fieldStates = const {}, int currentStep = 0, double progress = 0.0, }) : assert(progress >= 0.0 && progress <= 0.0), progress = progress.clamp(0.0, 1.0), super( - isValidByStep: isValidByStep, isEditing: isEditing, fieldBlocs: fieldBlocs, + fieldStates: fieldStates, currentStep: currentStep, ); @override List get props => [ - _isValidByStep, isEditing, _fieldBlocs, + _fieldStates, currentStep, progress, ]; @@ -661,24 +640,24 @@ class FormBlocLoadFailed bool get hasFailureResponse => failureResponse != null; FormBlocLoadFailed({ - Map isValidByStep = const {}, bool isEditing = false, this.failureResponse, - Map> fieldBlocs = const {}, + Map fieldBlocs = const {}, + Map fieldStates = const {}, int currentStep = 0, }) : super( - isValidByStep: isValidByStep, isEditing: isEditing, fieldBlocs: fieldBlocs, + fieldStates: fieldStates, currentStep: currentStep, ); @override List get props => [ - _isValidByStep, failureResponse, isEditing, _fieldBlocs, + _fieldStates, currentStep, ]; @@ -697,22 +676,22 @@ class FormBlocLoadFailed class FormBlocLoaded extends FormBlocState { FormBlocLoaded({ - Map isValidByStep = const {}, bool isEditing = false, - Map> fieldBlocs = const {}, + Map fieldBlocs = const {}, + Map fieldStates = const {}, int currentStep = 0, }) : super( - isValidByStep: isValidByStep, isEditing: isEditing, fieldBlocs: fieldBlocs, + fieldStates: fieldStates, currentStep: currentStep, ); @override List get props => [ - _isValidByStep, isEditing, _fieldBlocs, + _fieldStates, currentStep, ]; } @@ -737,16 +716,16 @@ class FormBlocSubmitting /// * If [progress] is less than 0, it will become 0.0 /// * If [progress] is greater than 1, it will become 1.0 FormBlocSubmitting({ - Map isValidByStep = const {}, bool isEditing = false, double progress = 0.0, this.isCanceling = false, - Map> fieldBlocs = const {}, + Map fieldBlocs = const {}, + Map fieldStates = const {}, int currentStep = 0, }) : assert(progress >= 0.0 && progress <= 1.0), progress = progress.clamp(0.0, 1.0), super( - isValidByStep: isValidByStep, + fieldStates: fieldStates, isEditing: isEditing, fieldBlocs: fieldBlocs, currentStep: currentStep, @@ -754,11 +733,11 @@ class FormBlocSubmitting @override List get props => [ - _isValidByStep, progress, isCanceling, isEditing, _fieldBlocs, + _fieldStates, currentStep, ]; @@ -788,15 +767,15 @@ class FormBlocSuccess bool get hasSuccessResponse => successResponse != null; FormBlocSuccess({ - Map isValidByStep = const {}, bool isEditing = false, this.successResponse, this.canSubmitAgain = false, - Map> fieldBlocs = const {}, + Map fieldBlocs = const {}, + Map fieldStates = const {}, int currentStep = 0, this.stepCompleted = 0, }) : super( - isValidByStep: isValidByStep, + fieldStates: fieldStates, isEditing: isEditing, fieldBlocs: fieldBlocs, currentStep: currentStep, @@ -804,11 +783,11 @@ class FormBlocSuccess @override List get props => [ - _isValidByStep, successResponse, isEditing, canSubmitAgain, _fieldBlocs, + _fieldStates, currentStep, stepCompleted, ]; @@ -837,24 +816,24 @@ class FormBlocFailure bool get hasFailureResponse => failureResponse != null; FormBlocFailure({ - required Map isValidByStep, bool isEditing = false, this.failureResponse, - Map> fieldBlocs = const {}, + Map fieldBlocs = const {}, + Map fieldStates = const {}, int currentStep = 0, }) : super( - isValidByStep: isValidByStep, isEditing: isEditing, fieldBlocs: fieldBlocs, + fieldStates: fieldStates, currentStep: currentStep, ); @override List get props => [ - _isValidByStep, failureResponse, isEditing, _fieldBlocs, + _fieldStates, currentStep, ]; @@ -875,20 +854,20 @@ class FormBlocFailure class FormBlocSubmissionCancelled extends FormBlocState { FormBlocSubmissionCancelled({ - Map isValidByStep = const {}, bool isEditing = false, - Map> fieldBlocs = const {}, + Map fieldBlocs = const {}, + Map fieldStates = const {}, int currentStep = 0, }) : super( - isValidByStep: isValidByStep, isEditing: isEditing, fieldBlocs: fieldBlocs, + fieldStates: fieldStates, currentStep: currentStep, ); @override List get props => [ - _isValidByStep, + _fieldStates, isEditing, _fieldBlocs, currentStep, @@ -902,12 +881,12 @@ class FormBlocSubmissionCancelled class FormBlocSubmissionFailed extends FormBlocState { FormBlocSubmissionFailed({ - Map isValidByStep = const {}, bool isEditing = false, - Map> fieldBlocs = const {}, + Map fieldBlocs = const {}, + Map fieldStates = const {}, int currentStep = 0, }) : super( - isValidByStep: isValidByStep, + fieldStates: fieldStates, isEditing: isEditing, fieldBlocs: fieldBlocs, currentStep: currentStep, @@ -915,9 +894,9 @@ class FormBlocSubmissionFailed @override List get props => [ - _isValidByStep, isEditing, _fieldBlocs, + _fieldStates, currentStep, ]; } @@ -930,25 +909,25 @@ class FormBlocDeleting final double progress; FormBlocDeleting({ - Map isValidByStep = const {}, bool isEditing = false, - Map> fieldBlocs = const {}, + Map fieldBlocs = const {}, + Map fieldStates = const {}, int currentStep = 0, double progress = 0.0, }) : assert(progress >= 0.0 && progress <= 1.0), progress = progress.clamp(0.0, 1.0), super( - isValidByStep: isValidByStep, isEditing: isEditing, fieldBlocs: fieldBlocs, + fieldStates: fieldStates, currentStep: currentStep, ); @override List get props => [ - _isValidByStep, isEditing, _fieldBlocs, + _fieldStates, currentStep, progress, ]; @@ -976,24 +955,24 @@ class FormBlocDeleteFailed bool get hasFailureResponse => failureResponse != null; FormBlocDeleteFailed({ - Map isValidByStep = const {}, bool isEditing = false, this.failureResponse, - Map> fieldBlocs = const {}, + Map fieldBlocs = const {}, + Map fieldStates = const {}, int currentStep = 0, }) : super( - isValidByStep: isValidByStep, isEditing: isEditing, fieldBlocs: fieldBlocs, + fieldStates: fieldStates, currentStep: currentStep, ); @override List get props => [ - _isValidByStep, failureResponse, isEditing, _fieldBlocs, + _fieldStates, currentStep, ]; @@ -1020,21 +999,21 @@ class FormBlocDeleteSuccessful bool get hasSuccessResponse => successResponse != null; FormBlocDeleteSuccessful({ - Map isValidByStep = const {}, bool isEditing = false, this.successResponse, - Map> fieldBlocs = const {}, + Map fieldBlocs = const {}, + Map fieldStates = const {}, int currentStep = 0, }) : super( - isValidByStep: isValidByStep, isEditing: isEditing, fieldBlocs: fieldBlocs, + fieldStates: fieldStates, currentStep: currentStep, ); @override List get props => [ - _isValidByStep, + _fieldStates, successResponse, isEditing, _fieldBlocs, @@ -1062,25 +1041,25 @@ class FormBlocUpdatingFields final double progress; FormBlocUpdatingFields({ - required Map isValidByStep, bool isEditing = false, - Map> fieldBlocs = const {}, + Map fieldBlocs = const {}, + Map fieldStates = const {}, int currentStep = 0, required double progress, }) : assert(progress >= 0.0 && progress <= 1.0), progress = progress.clamp(0.0, 1.0), super( - isValidByStep: isValidByStep, isEditing: isEditing, fieldBlocs: fieldBlocs, + fieldStates: fieldStates, currentStep: currentStep, ); @override List get props => [ - _isValidByStep, isEditing, _fieldBlocs, + _fieldStates, currentStep, progress, ]; diff --git a/packages/form_bloc/lib/src/blocs/group_field/group_field_bloc.dart b/packages/form_bloc/lib/src/blocs/group_field/group_field_bloc.dart index e7fabc15..d83d72a1 100644 --- a/packages/form_bloc/lib/src/blocs/group_field/group_field_bloc.dart +++ b/packages/form_bloc/lib/src/blocs/group_field/group_field_bloc.dart @@ -3,46 +3,50 @@ part of '../field/field_bloc.dart'; class GroupFieldBlocState extends MultiFieldBlocState { final Map fieldBlocs; + final Map fieldStates; + + @override + late final Map value = + fieldStates.map((name, state) { + return MapEntry(name, state.value); + }); GroupFieldBlocState({ - required FormBloc? formBloc, - required String name, - required bool isValidating, - required bool isValid, + required this.fieldBlocs, + required this.fieldStates, required ExtraData? extraData, - required Iterable fieldBlocs, - }) : fieldBlocs = { - for (final fb in fieldBlocs) fb.state.name: fb, - }, - super( - formBloc: formBloc, - name: name, - isValidating: isValidating, - isValid: isValid, + }) : super( extraData: extraData, ); + @override + Map toJson() { + return fieldStates.map((key, value) { + return MapEntry(key, value.toJson()); + }); + } + @override GroupFieldBlocState copyWith({ - Param? formBloc, - bool? isValidating, - bool? isValid, Param? extraData, - Iterable? fieldBlocs, + Map? fieldBlocs, + Map? fieldStates, }) { return GroupFieldBlocState( - formBloc: formBloc == null ? this.formBloc : formBloc.value, - name: name, - isValidating: isValidating ?? this.isValidating, - isValid: isValid ?? this.isValid, extraData: extraData == null ? this.extraData : extraData.value, - fieldBlocs: fieldBlocs ?? this.fieldBlocs.values, + fieldBlocs: fieldBlocs ?? this.fieldBlocs, + fieldStates: fieldStates ?? + fieldBlocs?.map((step, fb) => MapEntry(step, fb.state)) ?? + this.fieldStates, ); } @override Iterable get flatFieldBlocs => fieldBlocs.values; + @override + Iterable get flatFieldStates => fieldStates.values; + @override List get props => [super.props, fieldBlocs]; @@ -53,21 +57,42 @@ class GroupFieldBlocState class GroupFieldBloc extends MultiFieldBloc> { + late final StreamSubscription _onValidationStatus; + GroupFieldBloc({ - String? name, - List fieldBlocs = const [], + Map fieldBlocs = const {}, + bool autoValidate = true, ExtraData? extraData, - }) : super(GroupFieldBlocState( - name: name ?? Uuid().v1(), - isValid: MultiFieldBloc.areFieldBlocsValid(fieldBlocs), - isValidating: MultiFieldBloc.areFieldBlocsValidating(fieldBlocs), - formBloc: null, - extraData: extraData, - fieldBlocs: fieldBlocs, - )); + }) : super( + GroupFieldBlocState( + extraData: extraData, + fieldBlocs: fieldBlocs, + fieldStates: + fieldBlocs.map((name, fb) => MapEntry(name, fb.state)), + ), + autoValidate: autoValidate) { + _onValidationStatus = stream + .map((event) => event.fieldBlocs) + .distinct(const MapEquality().equals) + .switchMap((fieldBlocs) { + return Rx.combineLatestList(fieldBlocs.entries.map((entry) { + return entry.value.hotStream.map((state) => MapEntry(entry.key, state)); + })).skip(1); + }).listen((fieldStates) { + final effectiveFieldStates = Map.fromEntries(fieldStates); + + emit(state.copyWith( + fieldStates: effectiveFieldStates, + )); + }); + } @override - String toString() { - return '$runtimeType'; + Future close() async { + await _onValidationStatus.cancel(); + return super.close(); } + + @override + String toString() => '$runtimeType'; } diff --git a/packages/form_bloc/lib/src/blocs/input_field/input_field_bloc.dart b/packages/form_bloc/lib/src/blocs/input_field/input_field_bloc.dart index 5d5d24ae..784a7a84 100644 --- a/packages/form_bloc/lib/src/blocs/input_field/input_field_bloc.dart +++ b/packages/form_bloc/lib/src/blocs/input_field/input_field_bloc.dart @@ -1,8 +1,8 @@ part of '../field/field_bloc.dart'; /// A `FieldBloc` used for any type, for example `DateTime` or `File`. -class InputFieldBloc extends SingleFieldBloc, ExtraData?> { +class InputFieldBloc + extends SingleFieldBloc, ExtraData?> { /// ## InputFieldBloc /// /// ### Properties: @@ -37,7 +37,6 @@ class InputFieldBloc extends SingleFieldBloc>? validators, List>? asyncValidators, @@ -72,7 +71,6 @@ class InputFieldBloc extends SingleFieldBloc - extends FieldBlocState { +class InputFieldBlocState extends FieldBlocState { InputFieldBlocState({ required bool isValueChanged, required Value initialValue, @@ -12,8 +11,6 @@ class InputFieldBlocState required Suggestions? suggestions, required bool isValidated, required bool isValidating, - FormBloc? formBloc, - required String name, List additionalProps = const [], dynamic Function(Value value)? toJson, ExtraData? extraData, @@ -27,8 +24,6 @@ class InputFieldBlocState suggestions: suggestions, isValidated: isValidated, isValidating: isValidating, - formBloc: formBloc, - name: name, toJson: toJson, extraData: extraData, ); @@ -44,7 +39,6 @@ class InputFieldBlocState Param?>? suggestions, bool? isValidated, bool? isValidating, - Param? formBloc, Param? extraData, }) { return InputFieldBlocState( @@ -57,8 +51,6 @@ class InputFieldBlocState suggestions: suggestions == null ? this.suggestions : suggestions.value, isValidated: isValidated ?? this.isValidated, isValidating: isValidating ?? this.isValidating, - formBloc: formBloc == null ? this.formBloc : formBloc.value, - name: name, toJson: _toJson, extraData: extraData == null ? this.extraData : extraData.value, ); diff --git a/packages/form_bloc/lib/src/blocs/list_field/list_field_bloc.dart b/packages/form_bloc/lib/src/blocs/list_field/list_field_bloc.dart index 0f9bfe90..97abd009 100644 --- a/packages/form_bloc/lib/src/blocs/list_field/list_field_bloc.dart +++ b/packages/form_bloc/lib/src/blocs/list_field/list_field_bloc.dart @@ -3,43 +3,46 @@ part of '../field/field_bloc.dart'; class ListFieldBlocState extends MultiFieldBlocState { final List fieldBlocs; + final List fieldStates; + + @override + late final List value = fieldStates.map((state) { + return state.value; + }).toList(); ListFieldBlocState({ - required FormBloc? formBloc, - required String name, - required bool isValidating, - required bool isValid, required ExtraData? extraData, required this.fieldBlocs, + required this.fieldStates, }) : super( - formBloc: formBloc, - name: name, - isValidating: isValidating, - isValid: isValid, extraData: extraData, ); + @override + List toJson() => + fieldStates.map((e) => e.toJson()).toList(); + @override ListFieldBlocState copyWith({ - Param? formBloc, - bool? isValidating, - bool? isValid, Param? extraData, List? fieldBlocs, + List? fieldStates, }) { return ListFieldBlocState( - formBloc: formBloc == null ? this.formBloc : formBloc.value, - name: name, - isValidating: isValidating ?? this.isValidating, - isValid: isValid ?? this.isValid, extraData: extraData == null ? this.extraData : extraData.value, fieldBlocs: fieldBlocs ?? this.fieldBlocs, + fieldStates: fieldBlocs?.map((e) => e.state).toList() ?? + fieldStates ?? + this.fieldStates, ); } @override Iterable get flatFieldBlocs => fieldBlocs; + @override + Iterable get flatFieldStates => fieldStates; + @override List get props => [super.props, fieldBlocs]; @@ -50,18 +53,30 @@ class ListFieldBlocState class ListFieldBloc extends MultiFieldBloc> { + late final StreamSubscription _onValidationStatus; + ListFieldBloc({ - String? name, List fieldBlocs = const [], + bool autoValidate = true, ExtraData? extraData, - }) : super(ListFieldBlocState( - name: name ?? Uuid().v1(), - formBloc: null, - isValidating: MultiFieldBloc.areFieldBlocsValidating(fieldBlocs), - isValid: MultiFieldBloc.areFieldBlocsValid(fieldBlocs), - extraData: extraData, - fieldBlocs: fieldBlocs, - )); + }) : super( + ListFieldBlocState( + extraData: extraData, + fieldBlocs: fieldBlocs, + fieldStates: fieldBlocs.map((e) => e.state).toList(), + ), + autoValidate: autoValidate) { + _onValidationStatus = stream + .map((event) => event.fieldBlocs) + .distinct(const ListEquality().equals) + .switchMap((fieldBlocs) { + return Rx.combineLatestList(fieldBlocs.map((fb) => fb.hotStream)).skip(1); + }).listen((fieldStates) { + emit(state.copyWith( + fieldStates: fieldStates, + )); + }); + } List get value => state.fieldBlocs; @@ -69,21 +84,17 @@ class ListFieldBloc void addFieldBloc(T fieldBloc) => addFieldBlocs([fieldBloc]); /// Add [FieldBloc]s. - void addFieldBlocs(List fieldBlocs) { + void addFieldBlocs(List fieldBlocs, {bool inherit = true}) { if (fieldBlocs.isNotEmpty) { final nextFieldBlocs = [...state.fieldBlocs, ...fieldBlocs]; + for (final fieldBloc in fieldBlocs) { + fieldBloc.updateAutoValidation(_autoValidate); + } + emit(state.copyWith( - isValidating: MultiFieldBloc.areFieldBlocsValidating(nextFieldBlocs), - isValid: MultiFieldBloc.areFieldBlocsValid(nextFieldBlocs), fieldBlocs: nextFieldBlocs, )); - - FormBlocUtils.updateFormBloc( - fieldBlocs: fieldBlocs, - formBloc: state.formBloc, - autoValidate: _autoValidate, - ); } } @@ -91,18 +102,11 @@ class ListFieldBloc void removeFieldBlocAt(int index) { if (state.fieldBlocs.length > index) { final nextFieldBlocs = [...state.fieldBlocs]; - final fieldBlocRemoved = nextFieldBlocs.removeAt(index); + nextFieldBlocs.removeAt(index); emit(state.copyWith( - isValidating: MultiFieldBloc.areFieldBlocsValidating(nextFieldBlocs), - isValid: MultiFieldBloc.areFieldBlocsValid(nextFieldBlocs), fieldBlocs: nextFieldBlocs, )); - - FormBlocUtils.removeFormBloc( - fieldBlocs: [fieldBlocRemoved], - formBloc: state.formBloc, - ); } } @@ -128,15 +132,8 @@ class ListFieldBloc if (fieldBlocsRemoved.isEmpty) return; emit(state.copyWith( - isValidating: MultiFieldBloc.areFieldBlocsValidating(nextFieldBlocs), - isValid: MultiFieldBloc.areFieldBlocsValid(nextFieldBlocs), fieldBlocs: nextFieldBlocs, )); - - FormBlocUtils.removeFormBloc( - fieldBlocs: fieldBlocsRemoved, - formBloc: state.formBloc, - ); } /// Insert [FieldBloc] into index. @@ -150,46 +147,34 @@ class ListFieldBloc nextFieldBlocs.insertAll(index, fieldBlocs); + for (final fieldBloc in fieldBlocs) { + fieldBloc.updateAutoValidation(_autoValidate); + } + emit(state.copyWith( - isValidating: MultiFieldBloc.areFieldBlocsValidating(nextFieldBlocs), - isValid: MultiFieldBloc.areFieldBlocsValid(nextFieldBlocs), fieldBlocs: nextFieldBlocs, )); - - FormBlocUtils.updateFormBloc( - fieldBlocs: fieldBlocs, - formBloc: state.formBloc, - autoValidate: _autoValidate, - ); } } /// Updates [FieldBloc]s. void updateFieldBlocs(List fieldBlocs) { - final previousFieldBlocs = [...state.fieldBlocs]; final nextFieldBlocs = [...fieldBlocs]; - FormBlocUtils.removeFormBloc( - fieldBlocs: previousFieldBlocs, - formBloc: state.formBloc, - ); - emit(state.copyWith( - isValidating: MultiFieldBloc.areFieldBlocsValidating(nextFieldBlocs), - isValid: MultiFieldBloc.areFieldBlocsValid(nextFieldBlocs), fieldBlocs: nextFieldBlocs, )); - - FormBlocUtils.updateFormBloc( - fieldBlocs: fieldBlocs, - formBloc: state.formBloc, - autoValidate: _autoValidate, - ); } /// Removes all [FieldBloc]s. void clearFieldBlocs() => removeFieldBlocsWhere((element) => true); + @override + Future close() async { + await _onValidationStatus.cancel(); + return super.close(); + } + @override String toString() => '$runtimeType'; } diff --git a/packages/form_bloc/lib/src/blocs/multi_select_field/multi_select_field_bloc.dart b/packages/form_bloc/lib/src/blocs/multi_select_field/multi_select_field_bloc.dart index 48286552..060464bd 100644 --- a/packages/form_bloc/lib/src/blocs/multi_select_field/multi_select_field_bloc.dart +++ b/packages/form_bloc/lib/src/blocs/multi_select_field/multi_select_field_bloc.dart @@ -2,11 +2,8 @@ part of '../field/field_bloc.dart'; /// A `FieldBloc` used to select multiple items /// from multiple items. -class MultiSelectFieldBloc extends SingleFieldBloc< - List, - Value, - MultiSelectFieldBlocState, - ExtraData?> { +class MultiSelectFieldBloc extends SingleFieldBloc, Value, + MultiSelectFieldBlocState, ExtraData?> { /// ## MultiSelectFieldBloc /// /// ### Properties: @@ -43,7 +40,6 @@ class MultiSelectFieldBloc extends SingleFieldBloc< /// This method is called when you use [FormBlocState.toJson]… /// * [extraData] : It is an object that you can use to add extra data, it will be available in the state [FieldBlocState.extraData]. MultiSelectFieldBloc({ - String? name, List initialValue = const [], List>>? validators, List>>? asyncValidators, @@ -80,7 +76,6 @@ class MultiSelectFieldBloc extends SingleFieldBloc< validators: validators, value: initialValue, ), - name: FieldBlocUtils.generateName(name), items: SingleFieldBloc._itemsWithoutDuplicates(items), toJson: toJson, extraData: extraData, @@ -158,12 +153,10 @@ class MultiSelectFieldBloc extends SingleFieldBloc< /// {@macro form_bloc.field_bloc.update_value} void select(Value valueToSelect) { var newValue = state.value; - newValue = - SingleFieldBloc._itemsWithoutDuplicates([...newValue, valueToSelect]); + newValue = SingleFieldBloc._itemsWithoutDuplicates([...newValue, valueToSelect]); if (_canUpdateValue(value: newValue, isInitialValue: false)) { final error = _getError(value: newValue); - final isValidating = - _getAsyncValidatorsError(value: newValue, error: error); + final isValidating = _getAsyncValidatorsError(value: newValue, error: error); emit(state.copyWith( isValueChanged: true, @@ -186,8 +179,7 @@ class MultiSelectFieldBloc extends SingleFieldBloc< if (_canUpdateValue(value: newValue, isInitialValue: false)) { final error = _getError(value: newValue); - final isValidating = - _getAsyncValidatorsError(value: newValue, error: error); + final isValidating = _getAsyncValidatorsError(value: newValue, error: error); emit(state.copyWith( isValueChanged: true, diff --git a/packages/form_bloc/lib/src/blocs/multi_select_field/multi_select_field_state.dart b/packages/form_bloc/lib/src/blocs/multi_select_field/multi_select_field_state.dart index ab147935..94ebc455 100644 --- a/packages/form_bloc/lib/src/blocs/multi_select_field/multi_select_field_state.dart +++ b/packages/form_bloc/lib/src/blocs/multi_select_field/multi_select_field_state.dart @@ -14,8 +14,6 @@ class MultiSelectFieldBlocState required Suggestions? suggestions, required bool isValidated, required bool isValidating, - FormBloc? formBloc, - required String name, this.items = const [], dynamic Function(List value)? toJson, ExtraData? extraData, @@ -29,8 +27,6 @@ class MultiSelectFieldBlocState suggestions: suggestions, isValidated: isValidated, isValidating: isValidating, - formBloc: formBloc, - name: name, toJson: toJson, extraData: extraData, ); @@ -46,7 +42,6 @@ class MultiSelectFieldBlocState Param?>? suggestions, bool? isValidated, bool? isValidating, - Param? formBloc, List? items, Param? extraData, }) { @@ -60,8 +55,6 @@ class MultiSelectFieldBlocState suggestions: suggestions == null ? this.suggestions : suggestions.value, isValidated: isValidated ?? this.isValidated, isValidating: isValidating ?? this.isValidating, - formBloc: formBloc == null ? this.formBloc : formBloc.value, - name: name, items: items ?? this.items, toJson: _toJson, extraData: extraData == null ? this.extraData : extraData.value, @@ -69,8 +62,7 @@ class MultiSelectFieldBlocState } @override - String toString([String extra = '']) => - super.toString(',\n items: $items$extra'); + String toString([String extra = '']) => super.toString(',\n items: $items$extra'); @override List get props => [super.props, items]; diff --git a/packages/form_bloc/lib/src/blocs/select_field/select_field_bloc.dart b/packages/form_bloc/lib/src/blocs/select_field/select_field_bloc.dart index 403250bf..cdfbb385 100644 --- a/packages/form_bloc/lib/src/blocs/select_field/select_field_bloc.dart +++ b/packages/form_bloc/lib/src/blocs/select_field/select_field_bloc.dart @@ -2,8 +2,8 @@ part of '../field/field_bloc.dart'; /// A `FieldBloc` used to select one item /// from multiple items. -class SelectFieldBloc extends SingleFieldBloc, ExtraData?> { +class SelectFieldBloc + extends SingleFieldBloc, ExtraData?> { /// ## SelectFieldBloc /// /// ### Properties: @@ -39,7 +39,6 @@ class SelectFieldBloc extends SingleFieldBloc>? validators, List>? asyncValidators, @@ -75,7 +74,6 @@ class SelectFieldBloc extends SingleFieldBloc - extends FieldBlocState { +class SelectFieldBlocState extends FieldBlocState { final List items; SelectFieldBlocState({ @@ -14,8 +13,6 @@ class SelectFieldBlocState required Suggestions? suggestions, required bool isValidated, required bool isValidating, - FormBloc? formBloc, - required String name, this.items = const [], dynamic Function(Value? value)? toJson, ExtraData? extraData, @@ -29,8 +26,6 @@ class SelectFieldBlocState suggestions: suggestions, isValidated: isValidated, isValidating: isValidating, - formBloc: formBloc, - name: name, toJson: toJson, extraData: extraData, ); @@ -46,7 +41,6 @@ class SelectFieldBlocState Param?>? suggestions, bool? isValidated, bool? isValidating, - Param? formBloc, List? items, Param? extraData, }) { @@ -60,8 +54,6 @@ class SelectFieldBlocState suggestions: suggestions == null ? this.suggestions : suggestions.value, isValidated: isValidated ?? this.isValidated, isValidating: isValidating ?? this.isValidating, - formBloc: formBloc == null ? this.formBloc : formBloc.value, - name: name, items: items ?? this.items, toJson: _toJson, extraData: extraData == null ? this.extraData : extraData.value, @@ -69,8 +61,7 @@ class SelectFieldBlocState } @override - String toString([String extra = '']) => - super.toString(',\n items: $items$extra'); + String toString([String extra = '']) => super.toString(',\n items: $items$extra'); @override List get props => [super.props, items]; diff --git a/packages/form_bloc/lib/src/blocs/text_field/text_field_bloc.dart b/packages/form_bloc/lib/src/blocs/text_field/text_field_bloc.dart index d6c2b43d..8a14a788 100644 --- a/packages/form_bloc/lib/src/blocs/text_field/text_field_bloc.dart +++ b/packages/form_bloc/lib/src/blocs/text_field/text_field_bloc.dart @@ -4,8 +4,8 @@ part of '../field/field_bloc.dart'; /// it is also used to obtain `int` and `double` values /// ​​of texts thanks to the methods /// [valueToInt] and [valueToDouble]. -class TextFieldBloc extends SingleFieldBloc, ExtraData?> { +class TextFieldBloc + extends SingleFieldBloc, ExtraData?> { /// ## TextFieldBloc /// /// ### Properties: @@ -38,7 +38,6 @@ class TextFieldBloc extends SingleFieldBloc>? validators, List>? asyncValidators, @@ -72,7 +71,6 @@ class TextFieldBloc extends SingleFieldBloc value, extraData: extraData, ), diff --git a/packages/form_bloc/lib/src/blocs/text_field/text_field_state.dart b/packages/form_bloc/lib/src/blocs/text_field/text_field_state.dart index 8c3c8d29..13d64aa5 100644 --- a/packages/form_bloc/lib/src/blocs/text_field/text_field_state.dart +++ b/packages/form_bloc/lib/src/blocs/text_field/text_field_state.dart @@ -1,7 +1,6 @@ part of '../field/field_bloc.dart'; -class TextFieldBlocState - extends FieldBlocState { +class TextFieldBlocState extends FieldBlocState { TextFieldBlocState({ required bool isValueChanged, required String initialValue, @@ -12,8 +11,6 @@ class TextFieldBlocState required Suggestions? suggestions, required bool isValidated, required bool isValidating, - FormBloc? formBloc, - required String name, dynamic Function(String value)? toJson, ExtraData? extraData, }) : super( @@ -26,8 +23,6 @@ class TextFieldBlocState suggestions: suggestions, isValidated: isValidated, isValidating: isValidating, - formBloc: formBloc, - name: name, toJson: toJson, extraData: extraData, ); @@ -53,7 +48,6 @@ class TextFieldBlocState Param?>? suggestions, bool? isValidated, bool? isValidating, - Param? formBloc, Param? extraData, }) { return TextFieldBlocState( @@ -66,8 +60,6 @@ class TextFieldBlocState suggestions: suggestions == null ? this.suggestions : suggestions.value, isValidated: isValidated ?? this.isValidated, isValidating: isValidating ?? this.isValidating, - formBloc: formBloc == null ? this.formBloc : formBloc.value, - name: name, toJson: _toJson, extraData: extraData == null ? this.extraData : extraData.value, ); diff --git a/packages/form_bloc/lib/src/extension/extension.dart b/packages/form_bloc/lib/src/extension/extension.dart index b8c56cf2..ec607cd2 100644 --- a/packages/form_bloc/lib/src/extension/extension.dart +++ b/packages/form_bloc/lib/src/extension/extension.dart @@ -1,3 +1,16 @@ +import 'package:bloc/bloc.dart'; +import 'package:rxdart/rxdart.dart'; + +extension RemoveAllListExtension on List { + void removeAll(Iterable elements) => elements.forEach(remove); +} + +extension WhereMapExtension on Map { + Map where(bool Function(K key, V value) predicate) { + return Map.fromEntries(entries.where((entry) => predicate(entry.key, entry.value))); + } +} + extension StreamExtension on Stream { Future firstWhereOrNull(bool Function(T element) test) async { try { @@ -8,6 +21,6 @@ extension StreamExtension on Stream { } } -extension RemoveAllListExtension on List { - void removeAll(Iterable elements) => elements.forEach(remove); +extension HotStreamBloc on BlocBase { + Stream get hotStream => Rx.merge([Stream.value(state), stream]); } diff --git a/packages/form_bloc/test/boolean_field/boolean_field_bloc_test.dart b/packages/form_bloc/test/boolean_field/boolean_field_bloc_test.dart index 76663e6e..7d13eaad 100644 --- a/packages/form_bloc/test/boolean_field/boolean_field_bloc_test.dart +++ b/packages/form_bloc/test/boolean_field/boolean_field_bloc_test.dart @@ -12,7 +12,6 @@ void main() { final validators = [(bool? value) => value! ? 'error' : null]; final fieldBloc = BooleanFieldBloc( - name: 'name', initialValue: false, validators: validators, suggestions: suggestions, @@ -28,7 +27,6 @@ void main() { suggestions: suggestions, isValidated: true, isValidating: false, - name: 'name', ); final state2 = state1.copyWith( updatedValue: Param(true), @@ -52,9 +50,7 @@ void main() { BooleanFieldBloc fieldBloc; BooleanFieldBlocState initialState; - fieldBloc = BooleanFieldBloc( - name: 'name', - ); + fieldBloc = BooleanFieldBloc(); initialState = createBooleanState( value: false, @@ -74,7 +70,6 @@ void main() { fieldBloc.close(); fieldBloc = BooleanFieldBloc( - name: 'name', initialValue: true, validators: [FieldBlocValidators.required, (value) => 'error'], ); @@ -99,7 +94,7 @@ void main() { BooleanFieldBloc fieldBloc; BooleanFieldBlocState initialState; - fieldBloc = BooleanFieldBloc(name: 'name'); + fieldBloc = BooleanFieldBloc(); initialState = createBooleanState( value: false, diff --git a/packages/form_bloc/test/boolean_field/boolean_field_state_test.dart b/packages/form_bloc/test/boolean_field/boolean_field_state_test.dart index 2a91fe03..8a4d4149 100644 --- a/packages/form_bloc/test/boolean_field/boolean_field_state_test.dart +++ b/packages/form_bloc/test/boolean_field/boolean_field_state_test.dart @@ -20,7 +20,6 @@ void main() { isDirty: false, isValidated: false, isValidating: false, - name: 'fieldName', ); expectState( @@ -45,7 +44,6 @@ void main() { isDirty: true, isValidated: true, isValidating: true, - name: 'fieldName', ), ); }); @@ -61,7 +59,6 @@ void main() { isDirty: true, isValidated: true, isValidating: true, - name: 'fieldName', ); expectState( @@ -86,7 +83,6 @@ void main() { isDirty: false, isValidated: false, isValidating: false, - name: 'fieldName', ), ); }); @@ -102,7 +98,6 @@ void main() { isDirty: true, isValidated: true, isValidating: true, - name: 'fieldName', ); expectState( diff --git a/packages/form_bloc/test/field_bloc/field_bloc_test.dart b/packages/form_bloc/test/field_bloc/field_bloc_test.dart index 59c8f507..1f7ed28f 100644 --- a/packages/form_bloc/test/field_bloc/field_bloc_test.dart +++ b/packages/form_bloc/test/field_bloc/field_bloc_test.dart @@ -22,7 +22,6 @@ void main() { InputFieldBlocState initialState; fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: null, ); initialState = InputFieldBlocState( @@ -35,7 +34,6 @@ void main() { suggestions: null, isValidated: true, isValidating: false, - name: 'fieldName', ); expect( @@ -45,7 +43,6 @@ void main() { fieldBloc.close(); fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: 1, ); initialState = InputFieldBlocState( @@ -58,7 +55,6 @@ void main() { suggestions: null, isValidated: true, isValidating: false, - name: 'fieldName', ); expect( @@ -70,7 +66,6 @@ void main() { test('validators verify the value and add the corresponding error.', () async { final fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: null, validators: [ FieldBlocValidators.required, @@ -129,7 +124,6 @@ void main() { test('asyncValidators verify the value and add the corresponding error.', () async { final fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: null, asyncValidators: [ (value) async { @@ -180,7 +174,6 @@ void main() { () async { Future> suggestions(String pattern) async => [1, 2, 3]; final fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: null, suggestions: suggestions, ); @@ -207,22 +200,10 @@ void main() { [1, 2, 3], ); }); - test('_name is added to the current state', () async { - final fieldBloc = InputFieldBloc( - name: 'fieldName', - initialValue: null, - ); - - expect( - 'fieldName', - fieldBloc.state.name, - ); - }); }); test('initial state has isInitial in true.', () { final fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: null, ); @@ -244,7 +225,6 @@ void main() { test('changeValue.', () async { final fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: null, validators: [FieldBlocValidators.required], ); @@ -293,7 +273,6 @@ void main() { test('updateValue method.', () { final fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: null, validators: [FieldBlocValidators.required], ); @@ -338,7 +317,6 @@ void main() { test('updateValue method with isRequired false', () { final fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: null, ); @@ -382,7 +360,6 @@ void main() { test('updateInitialValue.', () { final fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: null, validators: [FieldBlocValidators.required], ); @@ -432,7 +409,6 @@ void main() { test('clear method set value to initialValue.', () async { final fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: null, validators: [FieldBlocValidators.required], ); @@ -468,7 +444,6 @@ void main() { 'selectSuggestion method SelectSuggestion event and selectedSuggestion stream.', () { final fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: null, ); @@ -487,7 +462,6 @@ void main() { group('maybeValidate', () { test('auto validation', () async { final fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: null, ); var state = fieldBloc.state; @@ -506,7 +480,6 @@ void main() { test('force validation', () async { final fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: null, ); fieldBloc.updateFormBloc(formBloc, autoValidate: false); @@ -532,7 +505,6 @@ void main() { group('addValidators', () { test('add validators and emit error', () async { final fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: 1, validators: [FieldBlocValidators.required], ); @@ -574,7 +546,6 @@ void main() { group('addAsyncValidators', () { test('Add async validators and emit error', () async { final fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: null, asyncValidatorDebounceTime: const Duration(), ); @@ -603,7 +574,6 @@ void main() { test('updateValidators.', () { final fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: 1, validators: [(value) => value == 1 ? '1 error' : null], ); @@ -649,7 +619,6 @@ void main() { test('updateAsyncValidators method.', () async { final fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: 1, asyncValidatorDebounceTime: Duration(milliseconds: 0), validators: [FieldBlocValidators.required], @@ -733,7 +702,6 @@ void main() { group('removeValidators', () { test('remove validators and not emit error', () async { final fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: null, validators: [FieldBlocValidators.required], ); @@ -762,7 +730,6 @@ void main() { value == null ? '1 error' : null; final fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: null, asyncValidatorDebounceTime: const Duration(), asyncValidators: [validator], @@ -795,7 +762,6 @@ void main() { Future> suggestions2(String pattern) async => [2]; final fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: null, suggestions: suggestions1, ); @@ -835,7 +801,6 @@ void main() { 'after DisableFieldBlocAutoValidate event was dispatched, updateValue method updates the value without verify the value in validators and asyncValidators.', () async { final fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: null, validators: [ FieldBlocValidators.required, @@ -909,7 +874,6 @@ void main() { test('ResetFieldBlocStateIsValidated event.', () { final fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: null, ); @@ -940,7 +904,6 @@ void main() { test('UpdateFieldBlocStateError event.', () { final fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: null, ); @@ -973,11 +936,9 @@ void main() { test('on subscribeToFieldBlocs method and SubscribeToFieldBlocs event.', () async { final fieldBloc1 = InputFieldBloc( - name: 'fieldName', initialValue: null, ); final fieldBloc2 = InputFieldBloc( - name: 'fieldName2', initialValue: null, ); @@ -1058,7 +1019,6 @@ void main() { group('addError', () { test('addError method and with isPermanent false.', () async { final fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: null, ); @@ -1102,7 +1062,6 @@ void main() { test('addError method and with isPermanent true.', () async { final fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: 1, ); @@ -1146,7 +1105,6 @@ void main() { () async { final fieldBloc = InputFieldBloc( initialValue: 1, - name: 'fieldName', ); final state1 = createInputState( @@ -1195,7 +1153,6 @@ void main() { test('addError method isPermanent false after disable auto validation.', () async { final fieldBloc = InputFieldBloc( - name: 'fieldName', initialValue: null, ); diff --git a/packages/form_bloc/test/field_bloc/multi_field_bloc_test.dart b/packages/form_bloc/test/field_bloc/multi_field_bloc_test.dart index 95a020fe..3936333b 100644 --- a/packages/form_bloc/test/field_bloc/multi_field_bloc_test.dart +++ b/packages/form_bloc/test/field_bloc/multi_field_bloc_test.dart @@ -13,9 +13,7 @@ void main() { group('MultiFieldBloc:', () { group('validate', () { test('Success empty validation', () async { - final multiField = ListFieldBloc, String>( - name: 'list', - ); + final multiField = ListFieldBloc, String>(); final expected = createListState, String>( name: 'list', @@ -27,10 +25,9 @@ void main() { }); test('Success validation', () async { - final field = BooleanFieldBloc(name: 'bool'); + final field = BooleanFieldBloc(); final multiField = ListFieldBloc, String>( - name: 'list', fieldBlocs: [field], ); @@ -46,12 +43,10 @@ void main() { test('Failed validation', () async { final field = BooleanFieldBloc( - name: 'bool', validators: [FieldBlocValidators.required], ); final multiField = ListFieldBloc, String>( - name: 'list', fieldBlocs: [field], ); @@ -68,9 +63,7 @@ void main() { group('updateExtraData', () { test('update', () { - final list = ListFieldBloc, String>( - name: 'list', - ); + final list = ListFieldBloc, String>(); final expected = createListState, String>( name: 'list', @@ -90,9 +83,7 @@ void main() { final formBloc = _FakeFormBloc(); test('Success update and remove formBloc', () { - final list = ListFieldBloc, String>( - name: 'list', - ); + final list = ListFieldBloc, String>(); final expectedUpdate = createListState, String>( @@ -112,9 +103,8 @@ void main() { }); test('Success update the fieldBlocs with the new FormBloc', () { - final field = BooleanFieldBloc(name: 'bool'); + final field = BooleanFieldBloc(); final list = ListFieldBloc, String>( - name: 'list', fieldBlocs: [field], ); @@ -151,9 +141,8 @@ void main() { }); test('Failure to remove formBloc because it is not theirs', () { - final field = BooleanFieldBloc(name: 'bool'); + final field = BooleanFieldBloc(); final list = ListFieldBloc, String>( - name: 'list', fieldBlocs: [field], ); diff --git a/packages/form_bloc/test/form_bloc/form_bloc_test.dart b/packages/form_bloc/test/form_bloc/form_bloc_test.dart index 087b5a64..06a4d53e 100644 --- a/packages/form_bloc/test/form_bloc/form_bloc_test.dart +++ b/packages/form_bloc/test/form_bloc/form_bloc_test.dart @@ -66,7 +66,7 @@ void main() { test('check fieldBloc was added with valid formBloc state', () async { final formBloc = _FormBlocImpl(); - formBloc.addFieldBloc(fieldBloc: formBloc.optionalField); + formBloc.addStep(fieldBloc: formBloc.optionalField); expect( formBloc.state, @@ -82,7 +82,7 @@ void main() { test('fieldBloc was added with invalid formBloc state', () async { final formBloc = _FormBlocImpl(); - formBloc.addFieldBloc(fieldBloc: formBloc.requiredField); + formBloc.addStep(fieldBloc: formBloc.requiredField); expect( formBloc.state, @@ -112,7 +112,7 @@ void main() { ), ); - formBloc.addFieldBloc(fieldBloc: fieldBloc); + formBloc.addStep(fieldBloc: fieldBloc); verify(() => fieldBloc.updateFormBloc(formBloc, autoValidate: true)); }); @@ -122,8 +122,8 @@ void main() { test('fieldBloc and step was removed from formBloc', () async { final formBloc = _FormBlocImpl(); - formBloc.addFieldBloc(fieldBloc: formBloc.requiredField); - formBloc.removeFieldBloc(fieldBloc: formBloc.requiredField); + formBloc.addStep(fieldBloc: formBloc.requiredField); + formBloc.removeStep(fieldBloc: formBloc.requiredField); expect( formBloc.state, @@ -140,7 +140,7 @@ void main() { formBloc.addFieldBlocs( fieldBlocs: [formBloc.requiredField, formBloc.optionalField], ); - formBloc.removeFieldBloc(fieldBloc: formBloc.requiredField); + formBloc.removeStep(fieldBloc: formBloc.requiredField); expect( formBloc.state, @@ -170,8 +170,8 @@ void main() { ), ); - formBloc.addFieldBloc(fieldBloc: fieldBloc); - formBloc.removeFieldBloc(fieldBloc: fieldBloc); + formBloc.addStep(fieldBloc: fieldBloc); + formBloc.removeStep(fieldBloc: fieldBloc); verify(() => fieldBloc.removeFormBloc(formBloc)); }); @@ -213,7 +213,7 @@ void main() { expect(formBloc.stream, emitsInOrder(expectedStates)); - formBloc.addFieldBloc(fieldBloc: formBloc.requiredField); + formBloc.addStep(fieldBloc: formBloc.requiredField); formBloc.requiredField.updateValue('x'); }); @@ -254,7 +254,7 @@ void main() { await expectBloc( formBloc, act: () { - formBloc.addFieldBloc(fieldBloc: formBloc.requiredField); + formBloc.addStep(fieldBloc: formBloc.requiredField); formBloc.submit(); }, stream: expectedStates, @@ -294,7 +294,7 @@ void main() { await expectBloc( formBloc, act: () { - formBloc.addFieldBloc(fieldBloc: formBloc.optionalField); + formBloc.addStep(fieldBloc: formBloc.optionalField); formBloc.submit(); }, stream: expectedStates, @@ -357,7 +357,7 @@ void main() { expect(formBloc.stream, emitsInOrder(expectedStates)); - formBloc.addFieldBloc(fieldBloc: formBloc.optionalField); + formBloc.addStep(fieldBloc: formBloc.optionalField); formBloc.submit(); }); @@ -459,7 +459,7 @@ void main() { initialState: initialState, ); - formBloc.addFieldBloc(fieldBloc: fieldBloc); + formBloc.addStep(fieldBloc: fieldBloc); formBloc.clear(); verify(() => fieldBloc.clear()); @@ -520,7 +520,7 @@ void main() { expect(formBloc.stream, emitsInOrder(expectedStates)); - formBloc.addFieldBloc(fieldBloc: formBloc.optionalField); + formBloc.addStep(fieldBloc: formBloc.optionalField); formBloc.cancelSubmission(); formBloc.submit(); diff --git a/packages/form_bloc/test/form_bloc/form_bloc_utils_test.dart b/packages/form_bloc/test/form_bloc/form_bloc_utils_test.dart index 9a4c29ef..c0ab842d 100644 --- a/packages/form_bloc/test/form_bloc/form_bloc_utils_test.dart +++ b/packages/form_bloc/test/form_bloc/form_bloc_utils_test.dart @@ -6,14 +6,13 @@ import '../utils/states.dart'; class GroupFieldBlocImpl extends GroupFieldBloc { GroupFieldBlocImpl({ - required String name, - required List fieldBlocs, + required Map fieldBlocs, required dynamic extraData, - }) : super(fieldBlocs: fieldBlocs, name: name, extraData: extraData); + }) : super(fieldBlocs: fieldBlocs, extraData: extraData); @override String toString() { - return '$runtimeType: ${state.name}'; + return '$runtimeType'; } } @@ -26,43 +25,33 @@ void main() { group('FormBlocUtils:', () { group('getAllSingleFieldBlocs:', () { final textFieldBloc1 = TextFieldBloc( - name: 'textFieldBloc1', initialValue: 'text1', ); final textFieldBloc2 = TextFieldBloc( - name: 'textFieldBloc2', initialValue: 'text2', ); final textFieldBloc3 = TextFieldBloc( - name: 'textFieldBloc3', initialValue: 'text3', ); final textFieldBloc4 = TextFieldBloc( - name: 'textFieldBloc4', initialValue: 'text4', ); final textFieldBloc5 = TextFieldBloc( - name: 'textFieldBloc5', initialValue: 'text5', ); final textFieldBloc6 = TextFieldBloc( - name: 'textFieldBloc6', initialValue: 'text6', ); - final textFieldBloc7 = TextFieldBloc( - name: 'textFieldBloc7', initialValue: 'text7', ); final textFieldBloc8 = TextFieldBloc( - name: 'textFieldBloc8', initialValue: 'text8', ); final groupFieldWithSingleFieldBlocs1 = GroupFieldBlocImpl( - name: 'groupFieldWithSingleFieldBlocs1', fieldBlocs: [ textFieldBloc3, textFieldBloc4, @@ -71,7 +60,6 @@ void main() { ); final groupFieldWithSingleFieldBlocs2 = GroupFieldBlocImpl( - name: 'groupFieldWithSingleFieldBlocs2', fieldBlocs: [ textFieldBloc5, textFieldBloc6, @@ -80,7 +68,6 @@ void main() { ); final groupFieldBlocWithGroupAndSingleFieldBlocs1 = GroupFieldBlocImpl( - name: 'groupFieldBlocWithGroupAndSingleFieldBlocs', fieldBlocs: [ groupFieldWithSingleFieldBlocs2, textFieldBloc7, @@ -89,33 +76,25 @@ void main() { extraData: null, ); - final booleanFieldBloc1 = - BooleanFieldBloc(name: 'booleanFieldBloc1'); - final booleanFieldBloc2 = - BooleanFieldBloc(name: 'booleanFieldBloc2'); + final booleanFieldBloc1 = BooleanFieldBloc(); + final booleanFieldBloc2 = BooleanFieldBloc(); final fieldBlocListWithSingleFieldBlocs1 = ListFieldBloc( - name: 'fieldBlocListWithSingleFieldBlocs1', fieldBlocs: [ booleanFieldBloc1, booleanFieldBloc2, ], ); - final selectFieldBloc1 = - SelectFieldBloc(name: 'selectFieldBloc1'); - final multiSelectFieldBloc1 = - MultiSelectFieldBloc(name: 'multiSelectFieldBloc1'); + final selectFieldBloc1 = SelectFieldBloc(); + final multiSelectFieldBloc1 = MultiSelectFieldBloc(); - final multiSelectFieldBloc3 = - MultiSelectFieldBloc(name: 'booleanFieldBloc3'); - final textFieldBloc9 = TextFieldBloc(name: 'textFieldBloc9'); - final inputFieldBloc1 = InputFieldBloc( - name: 'inputFieldBloc1', initialValue: null); + final multiSelectFieldBloc3 = MultiSelectFieldBloc(); + final textFieldBloc9 = TextFieldBloc(); + final inputFieldBloc1 = InputFieldBloc(initialValue: null); final groupFieldBlocWithAll1 = GroupFieldBlocImpl( - name: 'groupFieldBlocWithAll1', fieldBlocs: [ selectFieldBloc1, multiSelectFieldBloc1, @@ -128,7 +107,6 @@ void main() { ); final fieldBlocListWithAll1 = ListFieldBloc( - name: 'fieldBlocListWithAll1', fieldBlocs: [ selectFieldBloc1, multiSelectFieldBloc1, @@ -140,7 +118,6 @@ void main() { ); final fieldBlocListWithAll2 = ListFieldBloc( - name: 'fieldBlocListWithAll2', fieldBlocs: [ selectFieldBloc1, multiSelectFieldBloc1, @@ -152,7 +129,6 @@ void main() { ); final fieldBlocListWithAll3 = ListFieldBloc( - name: 'fieldBlocListWithAll3', fieldBlocs: [ fieldBlocListWithAll1, fieldBlocListWithAll2, @@ -232,8 +208,7 @@ void main() { }); test('Empty FieldBlocList', () { - final fieldBlocList21 = - ListFieldBloc(name: 'list21'); + final fieldBlocList21 = ListFieldBloc(); final fieldBlocs = [ fieldBlocList21, @@ -251,7 +226,6 @@ void main() { test('FieldBlocList with SingleFieldBlocs', () { final fieldBlocList21 = ListFieldBloc( - name: 'list21', fieldBlocs: [ textFieldBloc1, booleanFieldBloc1, @@ -321,7 +295,6 @@ void main() { test('FieldBlocList with GroupFieldBlocs', () { final fieldBlocList21 = ListFieldBloc( - name: 'list21', fieldBlocs: [ groupFieldWithSingleFieldBlocs1, ], @@ -345,34 +318,32 @@ void main() { }); }); - final booleanFieldBloc1 = BooleanFieldBloc(name: 'boolean1'); - final textFieldBloc1 = - TextFieldBloc(name: 'textFieldBloc1', initialValue: 'text1'); + final booleanFieldBloc1 = BooleanFieldBloc(); + final textFieldBloc1 = TextFieldBloc(initialValue: 'text1'); - final booleanFieldBloc2 = BooleanFieldBloc(name: 'boolean2'); - final textFieldBloc2 = - TextFieldBloc(name: 'textFieldBloc2', initialValue: 'text2'); + final booleanFieldBloc2 = BooleanFieldBloc(); + final textFieldBloc2 = TextFieldBloc(initialValue: 'text2'); - final groupFieldBloc1 = GroupFieldBlocImpl( - name: 'group1', fieldBlocs: [booleanFieldBloc1], extraData: null); + final groupFieldBloc1 = + GroupFieldBlocImpl(fieldBlocs: [booleanFieldBloc1], extraData: null); final fieldBlocList1 = ListFieldBloc( - name: 'list1', fieldBlocs: [booleanFieldBloc1, textFieldBloc1]); + fieldBlocs: [booleanFieldBloc1, textFieldBloc1]); final fieldBlocList2 = ListFieldBloc( - name: 'list2', fieldBlocs: [booleanFieldBloc2, textFieldBloc2]); + fieldBlocs: [booleanFieldBloc2, textFieldBloc2]); final fieldBlocList3 = ListFieldBloc( - name: 'list3', fieldBlocs: [fieldBlocList1, fieldBlocList2]); + fieldBlocs: [fieldBlocList1, fieldBlocList2]); - final groupFieldBloc2 = GroupFieldBlocImpl( - name: 'group2', fieldBlocs: [fieldBlocList3], extraData: null); + final groupFieldBloc2 = + GroupFieldBlocImpl(fieldBlocs: [fieldBlocList3], extraData: null); - final groupFieldBloc3 = GroupFieldBlocImpl( - name: 'group3', fieldBlocs: [groupFieldBloc2], extraData: null); + final groupFieldBloc3 = + GroupFieldBlocImpl(fieldBlocs: [groupFieldBloc2], extraData: null); - final fieldBlocList4 = ListFieldBloc( - name: 'list4', fieldBlocs: [groupFieldBloc3]); + final fieldBlocList4 = + ListFieldBloc(fieldBlocs: [groupFieldBloc3]); group('getFieldBlocFromPath', () { test('First name of path is a SingleFieldBloc', () { final fieldBlocs = { @@ -799,7 +770,7 @@ void main() { test('SingleFieldBloc', () async { final formBloc = FormBlocImpl(); - final booleanFieldBloc = BooleanFieldBloc(name: ''); + final booleanFieldBloc = BooleanFieldBloc(); FormBlocUtils.updateFormBloc( fieldBlocs: [booleanFieldBloc], @@ -843,7 +814,7 @@ void main() { test('ListFieldBloc', () async { final formBloc = FormBlocImpl(); - final listFieldBloc = ListFieldBloc(name: ''); + final listFieldBloc = ListFieldBloc(); FormBlocUtils.updateFormBloc( fieldBlocs: [listFieldBloc], @@ -878,7 +849,7 @@ void main() { final formBloc = FormBlocImpl(); final listFieldBloc = - GroupFieldBlocImpl(name: '', fieldBlocs: [], extraData: null); + GroupFieldBlocImpl(fieldBlocs: [], extraData: null); FormBlocUtils.updateFormBloc( fieldBlocs: [listFieldBloc], diff --git a/packages/form_bloc/test/text_field/text_field_state_test.dart b/packages/form_bloc/test/text_field/text_field_state_test.dart index d727cff7..58c96e3e 100644 --- a/packages/form_bloc/test/text_field/text_field_state_test.dart +++ b/packages/form_bloc/test/text_field/text_field_state_test.dart @@ -20,7 +20,6 @@ void main() { isDirty: false, isValidated: false, isValidating: false, - name: 'fieldName', ); expectState( @@ -45,7 +44,6 @@ void main() { isDirty: true, isValidated: true, isValidating: true, - name: 'fieldName', ), ); }); @@ -61,7 +59,6 @@ void main() { isDirty: true, isValidated: true, isValidating: true, - name: 'fieldName', ); expectState( @@ -86,7 +83,6 @@ void main() { isDirty: false, isValidated: false, isValidating: false, - name: 'fieldName', ), ); }); @@ -102,7 +98,6 @@ void main() { isDirty: true, isValidated: true, isValidating: true, - name: 'fieldName', ); expectState( From 45456cb16dffdd2c1c42b33728f593986d595c92 Mon Sep 17 00:00:00 2001 From: BreX900 Date: Sun, 15 May 2022 11:38:25 +0200 Subject: [PATCH 2/3] fix field bloc states equality --- .../lib/flutter_form_bloc.dart | 2 +- ...er.dart => multi_field_bloc_consumer.dart} | 32 ++++++++- .../src/fields/simple_field_bloc_builder.dart | 2 +- .../appear => form}/form_bloc_provider.dart | 0 .../suffix_button_bloc_builder.dart | 2 +- .../lib/src/blocs/field/field_state.dart | 2 +- .../lib/src/blocs/form/form_bloc.dart | 20 +++--- .../lib/src/blocs/form/form_state.dart | 72 ++++--------------- .../blocs/group_field/group_field_bloc.dart | 67 +++++++++++------ .../src/blocs/list_field/list_field_bloc.dart | 4 +- 10 files changed, 108 insertions(+), 95 deletions(-) rename packages/flutter_form_bloc/lib/src/fields/{list_field_bloc_consumer.dart => multi_field_bloc_consumer.dart} (62%) rename packages/flutter_form_bloc/lib/src/{features/appear => form}/form_bloc_provider.dart (100%) diff --git a/packages/flutter_form_bloc/lib/flutter_form_bloc.dart b/packages/flutter_form_bloc/lib/flutter_form_bloc.dart index 6e78562e..2a34d652 100644 --- a/packages/flutter_form_bloc/lib/flutter_form_bloc.dart +++ b/packages/flutter_form_bloc/lib/flutter_form_bloc.dart @@ -10,11 +10,11 @@ export 'src/date_time/date_time_field_bloc_builder.dart'; export 'src/date_time/time_field_bloc_builder.dart'; export 'src/dropdown_field_bloc_builder.dart'; export 'src/features/appear/can_show_field_bloc_builder.dart'; -export 'src/features/appear/form_bloc_provider.dart'; export 'src/features/scroll/scrollable_field_bloc_target.dart'; export 'src/features/scroll/scrollable_form_bloc_manager.dart'; export 'src/field_bloc_builder.dart'; export 'src/fields/simple_field_bloc_builder.dart'; +export 'src/form/form_bloc_provider.dart'; export 'src/form_bloc_listener.dart'; export 'src/groups/fields/checkbox_group_field_bloc_builder.dart'; export 'src/groups/fields/radio_button_group_field_bloc.dart'; diff --git a/packages/flutter_form_bloc/lib/src/fields/list_field_bloc_consumer.dart b/packages/flutter_form_bloc/lib/src/fields/multi_field_bloc_consumer.dart similarity index 62% rename from packages/flutter_form_bloc/lib/src/fields/list_field_bloc_consumer.dart rename to packages/flutter_form_bloc/lib/src/fields/multi_field_bloc_consumer.dart index 788e08a4..f8e10952 100644 --- a/packages/flutter_form_bloc/lib/src/fields/list_field_bloc_consumer.dart +++ b/packages/flutter_form_bloc/lib/src/fields/multi_field_bloc_consumer.dart @@ -57,6 +57,36 @@ class ListFieldBlocConsumer static bool fieldBlocsChanges( ListFieldBlocState prev, ListFieldBlocState curr) { - return prev.fieldBlocs.equals(curr.fieldBlocs); + return !prev.fieldBlocs.equals(curr.fieldBlocs); } } + +class MapFieldBlocConsumer + extends MultiFieldBlocConsumer> { + const MapFieldBlocConsumer({ + Key? key, + required MapFieldBloc mapFieldBloc, + CubitCondition>? listenWhen, + CubitListener>? listener, + CubitCondition>? buildWhen = + fieldBlocsChanges, + Widget? child, + CubitBuilder>? builder, + }) : super( + key: key, + multiFieldBloc: mapFieldBloc, + listener: listener, + builder: builder, + child: child, + ); + + static bool fieldBlocsChanges( + MapFieldBlocState prev, MapFieldBlocState curr) { + return !prev.fieldBlocs.equals(curr.fieldBlocs); + } +} + +extension on Map { + bool equals(Map other) => const MapEquality().equals(this, other); +} diff --git a/packages/flutter_form_bloc/lib/src/fields/simple_field_bloc_builder.dart b/packages/flutter_form_bloc/lib/src/fields/simple_field_bloc_builder.dart index b1e25f7e..3e7b8ee8 100644 --- a/packages/flutter_form_bloc/lib/src/fields/simple_field_bloc_builder.dart +++ b/packages/flutter_form_bloc/lib/src/fields/simple_field_bloc_builder.dart @@ -2,8 +2,8 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_form_bloc/src/cubit_consumer.dart'; import 'package:flutter_form_bloc/src/features/appear/can_show_field_bloc_builder.dart'; -import 'package:flutter_form_bloc/src/features/appear/form_bloc_provider.dart'; import 'package:flutter_form_bloc/src/features/scroll/scrollable_field_bloc_target.dart'; +import 'package:flutter_form_bloc/src/form/form_bloc_provider.dart'; import 'package:form_bloc/form_bloc.dart'; class FieldBlocBuilderData extends Equatable { diff --git a/packages/flutter_form_bloc/lib/src/features/appear/form_bloc_provider.dart b/packages/flutter_form_bloc/lib/src/form/form_bloc_provider.dart similarity index 100% rename from packages/flutter_form_bloc/lib/src/features/appear/form_bloc_provider.dart rename to packages/flutter_form_bloc/lib/src/form/form_bloc_provider.dart diff --git a/packages/flutter_form_bloc/lib/src/suffix_buttons/suffix_button_bloc_builder.dart b/packages/flutter_form_bloc/lib/src/suffix_buttons/suffix_button_bloc_builder.dart index 6865d5c7..8ce27901 100644 --- a/packages/flutter_form_bloc/lib/src/suffix_buttons/suffix_button_bloc_builder.dart +++ b/packages/flutter_form_bloc/lib/src/suffix_buttons/suffix_button_bloc_builder.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_form_bloc/src/cubit_consumer.dart'; -import 'package:flutter_form_bloc/src/features/appear/form_bloc_provider.dart'; +import 'package:flutter_form_bloc/src/form/form_bloc_provider.dart'; import 'package:form_bloc/form_bloc.dart'; typedef BlocChildBuilder = Widget Function( diff --git a/packages/form_bloc/lib/src/blocs/field/field_state.dart b/packages/form_bloc/lib/src/blocs/field/field_state.dart index ec5e1c4b..56d84c22 100644 --- a/packages/form_bloc/lib/src/blocs/field/field_state.dart +++ b/packages/form_bloc/lib/src/blocs/field/field_state.dart @@ -235,7 +235,7 @@ abstract class MultiFieldBlocState extends Equatable }); @override - List get props => [isValidating, isValid, extraData]; + List get props => [extraData]; @override String toString([Object? other]) { diff --git a/packages/form_bloc/lib/src/blocs/form/form_bloc.dart b/packages/form_bloc/lib/src/blocs/form/form_bloc.dart index 6aca68b9..300bb8ee 100644 --- a/packages/form_bloc/lib/src/blocs/form/form_bloc.dart +++ b/packages/form_bloc/lib/src/blocs/form/form_bloc.dart @@ -187,13 +187,13 @@ abstract class FormBloc /// TODO: Fix /// You can set [insertAt] of this fields, by default is `0`. void addStep(FieldBloc fieldBloc, {int? insertAt}) => - _onAddStep(step: insertAt, fieldBlocs: fieldBloc); + _onAddStep(step: insertAt, fieldBloc: fieldBloc); /// Adds [fieldBloc] to the [FormBloc]. /// /// You can set [at] of this fields, by default is `0`. void updateStep(int at, FieldBloc fieldBloc) => - _onUpdateStep(step: at, fieldBlocs: fieldBloc); + _onUpdateStep(step: at, fieldBloc: fieldBloc); // /// Adds [fieldBlocs] to the [FormBloc]. // /// @@ -433,32 +433,36 @@ abstract class FormBloc void _onAddStep({ int? step, - required FieldBloc fieldBlocs, + required FieldBloc fieldBloc, }) { assert(step == null || step > 0 && step <= state._fieldBlocs.length); - if (state._fieldBlocs[step] == fieldBlocs) return; + if (state._fieldBlocs[step] == fieldBloc) return; + + fieldBloc.updateAutoValidation(_autoValidate); emit(state._copyWith( fieldBlocs: { ...state._fieldBlocs, - step ?? state._fieldStates.length: fieldBlocs, + step ?? state._fieldStates.length: fieldBloc, }, )); } void _onUpdateStep({ required int step, - required FieldBloc fieldBlocs, + required FieldBloc fieldBloc, }) { assert(step > 0 && step <= state._fieldBlocs.length); - if (state._fieldBlocs[step] == fieldBlocs) return; + if (state._fieldBlocs[step] == fieldBloc) return; + + fieldBloc.updateAutoValidation(_autoValidate); emit(state._copyWith( fieldBlocs: { ...state._fieldBlocs, - step: fieldBlocs, + step: fieldBloc, }, )); } diff --git a/packages/form_bloc/lib/src/blocs/form/form_state.dart b/packages/form_bloc/lib/src/blocs/form/form_state.dart index 9774d13f..8ccd6a0f 100644 --- a/packages/form_bloc/lib/src/blocs/form/form_state.dart +++ b/packages/form_bloc/lib/src/blocs/form/form_state.dart @@ -531,6 +531,9 @@ abstract class FormBlocState extends Equatable } } + @override + List get props => [fieldBlocs, fieldStates, isEditing, currentStep]; + @override String toString() => _toStringWith(); @@ -610,10 +613,7 @@ class FormBlocLoading @override List get props => [ - isEditing, - _fieldBlocs, - _fieldStates, - currentStep, + super.props, progress, ]; @@ -654,11 +654,8 @@ class FormBlocLoadFailed @override List get props => [ + super.props, failureResponse, - isEditing, - _fieldBlocs, - _fieldStates, - currentStep, ]; @override @@ -686,14 +683,6 @@ class FormBlocLoaded fieldStates: fieldStates, currentStep: currentStep, ); - - @override - List get props => [ - isEditing, - _fieldBlocs, - _fieldStates, - currentStep, - ]; } /// {@template form_bloc.form_state.FormBlocSubmitting} @@ -733,12 +722,9 @@ class FormBlocSubmitting @override List get props => [ + super.props, progress, isCanceling, - isEditing, - _fieldBlocs, - _fieldStates, - currentStep, ]; @override @@ -783,12 +769,9 @@ class FormBlocSuccess @override List get props => [ + super.props, successResponse, - isEditing, canSubmitAgain, - _fieldBlocs, - _fieldStates, - currentStep, stepCompleted, ]; @@ -830,11 +813,8 @@ class FormBlocFailure @override List get props => [ + super.props, failureResponse, - isEditing, - _fieldBlocs, - _fieldStates, - currentStep, ]; @override @@ -864,14 +844,6 @@ class FormBlocSubmissionCancelled fieldStates: fieldStates, currentStep: currentStep, ); - - @override - List get props => [ - _fieldStates, - isEditing, - _fieldBlocs, - currentStep, - ]; } /// {@template form_bloc.form_state.FormBlocSubmissionFailed} @@ -891,14 +863,6 @@ class FormBlocSubmissionFailed fieldBlocs: fieldBlocs, currentStep: currentStep, ); - - @override - List get props => [ - isEditing, - _fieldBlocs, - _fieldStates, - currentStep, - ]; } /// {@template form_bloc.form_state.FormBlocDeleting} @@ -925,10 +889,7 @@ class FormBlocDeleting @override List get props => [ - isEditing, - _fieldBlocs, - _fieldStates, - currentStep, + super.props, progress, ]; @@ -969,11 +930,8 @@ class FormBlocDeleteFailed @override List get props => [ + super.props, failureResponse, - isEditing, - _fieldBlocs, - _fieldStates, - currentStep, ]; @override @@ -1013,11 +971,8 @@ class FormBlocDeleteSuccessful @override List get props => [ - _fieldStates, + super.props, successResponse, - isEditing, - _fieldBlocs, - currentStep, ]; @override @@ -1057,10 +1012,7 @@ class FormBlocUpdatingFields @override List get props => [ - isEditing, - _fieldBlocs, - _fieldStates, - currentStep, + super.props, progress, ]; diff --git a/packages/form_bloc/lib/src/blocs/group_field/group_field_bloc.dart b/packages/form_bloc/lib/src/blocs/group_field/group_field_bloc.dart index d83d72a1..68e9be2a 100644 --- a/packages/form_bloc/lib/src/blocs/group_field/group_field_bloc.dart +++ b/packages/form_bloc/lib/src/blocs/group_field/group_field_bloc.dart @@ -1,17 +1,23 @@ part of '../field/field_bloc.dart'; -class GroupFieldBlocState +typedef GroupFieldBlocState + = MapFieldBlocState; + +typedef GroupFieldBloc + = MapFieldBloc; + +class MapFieldBlocState extends MultiFieldBlocState { - final Map fieldBlocs; - final Map fieldStates; + final Map fieldBlocs; + final Map fieldStates; @override - late final Map value = - fieldStates.map((name, state) { - return MapEntry(name, state.value); + late final Map value = + fieldStates.map((name, state) { + return MapEntry(name, state.value); }); - GroupFieldBlocState({ + MapFieldBlocState({ required this.fieldBlocs, required this.fieldStates, required ExtraData? extraData, @@ -20,19 +26,19 @@ class GroupFieldBlocState ); @override - Map toJson() { - return fieldStates.map((key, value) { - return MapEntry(key, value.toJson()); + Map toJson() { + return fieldStates.map((key, value) { + return MapEntry(key, value.toJson()); }); } @override - GroupFieldBlocState copyWith({ + MapFieldBlocState copyWith({ Param? extraData, - Map? fieldBlocs, - Map? fieldStates, + Map? fieldBlocs, + Map? fieldStates, }) { - return GroupFieldBlocState( + return MapFieldBlocState( extraData: extraData == null ? this.extraData : extraData.value, fieldBlocs: fieldBlocs ?? this.fieldBlocs, fieldStates: fieldStates ?? @@ -48,23 +54,24 @@ class GroupFieldBlocState Iterable get flatFieldStates => fieldStates.values; @override - List get props => [super.props, fieldBlocs]; + List get props => [super.props, fieldBlocs, fieldStates]; @override String toString([Object? other]) => super.toString(',\n fieldBlocs: $fieldBlocs'); } -class GroupFieldBloc - extends MultiFieldBloc> { +class MapFieldBloc + extends MultiFieldBloc> { late final StreamSubscription _onValidationStatus; - GroupFieldBloc({ - Map fieldBlocs = const {}, + MapFieldBloc({ + Map fieldBlocs = const {}, bool autoValidate = true, ExtraData? extraData, }) : super( - GroupFieldBlocState( + MapFieldBlocState( extraData: extraData, fieldBlocs: fieldBlocs, fieldStates: @@ -87,6 +94,26 @@ class GroupFieldBloc }); } + void addAll(Map fieldBlocs) { + for (final fieldBloc in fieldBlocs.values) { + fieldBloc.updateAutoValidation(_autoValidate); + } + + emit(state.copyWith( + fieldBlocs: {...state.fieldBlocs, ...fieldBlocs}, + )); + } + + void add(TKey key, TFieldBloc fieldBloc) => addAll({key: fieldBloc}); + + void removeWhere(bool Function(TKey key, TFieldBloc fieldBloc) predicate) { + emit(state.copyWith( + fieldBlocs: state.fieldBlocs.where((k, v) => !predicate(k, v)), + )); + } + + void remove(TKey key) => removeWhere((k, fb) => k == key); + @override Future close() async { await _onValidationStatus.cancel(); diff --git a/packages/form_bloc/lib/src/blocs/list_field/list_field_bloc.dart b/packages/form_bloc/lib/src/blocs/list_field/list_field_bloc.dart index 97abd009..a2e8c84d 100644 --- a/packages/form_bloc/lib/src/blocs/list_field/list_field_bloc.dart +++ b/packages/form_bloc/lib/src/blocs/list_field/list_field_bloc.dart @@ -44,7 +44,7 @@ class ListFieldBlocState Iterable get flatFieldStates => fieldStates; @override - List get props => [super.props, fieldBlocs]; + List get props => [super.props, fieldBlocs, fieldStates]; @override String toString([Object? other]) => @@ -84,7 +84,7 @@ class ListFieldBloc void addFieldBloc(T fieldBloc) => addFieldBlocs([fieldBloc]); /// Add [FieldBloc]s. - void addFieldBlocs(List fieldBlocs, {bool inherit = true}) { + void addFieldBlocs(List fieldBlocs) { if (fieldBlocs.isNotEmpty) { final nextFieldBlocs = [...state.fieldBlocs, ...fieldBlocs]; From c84046673714bce02486c37ef17e6b9868a27b62 Mon Sep 17 00:00:00 2001 From: BreX900 Date: Sun, 3 Jul 2022 18:03:48 +0200 Subject: [PATCH 3/3] feat retrieve packages from github --- packages/flutter_form_bloc/pubspec.yaml | 6 +++++- packages/form_bloc/lib/src/blocs/field/field_bloc.dart | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/flutter_form_bloc/pubspec.yaml b/packages/flutter_form_bloc/pubspec.yaml index 9130a4f7..e3098a68 100644 --- a/packages/flutter_form_bloc/pubspec.yaml +++ b/packages/flutter_form_bloc/pubspec.yaml @@ -15,7 +15,11 @@ dependencies: bloc: ^8.0.3 # flutter_bloc: ^8.0.1 form_bloc: - path: ../form_bloc +# path: ../form_bloc + git: + url: https://github.com/BreX900/form_bloc.git + path: ./packages/form_bloc + ref: next # form_bloc: ^0.30.0 equatable: ^2.0.3 rxdart: ^0.27.3 diff --git a/packages/form_bloc/lib/src/blocs/field/field_bloc.dart b/packages/form_bloc/lib/src/blocs/field/field_bloc.dart index 6d41cc8a..d308a45f 100644 --- a/packages/form_bloc/lib/src/blocs/field/field_bloc.dart +++ b/packages/form_bloc/lib/src/blocs/field/field_bloc.dart @@ -320,9 +320,9 @@ abstract class SingleFieldBloc< // because it emit a completed async validation if (fieldBlocs.isNotEmpty) { _revalidateFieldBlocsSubscription = Rx.merge(fieldBlocs.map((e) { - return e.stream.map((event) { - event.value; - }).distinct(DeepCollectionEquality().equals); + return e.stream + .map((state) => state.value) + .distinct(DeepCollectionEquality().equals); })).listen((_) { if (_autoValidate) { _validate();