diff --git a/packages/generator_tests/pubspec.lock b/packages/generator_tests/pubspec.lock index b64857db..bf7c8616 100644 --- a/packages/generator_tests/pubspec.lock +++ b/packages/generator_tests/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" url: "https://pub.dev" source: hosted - version: "64.0.0" + version: "67.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.4.1" args: dependency: transitive description: @@ -85,10 +85,10 @@ packages: dependency: transitive description: name: build_runner_core - sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41" + sha256: "30859c90e9ddaccc484f56303931f477b1f1ba2bab74aa32ed5d6ce15870f8cf" url: "https://pub.dev" source: hosted - version: "7.2.10" + version: "7.2.8" build_test: dependency: "direct dev" description: @@ -338,10 +338,10 @@ packages: dependency: "direct dev" description: name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.1.1" matcher: dependency: transitive description: @@ -354,18 +354,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: @@ -436,14 +436,14 @@ packages: path: "../reactive_forms_annotations" relative: true source: path - version: "5.0.0" + version: "6.0.0-beta.5" reactive_forms_generator: dependency: "direct dev" description: path: "../reactive_forms_generator" relative: true source: path - version: "5.0.2" + version: "6.0.0-beta.8" recase: dependency: "direct main" description: diff --git a/packages/generator_tests/test/doc/annotateless_test.dart b/packages/generator_tests/test/doc/annotateless_test.dart index cfd9d444..d951955d 100644 --- a/packages/generator_tests/test/doc/annotateless_test.dart +++ b/packages/generator_tests/test/doc/annotateless_test.dart @@ -19,7 +19,7 @@ void main() { part '$fileName.gform.dart'; - @Rf() + @Rf(output: false) class Annotateless { final String email; @@ -41,7 +41,7 @@ void main() { const generatedFile = r'''// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'annotateless.dart'; @@ -188,6 +188,34 @@ class _AnnotatelessFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logAnnotatelessForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -225,7 +253,9 @@ class _AnnotatelessFormBuilderState extends State { } } -class AnnotatelessForm implements FormModel { +final _logAnnotatelessForm = Logger('AnnotatelessForm'); + +class AnnotatelessForm implements FormModel { AnnotatelessForm( this.form, this.path, @@ -378,9 +408,11 @@ class AnnotatelessForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'AnnotatelessForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logAnnotatelessForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Annotateless(email: _emailValue, password: _passwordValue); } @@ -426,6 +458,8 @@ class AnnotatelessForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logAnnotatelessForm.info('Errors'); + _logAnnotatelessForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } diff --git a/packages/generator_tests/test/doc/array_nullable_test.dart b/packages/generator_tests/test/doc/array_nullable_test.dart index 6db3629c..fb5888b5 100644 --- a/packages/generator_tests/test/doc/array_nullable_test.dart +++ b/packages/generator_tests/test/doc/array_nullable_test.dart @@ -22,7 +22,7 @@ void main() { enum UserMode { user, admin } - @Rf() + @Rf(output: false) class ArrayNullable { final List emailList; @@ -56,7 +56,7 @@ void main() { const generatedFile = r'''// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'array_nullable.dart'; @@ -203,6 +203,34 @@ class _ArrayNullableFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logArrayNullableForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -240,7 +268,9 @@ class _ArrayNullableFormBuilderState extends State { } } -class ArrayNullableForm implements FormModel { +final _logArrayNullableForm = Logger('ArrayNullableForm'); + +class ArrayNullableForm implements FormModel { ArrayNullableForm( this.form, this.path, @@ -843,9 +873,11 @@ class ArrayNullableForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'ArrayNullableForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logArrayNullableForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return ArrayNullable( emailList: _emailListValue, @@ -896,6 +928,8 @@ class ArrayNullableForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logArrayNullableForm.info('Errors'); + _logArrayNullableForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } diff --git a/packages/generator_tests/test/doc/delivery_list_test.dart b/packages/generator_tests/test/doc/delivery_list_test.dart index 301664e3..40ab7af1 100644 --- a/packages/generator_tests/test/doc/delivery_list_test.dart +++ b/packages/generator_tests/test/doc/delivery_list_test.dart @@ -20,7 +20,7 @@ void main() { part '$fileName.gform.dart'; - @Rf() + @Rf(output: false) class DeliveryList { final List deliveryList; final List? clientList; @@ -31,7 +31,7 @@ void main() { }); } - @Rf(name: 'StandaloneDeliveryPoint') + @Rf(output: false, name: 'StandaloneDeliveryPoint') @RfGroup() class DeliveryPoint { final String name; @@ -89,7 +89,7 @@ void main() { const generatedFile = r'''// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'delivery_list.dart'; @@ -236,6 +236,34 @@ class _DeliveryListFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logDeliveryListForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -273,7 +301,9 @@ class _DeliveryListFormBuilderState extends State { } } -class DeliveryListForm implements FormModel { +final _logDeliveryListForm = Logger('DeliveryListForm'); + +class DeliveryListForm implements FormModel { DeliveryListForm( this.form, this.path, @@ -680,9 +710,11 @@ class DeliveryListForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'DeliveryListForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logDeliveryListForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return DeliveryList( deliveryList: _deliveryListValue, clientList: _clientListValue); @@ -735,6 +767,8 @@ class DeliveryListForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logDeliveryListForm.info('Errors'); + _logDeliveryListForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } @@ -790,7 +824,9 @@ class DeliveryListForm implements FormModel { disabled: false); } -class DeliveryPointForm implements FormModel { +final _logDeliveryPointForm = Logger('DeliveryPointForm'); + +class DeliveryPointForm implements FormModel { DeliveryPointForm( this.form, this.path, @@ -973,9 +1009,11 @@ class DeliveryPointForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'DeliveryPointForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logDeliveryPointForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return DeliveryPoint(name: _nameValue, address: _addressValue); } @@ -1023,6 +1061,8 @@ class DeliveryPointForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logDeliveryPointForm.info('Errors'); + _logDeliveryPointForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } @@ -1070,7 +1110,9 @@ class DeliveryPointForm implements FormModel { disabled: false); } -class AddressForm implements FormModel
{ +final _logAddressForm = Logger('AddressForm'); + +class AddressForm implements FormModel { AddressForm( this.form, this.path, @@ -1277,9 +1319,11 @@ class AddressForm implements FormModel
{ final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'AddressForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logAddressForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Address(street: _streetValue, city: _cityValue); } @@ -1325,6 +1369,8 @@ class AddressForm implements FormModel
{ if (currentForm.valid) { onValid(model); } else { + _logAddressForm.info('Errors'); + _logAddressForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } @@ -1378,7 +1424,9 @@ class AddressForm implements FormModel
{ disabled: false); } -class ClientForm implements FormModel { +final _logClientForm = Logger('ClientForm'); + +class ClientForm implements FormModel { ClientForm( this.form, this.path, @@ -1653,9 +1701,11 @@ class ClientForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'ClientForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logClientForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Client( clientType: _clientTypeValue, name: _nameValue, notes: _notesValue); @@ -1702,6 +1752,8 @@ class ClientForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logClientForm.info('Errors'); + _logClientForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } @@ -2035,6 +2087,34 @@ class _StandaloneDeliveryPointFormBuilderState widget.initState?.call(context, _formModel); + _logStandaloneDeliveryPointForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -2072,7 +2152,10 @@ class _StandaloneDeliveryPointFormBuilderState } } -class StandaloneDeliveryPointForm implements FormModel { +final _logStandaloneDeliveryPointForm = Logger('StandaloneDeliveryPointForm'); + +class StandaloneDeliveryPointForm + implements FormModel { StandaloneDeliveryPointForm( this.form, this.path, @@ -2255,9 +2338,11 @@ class StandaloneDeliveryPointForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'StandaloneDeliveryPointForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logStandaloneDeliveryPointForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return DeliveryPoint(name: _nameValue, address: _addressValue); } @@ -2305,6 +2390,8 @@ class StandaloneDeliveryPointForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logStandaloneDeliveryPointForm.info('Errors'); + _logStandaloneDeliveryPointForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } diff --git a/packages/generator_tests/test/doc/freezed_class_output_test.dart b/packages/generator_tests/test/doc/freezed_class_output_test.dart new file mode 100644 index 00000000..51a5118d --- /dev/null +++ b/packages/generator_tests/test/doc/freezed_class_output_test.dart @@ -0,0 +1,1244 @@ +@Timeout(Duration(seconds: 145)) +import 'package:test/test.dart'; + +import '../helpers.dart'; + +const fileName = 'freezed_class_output'; + +void main() { + group('reactive_forms_generator', () { + test( + 'Freezed class output', + () async { + return testGenerator( + fileName: fileName, + model: ''' + import 'package:flutter/material.dart'; + import 'package:reactive_forms/reactive_forms.dart'; + import 'package:reactive_forms/src/validators/required_validator.dart'; + import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; + import 'package:freezed_annotation/freezed_annotation.dart'; + import 'package:example/helpers.dart'; + + part '$fileName.gform.dart'; + part '$fileName.g.dart'; + part '$fileName.freezed.dart'; + + @freezed + @Rf(output: true) + class FreezedClassO with _\$FreezedClassO { + FreezedClassO._(); + + factory FreezedClassO( + @RfControl() String? gender, + @RfControl(validators: [RequiredValidator()]) String? genderR, { + @RfControl() String? id, + @RfControl(validators: [RequiredValidator()]) String? idR, + @RfControl() String? name, + @JsonKey(name: 'logo_image') @RfControl() String? logoImage, + @RfControl() double? year, + }) = _FreezedClassO; + + factory FreezedClassO.fromJson(Map json) => + _\$FreezedClassOFromJson(json); + + bool method() => false; + } + ''', + generatedFile: generatedFile, + ); + }, + ); + }); +} + +const generatedFile = r'''// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'freezed_class_output.dart'; + +// ************************************************************************** +// ReactiveFormsGenerator +// ************************************************************************** + +class ReactiveFreezedClassOFormConsumer extends StatelessWidget { + const ReactiveFreezedClassOFormConsumer({ + Key? key, + required this.builder, + this.child, + }) : super(key: key); + + final Widget? child; + + final Widget Function( + BuildContext context, FreezedClassOForm formModel, Widget? child) builder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveFreezedClassOForm.of(context); + + if (formModel is! FreezedClassOForm) { + throw FormControlParentNotFoundException(this); + } + return builder(context, formModel, child); + } +} + +class FreezedClassOFormInheritedStreamer extends InheritedStreamer { + const FreezedClassOFormInheritedStreamer({ + Key? key, + required this.form, + required Stream stream, + required Widget child, + }) : super( + stream, + child, + key: key, + ); + + final FreezedClassOForm form; +} + +class ReactiveFreezedClassOForm extends StatelessWidget { + const ReactiveFreezedClassOForm({ + Key? key, + required this.form, + required this.child, + this.canPop, + this.onPopInvoked, + }) : super(key: key); + + final Widget child; + + final FreezedClassOForm form; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + static FreezedClassOForm? of( + BuildContext context, { + bool listen = true, + }) { + if (listen) { + return context + .dependOnInheritedWidgetOfExactType< + FreezedClassOFormInheritedStreamer>() + ?.form; + } + + final element = context.getElementForInheritedWidgetOfExactType< + FreezedClassOFormInheritedStreamer>(); + return element == null + ? null + : (element.widget as FreezedClassOFormInheritedStreamer).form; + } + + @override + Widget build(BuildContext context) { + return FreezedClassOFormInheritedStreamer( + form: form, + stream: form.form.statusChanged, + child: ReactiveFormPopScope( + canPop: canPop, + onPopInvoked: onPopInvoked, + child: child, + ), + ); + } +} + +extension ReactiveReactiveFreezedClassOFormExt on BuildContext { + FreezedClassOForm? freezedClassOFormWatch() => + ReactiveFreezedClassOForm.of(this); + + FreezedClassOForm? freezedClassOFormRead() => + ReactiveFreezedClassOForm.of(this, listen: false); +} + +class FreezedClassOFormBuilder extends StatefulWidget { + const FreezedClassOFormBuilder({ + Key? key, + this.model, + this.child, + this.canPop, + this.onPopInvoked, + required this.builder, + this.initState, + }) : super(key: key); + + final FreezedClassO? model; + + final Widget? child; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + final Widget Function( + BuildContext context, FreezedClassOForm formModel, Widget? child) builder; + + final void Function(BuildContext context, FreezedClassOForm formModel)? + initState; + + @override + _FreezedClassOFormBuilderState createState() => + _FreezedClassOFormBuilderState(); +} + +class _FreezedClassOFormBuilderState extends State { + late FreezedClassOForm _formModel; + + @override + void initState() { + _formModel = + FreezedClassOForm(FreezedClassOForm.formElements(widget.model), null); + + if (_formModel.form.disabled) { + _formModel.form.markAsDisabled(); + } + + widget.initState?.call(context, _formModel); + + _logFreezedClassOForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + + super.initState(); + } + + @override + void didUpdateWidget(covariant FreezedClassOFormBuilder oldWidget) { + if (widget.model != oldWidget.model) { + _formModel.updateValue(widget.model); + } + + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + _formModel.form.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ReactiveFreezedClassOForm( + key: ObjectKey(_formModel), + form: _formModel, + // canPop: widget.canPop, + // onPopInvoked: widget.onPopInvoked, + child: ReactiveFormBuilder( + form: () => _formModel.form, + canPop: widget.canPop, + onPopInvoked: widget.onPopInvoked, + builder: (context, formGroup, child) => + widget.builder(context, _formModel, widget.child), + child: widget.child, + ), + ); + } +} + +final _logFreezedClassOForm = Logger('FreezedClassOForm'); + +class FreezedClassOForm + implements FormModel { + FreezedClassOForm( + this.form, + this.path, + ); + + static const String genderControlName = "gender"; + + static const String genderRControlName = "genderR"; + + static const String idControlName = "id"; + + static const String idRControlName = "idR"; + + static const String nameControlName = "name"; + + static const String logoImageControlName = "logoImage"; + + static const String yearControlName = "year"; + + final FormGroup form; + + final String? path; + + final Map _disabled = {}; + + String genderControlPath() => pathBuilder(genderControlName); + + String genderRControlPath() => pathBuilder(genderRControlName); + + String idControlPath() => pathBuilder(idControlName); + + String idRControlPath() => pathBuilder(idRControlName); + + String nameControlPath() => pathBuilder(nameControlName); + + String logoImageControlPath() => pathBuilder(logoImageControlName); + + String yearControlPath() => pathBuilder(yearControlName); + + String? get _genderValue => genderControl?.value; + + String get _genderRValue => genderRControl.value as String; + + String? get _idValue => idControl?.value; + + String get _idRValue => idRControl.value as String; + + String? get _nameValue => nameControl?.value; + + String? get _logoImageValue => logoImageControl?.value; + + double? get _yearValue => yearControl?.value; + + bool get containsGender { + try { + form.control(genderControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsGenderR { + try { + form.control(genderRControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsId { + try { + form.control(idControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsIdR { + try { + form.control(idRControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsName { + try { + form.control(nameControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsLogoImage { + try { + form.control(logoImageControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsYear { + try { + form.control(yearControlPath()); + return true; + } catch (e) { + return false; + } + } + + Map? get genderErrors => genderControl?.errors; + + Map? get genderRErrors => genderRControl.errors; + + Map? get idErrors => idControl?.errors; + + Map? get idRErrors => idRControl.errors; + + Map? get nameErrors => nameControl?.errors; + + Map? get logoImageErrors => logoImageControl?.errors; + + Map? get yearErrors => yearControl?.errors; + + void get genderFocus => form.focus(genderControlPath()); + + void get genderRFocus => form.focus(genderRControlPath()); + + void get idFocus => form.focus(idControlPath()); + + void get idRFocus => form.focus(idRControlPath()); + + void get nameFocus => form.focus(nameControlPath()); + + void get logoImageFocus => form.focus(logoImageControlPath()); + + void get yearFocus => form.focus(yearControlPath()); + + void genderRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsGender) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + genderControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + genderControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void genderRRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsGenderR) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + genderRControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + genderRControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void idRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsId) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + idControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + idControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void idRRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsIdR) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + idRControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + idRControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void nameRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsName) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + nameControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + nameControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void logoImageRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsLogoImage) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + logoImageControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + logoImageControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void yearRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsYear) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + yearControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + yearControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void genderValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + genderControl?.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void genderRValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + genderRControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void idValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + idControl?.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void idRValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + idRControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void nameValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + nameControl?.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void logoImageValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + logoImageControl?.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void yearValueUpdate( + double? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + yearControl?.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void genderValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + genderControl?.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void genderRValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + genderRControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void idValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + idControl?.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void idRValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + idRControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void nameValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + nameControl?.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void logoImageValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + logoImageControl?.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void yearValuePatch( + double? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + yearControl?.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void genderValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + genderControl?.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void genderRValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + genderRControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void idValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + idControl?.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void idRValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + idRControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void nameValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + nameControl?.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void logoImageValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + logoImageControl?.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void yearValueReset( + double? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + yearControl?.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + FormControl? get genderControl => containsGender + ? form.control(genderControlPath()) as FormControl? + : null; + + FormControl get genderRControl => + form.control(genderRControlPath()) as FormControl; + + FormControl? get idControl => + containsId ? form.control(idControlPath()) as FormControl? : null; + + FormControl get idRControl => + form.control(idRControlPath()) as FormControl; + + FormControl? get nameControl => containsName + ? form.control(nameControlPath()) as FormControl? + : null; + + FormControl? get logoImageControl => containsLogoImage + ? form.control(logoImageControlPath()) as FormControl? + : null; + + FormControl? get yearControl => containsYear + ? form.control(yearControlPath()) as FormControl? + : null; + + void genderSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + genderControl?.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + genderControl?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void genderRSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + genderRControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + genderRControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void idSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + idControl?.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + idControl?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void idRSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + idRControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + idRControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void nameSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + nameControl?.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + nameControl?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void logoImageSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + logoImageControl?.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + logoImageControl?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void yearSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + yearControl?.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + yearControl?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + @override + FreezedClassOOutput get model { + final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; + + if (!isValid) { + _logFreezedClassOForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); + } + return FreezedClassOOutput(_genderValue, _genderRValue, + id: _idValue, + idR: _idRValue, + name: _nameValue, + logoImage: _logoImageValue, + year: _yearValue); + } + + @override + void toggleDisabled({ + bool updateParent = true, + bool emitEvent = true, + }) { + final currentFormInstance = currentForm; + + if (currentFormInstance is! FormGroup) { + return; + } + + if (_disabled.isEmpty) { + currentFormInstance.controls.forEach((key, control) { + _disabled[key] = control.disabled; + }); + + currentForm.markAsDisabled( + updateParent: updateParent, emitEvent: emitEvent); + } else { + currentFormInstance.controls.forEach((key, control) { + if (_disabled[key] == false) { + currentFormInstance.controls[key]?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + + _disabled.remove(key); + }); + } + } + + @override + void submit({ + required void Function(FreezedClassOOutput model) onValid, + void Function()? onNotValid, + }) { + currentForm.markAllAsTouched(); + if (currentForm.valid) { + onValid(model); + } else { + _logFreezedClassOForm.info('Errors'); + _logFreezedClassOForm.info('┗━━ ${form.errors}'); + onNotValid?.call(); + } + } + + AbstractControl get currentForm { + return path == null ? form : form.control(path!); + } + + @override + void updateValue( + FreezedClassO? value, { + bool updateParent = true, + bool emitEvent = true, + }) => + form.updateValue(FreezedClassOForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + + @override + void reset({ + FreezedClassO? value, + bool updateParent = true, + bool emitEvent = true, + }) => + form.reset( + value: value != null ? formElements(value).rawValue : null, + updateParent: updateParent, + emitEvent: emitEvent); + + String pathBuilder(String? pathItem) => + [path, pathItem].whereType().join("."); + + static FormGroup formElements(FreezedClassO? freezedClassO) => FormGroup({ + genderControlName: FormControl( + value: freezedClassO?.gender, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + genderRControlName: FormControl( + value: freezedClassO?.genderR, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + idControlName: FormControl( + value: freezedClassO?.id, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + idRControlName: FormControl( + value: freezedClassO?.idR, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + nameControlName: FormControl( + value: freezedClassO?.name, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + logoImageControlName: FormControl( + value: freezedClassO?.logoImage, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + yearControlName: FormControl( + value: freezedClassO?.year, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false) + }, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false); +} + +@freezed +@Rf(output: true) +class FreezedClassOOutput with _$FreezedClassOOutput { + FreezedClassOOutput._(); + factory FreezedClassOOutput(@RfControl() String? gender, + @RfControl(validators: [RequiredValidator()]) String genderR, + {@RfControl() String? id, + @RfControl(validators: [RequiredValidator()]) required String idR, + @RfControl() String? name, + @JsonKey(name: 'logo_image') @RfControl() String? logoImage, + @RfControl() double? year}) = _FreezedClassOOutput; + factory FreezedClassOOutput.fromJson(Map json) => + _$FreezedClassOOutputFromJson(json); + bool method() => false; +} + +class ReactiveFreezedClassOFormArrayBuilder< + ReactiveFreezedClassOFormArrayBuilderT> extends StatelessWidget { + const ReactiveFreezedClassOFormArrayBuilder({ + Key? key, + this.control, + this.formControl, + this.builder, + required this.itemBuilder, + }) : assert(control != null || formControl != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final FormArray? formControl; + + final FormArray? Function( + FreezedClassOForm formModel)? control; + + final Widget Function(BuildContext context, List itemList, + FreezedClassOForm formModel)? builder; + + final Widget Function( + BuildContext context, + int i, + ReactiveFreezedClassOFormArrayBuilderT? item, + FreezedClassOForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveFreezedClassOForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + return ReactiveFormArray( + formArray: formControl ?? control?.call(formModel), + builder: (context, formArray, child) { + final values = formArray.controls.map((e) => e.value).toList(); + final itemList = values + .asMap() + .map((i, item) { + return MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + ); + }) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} + +class ReactiveFreezedClassOFormFormGroupArrayBuilder< + ReactiveFreezedClassOFormFormGroupArrayBuilderT> extends StatelessWidget { + const ReactiveFreezedClassOFormFormGroupArrayBuilder({ + Key? key, + this.extended, + this.getExtended, + this.builder, + required this.itemBuilder, + }) : assert(extended != null || getExtended != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final ExtendedControl?>, + List>? extended; + + final ExtendedControl?>, + List> + Function(FreezedClassOForm formModel)? getExtended; + + final Widget Function(BuildContext context, List itemList, + FreezedClassOForm formModel)? builder; + + final Widget Function( + BuildContext context, + int i, + ReactiveFreezedClassOFormFormGroupArrayBuilderT? item, + FreezedClassOForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveFreezedClassOForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + final value = (extended ?? getExtended?.call(formModel))!; + + return StreamBuilder?>?>( + stream: value.control.valueChanges, + builder: (context, snapshot) { + final itemList = (value.value() ?? + []) + .asMap() + .map((i, item) => MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + )) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} +'''; diff --git a/packages/generator_tests/test/doc/freezed_class_test.dart b/packages/generator_tests/test/doc/freezed_class_test.dart index 01a43202..0f8b505d 100644 --- a/packages/generator_tests/test/doc/freezed_class_test.dart +++ b/packages/generator_tests/test/doc/freezed_class_test.dart @@ -8,7 +8,7 @@ const fileName = 'freezed_class'; void main() { group('reactive_forms_generator', () { test( - 'Freezed support', + 'Freezed class', () async { return testGenerator( fileName: fileName, @@ -25,7 +25,7 @@ void main() { part '$fileName.freezed.dart'; @freezed - @Rf() + @Rf(output: false) class FreezedClass with _\$FreezedClass { FreezedClass._(); @@ -55,7 +55,7 @@ void main() { const generatedFile = r'''// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'freezed_class.dart'; @@ -202,6 +202,34 @@ class _FreezedClassFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logFreezedClassForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -239,7 +267,9 @@ class _FreezedClassFormBuilderState extends State { } } -class FreezedClassForm implements FormModel { +final _logFreezedClassForm = Logger('FreezedClassForm'); + +class FreezedClassForm implements FormModel { FreezedClassForm( this.form, this.path, @@ -730,9 +760,11 @@ class FreezedClassForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'FreezedClassForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logFreezedClassForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return FreezedClass(_genderValue, id: _idValue, @@ -782,6 +814,8 @@ class FreezedClassForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logFreezedClassForm.info('Errors'); + _logFreezedClassForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } diff --git a/packages/generator_tests/test/doc/generic_status_list_test.dart b/packages/generator_tests/test/doc/generic_status_list_test.dart index 11fc576a..377d2264 100644 --- a/packages/generator_tests/test/doc/generic_status_list_test.dart +++ b/packages/generator_tests/test/doc/generic_status_list_test.dart @@ -17,7 +17,7 @@ void main() { part '$fileName.gform.dart'; - @Rf() + @Rf(output: false) class StatusList { final List list; @@ -36,7 +36,7 @@ void main() { const generatedFile = r'''// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'generic_status_list.dart'; @@ -185,6 +185,34 @@ class _StatusListFormBuilderState widget.initState?.call(context, _formModel); + _logStatusListForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -222,7 +250,10 @@ class _StatusListFormBuilderState } } -class StatusListForm implements FormModel> { +final _logStatusListForm = Logger('StatusListForm'); + +class StatusListForm + implements FormModel, StatusList> { StatusListForm( this.form, this.path, @@ -347,9 +378,11 @@ class StatusListForm implements FormModel> { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'StatusListForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logStatusListForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return StatusList(list: _listValue); } @@ -395,6 +428,8 @@ class StatusListForm implements FormModel> { if (currentForm.valid) { onValid(model); } else { + _logStatusListForm.info('Errors'); + _logStatusListForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } diff --git a/packages/generator_tests/test/doc/generic_test.dart b/packages/generator_tests/test/doc/generic_test.dart index 3145b885..35516940 100644 --- a/packages/generator_tests/test/doc/generic_test.dart +++ b/packages/generator_tests/test/doc/generic_test.dart @@ -8,7 +8,7 @@ const fileName = 'generic'; void main() { group('reactive_forms_generator', () { test( - 'Freezed support', + 'Generic', () async { return testGenerator( fileName: fileName, @@ -20,7 +20,7 @@ void main() { part '$fileName.gform.dart'; @freezed - @Rf() + @Rf(output: false) class Tags with _$Tags { factory Tags({ @RfControl() required List? tags, @@ -39,7 +39,7 @@ void main() { const generatedFile = r'''// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'generic.dart'; @@ -180,6 +180,34 @@ class _TagsFormBuilderState extends State> { widget.initState?.call(context, _formModel); + _logTagsForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -217,7 +245,9 @@ class _TagsFormBuilderState extends State> { } } -class TagsForm implements FormModel> { +final _logTagsForm = Logger('TagsForm'); + +class TagsForm implements FormModel, Tags> { TagsForm( this.form, this.path, @@ -329,9 +359,11 @@ class TagsForm implements FormModel> { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'TagsForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logTagsForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Tags(tags: _tagsValue); } @@ -377,6 +409,8 @@ class TagsForm implements FormModel> { if (currentForm.valid) { onValid(model); } else { + _logTagsForm.info('Errors'); + _logTagsForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } diff --git a/packages/generator_tests/test/doc/group_test.dart b/packages/generator_tests/test/doc/group_test.dart index ed5d6c99..81fd40ba 100644 --- a/packages/generator_tests/test/doc/group_test.dart +++ b/packages/generator_tests/test/doc/group_test.dart @@ -19,7 +19,7 @@ void main() { part '$fileName.gform.dart'; - @Rf() + @Rf(output: false) class Group { final Personal? personal; @@ -86,7 +86,7 @@ void main() { const generatedFile = r'''// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'group.dart'; @@ -227,6 +227,34 @@ class _GroupFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logGroupForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -264,7 +292,9 @@ class _GroupFormBuilderState extends State { } } -class GroupForm implements FormModel { +final _logGroupForm = Logger('GroupForm'); + +class GroupForm implements FormModel { GroupForm( this.form, this.path, @@ -675,9 +705,11 @@ class GroupForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'GroupForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logGroupForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Group( personal: _personalValue, @@ -735,6 +767,8 @@ class GroupForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logGroupForm.info('Errors'); + _logGroupForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } @@ -778,7 +812,9 @@ class GroupForm implements FormModel { disabled: false); } -class PersonalForm implements FormModel { +final _logPersonalForm = Logger('PersonalForm'); + +class PersonalForm implements FormModel { PersonalForm( this.form, this.path, @@ -985,9 +1021,11 @@ class PersonalForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'PersonalForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logPersonalForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Personal(name: _nameValue, email: _emailValue); } @@ -1033,6 +1071,8 @@ class PersonalForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logPersonalForm.info('Errors'); + _logPersonalForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } @@ -1086,7 +1126,9 @@ class PersonalForm implements FormModel { disabled: false); } -class PhoneForm implements FormModel { +final _logPhoneForm = Logger('PhoneForm'); + +class PhoneForm implements FormModel { PhoneForm( this.form, this.path, @@ -1293,9 +1335,11 @@ class PhoneForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'PhoneForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logPhoneForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Phone(phoneNumber: _phoneNumberValue, countryIso: _countryIsoValue); } @@ -1341,6 +1385,8 @@ class PhoneForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logPhoneForm.info('Errors'); + _logPhoneForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } @@ -1394,7 +1440,9 @@ class PhoneForm implements FormModel { disabled: false); } -class AddressForm implements FormModel
{ +final _logAddressForm = Logger('AddressForm'); + +class AddressForm implements FormModel { AddressForm( this.form, this.path, @@ -1696,9 +1744,11 @@ class AddressForm implements FormModel
{ final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'AddressForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logAddressForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Address(street: _streetValue, city: _cityValue, zip: _zipValue); } @@ -1744,6 +1794,8 @@ class AddressForm implements FormModel
{ if (currentForm.valid) { onValid(model); } else { + _logAddressForm.info('Errors'); + _logAddressForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } diff --git a/packages/generator_tests/test/doc/login_extended_nullable_test.dart b/packages/generator_tests/test/doc/login_extended_nullable_test.dart index 12e56a12..e946732c 100644 --- a/packages/generator_tests/test/doc/login_extended_nullable_test.dart +++ b/packages/generator_tests/test/doc/login_extended_nullable_test.dart @@ -8,7 +8,7 @@ const fileName = 'login_extended_nullable'; void main() { group('reactive_forms_generator', () { test( - 'Form with simple nullable types', + 'Login extended nullable', () async { return testGenerator( fileName: fileName, @@ -21,7 +21,7 @@ void main() { enum UserMode { user, admin } - @Rf() + @Rf(output: false) class LoginExtendedNullable { final String? email; @@ -58,7 +58,7 @@ void main() { const generatedFile = r'''// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'login_extended_nullable.dart'; @@ -207,6 +207,34 @@ class _LoginExtendedNullableFormBuilderState widget.initState?.call(context, _formModel); + _logLoginExtendedNullableForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -244,7 +272,10 @@ class _LoginExtendedNullableFormBuilderState } } -class LoginExtendedNullableForm implements FormModel { +final _logLoginExtendedNullableForm = Logger('LoginExtendedNullableForm'); + +class LoginExtendedNullableForm + implements FormModel { LoginExtendedNullableForm( this.form, this.path, @@ -926,9 +957,11 @@ class LoginExtendedNullableForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'LoginExtendedNullableForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logLoginExtendedNullableForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return LoginExtendedNullable( email: _emailValue, @@ -981,6 +1014,8 @@ class LoginExtendedNullableForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logLoginExtendedNullableForm.info('Errors'); + _logLoginExtendedNullableForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } diff --git a/packages/generator_tests/test/doc/login_extended_output_test.dart b/packages/generator_tests/test/doc/login_extended_output_test.dart new file mode 100644 index 00000000..73b631c2 --- /dev/null +++ b/packages/generator_tests/test/doc/login_extended_output_test.dart @@ -0,0 +1,1332 @@ +@Timeout(Duration(seconds: 145)) +import 'package:test/test.dart'; + +import '../helpers.dart'; + +const fileName = 'login_extended_output'; + +void main() { + group('reactive_forms_generator', () { + test( + 'Login extended Output', + () async { + return testGenerator( + fileName: fileName, + model: ''' + import 'package:flutter/material.dart'; + import 'package:reactive_forms/reactive_forms.dart'; + import 'package:reactive_forms/src/validators/required_validator.dart'; + import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; + + part '$fileName.gform.dart'; + + class AllFieldsRequired extends Validator { + const AllFieldsRequired() : super(); + + @override + Map? validate(AbstractControl control) { + return null; + } + } + + class UniqueEmailAsyncValidator extends AsyncValidator { + const UniqueEmailAsyncValidator() : super(); + + @override + Future?> validate( + AbstractControl control) async { + } + } + + enum UserMode { user, admin } + + @Rf() + @RfGroup( + validators: [AllFieldsRequired()], + ) + class LoginExtendedO { + final String? email; + + final String password; + + final bool rememberMe; + + final String theme; + + final UserMode mode; + + final int timeout; + + final double height; + + final String? unAnnotated; + final List someIntList; + + LoginExtendedO({ + @RfControl( + validators: [RequiredValidator()], + asyncValidators: [UniqueEmailAsyncValidator()], + ) + this.email, + @RfControl( + validators: [RequiredValidator()], + ) + required this.password, + @RfControl( + validators: [RequiredValidator()], + ) + required this.rememberMe, + @RfControl( + validators: [RequiredValidator()], + ) + required this.theme, + @RfControl( + validators: [RequiredValidator()], + ) + required this.mode, + @RfControl( + validators: [RequiredValidator()], + ) + required this.timeout, + @RfControl( + validators: [RequiredValidator()], + ) + required this.height, + this.unAnnotated, + this.someIntList = const [], + }); + } + ''', + generatedFile: generatedFile, + ); + }, + ); + }); +} + +const generatedFile = r'''// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'login_extended_output.dart'; + +// ************************************************************************** +// ReactiveFormsGenerator +// ************************************************************************** + +class ReactiveLoginExtendedOFormConsumer extends StatelessWidget { + const ReactiveLoginExtendedOFormConsumer({ + Key? key, + required this.builder, + this.child, + }) : super(key: key); + + final Widget? child; + + final Widget Function( + BuildContext context, LoginExtendedOForm formModel, Widget? child) + builder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveLoginExtendedOForm.of(context); + + if (formModel is! LoginExtendedOForm) { + throw FormControlParentNotFoundException(this); + } + return builder(context, formModel, child); + } +} + +class LoginExtendedOFormInheritedStreamer extends InheritedStreamer { + const LoginExtendedOFormInheritedStreamer({ + Key? key, + required this.form, + required Stream stream, + required Widget child, + }) : super( + stream, + child, + key: key, + ); + + final LoginExtendedOForm form; +} + +class ReactiveLoginExtendedOForm extends StatelessWidget { + const ReactiveLoginExtendedOForm({ + Key? key, + required this.form, + required this.child, + this.canPop, + this.onPopInvoked, + }) : super(key: key); + + final Widget child; + + final LoginExtendedOForm form; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + static LoginExtendedOForm? of( + BuildContext context, { + bool listen = true, + }) { + if (listen) { + return context + .dependOnInheritedWidgetOfExactType< + LoginExtendedOFormInheritedStreamer>() + ?.form; + } + + final element = context.getElementForInheritedWidgetOfExactType< + LoginExtendedOFormInheritedStreamer>(); + return element == null + ? null + : (element.widget as LoginExtendedOFormInheritedStreamer).form; + } + + @override + Widget build(BuildContext context) { + return LoginExtendedOFormInheritedStreamer( + form: form, + stream: form.form.statusChanged, + child: ReactiveFormPopScope( + canPop: canPop, + onPopInvoked: onPopInvoked, + child: child, + ), + ); + } +} + +extension ReactiveReactiveLoginExtendedOFormExt on BuildContext { + LoginExtendedOForm? loginExtendedOFormWatch() => + ReactiveLoginExtendedOForm.of(this); + + LoginExtendedOForm? loginExtendedOFormRead() => + ReactiveLoginExtendedOForm.of(this, listen: false); +} + +class LoginExtendedOFormBuilder extends StatefulWidget { + const LoginExtendedOFormBuilder({ + Key? key, + this.model, + this.child, + this.canPop, + this.onPopInvoked, + required this.builder, + this.initState, + }) : super(key: key); + + final LoginExtendedO? model; + + final Widget? child; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + final Widget Function( + BuildContext context, LoginExtendedOForm formModel, Widget? child) + builder; + + final void Function(BuildContext context, LoginExtendedOForm formModel)? + initState; + + @override + _LoginExtendedOFormBuilderState createState() => + _LoginExtendedOFormBuilderState(); +} + +class _LoginExtendedOFormBuilderState extends State { + late LoginExtendedOForm _formModel; + + @override + void initState() { + _formModel = + LoginExtendedOForm(LoginExtendedOForm.formElements(widget.model), null); + + if (_formModel.form.disabled) { + _formModel.form.markAsDisabled(); + } + + widget.initState?.call(context, _formModel); + + _logLoginExtendedOForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + + super.initState(); + } + + @override + void didUpdateWidget(covariant LoginExtendedOFormBuilder oldWidget) { + if (widget.model != oldWidget.model) { + _formModel.updateValue(widget.model); + } + + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + _formModel.form.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ReactiveLoginExtendedOForm( + key: ObjectKey(_formModel), + form: _formModel, + // canPop: widget.canPop, + // onPopInvoked: widget.onPopInvoked, + child: ReactiveFormBuilder( + form: () => _formModel.form, + canPop: widget.canPop, + onPopInvoked: widget.onPopInvoked, + builder: (context, formGroup, child) => + widget.builder(context, _formModel, widget.child), + child: widget.child, + ), + ); + } +} + +final _logLoginExtendedOForm = Logger('LoginExtendedOForm'); + +class LoginExtendedOForm + implements FormModel { + LoginExtendedOForm( + this.form, + this.path, + ); + + static const String emailControlName = "email"; + + static const String passwordControlName = "password"; + + static const String rememberMeControlName = "rememberMe"; + + static const String themeControlName = "theme"; + + static const String modeControlName = "mode"; + + static const String timeoutControlName = "timeout"; + + static const String heightControlName = "height"; + + static const String unAnnotatedControlName = "unAnnotated"; + + static const String someIntListControlName = "someIntList"; + + final FormGroup form; + + final String? path; + + final Map _disabled = {}; + + String emailControlPath() => pathBuilder(emailControlName); + + String passwordControlPath() => pathBuilder(passwordControlName); + + String rememberMeControlPath() => pathBuilder(rememberMeControlName); + + String themeControlPath() => pathBuilder(themeControlName); + + String modeControlPath() => pathBuilder(modeControlName); + + String timeoutControlPath() => pathBuilder(timeoutControlName); + + String heightControlPath() => pathBuilder(heightControlName); + + String unAnnotatedControlPath() => pathBuilder(unAnnotatedControlName); + + String someIntListControlPath() => pathBuilder(someIntListControlName); + + String get _emailValue => emailControl.value as String; + + String get _passwordValue => passwordControl.value as String; + + bool get _rememberMeValue => rememberMeControl.value as bool; + + String get _themeValue => themeControl.value as String; + + UserMode get _modeValue => modeControl.value as UserMode; + + int get _timeoutValue => timeoutControl.value as int; + + double get _heightValue => heightControl.value as double; + + String? get _unAnnotatedValue => unAnnotatedControl?.value; + + List get _someIntListValue => someIntListControl.value ?? []; + + bool get containsEmail { + try { + form.control(emailControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsPassword { + try { + form.control(passwordControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsRememberMe { + try { + form.control(rememberMeControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsTheme { + try { + form.control(themeControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsMode { + try { + form.control(modeControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsTimeout { + try { + form.control(timeoutControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsHeight { + try { + form.control(heightControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsUnAnnotated { + try { + form.control(unAnnotatedControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsSomeIntList { + try { + form.control(someIntListControlPath()); + return true; + } catch (e) { + return false; + } + } + + Map? get emailErrors => emailControl.errors; + + Map get passwordErrors => passwordControl.errors; + + Map get rememberMeErrors => rememberMeControl.errors; + + Map get themeErrors => themeControl.errors; + + Map get modeErrors => modeControl.errors; + + Map get timeoutErrors => timeoutControl.errors; + + Map get heightErrors => heightControl.errors; + + Map? get unAnnotatedErrors => unAnnotatedControl?.errors; + + Map get someIntListErrors => someIntListControl.errors; + + void get emailFocus => form.focus(emailControlPath()); + + void get passwordFocus => form.focus(passwordControlPath()); + + void get rememberMeFocus => form.focus(rememberMeControlPath()); + + void get themeFocus => form.focus(themeControlPath()); + + void get modeFocus => form.focus(modeControlPath()); + + void get timeoutFocus => form.focus(timeoutControlPath()); + + void get heightFocus => form.focus(heightControlPath()); + + void get unAnnotatedFocus => form.focus(unAnnotatedControlPath()); + + void get someIntListFocus => form.focus(someIntListControlPath()); + + void emailRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsEmail) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + emailControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + emailControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void unAnnotatedRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsUnAnnotated) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + unAnnotatedControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + unAnnotatedControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void emailValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + emailControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void passwordValueUpdate( + String value, { + bool updateParent = true, + bool emitEvent = true, + }) { + passwordControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void rememberMeValueUpdate( + bool value, { + bool updateParent = true, + bool emitEvent = true, + }) { + rememberMeControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void themeValueUpdate( + String value, { + bool updateParent = true, + bool emitEvent = true, + }) { + themeControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void modeValueUpdate( + UserMode value, { + bool updateParent = true, + bool emitEvent = true, + }) { + modeControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void timeoutValueUpdate( + int value, { + bool updateParent = true, + bool emitEvent = true, + }) { + timeoutControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void heightValueUpdate( + double value, { + bool updateParent = true, + bool emitEvent = true, + }) { + heightControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void unAnnotatedValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + unAnnotatedControl?.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void someIntListValueUpdate( + List value, { + bool updateParent = true, + bool emitEvent = true, + }) { + someIntListControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void emailValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + emailControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void passwordValuePatch( + String value, { + bool updateParent = true, + bool emitEvent = true, + }) { + passwordControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void rememberMeValuePatch( + bool value, { + bool updateParent = true, + bool emitEvent = true, + }) { + rememberMeControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void themeValuePatch( + String value, { + bool updateParent = true, + bool emitEvent = true, + }) { + themeControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void modeValuePatch( + UserMode value, { + bool updateParent = true, + bool emitEvent = true, + }) { + modeControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void timeoutValuePatch( + int value, { + bool updateParent = true, + bool emitEvent = true, + }) { + timeoutControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void heightValuePatch( + double value, { + bool updateParent = true, + bool emitEvent = true, + }) { + heightControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void unAnnotatedValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + unAnnotatedControl?.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void someIntListValuePatch( + List value, { + bool updateParent = true, + bool emitEvent = true, + }) { + someIntListControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void emailValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + emailControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void passwordValueReset( + String value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + passwordControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void rememberMeValueReset( + bool value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + rememberMeControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void themeValueReset( + String value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + themeControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void modeValueReset( + UserMode value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + modeControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void timeoutValueReset( + int value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + timeoutControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void heightValueReset( + double value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + heightControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void unAnnotatedValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + unAnnotatedControl?.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void someIntListValueReset( + List value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + someIntListControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + FormControl get emailControl => + form.control(emailControlPath()) as FormControl; + + FormControl get passwordControl => + form.control(passwordControlPath()) as FormControl; + + FormControl get rememberMeControl => + form.control(rememberMeControlPath()) as FormControl; + + FormControl get themeControl => + form.control(themeControlPath()) as FormControl; + + FormControl get modeControl => + form.control(modeControlPath()) as FormControl; + + FormControl get timeoutControl => + form.control(timeoutControlPath()) as FormControl; + + FormControl get heightControl => + form.control(heightControlPath()) as FormControl; + + FormControl? get unAnnotatedControl => containsUnAnnotated + ? form.control(unAnnotatedControlPath()) as FormControl? + : null; + + FormControl> get someIntListControl => + form.control(someIntListControlPath()) as FormControl>; + + void emailSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + emailControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + emailControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void passwordSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + passwordControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + passwordControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void rememberMeSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + rememberMeControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + rememberMeControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void themeSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + themeControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + themeControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void modeSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + modeControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + modeControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void timeoutSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + timeoutControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + timeoutControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void heightSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + heightControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + heightControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void unAnnotatedSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + unAnnotatedControl?.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + unAnnotatedControl?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void someIntListSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + someIntListControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + someIntListControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + @override + LoginExtendedOOutput get model { + final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; + + if (!isValid) { + _logLoginExtendedOForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); + } + return LoginExtendedOOutput( + email: _emailValue, + password: _passwordValue, + rememberMe: _rememberMeValue, + theme: _themeValue, + mode: _modeValue, + timeout: _timeoutValue, + height: _heightValue, + unAnnotated: _unAnnotatedValue, + someIntList: _someIntListValue); + } + + @override + void toggleDisabled({ + bool updateParent = true, + bool emitEvent = true, + }) { + final currentFormInstance = currentForm; + + if (currentFormInstance is! FormGroup) { + return; + } + + if (_disabled.isEmpty) { + currentFormInstance.controls.forEach((key, control) { + _disabled[key] = control.disabled; + }); + + currentForm.markAsDisabled( + updateParent: updateParent, emitEvent: emitEvent); + } else { + currentFormInstance.controls.forEach((key, control) { + if (_disabled[key] == false) { + currentFormInstance.controls[key]?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + + _disabled.remove(key); + }); + } + } + + @override + void submit({ + required void Function(LoginExtendedOOutput model) onValid, + void Function()? onNotValid, + }) { + currentForm.markAllAsTouched(); + if (currentForm.valid) { + onValid(model); + } else { + _logLoginExtendedOForm.info('Errors'); + _logLoginExtendedOForm.info('┗━━ ${form.errors}'); + onNotValid?.call(); + } + } + + AbstractControl get currentForm { + return path == null ? form : form.control(path!); + } + + @override + void updateValue( + LoginExtendedO? value, { + bool updateParent = true, + bool emitEvent = true, + }) => + form.updateValue(LoginExtendedOForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + + @override + void reset({ + LoginExtendedO? value, + bool updateParent = true, + bool emitEvent = true, + }) => + form.reset( + value: value != null ? formElements(value).rawValue : null, + updateParent: updateParent, + emitEvent: emitEvent); + + String pathBuilder(String? pathItem) => + [path, pathItem].whereType().join("."); + + static FormGroup formElements(LoginExtendedO? loginExtendedO) => FormGroup({ + emailControlName: FormControl( + value: loginExtendedO?.email, + validators: [RequiredValidator()], + asyncValidators: [UniqueEmailAsyncValidator()], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + passwordControlName: FormControl( + value: loginExtendedO?.password, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + rememberMeControlName: FormControl( + value: loginExtendedO?.rememberMe, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + themeControlName: FormControl( + value: loginExtendedO?.theme, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + modeControlName: FormControl( + value: loginExtendedO?.mode, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + timeoutControlName: FormControl( + value: loginExtendedO?.timeout, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + heightControlName: FormControl( + value: loginExtendedO?.height, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + unAnnotatedControlName: FormControl( + value: loginExtendedO?.unAnnotated, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + someIntListControlName: FormControl>( + value: loginExtendedO?.someIntList, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false) + }, + validators: [ + AllFieldsRequired() + ], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false); +} + +@Rf() +@RfGroup(validators: [AllFieldsRequired()]) +class LoginExtendedOOutput { + final String email; + final String password; + final bool rememberMe; + final String theme; + final UserMode mode; + final int timeout; + final double height; + final String? unAnnotated; + final List someIntList; + LoginExtendedOOutput( + {@RfControl( + validators: [RequiredValidator()], + asyncValidators: [UniqueEmailAsyncValidator()]) + required this.email, + @RfControl(validators: [RequiredValidator()]) required this.password, + @RfControl(validators: [RequiredValidator()]) required this.rememberMe, + @RfControl(validators: [RequiredValidator()]) required this.theme, + @RfControl(validators: [RequiredValidator()]) required this.mode, + @RfControl(validators: [RequiredValidator()]) required this.timeout, + @RfControl(validators: [RequiredValidator()]) required this.height, + this.unAnnotated, + this.someIntList = const []}); +} + +class ReactiveLoginExtendedOFormArrayBuilder< + ReactiveLoginExtendedOFormArrayBuilderT> extends StatelessWidget { + const ReactiveLoginExtendedOFormArrayBuilder({ + Key? key, + this.control, + this.formControl, + this.builder, + required this.itemBuilder, + }) : assert(control != null || formControl != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final FormArray? formControl; + + final FormArray? Function( + LoginExtendedOForm formModel)? control; + + final Widget Function(BuildContext context, List itemList, + LoginExtendedOForm formModel)? builder; + + final Widget Function( + BuildContext context, + int i, + ReactiveLoginExtendedOFormArrayBuilderT? item, + LoginExtendedOForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveLoginExtendedOForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + return ReactiveFormArray( + formArray: formControl ?? control?.call(formModel), + builder: (context, formArray, child) { + final values = formArray.controls.map((e) => e.value).toList(); + final itemList = values + .asMap() + .map((i, item) { + return MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + ); + }) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} + +class ReactiveLoginExtendedOFormFormGroupArrayBuilder< + ReactiveLoginExtendedOFormFormGroupArrayBuilderT> extends StatelessWidget { + const ReactiveLoginExtendedOFormFormGroupArrayBuilder({ + Key? key, + this.extended, + this.getExtended, + this.builder, + required this.itemBuilder, + }) : assert(extended != null || getExtended != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final ExtendedControl?>, + List>? extended; + + final ExtendedControl?>, + List> + Function(LoginExtendedOForm formModel)? getExtended; + + final Widget Function(BuildContext context, List itemList, + LoginExtendedOForm formModel)? builder; + + final Widget Function( + BuildContext context, + int i, + ReactiveLoginExtendedOFormFormGroupArrayBuilderT? item, + LoginExtendedOForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveLoginExtendedOForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + final value = (extended ?? getExtended?.call(formModel))!; + + return StreamBuilder?>?>( + stream: value.control.valueChanges, + builder: (context, snapshot) { + final itemList = (value.value() ?? + []) + .asMap() + .map((i, item) => MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + )) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} +'''; diff --git a/packages/generator_tests/test/doc/login_extended_test.dart b/packages/generator_tests/test/doc/login_extended_test.dart index 7ae41df3..8caccc0c 100644 --- a/packages/generator_tests/test/doc/login_extended_test.dart +++ b/packages/generator_tests/test/doc/login_extended_test.dart @@ -8,7 +8,7 @@ const fileName = 'login_extended'; void main() { group('reactive_forms_generator', () { test( - 'Form with simple non-nullable types', + 'Login extended', () async { return testGenerator( fileName: fileName, @@ -40,7 +40,7 @@ void main() { enum UserMode { user, admin } - @Rf() + @Rf(output: false) @RfGroup( validators: [AllFieldsRequired()], ) @@ -108,7 +108,7 @@ void main() { const generatedFile = r'''// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'login_extended.dart'; @@ -255,6 +255,34 @@ class _LoginExtendedFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logLoginExtendedForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -292,7 +320,9 @@ class _LoginExtendedFormBuilderState extends State { } } -class LoginExtendedForm implements FormModel { +final _logLoginExtendedForm = Logger('LoginExtendedForm'); + +class LoginExtendedForm implements FormModel { LoginExtendedForm( this.form, this.path, @@ -948,9 +978,11 @@ class LoginExtendedForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'LoginExtendedForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logLoginExtendedForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return LoginExtended( email: _emailValue, @@ -1005,6 +1037,8 @@ class LoginExtendedForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logLoginExtendedForm.info('Errors'); + _logLoginExtendedForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } diff --git a/packages/generator_tests/test/doc/login_output_test.dart b/packages/generator_tests/test/doc/login_output_test.dart new file mode 100644 index 00000000..10698964 --- /dev/null +++ b/packages/generator_tests/test/doc/login_output_test.dart @@ -0,0 +1,730 @@ +@Timeout(Duration(seconds: 145)) +import 'package:test/test.dart'; + +import '../helpers.dart'; + +const fileName = 'login_output'; + +void main() { + group('doc', () { + test( + 'Login Output', + () async { + return testGenerator( + fileName: fileName, + model: ''' + import 'package:flutter/material.dart'; + import 'package:reactive_forms/reactive_forms.dart'; + import 'package:reactive_forms/src/validators/required_validator.dart'; + import 'package:example/helpers.dart'; + import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; + + part '$fileName.gform.dart'; + + class MustMatchValidator extends Validator { + const MustMatchValidator() : super(); + + @override + Map? validate(AbstractControl control) { + return null; + } + } + + @Rf() + @RfGroup( + validators: [MustMatchValidator()], + ) + class LoginO extends Equatable { + final String? email; + + final String? password; + + const LoginO({ + @RfControl( + validators: [RequiredValidator(), RequiredValidator()], + ) + this.email, + @RfControl( + validators: [RequiredValidator()], + ) + this.password, + }); + + @override + List get props => [email, password]; + } + ''', + generatedFile: generatedFile, + ); + }, + ); + }); +} + +const generatedFile = r'''// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'login_output.dart'; + +// ************************************************************************** +// ReactiveFormsGenerator +// ************************************************************************** + +class ReactiveLoginOFormConsumer extends StatelessWidget { + const ReactiveLoginOFormConsumer({ + Key? key, + required this.builder, + this.child, + }) : super(key: key); + + final Widget? child; + + final Widget Function( + BuildContext context, LoginOForm formModel, Widget? child) builder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveLoginOForm.of(context); + + if (formModel is! LoginOForm) { + throw FormControlParentNotFoundException(this); + } + return builder(context, formModel, child); + } +} + +class LoginOFormInheritedStreamer extends InheritedStreamer { + const LoginOFormInheritedStreamer({ + Key? key, + required this.form, + required Stream stream, + required Widget child, + }) : super( + stream, + child, + key: key, + ); + + final LoginOForm form; +} + +class ReactiveLoginOForm extends StatelessWidget { + const ReactiveLoginOForm({ + Key? key, + required this.form, + required this.child, + this.canPop, + this.onPopInvoked, + }) : super(key: key); + + final Widget child; + + final LoginOForm form; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + static LoginOForm? of( + BuildContext context, { + bool listen = true, + }) { + if (listen) { + return context + .dependOnInheritedWidgetOfExactType() + ?.form; + } + + final element = context + .getElementForInheritedWidgetOfExactType(); + return element == null + ? null + : (element.widget as LoginOFormInheritedStreamer).form; + } + + @override + Widget build(BuildContext context) { + return LoginOFormInheritedStreamer( + form: form, + stream: form.form.statusChanged, + child: ReactiveFormPopScope( + canPop: canPop, + onPopInvoked: onPopInvoked, + child: child, + ), + ); + } +} + +extension ReactiveReactiveLoginOFormExt on BuildContext { + LoginOForm? loginOFormWatch() => ReactiveLoginOForm.of(this); + + LoginOForm? loginOFormRead() => ReactiveLoginOForm.of(this, listen: false); +} + +class LoginOFormBuilder extends StatefulWidget { + const LoginOFormBuilder({ + Key? key, + this.model, + this.child, + this.canPop, + this.onPopInvoked, + required this.builder, + this.initState, + }) : super(key: key); + + final LoginO? model; + + final Widget? child; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + final Widget Function( + BuildContext context, LoginOForm formModel, Widget? child) builder; + + final void Function(BuildContext context, LoginOForm formModel)? initState; + + @override + _LoginOFormBuilderState createState() => _LoginOFormBuilderState(); +} + +class _LoginOFormBuilderState extends State { + late LoginOForm _formModel; + + @override + void initState() { + _formModel = LoginOForm(LoginOForm.formElements(widget.model), null); + + if (_formModel.form.disabled) { + _formModel.form.markAsDisabled(); + } + + widget.initState?.call(context, _formModel); + + _logLoginOForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + + super.initState(); + } + + @override + void didUpdateWidget(covariant LoginOFormBuilder oldWidget) { + if (widget.model != oldWidget.model) { + _formModel.updateValue(widget.model); + } + + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + _formModel.form.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ReactiveLoginOForm( + key: ObjectKey(_formModel), + form: _formModel, + // canPop: widget.canPop, + // onPopInvoked: widget.onPopInvoked, + child: ReactiveFormBuilder( + form: () => _formModel.form, + canPop: widget.canPop, + onPopInvoked: widget.onPopInvoked, + builder: (context, formGroup, child) => + widget.builder(context, _formModel, widget.child), + child: widget.child, + ), + ); + } +} + +final _logLoginOForm = Logger('LoginOForm'); + +class LoginOForm implements FormModel { + LoginOForm( + this.form, + this.path, + ); + + static const String emailControlName = "email"; + + static const String passwordControlName = "password"; + + final FormGroup form; + + final String? path; + + final Map _disabled = {}; + + String emailControlPath() => pathBuilder(emailControlName); + + String passwordControlPath() => pathBuilder(passwordControlName); + + String get _emailValue => emailControl.value as String; + + String get _passwordValue => passwordControl.value as String; + + bool get containsEmail { + try { + form.control(emailControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsPassword { + try { + form.control(passwordControlPath()); + return true; + } catch (e) { + return false; + } + } + + Map? get emailErrors => emailControl.errors; + + Map? get passwordErrors => passwordControl.errors; + + void get emailFocus => form.focus(emailControlPath()); + + void get passwordFocus => form.focus(passwordControlPath()); + + void emailRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsEmail) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + emailControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + emailControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void passwordRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsPassword) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + passwordControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + passwordControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void emailValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + emailControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void passwordValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + passwordControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void emailValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + emailControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void passwordValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + passwordControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void emailValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + emailControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void passwordValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + passwordControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + FormControl get emailControl => + form.control(emailControlPath()) as FormControl; + + FormControl get passwordControl => + form.control(passwordControlPath()) as FormControl; + + void emailSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + emailControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + emailControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void passwordSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + passwordControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + passwordControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + @override + LoginOOutput get model { + final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; + + if (!isValid) { + _logLoginOForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); + } + return LoginOOutput(email: _emailValue, password: _passwordValue); + } + + @override + void toggleDisabled({ + bool updateParent = true, + bool emitEvent = true, + }) { + final currentFormInstance = currentForm; + + if (currentFormInstance is! FormGroup) { + return; + } + + if (_disabled.isEmpty) { + currentFormInstance.controls.forEach((key, control) { + _disabled[key] = control.disabled; + }); + + currentForm.markAsDisabled( + updateParent: updateParent, emitEvent: emitEvent); + } else { + currentFormInstance.controls.forEach((key, control) { + if (_disabled[key] == false) { + currentFormInstance.controls[key]?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + + _disabled.remove(key); + }); + } + } + + @override + void submit({ + required void Function(LoginOOutput model) onValid, + void Function()? onNotValid, + }) { + currentForm.markAllAsTouched(); + if (currentForm.valid) { + onValid(model); + } else { + _logLoginOForm.info('Errors'); + _logLoginOForm.info('┗━━ ${form.errors}'); + onNotValid?.call(); + } + } + + AbstractControl get currentForm { + return path == null ? form : form.control(path!); + } + + @override + void updateValue( + LoginO? value, { + bool updateParent = true, + bool emitEvent = true, + }) => + form.updateValue(LoginOForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + + @override + void reset({ + LoginO? value, + bool updateParent = true, + bool emitEvent = true, + }) => + form.reset( + value: value != null ? formElements(value).rawValue : null, + updateParent: updateParent, + emitEvent: emitEvent); + + String pathBuilder(String? pathItem) => + [path, pathItem].whereType().join("."); + + static FormGroup formElements(LoginO? loginO) => FormGroup({ + emailControlName: FormControl( + value: loginO?.email, + validators: [RequiredValidator(), RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + passwordControlName: FormControl( + value: loginO?.password, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false) + }, + validators: [ + MustMatchValidator() + ], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false); +} + +@Rf() +@RfGroup(validators: [MustMatchValidator()]) +class LoginOOutput extends Equatable { + final String email; + final String password; + const LoginOOutput( + {@RfControl(validators: [RequiredValidator(), RequiredValidator()]) + required this.email, + @RfControl(validators: [RequiredValidator()]) required this.password}); + @override + List get props => [email, password]; +} + +class ReactiveLoginOFormArrayBuilder + extends StatelessWidget { + const ReactiveLoginOFormArrayBuilder({ + Key? key, + this.control, + this.formControl, + this.builder, + required this.itemBuilder, + }) : assert(control != null || formControl != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final FormArray? formControl; + + final FormArray? Function( + LoginOForm formModel)? control; + + final Widget Function( + BuildContext context, List itemList, LoginOForm formModel)? + builder; + + final Widget Function(BuildContext context, int i, + ReactiveLoginOFormArrayBuilderT? item, LoginOForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveLoginOForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + return ReactiveFormArray( + formArray: formControl ?? control?.call(formModel), + builder: (context, formArray, child) { + final values = formArray.controls.map((e) => e.value).toList(); + final itemList = values + .asMap() + .map((i, item) { + return MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + ); + }) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} + +class ReactiveLoginOFormFormGroupArrayBuilder< + ReactiveLoginOFormFormGroupArrayBuilderT> extends StatelessWidget { + const ReactiveLoginOFormFormGroupArrayBuilder({ + Key? key, + this.extended, + this.getExtended, + this.builder, + required this.itemBuilder, + }) : assert(extended != null || getExtended != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final ExtendedControl?>, + List>? extended; + + final ExtendedControl?>, + List> + Function(LoginOForm formModel)? getExtended; + + final Widget Function( + BuildContext context, List itemList, LoginOForm formModel)? + builder; + + final Widget Function( + BuildContext context, + int i, + ReactiveLoginOFormFormGroupArrayBuilderT? item, + LoginOForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveLoginOForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + final value = (extended ?? getExtended?.call(formModel))!; + + return StreamBuilder?>?>( + stream: value.control.valueChanges, + builder: (context, snapshot) { + final itemList = + (value.value() ?? []) + .asMap() + .map((i, item) => MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + )) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} +'''; diff --git a/packages/generator_tests/test/doc/login_test.dart b/packages/generator_tests/test/doc/login_test.dart index b926b228..747364e1 100644 --- a/packages/generator_tests/test/doc/login_test.dart +++ b/packages/generator_tests/test/doc/login_test.dart @@ -9,7 +9,7 @@ void main() { group('doc', () { test( 'Login', - () async { + () async { return testGenerator( fileName: fileName, model: ''' @@ -30,7 +30,7 @@ void main() { } } - @Rf() + @Rf(output: false) @RfGroup( validators: [MustMatchValidator()], ) @@ -61,7 +61,7 @@ void main() { const generatedFile = r'''// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'login.dart'; @@ -202,6 +202,34 @@ class _LoginFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logLoginForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -239,7 +267,9 @@ class _LoginFormBuilderState extends State { } } -class LoginForm implements FormModel { +final _logLoginForm = Logger('LoginForm'); + +class LoginForm implements FormModel { LoginForm( this.form, this.path, @@ -392,9 +422,11 @@ class LoginForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'LoginForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logLoginForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Login(email: _emailValue, password: _passwordValue); } @@ -440,6 +472,8 @@ class LoginForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logLoginForm.info('Errors'); + _logLoginForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } diff --git a/packages/generator_tests/test/doc/mailing_list_test.dart b/packages/generator_tests/test/doc/mailing_list_test.dart index a8ad2e44..9c120e88 100644 --- a/packages/generator_tests/test/doc/mailing_list_test.dart +++ b/packages/generator_tests/test/doc/mailing_list_test.dart @@ -29,7 +29,7 @@ void main() { } } - @Rf() + @Rf(output: false) class MailingList { final List emailList; @@ -52,7 +52,7 @@ void main() { const generatedFile = r'''// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'mailing_list.dart'; @@ -197,6 +197,34 @@ class _MailingListFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logMailingListForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -234,7 +262,9 @@ class _MailingListFormBuilderState extends State { } } -class MailingListForm implements FormModel { +final _logMailingListForm = Logger('MailingListForm'); + +class MailingListForm implements FormModel { MailingListForm( this.form, this.path, @@ -360,9 +390,11 @@ class MailingListForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'MailingListForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logMailingListForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return MailingList(emailList: _emailListValue); } @@ -408,6 +440,8 @@ class MailingListForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logMailingListForm.info('Errors'); + _logMailingListForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } diff --git a/packages/generator_tests/test/doc/profile_test.dart b/packages/generator_tests/test/doc/profile_test.dart index 94ae6515..a21e0378 100644 --- a/packages/generator_tests/test/doc/profile_test.dart +++ b/packages/generator_tests/test/doc/profile_test.dart @@ -58,7 +58,7 @@ void main() { } @freezed - @Rf() + @Rf(output: false) class Profile with _\$Profile { const Profile._(); @@ -284,7 +284,7 @@ void main() { const generatedFile = r'''// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'profile.dart'; @@ -425,6 +425,34 @@ class _ProfileFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logProfileForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -462,7 +490,9 @@ class _ProfileFormBuilderState extends State { } } -class ProfileForm implements FormModel { +final _logProfileForm = Logger('ProfileForm'); + +class ProfileForm implements FormModel { ProfileForm( this.form, this.path, @@ -1193,9 +1223,11 @@ class ProfileForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'ProfileForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logProfileForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Profile(_idValue, anotherId: _anotherIdValue, @@ -1256,6 +1288,8 @@ class ProfileForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logProfileForm.info('Errors'); + _logProfileForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } @@ -1349,7 +1383,10 @@ class ProfileForm implements FormModel { disabled: false); } -class IncidenceFilterForm implements FormModel { +final _logIncidenceFilterForm = Logger('IncidenceFilterForm'); + +class IncidenceFilterForm + implements FormModel { IncidenceFilterForm( this.form, this.path, @@ -1789,9 +1826,11 @@ class IncidenceFilterForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'IncidenceFilterForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logIncidenceFilterForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return IncidenceFilter( isMobilityEnabled: _isMobilityEnabledValue, @@ -1843,6 +1882,8 @@ class IncidenceFilterForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logIncidenceFilterForm.info('Errors'); + _logIncidenceFilterForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } @@ -1924,7 +1965,10 @@ class IncidenceFilterForm implements FormModel { disabled: false); } -class ThresholdSettingForm implements FormModel { +final _logThresholdSettingForm = Logger('ThresholdSettingForm'); + +class ThresholdSettingForm + implements FormModel { ThresholdSettingForm( this.form, this.path, @@ -2077,9 +2121,11 @@ class ThresholdSettingForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'ThresholdSettingForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logThresholdSettingForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return ThresholdSetting(isEnabled: _isEnabledValue, value: _valueValue); } @@ -2125,6 +2171,8 @@ class ThresholdSettingForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logThresholdSettingForm.info('Errors'); + _logThresholdSettingForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } @@ -2179,7 +2227,9 @@ class ThresholdSettingForm implements FormModel { disabled: false); } -class TimerSettingForm implements FormModel { +final _logTimerSettingForm = Logger('TimerSettingForm'); + +class TimerSettingForm implements FormModel { TimerSettingForm( this.form, this.path, @@ -2332,9 +2382,11 @@ class TimerSettingForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'TimerSettingForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logTimerSettingForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return TimerSetting(isEnabled: _isEnabledValue, value: _valueValue); } @@ -2380,6 +2432,8 @@ class TimerSettingForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logTimerSettingForm.info('Errors'); + _logTimerSettingForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } diff --git a/packages/generator_tests/test/doc/renamed_basic_output_test.dart b/packages/generator_tests/test/doc/renamed_basic_output_test.dart new file mode 100644 index 00000000..12ed9923 --- /dev/null +++ b/packages/generator_tests/test/doc/renamed_basic_output_test.dart @@ -0,0 +1,717 @@ +@Timeout(Duration(seconds: 145)) +import 'package:test/test.dart'; + +import '../helpers.dart'; + +const fileName = 'renamed_basic_output'; + +void main() { + group('doc', () { + test( + 'Renamed basic Output', + () async { + return testGenerator( + fileName: fileName, + model: ''' + import 'package:flutter/material.dart'; + import 'package:reactive_forms/reactive_forms.dart'; + import 'package:reactive_forms/src/validators/required_validator.dart'; + import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; + import 'package:example/helpers.dart'; + + part '$fileName.gform.dart'; + + @Rf(name: 'SomeWiredName') + class RenamedBasicO { + final String? email; + + final String? password; + + RenamedBasicO({ + @RfControl( + validators: [RequiredValidator()], + ) + this.email, + @RfControl( + validators: [RequiredValidator()], + ) + this.password, + }); + } + ''', + generatedFile: generatedFile, + ); + }, + ); + }); +} + +const generatedFile = r'''// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'renamed_basic_output.dart'; + +// ************************************************************************** +// ReactiveFormsGenerator +// ************************************************************************** + +class ReactiveSomeWiredNameFormConsumer extends StatelessWidget { + const ReactiveSomeWiredNameFormConsumer({ + Key? key, + required this.builder, + this.child, + }) : super(key: key); + + final Widget? child; + + final Widget Function( + BuildContext context, SomeWiredNameForm formModel, Widget? child) builder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveSomeWiredNameForm.of(context); + + if (formModel is! SomeWiredNameForm) { + throw FormControlParentNotFoundException(this); + } + return builder(context, formModel, child); + } +} + +class SomeWiredNameFormInheritedStreamer extends InheritedStreamer { + const SomeWiredNameFormInheritedStreamer({ + Key? key, + required this.form, + required Stream stream, + required Widget child, + }) : super( + stream, + child, + key: key, + ); + + final SomeWiredNameForm form; +} + +class ReactiveSomeWiredNameForm extends StatelessWidget { + const ReactiveSomeWiredNameForm({ + Key? key, + required this.form, + required this.child, + this.canPop, + this.onPopInvoked, + }) : super(key: key); + + final Widget child; + + final SomeWiredNameForm form; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + static SomeWiredNameForm? of( + BuildContext context, { + bool listen = true, + }) { + if (listen) { + return context + .dependOnInheritedWidgetOfExactType< + SomeWiredNameFormInheritedStreamer>() + ?.form; + } + + final element = context.getElementForInheritedWidgetOfExactType< + SomeWiredNameFormInheritedStreamer>(); + return element == null + ? null + : (element.widget as SomeWiredNameFormInheritedStreamer).form; + } + + @override + Widget build(BuildContext context) { + return SomeWiredNameFormInheritedStreamer( + form: form, + stream: form.form.statusChanged, + child: ReactiveFormPopScope( + canPop: canPop, + onPopInvoked: onPopInvoked, + child: child, + ), + ); + } +} + +extension ReactiveReactiveSomeWiredNameFormExt on BuildContext { + SomeWiredNameForm? someWiredNameFormWatch() => + ReactiveSomeWiredNameForm.of(this); + + SomeWiredNameForm? someWiredNameFormRead() => + ReactiveSomeWiredNameForm.of(this, listen: false); +} + +class SomeWiredNameFormBuilder extends StatefulWidget { + const SomeWiredNameFormBuilder({ + Key? key, + this.model, + this.child, + this.canPop, + this.onPopInvoked, + required this.builder, + this.initState, + }) : super(key: key); + + final RenamedBasicO? model; + + final Widget? child; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + final Widget Function( + BuildContext context, SomeWiredNameForm formModel, Widget? child) builder; + + final void Function(BuildContext context, SomeWiredNameForm formModel)? + initState; + + @override + _SomeWiredNameFormBuilderState createState() => + _SomeWiredNameFormBuilderState(); +} + +class _SomeWiredNameFormBuilderState extends State { + late SomeWiredNameForm _formModel; + + @override + void initState() { + _formModel = + SomeWiredNameForm(SomeWiredNameForm.formElements(widget.model), null); + + if (_formModel.form.disabled) { + _formModel.form.markAsDisabled(); + } + + widget.initState?.call(context, _formModel); + + _logSomeWiredNameForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + + super.initState(); + } + + @override + void didUpdateWidget(covariant SomeWiredNameFormBuilder oldWidget) { + if (widget.model != oldWidget.model) { + _formModel.updateValue(widget.model); + } + + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + _formModel.form.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ReactiveSomeWiredNameForm( + key: ObjectKey(_formModel), + form: _formModel, + // canPop: widget.canPop, + // onPopInvoked: widget.onPopInvoked, + child: ReactiveFormBuilder( + form: () => _formModel.form, + canPop: widget.canPop, + onPopInvoked: widget.onPopInvoked, + builder: (context, formGroup, child) => + widget.builder(context, _formModel, widget.child), + child: widget.child, + ), + ); + } +} + +final _logSomeWiredNameForm = Logger('SomeWiredNameForm'); + +class SomeWiredNameForm + implements FormModel { + SomeWiredNameForm( + this.form, + this.path, + ); + + static const String emailControlName = "email"; + + static const String passwordControlName = "password"; + + final FormGroup form; + + final String? path; + + final Map _disabled = {}; + + String emailControlPath() => pathBuilder(emailControlName); + + String passwordControlPath() => pathBuilder(passwordControlName); + + String get _emailValue => emailControl.value as String; + + String get _passwordValue => passwordControl.value as String; + + bool get containsEmail { + try { + form.control(emailControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsPassword { + try { + form.control(passwordControlPath()); + return true; + } catch (e) { + return false; + } + } + + Map? get emailErrors => emailControl.errors; + + Map? get passwordErrors => passwordControl.errors; + + void get emailFocus => form.focus(emailControlPath()); + + void get passwordFocus => form.focus(passwordControlPath()); + + void emailRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsEmail) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + emailControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + emailControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void passwordRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsPassword) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + passwordControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + passwordControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void emailValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + emailControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void passwordValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + passwordControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void emailValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + emailControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void passwordValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + passwordControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void emailValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + emailControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void passwordValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + passwordControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + FormControl get emailControl => + form.control(emailControlPath()) as FormControl; + + FormControl get passwordControl => + form.control(passwordControlPath()) as FormControl; + + void emailSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + emailControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + emailControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void passwordSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + passwordControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + passwordControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + @override + RenamedBasicOOutput get model { + final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; + + if (!isValid) { + _logSomeWiredNameForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); + } + return RenamedBasicOOutput(email: _emailValue, password: _passwordValue); + } + + @override + void toggleDisabled({ + bool updateParent = true, + bool emitEvent = true, + }) { + final currentFormInstance = currentForm; + + if (currentFormInstance is! FormGroup) { + return; + } + + if (_disabled.isEmpty) { + currentFormInstance.controls.forEach((key, control) { + _disabled[key] = control.disabled; + }); + + currentForm.markAsDisabled( + updateParent: updateParent, emitEvent: emitEvent); + } else { + currentFormInstance.controls.forEach((key, control) { + if (_disabled[key] == false) { + currentFormInstance.controls[key]?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + + _disabled.remove(key); + }); + } + } + + @override + void submit({ + required void Function(RenamedBasicOOutput model) onValid, + void Function()? onNotValid, + }) { + currentForm.markAllAsTouched(); + if (currentForm.valid) { + onValid(model); + } else { + _logSomeWiredNameForm.info('Errors'); + _logSomeWiredNameForm.info('┗━━ ${form.errors}'); + onNotValid?.call(); + } + } + + AbstractControl get currentForm { + return path == null ? form : form.control(path!); + } + + @override + void updateValue( + RenamedBasicO? value, { + bool updateParent = true, + bool emitEvent = true, + }) => + form.updateValue(SomeWiredNameForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + + @override + void reset({ + RenamedBasicO? value, + bool updateParent = true, + bool emitEvent = true, + }) => + form.reset( + value: value != null ? formElements(value).rawValue : null, + updateParent: updateParent, + emitEvent: emitEvent); + + String pathBuilder(String? pathItem) => + [path, pathItem].whereType().join("."); + + static FormGroup formElements(RenamedBasicO? renamedBasicO) => FormGroup({ + emailControlName: FormControl( + value: renamedBasicO?.email, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + passwordControlName: FormControl( + value: renamedBasicO?.password, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false) + }, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false); +} + +@Rf(name: 'SomeWiredName') +class RenamedBasicOOutput { + final String email; + final String password; + RenamedBasicOOutput( + {@RfControl(validators: [RequiredValidator()]) required this.email, + @RfControl(validators: [RequiredValidator()]) required this.password}); +} + +class ReactiveSomeWiredNameFormArrayBuilder< + ReactiveSomeWiredNameFormArrayBuilderT> extends StatelessWidget { + const ReactiveSomeWiredNameFormArrayBuilder({ + Key? key, + this.control, + this.formControl, + this.builder, + required this.itemBuilder, + }) : assert(control != null || formControl != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final FormArray? formControl; + + final FormArray? Function( + SomeWiredNameForm formModel)? control; + + final Widget Function(BuildContext context, List itemList, + SomeWiredNameForm formModel)? builder; + + final Widget Function( + BuildContext context, + int i, + ReactiveSomeWiredNameFormArrayBuilderT? item, + SomeWiredNameForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveSomeWiredNameForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + return ReactiveFormArray( + formArray: formControl ?? control?.call(formModel), + builder: (context, formArray, child) { + final values = formArray.controls.map((e) => e.value).toList(); + final itemList = values + .asMap() + .map((i, item) { + return MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + ); + }) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} + +class ReactiveSomeWiredNameFormFormGroupArrayBuilder< + ReactiveSomeWiredNameFormFormGroupArrayBuilderT> extends StatelessWidget { + const ReactiveSomeWiredNameFormFormGroupArrayBuilder({ + Key? key, + this.extended, + this.getExtended, + this.builder, + required this.itemBuilder, + }) : assert(extended != null || getExtended != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final ExtendedControl?>, + List>? extended; + + final ExtendedControl?>, + List> + Function(SomeWiredNameForm formModel)? getExtended; + + final Widget Function(BuildContext context, List itemList, + SomeWiredNameForm formModel)? builder; + + final Widget Function( + BuildContext context, + int i, + ReactiveSomeWiredNameFormFormGroupArrayBuilderT? item, + SomeWiredNameForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveSomeWiredNameForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + final value = (extended ?? getExtended?.call(formModel))!; + + return StreamBuilder?>?>( + stream: value.control.valueChanges, + builder: (context, snapshot) { + final itemList = (value.value() ?? + []) + .asMap() + .map((i, item) => MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + )) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} +'''; diff --git a/packages/generator_tests/test/doc/renamed_basic_test.dart b/packages/generator_tests/test/doc/renamed_basic_test.dart index 589b440c..2381d8b2 100644 --- a/packages/generator_tests/test/doc/renamed_basic_test.dart +++ b/packages/generator_tests/test/doc/renamed_basic_test.dart @@ -21,7 +21,7 @@ void main() { part '$fileName.gform.dart'; - @Rf(name: 'SomeWiredName') + @Rf(output: false, name: 'SomeWiredName') class RenamedBasic { final String email; @@ -49,7 +49,7 @@ void main() { const generatedFile = r'''// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'renamed_basic.dart'; @@ -196,6 +196,34 @@ class _SomeWiredNameFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logSomeWiredNameForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -233,7 +261,9 @@ class _SomeWiredNameFormBuilderState extends State { } } -class SomeWiredNameForm implements FormModel { +final _logSomeWiredNameForm = Logger('SomeWiredNameForm'); + +class SomeWiredNameForm implements FormModel { SomeWiredNameForm( this.form, this.path, @@ -386,9 +416,11 @@ class SomeWiredNameForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'SomeWiredNameForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logSomeWiredNameForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return RenamedBasic(email: _emailValue, password: _passwordValue); } @@ -434,6 +466,8 @@ class SomeWiredNameForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logSomeWiredNameForm.info('Errors'); + _logSomeWiredNameForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } diff --git a/packages/generator_tests/test/doc/url_list_output_test.dart b/packages/generator_tests/test/doc/url_list_output_test.dart new file mode 100644 index 00000000..41c409ef --- /dev/null +++ b/packages/generator_tests/test/doc/url_list_output_test.dart @@ -0,0 +1,1017 @@ +@Timeout(Duration(seconds: 145)) +import 'package:test/test.dart'; + +import '../helpers.dart'; + +const fileName = 'url_output'; + +void main() { + group('reactive_forms_generator', () { + test( + 'Animated URL list Output', + () async { + return testGenerator( + fileName: fileName, + model: ''' + import 'package:flutter/material.dart'; + import 'package:reactive_forms/reactive_forms.dart'; + import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; + + part '$fileName.gform.dart'; + + @Rf() + class UrlO { + final List urlList; + + UrlO({@RfArray() this.urlList = const []}); + } + + @RfGroup() + class UrlEntityO { + final String? label; + final String? url; + + UrlEntityO({ + @RfControl(validators: [ + RequiredValidator(), + ]) + this.label, + @RfControl(validators: [ + RequiredValidator(), + ]) + this.url, + }); + } + ''', + generatedFile: generatedFile, + ); + }, + ); + }); +} + +const generatedFile = r'''// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'url_output.dart'; + +// ************************************************************************** +// ReactiveFormsGenerator +// ************************************************************************** + +class ReactiveUrlOFormConsumer extends StatelessWidget { + const ReactiveUrlOFormConsumer({ + Key? key, + required this.builder, + this.child, + }) : super(key: key); + + final Widget? child; + + final Widget Function(BuildContext context, UrlOForm formModel, Widget? child) + builder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveUrlOForm.of(context); + + if (formModel is! UrlOForm) { + throw FormControlParentNotFoundException(this); + } + return builder(context, formModel, child); + } +} + +class UrlOFormInheritedStreamer extends InheritedStreamer { + const UrlOFormInheritedStreamer({ + Key? key, + required this.form, + required Stream stream, + required Widget child, + }) : super( + stream, + child, + key: key, + ); + + final UrlOForm form; +} + +class ReactiveUrlOForm extends StatelessWidget { + const ReactiveUrlOForm({ + Key? key, + required this.form, + required this.child, + this.canPop, + this.onPopInvoked, + }) : super(key: key); + + final Widget child; + + final UrlOForm form; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + static UrlOForm? of( + BuildContext context, { + bool listen = true, + }) { + if (listen) { + return context + .dependOnInheritedWidgetOfExactType() + ?.form; + } + + final element = context + .getElementForInheritedWidgetOfExactType(); + return element == null + ? null + : (element.widget as UrlOFormInheritedStreamer).form; + } + + @override + Widget build(BuildContext context) { + return UrlOFormInheritedStreamer( + form: form, + stream: form.form.statusChanged, + child: ReactiveFormPopScope( + canPop: canPop, + onPopInvoked: onPopInvoked, + child: child, + ), + ); + } +} + +extension ReactiveReactiveUrlOFormExt on BuildContext { + UrlOForm? urlOFormWatch() => ReactiveUrlOForm.of(this); + + UrlOForm? urlOFormRead() => ReactiveUrlOForm.of(this, listen: false); +} + +class UrlOFormBuilder extends StatefulWidget { + const UrlOFormBuilder({ + Key? key, + this.model, + this.child, + this.canPop, + this.onPopInvoked, + required this.builder, + this.initState, + }) : super(key: key); + + final UrlO? model; + + final Widget? child; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + final Widget Function(BuildContext context, UrlOForm formModel, Widget? child) + builder; + + final void Function(BuildContext context, UrlOForm formModel)? initState; + + @override + _UrlOFormBuilderState createState() => _UrlOFormBuilderState(); +} + +class _UrlOFormBuilderState extends State { + late UrlOForm _formModel; + + @override + void initState() { + _formModel = UrlOForm(UrlOForm.formElements(widget.model), null); + + if (_formModel.form.disabled) { + _formModel.form.markAsDisabled(); + } + + widget.initState?.call(context, _formModel); + + _logUrlOForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + + super.initState(); + } + + @override + void didUpdateWidget(covariant UrlOFormBuilder oldWidget) { + if (widget.model != oldWidget.model) { + _formModel.updateValue(widget.model); + } + + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + _formModel.form.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ReactiveUrlOForm( + key: ObjectKey(_formModel), + form: _formModel, + // canPop: widget.canPop, + // onPopInvoked: widget.onPopInvoked, + child: ReactiveFormBuilder( + form: () => _formModel.form, + canPop: widget.canPop, + onPopInvoked: widget.onPopInvoked, + builder: (context, formGroup, child) => + widget.builder(context, _formModel, widget.child), + child: widget.child, + ), + ); + } +} + +final _logUrlOForm = Logger('UrlOForm'); + +class UrlOForm implements FormModel { + UrlOForm( + this.form, + this.path, + ); + + static const String urlListControlName = "urlList"; + + final FormGroup form; + + final String? path; + + final Map _disabled = {}; + + String urlListControlPath() => pathBuilder(urlListControlName); + + List get _urlListValue => + urlListUrlEntityOForm.map((e) => e.model).toList(); + + bool get containsUrlList { + try { + form.control(urlListControlPath()); + return true; + } catch (e) { + return false; + } + } + + Map get urlListErrors => urlListControl.errors; + + void get urlListFocus => form.focus(urlListControlPath()); + + void urlListValueUpdate( + List value, { + bool updateParent = true, + bool emitEvent = true, + }) { + final localValue = (value); + if (localValue.isEmpty) { + urlListClear(updateParent: updateParent, emitEvent: emitEvent); + + return; + } + + final toUpdate = []; + final toAdd = []; + + localValue.asMap().forEach((k, v) { + final values = (urlListControl.controls).map((e) => e.value).toList(); + + if (urlListUrlEntityOForm.asMap().containsKey(k) && + values.asMap().containsKey(k)) { + toUpdate.add(v); + } else { + toAdd.add(v); + } + }); + + if (toUpdate.isNotEmpty) { + urlListControl.updateValue( + toUpdate.map((e) => UrlEntityOForm.formElements(e).rawValue).toList(), + updateParent: updateParent, + emitEvent: emitEvent); + } + + if (toAdd.isNotEmpty) { + toAdd.forEach((e) { + urlListControl.add(UrlEntityOForm.formElements(e), + updateParent: updateParent, emitEvent: emitEvent); + }); + } + } + + void urlListInsert( + int i, + UrlEntityO value, { + bool updateParent = true, + bool emitEvent = true, + }) { + final values = (urlListControl.controls).map((e) => e.value).toList(); + if (values.length < i) { + addUrlListItem(value); + return; + } + + urlListControl.insert( + i, + UrlEntityOForm.formElements(value), + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + + void urlListClear({ + bool updateParent = true, + bool emitEvent = true, + }) { + urlListUrlEntityOForm.clear(); + urlListControl.clear(updateParent: updateParent, emitEvent: emitEvent); + } + + void urlListValuePatch( + List value, { + bool updateParent = true, + bool emitEvent = true, + }) { + final keys = urlListUrlEntityOForm.asMap().keys; + + final toPatch = []; + (value).asMap().forEach( + (k, v) { + if (keys.contains(k)) { + toPatch.add(v); + } + }, + ); + + urlListControl.patchValue( + toPatch.map((e) => UrlEntityOForm.formElements(e).rawValue).toList(), + updateParent: updateParent, + emitEvent: emitEvent); + } + + void urlListValueReset( + List value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + urlListControl.reset( + value: value + .map((e) => UrlEntityOForm.formElements(e).rawValue) + .toList(), + updateParent: updateParent, + emitEvent: emitEvent); + + FormArray> get urlListControl => + form.control(urlListControlPath()) as FormArray>; + + List get urlListUrlEntityOForm { + final values = (urlListControl.controls).map((e) => e.value).toList(); + + return values + .asMap() + .map((k, v) => + MapEntry(k, UrlEntityOForm(form, pathBuilder("urlList.$k")))) + .values + .toList(); + } + + void urlListSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + urlListControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + urlListControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + ExtendedControl?>, List> + get urlListExtendedControl => + ExtendedControl?>, List>( + form.control(urlListControlPath()) + as FormArray>, + () => urlListUrlEntityOForm); + + void addUrlListItem(UrlEntityO value) { + urlListControl.add(UrlEntityOForm.formElements(value)); + } + + void removeUrlListItemAtIndex(int i) { + if ((urlListControl.value ?? []).length > i) { + urlListControl.removeAt(i); + } + } + + void addUrlListItemList(List value) { + value.map((e) => addUrlListItem(e)); + } + + @override + UrlOOutput get model { + final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; + + if (!isValid) { + _logUrlOForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); + } + return UrlOOutput(urlList: _urlListValue); + } + + @override + void toggleDisabled({ + bool updateParent = true, + bool emitEvent = true, + }) { + final currentFormInstance = currentForm; + + if (currentFormInstance is! FormGroup) { + return; + } + + if (_disabled.isEmpty) { + currentFormInstance.controls.forEach((key, control) { + _disabled[key] = control.disabled; + }); + + urlListUrlEntityOForm.forEach((e) => e.toggleDisabled()); + + currentForm.markAsDisabled( + updateParent: updateParent, emitEvent: emitEvent); + } else { + urlListUrlEntityOForm.forEach((e) => e.toggleDisabled()); + + currentFormInstance.controls.forEach((key, control) { + if (_disabled[key] == false) { + currentFormInstance.controls[key]?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + + _disabled.remove(key); + }); + } + } + + @override + void submit({ + required void Function(UrlOOutput model) onValid, + void Function()? onNotValid, + }) { + currentForm.markAllAsTouched(); + if (currentForm.valid) { + onValid(model); + } else { + _logUrlOForm.info('Errors'); + _logUrlOForm.info('┗━━ ${form.errors}'); + onNotValid?.call(); + } + } + + AbstractControl get currentForm { + return path == null ? form : form.control(path!); + } + + @override + void updateValue( + UrlO? value, { + bool updateParent = true, + bool emitEvent = true, + }) => + form.updateValue(UrlOForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + + @override + void reset({ + UrlO? value, + bool updateParent = true, + bool emitEvent = true, + }) => + form.reset( + value: value != null ? formElements(value).rawValue : null, + updateParent: updateParent, + emitEvent: emitEvent); + + String pathBuilder(String? pathItem) => + [path, pathItem].whereType().join("."); + + static FormGroup formElements(UrlO? urlO) => FormGroup({ + urlListControlName: FormArray( + (urlO?.urlList ?? []) + .map((e) => UrlEntityOForm.formElements(e)) + .toList(), + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false) + }, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false); +} + +final _logUrlEntityOForm = Logger('UrlEntityOForm'); + +class UrlEntityOForm implements FormModel { + UrlEntityOForm( + this.form, + this.path, + ); + + static const String labelControlName = "label"; + + static const String urlControlName = "url"; + + final FormGroup form; + + final String? path; + + final Map _disabled = {}; + + String labelControlPath() => pathBuilder(labelControlName); + + String urlControlPath() => pathBuilder(urlControlName); + + String get _labelValue => labelControl.value as String; + + String get _urlValue => urlControl.value as String; + + bool get containsLabel { + try { + form.control(labelControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsUrl { + try { + form.control(urlControlPath()); + return true; + } catch (e) { + return false; + } + } + + Map? get labelErrors => labelControl.errors; + + Map? get urlErrors => urlControl.errors; + + void get labelFocus => form.focus(labelControlPath()); + + void get urlFocus => form.focus(urlControlPath()); + + void labelRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsLabel) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + labelControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + labelControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void urlRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsUrl) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + urlControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + urlControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void labelValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + labelControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void urlValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + urlControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void labelValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + labelControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void urlValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + urlControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void labelValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + labelControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void urlValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + urlControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + FormControl get labelControl => + form.control(labelControlPath()) as FormControl; + + FormControl get urlControl => + form.control(urlControlPath()) as FormControl; + + void labelSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + labelControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + labelControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void urlSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + urlControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + urlControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + @override + UrlEntityOOutput get model { + final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; + + if (!isValid) { + _logUrlEntityOForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); + } + return UrlEntityOOutput(label: _labelValue, url: _urlValue); + } + + @override + void toggleDisabled({ + bool updateParent = true, + bool emitEvent = true, + }) { + final currentFormInstance = currentForm; + + if (currentFormInstance is! FormGroup) { + return; + } + + if (_disabled.isEmpty) { + currentFormInstance.controls.forEach((key, control) { + _disabled[key] = control.disabled; + }); + + currentForm.markAsDisabled( + updateParent: updateParent, emitEvent: emitEvent); + } else { + currentFormInstance.controls.forEach((key, control) { + if (_disabled[key] == false) { + currentFormInstance.controls[key]?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + + _disabled.remove(key); + }); + } + } + + @override + void submit({ + required void Function(UrlEntityOOutput model) onValid, + void Function()? onNotValid, + }) { + currentForm.markAllAsTouched(); + if (currentForm.valid) { + onValid(model); + } else { + _logUrlEntityOForm.info('Errors'); + _logUrlEntityOForm.info('┗━━ ${form.errors}'); + onNotValid?.call(); + } + } + + AbstractControl get currentForm { + return path == null ? form : form.control(path!); + } + + @override + void updateValue( + UrlEntityO? value, { + bool updateParent = true, + bool emitEvent = true, + }) => + form.updateValue(UrlEntityOForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + + @override + void reset({ + UrlEntityO? value, + bool updateParent = true, + bool emitEvent = true, + }) => + form.reset( + value: value != null ? formElements(value).rawValue : null, + updateParent: updateParent, + emitEvent: emitEvent); + + String pathBuilder(String? pathItem) => + [path, pathItem].whereType().join("."); + + static FormGroup formElements(UrlEntityO? urlEntityO) => FormGroup({ + labelControlName: FormControl( + value: urlEntityO?.label, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + urlControlName: FormControl( + value: urlEntityO?.url, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false) + }, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false); +} + +@Rf() +class UrlOOutput { + final List urlList; + UrlOOutput({@RfArray() this.urlList = const []}); +} + +@RfGroup() +class UrlEntityOOutput { + final String label; + final String url; + UrlEntityOOutput( + {@RfControl(validators: [RequiredValidator()]) required this.label, + @RfControl(validators: [RequiredValidator()]) required this.url}); +} + +class ReactiveUrlOFormArrayBuilder + extends StatelessWidget { + const ReactiveUrlOFormArrayBuilder({ + Key? key, + this.control, + this.formControl, + this.builder, + required this.itemBuilder, + }) : assert(control != null || formControl != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final FormArray? formControl; + + final FormArray? Function(UrlOForm formModel)? + control; + + final Widget Function( + BuildContext context, List itemList, UrlOForm formModel)? builder; + + final Widget Function(BuildContext context, int i, + ReactiveUrlOFormArrayBuilderT? item, UrlOForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveUrlOForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + return ReactiveFormArray( + formArray: formControl ?? control?.call(formModel), + builder: (context, formArray, child) { + final values = formArray.controls.map((e) => e.value).toList(); + final itemList = values + .asMap() + .map((i, item) { + return MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + ); + }) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} + +class ReactiveUrlOFormFormGroupArrayBuilder< + ReactiveUrlOFormFormGroupArrayBuilderT> extends StatelessWidget { + const ReactiveUrlOFormFormGroupArrayBuilder({ + Key? key, + this.extended, + this.getExtended, + this.builder, + required this.itemBuilder, + }) : assert(extended != null || getExtended != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final ExtendedControl?>, + List>? extended; + + final ExtendedControl?>, + List> + Function(UrlOForm formModel)? getExtended; + + final Widget Function( + BuildContext context, List itemList, UrlOForm formModel)? builder; + + final Widget Function( + BuildContext context, + int i, + ReactiveUrlOFormFormGroupArrayBuilderT? item, + UrlOForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveUrlOForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + final value = (extended ?? getExtended?.call(formModel))!; + + return StreamBuilder?>?>( + stream: value.control.valueChanges, + builder: (context, snapshot) { + final itemList = + (value.value() ?? []) + .asMap() + .map((i, item) => MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + )) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} +'''; diff --git a/packages/generator_tests/test/doc/user_profile_output_test.dart b/packages/generator_tests/test/doc/user_profile_output_test.dart new file mode 100644 index 00000000..509c9a18 --- /dev/null +++ b/packages/generator_tests/test/doc/user_profile_output_test.dart @@ -0,0 +1,1430 @@ +@Timeout(Duration(seconds: 145)) +import 'package:test/test.dart'; + +import '../helpers.dart'; + +const fileName = 'user_profile_output'; + +void main() { + group('doc', () { + test( + 'User profile', + () async { + return testGenerator( + fileName: fileName, + model: ''' + import 'package:flutter/material.dart'; + import 'package:reactive_forms/reactive_forms.dart'; + import 'package:reactive_forms/src/validators/required_validator.dart'; + import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; + + part '$fileName.gform.dart'; + + @Rf() + class UserProfileO { + final String id; + + final String? firstName; + + final String? lastName; + + final AddressO home; + + final AddressO? office; + + UserProfileO({ + required this.id, + @RfControl( + validators: [RequiredValidator()], + ) + this.firstName, + @RfControl( + validators: [RequiredValidator()], + ) + this.lastName, + required this.home, + this.office, + }); + } + + @RfGroup() + class AddressO { + final String? street; + + final String? city; + + final String? zip; + + AddressO({ + @RfControl() this.street, + @RfControl( + validators: [RequiredValidator()], + ) + this.city, + @RfControl() this.zip, + }); + } + ''', + generatedFile: generatedFile, + ); + }, + ); + }); +} + +const generatedFile = r'''// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'user_profile_output.dart'; + +// ************************************************************************** +// ReactiveFormsGenerator +// ************************************************************************** + +class ReactiveUserProfileOFormConsumer extends StatelessWidget { + const ReactiveUserProfileOFormConsumer({ + Key? key, + required this.builder, + this.child, + }) : super(key: key); + + final Widget? child; + + final Widget Function( + BuildContext context, UserProfileOForm formModel, Widget? child) builder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveUserProfileOForm.of(context); + + if (formModel is! UserProfileOForm) { + throw FormControlParentNotFoundException(this); + } + return builder(context, formModel, child); + } +} + +class UserProfileOFormInheritedStreamer extends InheritedStreamer { + const UserProfileOFormInheritedStreamer({ + Key? key, + required this.form, + required Stream stream, + required Widget child, + }) : super( + stream, + child, + key: key, + ); + + final UserProfileOForm form; +} + +class ReactiveUserProfileOForm extends StatelessWidget { + const ReactiveUserProfileOForm({ + Key? key, + required this.form, + required this.child, + this.canPop, + this.onPopInvoked, + }) : super(key: key); + + final Widget child; + + final UserProfileOForm form; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + static UserProfileOForm? of( + BuildContext context, { + bool listen = true, + }) { + if (listen) { + return context + .dependOnInheritedWidgetOfExactType< + UserProfileOFormInheritedStreamer>() + ?.form; + } + + final element = context.getElementForInheritedWidgetOfExactType< + UserProfileOFormInheritedStreamer>(); + return element == null + ? null + : (element.widget as UserProfileOFormInheritedStreamer).form; + } + + @override + Widget build(BuildContext context) { + return UserProfileOFormInheritedStreamer( + form: form, + stream: form.form.statusChanged, + child: ReactiveFormPopScope( + canPop: canPop, + onPopInvoked: onPopInvoked, + child: child, + ), + ); + } +} + +extension ReactiveReactiveUserProfileOFormExt on BuildContext { + UserProfileOForm? userProfileOFormWatch() => + ReactiveUserProfileOForm.of(this); + + UserProfileOForm? userProfileOFormRead() => + ReactiveUserProfileOForm.of(this, listen: false); +} + +class UserProfileOFormBuilder extends StatefulWidget { + const UserProfileOFormBuilder({ + Key? key, + this.model, + this.child, + this.canPop, + this.onPopInvoked, + required this.builder, + this.initState, + }) : super(key: key); + + final UserProfileO? model; + + final Widget? child; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + final Widget Function( + BuildContext context, UserProfileOForm formModel, Widget? child) builder; + + final void Function(BuildContext context, UserProfileOForm formModel)? + initState; + + @override + _UserProfileOFormBuilderState createState() => + _UserProfileOFormBuilderState(); +} + +class _UserProfileOFormBuilderState extends State { + late UserProfileOForm _formModel; + + @override + void initState() { + _formModel = + UserProfileOForm(UserProfileOForm.formElements(widget.model), null); + + if (_formModel.form.disabled) { + _formModel.form.markAsDisabled(); + } + + widget.initState?.call(context, _formModel); + + _logUserProfileOForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + + super.initState(); + } + + @override + void didUpdateWidget(covariant UserProfileOFormBuilder oldWidget) { + if (widget.model != oldWidget.model) { + _formModel.updateValue(widget.model); + } + + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + _formModel.form.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ReactiveUserProfileOForm( + key: ObjectKey(_formModel), + form: _formModel, + // canPop: widget.canPop, + // onPopInvoked: widget.onPopInvoked, + child: ReactiveFormBuilder( + form: () => _formModel.form, + canPop: widget.canPop, + onPopInvoked: widget.onPopInvoked, + builder: (context, formGroup, child) => + widget.builder(context, _formModel, widget.child), + child: widget.child, + ), + ); + } +} + +final _logUserProfileOForm = Logger('UserProfileOForm'); + +class UserProfileOForm implements FormModel { + UserProfileOForm( + this.form, + this.path, + ); + + static const String idControlName = "id"; + + static const String firstNameControlName = "firstName"; + + static const String lastNameControlName = "lastName"; + + static const String homeControlName = "home"; + + static const String officeControlName = "office"; + + final FormGroup form; + + final String? path; + + final Map _disabled = {}; + + String idControlPath() => pathBuilder(idControlName); + + String firstNameControlPath() => pathBuilder(firstNameControlName); + + String lastNameControlPath() => pathBuilder(lastNameControlName); + + String homeControlPath() => pathBuilder(homeControlName); + + String officeControlPath() => pathBuilder(officeControlName); + + String get _idValue => idControl.value as String; + + String get _firstNameValue => firstNameControl.value as String; + + String get _lastNameValue => lastNameControl.value as String; + + AddressOOutput get _homeValue => homeForm.model; + + AddressOOutput? get _officeValue => officeForm.model; + + bool get containsId { + try { + form.control(idControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsFirstName { + try { + form.control(firstNameControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsLastName { + try { + form.control(lastNameControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsHome { + try { + form.control(homeControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsOffice { + try { + form.control(officeControlPath()); + return true; + } catch (e) { + return false; + } + } + + Map get idErrors => idControl.errors; + + Map? get firstNameErrors => firstNameControl.errors; + + Map? get lastNameErrors => lastNameControl.errors; + + Map get homeErrors => homeControl.errors; + + Map? get officeErrors => officeControl?.errors; + + void get idFocus => form.focus(idControlPath()); + + void get firstNameFocus => form.focus(firstNameControlPath()); + + void get lastNameFocus => form.focus(lastNameControlPath()); + + void get homeFocus => form.focus(homeControlPath()); + + void get officeFocus => form.focus(officeControlPath()); + + void firstNameRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsFirstName) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + firstNameControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + firstNameControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void lastNameRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsLastName) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + lastNameControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + lastNameControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void officeRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsOffice) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + officeControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + officeControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void idValueUpdate( + String value, { + bool updateParent = true, + bool emitEvent = true, + }) { + idControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void firstNameValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + firstNameControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void lastNameValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + lastNameControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void homeValueUpdate( + AddressO value, { + bool updateParent = true, + bool emitEvent = true, + }) { + homeControl.updateValue(AddressOForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + } + + void officeValueUpdate( + AddressO? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + officeControl?.updateValue(AddressOForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + } + + void idValuePatch( + String value, { + bool updateParent = true, + bool emitEvent = true, + }) { + idControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void firstNameValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + firstNameControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void lastNameValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + lastNameControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void homeValuePatch( + AddressO value, { + bool updateParent = true, + bool emitEvent = true, + }) { + homeControl.updateValue(AddressOForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + } + + void officeValuePatch( + AddressO? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + officeControl?.updateValue(AddressOForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + } + + void idValueReset( + String value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + idControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void firstNameValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + firstNameControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void lastNameValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + lastNameControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void homeValueReset( + AddressO value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + homeControl.reset( + value: AddressOForm.formElements(value).rawValue, + updateParent: updateParent, + emitEvent: emitEvent); + + void officeValueReset( + AddressO? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + officeControl?.reset( + value: AddressOForm.formElements(value).rawValue, + updateParent: updateParent, + emitEvent: emitEvent); + + FormControl get idControl => + form.control(idControlPath()) as FormControl; + + FormControl get firstNameControl => + form.control(firstNameControlPath()) as FormControl; + + FormControl get lastNameControl => + form.control(lastNameControlPath()) as FormControl; + + FormGroup get homeControl => form.control(homeControlPath()) as FormGroup; + + FormGroup? get officeControl => + containsOffice ? form.control(officeControlPath()) as FormGroup? : null; + + AddressOForm get homeForm => AddressOForm(form, pathBuilder('home')); + + AddressOForm get officeForm => AddressOForm(form, pathBuilder('office')); + + void idSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + idControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + idControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void firstNameSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + firstNameControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + firstNameControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void lastNameSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + lastNameControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + lastNameControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void homeSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + homeControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + homeControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void officeSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + officeControl?.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + officeControl?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + @override + UserProfileOOutput get model { + final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; + + if (!isValid) { + _logUserProfileOForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); + } + return UserProfileOOutput( + id: _idValue, + firstName: _firstNameValue, + lastName: _lastNameValue, + home: _homeValue, + office: _officeValue); + } + + @override + void toggleDisabled({ + bool updateParent = true, + bool emitEvent = true, + }) { + final currentFormInstance = currentForm; + + if (currentFormInstance is! FormGroup) { + return; + } + + if (_disabled.isEmpty) { + currentFormInstance.controls.forEach((key, control) { + _disabled[key] = control.disabled; + }); + + homeForm.toggleDisabled(); + officeForm.toggleDisabled(); + currentForm.markAsDisabled( + updateParent: updateParent, emitEvent: emitEvent); + } else { + homeForm.toggleDisabled(); + officeForm.toggleDisabled(); + currentFormInstance.controls.forEach((key, control) { + if (_disabled[key] == false) { + currentFormInstance.controls[key]?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + + _disabled.remove(key); + }); + } + } + + @override + void submit({ + required void Function(UserProfileOOutput model) onValid, + void Function()? onNotValid, + }) { + currentForm.markAllAsTouched(); + if (currentForm.valid) { + onValid(model); + } else { + _logUserProfileOForm.info('Errors'); + _logUserProfileOForm.info('┗━━ ${form.errors}'); + onNotValid?.call(); + } + } + + AbstractControl get currentForm { + return path == null ? form : form.control(path!); + } + + @override + void updateValue( + UserProfileO? value, { + bool updateParent = true, + bool emitEvent = true, + }) => + form.updateValue(UserProfileOForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + + @override + void reset({ + UserProfileO? value, + bool updateParent = true, + bool emitEvent = true, + }) => + form.reset( + value: value != null ? formElements(value).rawValue : null, + updateParent: updateParent, + emitEvent: emitEvent); + + String pathBuilder(String? pathItem) => + [path, pathItem].whereType().join("."); + + static FormGroup formElements(UserProfileO? userProfileO) => FormGroup({ + idControlName: FormControl( + value: userProfileO?.id, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + firstNameControlName: FormControl( + value: userProfileO?.firstName, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + lastNameControlName: FormControl( + value: userProfileO?.lastName, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + homeControlName: AddressOForm.formElements(userProfileO?.home), + officeControlName: AddressOForm.formElements(userProfileO?.office) + }, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false); +} + +final _logAddressOForm = Logger('AddressOForm'); + +class AddressOForm implements FormModel { + AddressOForm( + this.form, + this.path, + ); + + static const String streetControlName = "street"; + + static const String cityControlName = "city"; + + static const String zipControlName = "zip"; + + final FormGroup form; + + final String? path; + + final Map _disabled = {}; + + String streetControlPath() => pathBuilder(streetControlName); + + String cityControlPath() => pathBuilder(cityControlName); + + String zipControlPath() => pathBuilder(zipControlName); + + String? get _streetValue => streetControl?.value; + + String get _cityValue => cityControl.value as String; + + String? get _zipValue => zipControl?.value; + + bool get containsStreet { + try { + form.control(streetControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsCity { + try { + form.control(cityControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsZip { + try { + form.control(zipControlPath()); + return true; + } catch (e) { + return false; + } + } + + Map? get streetErrors => streetControl?.errors; + + Map? get cityErrors => cityControl.errors; + + Map? get zipErrors => zipControl?.errors; + + void get streetFocus => form.focus(streetControlPath()); + + void get cityFocus => form.focus(cityControlPath()); + + void get zipFocus => form.focus(zipControlPath()); + + void streetRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsStreet) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + streetControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + streetControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void cityRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsCity) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + cityControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + cityControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void zipRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsZip) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + zipControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + zipControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void streetValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + streetControl?.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void cityValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + cityControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void zipValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + zipControl?.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void streetValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + streetControl?.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void cityValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + cityControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void zipValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + zipControl?.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void streetValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + streetControl?.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void cityValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + cityControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void zipValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + zipControl?.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + FormControl? get streetControl => containsStreet + ? form.control(streetControlPath()) as FormControl? + : null; + + FormControl get cityControl => + form.control(cityControlPath()) as FormControl; + + FormControl? get zipControl => containsZip + ? form.control(zipControlPath()) as FormControl? + : null; + + void streetSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + streetControl?.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + streetControl?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void citySetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + cityControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + cityControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void zipSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + zipControl?.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + zipControl?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + @override + AddressOOutput get model { + final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; + + if (!isValid) { + _logAddressOForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); + } + return AddressOOutput( + street: _streetValue, city: _cityValue, zip: _zipValue); + } + + @override + void toggleDisabled({ + bool updateParent = true, + bool emitEvent = true, + }) { + final currentFormInstance = currentForm; + + if (currentFormInstance is! FormGroup) { + return; + } + + if (_disabled.isEmpty) { + currentFormInstance.controls.forEach((key, control) { + _disabled[key] = control.disabled; + }); + + currentForm.markAsDisabled( + updateParent: updateParent, emitEvent: emitEvent); + } else { + currentFormInstance.controls.forEach((key, control) { + if (_disabled[key] == false) { + currentFormInstance.controls[key]?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + + _disabled.remove(key); + }); + } + } + + @override + void submit({ + required void Function(AddressOOutput model) onValid, + void Function()? onNotValid, + }) { + currentForm.markAllAsTouched(); + if (currentForm.valid) { + onValid(model); + } else { + _logAddressOForm.info('Errors'); + _logAddressOForm.info('┗━━ ${form.errors}'); + onNotValid?.call(); + } + } + + AbstractControl get currentForm { + return path == null ? form : form.control(path!); + } + + @override + void updateValue( + AddressO? value, { + bool updateParent = true, + bool emitEvent = true, + }) => + form.updateValue(AddressOForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + + @override + void reset({ + AddressO? value, + bool updateParent = true, + bool emitEvent = true, + }) => + form.reset( + value: value != null ? formElements(value).rawValue : null, + updateParent: updateParent, + emitEvent: emitEvent); + + String pathBuilder(String? pathItem) => + [path, pathItem].whereType().join("."); + + static FormGroup formElements(AddressO? addressO) => FormGroup({ + streetControlName: FormControl( + value: addressO?.street, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + cityControlName: FormControl( + value: addressO?.city, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + zipControlName: FormControl( + value: addressO?.zip, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false) + }, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false); +} + +@Rf() +class UserProfileOOutput { + final String id; + final String firstName; + final String lastName; + final AddressOOutput home; + final AddressOOutput? office; + UserProfileOOutput( + {required this.id, + @RfControl(validators: [RequiredValidator()]) required this.firstName, + @RfControl(validators: [RequiredValidator()]) required this.lastName, + required this.home, + this.office}); +} + +@RfGroup() +class AddressOOutput { + final String? street; + final String city; + final String? zip; + AddressOOutput( + {@RfControl() this.street, + @RfControl(validators: [RequiredValidator()]) required this.city, + @RfControl() this.zip}); +} + +class ReactiveUserProfileOFormArrayBuilder< + ReactiveUserProfileOFormArrayBuilderT> extends StatelessWidget { + const ReactiveUserProfileOFormArrayBuilder({ + Key? key, + this.control, + this.formControl, + this.builder, + required this.itemBuilder, + }) : assert(control != null || formControl != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final FormArray? formControl; + + final FormArray? Function( + UserProfileOForm formModel)? control; + + final Widget Function(BuildContext context, List itemList, + UserProfileOForm formModel)? builder; + + final Widget Function( + BuildContext context, + int i, + ReactiveUserProfileOFormArrayBuilderT? item, + UserProfileOForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveUserProfileOForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + return ReactiveFormArray( + formArray: formControl ?? control?.call(formModel), + builder: (context, formArray, child) { + final values = formArray.controls.map((e) => e.value).toList(); + final itemList = values + .asMap() + .map((i, item) { + return MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + ); + }) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} + +class ReactiveUserProfileOFormFormGroupArrayBuilder< + ReactiveUserProfileOFormFormGroupArrayBuilderT> extends StatelessWidget { + const ReactiveUserProfileOFormFormGroupArrayBuilder({ + Key? key, + this.extended, + this.getExtended, + this.builder, + required this.itemBuilder, + }) : assert(extended != null || getExtended != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final ExtendedControl?>, + List>? extended; + + final ExtendedControl?>, + List> + Function(UserProfileOForm formModel)? getExtended; + + final Widget Function(BuildContext context, List itemList, + UserProfileOForm formModel)? builder; + + final Widget Function( + BuildContext context, + int i, + ReactiveUserProfileOFormFormGroupArrayBuilderT? item, + UserProfileOForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveUserProfileOForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + final value = (extended ?? getExtended?.call(formModel))!; + + return StreamBuilder?>?>( + stream: value.control.valueChanges, + builder: (context, snapshot) { + final itemList = (value.value() ?? + []) + .asMap() + .map((i, item) => MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + )) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} +'''; diff --git a/packages/generator_tests/test/doc/user_profile_test.dart b/packages/generator_tests/test/doc/user_profile_test.dart index 7254ad9d..74330873 100644 --- a/packages/generator_tests/test/doc/user_profile_test.dart +++ b/packages/generator_tests/test/doc/user_profile_test.dart @@ -20,7 +20,7 @@ void main() { part '$fileName.gform.dart'; - @Rf() + @Rf(output: false) class UserProfile { final String id; @@ -75,7 +75,7 @@ void main() { const generatedFile = r'''// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'user_profile.dart'; @@ -220,6 +220,34 @@ class _UserProfileFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logUserProfileForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -257,7 +285,9 @@ class _UserProfileFormBuilderState extends State { } } -class UserProfileForm implements FormModel { +final _logUserProfileForm = Logger('UserProfileForm'); + +class UserProfileForm implements FormModel { UserProfileForm( this.form, this.path, @@ -647,9 +677,11 @@ class UserProfileForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'UserProfileForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logUserProfileForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return UserProfile( id: _idValue, @@ -704,6 +736,8 @@ class UserProfileForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logUserProfileForm.info('Errors'); + _logUserProfileForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } @@ -766,7 +800,9 @@ class UserProfileForm implements FormModel { disabled: false); } -class AddressForm implements FormModel
{ +final _logAddressForm = Logger('AddressForm'); + +class AddressForm implements FormModel { AddressForm( this.form, this.path, @@ -1068,9 +1104,11 @@ class AddressForm implements FormModel
{ final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'AddressForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logAddressForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Address(street: _streetValue, city: _cityValue, zip: _zipValue); } @@ -1116,6 +1154,8 @@ class AddressForm implements FormModel
{ if (currentForm.valid) { onValid(model); } else { + _logAddressForm.info('Errors'); + _logAddressForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } diff --git a/packages/generator_tests/test/form_array_annotation_exception_test.dart b/packages/generator_tests/test/form_array_annotation_exception_test.dart index 75c6ae3f..cab1226f 100644 --- a/packages/generator_tests/test/form_array_annotation_exception_test.dart +++ b/packages/generator_tests/test/form_array_annotation_exception_test.dart @@ -18,7 +18,7 @@ const model = ''' part '$fileName.gform.dart'; - @Rf() + @Rf(output: false) class ArrayNullable { final List emailList; diff --git a/packages/generator_tests/test/form_control_annotation_exception_test.dart b/packages/generator_tests/test/form_control_annotation_exception_test.dart index dccf8614..8da520d3 100644 --- a/packages/generator_tests/test/form_control_annotation_exception_test.dart +++ b/packages/generator_tests/test/form_control_annotation_exception_test.dart @@ -21,7 +21,7 @@ const model = r''' return Validators.required(control); } - @Rf() + @Rf(output: false) class Basic { final String email; diff --git a/packages/reactive_forms_annotations/CHANGELOG.md b/packages/reactive_forms_annotations/CHANGELOG.md index bfe0212a..676f6b1a 100644 --- a/packages/reactive_forms_annotations/CHANGELOG.md +++ b/packages/reactive_forms_annotations/CHANGELOG.md @@ -1,3 +1,27 @@ +## [6.0.0-beta.6] + +* export `DeepCollectionEquality` + +## [6.0.0-beta.5] + +* equalsTo method + +## [6.0.0-beta.3] + +* improved logging + +## [6.0.0-beta.2] + +* improved logging + +## [6.0.0-beta.1] + +* BREAKING CHANGE: before generating code make sure to replace @Rf() with @Rf(output: false) + +## [6.0.0-beta.0] + +* output + ## [5.0.0] * rf17 @@ -16,7 +40,8 @@ ## [4.0.0] -* annotation shorthands support - this is non-breaking change, old annotations will continue working. +* annotation shorthands support - this is non-breaking change, old annotations will continue + working. just a major bump for preventing unexpected auto updates ## [3.0.0] @@ -33,7 +58,8 @@ ## [1.0.0] -* Internal refactoring. Everything should work as previously but there is a small chance of breaking changes +* Internal refactoring. Everything should work as previously but there is a small chance of breaking + changes ## [0.14.0-beta] diff --git a/packages/reactive_forms_annotations/lib/reactive_forms_annotations.dart b/packages/reactive_forms_annotations/lib/reactive_forms_annotations.dart index efd9ff7a..9514183a 100644 --- a/packages/reactive_forms_annotations/lib/reactive_forms_annotations.dart +++ b/packages/reactive_forms_annotations/lib/reactive_forms_annotations.dart @@ -1,6 +1,11 @@ library reactive_forms_annotations; +export 'dart:developer' hide Flow; +export 'dart:async'; export 'package:flutter/widgets.dart'; +export 'package:collection/collection.dart' show DeepCollectionEquality; +export 'package:flutter/foundation.dart'; +export 'package:logging/logging.dart'; export 'package:reactive_forms/reactive_forms.dart'; export 'package:reactive_forms/src/widgets/reactive_form_pop_scope.dart'; export 'src/reactive_form_annotation.dart'; diff --git a/packages/reactive_forms_annotations/lib/src/form_model.dart b/packages/reactive_forms_annotations/lib/src/form_model.dart index 34d6b331..29891c2e 100644 --- a/packages/reactive_forms_annotations/lib/src/form_model.dart +++ b/packages/reactive_forms_annotations/lib/src/form_model.dart @@ -1,19 +1,21 @@ import 'package:reactive_forms/reactive_forms.dart'; -abstract class FormModel { +abstract class FormModel { FormModel({ required this.form, }); final FormGroup form; - TModel get model; + TModelOutput get model; void submit({ - required void Function(TModel model) onValid, + required void Function(TModelOutput model) onValid, void Function()? onNotValid, }); + bool equalsTo(TModel other); + void updateValue( TModel? value, { bool updateParent = true, diff --git a/packages/reactive_forms_annotations/lib/src/reactive_form_annotation.dart b/packages/reactive_forms_annotations/lib/src/reactive_form_annotation.dart index 8ddf4709..329dc25e 100644 --- a/packages/reactive_forms_annotations/lib/src/reactive_form_annotation.dart +++ b/packages/reactive_forms_annotations/lib/src/reactive_form_annotation.dart @@ -1,7 +1,13 @@ class ReactiveFormAnnotation { final String? name; + final bool output; + final String requiredValidatorName; - const ReactiveFormAnnotation({this.name}); + const ReactiveFormAnnotation({ + this.name, + this.output = true, + this.requiredValidatorName = 'RequiredValidator', + }); } typedef Rf = ReactiveFormAnnotation; diff --git a/packages/reactive_forms_annotations/pubspec.yaml b/packages/reactive_forms_annotations/pubspec.yaml index 0b5e0519..42e0cff5 100644 --- a/packages/reactive_forms_annotations/pubspec.yaml +++ b/packages/reactive_forms_annotations/pubspec.yaml @@ -16,15 +16,17 @@ repository: https://github.com/artflutter/reactive_forms_generator # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 5.0.0 +version: 6.0.0-beta.6 environment: sdk: ">=3.0.0 <4.0.0" dependencies: - reactive_forms: ^17.0.0 + collection: ^1.18.0 flutter: sdk: flutter + logging: 1.1.1 + reactive_forms: ^17.0.0 dev_dependencies: flutter_lints: ^3.0.2 diff --git a/packages/reactive_forms_generator/CHANGELOG.md b/packages/reactive_forms_generator/CHANGELOG.md index 4e554244..0793e9e5 100644 --- a/packages/reactive_forms_generator/CHANGELOG.md +++ b/packages/reactive_forms_generator/CHANGELOG.md @@ -1,3 +1,47 @@ +## [6.0.0-beta.10] + +* equalsTo `value` => `rawValue` + +## [6.0.0-beta.9] + +* equalsTo `mapEquals` => `DeepCollectionEquality` + +## [6.0.0-beta.8] + +* equalsTo form => currentForm + +## [6.0.0-beta.7] + +* equalsTo method + +## [6.0.0-beta.6] + +* improved logging + +## [6.0.0-beta.5] + +* improved logging + +## [6.0.0-beta.4] + +* improved logging + +## [6.0.0-beta.3] + +* InconsistentAnalysisException fix + +## [6.0.0-beta.2] + +* InconsistentAnalysisException fix + +## [6.0.0-beta.1] + +* BREAKING CHANGE: before generating code make sure to replace @Rf() with @Rf(output: false) + +## [6.0.0-beta.0] + +* output + ## [5.0.5] * https://github.com/artflutter/reactive_forms_generator/pull/169 @@ -49,12 +93,14 @@ ## [4.5.1] * generate BuildContext extension for listening and watching the form -* fix for cases with disabled fields for arrays, the disabled items were thrown off the `value` which caused issues. +* fix for cases with disabled fields for arrays, the disabled items were thrown off the `value` + which caused issues. * debugLog replaced with debugLogStackTrace for warnings when calling model on non-valid form ## [4.5.0] -* fix for cases with disabled fields for arrays, the disabled items were thrown off the `value` which caused issues. +* fix for cases with disabled fields for arrays, the disabled items were thrown off the `value` + which caused issues. ## [4.4.0] @@ -84,7 +130,8 @@ ## [4.0.0] -* annotation shorthands support - this is non-breaking change, old annotations will continue working. +* annotation shorthands support - this is non-breaking change, old annotations will continue + working. just a major bump for preventing unexpected auto updates ## [3.0.1] @@ -134,7 +181,8 @@ ## [1.0.0] -* Internal refactoring. Everything should work as previously but there is a small chance of breaking changes +* Internal refactoring. Everything should work as previously but there is a small chance of breaking + changes ## [0.24.2-beta] @@ -194,7 +242,8 @@ * initState method for *FormBuilder class * *SetDisabled helper methods for all controls * *ControlValue accessors are private now because it is unsafe to call them on non valid form -* added additional failsafe by falling back to default values on required fields which have default value in model(for +* added additional failsafe by falling back to default values on required fields which have default + value in model(for strings only) ## [0.17.2-beta] @@ -241,12 +290,14 @@ ## [0.11.0-beta] * widget for handling array of forms items see - packages/reactive_forms_generator/example/lib/docs/mailing_list/delivery_route_form.dart for example of use + packages/reactive_forms_generator/example/lib/docs/mailing_list/delivery_route_form.dart for + example of use ## [0.10.0-beta] * widget for handling array of items see - packages/reactive_forms_generator/example/lib/docs/mailing_list/mailing_list_form.dart for example of use + packages/reactive_forms_generator/example/lib/docs/mailing_list/mailing_list_form.dart for example + of use ## [0.9.8-beta] diff --git a/packages/reactive_forms_generator/README.md b/packages/reactive_forms_generator/README.md index b1fcc056..7280bda4 100644 --- a/packages/reactive_forms_generator/README.md +++ b/packages/reactive_forms_generator/README.md @@ -121,6 +121,7 @@ final form = ReactiveFormBuilder( 2. Second issue is output which is always `Map`. It is ok for languages like JS. But for the typed language you would prefer to get the output fom the form like model. And avoid manual type casting like this one. + ```dart final document = DocumentInput( diff --git a/packages/reactive_forms_generator/build.yaml b/packages/reactive_forms_generator/build.yaml index 253b6a80..16ed8940 100644 --- a/packages/reactive_forms_generator/build.yaml +++ b/packages/reactive_forms_generator/build.yaml @@ -17,4 +17,5 @@ builders: builder_factories: [ "reactiveFormsGenerator" ] build_extensions: { ".dart": [ ".gform.dart" ] } auto_apply: dependents - build_to: source \ No newline at end of file + build_to: source + runs_before: [ "freezed|freezed", "json_serializable|json_serializable" ] \ No newline at end of file diff --git a/packages/reactive_forms_generator/example/.gitignore b/packages/reactive_forms_generator/example/.gitignore index a1345d01..2bc0298c 100644 --- a/packages/reactive_forms_generator/example/.gitignore +++ b/packages/reactive_forms_generator/example/.gitignore @@ -14,6 +14,8 @@ *.ipr *.iws .idea/ +linux +macos # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line diff --git a/packages/reactive_forms_generator/example/lib/docs/animated_url_list/url.dart b/packages/reactive_forms_generator/example/lib/docs/animated_url_list/url.dart index 7b28307a..fedc91e6 100644 --- a/packages/reactive_forms_generator/example/lib/docs/animated_url_list/url.dart +++ b/packages/reactive_forms_generator/example/lib/docs/animated_url_list/url.dart @@ -1,9 +1,8 @@ import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; - part 'url.gform.dart'; -@Rf() +@Rf(output: false) class Url { final List urlList; @@ -18,9 +17,11 @@ class UrlEntity { UrlEntity({ @RfControl(validators: [ RequiredValidator(), - ]) this.label = '', + ]) + this.label = '', @RfControl(validators: [ RequiredValidator(), - ]) this.url = '', + ]) + this.url = '', }); } diff --git a/packages/reactive_forms_generator/example/lib/docs/animated_url_list/url.gform.dart b/packages/reactive_forms_generator/example/lib/docs/animated_url_list/url.gform.dart index 5ee8e123..3bc0aa47 100644 --- a/packages/reactive_forms_generator/example/lib/docs/animated_url_list/url.gform.dart +++ b/packages/reactive_forms_generator/example/lib/docs/animated_url_list/url.gform.dart @@ -1,7 +1,7 @@ // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'url.dart'; @@ -132,6 +132,8 @@ class UrlFormBuilder extends StatefulWidget { class _UrlFormBuilderState extends State { late UrlForm _formModel; + StreamSubscription? _logSubscription; + @override void initState() { _formModel = UrlForm(UrlForm.formElements(widget.model), null); @@ -142,6 +144,34 @@ class _UrlFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logSubscription = _logUrlForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -157,6 +187,7 @@ class _UrlFormBuilderState extends State { @override void dispose() { _formModel.form.dispose(); + _logSubscription?.cancel(); super.dispose(); } @@ -179,7 +210,9 @@ class _UrlFormBuilderState extends State { } } -class UrlForm implements FormModel { +final _logUrlForm = Logger.detached('UrlForm'); + +class UrlForm implements FormModel { UrlForm( this.form, this.path, @@ -373,9 +406,11 @@ class UrlForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'UrlForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logUrlForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Url(urlList: _urlListValue); } @@ -425,10 +460,20 @@ class UrlForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logUrlForm.info('Errors'); + _logUrlForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(Url? other) { + return mapEquals( + this.form.value, + UrlForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } @@ -472,7 +517,9 @@ class UrlForm implements FormModel { disabled: false); } -class UrlEntityForm implements FormModel { +final _logUrlEntityForm = Logger.detached('UrlEntityForm'); + +class UrlEntityForm implements FormModel { UrlEntityForm( this.form, this.path, @@ -625,9 +672,11 @@ class UrlEntityForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'UrlEntityForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logUrlEntityForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return UrlEntity(label: _labelValue, url: _urlValue); } @@ -673,10 +722,20 @@ class UrlEntityForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logUrlEntityForm.info('Errors'); + _logUrlEntityForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(UrlEntity? other) { + return mapEquals( + this.form.value, + UrlEntityForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } diff --git a/packages/reactive_forms_generator/example/lib/docs/animated_url_list/url_output.dart b/packages/reactive_forms_generator/example/lib/docs/animated_url_list/url_output.dart new file mode 100644 index 00000000..5b295219 --- /dev/null +++ b/packages/reactive_forms_generator/example/lib/docs/animated_url_list/url_output.dart @@ -0,0 +1,27 @@ +import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; + +part 'url_output.gform.dart'; + +@Rf() +class UrlO { + final List urlList; + + UrlO({@RfArray() this.urlList = const []}); +} + +@RfGroup() +class UrlEntityO { + final String? label; + final String? url; + + UrlEntityO({ + @RfControl(validators: [ + RequiredValidator(), + ]) + this.label, + @RfControl(validators: [ + RequiredValidator(), + ]) + this.url, + }); +} diff --git a/packages/reactive_forms_generator/example/lib/docs/animated_url_list/url_output.gform.dart b/packages/reactive_forms_generator/example/lib/docs/animated_url_list/url_output.gform.dart new file mode 100644 index 00000000..03305ed0 --- /dev/null +++ b/packages/reactive_forms_generator/example/lib/docs/animated_url_list/url_output.gform.dart @@ -0,0 +1,983 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'url_output.dart'; + +// ************************************************************************** +// ReactiveFormsGenerator +// ************************************************************************** + +class ReactiveUrlOFormConsumer extends StatelessWidget { + const ReactiveUrlOFormConsumer({ + Key? key, + required this.builder, + this.child, + }) : super(key: key); + + final Widget? child; + + final Widget Function(BuildContext context, UrlOForm formModel, Widget? child) + builder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveUrlOForm.of(context); + + if (formModel is! UrlOForm) { + throw FormControlParentNotFoundException(this); + } + return builder(context, formModel, child); + } +} + +class UrlOFormInheritedStreamer extends InheritedStreamer { + const UrlOFormInheritedStreamer({ + Key? key, + required this.form, + required Stream stream, + required Widget child, + }) : super( + stream, + child, + key: key, + ); + + final UrlOForm form; +} + +class ReactiveUrlOForm extends StatelessWidget { + const ReactiveUrlOForm({ + Key? key, + required this.form, + required this.child, + this.canPop, + this.onPopInvoked, + }) : super(key: key); + + final Widget child; + + final UrlOForm form; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + static UrlOForm? of( + BuildContext context, { + bool listen = true, + }) { + if (listen) { + return context + .dependOnInheritedWidgetOfExactType() + ?.form; + } + + final element = context + .getElementForInheritedWidgetOfExactType(); + return element == null + ? null + : (element.widget as UrlOFormInheritedStreamer).form; + } + + @override + Widget build(BuildContext context) { + return UrlOFormInheritedStreamer( + form: form, + stream: form.form.statusChanged, + child: ReactiveFormPopScope( + canPop: canPop, + onPopInvoked: onPopInvoked, + child: child, + ), + ); + } +} + +extension ReactiveReactiveUrlOFormExt on BuildContext { + UrlOForm? urlOFormWatch() => ReactiveUrlOForm.of(this); + + UrlOForm? urlOFormRead() => ReactiveUrlOForm.of(this, listen: false); +} + +class UrlOFormBuilder extends StatefulWidget { + const UrlOFormBuilder({ + Key? key, + this.model, + this.child, + this.canPop, + this.onPopInvoked, + required this.builder, + this.initState, + }) : super(key: key); + + final UrlO? model; + + final Widget? child; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + final Widget Function(BuildContext context, UrlOForm formModel, Widget? child) + builder; + + final void Function(BuildContext context, UrlOForm formModel)? initState; + + @override + _UrlOFormBuilderState createState() => _UrlOFormBuilderState(); +} + +class _UrlOFormBuilderState extends State { + late UrlOForm _formModel; + + StreamSubscription? _logSubscription; + + @override + void initState() { + _formModel = UrlOForm(UrlOForm.formElements(widget.model), null); + + if (_formModel.form.disabled) { + _formModel.form.markAsDisabled(); + } + + widget.initState?.call(context, _formModel); + + _logSubscription = _logUrlOForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + + super.initState(); + } + + @override + void didUpdateWidget(covariant UrlOFormBuilder oldWidget) { + if (widget.model != oldWidget.model) { + _formModel.updateValue(widget.model); + } + + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + _formModel.form.dispose(); + _logSubscription?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ReactiveUrlOForm( + key: ObjectKey(_formModel), + form: _formModel, + // canPop: widget.canPop, + // onPopInvoked: widget.onPopInvoked, + child: ReactiveFormBuilder( + form: () => _formModel.form, + canPop: widget.canPop, + onPopInvoked: widget.onPopInvoked, + builder: (context, formGroup, child) => + widget.builder(context, _formModel, widget.child), + child: widget.child, + ), + ); + } +} + +final _logUrlOForm = Logger.detached('UrlOForm'); + +class UrlOForm implements FormModel { + UrlOForm( + this.form, + this.path, + ); + + static const String urlListControlName = "urlList"; + + final FormGroup form; + + final String? path; + + final Map _disabled = {}; + + String urlListControlPath() => pathBuilder(urlListControlName); + + List get _urlListValue => + urlListUrlEntityOForm.map((e) => e.model).toList(); + + bool get containsUrlList { + try { + form.control(urlListControlPath()); + return true; + } catch (e) { + return false; + } + } + + Map get urlListErrors => urlListControl.errors; + + void get urlListFocus => form.focus(urlListControlPath()); + + void urlListValueUpdate( + List value, { + bool updateParent = true, + bool emitEvent = true, + }) { + final localValue = (value); + if (localValue.isEmpty) { + urlListClear(updateParent: updateParent, emitEvent: emitEvent); + + return; + } + + final toUpdate = []; + final toAdd = []; + + localValue.asMap().forEach((k, v) { + final values = (urlListControl.controls).map((e) => e.value).toList(); + + if (urlListUrlEntityOForm.asMap().containsKey(k) && + values.asMap().containsKey(k)) { + toUpdate.add(v); + } else { + toAdd.add(v); + } + }); + + if (toUpdate.isNotEmpty) { + urlListControl.updateValue( + toUpdate.map((e) => UrlEntityOForm.formElements(e).rawValue).toList(), + updateParent: updateParent, + emitEvent: emitEvent); + } + + if (toAdd.isNotEmpty) { + toAdd.forEach((e) { + urlListControl.add(UrlEntityOForm.formElements(e), + updateParent: updateParent, emitEvent: emitEvent); + }); + } + } + + void urlListInsert( + int i, + UrlEntityO value, { + bool updateParent = true, + bool emitEvent = true, + }) { + final values = (urlListControl.controls).map((e) => e.value).toList(); + if (values.length < i) { + addUrlListItem(value); + return; + } + + urlListControl.insert( + i, + UrlEntityOForm.formElements(value), + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + + void urlListClear({ + bool updateParent = true, + bool emitEvent = true, + }) { + urlListUrlEntityOForm.clear(); + urlListControl.clear(updateParent: updateParent, emitEvent: emitEvent); + } + + void urlListValuePatch( + List value, { + bool updateParent = true, + bool emitEvent = true, + }) { + final keys = urlListUrlEntityOForm.asMap().keys; + + final toPatch = []; + (value).asMap().forEach( + (k, v) { + if (keys.contains(k)) { + toPatch.add(v); + } + }, + ); + + urlListControl.patchValue( + toPatch.map((e) => UrlEntityOForm.formElements(e).rawValue).toList(), + updateParent: updateParent, + emitEvent: emitEvent); + } + + void urlListValueReset( + List value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + urlListControl.reset( + value: value + .map((e) => UrlEntityOForm.formElements(e).rawValue) + .toList(), + updateParent: updateParent, + emitEvent: emitEvent); + + FormArray> get urlListControl => + form.control(urlListControlPath()) as FormArray>; + + List get urlListUrlEntityOForm { + final values = (urlListControl.controls).map((e) => e.value).toList(); + + return values + .asMap() + .map((k, v) => + MapEntry(k, UrlEntityOForm(form, pathBuilder("urlList.$k")))) + .values + .toList(); + } + + void urlListSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + urlListControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + urlListControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + ExtendedControl?>, List> + get urlListExtendedControl => + ExtendedControl?>, List>( + form.control(urlListControlPath()) + as FormArray>, + () => urlListUrlEntityOForm); + + void addUrlListItem(UrlEntityO value) { + urlListControl.add(UrlEntityOForm.formElements(value)); + } + + void removeUrlListItemAtIndex(int i) { + if ((urlListControl.value ?? []).length > i) { + urlListControl.removeAt(i); + } + } + + void addUrlListItemList(List value) { + value.map((e) => addUrlListItem(e)); + } + + @override + UrlOOutput get model { + final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; + + if (!isValid) { + _logUrlOForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); + } + return UrlOOutput(urlList: _urlListValue); + } + + @override + void toggleDisabled({ + bool updateParent = true, + bool emitEvent = true, + }) { + final currentFormInstance = currentForm; + + if (currentFormInstance is! FormGroup) { + return; + } + + if (_disabled.isEmpty) { + currentFormInstance.controls.forEach((key, control) { + _disabled[key] = control.disabled; + }); + + urlListUrlEntityOForm.forEach((e) => e.toggleDisabled()); + + currentForm.markAsDisabled( + updateParent: updateParent, emitEvent: emitEvent); + } else { + urlListUrlEntityOForm.forEach((e) => e.toggleDisabled()); + + currentFormInstance.controls.forEach((key, control) { + if (_disabled[key] == false) { + currentFormInstance.controls[key]?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + + _disabled.remove(key); + }); + } + } + + @override + void submit({ + required void Function(UrlOOutput model) onValid, + void Function()? onNotValid, + }) { + currentForm.markAllAsTouched(); + if (currentForm.valid) { + onValid(model); + } else { + _logUrlOForm.info('Errors'); + _logUrlOForm.info('┗━━ ${form.errors}'); + onNotValid?.call(); + } + } + + @override + bool equalsTo(UrlO? other) { + return mapEquals( + this.form.value, + UrlOForm.formElements(other).value, + ); + } + + AbstractControl get currentForm { + return path == null ? form : form.control(path!); + } + + @override + void updateValue( + UrlO? value, { + bool updateParent = true, + bool emitEvent = true, + }) => + form.updateValue(UrlOForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + + @override + void reset({ + UrlO? value, + bool updateParent = true, + bool emitEvent = true, + }) => + form.reset( + value: value != null ? formElements(value).rawValue : null, + updateParent: updateParent, + emitEvent: emitEvent); + + String pathBuilder(String? pathItem) => + [path, pathItem].whereType().join("."); + + static FormGroup formElements(UrlO? urlO) => FormGroup({ + urlListControlName: FormArray( + (urlO?.urlList ?? []) + .map((e) => UrlEntityOForm.formElements(e)) + .toList(), + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false) + }, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false); +} + +final _logUrlEntityOForm = Logger.detached('UrlEntityOForm'); + +class UrlEntityOForm implements FormModel { + UrlEntityOForm( + this.form, + this.path, + ); + + static const String labelControlName = "label"; + + static const String urlControlName = "url"; + + final FormGroup form; + + final String? path; + + final Map _disabled = {}; + + String labelControlPath() => pathBuilder(labelControlName); + + String urlControlPath() => pathBuilder(urlControlName); + + String get _labelValue => labelControl.value as String; + + String get _urlValue => urlControl.value as String; + + bool get containsLabel { + try { + form.control(labelControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsUrl { + try { + form.control(urlControlPath()); + return true; + } catch (e) { + return false; + } + } + + Map? get labelErrors => labelControl.errors; + + Map? get urlErrors => urlControl.errors; + + void get labelFocus => form.focus(labelControlPath()); + + void get urlFocus => form.focus(urlControlPath()); + + void labelRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsLabel) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + labelControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + labelControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void urlRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsUrl) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + urlControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + urlControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void labelValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + labelControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void urlValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + urlControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void labelValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + labelControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void urlValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + urlControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void labelValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + labelControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void urlValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + urlControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + FormControl get labelControl => + form.control(labelControlPath()) as FormControl; + + FormControl get urlControl => + form.control(urlControlPath()) as FormControl; + + void labelSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + labelControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + labelControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void urlSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + urlControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + urlControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + @override + UrlEntityOOutput get model { + final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; + + if (!isValid) { + _logUrlEntityOForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); + } + return UrlEntityOOutput(label: _labelValue, url: _urlValue); + } + + @override + void toggleDisabled({ + bool updateParent = true, + bool emitEvent = true, + }) { + final currentFormInstance = currentForm; + + if (currentFormInstance is! FormGroup) { + return; + } + + if (_disabled.isEmpty) { + currentFormInstance.controls.forEach((key, control) { + _disabled[key] = control.disabled; + }); + + currentForm.markAsDisabled( + updateParent: updateParent, emitEvent: emitEvent); + } else { + currentFormInstance.controls.forEach((key, control) { + if (_disabled[key] == false) { + currentFormInstance.controls[key]?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + + _disabled.remove(key); + }); + } + } + + @override + void submit({ + required void Function(UrlEntityOOutput model) onValid, + void Function()? onNotValid, + }) { + currentForm.markAllAsTouched(); + if (currentForm.valid) { + onValid(model); + } else { + _logUrlEntityOForm.info('Errors'); + _logUrlEntityOForm.info('┗━━ ${form.errors}'); + onNotValid?.call(); + } + } + + @override + bool equalsTo(UrlEntityO? other) { + return mapEquals( + this.form.value, + UrlEntityOForm.formElements(other).value, + ); + } + + AbstractControl get currentForm { + return path == null ? form : form.control(path!); + } + + @override + void updateValue( + UrlEntityO? value, { + bool updateParent = true, + bool emitEvent = true, + }) => + form.updateValue(UrlEntityOForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + + @override + void reset({ + UrlEntityO? value, + bool updateParent = true, + bool emitEvent = true, + }) => + form.reset( + value: value != null ? formElements(value).rawValue : null, + updateParent: updateParent, + emitEvent: emitEvent); + + String pathBuilder(String? pathItem) => + [path, pathItem].whereType().join("."); + + static FormGroup formElements(UrlEntityO? urlEntityO) => FormGroup({ + labelControlName: FormControl( + value: urlEntityO?.label, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + urlControlName: FormControl( + value: urlEntityO?.url, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false) + }, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false); +} + +@Rf() +class UrlOOutput { + final List urlList; + UrlOOutput({@RfArray() this.urlList = const []}); +} + +@RfGroup() +class UrlEntityOOutput { + final String label; + final String url; + UrlEntityOOutput( + {@RfControl(validators: [RequiredValidator()]) required this.label, + @RfControl(validators: [RequiredValidator()]) required this.url}); +} + +class ReactiveUrlOFormArrayBuilder + extends StatelessWidget { + const ReactiveUrlOFormArrayBuilder({ + Key? key, + this.control, + this.formControl, + this.builder, + required this.itemBuilder, + }) : assert(control != null || formControl != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final FormArray? formControl; + + final FormArray? Function(UrlOForm formModel)? + control; + + final Widget Function( + BuildContext context, List itemList, UrlOForm formModel)? builder; + + final Widget Function(BuildContext context, int i, + ReactiveUrlOFormArrayBuilderT? item, UrlOForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveUrlOForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + return ReactiveFormArray( + formArray: formControl ?? control?.call(formModel), + builder: (context, formArray, child) { + final values = formArray.controls.map((e) => e.value).toList(); + final itemList = values + .asMap() + .map((i, item) { + return MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + ); + }) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} + +class ReactiveUrlOFormFormGroupArrayBuilder< + ReactiveUrlOFormFormGroupArrayBuilderT> extends StatelessWidget { + const ReactiveUrlOFormFormGroupArrayBuilder({ + Key? key, + this.extended, + this.getExtended, + this.builder, + required this.itemBuilder, + }) : assert(extended != null || getExtended != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final ExtendedControl?>, + List>? extended; + + final ExtendedControl?>, + List> + Function(UrlOForm formModel)? getExtended; + + final Widget Function( + BuildContext context, List itemList, UrlOForm formModel)? builder; + + final Widget Function( + BuildContext context, + int i, + ReactiveUrlOFormFormGroupArrayBuilderT? item, + UrlOForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveUrlOForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + final value = (extended ?? getExtended?.call(formModel))!; + + return StreamBuilder?>?>( + stream: value.control.valueChanges, + builder: (context, snapshot) { + final itemList = + (value.value() ?? []) + .asMap() + .map((i, item) => MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + )) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} diff --git a/packages/reactive_forms_generator/example/lib/docs/annotateless/annotateless.dart b/packages/reactive_forms_generator/example/lib/docs/annotateless/annotateless.dart index 29d28e42..1313e996 100644 --- a/packages/reactive_forms_generator/example/lib/docs/annotateless/annotateless.dart +++ b/packages/reactive_forms_generator/example/lib/docs/annotateless/annotateless.dart @@ -3,7 +3,7 @@ import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; part 'annotateless.gform.dart'; -@Rf() +@Rf(output: false) class Annotateless extends Equatable { final String email; diff --git a/packages/reactive_forms_generator/example/lib/docs/annotateless/annotateless.gform.dart b/packages/reactive_forms_generator/example/lib/docs/annotateless/annotateless.gform.dart index b13d642d..6b3c529e 100644 --- a/packages/reactive_forms_generator/example/lib/docs/annotateless/annotateless.gform.dart +++ b/packages/reactive_forms_generator/example/lib/docs/annotateless/annotateless.gform.dart @@ -1,7 +1,7 @@ // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'annotateless.dart'; @@ -137,6 +137,8 @@ class AnnotatelessFormBuilder extends StatefulWidget { class _AnnotatelessFormBuilderState extends State { late AnnotatelessForm _formModel; + StreamSubscription? _logSubscription; + @override void initState() { _formModel = @@ -148,6 +150,34 @@ class _AnnotatelessFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logSubscription = _logAnnotatelessForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -163,6 +193,7 @@ class _AnnotatelessFormBuilderState extends State { @override void dispose() { _formModel.form.dispose(); + _logSubscription?.cancel(); super.dispose(); } @@ -185,7 +216,9 @@ class _AnnotatelessFormBuilderState extends State { } } -class AnnotatelessForm implements FormModel { +final _logAnnotatelessForm = Logger.detached('AnnotatelessForm'); + +class AnnotatelessForm implements FormModel { AnnotatelessForm( this.form, this.path, @@ -338,9 +371,11 @@ class AnnotatelessForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'AnnotatelessForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logAnnotatelessForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Annotateless(email: _emailValue, password: _passwordValue); } @@ -386,10 +421,20 @@ class AnnotatelessForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logAnnotatelessForm.info('Errors'); + _logAnnotatelessForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(Annotateless? other) { + return mapEquals( + this.form.value, + AnnotatelessForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } diff --git a/packages/reactive_forms_generator/example/lib/docs/annotateless/annotateless_output.dart b/packages/reactive_forms_generator/example/lib/docs/annotateless/annotateless_output.dart new file mode 100644 index 00000000..1d7f229a --- /dev/null +++ b/packages/reactive_forms_generator/example/lib/docs/annotateless/annotateless_output.dart @@ -0,0 +1,19 @@ +import 'package:equatable/equatable.dart'; +import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; + +part 'annotateless_output.gform.dart'; + +@Rf(output: true) +class AnnotatelessO extends Equatable { + final String email; + + final String password; + + const AnnotatelessO({ + @RfControl() this.email = '', + this.password = '', + }); + + @override + List get props => [email, password]; +} diff --git a/packages/reactive_forms_generator/example/lib/docs/annotateless/annotateless_output.gform.dart b/packages/reactive_forms_generator/example/lib/docs/annotateless/annotateless_output.gform.dart new file mode 100644 index 00000000..f2d5607d --- /dev/null +++ b/packages/reactive_forms_generator/example/lib/docs/annotateless/annotateless_output.gform.dart @@ -0,0 +1,627 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'annotateless_output.dart'; + +// ************************************************************************** +// ReactiveFormsGenerator +// ************************************************************************** + +class ReactiveAnnotatelessOFormConsumer extends StatelessWidget { + const ReactiveAnnotatelessOFormConsumer({ + Key? key, + required this.builder, + this.child, + }) : super(key: key); + + final Widget? child; + + final Widget Function( + BuildContext context, AnnotatelessOForm formModel, Widget? child) builder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveAnnotatelessOForm.of(context); + + if (formModel is! AnnotatelessOForm) { + throw FormControlParentNotFoundException(this); + } + return builder(context, formModel, child); + } +} + +class AnnotatelessOFormInheritedStreamer extends InheritedStreamer { + const AnnotatelessOFormInheritedStreamer({ + Key? key, + required this.form, + required Stream stream, + required Widget child, + }) : super( + stream, + child, + key: key, + ); + + final AnnotatelessOForm form; +} + +class ReactiveAnnotatelessOForm extends StatelessWidget { + const ReactiveAnnotatelessOForm({ + Key? key, + required this.form, + required this.child, + this.canPop, + this.onPopInvoked, + }) : super(key: key); + + final Widget child; + + final AnnotatelessOForm form; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + static AnnotatelessOForm? of( + BuildContext context, { + bool listen = true, + }) { + if (listen) { + return context + .dependOnInheritedWidgetOfExactType< + AnnotatelessOFormInheritedStreamer>() + ?.form; + } + + final element = context.getElementForInheritedWidgetOfExactType< + AnnotatelessOFormInheritedStreamer>(); + return element == null + ? null + : (element.widget as AnnotatelessOFormInheritedStreamer).form; + } + + @override + Widget build(BuildContext context) { + return AnnotatelessOFormInheritedStreamer( + form: form, + stream: form.form.statusChanged, + child: ReactiveFormPopScope( + canPop: canPop, + onPopInvoked: onPopInvoked, + child: child, + ), + ); + } +} + +extension ReactiveReactiveAnnotatelessOFormExt on BuildContext { + AnnotatelessOForm? annotatelessOFormWatch() => + ReactiveAnnotatelessOForm.of(this); + + AnnotatelessOForm? annotatelessOFormRead() => + ReactiveAnnotatelessOForm.of(this, listen: false); +} + +class AnnotatelessOFormBuilder extends StatefulWidget { + const AnnotatelessOFormBuilder({ + Key? key, + this.model, + this.child, + this.canPop, + this.onPopInvoked, + required this.builder, + this.initState, + }) : super(key: key); + + final AnnotatelessO? model; + + final Widget? child; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + final Widget Function( + BuildContext context, AnnotatelessOForm formModel, Widget? child) builder; + + final void Function(BuildContext context, AnnotatelessOForm formModel)? + initState; + + @override + _AnnotatelessOFormBuilderState createState() => + _AnnotatelessOFormBuilderState(); +} + +class _AnnotatelessOFormBuilderState extends State { + late AnnotatelessOForm _formModel; + + StreamSubscription? _logSubscription; + + @override + void initState() { + _formModel = + AnnotatelessOForm(AnnotatelessOForm.formElements(widget.model), null); + + if (_formModel.form.disabled) { + _formModel.form.markAsDisabled(); + } + + widget.initState?.call(context, _formModel); + + _logSubscription = _logAnnotatelessOForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + + super.initState(); + } + + @override + void didUpdateWidget(covariant AnnotatelessOFormBuilder oldWidget) { + if (widget.model != oldWidget.model) { + _formModel.updateValue(widget.model); + } + + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + _formModel.form.dispose(); + _logSubscription?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ReactiveAnnotatelessOForm( + key: ObjectKey(_formModel), + form: _formModel, + // canPop: widget.canPop, + // onPopInvoked: widget.onPopInvoked, + child: ReactiveFormBuilder( + form: () => _formModel.form, + canPop: widget.canPop, + onPopInvoked: widget.onPopInvoked, + builder: (context, formGroup, child) => + widget.builder(context, _formModel, widget.child), + child: widget.child, + ), + ); + } +} + +final _logAnnotatelessOForm = Logger.detached('AnnotatelessOForm'); + +class AnnotatelessOForm + implements FormModel { + AnnotatelessOForm( + this.form, + this.path, + ); + + static const String emailControlName = "email"; + + static const String passwordControlName = "password"; + + final FormGroup form; + + final String? path; + + final Map _disabled = {}; + + String emailControlPath() => pathBuilder(emailControlName); + + String passwordControlPath() => pathBuilder(passwordControlName); + + String get _emailValue => emailControl.value ?? ""; + + String get _passwordValue => passwordControl.value ?? ""; + + bool get containsEmail { + try { + form.control(emailControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsPassword { + try { + form.control(passwordControlPath()); + return true; + } catch (e) { + return false; + } + } + + Map get emailErrors => emailControl.errors; + + Map get passwordErrors => passwordControl.errors; + + void get emailFocus => form.focus(emailControlPath()); + + void get passwordFocus => form.focus(passwordControlPath()); + + void emailValueUpdate( + String value, { + bool updateParent = true, + bool emitEvent = true, + }) { + emailControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void passwordValueUpdate( + String value, { + bool updateParent = true, + bool emitEvent = true, + }) { + passwordControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void emailValuePatch( + String value, { + bool updateParent = true, + bool emitEvent = true, + }) { + emailControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void passwordValuePatch( + String value, { + bool updateParent = true, + bool emitEvent = true, + }) { + passwordControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void emailValueReset( + String value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + emailControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void passwordValueReset( + String value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + passwordControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + FormControl get emailControl => + form.control(emailControlPath()) as FormControl; + + FormControl get passwordControl => + form.control(passwordControlPath()) as FormControl; + + void emailSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + emailControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + emailControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void passwordSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + passwordControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + passwordControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + @override + AnnotatelessOOutput get model { + final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; + + if (!isValid) { + _logAnnotatelessOForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); + } + return AnnotatelessOOutput(email: _emailValue, password: _passwordValue); + } + + @override + void toggleDisabled({ + bool updateParent = true, + bool emitEvent = true, + }) { + final currentFormInstance = currentForm; + + if (currentFormInstance is! FormGroup) { + return; + } + + if (_disabled.isEmpty) { + currentFormInstance.controls.forEach((key, control) { + _disabled[key] = control.disabled; + }); + + currentForm.markAsDisabled( + updateParent: updateParent, emitEvent: emitEvent); + } else { + currentFormInstance.controls.forEach((key, control) { + if (_disabled[key] == false) { + currentFormInstance.controls[key]?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + + _disabled.remove(key); + }); + } + } + + @override + void submit({ + required void Function(AnnotatelessOOutput model) onValid, + void Function()? onNotValid, + }) { + currentForm.markAllAsTouched(); + if (currentForm.valid) { + onValid(model); + } else { + _logAnnotatelessOForm.info('Errors'); + _logAnnotatelessOForm.info('┗━━ ${form.errors}'); + onNotValid?.call(); + } + } + + @override + bool equalsTo(AnnotatelessO? other) { + return mapEquals( + this.form.value, + AnnotatelessOForm.formElements(other).value, + ); + } + + AbstractControl get currentForm { + return path == null ? form : form.control(path!); + } + + @override + void updateValue( + AnnotatelessO? value, { + bool updateParent = true, + bool emitEvent = true, + }) => + form.updateValue(AnnotatelessOForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + + @override + void reset({ + AnnotatelessO? value, + bool updateParent = true, + bool emitEvent = true, + }) => + form.reset( + value: value != null ? formElements(value).rawValue : null, + updateParent: updateParent, + emitEvent: emitEvent); + + String pathBuilder(String? pathItem) => + [path, pathItem].whereType().join("."); + + static FormGroup formElements(AnnotatelessO? annotatelessO) => FormGroup({ + emailControlName: FormControl( + value: annotatelessO?.email, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + passwordControlName: FormControl( + value: annotatelessO?.password, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false) + }, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false); +} + +@Rf(output: true) +class AnnotatelessOOutput extends Equatable { + final String email; + final String password; + const AnnotatelessOOutput({@RfControl() this.email = '', this.password = ''}); + @override + List get props => [email, password]; +} + +class ReactiveAnnotatelessOFormArrayBuilder< + ReactiveAnnotatelessOFormArrayBuilderT> extends StatelessWidget { + const ReactiveAnnotatelessOFormArrayBuilder({ + Key? key, + this.control, + this.formControl, + this.builder, + required this.itemBuilder, + }) : assert(control != null || formControl != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final FormArray? formControl; + + final FormArray? Function( + AnnotatelessOForm formModel)? control; + + final Widget Function(BuildContext context, List itemList, + AnnotatelessOForm formModel)? builder; + + final Widget Function( + BuildContext context, + int i, + ReactiveAnnotatelessOFormArrayBuilderT? item, + AnnotatelessOForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveAnnotatelessOForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + return ReactiveFormArray( + formArray: formControl ?? control?.call(formModel), + builder: (context, formArray, child) { + final values = formArray.controls.map((e) => e.value).toList(); + final itemList = values + .asMap() + .map((i, item) { + return MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + ); + }) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} + +class ReactiveAnnotatelessOFormFormGroupArrayBuilder< + ReactiveAnnotatelessOFormFormGroupArrayBuilderT> extends StatelessWidget { + const ReactiveAnnotatelessOFormFormGroupArrayBuilder({ + Key? key, + this.extended, + this.getExtended, + this.builder, + required this.itemBuilder, + }) : assert(extended != null || getExtended != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final ExtendedControl?>, + List>? extended; + + final ExtendedControl?>, + List> + Function(AnnotatelessOForm formModel)? getExtended; + + final Widget Function(BuildContext context, List itemList, + AnnotatelessOForm formModel)? builder; + + final Widget Function( + BuildContext context, + int i, + ReactiveAnnotatelessOFormFormGroupArrayBuilderT? item, + AnnotatelessOForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveAnnotatelessOForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + final value = (extended ?? getExtended?.call(formModel))!; + + return StreamBuilder?>?>( + stream: value.control.valueChanges, + builder: (context, snapshot) { + final itemList = (value.value() ?? + []) + .asMap() + .map((i, item) => MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + )) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} diff --git a/packages/reactive_forms_generator/example/lib/docs/array_nullable/array_nullable.dart b/packages/reactive_forms_generator/example/lib/docs/array_nullable/array_nullable.dart index 60fdddf6..8724b7c0 100644 --- a/packages/reactive_forms_generator/example/lib/docs/array_nullable/array_nullable.dart +++ b/packages/reactive_forms_generator/example/lib/docs/array_nullable/array_nullable.dart @@ -3,7 +3,7 @@ import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; part 'array_nullable.gform.dart'; -@Rf() +@Rf(output: false) class ArrayNullable { final List emailList; diff --git a/packages/reactive_forms_generator/example/lib/docs/array_nullable/array_nullable.gform.dart b/packages/reactive_forms_generator/example/lib/docs/array_nullable/array_nullable.gform.dart index dfc8ec46..7e5b7bb4 100644 --- a/packages/reactive_forms_generator/example/lib/docs/array_nullable/array_nullable.gform.dart +++ b/packages/reactive_forms_generator/example/lib/docs/array_nullable/array_nullable.gform.dart @@ -1,7 +1,7 @@ // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'array_nullable.dart'; @@ -137,6 +137,8 @@ class ArrayNullableFormBuilder extends StatefulWidget { class _ArrayNullableFormBuilderState extends State { late ArrayNullableForm _formModel; + StreamSubscription? _logSubscription; + @override void initState() { _formModel = @@ -148,6 +150,34 @@ class _ArrayNullableFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logSubscription = _logArrayNullableForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -163,6 +193,7 @@ class _ArrayNullableFormBuilderState extends State { @override void dispose() { _formModel.form.dispose(); + _logSubscription?.cancel(); super.dispose(); } @@ -185,7 +216,9 @@ class _ArrayNullableFormBuilderState extends State { } } -class ArrayNullableForm implements FormModel { +final _logArrayNullableForm = Logger.detached('ArrayNullableForm'); + +class ArrayNullableForm implements FormModel { ArrayNullableForm( this.form, this.path, @@ -788,9 +821,11 @@ class ArrayNullableForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'ArrayNullableForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logArrayNullableForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return ArrayNullable( emailList: _emailListValue, @@ -841,10 +876,20 @@ class ArrayNullableForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logArrayNullableForm.info('Errors'); + _logArrayNullableForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(ArrayNullable? other) { + return mapEquals( + this.form.value, + ArrayNullableForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } diff --git a/packages/reactive_forms_generator/example/lib/docs/delivery_list/delivery_list.dart b/packages/reactive_forms_generator/example/lib/docs/delivery_list/delivery_list.dart index 06852199..90d45a90 100644 --- a/packages/reactive_forms_generator/example/lib/docs/delivery_list/delivery_list.dart +++ b/packages/reactive_forms_generator/example/lib/docs/delivery_list/delivery_list.dart @@ -3,7 +3,7 @@ import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; part 'delivery_list.gform.dart'; -@Rf() +@Rf(output: false) class DeliveryList extends Equatable { final List deliveryList; final List? clientList; @@ -17,7 +17,7 @@ class DeliveryList extends Equatable { List get props => [deliveryList, clientList]; } -@Rf(name: 'StandaloneDeliveryPoint') +@Rf(output: false, name: 'StandaloneDeliveryPoint') @RfGroup() class DeliveryPoint extends Equatable { final String name; diff --git a/packages/reactive_forms_generator/example/lib/docs/delivery_list/delivery_list.gform.dart b/packages/reactive_forms_generator/example/lib/docs/delivery_list/delivery_list.gform.dart index d4c15560..93c1747d 100644 --- a/packages/reactive_forms_generator/example/lib/docs/delivery_list/delivery_list.gform.dart +++ b/packages/reactive_forms_generator/example/lib/docs/delivery_list/delivery_list.gform.dart @@ -1,7 +1,7 @@ // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'delivery_list.dart'; @@ -137,6 +137,8 @@ class DeliveryListFormBuilder extends StatefulWidget { class _DeliveryListFormBuilderState extends State { late DeliveryListForm _formModel; + StreamSubscription? _logSubscription; + @override void initState() { _formModel = @@ -148,6 +150,34 @@ class _DeliveryListFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logSubscription = _logDeliveryListForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -163,6 +193,7 @@ class _DeliveryListFormBuilderState extends State { @override void dispose() { _formModel.form.dispose(); + _logSubscription?.cancel(); super.dispose(); } @@ -185,7 +216,9 @@ class _DeliveryListFormBuilderState extends State { } } -class DeliveryListForm implements FormModel { +final _logDeliveryListForm = Logger.detached('DeliveryListForm'); + +class DeliveryListForm implements FormModel { DeliveryListForm( this.form, this.path, @@ -592,9 +625,11 @@ class DeliveryListForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'DeliveryListForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logDeliveryListForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return DeliveryList( deliveryList: _deliveryListValue, clientList: _clientListValue); @@ -647,10 +682,20 @@ class DeliveryListForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logDeliveryListForm.info('Errors'); + _logDeliveryListForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(DeliveryList? other) { + return mapEquals( + this.form.value, + DeliveryListForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } @@ -702,7 +747,9 @@ class DeliveryListForm implements FormModel { disabled: false); } -class DeliveryPointForm implements FormModel { +final _logDeliveryPointForm = Logger.detached('DeliveryPointForm'); + +class DeliveryPointForm implements FormModel { DeliveryPointForm( this.form, this.path, @@ -885,9 +932,11 @@ class DeliveryPointForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'DeliveryPointForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logDeliveryPointForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return DeliveryPoint(name: _nameValue, address: _addressValue); } @@ -935,10 +984,20 @@ class DeliveryPointForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logDeliveryPointForm.info('Errors'); + _logDeliveryPointForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(DeliveryPoint? other) { + return mapEquals( + this.form.value, + DeliveryPointForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } @@ -982,7 +1041,9 @@ class DeliveryPointForm implements FormModel { disabled: false); } -class AddressForm implements FormModel
{ +final _logAddressForm = Logger.detached('AddressForm'); + +class AddressForm implements FormModel { AddressForm( this.form, this.path, @@ -1189,9 +1250,11 @@ class AddressForm implements FormModel
{ final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'AddressForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logAddressForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Address(street: _streetValue, city: _cityValue); } @@ -1237,10 +1300,20 @@ class AddressForm implements FormModel
{ if (currentForm.valid) { onValid(model); } else { + _logAddressForm.info('Errors'); + _logAddressForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(Address? other) { + return mapEquals( + this.form.value, + AddressForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } @@ -1290,7 +1363,9 @@ class AddressForm implements FormModel
{ disabled: false); } -class ClientForm implements FormModel { +final _logClientForm = Logger.detached('ClientForm'); + +class ClientForm implements FormModel { ClientForm( this.form, this.path, @@ -1565,9 +1640,11 @@ class ClientForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'ClientForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logClientForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Client( clientType: _clientTypeValue, name: _nameValue, notes: _notesValue); @@ -1614,10 +1691,20 @@ class ClientForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logClientForm.info('Errors'); + _logClientForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(Client? other) { + return mapEquals( + this.form.value, + ClientForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } @@ -1936,6 +2023,8 @@ class _StandaloneDeliveryPointFormBuilderState extends State { late StandaloneDeliveryPointForm _formModel; + StreamSubscription? _logSubscription; + @override void initState() { _formModel = StandaloneDeliveryPointForm( @@ -1947,6 +2036,35 @@ class _StandaloneDeliveryPointFormBuilderState widget.initState?.call(context, _formModel); + _logSubscription = + _logStandaloneDeliveryPointForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -1962,6 +2080,7 @@ class _StandaloneDeliveryPointFormBuilderState @override void dispose() { _formModel.form.dispose(); + _logSubscription?.cancel(); super.dispose(); } @@ -1984,7 +2103,11 @@ class _StandaloneDeliveryPointFormBuilderState } } -class StandaloneDeliveryPointForm implements FormModel { +final _logStandaloneDeliveryPointForm = + Logger.detached('StandaloneDeliveryPointForm'); + +class StandaloneDeliveryPointForm + implements FormModel { StandaloneDeliveryPointForm( this.form, this.path, @@ -2167,9 +2290,11 @@ class StandaloneDeliveryPointForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'StandaloneDeliveryPointForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logStandaloneDeliveryPointForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return DeliveryPoint(name: _nameValue, address: _addressValue); } @@ -2217,10 +2342,20 @@ class StandaloneDeliveryPointForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logStandaloneDeliveryPointForm.info('Errors'); + _logStandaloneDeliveryPointForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(DeliveryPoint? other) { + return mapEquals( + this.form.value, + StandaloneDeliveryPointForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } diff --git a/packages/reactive_forms_generator/example/lib/docs/freezed/freezed_class.dart b/packages/reactive_forms_generator/example/lib/docs/freezed/freezed_class.dart index a9a50f15..813f24ab 100644 --- a/packages/reactive_forms_generator/example/lib/docs/freezed/freezed_class.dart +++ b/packages/reactive_forms_generator/example/lib/docs/freezed/freezed_class.dart @@ -9,7 +9,7 @@ part 'freezed_class.freezed.dart'; part 'freezed_class.gform.dart'; @freezed -@Rf() +@Rf(output: false) class FreezedClass with _$FreezedClass { FreezedClass._(); diff --git a/packages/reactive_forms_generator/example/lib/docs/freezed/freezed_class.gform.dart b/packages/reactive_forms_generator/example/lib/docs/freezed/freezed_class.gform.dart index 0647bdf2..f4dcb7aa 100644 --- a/packages/reactive_forms_generator/example/lib/docs/freezed/freezed_class.gform.dart +++ b/packages/reactive_forms_generator/example/lib/docs/freezed/freezed_class.gform.dart @@ -1,7 +1,7 @@ // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'freezed_class.dart'; @@ -137,6 +137,8 @@ class FreezedClassFormBuilder extends StatefulWidget { class _FreezedClassFormBuilderState extends State { late FreezedClassForm _formModel; + StreamSubscription? _logSubscription; + @override void initState() { _formModel = @@ -148,6 +150,34 @@ class _FreezedClassFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logSubscription = _logFreezedClassForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -163,6 +193,7 @@ class _FreezedClassFormBuilderState extends State { @override void dispose() { _formModel.form.dispose(); + _logSubscription?.cancel(); super.dispose(); } @@ -185,7 +216,9 @@ class _FreezedClassFormBuilderState extends State { } } -class FreezedClassForm implements FormModel { +final _logFreezedClassForm = Logger.detached('FreezedClassForm'); + +class FreezedClassForm implements FormModel { FreezedClassForm( this.form, this.path, @@ -676,9 +709,11 @@ class FreezedClassForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'FreezedClassForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logFreezedClassForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return FreezedClass(_genderValue, id: _idValue, @@ -728,10 +763,20 @@ class FreezedClassForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logFreezedClassForm.info('Errors'); + _logFreezedClassForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(FreezedClass? other) { + return mapEquals( + this.form.value, + FreezedClassForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } diff --git a/packages/reactive_forms_generator/example/lib/docs/freezed/freezed_class_output.dart b/packages/reactive_forms_generator/example/lib/docs/freezed/freezed_class_output.dart new file mode 100644 index 00000000..45624702 --- /dev/null +++ b/packages/reactive_forms_generator/example/lib/docs/freezed/freezed_class_output.dart @@ -0,0 +1,30 @@ +// ignore_for_file: invalid_annotation_target +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; + +part 'freezed_class_output.g.dart'; + +part 'freezed_class_output.freezed.dart'; + +part 'freezed_class_output.gform.dart'; + +@freezed +@Rf(output: true) +class FreezedClassO with _$FreezedClassO { + FreezedClassO._(); + + factory FreezedClassO( + @RfControl() String? gender, + @RfControl(validators: [RequiredValidator()]) String? genderR, { + @RfControl() String? id, + @RfControl(validators: [RequiredValidator()]) String? idR, + @RfControl() String? name, + @JsonKey(name: 'logo_image') @RfControl() String? logoImage, + @RfControl() double? year, + }) = _FreezedClassO; + + factory FreezedClassO.fromJson(Map json) => + _$FreezedClassOFromJson(json); + + bool method() => false; +} diff --git a/packages/reactive_forms_generator/example/lib/docs/freezed/freezed_class_output.freezed.dart b/packages/reactive_forms_generator/example/lib/docs/freezed/freezed_class_output.freezed.dart new file mode 100644 index 00000000..802ede35 --- /dev/null +++ b/packages/reactive_forms_generator/example/lib/docs/freezed/freezed_class_output.freezed.dart @@ -0,0 +1,588 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'freezed_class_output.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +FreezedClassO _$FreezedClassOFromJson(Map json) { + return _FreezedClassO.fromJson(json); +} + +/// @nodoc +mixin _$FreezedClassO { + @RfControl() + String? get gender => throw _privateConstructorUsedError; + @RfControl(validators: [RequiredValidator()]) + String? get genderR => throw _privateConstructorUsedError; + @RfControl() + String? get id => throw _privateConstructorUsedError; + @RfControl(validators: [RequiredValidator()]) + String? get idR => throw _privateConstructorUsedError; + @RfControl() + String? get name => throw _privateConstructorUsedError; + @JsonKey(name: 'logo_image') + @RfControl() + String? get logoImage => throw _privateConstructorUsedError; + @RfControl() + double? get year => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $FreezedClassOCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $FreezedClassOCopyWith<$Res> { + factory $FreezedClassOCopyWith( + FreezedClassO value, $Res Function(FreezedClassO) then) = + _$FreezedClassOCopyWithImpl<$Res, FreezedClassO>; + @useResult + $Res call( + {@RfControl() String? gender, + @RfControl(validators: [RequiredValidator()]) String? genderR, + @RfControl() String? id, + @RfControl(validators: [RequiredValidator()]) String? idR, + @RfControl() String? name, + @JsonKey(name: 'logo_image') @RfControl() String? logoImage, + @RfControl() double? year}); +} + +/// @nodoc +class _$FreezedClassOCopyWithImpl<$Res, $Val extends FreezedClassO> + implements $FreezedClassOCopyWith<$Res> { + _$FreezedClassOCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? gender = freezed, + Object? genderR = freezed, + Object? id = freezed, + Object? idR = freezed, + Object? name = freezed, + Object? logoImage = freezed, + Object? year = freezed, + }) { + return _then(_value.copyWith( + gender: freezed == gender + ? _value.gender + : gender // ignore: cast_nullable_to_non_nullable + as String?, + genderR: freezed == genderR + ? _value.genderR + : genderR // ignore: cast_nullable_to_non_nullable + as String?, + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + idR: freezed == idR + ? _value.idR + : idR // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + logoImage: freezed == logoImage + ? _value.logoImage + : logoImage // ignore: cast_nullable_to_non_nullable + as String?, + year: freezed == year + ? _value.year + : year // ignore: cast_nullable_to_non_nullable + as double?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$FreezedClassOImplCopyWith<$Res> + implements $FreezedClassOCopyWith<$Res> { + factory _$$FreezedClassOImplCopyWith( + _$FreezedClassOImpl value, $Res Function(_$FreezedClassOImpl) then) = + __$$FreezedClassOImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {@RfControl() String? gender, + @RfControl(validators: [RequiredValidator()]) String? genderR, + @RfControl() String? id, + @RfControl(validators: [RequiredValidator()]) String? idR, + @RfControl() String? name, + @JsonKey(name: 'logo_image') @RfControl() String? logoImage, + @RfControl() double? year}); +} + +/// @nodoc +class __$$FreezedClassOImplCopyWithImpl<$Res> + extends _$FreezedClassOCopyWithImpl<$Res, _$FreezedClassOImpl> + implements _$$FreezedClassOImplCopyWith<$Res> { + __$$FreezedClassOImplCopyWithImpl( + _$FreezedClassOImpl _value, $Res Function(_$FreezedClassOImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? gender = freezed, + Object? genderR = freezed, + Object? id = freezed, + Object? idR = freezed, + Object? name = freezed, + Object? logoImage = freezed, + Object? year = freezed, + }) { + return _then(_$FreezedClassOImpl( + freezed == gender + ? _value.gender + : gender // ignore: cast_nullable_to_non_nullable + as String?, + freezed == genderR + ? _value.genderR + : genderR // ignore: cast_nullable_to_non_nullable + as String?, + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + idR: freezed == idR + ? _value.idR + : idR // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + logoImage: freezed == logoImage + ? _value.logoImage + : logoImage // ignore: cast_nullable_to_non_nullable + as String?, + year: freezed == year + ? _value.year + : year // ignore: cast_nullable_to_non_nullable + as double?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$FreezedClassOImpl extends _FreezedClassO { + _$FreezedClassOImpl(@RfControl() this.gender, + @RfControl(validators: [RequiredValidator()]) this.genderR, + {@RfControl() this.id, + @RfControl(validators: [RequiredValidator()]) this.idR, + @RfControl() this.name, + @JsonKey(name: 'logo_image') @RfControl() this.logoImage, + @RfControl() this.year}) + : super._(); + + factory _$FreezedClassOImpl.fromJson(Map json) => + _$$FreezedClassOImplFromJson(json); + + @override + @RfControl() + final String? gender; + @override + @RfControl(validators: [RequiredValidator()]) + final String? genderR; + @override + @RfControl() + final String? id; + @override + @RfControl(validators: [RequiredValidator()]) + final String? idR; + @override + @RfControl() + final String? name; + @override + @JsonKey(name: 'logo_image') + @RfControl() + final String? logoImage; + @override + @RfControl() + final double? year; + + @override + String toString() { + return 'FreezedClassO(gender: $gender, genderR: $genderR, id: $id, idR: $idR, name: $name, logoImage: $logoImage, year: $year)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$FreezedClassOImpl && + (identical(other.gender, gender) || other.gender == gender) && + (identical(other.genderR, genderR) || other.genderR == genderR) && + (identical(other.id, id) || other.id == id) && + (identical(other.idR, idR) || other.idR == idR) && + (identical(other.name, name) || other.name == name) && + (identical(other.logoImage, logoImage) || + other.logoImage == logoImage) && + (identical(other.year, year) || other.year == year)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, gender, genderR, id, idR, name, logoImage, year); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$FreezedClassOImplCopyWith<_$FreezedClassOImpl> get copyWith => + __$$FreezedClassOImplCopyWithImpl<_$FreezedClassOImpl>(this, _$identity); + + @override + Map toJson() { + return _$$FreezedClassOImplToJson( + this, + ); + } +} + +abstract class _FreezedClassO extends FreezedClassO { + factory _FreezedClassO(@RfControl() final String? gender, + @RfControl(validators: [RequiredValidator()]) final String? genderR, + {@RfControl() final String? id, + @RfControl(validators: [RequiredValidator()]) final String? idR, + @RfControl() final String? name, + @JsonKey(name: 'logo_image') @RfControl() final String? logoImage, + @RfControl() final double? year}) = _$FreezedClassOImpl; + _FreezedClassO._() : super._(); + + factory _FreezedClassO.fromJson(Map json) = + _$FreezedClassOImpl.fromJson; + + @override + @RfControl() + String? get gender; + @override + @RfControl(validators: [RequiredValidator()]) + String? get genderR; + @override + @RfControl() + String? get id; + @override + @RfControl(validators: [RequiredValidator()]) + String? get idR; + @override + @RfControl() + String? get name; + @override + @JsonKey(name: 'logo_image') + @RfControl() + String? get logoImage; + @override + @RfControl() + double? get year; + @override + @JsonKey(ignore: true) + _$$FreezedClassOImplCopyWith<_$FreezedClassOImpl> get copyWith => + throw _privateConstructorUsedError; +} + +FreezedClassOOutput _$FreezedClassOOutputFromJson(Map json) { + return _FreezedClassOOutput.fromJson(json); +} + +/// @nodoc +mixin _$FreezedClassOOutput { + @RfControl() + String? get gender => throw _privateConstructorUsedError; + @RfControl(validators: [RequiredValidator()]) + String get genderR => throw _privateConstructorUsedError; + @RfControl() + String? get id => throw _privateConstructorUsedError; + @RfControl(validators: [RequiredValidator()]) + String get idR => throw _privateConstructorUsedError; + @RfControl() + String? get name => throw _privateConstructorUsedError; + @JsonKey(name: 'logo_image') + @RfControl() + String? get logoImage => throw _privateConstructorUsedError; + @RfControl() + double? get year => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $FreezedClassOOutputCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $FreezedClassOOutputCopyWith<$Res> { + factory $FreezedClassOOutputCopyWith( + FreezedClassOOutput value, $Res Function(FreezedClassOOutput) then) = + _$FreezedClassOOutputCopyWithImpl<$Res, FreezedClassOOutput>; + @useResult + $Res call( + {@RfControl() String? gender, + @RfControl(validators: [RequiredValidator()]) String genderR, + @RfControl() String? id, + @RfControl(validators: [RequiredValidator()]) String idR, + @RfControl() String? name, + @JsonKey(name: 'logo_image') @RfControl() String? logoImage, + @RfControl() double? year}); +} + +/// @nodoc +class _$FreezedClassOOutputCopyWithImpl<$Res, $Val extends FreezedClassOOutput> + implements $FreezedClassOOutputCopyWith<$Res> { + _$FreezedClassOOutputCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? gender = freezed, + Object? genderR = null, + Object? id = freezed, + Object? idR = null, + Object? name = freezed, + Object? logoImage = freezed, + Object? year = freezed, + }) { + return _then(_value.copyWith( + gender: freezed == gender + ? _value.gender + : gender // ignore: cast_nullable_to_non_nullable + as String?, + genderR: null == genderR + ? _value.genderR + : genderR // ignore: cast_nullable_to_non_nullable + as String, + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + idR: null == idR + ? _value.idR + : idR // ignore: cast_nullable_to_non_nullable + as String, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + logoImage: freezed == logoImage + ? _value.logoImage + : logoImage // ignore: cast_nullable_to_non_nullable + as String?, + year: freezed == year + ? _value.year + : year // ignore: cast_nullable_to_non_nullable + as double?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$FreezedClassOOutputImplCopyWith<$Res> + implements $FreezedClassOOutputCopyWith<$Res> { + factory _$$FreezedClassOOutputImplCopyWith(_$FreezedClassOOutputImpl value, + $Res Function(_$FreezedClassOOutputImpl) then) = + __$$FreezedClassOOutputImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {@RfControl() String? gender, + @RfControl(validators: [RequiredValidator()]) String genderR, + @RfControl() String? id, + @RfControl(validators: [RequiredValidator()]) String idR, + @RfControl() String? name, + @JsonKey(name: 'logo_image') @RfControl() String? logoImage, + @RfControl() double? year}); +} + +/// @nodoc +class __$$FreezedClassOOutputImplCopyWithImpl<$Res> + extends _$FreezedClassOOutputCopyWithImpl<$Res, _$FreezedClassOOutputImpl> + implements _$$FreezedClassOOutputImplCopyWith<$Res> { + __$$FreezedClassOOutputImplCopyWithImpl(_$FreezedClassOOutputImpl _value, + $Res Function(_$FreezedClassOOutputImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? gender = freezed, + Object? genderR = null, + Object? id = freezed, + Object? idR = null, + Object? name = freezed, + Object? logoImage = freezed, + Object? year = freezed, + }) { + return _then(_$FreezedClassOOutputImpl( + freezed == gender + ? _value.gender + : gender // ignore: cast_nullable_to_non_nullable + as String?, + null == genderR + ? _value.genderR + : genderR // ignore: cast_nullable_to_non_nullable + as String, + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + idR: null == idR + ? _value.idR + : idR // ignore: cast_nullable_to_non_nullable + as String, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + logoImage: freezed == logoImage + ? _value.logoImage + : logoImage // ignore: cast_nullable_to_non_nullable + as String?, + year: freezed == year + ? _value.year + : year // ignore: cast_nullable_to_non_nullable + as double?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$FreezedClassOOutputImpl extends _FreezedClassOOutput { + _$FreezedClassOOutputImpl(@RfControl() this.gender, + @RfControl(validators: [RequiredValidator()]) this.genderR, + {@RfControl() this.id, + @RfControl(validators: [RequiredValidator()]) required this.idR, + @RfControl() this.name, + @JsonKey(name: 'logo_image') @RfControl() this.logoImage, + @RfControl() this.year}) + : super._(); + + factory _$FreezedClassOOutputImpl.fromJson(Map json) => + _$$FreezedClassOOutputImplFromJson(json); + + @override + @RfControl() + final String? gender; + @override + @RfControl(validators: [RequiredValidator()]) + final String genderR; + @override + @RfControl() + final String? id; + @override + @RfControl(validators: [RequiredValidator()]) + final String idR; + @override + @RfControl() + final String? name; + @override + @JsonKey(name: 'logo_image') + @RfControl() + final String? logoImage; + @override + @RfControl() + final double? year; + + @override + String toString() { + return 'FreezedClassOOutput(gender: $gender, genderR: $genderR, id: $id, idR: $idR, name: $name, logoImage: $logoImage, year: $year)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$FreezedClassOOutputImpl && + (identical(other.gender, gender) || other.gender == gender) && + (identical(other.genderR, genderR) || other.genderR == genderR) && + (identical(other.id, id) || other.id == id) && + (identical(other.idR, idR) || other.idR == idR) && + (identical(other.name, name) || other.name == name) && + (identical(other.logoImage, logoImage) || + other.logoImage == logoImage) && + (identical(other.year, year) || other.year == year)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, gender, genderR, id, idR, name, logoImage, year); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$FreezedClassOOutputImplCopyWith<_$FreezedClassOOutputImpl> get copyWith => + __$$FreezedClassOOutputImplCopyWithImpl<_$FreezedClassOOutputImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$FreezedClassOOutputImplToJson( + this, + ); + } +} + +abstract class _FreezedClassOOutput extends FreezedClassOOutput { + factory _FreezedClassOOutput(@RfControl() final String? gender, + @RfControl(validators: [RequiredValidator()]) final String genderR, + {@RfControl() final String? id, + @RfControl(validators: [RequiredValidator()]) required final String idR, + @RfControl() final String? name, + @JsonKey(name: 'logo_image') @RfControl() final String? logoImage, + @RfControl() final double? year}) = _$FreezedClassOOutputImpl; + _FreezedClassOOutput._() : super._(); + + factory _FreezedClassOOutput.fromJson(Map json) = + _$FreezedClassOOutputImpl.fromJson; + + @override + @RfControl() + String? get gender; + @override + @RfControl(validators: [RequiredValidator()]) + String get genderR; + @override + @RfControl() + String? get id; + @override + @RfControl(validators: [RequiredValidator()]) + String get idR; + @override + @RfControl() + String? get name; + @override + @JsonKey(name: 'logo_image') + @RfControl() + String? get logoImage; + @override + @RfControl() + double? get year; + @override + @JsonKey(ignore: true) + _$$FreezedClassOOutputImplCopyWith<_$FreezedClassOOutputImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/reactive_forms_generator/example/lib/docs/freezed/freezed_class_output.g.dart b/packages/reactive_forms_generator/example/lib/docs/freezed/freezed_class_output.g.dart new file mode 100644 index 00000000..db432dbd --- /dev/null +++ b/packages/reactive_forms_generator/example/lib/docs/freezed/freezed_class_output.g.dart @@ -0,0 +1,53 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'freezed_class_output.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$FreezedClassOImpl _$$FreezedClassOImplFromJson(Map json) => + _$FreezedClassOImpl( + json['gender'] as String?, + json['genderR'] as String?, + id: json['id'] as String?, + idR: json['idR'] as String?, + name: json['name'] as String?, + logoImage: json['logo_image'] as String?, + year: (json['year'] as num?)?.toDouble(), + ); + +Map _$$FreezedClassOImplToJson(_$FreezedClassOImpl instance) => + { + 'gender': instance.gender, + 'genderR': instance.genderR, + 'id': instance.id, + 'idR': instance.idR, + 'name': instance.name, + 'logo_image': instance.logoImage, + 'year': instance.year, + }; + +_$FreezedClassOOutputImpl _$$FreezedClassOOutputImplFromJson( + Map json) => + _$FreezedClassOOutputImpl( + json['gender'] as String?, + json['genderR'] as String, + id: json['id'] as String?, + idR: json['idR'] as String, + name: json['name'] as String?, + logoImage: json['logo_image'] as String?, + year: (json['year'] as num?)?.toDouble(), + ); + +Map _$$FreezedClassOOutputImplToJson( + _$FreezedClassOOutputImpl instance) => + { + 'gender': instance.gender, + 'genderR': instance.genderR, + 'id': instance.id, + 'idR': instance.idR, + 'name': instance.name, + 'logo_image': instance.logoImage, + 'year': instance.year, + }; diff --git a/packages/reactive_forms_generator/example/lib/docs/freezed/freezed_class_output.gform.dart b/packages/reactive_forms_generator/example/lib/docs/freezed/freezed_class_output.gform.dart new file mode 100644 index 00000000..dafc8581 --- /dev/null +++ b/packages/reactive_forms_generator/example/lib/docs/freezed/freezed_class_output.gform.dart @@ -0,0 +1,1200 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'freezed_class_output.dart'; + +// ************************************************************************** +// ReactiveFormsGenerator +// ************************************************************************** + +class ReactiveFreezedClassOFormConsumer extends StatelessWidget { + const ReactiveFreezedClassOFormConsumer({ + Key? key, + required this.builder, + this.child, + }) : super(key: key); + + final Widget? child; + + final Widget Function( + BuildContext context, FreezedClassOForm formModel, Widget? child) builder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveFreezedClassOForm.of(context); + + if (formModel is! FreezedClassOForm) { + throw FormControlParentNotFoundException(this); + } + return builder(context, formModel, child); + } +} + +class FreezedClassOFormInheritedStreamer extends InheritedStreamer { + const FreezedClassOFormInheritedStreamer({ + Key? key, + required this.form, + required Stream stream, + required Widget child, + }) : super( + stream, + child, + key: key, + ); + + final FreezedClassOForm form; +} + +class ReactiveFreezedClassOForm extends StatelessWidget { + const ReactiveFreezedClassOForm({ + Key? key, + required this.form, + required this.child, + this.canPop, + this.onPopInvoked, + }) : super(key: key); + + final Widget child; + + final FreezedClassOForm form; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + static FreezedClassOForm? of( + BuildContext context, { + bool listen = true, + }) { + if (listen) { + return context + .dependOnInheritedWidgetOfExactType< + FreezedClassOFormInheritedStreamer>() + ?.form; + } + + final element = context.getElementForInheritedWidgetOfExactType< + FreezedClassOFormInheritedStreamer>(); + return element == null + ? null + : (element.widget as FreezedClassOFormInheritedStreamer).form; + } + + @override + Widget build(BuildContext context) { + return FreezedClassOFormInheritedStreamer( + form: form, + stream: form.form.statusChanged, + child: ReactiveFormPopScope( + canPop: canPop, + onPopInvoked: onPopInvoked, + child: child, + ), + ); + } +} + +extension ReactiveReactiveFreezedClassOFormExt on BuildContext { + FreezedClassOForm? freezedClassOFormWatch() => + ReactiveFreezedClassOForm.of(this); + + FreezedClassOForm? freezedClassOFormRead() => + ReactiveFreezedClassOForm.of(this, listen: false); +} + +class FreezedClassOFormBuilder extends StatefulWidget { + const FreezedClassOFormBuilder({ + Key? key, + this.model, + this.child, + this.canPop, + this.onPopInvoked, + required this.builder, + this.initState, + }) : super(key: key); + + final FreezedClassO? model; + + final Widget? child; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + final Widget Function( + BuildContext context, FreezedClassOForm formModel, Widget? child) builder; + + final void Function(BuildContext context, FreezedClassOForm formModel)? + initState; + + @override + _FreezedClassOFormBuilderState createState() => + _FreezedClassOFormBuilderState(); +} + +class _FreezedClassOFormBuilderState extends State { + late FreezedClassOForm _formModel; + + StreamSubscription? _logSubscription; + + @override + void initState() { + _formModel = + FreezedClassOForm(FreezedClassOForm.formElements(widget.model), null); + + if (_formModel.form.disabled) { + _formModel.form.markAsDisabled(); + } + + widget.initState?.call(context, _formModel); + + _logSubscription = _logFreezedClassOForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + + super.initState(); + } + + @override + void didUpdateWidget(covariant FreezedClassOFormBuilder oldWidget) { + if (widget.model != oldWidget.model) { + _formModel.updateValue(widget.model); + } + + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + _formModel.form.dispose(); + _logSubscription?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ReactiveFreezedClassOForm( + key: ObjectKey(_formModel), + form: _formModel, + // canPop: widget.canPop, + // onPopInvoked: widget.onPopInvoked, + child: ReactiveFormBuilder( + form: () => _formModel.form, + canPop: widget.canPop, + onPopInvoked: widget.onPopInvoked, + builder: (context, formGroup, child) => + widget.builder(context, _formModel, widget.child), + child: widget.child, + ), + ); + } +} + +final _logFreezedClassOForm = Logger.detached('FreezedClassOForm'); + +class FreezedClassOForm + implements FormModel { + FreezedClassOForm( + this.form, + this.path, + ); + + static const String genderControlName = "gender"; + + static const String genderRControlName = "genderR"; + + static const String idControlName = "id"; + + static const String idRControlName = "idR"; + + static const String nameControlName = "name"; + + static const String logoImageControlName = "logoImage"; + + static const String yearControlName = "year"; + + final FormGroup form; + + final String? path; + + final Map _disabled = {}; + + String genderControlPath() => pathBuilder(genderControlName); + + String genderRControlPath() => pathBuilder(genderRControlName); + + String idControlPath() => pathBuilder(idControlName); + + String idRControlPath() => pathBuilder(idRControlName); + + String nameControlPath() => pathBuilder(nameControlName); + + String logoImageControlPath() => pathBuilder(logoImageControlName); + + String yearControlPath() => pathBuilder(yearControlName); + + String? get _genderValue => genderControl?.value; + + String get _genderRValue => genderRControl.value as String; + + String? get _idValue => idControl?.value; + + String get _idRValue => idRControl.value as String; + + String? get _nameValue => nameControl?.value; + + String? get _logoImageValue => logoImageControl?.value; + + double? get _yearValue => yearControl?.value; + + bool get containsGender { + try { + form.control(genderControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsGenderR { + try { + form.control(genderRControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsId { + try { + form.control(idControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsIdR { + try { + form.control(idRControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsName { + try { + form.control(nameControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsLogoImage { + try { + form.control(logoImageControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsYear { + try { + form.control(yearControlPath()); + return true; + } catch (e) { + return false; + } + } + + Map? get genderErrors => genderControl?.errors; + + Map? get genderRErrors => genderRControl.errors; + + Map? get idErrors => idControl?.errors; + + Map? get idRErrors => idRControl.errors; + + Map? get nameErrors => nameControl?.errors; + + Map? get logoImageErrors => logoImageControl?.errors; + + Map? get yearErrors => yearControl?.errors; + + void get genderFocus => form.focus(genderControlPath()); + + void get genderRFocus => form.focus(genderRControlPath()); + + void get idFocus => form.focus(idControlPath()); + + void get idRFocus => form.focus(idRControlPath()); + + void get nameFocus => form.focus(nameControlPath()); + + void get logoImageFocus => form.focus(logoImageControlPath()); + + void get yearFocus => form.focus(yearControlPath()); + + void genderRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsGender) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + genderControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + genderControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void genderRRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsGenderR) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + genderRControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + genderRControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void idRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsId) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + idControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + idControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void idRRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsIdR) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + idRControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + idRControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void nameRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsName) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + nameControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + nameControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void logoImageRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsLogoImage) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + logoImageControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + logoImageControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void yearRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsYear) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + yearControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + yearControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void genderValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + genderControl?.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void genderRValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + genderRControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void idValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + idControl?.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void idRValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + idRControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void nameValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + nameControl?.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void logoImageValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + logoImageControl?.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void yearValueUpdate( + double? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + yearControl?.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void genderValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + genderControl?.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void genderRValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + genderRControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void idValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + idControl?.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void idRValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + idRControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void nameValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + nameControl?.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void logoImageValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + logoImageControl?.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void yearValuePatch( + double? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + yearControl?.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void genderValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + genderControl?.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void genderRValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + genderRControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void idValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + idControl?.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void idRValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + idRControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void nameValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + nameControl?.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void logoImageValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + logoImageControl?.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void yearValueReset( + double? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + yearControl?.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + FormControl? get genderControl => containsGender + ? form.control(genderControlPath()) as FormControl? + : null; + + FormControl get genderRControl => + form.control(genderRControlPath()) as FormControl; + + FormControl? get idControl => + containsId ? form.control(idControlPath()) as FormControl? : null; + + FormControl get idRControl => + form.control(idRControlPath()) as FormControl; + + FormControl? get nameControl => containsName + ? form.control(nameControlPath()) as FormControl? + : null; + + FormControl? get logoImageControl => containsLogoImage + ? form.control(logoImageControlPath()) as FormControl? + : null; + + FormControl? get yearControl => containsYear + ? form.control(yearControlPath()) as FormControl? + : null; + + void genderSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + genderControl?.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + genderControl?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void genderRSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + genderRControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + genderRControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void idSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + idControl?.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + idControl?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void idRSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + idRControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + idRControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void nameSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + nameControl?.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + nameControl?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void logoImageSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + logoImageControl?.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + logoImageControl?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void yearSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + yearControl?.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + yearControl?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + @override + FreezedClassOOutput get model { + final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; + + if (!isValid) { + _logFreezedClassOForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); + } + return FreezedClassOOutput(_genderValue, _genderRValue, + id: _idValue, + idR: _idRValue, + name: _nameValue, + logoImage: _logoImageValue, + year: _yearValue); + } + + @override + void toggleDisabled({ + bool updateParent = true, + bool emitEvent = true, + }) { + final currentFormInstance = currentForm; + + if (currentFormInstance is! FormGroup) { + return; + } + + if (_disabled.isEmpty) { + currentFormInstance.controls.forEach((key, control) { + _disabled[key] = control.disabled; + }); + + currentForm.markAsDisabled( + updateParent: updateParent, emitEvent: emitEvent); + } else { + currentFormInstance.controls.forEach((key, control) { + if (_disabled[key] == false) { + currentFormInstance.controls[key]?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + + _disabled.remove(key); + }); + } + } + + @override + void submit({ + required void Function(FreezedClassOOutput model) onValid, + void Function()? onNotValid, + }) { + currentForm.markAllAsTouched(); + if (currentForm.valid) { + onValid(model); + } else { + _logFreezedClassOForm.info('Errors'); + _logFreezedClassOForm.info('┗━━ ${form.errors}'); + onNotValid?.call(); + } + } + + @override + bool equalsTo(FreezedClassO? other) { + return mapEquals( + this.form.value, + FreezedClassOForm.formElements(other).value, + ); + } + + AbstractControl get currentForm { + return path == null ? form : form.control(path!); + } + + @override + void updateValue( + FreezedClassO? value, { + bool updateParent = true, + bool emitEvent = true, + }) => + form.updateValue(FreezedClassOForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + + @override + void reset({ + FreezedClassO? value, + bool updateParent = true, + bool emitEvent = true, + }) => + form.reset( + value: value != null ? formElements(value).rawValue : null, + updateParent: updateParent, + emitEvent: emitEvent); + + String pathBuilder(String? pathItem) => + [path, pathItem].whereType().join("."); + + static FormGroup formElements(FreezedClassO? freezedClassO) => FormGroup({ + genderControlName: FormControl( + value: freezedClassO?.gender, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + genderRControlName: FormControl( + value: freezedClassO?.genderR, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + idControlName: FormControl( + value: freezedClassO?.id, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + idRControlName: FormControl( + value: freezedClassO?.idR, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + nameControlName: FormControl( + value: freezedClassO?.name, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + logoImageControlName: FormControl( + value: freezedClassO?.logoImage, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + yearControlName: FormControl( + value: freezedClassO?.year, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false) + }, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false); +} + +@freezed +@Rf(output: true) +class FreezedClassOOutput with _$FreezedClassOOutput { + FreezedClassOOutput._(); + factory FreezedClassOOutput(@RfControl() String? gender, + @RfControl(validators: [RequiredValidator()]) String genderR, + {@RfControl() String? id, + @RfControl(validators: [RequiredValidator()]) required String idR, + @RfControl() String? name, + @JsonKey(name: 'logo_image') @RfControl() String? logoImage, + @RfControl() double? year}) = _FreezedClassOOutput; + factory FreezedClassOOutput.fromJson(Map json) => + _$FreezedClassOOutputFromJson(json); + bool method() => false; +} + +class ReactiveFreezedClassOFormArrayBuilder< + ReactiveFreezedClassOFormArrayBuilderT> extends StatelessWidget { + const ReactiveFreezedClassOFormArrayBuilder({ + Key? key, + this.control, + this.formControl, + this.builder, + required this.itemBuilder, + }) : assert(control != null || formControl != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final FormArray? formControl; + + final FormArray? Function( + FreezedClassOForm formModel)? control; + + final Widget Function(BuildContext context, List itemList, + FreezedClassOForm formModel)? builder; + + final Widget Function( + BuildContext context, + int i, + ReactiveFreezedClassOFormArrayBuilderT? item, + FreezedClassOForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveFreezedClassOForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + return ReactiveFormArray( + formArray: formControl ?? control?.call(formModel), + builder: (context, formArray, child) { + final values = formArray.controls.map((e) => e.value).toList(); + final itemList = values + .asMap() + .map((i, item) { + return MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + ); + }) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} + +class ReactiveFreezedClassOFormFormGroupArrayBuilder< + ReactiveFreezedClassOFormFormGroupArrayBuilderT> extends StatelessWidget { + const ReactiveFreezedClassOFormFormGroupArrayBuilder({ + Key? key, + this.extended, + this.getExtended, + this.builder, + required this.itemBuilder, + }) : assert(extended != null || getExtended != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final ExtendedControl?>, + List>? extended; + + final ExtendedControl?>, + List> + Function(FreezedClassOForm formModel)? getExtended; + + final Widget Function(BuildContext context, List itemList, + FreezedClassOForm formModel)? builder; + + final Widget Function( + BuildContext context, + int i, + ReactiveFreezedClassOFormFormGroupArrayBuilderT? item, + FreezedClassOForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveFreezedClassOForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + final value = (extended ?? getExtended?.call(formModel))!; + + return StreamBuilder?>?>( + stream: value.control.valueChanges, + builder: (context, snapshot) { + final itemList = (value.value() ?? + []) + .asMap() + .map((i, item) => MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + )) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} diff --git a/packages/reactive_forms_generator/example/lib/docs/freezed2/test.dart b/packages/reactive_forms_generator/example/lib/docs/freezed2/test.dart index e7b8c766..4ab36373 100644 --- a/packages/reactive_forms_generator/example/lib/docs/freezed2/test.dart +++ b/packages/reactive_forms_generator/example/lib/docs/freezed2/test.dart @@ -7,7 +7,7 @@ part 'test.freezed.dart'; part 'test.gform.dart'; @freezed -@Rf() +@Rf(output: false) class Test with _$Test { const Test._(); diff --git a/packages/reactive_forms_generator/example/lib/docs/freezed2/test.gform.dart b/packages/reactive_forms_generator/example/lib/docs/freezed2/test.gform.dart index 1eaa479c..6ec87947 100644 --- a/packages/reactive_forms_generator/example/lib/docs/freezed2/test.gform.dart +++ b/packages/reactive_forms_generator/example/lib/docs/freezed2/test.gform.dart @@ -1,7 +1,7 @@ // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'test.dart'; @@ -132,6 +132,8 @@ class TestFormBuilder extends StatefulWidget { class _TestFormBuilderState extends State { late TestForm _formModel; + StreamSubscription? _logSubscription; + @override void initState() { _formModel = TestForm(TestForm.formElements(widget.model), null); @@ -142,6 +144,34 @@ class _TestFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logSubscription = _logTestForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -157,6 +187,7 @@ class _TestFormBuilderState extends State { @override void dispose() { _formModel.form.dispose(); + _logSubscription?.cancel(); super.dispose(); } @@ -179,7 +210,9 @@ class _TestFormBuilderState extends State { } } -class TestForm implements FormModel { +final _logTestForm = Logger.detached('TestForm'); + +class TestForm implements FormModel { TestForm( this.form, this.path, @@ -359,9 +392,11 @@ class TestForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'TestForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logTestForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Test(title: _titleValue, description: _descriptionValue); } @@ -407,10 +442,20 @@ class TestForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logTestForm.info('Errors'); + _logTestForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(Test? other) { + return mapEquals( + this.form.value, + TestForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } diff --git a/packages/reactive_forms_generator/example/lib/docs/generic/generic.dart b/packages/reactive_forms_generator/example/lib/docs/generic/generic.dart index 9005b3e1..521a5027 100644 --- a/packages/reactive_forms_generator/example/lib/docs/generic/generic.dart +++ b/packages/reactive_forms_generator/example/lib/docs/generic/generic.dart @@ -6,7 +6,7 @@ part 'generic.freezed.dart'; part 'generic.gform.dart'; @freezed -@Rf() +@Rf(output: false) class Tags with _$Tags { factory Tags({ @RfControl() required List? tags, diff --git a/packages/reactive_forms_generator/example/lib/docs/generic/generic.gform.dart b/packages/reactive_forms_generator/example/lib/docs/generic/generic.gform.dart index 3b6c7e88..f28f19a0 100644 --- a/packages/reactive_forms_generator/example/lib/docs/generic/generic.gform.dart +++ b/packages/reactive_forms_generator/example/lib/docs/generic/generic.gform.dart @@ -1,7 +1,7 @@ // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'generic.dart'; @@ -132,6 +132,8 @@ class TagsFormBuilder extends StatefulWidget { class _TagsFormBuilderState extends State> { late TagsForm _formModel; + StreamSubscription? _logSubscription; + @override void initState() { _formModel = TagsForm(TagsForm.formElements(widget.model), null); @@ -142,6 +144,34 @@ class _TagsFormBuilderState extends State> { widget.initState?.call(context, _formModel); + _logSubscription = _logTagsForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -157,6 +187,7 @@ class _TagsFormBuilderState extends State> { @override void dispose() { _formModel.form.dispose(); + _logSubscription?.cancel(); super.dispose(); } @@ -179,7 +210,9 @@ class _TagsFormBuilderState extends State> { } } -class TagsForm implements FormModel> { +final _logTagsForm = Logger.detached('TagsForm'); + +class TagsForm implements FormModel, Tags> { TagsForm( this.form, this.path, @@ -291,9 +324,11 @@ class TagsForm implements FormModel> { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'TagsForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logTagsForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Tags(tags: _tagsValue); } @@ -339,10 +374,20 @@ class TagsForm implements FormModel> { if (currentForm.valid) { onValid(model); } else { + _logTagsForm.info('Errors'); + _logTagsForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(Tags? other) { + return mapEquals( + this.form.value, + TagsForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } diff --git a/packages/reactive_forms_generator/example/lib/docs/generic_status_list/generic_status_list.dart b/packages/reactive_forms_generator/example/lib/docs/generic_status_list/generic_status_list.dart index 3100bfaf..753bca52 100644 --- a/packages/reactive_forms_generator/example/lib/docs/generic_status_list/generic_status_list.dart +++ b/packages/reactive_forms_generator/example/lib/docs/generic_status_list/generic_status_list.dart @@ -2,7 +2,7 @@ import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; part 'generic_status_list.gform.dart'; -@Rf() +@Rf(output: false) class StatusList { final List list; diff --git a/packages/reactive_forms_generator/example/lib/docs/generic_status_list/generic_status_list.gform.dart b/packages/reactive_forms_generator/example/lib/docs/generic_status_list/generic_status_list.gform.dart index eb6661ac..d5650749 100644 --- a/packages/reactive_forms_generator/example/lib/docs/generic_status_list/generic_status_list.gform.dart +++ b/packages/reactive_forms_generator/example/lib/docs/generic_status_list/generic_status_list.gform.dart @@ -1,7 +1,7 @@ // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'generic_status_list.dart'; @@ -139,6 +139,8 @@ class _StatusListFormBuilderState extends State> { late StatusListForm _formModel; + StreamSubscription? _logSubscription; + @override void initState() { _formModel = @@ -150,6 +152,34 @@ class _StatusListFormBuilderState widget.initState?.call(context, _formModel); + _logSubscription = _logStatusListForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -165,6 +195,7 @@ class _StatusListFormBuilderState @override void dispose() { _formModel.form.dispose(); + _logSubscription?.cancel(); super.dispose(); } @@ -187,7 +218,10 @@ class _StatusListFormBuilderState } } -class StatusListForm implements FormModel> { +final _logStatusListForm = Logger.detached('StatusListForm'); + +class StatusListForm + implements FormModel, StatusList> { StatusListForm( this.form, this.path, @@ -312,9 +346,11 @@ class StatusListForm implements FormModel> { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'StatusListForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logStatusListForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return StatusList(list: _listValue); } @@ -360,10 +396,20 @@ class StatusListForm implements FormModel> { if (currentForm.valid) { onValid(model); } else { + _logStatusListForm.info('Errors'); + _logStatusListForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(StatusList? other) { + return mapEquals( + this.form.value, + StatusListForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } diff --git a/packages/reactive_forms_generator/example/lib/docs/group/group.dart b/packages/reactive_forms_generator/example/lib/docs/group/group.dart index e772ac1c..a33ea9fb 100644 --- a/packages/reactive_forms_generator/example/lib/docs/group/group.dart +++ b/packages/reactive_forms_generator/example/lib/docs/group/group.dart @@ -2,7 +2,7 @@ import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; part 'group.gform.dart'; -@Rf() +@Rf(output: false) class Group { final Personal? personal; diff --git a/packages/reactive_forms_generator/example/lib/docs/group/group.gform.dart b/packages/reactive_forms_generator/example/lib/docs/group/group.gform.dart index 65a95746..d7605db3 100644 --- a/packages/reactive_forms_generator/example/lib/docs/group/group.gform.dart +++ b/packages/reactive_forms_generator/example/lib/docs/group/group.gform.dart @@ -1,7 +1,7 @@ // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'group.dart'; @@ -132,6 +132,8 @@ class GroupFormBuilder extends StatefulWidget { class _GroupFormBuilderState extends State { late GroupForm _formModel; + StreamSubscription? _logSubscription; + @override void initState() { _formModel = GroupForm(GroupForm.formElements(widget.model), null); @@ -142,6 +144,34 @@ class _GroupFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logSubscription = _logGroupForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -157,6 +187,7 @@ class _GroupFormBuilderState extends State { @override void dispose() { _formModel.form.dispose(); + _logSubscription?.cancel(); super.dispose(); } @@ -179,7 +210,9 @@ class _GroupFormBuilderState extends State { } } -class GroupForm implements FormModel { +final _logGroupForm = Logger.detached('GroupForm'); + +class GroupForm implements FormModel { GroupForm( this.form, this.path, @@ -590,9 +623,11 @@ class GroupForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'GroupForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logGroupForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Group( personal: _personalValue, @@ -650,10 +685,20 @@ class GroupForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logGroupForm.info('Errors'); + _logGroupForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(Group? other) { + return mapEquals( + this.form.value, + GroupForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } @@ -693,7 +738,9 @@ class GroupForm implements FormModel { disabled: false); } -class PersonalForm implements FormModel { +final _logPersonalForm = Logger.detached('PersonalForm'); + +class PersonalForm implements FormModel { PersonalForm( this.form, this.path, @@ -900,9 +947,11 @@ class PersonalForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'PersonalForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logPersonalForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Personal(name: _nameValue, email: _emailValue); } @@ -948,10 +997,20 @@ class PersonalForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logPersonalForm.info('Errors'); + _logPersonalForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(Personal? other) { + return mapEquals( + this.form.value, + PersonalForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } @@ -1001,7 +1060,9 @@ class PersonalForm implements FormModel { disabled: false); } -class PhoneForm implements FormModel { +final _logPhoneForm = Logger.detached('PhoneForm'); + +class PhoneForm implements FormModel { PhoneForm( this.form, this.path, @@ -1208,9 +1269,11 @@ class PhoneForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'PhoneForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logPhoneForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Phone(phoneNumber: _phoneNumberValue, countryIso: _countryIsoValue); } @@ -1256,10 +1319,20 @@ class PhoneForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logPhoneForm.info('Errors'); + _logPhoneForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(Phone? other) { + return mapEquals( + this.form.value, + PhoneForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } @@ -1309,7 +1382,9 @@ class PhoneForm implements FormModel { disabled: false); } -class AddressForm implements FormModel
{ +final _logAddressForm = Logger.detached('AddressForm'); + +class AddressForm implements FormModel { AddressForm( this.form, this.path, @@ -1611,9 +1686,11 @@ class AddressForm implements FormModel
{ final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'AddressForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logAddressForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Address(street: _streetValue, city: _cityValue, zip: _zipValue); } @@ -1659,10 +1736,20 @@ class AddressForm implements FormModel
{ if (currentForm.valid) { onValid(model); } else { + _logAddressForm.info('Errors'); + _logAddressForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(Address? other) { + return mapEquals( + this.form.value, + AddressForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } diff --git a/packages/reactive_forms_generator/example/lib/docs/login/login.dart b/packages/reactive_forms_generator/example/lib/docs/login/login.dart index bdfddcd4..f6235f1f 100644 --- a/packages/reactive_forms_generator/example/lib/docs/login/login.dart +++ b/packages/reactive_forms_generator/example/lib/docs/login/login.dart @@ -5,7 +5,7 @@ import 'package:reactive_forms_annotations/reactive_forms_annotations.dart' part 'login.gform.dart'; -@Rf() +@Rf(output: false) @RfGroup( validators: [MustMatchValidator()], ) diff --git a/packages/reactive_forms_generator/example/lib/docs/login/login.gform.dart b/packages/reactive_forms_generator/example/lib/docs/login/login.gform.dart index 99b87978..62b6f8f1 100644 --- a/packages/reactive_forms_generator/example/lib/docs/login/login.gform.dart +++ b/packages/reactive_forms_generator/example/lib/docs/login/login.gform.dart @@ -1,7 +1,7 @@ // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'login.dart'; @@ -132,6 +132,8 @@ class LoginFormBuilder extends StatefulWidget { class _LoginFormBuilderState extends State { late LoginForm _formModel; + StreamSubscription? _logSubscription; + @override void initState() { _formModel = LoginForm(LoginForm.formElements(widget.model), null); @@ -142,6 +144,34 @@ class _LoginFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logSubscription = _logLoginForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -157,6 +187,7 @@ class _LoginFormBuilderState extends State { @override void dispose() { _formModel.form.dispose(); + _logSubscription?.cancel(); super.dispose(); } @@ -179,7 +210,9 @@ class _LoginFormBuilderState extends State { } } -class LoginForm implements FormModel { +final _logLoginForm = Logger.detached('LoginForm'); + +class LoginForm implements FormModel { LoginForm( this.form, this.path, @@ -332,9 +365,11 @@ class LoginForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'LoginForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logLoginForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Login(email: _emailValue, password: _passwordValue); } @@ -380,10 +415,20 @@ class LoginForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logLoginForm.info('Errors'); + _logLoginForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(Login? other) { + return mapEquals( + this.form.value, + LoginForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } diff --git a/packages/reactive_forms_generator/example/lib/docs/login/login_form.dart b/packages/reactive_forms_generator/example/lib/docs/login/login_form.dart index e6d70bdc..c54bfcbd 100644 --- a/packages/reactive_forms_generator/example/lib/docs/login/login_form.dart +++ b/packages/reactive_forms_generator/example/lib/docs/login/login_form.dart @@ -83,10 +83,14 @@ class _LoginFormWidgetState extends State { child: ElevatedButton( key: submitRaw.itemKey, onPressed: () { - debugPrint(formModel.model.email); - debugPrint(formModel.model.password); + formModel.submit(onValid: (_) {}); + final model = formModel.model; + formModel.form.markAllAsTouched(); - widget.onChange?.call(formModel.model); + widget.onChange?.call(model); + + debugPrint(model.email); + debugPrint(model.password); }, child: const Text('Submit raw'), ), diff --git a/packages/reactive_forms_generator/example/lib/docs/login/login_output.dart b/packages/reactive_forms_generator/example/lib/docs/login/login_output.dart new file mode 100644 index 00000000..8200f51a --- /dev/null +++ b/packages/reactive_forms_generator/example/lib/docs/login/login_output.dart @@ -0,0 +1,30 @@ +import 'package:example/helpers.dart'; +import 'package:equatable/equatable.dart'; +import 'package:reactive_forms_annotations/reactive_forms_annotations.dart' + hide MustMatchValidator; + +part 'login_output.gform.dart'; + +@Rf() +@RfGroup( + validators: [MustMatchValidator()], +) +class LoginO extends Equatable { + final String? email; + + final String? password; + + const LoginO({ + @RfControl( + validators: [RequiredValidator(), RequiredValidator()], + ) + this.email, + @RfControl( + validators: [RequiredValidator()], + ) + this.password, + }); + + @override + List get props => [email, password]; +} diff --git a/packages/reactive_forms_generator/example/lib/docs/login/login_output.gform.dart b/packages/reactive_forms_generator/example/lib/docs/login/login_output.gform.dart new file mode 100644 index 00000000..d22cedcb --- /dev/null +++ b/packages/reactive_forms_generator/example/lib/docs/login/login_output.gform.dart @@ -0,0 +1,677 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'login_output.dart'; + +// ************************************************************************** +// ReactiveFormsGenerator +// ************************************************************************** + +class ReactiveLoginOFormConsumer extends StatelessWidget { + const ReactiveLoginOFormConsumer({ + Key? key, + required this.builder, + this.child, + }) : super(key: key); + + final Widget? child; + + final Widget Function( + BuildContext context, LoginOForm formModel, Widget? child) builder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveLoginOForm.of(context); + + if (formModel is! LoginOForm) { + throw FormControlParentNotFoundException(this); + } + return builder(context, formModel, child); + } +} + +class LoginOFormInheritedStreamer extends InheritedStreamer { + const LoginOFormInheritedStreamer({ + Key? key, + required this.form, + required Stream stream, + required Widget child, + }) : super( + stream, + child, + key: key, + ); + + final LoginOForm form; +} + +class ReactiveLoginOForm extends StatelessWidget { + const ReactiveLoginOForm({ + Key? key, + required this.form, + required this.child, + this.canPop, + this.onPopInvoked, + }) : super(key: key); + + final Widget child; + + final LoginOForm form; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + static LoginOForm? of( + BuildContext context, { + bool listen = true, + }) { + if (listen) { + return context + .dependOnInheritedWidgetOfExactType() + ?.form; + } + + final element = context + .getElementForInheritedWidgetOfExactType(); + return element == null + ? null + : (element.widget as LoginOFormInheritedStreamer).form; + } + + @override + Widget build(BuildContext context) { + return LoginOFormInheritedStreamer( + form: form, + stream: form.form.statusChanged, + child: ReactiveFormPopScope( + canPop: canPop, + onPopInvoked: onPopInvoked, + child: child, + ), + ); + } +} + +extension ReactiveReactiveLoginOFormExt on BuildContext { + LoginOForm? loginOFormWatch() => ReactiveLoginOForm.of(this); + + LoginOForm? loginOFormRead() => ReactiveLoginOForm.of(this, listen: false); +} + +class LoginOFormBuilder extends StatefulWidget { + const LoginOFormBuilder({ + Key? key, + this.model, + this.child, + this.canPop, + this.onPopInvoked, + required this.builder, + this.initState, + }) : super(key: key); + + final LoginO? model; + + final Widget? child; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + final Widget Function( + BuildContext context, LoginOForm formModel, Widget? child) builder; + + final void Function(BuildContext context, LoginOForm formModel)? initState; + + @override + _LoginOFormBuilderState createState() => _LoginOFormBuilderState(); +} + +class _LoginOFormBuilderState extends State { + late LoginOForm _formModel; + + StreamSubscription? _logSubscription; + + @override + void initState() { + _formModel = LoginOForm(LoginOForm.formElements(widget.model), null); + + if (_formModel.form.disabled) { + _formModel.form.markAsDisabled(); + } + + widget.initState?.call(context, _formModel); + + _logSubscription = _logLoginOForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + + super.initState(); + } + + @override + void didUpdateWidget(covariant LoginOFormBuilder oldWidget) { + if (widget.model != oldWidget.model) { + _formModel.updateValue(widget.model); + } + + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + _formModel.form.dispose(); + _logSubscription?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ReactiveLoginOForm( + key: ObjectKey(_formModel), + form: _formModel, + // canPop: widget.canPop, + // onPopInvoked: widget.onPopInvoked, + child: ReactiveFormBuilder( + form: () => _formModel.form, + canPop: widget.canPop, + onPopInvoked: widget.onPopInvoked, + builder: (context, formGroup, child) => + widget.builder(context, _formModel, widget.child), + child: widget.child, + ), + ); + } +} + +final _logLoginOForm = Logger.detached('LoginOForm'); + +class LoginOForm implements FormModel { + LoginOForm( + this.form, + this.path, + ); + + static const String emailControlName = "email"; + + static const String passwordControlName = "password"; + + final FormGroup form; + + final String? path; + + final Map _disabled = {}; + + String emailControlPath() => pathBuilder(emailControlName); + + String passwordControlPath() => pathBuilder(passwordControlName); + + String get _emailValue => emailControl.value as String; + + String get _passwordValue => passwordControl.value as String; + + bool get containsEmail { + try { + form.control(emailControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsPassword { + try { + form.control(passwordControlPath()); + return true; + } catch (e) { + return false; + } + } + + Map? get emailErrors => emailControl.errors; + + Map? get passwordErrors => passwordControl.errors; + + void get emailFocus => form.focus(emailControlPath()); + + void get passwordFocus => form.focus(passwordControlPath()); + + void emailRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsEmail) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + emailControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + emailControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void passwordRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsPassword) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + passwordControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + passwordControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void emailValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + emailControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void passwordValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + passwordControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void emailValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + emailControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void passwordValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + passwordControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void emailValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + emailControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void passwordValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + passwordControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + FormControl get emailControl => + form.control(emailControlPath()) as FormControl; + + FormControl get passwordControl => + form.control(passwordControlPath()) as FormControl; + + void emailSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + emailControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + emailControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void passwordSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + passwordControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + passwordControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + @override + LoginOOutput get model { + final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; + + if (!isValid) { + _logLoginOForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); + } + return LoginOOutput(email: _emailValue, password: _passwordValue); + } + + @override + void toggleDisabled({ + bool updateParent = true, + bool emitEvent = true, + }) { + final currentFormInstance = currentForm; + + if (currentFormInstance is! FormGroup) { + return; + } + + if (_disabled.isEmpty) { + currentFormInstance.controls.forEach((key, control) { + _disabled[key] = control.disabled; + }); + + currentForm.markAsDisabled( + updateParent: updateParent, emitEvent: emitEvent); + } else { + currentFormInstance.controls.forEach((key, control) { + if (_disabled[key] == false) { + currentFormInstance.controls[key]?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + + _disabled.remove(key); + }); + } + } + + @override + void submit({ + required void Function(LoginOOutput model) onValid, + void Function()? onNotValid, + }) { + currentForm.markAllAsTouched(); + if (currentForm.valid) { + onValid(model); + } else { + _logLoginOForm.info('Errors'); + _logLoginOForm.info('┗━━ ${form.errors}'); + onNotValid?.call(); + } + } + + @override + bool equalsTo(LoginO? other) { + return mapEquals( + this.form.value, + LoginOForm.formElements(other).value, + ); + } + + AbstractControl get currentForm { + return path == null ? form : form.control(path!); + } + + @override + void updateValue( + LoginO? value, { + bool updateParent = true, + bool emitEvent = true, + }) => + form.updateValue(LoginOForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + + @override + void reset({ + LoginO? value, + bool updateParent = true, + bool emitEvent = true, + }) => + form.reset( + value: value != null ? formElements(value).rawValue : null, + updateParent: updateParent, + emitEvent: emitEvent); + + String pathBuilder(String? pathItem) => + [path, pathItem].whereType().join("."); + + static FormGroup formElements(LoginO? loginO) => FormGroup({ + emailControlName: FormControl( + value: loginO?.email, + validators: [RequiredValidator(), RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + passwordControlName: FormControl( + value: loginO?.password, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false) + }, + validators: [ + MustMatchValidator() + ], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false); +} + +@Rf() +@RfGroup(validators: [MustMatchValidator()]) +class LoginOOutput extends Equatable { + final String email; + final String password; + const LoginOOutput( + {@RfControl(validators: [RequiredValidator(), RequiredValidator()]) + required this.email, + @RfControl(validators: [RequiredValidator()]) required this.password}); + @override + List get props => [email, password]; +} + +class ReactiveLoginOFormArrayBuilder + extends StatelessWidget { + const ReactiveLoginOFormArrayBuilder({ + Key? key, + this.control, + this.formControl, + this.builder, + required this.itemBuilder, + }) : assert(control != null || formControl != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final FormArray? formControl; + + final FormArray? Function( + LoginOForm formModel)? control; + + final Widget Function( + BuildContext context, List itemList, LoginOForm formModel)? + builder; + + final Widget Function(BuildContext context, int i, + ReactiveLoginOFormArrayBuilderT? item, LoginOForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveLoginOForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + return ReactiveFormArray( + formArray: formControl ?? control?.call(formModel), + builder: (context, formArray, child) { + final values = formArray.controls.map((e) => e.value).toList(); + final itemList = values + .asMap() + .map((i, item) { + return MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + ); + }) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} + +class ReactiveLoginOFormFormGroupArrayBuilder< + ReactiveLoginOFormFormGroupArrayBuilderT> extends StatelessWidget { + const ReactiveLoginOFormFormGroupArrayBuilder({ + Key? key, + this.extended, + this.getExtended, + this.builder, + required this.itemBuilder, + }) : assert(extended != null || getExtended != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final ExtendedControl?>, + List>? extended; + + final ExtendedControl?>, + List> + Function(LoginOForm formModel)? getExtended; + + final Widget Function( + BuildContext context, List itemList, LoginOForm formModel)? + builder; + + final Widget Function( + BuildContext context, + int i, + ReactiveLoginOFormFormGroupArrayBuilderT? item, + LoginOForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveLoginOForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + final value = (extended ?? getExtended?.call(formModel))!; + + return StreamBuilder?>?>( + stream: value.control.valueChanges, + builder: (context, snapshot) { + final itemList = + (value.value() ?? []) + .asMap() + .map((i, item) => MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + )) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} diff --git a/packages/reactive_forms_generator/example/lib/docs/login/login_output_form.dart b/packages/reactive_forms_generator/example/lib/docs/login/login_output_form.dart new file mode 100644 index 00000000..0256c5e7 --- /dev/null +++ b/packages/reactive_forms_generator/example/lib/docs/login/login_output_form.dart @@ -0,0 +1,177 @@ +import 'package:example/docs/login/labels.dart'; +import 'package:example/docs/login/login_output.dart'; +import 'package:example/docs/login/mocks.dart'; +import 'package:example/sample_screen.dart'; +import 'package:flutter/material.dart' hide ProgressIndicator; +import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; + +class LoginOutputFormWidget extends StatefulWidget { + final ValueChanged? onChange; + + const LoginOutputFormWidget({super.key, this.onChange}); + + @override + State createState() => _LoginFormWidgetState(); +} + +class _LoginFormWidgetState extends State { + LoginO _emptyModel = mockedLoginOEmpty; + + @override + Widget build(BuildContext context) { + return SampleScreen( + title: const Text('Login Output(beta)'), + body: LoginOFormBuilder( + model: _emptyModel, + builder: (context, formModel, child) { + return Column( + children: [ + ReactiveTextField( + key: email.itemKey, + formControl: formModel.emailControl, + validationMessages: { + ValidationMessage.required: (_) => errorRequired + }, + decoration: InputDecoration( + labelText: email.name, + helperText: '', + helperStyle: const TextStyle(height: 0.8), + errorStyle: const TextStyle(height: 0.8), + ), + ), + const SizedBox(height: 8.0), + ReactiveTextField( + key: password.itemKey, + formControl: formModel.passwordControl, + obscureText: true, + validationMessages: { + ValidationMessage.required: (_) => errorRequired, + 'mustMatch': (_) => errorMustMatch, + }, + textInputAction: TextInputAction.done, + decoration: InputDecoration( + labelText: password.name, + helperText: '', + helperStyle: const TextStyle(height: 0.8), + errorStyle: const TextStyle(height: 0.8), + ), + ), + // ReactiveLoginFormConsumer( + // builder: (context, formModel, child) { + // // debugPrint(formModel.passwordControl.errors); + // // debugPrint(formModel.form); + // debugPrint('dirty => ${formModel.form.dirty}'); + // debugPrint( + // 'passwordDirty => ${formModel.passwordControl.dirty}'); + // + // return Column( + // children: [ + // Text(formModel.emailControl.errors.toString()), + // Text(formModel.passwordControl.errors.toString()), + // ], + // ); + // }, + // ), + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + Expanded( + child: ElevatedButton( + key: submitRaw.itemKey, + onPressed: () { + debugPrint(formModel.model.email); + debugPrint(formModel.model.password); + formModel.form.markAllAsTouched(); + widget.onChange?.call(formModel.model); + }, + child: const Text('Submit raw'), + ), + ), + const SizedBox(width: 8), + Expanded( + child: ReactiveLoginOFormConsumer( + builder: (context, formModel, child) { + return ElevatedButton( + key: submit.itemKey, + onPressed: formModel.form.valid + ? () { + debugPrint(formModel.model.toString()); + debugPrint(formModel.model.email); + debugPrint(formModel.model.password); + widget.onChange?.call(formModel.model); + } + : null, + child: const Text('Submit'), + ); + }, + ), + ), + ], + ), + Row( + children: [ + Expanded( + child: ElevatedButton( + key: submitRaw.itemKey, + onPressed: () => formModel.submit( + onValid: (_) => debugPrint('FormValid'), + onNotValid: () { + debugPrint('FormInvalid'); + }, + ), + child: const Text('Submit method'), + ), + ), + const SizedBox(width: 8), + ], + ), + Row( + children: [ + Expanded( + child: ElevatedButton( + key: updateModel.itemKey, + onPressed: () { + setState(() => _emptyModel = mockedLoginO); + }, + child: const Text('Update model'), + ), + ), + const SizedBox(width: 8), + Expanded( + child: ElevatedButton( + key: reset.itemKey, + onPressed: () => formModel.reset(), + child: const Text('Reset'), + ), + ), + ], + ), + Row( + children: [ + Expanded( + child: ElevatedButton( + onPressed: () => + formModel.emailControl.markAsDisabled(), + child: const Text('Disable Email'), + ), + ), + const SizedBox(width: 8), + Expanded( + child: ElevatedButton( + onPressed: () => formModel.toggleDisabled(), + child: const Text('Toggle Disabled'), + ), + ), + ], + ), + ], + ), + ], + ); + }, + ), + ); + } +} diff --git a/packages/reactive_forms_generator/example/lib/docs/login/mocks.dart b/packages/reactive_forms_generator/example/lib/docs/login/mocks.dart index 66e08934..a8cee7a8 100644 --- a/packages/reactive_forms_generator/example/lib/docs/login/mocks.dart +++ b/packages/reactive_forms_generator/example/lib/docs/login/mocks.dart @@ -1,7 +1,10 @@ import 'package:example/docs/login/login.dart'; +import 'package:example/docs/login/login_output.dart'; const mockedLoginEmpty = Login(); +const mockedLoginOEmpty = LoginO(); const mockedLogin = Login(email: 'some@e.mail', password: 'xx'); +const mockedLoginO = LoginO(email: 'some@e.mail', password: 'xx'); const mockedLogin1 = Login(email: 'some@e.mail', password: 'some@e.mail'); diff --git a/packages/reactive_forms_generator/example/lib/docs/login_extended/login_extended.dart b/packages/reactive_forms_generator/example/lib/docs/login_extended/login_extended.dart index 46525168..f92d35c3 100644 --- a/packages/reactive_forms_generator/example/lib/docs/login_extended/login_extended.dart +++ b/packages/reactive_forms_generator/example/lib/docs/login_extended/login_extended.dart @@ -3,7 +3,7 @@ import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; part 'login_extended.gform.dart'; -@Rf() +@Rf(output: false) @RfGroup( validators: [AllFieldsRequired()], ) diff --git a/packages/reactive_forms_generator/example/lib/docs/login_extended/login_extended.gform.dart b/packages/reactive_forms_generator/example/lib/docs/login_extended/login_extended.gform.dart index 8c5b39f9..d25141c5 100644 --- a/packages/reactive_forms_generator/example/lib/docs/login_extended/login_extended.gform.dart +++ b/packages/reactive_forms_generator/example/lib/docs/login_extended/login_extended.gform.dart @@ -1,7 +1,7 @@ // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'login_extended.dart'; @@ -137,6 +137,8 @@ class LoginExtendedFormBuilder extends StatefulWidget { class _LoginExtendedFormBuilderState extends State { late LoginExtendedForm _formModel; + StreamSubscription? _logSubscription; + @override void initState() { _formModel = @@ -148,6 +150,34 @@ class _LoginExtendedFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logSubscription = _logLoginExtendedForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -163,6 +193,7 @@ class _LoginExtendedFormBuilderState extends State { @override void dispose() { _formModel.form.dispose(); + _logSubscription?.cancel(); super.dispose(); } @@ -185,7 +216,9 @@ class _LoginExtendedFormBuilderState extends State { } } -class LoginExtendedForm implements FormModel { +final _logLoginExtendedForm = Logger.detached('LoginExtendedForm'); + +class LoginExtendedForm implements FormModel { LoginExtendedForm( this.form, this.path, @@ -841,9 +874,11 @@ class LoginExtendedForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'LoginExtendedForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logLoginExtendedForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return LoginExtended( email: _emailValue, @@ -898,10 +933,20 @@ class LoginExtendedForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logLoginExtendedForm.info('Errors'); + _logLoginExtendedForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(LoginExtended? other) { + return mapEquals( + this.form.value, + LoginExtendedForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } diff --git a/packages/reactive_forms_generator/example/lib/docs/login_extended/login_extended_output.dart b/packages/reactive_forms_generator/example/lib/docs/login_extended/login_extended_output.dart new file mode 100644 index 00000000..9c8e3051 --- /dev/null +++ b/packages/reactive_forms_generator/example/lib/docs/login_extended/login_extended_output.dart @@ -0,0 +1,61 @@ +import 'package:example/helpers.dart'; +import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; + +part 'login_extended_output.gform.dart'; + +@Rf() +@RfGroup( + validators: [AllFieldsRequired()], +) +class LoginExtendedO { + final String? email; + + final String password; + + final bool rememberMe; + + final String theme; + + final UserMode mode; + + final int timeout; + + final double height; + + final String? unAnnotated; + final List someIntList; + + LoginExtendedO({ + @RfControl( + validators: [RequiredValidator()], + asyncValidators: [UniqueEmailAsyncValidator()], + ) + this.email, + @RfControl( + validators: [RequiredValidator()], + ) + required this.password, + @RfControl( + validators: [RequiredValidator()], + ) + required this.rememberMe, + @RfControl( + validators: [RequiredValidator()], + ) + required this.theme, + @RfControl( + validators: [RequiredValidator()], + ) + required this.mode, + @RfControl( + validators: [RequiredValidator()], + ) + required this.timeout, + @RfControl( + validators: [RequiredValidator()], + ) + required this.height, + this.unAnnotated, + this.someIntList = const [], + }); +} diff --git a/packages/reactive_forms_generator/example/lib/docs/login_extended/login_extended_output.gform.dart b/packages/reactive_forms_generator/example/lib/docs/login_extended/login_extended_output.gform.dart new file mode 100644 index 00000000..34816eda --- /dev/null +++ b/packages/reactive_forms_generator/example/lib/docs/login_extended/login_extended_output.gform.dart @@ -0,0 +1,1236 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'login_extended_output.dart'; + +// ************************************************************************** +// ReactiveFormsGenerator +// ************************************************************************** + +class ReactiveLoginExtendedOFormConsumer extends StatelessWidget { + const ReactiveLoginExtendedOFormConsumer({ + Key? key, + required this.builder, + this.child, + }) : super(key: key); + + final Widget? child; + + final Widget Function( + BuildContext context, LoginExtendedOForm formModel, Widget? child) + builder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveLoginExtendedOForm.of(context); + + if (formModel is! LoginExtendedOForm) { + throw FormControlParentNotFoundException(this); + } + return builder(context, formModel, child); + } +} + +class LoginExtendedOFormInheritedStreamer extends InheritedStreamer { + const LoginExtendedOFormInheritedStreamer({ + Key? key, + required this.form, + required Stream stream, + required Widget child, + }) : super( + stream, + child, + key: key, + ); + + final LoginExtendedOForm form; +} + +class ReactiveLoginExtendedOForm extends StatelessWidget { + const ReactiveLoginExtendedOForm({ + Key? key, + required this.form, + required this.child, + this.canPop, + this.onPopInvoked, + }) : super(key: key); + + final Widget child; + + final LoginExtendedOForm form; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + static LoginExtendedOForm? of( + BuildContext context, { + bool listen = true, + }) { + if (listen) { + return context + .dependOnInheritedWidgetOfExactType< + LoginExtendedOFormInheritedStreamer>() + ?.form; + } + + final element = context.getElementForInheritedWidgetOfExactType< + LoginExtendedOFormInheritedStreamer>(); + return element == null + ? null + : (element.widget as LoginExtendedOFormInheritedStreamer).form; + } + + @override + Widget build(BuildContext context) { + return LoginExtendedOFormInheritedStreamer( + form: form, + stream: form.form.statusChanged, + child: ReactiveFormPopScope( + canPop: canPop, + onPopInvoked: onPopInvoked, + child: child, + ), + ); + } +} + +extension ReactiveReactiveLoginExtendedOFormExt on BuildContext { + LoginExtendedOForm? loginExtendedOFormWatch() => + ReactiveLoginExtendedOForm.of(this); + + LoginExtendedOForm? loginExtendedOFormRead() => + ReactiveLoginExtendedOForm.of(this, listen: false); +} + +class LoginExtendedOFormBuilder extends StatefulWidget { + const LoginExtendedOFormBuilder({ + Key? key, + this.model, + this.child, + this.canPop, + this.onPopInvoked, + required this.builder, + this.initState, + }) : super(key: key); + + final LoginExtendedO? model; + + final Widget? child; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + final Widget Function( + BuildContext context, LoginExtendedOForm formModel, Widget? child) + builder; + + final void Function(BuildContext context, LoginExtendedOForm formModel)? + initState; + + @override + _LoginExtendedOFormBuilderState createState() => + _LoginExtendedOFormBuilderState(); +} + +class _LoginExtendedOFormBuilderState extends State { + late LoginExtendedOForm _formModel; + + StreamSubscription? _logSubscription; + + @override + void initState() { + _formModel = + LoginExtendedOForm(LoginExtendedOForm.formElements(widget.model), null); + + if (_formModel.form.disabled) { + _formModel.form.markAsDisabled(); + } + + widget.initState?.call(context, _formModel); + + _logSubscription = _logLoginExtendedOForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + + super.initState(); + } + + @override + void didUpdateWidget(covariant LoginExtendedOFormBuilder oldWidget) { + if (widget.model != oldWidget.model) { + _formModel.updateValue(widget.model); + } + + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + _formModel.form.dispose(); + _logSubscription?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ReactiveLoginExtendedOForm( + key: ObjectKey(_formModel), + form: _formModel, + // canPop: widget.canPop, + // onPopInvoked: widget.onPopInvoked, + child: ReactiveFormBuilder( + form: () => _formModel.form, + canPop: widget.canPop, + onPopInvoked: widget.onPopInvoked, + builder: (context, formGroup, child) => + widget.builder(context, _formModel, widget.child), + child: widget.child, + ), + ); + } +} + +final _logLoginExtendedOForm = Logger.detached('LoginExtendedOForm'); + +class LoginExtendedOForm + implements FormModel { + LoginExtendedOForm( + this.form, + this.path, + ); + + static const String emailControlName = "email"; + + static const String passwordControlName = "password"; + + static const String rememberMeControlName = "rememberMe"; + + static const String themeControlName = "theme"; + + static const String modeControlName = "mode"; + + static const String timeoutControlName = "timeout"; + + static const String heightControlName = "height"; + + static const String unAnnotatedControlName = "unAnnotated"; + + static const String someIntListControlName = "someIntList"; + + final FormGroup form; + + final String? path; + + final Map _disabled = {}; + + String emailControlPath() => pathBuilder(emailControlName); + + String passwordControlPath() => pathBuilder(passwordControlName); + + String rememberMeControlPath() => pathBuilder(rememberMeControlName); + + String themeControlPath() => pathBuilder(themeControlName); + + String modeControlPath() => pathBuilder(modeControlName); + + String timeoutControlPath() => pathBuilder(timeoutControlName); + + String heightControlPath() => pathBuilder(heightControlName); + + String unAnnotatedControlPath() => pathBuilder(unAnnotatedControlName); + + String someIntListControlPath() => pathBuilder(someIntListControlName); + + String get _emailValue => emailControl.value as String; + + String get _passwordValue => passwordControl.value as String; + + bool get _rememberMeValue => rememberMeControl.value as bool; + + String get _themeValue => themeControl.value as String; + + UserMode get _modeValue => modeControl.value as UserMode; + + int get _timeoutValue => timeoutControl.value as int; + + double get _heightValue => heightControl.value as double; + + String? get _unAnnotatedValue => unAnnotatedControl?.value; + + List get _someIntListValue => someIntListControl.value ?? []; + + bool get containsEmail { + try { + form.control(emailControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsPassword { + try { + form.control(passwordControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsRememberMe { + try { + form.control(rememberMeControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsTheme { + try { + form.control(themeControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsMode { + try { + form.control(modeControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsTimeout { + try { + form.control(timeoutControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsHeight { + try { + form.control(heightControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsUnAnnotated { + try { + form.control(unAnnotatedControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsSomeIntList { + try { + form.control(someIntListControlPath()); + return true; + } catch (e) { + return false; + } + } + + Map? get emailErrors => emailControl.errors; + + Map get passwordErrors => passwordControl.errors; + + Map get rememberMeErrors => rememberMeControl.errors; + + Map get themeErrors => themeControl.errors; + + Map get modeErrors => modeControl.errors; + + Map get timeoutErrors => timeoutControl.errors; + + Map get heightErrors => heightControl.errors; + + Map? get unAnnotatedErrors => unAnnotatedControl?.errors; + + Map get someIntListErrors => someIntListControl.errors; + + void get emailFocus => form.focus(emailControlPath()); + + void get passwordFocus => form.focus(passwordControlPath()); + + void get rememberMeFocus => form.focus(rememberMeControlPath()); + + void get themeFocus => form.focus(themeControlPath()); + + void get modeFocus => form.focus(modeControlPath()); + + void get timeoutFocus => form.focus(timeoutControlPath()); + + void get heightFocus => form.focus(heightControlPath()); + + void get unAnnotatedFocus => form.focus(unAnnotatedControlPath()); + + void get someIntListFocus => form.focus(someIntListControlPath()); + + void emailRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsEmail) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + emailControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + emailControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void unAnnotatedRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsUnAnnotated) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + unAnnotatedControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + unAnnotatedControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void emailValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + emailControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void passwordValueUpdate( + String value, { + bool updateParent = true, + bool emitEvent = true, + }) { + passwordControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void rememberMeValueUpdate( + bool value, { + bool updateParent = true, + bool emitEvent = true, + }) { + rememberMeControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void themeValueUpdate( + String value, { + bool updateParent = true, + bool emitEvent = true, + }) { + themeControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void modeValueUpdate( + UserMode value, { + bool updateParent = true, + bool emitEvent = true, + }) { + modeControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void timeoutValueUpdate( + int value, { + bool updateParent = true, + bool emitEvent = true, + }) { + timeoutControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void heightValueUpdate( + double value, { + bool updateParent = true, + bool emitEvent = true, + }) { + heightControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void unAnnotatedValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + unAnnotatedControl?.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void someIntListValueUpdate( + List value, { + bool updateParent = true, + bool emitEvent = true, + }) { + someIntListControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void emailValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + emailControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void passwordValuePatch( + String value, { + bool updateParent = true, + bool emitEvent = true, + }) { + passwordControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void rememberMeValuePatch( + bool value, { + bool updateParent = true, + bool emitEvent = true, + }) { + rememberMeControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void themeValuePatch( + String value, { + bool updateParent = true, + bool emitEvent = true, + }) { + themeControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void modeValuePatch( + UserMode value, { + bool updateParent = true, + bool emitEvent = true, + }) { + modeControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void timeoutValuePatch( + int value, { + bool updateParent = true, + bool emitEvent = true, + }) { + timeoutControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void heightValuePatch( + double value, { + bool updateParent = true, + bool emitEvent = true, + }) { + heightControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void unAnnotatedValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + unAnnotatedControl?.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void someIntListValuePatch( + List value, { + bool updateParent = true, + bool emitEvent = true, + }) { + someIntListControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void emailValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + emailControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void passwordValueReset( + String value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + passwordControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void rememberMeValueReset( + bool value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + rememberMeControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void themeValueReset( + String value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + themeControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void modeValueReset( + UserMode value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + modeControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void timeoutValueReset( + int value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + timeoutControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void heightValueReset( + double value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + heightControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void unAnnotatedValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + unAnnotatedControl?.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void someIntListValueReset( + List value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + someIntListControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + FormControl get emailControl => + form.control(emailControlPath()) as FormControl; + + FormControl get passwordControl => + form.control(passwordControlPath()) as FormControl; + + FormControl get rememberMeControl => + form.control(rememberMeControlPath()) as FormControl; + + FormControl get themeControl => + form.control(themeControlPath()) as FormControl; + + FormControl get modeControl => + form.control(modeControlPath()) as FormControl; + + FormControl get timeoutControl => + form.control(timeoutControlPath()) as FormControl; + + FormControl get heightControl => + form.control(heightControlPath()) as FormControl; + + FormControl? get unAnnotatedControl => containsUnAnnotated + ? form.control(unAnnotatedControlPath()) as FormControl? + : null; + + FormControl> get someIntListControl => + form.control(someIntListControlPath()) as FormControl>; + + void emailSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + emailControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + emailControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void passwordSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + passwordControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + passwordControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void rememberMeSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + rememberMeControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + rememberMeControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void themeSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + themeControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + themeControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void modeSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + modeControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + modeControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void timeoutSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + timeoutControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + timeoutControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void heightSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + heightControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + heightControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void unAnnotatedSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + unAnnotatedControl?.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + unAnnotatedControl?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void someIntListSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + someIntListControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + someIntListControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + @override + LoginExtendedOOutput get model { + final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; + + if (!isValid) { + _logLoginExtendedOForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); + } + return LoginExtendedOOutput( + email: _emailValue, + password: _passwordValue, + rememberMe: _rememberMeValue, + theme: _themeValue, + mode: _modeValue, + timeout: _timeoutValue, + height: _heightValue, + unAnnotated: _unAnnotatedValue, + someIntList: _someIntListValue); + } + + @override + void toggleDisabled({ + bool updateParent = true, + bool emitEvent = true, + }) { + final currentFormInstance = currentForm; + + if (currentFormInstance is! FormGroup) { + return; + } + + if (_disabled.isEmpty) { + currentFormInstance.controls.forEach((key, control) { + _disabled[key] = control.disabled; + }); + + currentForm.markAsDisabled( + updateParent: updateParent, emitEvent: emitEvent); + } else { + currentFormInstance.controls.forEach((key, control) { + if (_disabled[key] == false) { + currentFormInstance.controls[key]?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + + _disabled.remove(key); + }); + } + } + + @override + void submit({ + required void Function(LoginExtendedOOutput model) onValid, + void Function()? onNotValid, + }) { + currentForm.markAllAsTouched(); + if (currentForm.valid) { + onValid(model); + } else { + _logLoginExtendedOForm.info('Errors'); + _logLoginExtendedOForm.info('┗━━ ${form.errors}'); + onNotValid?.call(); + } + } + + @override + bool equalsTo(LoginExtendedO? other) { + return mapEquals( + this.form.value, + LoginExtendedOForm.formElements(other).value, + ); + } + + AbstractControl get currentForm { + return path == null ? form : form.control(path!); + } + + @override + void updateValue( + LoginExtendedO? value, { + bool updateParent = true, + bool emitEvent = true, + }) => + form.updateValue(LoginExtendedOForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + + @override + void reset({ + LoginExtendedO? value, + bool updateParent = true, + bool emitEvent = true, + }) => + form.reset( + value: value != null ? formElements(value).rawValue : null, + updateParent: updateParent, + emitEvent: emitEvent); + + String pathBuilder(String? pathItem) => + [path, pathItem].whereType().join("."); + + static FormGroup formElements(LoginExtendedO? loginExtendedO) => FormGroup({ + emailControlName: FormControl( + value: loginExtendedO?.email, + validators: [RequiredValidator()], + asyncValidators: [UniqueEmailAsyncValidator()], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + passwordControlName: FormControl( + value: loginExtendedO?.password, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + rememberMeControlName: FormControl( + value: loginExtendedO?.rememberMe, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + themeControlName: FormControl( + value: loginExtendedO?.theme, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + modeControlName: FormControl( + value: loginExtendedO?.mode, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + timeoutControlName: FormControl( + value: loginExtendedO?.timeout, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + heightControlName: FormControl( + value: loginExtendedO?.height, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + unAnnotatedControlName: FormControl( + value: loginExtendedO?.unAnnotated, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + someIntListControlName: FormControl>( + value: loginExtendedO?.someIntList, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false) + }, + validators: [ + AllFieldsRequired() + ], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false); +} + +@Rf() +@RfGroup(validators: [AllFieldsRequired()]) +class LoginExtendedOOutput { + final String email; + final String password; + final bool rememberMe; + final String theme; + final UserMode mode; + final int timeout; + final double height; + final String? unAnnotated; + final List someIntList; + LoginExtendedOOutput( + {@RfControl( + validators: [RequiredValidator()], + asyncValidators: [UniqueEmailAsyncValidator()]) + required this.email, + @RfControl(validators: [RequiredValidator()]) required this.password, + @RfControl(validators: [RequiredValidator()]) required this.rememberMe, + @RfControl(validators: [RequiredValidator()]) required this.theme, + @RfControl(validators: [RequiredValidator()]) required this.mode, + @RfControl(validators: [RequiredValidator()]) required this.timeout, + @RfControl(validators: [RequiredValidator()]) required this.height, + this.unAnnotated, + this.someIntList = const []}); +} + +class ReactiveLoginExtendedOFormArrayBuilder< + ReactiveLoginExtendedOFormArrayBuilderT> extends StatelessWidget { + const ReactiveLoginExtendedOFormArrayBuilder({ + Key? key, + this.control, + this.formControl, + this.builder, + required this.itemBuilder, + }) : assert(control != null || formControl != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final FormArray? formControl; + + final FormArray? Function( + LoginExtendedOForm formModel)? control; + + final Widget Function(BuildContext context, List itemList, + LoginExtendedOForm formModel)? builder; + + final Widget Function( + BuildContext context, + int i, + ReactiveLoginExtendedOFormArrayBuilderT? item, + LoginExtendedOForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveLoginExtendedOForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + return ReactiveFormArray( + formArray: formControl ?? control?.call(formModel), + builder: (context, formArray, child) { + final values = formArray.controls.map((e) => e.value).toList(); + final itemList = values + .asMap() + .map((i, item) { + return MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + ); + }) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} + +class ReactiveLoginExtendedOFormFormGroupArrayBuilder< + ReactiveLoginExtendedOFormFormGroupArrayBuilderT> extends StatelessWidget { + const ReactiveLoginExtendedOFormFormGroupArrayBuilder({ + Key? key, + this.extended, + this.getExtended, + this.builder, + required this.itemBuilder, + }) : assert(extended != null || getExtended != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final ExtendedControl?>, + List>? extended; + + final ExtendedControl?>, + List> + Function(LoginExtendedOForm formModel)? getExtended; + + final Widget Function(BuildContext context, List itemList, + LoginExtendedOForm formModel)? builder; + + final Widget Function( + BuildContext context, + int i, + ReactiveLoginExtendedOFormFormGroupArrayBuilderT? item, + LoginExtendedOForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveLoginExtendedOForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + final value = (extended ?? getExtended?.call(formModel))!; + + return StreamBuilder?>?>( + stream: value.control.valueChanges, + builder: (context, snapshot) { + final itemList = (value.value() ?? + []) + .asMap() + .map((i, item) => MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + )) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} diff --git a/packages/reactive_forms_generator/example/lib/docs/login_extended_nullable/login_extended_nullable.dart b/packages/reactive_forms_generator/example/lib/docs/login_extended_nullable/login_extended_nullable.dart index a004af61..57011806 100644 --- a/packages/reactive_forms_generator/example/lib/docs/login_extended_nullable/login_extended_nullable.dart +++ b/packages/reactive_forms_generator/example/lib/docs/login_extended_nullable/login_extended_nullable.dart @@ -3,7 +3,7 @@ import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; part 'login_extended_nullable.gform.dart'; -@Rf() +@Rf(output: false) class LoginExtendedNullable { final String? email; diff --git a/packages/reactive_forms_generator/example/lib/docs/login_extended_nullable/login_extended_nullable.gform.dart b/packages/reactive_forms_generator/example/lib/docs/login_extended_nullable/login_extended_nullable.gform.dart index 36325589..11bb4bb6 100644 --- a/packages/reactive_forms_generator/example/lib/docs/login_extended_nullable/login_extended_nullable.gform.dart +++ b/packages/reactive_forms_generator/example/lib/docs/login_extended_nullable/login_extended_nullable.gform.dart @@ -1,7 +1,7 @@ // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'login_extended_nullable.dart'; @@ -139,6 +139,8 @@ class _LoginExtendedNullableFormBuilderState extends State { late LoginExtendedNullableForm _formModel; + StreamSubscription? _logSubscription; + @override void initState() { _formModel = LoginExtendedNullableForm( @@ -150,6 +152,35 @@ class _LoginExtendedNullableFormBuilderState widget.initState?.call(context, _formModel); + _logSubscription = + _logLoginExtendedNullableForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -165,6 +196,7 @@ class _LoginExtendedNullableFormBuilderState @override void dispose() { _formModel.form.dispose(); + _logSubscription?.cancel(); super.dispose(); } @@ -187,7 +219,11 @@ class _LoginExtendedNullableFormBuilderState } } -class LoginExtendedNullableForm implements FormModel { +final _logLoginExtendedNullableForm = + Logger.detached('LoginExtendedNullableForm'); + +class LoginExtendedNullableForm + implements FormModel { LoginExtendedNullableForm( this.form, this.path, @@ -869,9 +905,11 @@ class LoginExtendedNullableForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'LoginExtendedNullableForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logLoginExtendedNullableForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return LoginExtendedNullable( email: _emailValue, @@ -924,10 +962,20 @@ class LoginExtendedNullableForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logLoginExtendedNullableForm.info('Errors'); + _logLoginExtendedNullableForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(LoginExtendedNullable? other) { + return mapEquals( + this.form.value, + LoginExtendedNullableForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } diff --git a/packages/reactive_forms_generator/example/lib/docs/login_extended_nullable/login_extended_nullable_output.dart b/packages/reactive_forms_generator/example/lib/docs/login_extended_nullable/login_extended_nullable_output.dart new file mode 100644 index 00000000..a1669b39 --- /dev/null +++ b/packages/reactive_forms_generator/example/lib/docs/login_extended_nullable/login_extended_nullable_output.dart @@ -0,0 +1,31 @@ +import 'package:example/helpers.dart'; +import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; + +part 'login_extended_nullable_output.gform.dart'; + +@Rf(output: false) +class LoginExtendedNullableO { + final String? email; + + final String? password; + + final bool? rememberMe; + + final String? theme; + + final UserMode? mode; + + final int? timeout; + + final double? height; + + LoginExtendedNullableO({ + @RfControl(validators: []) this.email, + @RfControl() this.password, + @RfControl() this.rememberMe, + @RfControl() this.theme, + @RfControl() this.mode, + @RfControl() this.timeout, + @RfControl() this.height, + }); +} diff --git a/packages/reactive_forms_generator/example/lib/docs/login_extended_nullable/login_extended_nullable_output.gform.dart b/packages/reactive_forms_generator/example/lib/docs/login_extended_nullable/login_extended_nullable_output.gform.dart new file mode 100644 index 00000000..27e8482c --- /dev/null +++ b/packages/reactive_forms_generator/example/lib/docs/login_extended_nullable/login_extended_nullable_output.gform.dart @@ -0,0 +1,1196 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'login_extended_nullable_output.dart'; + +// ************************************************************************** +// ReactiveFormsGenerator +// ************************************************************************** + +class ReactiveLoginExtendedNullableOFormConsumer extends StatelessWidget { + const ReactiveLoginExtendedNullableOFormConsumer({ + Key? key, + required this.builder, + this.child, + }) : super(key: key); + + final Widget? child; + + final Widget Function(BuildContext context, + LoginExtendedNullableOForm formModel, Widget? child) builder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveLoginExtendedNullableOForm.of(context); + + if (formModel is! LoginExtendedNullableOForm) { + throw FormControlParentNotFoundException(this); + } + return builder(context, formModel, child); + } +} + +class LoginExtendedNullableOFormInheritedStreamer + extends InheritedStreamer { + const LoginExtendedNullableOFormInheritedStreamer({ + Key? key, + required this.form, + required Stream stream, + required Widget child, + }) : super( + stream, + child, + key: key, + ); + + final LoginExtendedNullableOForm form; +} + +class ReactiveLoginExtendedNullableOForm extends StatelessWidget { + const ReactiveLoginExtendedNullableOForm({ + Key? key, + required this.form, + required this.child, + this.canPop, + this.onPopInvoked, + }) : super(key: key); + + final Widget child; + + final LoginExtendedNullableOForm form; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + static LoginExtendedNullableOForm? of( + BuildContext context, { + bool listen = true, + }) { + if (listen) { + return context + .dependOnInheritedWidgetOfExactType< + LoginExtendedNullableOFormInheritedStreamer>() + ?.form; + } + + final element = context.getElementForInheritedWidgetOfExactType< + LoginExtendedNullableOFormInheritedStreamer>(); + return element == null + ? null + : (element.widget as LoginExtendedNullableOFormInheritedStreamer).form; + } + + @override + Widget build(BuildContext context) { + return LoginExtendedNullableOFormInheritedStreamer( + form: form, + stream: form.form.statusChanged, + child: ReactiveFormPopScope( + canPop: canPop, + onPopInvoked: onPopInvoked, + child: child, + ), + ); + } +} + +extension ReactiveReactiveLoginExtendedNullableOFormExt on BuildContext { + LoginExtendedNullableOForm? loginExtendedNullableOFormWatch() => + ReactiveLoginExtendedNullableOForm.of(this); + + LoginExtendedNullableOForm? loginExtendedNullableOFormRead() => + ReactiveLoginExtendedNullableOForm.of(this, listen: false); +} + +class LoginExtendedNullableOFormBuilder extends StatefulWidget { + const LoginExtendedNullableOFormBuilder({ + Key? key, + this.model, + this.child, + this.canPop, + this.onPopInvoked, + required this.builder, + this.initState, + }) : super(key: key); + + final LoginExtendedNullableO? model; + + final Widget? child; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + final Widget Function(BuildContext context, + LoginExtendedNullableOForm formModel, Widget? child) builder; + + final void Function( + BuildContext context, LoginExtendedNullableOForm formModel)? initState; + + @override + _LoginExtendedNullableOFormBuilderState createState() => + _LoginExtendedNullableOFormBuilderState(); +} + +class _LoginExtendedNullableOFormBuilderState + extends State { + late LoginExtendedNullableOForm _formModel; + + StreamSubscription? _logSubscription; + + @override + void initState() { + _formModel = LoginExtendedNullableOForm( + LoginExtendedNullableOForm.formElements(widget.model), null); + + if (_formModel.form.disabled) { + _formModel.form.markAsDisabled(); + } + + widget.initState?.call(context, _formModel); + + _logSubscription = + _logLoginExtendedNullableOForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + + super.initState(); + } + + @override + void didUpdateWidget(covariant LoginExtendedNullableOFormBuilder oldWidget) { + if (widget.model != oldWidget.model) { + _formModel.updateValue(widget.model); + } + + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + _formModel.form.dispose(); + _logSubscription?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ReactiveLoginExtendedNullableOForm( + key: ObjectKey(_formModel), + form: _formModel, + // canPop: widget.canPop, + // onPopInvoked: widget.onPopInvoked, + child: ReactiveFormBuilder( + form: () => _formModel.form, + canPop: widget.canPop, + onPopInvoked: widget.onPopInvoked, + builder: (context, formGroup, child) => + widget.builder(context, _formModel, widget.child), + child: widget.child, + ), + ); + } +} + +final _logLoginExtendedNullableOForm = + Logger.detached('LoginExtendedNullableOForm'); + +class LoginExtendedNullableOForm + implements FormModel { + LoginExtendedNullableOForm( + this.form, + this.path, + ); + + static const String emailControlName = "email"; + + static const String passwordControlName = "password"; + + static const String rememberMeControlName = "rememberMe"; + + static const String themeControlName = "theme"; + + static const String modeControlName = "mode"; + + static const String timeoutControlName = "timeout"; + + static const String heightControlName = "height"; + + final FormGroup form; + + final String? path; + + final Map _disabled = {}; + + String emailControlPath() => pathBuilder(emailControlName); + + String passwordControlPath() => pathBuilder(passwordControlName); + + String rememberMeControlPath() => pathBuilder(rememberMeControlName); + + String themeControlPath() => pathBuilder(themeControlName); + + String modeControlPath() => pathBuilder(modeControlName); + + String timeoutControlPath() => pathBuilder(timeoutControlName); + + String heightControlPath() => pathBuilder(heightControlName); + + String? get _emailValue => emailControl?.value; + + String? get _passwordValue => passwordControl?.value; + + bool? get _rememberMeValue => rememberMeControl?.value; + + String? get _themeValue => themeControl?.value; + + UserMode? get _modeValue => modeControl?.value; + + int? get _timeoutValue => timeoutControl?.value; + + double? get _heightValue => heightControl?.value; + + bool get containsEmail { + try { + form.control(emailControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsPassword { + try { + form.control(passwordControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsRememberMe { + try { + form.control(rememberMeControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsTheme { + try { + form.control(themeControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsMode { + try { + form.control(modeControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsTimeout { + try { + form.control(timeoutControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsHeight { + try { + form.control(heightControlPath()); + return true; + } catch (e) { + return false; + } + } + + Map? get emailErrors => emailControl?.errors; + + Map? get passwordErrors => passwordControl?.errors; + + Map? get rememberMeErrors => rememberMeControl?.errors; + + Map? get themeErrors => themeControl?.errors; + + Map? get modeErrors => modeControl?.errors; + + Map? get timeoutErrors => timeoutControl?.errors; + + Map? get heightErrors => heightControl?.errors; + + void get emailFocus => form.focus(emailControlPath()); + + void get passwordFocus => form.focus(passwordControlPath()); + + void get rememberMeFocus => form.focus(rememberMeControlPath()); + + void get themeFocus => form.focus(themeControlPath()); + + void get modeFocus => form.focus(modeControlPath()); + + void get timeoutFocus => form.focus(timeoutControlPath()); + + void get heightFocus => form.focus(heightControlPath()); + + void emailRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsEmail) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + emailControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + emailControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void passwordRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsPassword) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + passwordControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + passwordControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void rememberMeRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsRememberMe) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + rememberMeControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + rememberMeControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void themeRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsTheme) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + themeControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + themeControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void modeRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsMode) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + modeControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + modeControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void timeoutRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsTimeout) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + timeoutControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + timeoutControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void heightRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsHeight) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + heightControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + heightControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void emailValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + emailControl?.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void passwordValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + passwordControl?.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void rememberMeValueUpdate( + bool? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + rememberMeControl?.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void themeValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + themeControl?.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void modeValueUpdate( + UserMode? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + modeControl?.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void timeoutValueUpdate( + int? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + timeoutControl?.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void heightValueUpdate( + double? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + heightControl?.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void emailValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + emailControl?.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void passwordValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + passwordControl?.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void rememberMeValuePatch( + bool? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + rememberMeControl?.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void themeValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + themeControl?.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void modeValuePatch( + UserMode? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + modeControl?.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void timeoutValuePatch( + int? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + timeoutControl?.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void heightValuePatch( + double? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + heightControl?.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void emailValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + emailControl?.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void passwordValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + passwordControl?.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void rememberMeValueReset( + bool? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + rememberMeControl?.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void themeValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + themeControl?.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void modeValueReset( + UserMode? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + modeControl?.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void timeoutValueReset( + int? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + timeoutControl?.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void heightValueReset( + double? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + heightControl?.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + FormControl? get emailControl => containsEmail + ? form.control(emailControlPath()) as FormControl? + : null; + + FormControl? get passwordControl => containsPassword + ? form.control(passwordControlPath()) as FormControl? + : null; + + FormControl? get rememberMeControl => containsRememberMe + ? form.control(rememberMeControlPath()) as FormControl? + : null; + + FormControl? get themeControl => containsTheme + ? form.control(themeControlPath()) as FormControl? + : null; + + FormControl? get modeControl => containsMode + ? form.control(modeControlPath()) as FormControl? + : null; + + FormControl? get timeoutControl => containsTimeout + ? form.control(timeoutControlPath()) as FormControl? + : null; + + FormControl? get heightControl => containsHeight + ? form.control(heightControlPath()) as FormControl? + : null; + + void emailSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + emailControl?.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + emailControl?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void passwordSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + passwordControl?.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + passwordControl?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void rememberMeSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + rememberMeControl?.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + rememberMeControl?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void themeSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + themeControl?.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + themeControl?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void modeSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + modeControl?.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + modeControl?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void timeoutSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + timeoutControl?.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + timeoutControl?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void heightSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + heightControl?.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + heightControl?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + @override + LoginExtendedNullableO get model { + final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; + + if (!isValid) { + _logLoginExtendedNullableOForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); + } + return LoginExtendedNullableO( + email: _emailValue, + password: _passwordValue, + rememberMe: _rememberMeValue, + theme: _themeValue, + mode: _modeValue, + timeout: _timeoutValue, + height: _heightValue); + } + + @override + void toggleDisabled({ + bool updateParent = true, + bool emitEvent = true, + }) { + final currentFormInstance = currentForm; + + if (currentFormInstance is! FormGroup) { + return; + } + + if (_disabled.isEmpty) { + currentFormInstance.controls.forEach((key, control) { + _disabled[key] = control.disabled; + }); + + currentForm.markAsDisabled( + updateParent: updateParent, emitEvent: emitEvent); + } else { + currentFormInstance.controls.forEach((key, control) { + if (_disabled[key] == false) { + currentFormInstance.controls[key]?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + + _disabled.remove(key); + }); + } + } + + @override + void submit({ + required void Function(LoginExtendedNullableO model) onValid, + void Function()? onNotValid, + }) { + currentForm.markAllAsTouched(); + if (currentForm.valid) { + onValid(model); + } else { + _logLoginExtendedNullableOForm.info('Errors'); + _logLoginExtendedNullableOForm.info('┗━━ ${form.errors}'); + onNotValid?.call(); + } + } + + @override + bool equalsTo(LoginExtendedNullableO? other) { + return mapEquals( + this.form.value, + LoginExtendedNullableOForm.formElements(other).value, + ); + } + + AbstractControl get currentForm { + return path == null ? form : form.control(path!); + } + + @override + void updateValue( + LoginExtendedNullableO? value, { + bool updateParent = true, + bool emitEvent = true, + }) => + form.updateValue(LoginExtendedNullableOForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + + @override + void reset({ + LoginExtendedNullableO? value, + bool updateParent = true, + bool emitEvent = true, + }) => + form.reset( + value: value != null ? formElements(value).rawValue : null, + updateParent: updateParent, + emitEvent: emitEvent); + + String pathBuilder(String? pathItem) => + [path, pathItem].whereType().join("."); + + static FormGroup formElements( + LoginExtendedNullableO? loginExtendedNullableO) => + FormGroup({ + emailControlName: FormControl( + value: loginExtendedNullableO?.email, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + passwordControlName: FormControl( + value: loginExtendedNullableO?.password, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + rememberMeControlName: FormControl( + value: loginExtendedNullableO?.rememberMe, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + themeControlName: FormControl( + value: loginExtendedNullableO?.theme, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + modeControlName: FormControl( + value: loginExtendedNullableO?.mode, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + timeoutControlName: FormControl( + value: loginExtendedNullableO?.timeout, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + heightControlName: FormControl( + value: loginExtendedNullableO?.height, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false) + }, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false); +} + +class ReactiveLoginExtendedNullableOFormArrayBuilder< + ReactiveLoginExtendedNullableOFormArrayBuilderT> extends StatelessWidget { + const ReactiveLoginExtendedNullableOFormArrayBuilder({ + Key? key, + this.control, + this.formControl, + this.builder, + required this.itemBuilder, + }) : assert(control != null || formControl != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final FormArray? formControl; + + final FormArray? Function( + LoginExtendedNullableOForm formModel)? control; + + final Widget Function(BuildContext context, List itemList, + LoginExtendedNullableOForm formModel)? builder; + + final Widget Function( + BuildContext context, + int i, + ReactiveLoginExtendedNullableOFormArrayBuilderT? item, + LoginExtendedNullableOForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveLoginExtendedNullableOForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + return ReactiveFormArray( + formArray: formControl ?? control?.call(formModel), + builder: (context, formArray, child) { + final values = formArray.controls.map((e) => e.value).toList(); + final itemList = values + .asMap() + .map((i, item) { + return MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + ); + }) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} + +class ReactiveLoginExtendedNullableOFormFormGroupArrayBuilder< + ReactiveLoginExtendedNullableOFormFormGroupArrayBuilderT> + extends StatelessWidget { + const ReactiveLoginExtendedNullableOFormFormGroupArrayBuilder({ + Key? key, + this.extended, + this.getExtended, + this.builder, + required this.itemBuilder, + }) : assert(extended != null || getExtended != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final ExtendedControl?>, + List>? extended; + + final ExtendedControl?>, + List> + Function(LoginExtendedNullableOForm formModel)? getExtended; + + final Widget Function(BuildContext context, List itemList, + LoginExtendedNullableOForm formModel)? builder; + + final Widget Function( + BuildContext context, + int i, + ReactiveLoginExtendedNullableOFormFormGroupArrayBuilderT? item, + LoginExtendedNullableOForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveLoginExtendedNullableOForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + final value = (extended ?? getExtended?.call(formModel))!; + + return StreamBuilder?>?>( + stream: value.control.valueChanges, + builder: (context, snapshot) { + final itemList = (value.value() ?? + []) + .asMap() + .map((i, item) => MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + )) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} diff --git a/packages/reactive_forms_generator/example/lib/docs/mailing_list/mailing_list.dart b/packages/reactive_forms_generator/example/lib/docs/mailing_list/mailing_list.dart index 36024a39..574ab7e2 100644 --- a/packages/reactive_forms_generator/example/lib/docs/mailing_list/mailing_list.dart +++ b/packages/reactive_forms_generator/example/lib/docs/mailing_list/mailing_list.dart @@ -3,7 +3,7 @@ import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; part 'mailing_list.gform.dart'; -@Rf() +@Rf(output: false) class MailingList { final List emailList; diff --git a/packages/reactive_forms_generator/example/lib/docs/mailing_list/mailing_list.gform.dart b/packages/reactive_forms_generator/example/lib/docs/mailing_list/mailing_list.gform.dart index 54cac27c..7875147b 100644 --- a/packages/reactive_forms_generator/example/lib/docs/mailing_list/mailing_list.gform.dart +++ b/packages/reactive_forms_generator/example/lib/docs/mailing_list/mailing_list.gform.dart @@ -1,7 +1,7 @@ // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'mailing_list.dart'; @@ -135,6 +135,8 @@ class MailingListFormBuilder extends StatefulWidget { class _MailingListFormBuilderState extends State { late MailingListForm _formModel; + StreamSubscription? _logSubscription; + @override void initState() { _formModel = @@ -146,6 +148,34 @@ class _MailingListFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logSubscription = _logMailingListForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -161,6 +191,7 @@ class _MailingListFormBuilderState extends State { @override void dispose() { _formModel.form.dispose(); + _logSubscription?.cancel(); super.dispose(); } @@ -183,7 +214,9 @@ class _MailingListFormBuilderState extends State { } } -class MailingListForm implements FormModel { +final _logMailingListForm = Logger.detached('MailingListForm'); + +class MailingListForm implements FormModel { MailingListForm( this.form, this.path, @@ -309,9 +342,11 @@ class MailingListForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'MailingListForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logMailingListForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return MailingList(emailList: _emailListValue); } @@ -357,10 +392,20 @@ class MailingListForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logMailingListForm.info('Errors'); + _logMailingListForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(MailingList? other) { + return mapEquals( + this.form.value, + MailingListForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } diff --git a/packages/reactive_forms_generator/example/lib/docs/model_extends/model_extends.dart b/packages/reactive_forms_generator/example/lib/docs/model_extends/model_extends.dart index 880689c8..65c886a7 100644 --- a/packages/reactive_forms_generator/example/lib/docs/model_extends/model_extends.dart +++ b/packages/reactive_forms_generator/example/lib/docs/model_extends/model_extends.dart @@ -8,7 +8,7 @@ abstract class Email { Email({this.email = ''}); } -@Rf() +@Rf(output: false) class ModelExtends extends Email { final String password; diff --git a/packages/reactive_forms_generator/example/lib/docs/model_extends/model_extends.gform.dart b/packages/reactive_forms_generator/example/lib/docs/model_extends/model_extends.gform.dart index 532ec2b2..78300e5d 100644 --- a/packages/reactive_forms_generator/example/lib/docs/model_extends/model_extends.gform.dart +++ b/packages/reactive_forms_generator/example/lib/docs/model_extends/model_extends.gform.dart @@ -1,7 +1,7 @@ // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'model_extends.dart'; @@ -137,6 +137,8 @@ class ModelExtendsFormBuilder extends StatefulWidget { class _ModelExtendsFormBuilderState extends State { late ModelExtendsForm _formModel; + StreamSubscription? _logSubscription; + @override void initState() { _formModel = @@ -148,6 +150,34 @@ class _ModelExtendsFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logSubscription = _logModelExtendsForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -163,6 +193,7 @@ class _ModelExtendsFormBuilderState extends State { @override void dispose() { _formModel.form.dispose(); + _logSubscription?.cancel(); super.dispose(); } @@ -185,7 +216,9 @@ class _ModelExtendsFormBuilderState extends State { } } -class ModelExtendsForm implements FormModel { +final _logModelExtendsForm = Logger.detached('ModelExtendsForm'); + +class ModelExtendsForm implements FormModel { ModelExtendsForm( this.form, this.path, @@ -338,9 +371,11 @@ class ModelExtendsForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'ModelExtendsForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logModelExtendsForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return ModelExtends(email: _emailValue, password: _passwordValue); } @@ -386,10 +421,20 @@ class ModelExtendsForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logModelExtendsForm.info('Errors'); + _logModelExtendsForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(ModelExtends? other) { + return mapEquals( + this.form.value, + ModelExtendsForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } diff --git a/packages/reactive_forms_generator/example/lib/docs/model_extends/model_extends_form.dart b/packages/reactive_forms_generator/example/lib/docs/model_extends/model_extends_form.dart index 4d05122f..d6ada5f6 100644 --- a/packages/reactive_forms_generator/example/lib/docs/model_extends/model_extends_form.dart +++ b/packages/reactive_forms_generator/example/lib/docs/model_extends/model_extends_form.dart @@ -102,7 +102,8 @@ class _ModelExtendsWidgetState extends State { onPressed: formModel.form.valid ? () { // ignore: unnecessary_cast, avoid_print - debugPrint((formModel as FormModel) + debugPrint((formModel + as FormModel) .model .toString()); // ignore: avoid_print diff --git a/packages/reactive_forms_generator/example/lib/docs/model_implements/model_implements.dart b/packages/reactive_forms_generator/example/lib/docs/model_implements/model_implements.dart index 53468fbd..97fc2763 100644 --- a/packages/reactive_forms_generator/example/lib/docs/model_implements/model_implements.dart +++ b/packages/reactive_forms_generator/example/lib/docs/model_implements/model_implements.dart @@ -14,7 +14,7 @@ abstract class Password { Password({this.password = ''}); } -@Rf() +@Rf(output: false) class ModelImplements implements Email, Password { @override final String email; diff --git a/packages/reactive_forms_generator/example/lib/docs/model_implements/model_implements.gform.dart b/packages/reactive_forms_generator/example/lib/docs/model_implements/model_implements.gform.dart index e2109279..94e8b6e5 100644 --- a/packages/reactive_forms_generator/example/lib/docs/model_implements/model_implements.gform.dart +++ b/packages/reactive_forms_generator/example/lib/docs/model_implements/model_implements.gform.dart @@ -1,7 +1,7 @@ // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'model_implements.dart'; @@ -140,6 +140,8 @@ class _ModelImplementsFormBuilderState extends State { late ModelImplementsForm _formModel; + StreamSubscription? _logSubscription; + @override void initState() { _formModel = ModelImplementsForm( @@ -151,6 +153,34 @@ class _ModelImplementsFormBuilderState widget.initState?.call(context, _formModel); + _logSubscription = _logModelImplementsForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -166,6 +196,7 @@ class _ModelImplementsFormBuilderState @override void dispose() { _formModel.form.dispose(); + _logSubscription?.cancel(); super.dispose(); } @@ -188,7 +219,10 @@ class _ModelImplementsFormBuilderState } } -class ModelImplementsForm implements FormModel { +final _logModelImplementsForm = Logger.detached('ModelImplementsForm'); + +class ModelImplementsForm + implements FormModel { ModelImplementsForm( this.form, this.path, @@ -341,9 +375,11 @@ class ModelImplementsForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'ModelImplementsForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logModelImplementsForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return ModelImplements(email: _emailValue, password: _passwordValue); } @@ -389,10 +425,20 @@ class ModelImplementsForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logModelImplementsForm.info('Errors'); + _logModelImplementsForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(ModelImplements? other) { + return mapEquals( + this.form.value, + ModelImplementsForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } diff --git a/packages/reactive_forms_generator/example/lib/docs/model_implements/model_implements_form.dart b/packages/reactive_forms_generator/example/lib/docs/model_implements/model_implements_form.dart index 1ba9e09e..43f4c5c3 100644 --- a/packages/reactive_forms_generator/example/lib/docs/model_implements/model_implements_form.dart +++ b/packages/reactive_forms_generator/example/lib/docs/model_implements/model_implements_form.dart @@ -100,7 +100,8 @@ class _ModelImplementsWidgetState extends State { onPressed: formModel.form.valid ? () { // ignore: unnecessary_cast, avoid_print - debugPrint((formModel as FormModel) + debugPrint((formModel as FormModel) .model .toString()); // ignore: avoid_print diff --git a/packages/reactive_forms_generator/example/lib/docs/nested/nested.dart b/packages/reactive_forms_generator/example/lib/docs/nested/nested.dart index b3af692c..b89c96b1 100644 --- a/packages/reactive_forms_generator/example/lib/docs/nested/nested.dart +++ b/packages/reactive_forms_generator/example/lib/docs/nested/nested.dart @@ -7,7 +7,7 @@ part 'nested.g.dart'; part 'nested.gform.dart'; -@Rf() +@Rf(output: false) @RfGroup() @freezed class SubGroup with _$SubGroup { @@ -19,7 +19,7 @@ class SubGroup with _$SubGroup { _$SubGroupFromJson(json); } -@Rf() +@Rf(output: false) @RfGroup() @freezed class Group with _$Group { @@ -31,7 +31,7 @@ class Group with _$Group { factory Group.fromJson(Map json) => _$GroupFromJson(json); } -@Rf() +@Rf(output: false) @RfGroup() @freezed class Nested with _$Nested { diff --git a/packages/reactive_forms_generator/example/lib/docs/nested/nested.gform.dart b/packages/reactive_forms_generator/example/lib/docs/nested/nested.gform.dart index a98c4a1a..e250e644 100644 --- a/packages/reactive_forms_generator/example/lib/docs/nested/nested.gform.dart +++ b/packages/reactive_forms_generator/example/lib/docs/nested/nested.gform.dart @@ -1,7 +1,7 @@ // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'nested.dart'; @@ -133,6 +133,8 @@ class SubGroupFormBuilder extends StatefulWidget { class _SubGroupFormBuilderState extends State { late SubGroupForm _formModel; + StreamSubscription? _logSubscription; + @override void initState() { _formModel = SubGroupForm(SubGroupForm.formElements(widget.model), null); @@ -143,6 +145,34 @@ class _SubGroupFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logSubscription = _logSubGroupForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -158,6 +188,7 @@ class _SubGroupFormBuilderState extends State { @override void dispose() { _formModel.form.dispose(); + _logSubscription?.cancel(); super.dispose(); } @@ -180,7 +211,9 @@ class _SubGroupFormBuilderState extends State { } } -class SubGroupForm implements FormModel { +final _logSubGroupForm = Logger.detached('SubGroupForm'); + +class SubGroupForm implements FormModel { SubGroupForm( this.form, this.path, @@ -265,9 +298,11 @@ class SubGroupForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'SubGroupForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logSubGroupForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return SubGroup(id: _idValue); } @@ -313,10 +348,20 @@ class SubGroupForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logSubGroupForm.info('Errors'); + _logSubGroupForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(SubGroup? other) { + return mapEquals( + this.form.value, + SubGroupForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } @@ -616,6 +661,8 @@ class GroupFormBuilder extends StatefulWidget { class _GroupFormBuilderState extends State { late GroupForm _formModel; + StreamSubscription? _logSubscription; + @override void initState() { _formModel = GroupForm(GroupForm.formElements(widget.model), null); @@ -626,6 +673,34 @@ class _GroupFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logSubscription = _logGroupForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -641,6 +716,7 @@ class _GroupFormBuilderState extends State { @override void dispose() { _formModel.form.dispose(); + _logSubscription?.cancel(); super.dispose(); } @@ -663,7 +739,9 @@ class _GroupFormBuilderState extends State { } } -class GroupForm implements FormModel { +final _logGroupForm = Logger.detached('GroupForm'); + +class GroupForm implements FormModel { GroupForm( this.form, this.path, @@ -927,9 +1005,11 @@ class GroupForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'GroupForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logGroupForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Group(id: _idValue, subGroupList: _subGroupListValue); } @@ -979,10 +1059,20 @@ class GroupForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logGroupForm.info('Errors'); + _logGroupForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(Group? other) { + return mapEquals( + this.form.value, + GroupForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } @@ -1287,6 +1377,8 @@ class NestedFormBuilder extends StatefulWidget { class _NestedFormBuilderState extends State { late NestedForm _formModel; + StreamSubscription? _logSubscription; + @override void initState() { _formModel = NestedForm(NestedForm.formElements(widget.model), null); @@ -1297,6 +1389,34 @@ class _NestedFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logSubscription = _logNestedForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -1312,6 +1432,7 @@ class _NestedFormBuilderState extends State { @override void dispose() { _formModel.form.dispose(); + _logSubscription?.cancel(); super.dispose(); } @@ -1334,7 +1455,9 @@ class _NestedFormBuilderState extends State { } } -class NestedForm implements FormModel { +final _logNestedForm = Logger.detached('NestedForm'); + +class NestedForm implements FormModel { NestedForm( this.form, this.path, @@ -1527,9 +1650,11 @@ class NestedForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'NestedForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logNestedForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Nested(groupList: _groupListValue); } @@ -1579,10 +1704,20 @@ class NestedForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logNestedForm.info('Errors'); + _logNestedForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(Nested? other) { + return mapEquals( + this.form.value, + NestedForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } diff --git a/packages/reactive_forms_generator/example/lib/docs/nested_generics/product.dart b/packages/reactive_forms_generator/example/lib/docs/nested_generics/product.dart index 4d011056..8a32c615 100644 --- a/packages/reactive_forms_generator/example/lib/docs/nested_generics/product.dart +++ b/packages/reactive_forms_generator/example/lib/docs/nested_generics/product.dart @@ -2,11 +2,13 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; part 'product.freezed.dart'; + part 'product.gform.dart'; @freezed -@Rf() -class ProductDetails

with _$ProductDetails { +@Rf(output: false) +class ProductDetails

+ with _$ProductDetails { factory ProductDetails({ @RfControl() String? description, @Rf() Id? id, @@ -16,10 +18,9 @@ class ProductDetails

with _$ProductDetails - with _$Id { +class Id

with _$Id { factory Id({ @RfControl() String? companyName, @RfControl() String? name, @@ -31,7 +32,7 @@ class Id

@freezed class Product with _$Product { const factory Product({ - String? companyName, + String? companyName, String? name, }) = _Product; @@ -41,7 +42,7 @@ class Product with _$Product { @freezed class Cart with _$Cart { const factory Cart({ - Product? product, + Product? product, String? description, }) = _Cart; diff --git a/packages/reactive_forms_generator/example/lib/docs/nested_generics/product.gform.dart b/packages/reactive_forms_generator/example/lib/docs/nested_generics/product.gform.dart index 967be42e..3ee7045f 100644 --- a/packages/reactive_forms_generator/example/lib/docs/nested_generics/product.gform.dart +++ b/packages/reactive_forms_generator/example/lib/docs/nested_generics/product.gform.dart @@ -1,7 +1,7 @@ // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'product.dart'; @@ -144,6 +144,8 @@ class _ProductDetailsFormBuilderState

extends State> { late ProductDetailsForm _formModel; + StreamSubscription? _logSubscription; + @override void initState() { _formModel = ProductDetailsForm( @@ -155,6 +157,34 @@ class _ProductDetailsFormBuilderState

widget.initState?.call(context, _formModel); + _logSubscription = _logProductDetailsForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -170,6 +200,7 @@ class _ProductDetailsFormBuilderState

@override void dispose() { _formModel.form.dispose(); + _logSubscription?.cancel(); super.dispose(); } @@ -192,8 +223,10 @@ class _ProductDetailsFormBuilderState

} } +final _logProductDetailsForm = Logger.detached('ProductDetailsForm'); + class ProductDetailsForm

- implements FormModel> { + implements FormModel, ProductDetails> { ProductDetailsForm( this.form, this.path, @@ -403,9 +436,11 @@ class ProductDetailsForm

final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'ProductDetailsForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logProductDetailsForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return ProductDetails(description: _descriptionValue, id: _idValue); } @@ -453,10 +488,20 @@ class ProductDetailsForm

if (currentForm.valid) { onValid(model); } else { + _logProductDetailsForm.info('Errors'); + _logProductDetailsForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(ProductDetails? other) { + return mapEquals( + this.form.value, + ProductDetailsForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } @@ -502,7 +547,10 @@ class ProductDetailsForm

disabled: false); } -class IdForm

implements FormModel> { +final _logIdForm = Logger.detached('IdForm'); + +class IdForm

+ implements FormModel, Id> { IdForm( this.form, this.path, @@ -709,9 +757,11 @@ class IdForm

implements FormModel> { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'IdForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logIdForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Id(companyName: _companyNameValue, name: _nameValue); } @@ -757,10 +807,20 @@ class IdForm

implements FormModel> { if (currentForm.valid) { onValid(model); } else { + _logIdForm.info('Errors'); + _logIdForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(Id? other) { + return mapEquals( + this.form.value, + IdForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } @@ -1077,6 +1137,8 @@ class _IdFormBuilderState

extends State> { late IdForm _formModel; + StreamSubscription? _logSubscription; + @override void initState() { _formModel = IdForm(IdForm.formElements(widget.model), null); @@ -1087,6 +1149,34 @@ class _IdFormBuilderState

widget.initState?.call(context, _formModel); + _logSubscription = _logIdForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -1102,6 +1192,7 @@ class _IdFormBuilderState

@override void dispose() { _formModel.form.dispose(); + _logSubscription?.cancel(); super.dispose(); } diff --git a/packages/reactive_forms_generator/example/lib/docs/profile/profile.dart b/packages/reactive_forms_generator/example/lib/docs/profile/profile.dart index e59dbbf6..f0991e3b 100644 --- a/packages/reactive_forms_generator/example/lib/docs/profile/profile.dart +++ b/packages/reactive_forms_generator/example/lib/docs/profile/profile.dart @@ -45,7 +45,7 @@ extension NumberingStandardExt on NumberingStandard { } @freezed -@Rf() +@Rf(output: false) class Profile with _$Profile { const Profile._(); diff --git a/packages/reactive_forms_generator/example/lib/docs/profile/profile.g.dart b/packages/reactive_forms_generator/example/lib/docs/profile/profile.g.dart index 1ba0becc..5a8bbd0e 100644 --- a/packages/reactive_forms_generator/example/lib/docs/profile/profile.g.dart +++ b/packages/reactive_forms_generator/example/lib/docs/profile/profile.g.dart @@ -54,7 +54,7 @@ _$ThresholdSettingImpl _$$ThresholdSettingImplFromJson( Map json) => _$ThresholdSettingImpl( isEnabled: json['isEnabled'] as bool? ?? true, - value: (json['value'] as num?)?.toInt() ?? 2, + value: json['value'] as int? ?? 2, ); Map _$$ThresholdSettingImplToJson( @@ -67,7 +67,7 @@ Map _$$ThresholdSettingImplToJson( _$TimerSettingImpl _$$TimerSettingImplFromJson(Map json) => _$TimerSettingImpl( isEnabled: json['isEnabled'] as bool? ?? false, - value: (json['value'] as num?)?.toInt() ?? 5, + value: json['value'] as int? ?? 5, ); Map _$$TimerSettingImplToJson(_$TimerSettingImpl instance) => @@ -160,7 +160,7 @@ _$ChartingOrderValueImpl _$$ChartingOrderValueImplFromJson( chartingOrder: $enumDecodeNullable( _$ChartingOrderTypeEnumMap, json['chartingOrder']) ?? ChartingOrderType.arch, - selectedOption: (json['selectedOption'] as num?)?.toInt() ?? 0, + selectedOption: json['selectedOption'] as int? ?? 0, order: (json['order'] as List) .map((e) => (e as List) .map((e) => ScanOrder.fromJson(e as Map)) diff --git a/packages/reactive_forms_generator/example/lib/docs/profile/profile.gform.dart b/packages/reactive_forms_generator/example/lib/docs/profile/profile.gform.dart index d9dd6d63..6973f1e8 100644 --- a/packages/reactive_forms_generator/example/lib/docs/profile/profile.gform.dart +++ b/packages/reactive_forms_generator/example/lib/docs/profile/profile.gform.dart @@ -1,7 +1,7 @@ // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'profile.dart'; @@ -132,6 +132,8 @@ class ProfileFormBuilder extends StatefulWidget { class _ProfileFormBuilderState extends State { late ProfileForm _formModel; + StreamSubscription? _logSubscription; + @override void initState() { _formModel = ProfileForm(ProfileForm.formElements(widget.model), null); @@ -142,6 +144,34 @@ class _ProfileFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logSubscription = _logProfileForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -157,6 +187,7 @@ class _ProfileFormBuilderState extends State { @override void dispose() { _formModel.form.dispose(); + _logSubscription?.cancel(); super.dispose(); } @@ -179,7 +210,9 @@ class _ProfileFormBuilderState extends State { } } -class ProfileForm implements FormModel { +final _logProfileForm = Logger.detached('ProfileForm'); + +class ProfileForm implements FormModel { ProfileForm( this.form, this.path, @@ -910,9 +943,11 @@ class ProfileForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'ProfileForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logProfileForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Profile(_idValue, anotherId: _anotherIdValue, @@ -973,10 +1008,20 @@ class ProfileForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logProfileForm.info('Errors'); + _logProfileForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(Profile? other) { + return mapEquals( + this.form.value, + ProfileForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } @@ -1066,7 +1111,10 @@ class ProfileForm implements FormModel { disabled: false); } -class IncidenceFilterForm implements FormModel { +final _logIncidenceFilterForm = Logger.detached('IncidenceFilterForm'); + +class IncidenceFilterForm + implements FormModel { IncidenceFilterForm( this.form, this.path, @@ -1506,9 +1554,11 @@ class IncidenceFilterForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'IncidenceFilterForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logIncidenceFilterForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return IncidenceFilter( isMobilityEnabled: _isMobilityEnabledValue, @@ -1560,10 +1610,20 @@ class IncidenceFilterForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logIncidenceFilterForm.info('Errors'); + _logIncidenceFilterForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(IncidenceFilter? other) { + return mapEquals( + this.form.value, + IncidenceFilterForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } @@ -1641,7 +1701,10 @@ class IncidenceFilterForm implements FormModel { disabled: false); } -class ThresholdSettingForm implements FormModel { +final _logThresholdSettingForm = Logger.detached('ThresholdSettingForm'); + +class ThresholdSettingForm + implements FormModel { ThresholdSettingForm( this.form, this.path, @@ -1794,9 +1857,11 @@ class ThresholdSettingForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'ThresholdSettingForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logThresholdSettingForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return ThresholdSetting(isEnabled: _isEnabledValue, value: _valueValue); } @@ -1842,10 +1907,20 @@ class ThresholdSettingForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logThresholdSettingForm.info('Errors'); + _logThresholdSettingForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(ThresholdSetting? other) { + return mapEquals( + this.form.value, + ThresholdSettingForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } @@ -1896,7 +1971,9 @@ class ThresholdSettingForm implements FormModel { disabled: false); } -class TimerSettingForm implements FormModel { +final _logTimerSettingForm = Logger.detached('TimerSettingForm'); + +class TimerSettingForm implements FormModel { TimerSettingForm( this.form, this.path, @@ -2049,9 +2126,11 @@ class TimerSettingForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'TimerSettingForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logTimerSettingForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return TimerSetting(isEnabled: _isEnabledValue, value: _valueValue); } @@ -2097,10 +2176,20 @@ class TimerSettingForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logTimerSettingForm.info('Errors'); + _logTimerSettingForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(TimerSetting? other) { + return mapEquals( + this.form.value, + TimerSettingForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } diff --git a/packages/reactive_forms_generator/example/lib/docs/renamed_basic/renamed_basic.dart b/packages/reactive_forms_generator/example/lib/docs/renamed_basic/renamed_basic.dart index b4a91258..d2a2c54e 100644 --- a/packages/reactive_forms_generator/example/lib/docs/renamed_basic/renamed_basic.dart +++ b/packages/reactive_forms_generator/example/lib/docs/renamed_basic/renamed_basic.dart @@ -2,7 +2,7 @@ import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; part 'renamed_basic.gform.dart'; -@Rf(name: 'SomeWiredName') +@Rf(output: false, name: 'SomeWiredName') class RenamedBasic { final String email; diff --git a/packages/reactive_forms_generator/example/lib/docs/renamed_basic/renamed_basic.gform.dart b/packages/reactive_forms_generator/example/lib/docs/renamed_basic/renamed_basic.gform.dart index 66db7cb2..95cef22f 100644 --- a/packages/reactive_forms_generator/example/lib/docs/renamed_basic/renamed_basic.gform.dart +++ b/packages/reactive_forms_generator/example/lib/docs/renamed_basic/renamed_basic.gform.dart @@ -1,7 +1,7 @@ // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'renamed_basic.dart'; @@ -137,6 +137,8 @@ class SomeWiredNameFormBuilder extends StatefulWidget { class _SomeWiredNameFormBuilderState extends State { late SomeWiredNameForm _formModel; + StreamSubscription? _logSubscription; + @override void initState() { _formModel = @@ -148,6 +150,34 @@ class _SomeWiredNameFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logSubscription = _logSomeWiredNameForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -163,6 +193,7 @@ class _SomeWiredNameFormBuilderState extends State { @override void dispose() { _formModel.form.dispose(); + _logSubscription?.cancel(); super.dispose(); } @@ -185,7 +216,9 @@ class _SomeWiredNameFormBuilderState extends State { } } -class SomeWiredNameForm implements FormModel { +final _logSomeWiredNameForm = Logger.detached('SomeWiredNameForm'); + +class SomeWiredNameForm implements FormModel { SomeWiredNameForm( this.form, this.path, @@ -338,9 +371,11 @@ class SomeWiredNameForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'SomeWiredNameForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logSomeWiredNameForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return RenamedBasic(email: _emailValue, password: _passwordValue); } @@ -386,10 +421,20 @@ class SomeWiredNameForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logSomeWiredNameForm.info('Errors'); + _logSomeWiredNameForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(RenamedBasic? other) { + return mapEquals( + this.form.value, + SomeWiredNameForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } diff --git a/packages/reactive_forms_generator/example/lib/docs/renamed_basic/renamed_basic_form.dart b/packages/reactive_forms_generator/example/lib/docs/renamed_basic/renamed_basic_form.dart index da09f9e1..d942e39a 100644 --- a/packages/reactive_forms_generator/example/lib/docs/renamed_basic/renamed_basic_form.dart +++ b/packages/reactive_forms_generator/example/lib/docs/renamed_basic/renamed_basic_form.dart @@ -51,7 +51,8 @@ class BasicFormWidget extends StatelessWidget { onPressed: formModel.form.valid ? () { // ignore: unnecessary_cast - debugPrint((formModel as FormModel) + debugPrint((formModel + as FormModel) .model .toString()); debugPrint(formModel.model.email); diff --git a/packages/reactive_forms_generator/example/lib/docs/renamed_basic/renamed_basic_output.dart b/packages/reactive_forms_generator/example/lib/docs/renamed_basic/renamed_basic_output.dart new file mode 100644 index 00000000..5b7094c5 --- /dev/null +++ b/packages/reactive_forms_generator/example/lib/docs/renamed_basic/renamed_basic_output.dart @@ -0,0 +1,21 @@ +import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; + +part 'renamed_basic_output.gform.dart'; + +@Rf(name: 'SomeWiredName') +class RenamedBasicO { + final String? email; + + final String? password; + + RenamedBasicO({ + @RfControl( + validators: [RequiredValidator()], + ) + this.email, + @RfControl( + validators: [RequiredValidator()], + ) + this.password, + }); +} diff --git a/packages/reactive_forms_generator/example/lib/docs/renamed_basic/renamed_basic_output.gform.dart b/packages/reactive_forms_generator/example/lib/docs/renamed_basic/renamed_basic_output.gform.dart new file mode 100644 index 00000000..e9dd92cf --- /dev/null +++ b/packages/reactive_forms_generator/example/lib/docs/renamed_basic/renamed_basic_output.gform.dart @@ -0,0 +1,679 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'renamed_basic_output.dart'; + +// ************************************************************************** +// ReactiveFormsGenerator +// ************************************************************************** + +class ReactiveSomeWiredNameFormConsumer extends StatelessWidget { + const ReactiveSomeWiredNameFormConsumer({ + Key? key, + required this.builder, + this.child, + }) : super(key: key); + + final Widget? child; + + final Widget Function( + BuildContext context, SomeWiredNameForm formModel, Widget? child) builder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveSomeWiredNameForm.of(context); + + if (formModel is! SomeWiredNameForm) { + throw FormControlParentNotFoundException(this); + } + return builder(context, formModel, child); + } +} + +class SomeWiredNameFormInheritedStreamer extends InheritedStreamer { + const SomeWiredNameFormInheritedStreamer({ + Key? key, + required this.form, + required Stream stream, + required Widget child, + }) : super( + stream, + child, + key: key, + ); + + final SomeWiredNameForm form; +} + +class ReactiveSomeWiredNameForm extends StatelessWidget { + const ReactiveSomeWiredNameForm({ + Key? key, + required this.form, + required this.child, + this.canPop, + this.onPopInvoked, + }) : super(key: key); + + final Widget child; + + final SomeWiredNameForm form; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + static SomeWiredNameForm? of( + BuildContext context, { + bool listen = true, + }) { + if (listen) { + return context + .dependOnInheritedWidgetOfExactType< + SomeWiredNameFormInheritedStreamer>() + ?.form; + } + + final element = context.getElementForInheritedWidgetOfExactType< + SomeWiredNameFormInheritedStreamer>(); + return element == null + ? null + : (element.widget as SomeWiredNameFormInheritedStreamer).form; + } + + @override + Widget build(BuildContext context) { + return SomeWiredNameFormInheritedStreamer( + form: form, + stream: form.form.statusChanged, + child: ReactiveFormPopScope( + canPop: canPop, + onPopInvoked: onPopInvoked, + child: child, + ), + ); + } +} + +extension ReactiveReactiveSomeWiredNameFormExt on BuildContext { + SomeWiredNameForm? someWiredNameFormWatch() => + ReactiveSomeWiredNameForm.of(this); + + SomeWiredNameForm? someWiredNameFormRead() => + ReactiveSomeWiredNameForm.of(this, listen: false); +} + +class SomeWiredNameFormBuilder extends StatefulWidget { + const SomeWiredNameFormBuilder({ + Key? key, + this.model, + this.child, + this.canPop, + this.onPopInvoked, + required this.builder, + this.initState, + }) : super(key: key); + + final RenamedBasicO? model; + + final Widget? child; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + final Widget Function( + BuildContext context, SomeWiredNameForm formModel, Widget? child) builder; + + final void Function(BuildContext context, SomeWiredNameForm formModel)? + initState; + + @override + _SomeWiredNameFormBuilderState createState() => + _SomeWiredNameFormBuilderState(); +} + +class _SomeWiredNameFormBuilderState extends State { + late SomeWiredNameForm _formModel; + + StreamSubscription? _logSubscription; + + @override + void initState() { + _formModel = + SomeWiredNameForm(SomeWiredNameForm.formElements(widget.model), null); + + if (_formModel.form.disabled) { + _formModel.form.markAsDisabled(); + } + + widget.initState?.call(context, _formModel); + + _logSubscription = _logSomeWiredNameForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + + super.initState(); + } + + @override + void didUpdateWidget(covariant SomeWiredNameFormBuilder oldWidget) { + if (widget.model != oldWidget.model) { + _formModel.updateValue(widget.model); + } + + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + _formModel.form.dispose(); + _logSubscription?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ReactiveSomeWiredNameForm( + key: ObjectKey(_formModel), + form: _formModel, + // canPop: widget.canPop, + // onPopInvoked: widget.onPopInvoked, + child: ReactiveFormBuilder( + form: () => _formModel.form, + canPop: widget.canPop, + onPopInvoked: widget.onPopInvoked, + builder: (context, formGroup, child) => + widget.builder(context, _formModel, widget.child), + child: widget.child, + ), + ); + } +} + +final _logSomeWiredNameForm = Logger.detached('SomeWiredNameForm'); + +class SomeWiredNameForm + implements FormModel { + SomeWiredNameForm( + this.form, + this.path, + ); + + static const String emailControlName = "email"; + + static const String passwordControlName = "password"; + + final FormGroup form; + + final String? path; + + final Map _disabled = {}; + + String emailControlPath() => pathBuilder(emailControlName); + + String passwordControlPath() => pathBuilder(passwordControlName); + + String get _emailValue => emailControl.value as String; + + String get _passwordValue => passwordControl.value as String; + + bool get containsEmail { + try { + form.control(emailControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsPassword { + try { + form.control(passwordControlPath()); + return true; + } catch (e) { + return false; + } + } + + Map? get emailErrors => emailControl.errors; + + Map? get passwordErrors => passwordControl.errors; + + void get emailFocus => form.focus(emailControlPath()); + + void get passwordFocus => form.focus(passwordControlPath()); + + void emailRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsEmail) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + emailControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + emailControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void passwordRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsPassword) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + passwordControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + passwordControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void emailValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + emailControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void passwordValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + passwordControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void emailValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + emailControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void passwordValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + passwordControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void emailValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + emailControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void passwordValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + passwordControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + FormControl get emailControl => + form.control(emailControlPath()) as FormControl; + + FormControl get passwordControl => + form.control(passwordControlPath()) as FormControl; + + void emailSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + emailControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + emailControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void passwordSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + passwordControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + passwordControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + @override + RenamedBasicOOutput get model { + final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; + + if (!isValid) { + _logSomeWiredNameForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); + } + return RenamedBasicOOutput(email: _emailValue, password: _passwordValue); + } + + @override + void toggleDisabled({ + bool updateParent = true, + bool emitEvent = true, + }) { + final currentFormInstance = currentForm; + + if (currentFormInstance is! FormGroup) { + return; + } + + if (_disabled.isEmpty) { + currentFormInstance.controls.forEach((key, control) { + _disabled[key] = control.disabled; + }); + + currentForm.markAsDisabled( + updateParent: updateParent, emitEvent: emitEvent); + } else { + currentFormInstance.controls.forEach((key, control) { + if (_disabled[key] == false) { + currentFormInstance.controls[key]?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + + _disabled.remove(key); + }); + } + } + + @override + void submit({ + required void Function(RenamedBasicOOutput model) onValid, + void Function()? onNotValid, + }) { + currentForm.markAllAsTouched(); + if (currentForm.valid) { + onValid(model); + } else { + _logSomeWiredNameForm.info('Errors'); + _logSomeWiredNameForm.info('┗━━ ${form.errors}'); + onNotValid?.call(); + } + } + + @override + bool equalsTo(RenamedBasicO? other) { + return mapEquals( + this.form.value, + SomeWiredNameForm.formElements(other).value, + ); + } + + AbstractControl get currentForm { + return path == null ? form : form.control(path!); + } + + @override + void updateValue( + RenamedBasicO? value, { + bool updateParent = true, + bool emitEvent = true, + }) => + form.updateValue(SomeWiredNameForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + + @override + void reset({ + RenamedBasicO? value, + bool updateParent = true, + bool emitEvent = true, + }) => + form.reset( + value: value != null ? formElements(value).rawValue : null, + updateParent: updateParent, + emitEvent: emitEvent); + + String pathBuilder(String? pathItem) => + [path, pathItem].whereType().join("."); + + static FormGroup formElements(RenamedBasicO? renamedBasicO) => FormGroup({ + emailControlName: FormControl( + value: renamedBasicO?.email, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + passwordControlName: FormControl( + value: renamedBasicO?.password, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false) + }, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false); +} + +@Rf(name: 'SomeWiredName') +class RenamedBasicOOutput { + final String email; + final String password; + RenamedBasicOOutput( + {@RfControl(validators: [RequiredValidator()]) required this.email, + @RfControl(validators: [RequiredValidator()]) required this.password}); +} + +class ReactiveSomeWiredNameFormArrayBuilder< + ReactiveSomeWiredNameFormArrayBuilderT> extends StatelessWidget { + const ReactiveSomeWiredNameFormArrayBuilder({ + Key? key, + this.control, + this.formControl, + this.builder, + required this.itemBuilder, + }) : assert(control != null || formControl != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final FormArray? formControl; + + final FormArray? Function( + SomeWiredNameForm formModel)? control; + + final Widget Function(BuildContext context, List itemList, + SomeWiredNameForm formModel)? builder; + + final Widget Function( + BuildContext context, + int i, + ReactiveSomeWiredNameFormArrayBuilderT? item, + SomeWiredNameForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveSomeWiredNameForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + return ReactiveFormArray( + formArray: formControl ?? control?.call(formModel), + builder: (context, formArray, child) { + final values = formArray.controls.map((e) => e.value).toList(); + final itemList = values + .asMap() + .map((i, item) { + return MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + ); + }) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} + +class ReactiveSomeWiredNameFormFormGroupArrayBuilder< + ReactiveSomeWiredNameFormFormGroupArrayBuilderT> extends StatelessWidget { + const ReactiveSomeWiredNameFormFormGroupArrayBuilder({ + Key? key, + this.extended, + this.getExtended, + this.builder, + required this.itemBuilder, + }) : assert(extended != null || getExtended != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final ExtendedControl?>, + List>? extended; + + final ExtendedControl?>, + List> + Function(SomeWiredNameForm formModel)? getExtended; + + final Widget Function(BuildContext context, List itemList, + SomeWiredNameForm formModel)? builder; + + final Widget Function( + BuildContext context, + int i, + ReactiveSomeWiredNameFormFormGroupArrayBuilderT? item, + SomeWiredNameForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveSomeWiredNameForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + final value = (extended ?? getExtended?.call(formModel))!; + + return StreamBuilder?>?>( + stream: value.control.valueChanges, + builder: (context, snapshot) { + final itemList = (value.value() ?? + []) + .asMap() + .map((i, item) => MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + )) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} diff --git a/packages/reactive_forms_generator/example/lib/docs/shop/shop.dart b/packages/reactive_forms_generator/example/lib/docs/shop/shop.dart deleted file mode 100644 index 152da5a1..00000000 --- a/packages/reactive_forms_generator/example/lib/docs/shop/shop.dart +++ /dev/null @@ -1,69 +0,0 @@ -// import 'package:equatable/equatable.dart'; -// import 'package:flutter/material.dart'; -// import 'package:reactive_forms/reactive_forms.dart'; -// import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; -// import 'package:example/helpers.dart'; -// -// part 'shop.gform.dart'; -// -// @Rf() -// class Shop extends Equatable { -// final Address? shopAddress; -// final List? clientList; -// -// const Shop({ -// this.shopAddress, -// @RfArray() this.clientList, -// }); -// -// @override -// List get props => [shopAddress, clientList]; -// } -// -// enum ClientType { home, office } -// -// @RfGroup() -// class Client extends Equatable { -// final ClientType clientType; -// -// final String? name; -// -// final String? notes; -// -// final List

addressList; -// -// const Client({ -// @RfControl() required this.clientType, -// @RfArray() this.addressList = const [], -// @RfControl() this.name, -// @RfControl() this.notes, -// }); -// -// @override -// List get props => [name, notes]; -// } -// -// @RfGroup() -// class Address extends Equatable { -// final String? type; -// -// final String? street; -// -// final String? city; -// -// const Address({ -// @RfControl( -// validators: [requiredValidator], -// ) -// this.type, -// @RfControl( -// validators: [requiredValidator], -// ) -// this.street, -// @RfControl() -// this.city, -// }); -// -// @override -// List get props => [street, city]; -// } diff --git a/packages/reactive_forms_generator/example/lib/docs/user_profile/user_profile.dart b/packages/reactive_forms_generator/example/lib/docs/user_profile/user_profile.dart index c233ce89..90270f73 100644 --- a/packages/reactive_forms_generator/example/lib/docs/user_profile/user_profile.dart +++ b/packages/reactive_forms_generator/example/lib/docs/user_profile/user_profile.dart @@ -2,7 +2,7 @@ import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; part 'user_profile.gform.dart'; -@Rf() +@Rf(output: false) class UserProfile { final String id; diff --git a/packages/reactive_forms_generator/example/lib/docs/user_profile/user_profile.gform.dart b/packages/reactive_forms_generator/example/lib/docs/user_profile/user_profile.gform.dart index 7269d723..74391534 100644 --- a/packages/reactive_forms_generator/example/lib/docs/user_profile/user_profile.gform.dart +++ b/packages/reactive_forms_generator/example/lib/docs/user_profile/user_profile.gform.dart @@ -1,7 +1,7 @@ // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint -// ignore_for_file: +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark part of 'user_profile.dart'; @@ -135,6 +135,8 @@ class UserProfileFormBuilder extends StatefulWidget { class _UserProfileFormBuilderState extends State { late UserProfileForm _formModel; + StreamSubscription? _logSubscription; + @override void initState() { _formModel = @@ -146,6 +148,34 @@ class _UserProfileFormBuilderState extends State { widget.initState?.call(context, _formModel); + _logSubscription = _logUserProfileForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); } @@ -161,6 +191,7 @@ class _UserProfileFormBuilderState extends State { @override void dispose() { _formModel.form.dispose(); + _logSubscription?.cancel(); super.dispose(); } @@ -183,7 +214,9 @@ class _UserProfileFormBuilderState extends State { } } -class UserProfileForm implements FormModel { +final _logUserProfileForm = Logger.detached('UserProfileForm'); + +class UserProfileForm implements FormModel { UserProfileForm( this.form, this.path, @@ -573,9 +606,11 @@ class UserProfileForm implements FormModel { final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'UserProfileForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logUserProfileForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return UserProfile( id: _idValue, @@ -630,10 +665,20 @@ class UserProfileForm implements FormModel { if (currentForm.valid) { onValid(model); } else { + _logUserProfileForm.info('Errors'); + _logUserProfileForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(UserProfile? other) { + return mapEquals( + this.form.value, + UserProfileForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } @@ -692,7 +737,9 @@ class UserProfileForm implements FormModel { disabled: false); } -class AddressForm implements FormModel
{ +final _logAddressForm = Logger.detached('AddressForm'); + +class AddressForm implements FormModel { AddressForm( this.form, this.path, @@ -994,9 +1041,11 @@ class AddressForm implements FormModel
{ final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack( - label: - '[${path ?? 'AddressForm'}]\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + _logAddressForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } return Address(street: _streetValue, city: _cityValue, zip: _zipValue); } @@ -1042,10 +1091,20 @@ class AddressForm implements FormModel
{ if (currentForm.valid) { onValid(model); } else { + _logAddressForm.info('Errors'); + _logAddressForm.info('┗━━ ${form.errors}'); onNotValid?.call(); } } + @override + bool equalsTo(Address? other) { + return mapEquals( + this.form.value, + AddressForm.formElements(other).value, + ); + } + AbstractControl get currentForm { return path == null ? form : form.control(path!); } diff --git a/packages/reactive_forms_generator/example/lib/docs/user_profile/user_profile_output.dart b/packages/reactive_forms_generator/example/lib/docs/user_profile/user_profile_output.dart new file mode 100644 index 00000000..157087ea --- /dev/null +++ b/packages/reactive_forms_generator/example/lib/docs/user_profile/user_profile_output.dart @@ -0,0 +1,48 @@ +import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; + +part 'user_profile_output.gform.dart'; + +@Rf() +class UserProfileO { + final String id; + + final String? firstName; + + final String? lastName; + + final AddressO home; + + final AddressO? office; + + UserProfileO({ + required this.id, + @RfControl( + validators: [RequiredValidator()], + ) + this.firstName, + @RfControl( + validators: [RequiredValidator()], + ) + this.lastName, + required this.home, + this.office, + }); +} + +@RfGroup() +class AddressO { + final String? street; + + final String? city; + + final String? zip; + + AddressO({ + @RfControl() this.street, + @RfControl( + validators: [RequiredValidator()], + ) + this.city, + @RfControl() this.zip, + }); +} diff --git a/packages/reactive_forms_generator/example/lib/docs/user_profile/user_profile_output.gform.dart b/packages/reactive_forms_generator/example/lib/docs/user_profile/user_profile_output.gform.dart new file mode 100644 index 00000000..0d2af0fa --- /dev/null +++ b/packages/reactive_forms_generator/example/lib/docs/user_profile/user_profile_output.gform.dart @@ -0,0 +1,1374 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'user_profile_output.dart'; + +// ************************************************************************** +// ReactiveFormsGenerator +// ************************************************************************** + +class ReactiveUserProfileOFormConsumer extends StatelessWidget { + const ReactiveUserProfileOFormConsumer({ + Key? key, + required this.builder, + this.child, + }) : super(key: key); + + final Widget? child; + + final Widget Function( + BuildContext context, UserProfileOForm formModel, Widget? child) builder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveUserProfileOForm.of(context); + + if (formModel is! UserProfileOForm) { + throw FormControlParentNotFoundException(this); + } + return builder(context, formModel, child); + } +} + +class UserProfileOFormInheritedStreamer extends InheritedStreamer { + const UserProfileOFormInheritedStreamer({ + Key? key, + required this.form, + required Stream stream, + required Widget child, + }) : super( + stream, + child, + key: key, + ); + + final UserProfileOForm form; +} + +class ReactiveUserProfileOForm extends StatelessWidget { + const ReactiveUserProfileOForm({ + Key? key, + required this.form, + required this.child, + this.canPop, + this.onPopInvoked, + }) : super(key: key); + + final Widget child; + + final UserProfileOForm form; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + static UserProfileOForm? of( + BuildContext context, { + bool listen = true, + }) { + if (listen) { + return context + .dependOnInheritedWidgetOfExactType< + UserProfileOFormInheritedStreamer>() + ?.form; + } + + final element = context.getElementForInheritedWidgetOfExactType< + UserProfileOFormInheritedStreamer>(); + return element == null + ? null + : (element.widget as UserProfileOFormInheritedStreamer).form; + } + + @override + Widget build(BuildContext context) { + return UserProfileOFormInheritedStreamer( + form: form, + stream: form.form.statusChanged, + child: ReactiveFormPopScope( + canPop: canPop, + onPopInvoked: onPopInvoked, + child: child, + ), + ); + } +} + +extension ReactiveReactiveUserProfileOFormExt on BuildContext { + UserProfileOForm? userProfileOFormWatch() => + ReactiveUserProfileOForm.of(this); + + UserProfileOForm? userProfileOFormRead() => + ReactiveUserProfileOForm.of(this, listen: false); +} + +class UserProfileOFormBuilder extends StatefulWidget { + const UserProfileOFormBuilder({ + Key? key, + this.model, + this.child, + this.canPop, + this.onPopInvoked, + required this.builder, + this.initState, + }) : super(key: key); + + final UserProfileO? model; + + final Widget? child; + + final bool Function(FormGroup formGroup)? canPop; + + final void Function(FormGroup formGroup, bool didPop)? onPopInvoked; + + final Widget Function( + BuildContext context, UserProfileOForm formModel, Widget? child) builder; + + final void Function(BuildContext context, UserProfileOForm formModel)? + initState; + + @override + _UserProfileOFormBuilderState createState() => + _UserProfileOFormBuilderState(); +} + +class _UserProfileOFormBuilderState extends State { + late UserProfileOForm _formModel; + + StreamSubscription? _logSubscription; + + @override + void initState() { + _formModel = + UserProfileOForm(UserProfileOForm.formElements(widget.model), null); + + if (_formModel.form.disabled) { + _formModel.form.markAsDisabled(); + } + + widget.initState?.call(context, _formModel); + + _logSubscription = _logUserProfileOForm.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + + super.initState(); + } + + @override + void didUpdateWidget(covariant UserProfileOFormBuilder oldWidget) { + if (widget.model != oldWidget.model) { + _formModel.updateValue(widget.model); + } + + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + _formModel.form.dispose(); + _logSubscription?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ReactiveUserProfileOForm( + key: ObjectKey(_formModel), + form: _formModel, + // canPop: widget.canPop, + // onPopInvoked: widget.onPopInvoked, + child: ReactiveFormBuilder( + form: () => _formModel.form, + canPop: widget.canPop, + onPopInvoked: widget.onPopInvoked, + builder: (context, formGroup, child) => + widget.builder(context, _formModel, widget.child), + child: widget.child, + ), + ); + } +} + +final _logUserProfileOForm = Logger.detached('UserProfileOForm'); + +class UserProfileOForm implements FormModel { + UserProfileOForm( + this.form, + this.path, + ); + + static const String idControlName = "id"; + + static const String firstNameControlName = "firstName"; + + static const String lastNameControlName = "lastName"; + + static const String homeControlName = "home"; + + static const String officeControlName = "office"; + + final FormGroup form; + + final String? path; + + final Map _disabled = {}; + + String idControlPath() => pathBuilder(idControlName); + + String firstNameControlPath() => pathBuilder(firstNameControlName); + + String lastNameControlPath() => pathBuilder(lastNameControlName); + + String homeControlPath() => pathBuilder(homeControlName); + + String officeControlPath() => pathBuilder(officeControlName); + + String get _idValue => idControl.value as String; + + String get _firstNameValue => firstNameControl.value as String; + + String get _lastNameValue => lastNameControl.value as String; + + AddressOOutput get _homeValue => homeForm.model; + + AddressOOutput? get _officeValue => officeForm.model; + + bool get containsId { + try { + form.control(idControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsFirstName { + try { + form.control(firstNameControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsLastName { + try { + form.control(lastNameControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsHome { + try { + form.control(homeControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsOffice { + try { + form.control(officeControlPath()); + return true; + } catch (e) { + return false; + } + } + + Map get idErrors => idControl.errors; + + Map? get firstNameErrors => firstNameControl.errors; + + Map? get lastNameErrors => lastNameControl.errors; + + Map get homeErrors => homeControl.errors; + + Map? get officeErrors => officeControl?.errors; + + void get idFocus => form.focus(idControlPath()); + + void get firstNameFocus => form.focus(firstNameControlPath()); + + void get lastNameFocus => form.focus(lastNameControlPath()); + + void get homeFocus => form.focus(homeControlPath()); + + void get officeFocus => form.focus(officeControlPath()); + + void firstNameRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsFirstName) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + firstNameControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + firstNameControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void lastNameRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsLastName) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + lastNameControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + lastNameControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void officeRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsOffice) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + officeControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + officeControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void idValueUpdate( + String value, { + bool updateParent = true, + bool emitEvent = true, + }) { + idControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void firstNameValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + firstNameControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void lastNameValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + lastNameControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void homeValueUpdate( + AddressO value, { + bool updateParent = true, + bool emitEvent = true, + }) { + homeControl.updateValue(AddressOForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + } + + void officeValueUpdate( + AddressO? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + officeControl?.updateValue(AddressOForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + } + + void idValuePatch( + String value, { + bool updateParent = true, + bool emitEvent = true, + }) { + idControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void firstNameValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + firstNameControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void lastNameValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + lastNameControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void homeValuePatch( + AddressO value, { + bool updateParent = true, + bool emitEvent = true, + }) { + homeControl.updateValue(AddressOForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + } + + void officeValuePatch( + AddressO? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + officeControl?.updateValue(AddressOForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + } + + void idValueReset( + String value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + idControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void firstNameValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + firstNameControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void lastNameValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + lastNameControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void homeValueReset( + AddressO value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + homeControl.reset( + value: AddressOForm.formElements(value).rawValue, + updateParent: updateParent, + emitEvent: emitEvent); + + void officeValueReset( + AddressO? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + officeControl?.reset( + value: AddressOForm.formElements(value).rawValue, + updateParent: updateParent, + emitEvent: emitEvent); + + FormControl get idControl => + form.control(idControlPath()) as FormControl; + + FormControl get firstNameControl => + form.control(firstNameControlPath()) as FormControl; + + FormControl get lastNameControl => + form.control(lastNameControlPath()) as FormControl; + + FormGroup get homeControl => form.control(homeControlPath()) as FormGroup; + + FormGroup? get officeControl => + containsOffice ? form.control(officeControlPath()) as FormGroup? : null; + + AddressOForm get homeForm => AddressOForm(form, pathBuilder('home')); + + AddressOForm get officeForm => AddressOForm(form, pathBuilder('office')); + + void idSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + idControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + idControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void firstNameSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + firstNameControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + firstNameControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void lastNameSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + lastNameControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + lastNameControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void homeSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + homeControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + homeControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void officeSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + officeControl?.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + officeControl?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + @override + UserProfileOOutput get model { + final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; + + if (!isValid) { + _logUserProfileOForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); + } + return UserProfileOOutput( + id: _idValue, + firstName: _firstNameValue, + lastName: _lastNameValue, + home: _homeValue, + office: _officeValue); + } + + @override + void toggleDisabled({ + bool updateParent = true, + bool emitEvent = true, + }) { + final currentFormInstance = currentForm; + + if (currentFormInstance is! FormGroup) { + return; + } + + if (_disabled.isEmpty) { + currentFormInstance.controls.forEach((key, control) { + _disabled[key] = control.disabled; + }); + + homeForm.toggleDisabled(); + officeForm.toggleDisabled(); + currentForm.markAsDisabled( + updateParent: updateParent, emitEvent: emitEvent); + } else { + homeForm.toggleDisabled(); + officeForm.toggleDisabled(); + currentFormInstance.controls.forEach((key, control) { + if (_disabled[key] == false) { + currentFormInstance.controls[key]?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + + _disabled.remove(key); + }); + } + } + + @override + void submit({ + required void Function(UserProfileOOutput model) onValid, + void Function()? onNotValid, + }) { + currentForm.markAllAsTouched(); + if (currentForm.valid) { + onValid(model); + } else { + _logUserProfileOForm.info('Errors'); + _logUserProfileOForm.info('┗━━ ${form.errors}'); + onNotValid?.call(); + } + } + + @override + bool equalsTo(UserProfileO? other) { + return mapEquals( + this.form.value, + UserProfileOForm.formElements(other).value, + ); + } + + AbstractControl get currentForm { + return path == null ? form : form.control(path!); + } + + @override + void updateValue( + UserProfileO? value, { + bool updateParent = true, + bool emitEvent = true, + }) => + form.updateValue(UserProfileOForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + + @override + void reset({ + UserProfileO? value, + bool updateParent = true, + bool emitEvent = true, + }) => + form.reset( + value: value != null ? formElements(value).rawValue : null, + updateParent: updateParent, + emitEvent: emitEvent); + + String pathBuilder(String? pathItem) => + [path, pathItem].whereType().join("."); + + static FormGroup formElements(UserProfileO? userProfileO) => FormGroup({ + idControlName: FormControl( + value: userProfileO?.id, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + firstNameControlName: FormControl( + value: userProfileO?.firstName, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + lastNameControlName: FormControl( + value: userProfileO?.lastName, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + homeControlName: AddressOForm.formElements(userProfileO?.home), + officeControlName: AddressOForm.formElements(userProfileO?.office) + }, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false); +} + +final _logAddressOForm = Logger.detached('AddressOForm'); + +class AddressOForm implements FormModel { + AddressOForm( + this.form, + this.path, + ); + + static const String streetControlName = "street"; + + static const String cityControlName = "city"; + + static const String zipControlName = "zip"; + + final FormGroup form; + + final String? path; + + final Map _disabled = {}; + + String streetControlPath() => pathBuilder(streetControlName); + + String cityControlPath() => pathBuilder(cityControlName); + + String zipControlPath() => pathBuilder(zipControlName); + + String? get _streetValue => streetControl?.value; + + String get _cityValue => cityControl.value as String; + + String? get _zipValue => zipControl?.value; + + bool get containsStreet { + try { + form.control(streetControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsCity { + try { + form.control(cityControlPath()); + return true; + } catch (e) { + return false; + } + } + + bool get containsZip { + try { + form.control(zipControlPath()); + return true; + } catch (e) { + return false; + } + } + + Map? get streetErrors => streetControl?.errors; + + Map? get cityErrors => cityControl.errors; + + Map? get zipErrors => zipControl?.errors; + + void get streetFocus => form.focus(streetControlPath()); + + void get cityFocus => form.focus(cityControlPath()); + + void get zipFocus => form.focus(zipControlPath()); + + void streetRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsStreet) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + streetControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + streetControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void cityRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsCity) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + cityControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + cityControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void zipRemove({ + bool updateParent = true, + bool emitEvent = true, + }) { + if (containsZip) { + final controlPath = path; + if (controlPath == null) { + form.removeControl( + zipControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + final formGroup = form.control(controlPath); + + if (formGroup is FormGroup) { + formGroup.removeControl( + zipControlName, + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + } + } + + void streetValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + streetControl?.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void cityValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + cityControl.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void zipValueUpdate( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + zipControl?.updateValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void streetValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + streetControl?.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void cityValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + cityControl.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void zipValuePatch( + String? value, { + bool updateParent = true, + bool emitEvent = true, + }) { + zipControl?.patchValue(value, + updateParent: updateParent, emitEvent: emitEvent); + } + + void streetValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + streetControl?.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void cityValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + cityControl.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + void zipValueReset( + String? value, { + bool updateParent = true, + bool emitEvent = true, + bool removeFocus = false, + bool? disabled, + }) => + zipControl?.reset( + value: value, updateParent: updateParent, emitEvent: emitEvent); + + FormControl? get streetControl => containsStreet + ? form.control(streetControlPath()) as FormControl? + : null; + + FormControl get cityControl => + form.control(cityControlPath()) as FormControl; + + FormControl? get zipControl => containsZip + ? form.control(zipControlPath()) as FormControl? + : null; + + void streetSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + streetControl?.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + streetControl?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void citySetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + cityControl.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + cityControl.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + void zipSetDisabled( + bool disabled, { + bool updateParent = true, + bool emitEvent = true, + }) { + if (disabled) { + zipControl?.markAsDisabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } else { + zipControl?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + } + + @override + AddressOOutput get model { + final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; + + if (!isValid) { + _logAddressOForm.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); + } + return AddressOOutput( + street: _streetValue, city: _cityValue, zip: _zipValue); + } + + @override + void toggleDisabled({ + bool updateParent = true, + bool emitEvent = true, + }) { + final currentFormInstance = currentForm; + + if (currentFormInstance is! FormGroup) { + return; + } + + if (_disabled.isEmpty) { + currentFormInstance.controls.forEach((key, control) { + _disabled[key] = control.disabled; + }); + + currentForm.markAsDisabled( + updateParent: updateParent, emitEvent: emitEvent); + } else { + currentFormInstance.controls.forEach((key, control) { + if (_disabled[key] == false) { + currentFormInstance.controls[key]?.markAsEnabled( + updateParent: updateParent, + emitEvent: emitEvent, + ); + } + + _disabled.remove(key); + }); + } + } + + @override + void submit({ + required void Function(AddressOOutput model) onValid, + void Function()? onNotValid, + }) { + currentForm.markAllAsTouched(); + if (currentForm.valid) { + onValid(model); + } else { + _logAddressOForm.info('Errors'); + _logAddressOForm.info('┗━━ ${form.errors}'); + onNotValid?.call(); + } + } + + @override + bool equalsTo(AddressO? other) { + return mapEquals( + this.form.value, + AddressOForm.formElements(other).value, + ); + } + + AbstractControl get currentForm { + return path == null ? form : form.control(path!); + } + + @override + void updateValue( + AddressO? value, { + bool updateParent = true, + bool emitEvent = true, + }) => + form.updateValue(AddressOForm.formElements(value).rawValue, + updateParent: updateParent, emitEvent: emitEvent); + + @override + void reset({ + AddressO? value, + bool updateParent = true, + bool emitEvent = true, + }) => + form.reset( + value: value != null ? formElements(value).rawValue : null, + updateParent: updateParent, + emitEvent: emitEvent); + + String pathBuilder(String? pathItem) => + [path, pathItem].whereType().join("."); + + static FormGroup formElements(AddressO? addressO) => FormGroup({ + streetControlName: FormControl( + value: addressO?.street, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + cityControlName: FormControl( + value: addressO?.city, + validators: [RequiredValidator()], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false), + zipControlName: FormControl( + value: addressO?.zip, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false, + touched: false) + }, + validators: [], + asyncValidators: [], + asyncValidatorsDebounceTime: 250, + disabled: false); +} + +@Rf() +class UserProfileOOutput { + final String id; + final String firstName; + final String lastName; + final AddressOOutput home; + final AddressOOutput? office; + UserProfileOOutput( + {required this.id, + @RfControl(validators: [RequiredValidator()]) required this.firstName, + @RfControl(validators: [RequiredValidator()]) required this.lastName, + required this.home, + this.office}); +} + +@RfGroup() +class AddressOOutput { + final String? street; + final String city; + final String? zip; + AddressOOutput( + {@RfControl() this.street, + @RfControl(validators: [RequiredValidator()]) required this.city, + @RfControl() this.zip}); +} + +class ReactiveUserProfileOFormArrayBuilder< + ReactiveUserProfileOFormArrayBuilderT> extends StatelessWidget { + const ReactiveUserProfileOFormArrayBuilder({ + Key? key, + this.control, + this.formControl, + this.builder, + required this.itemBuilder, + }) : assert(control != null || formControl != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final FormArray? formControl; + + final FormArray? Function( + UserProfileOForm formModel)? control; + + final Widget Function(BuildContext context, List itemList, + UserProfileOForm formModel)? builder; + + final Widget Function( + BuildContext context, + int i, + ReactiveUserProfileOFormArrayBuilderT? item, + UserProfileOForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveUserProfileOForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + return ReactiveFormArray( + formArray: formControl ?? control?.call(formModel), + builder: (context, formArray, child) { + final values = formArray.controls.map((e) => e.value).toList(); + final itemList = values + .asMap() + .map((i, item) { + return MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + ); + }) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} + +class ReactiveUserProfileOFormFormGroupArrayBuilder< + ReactiveUserProfileOFormFormGroupArrayBuilderT> extends StatelessWidget { + const ReactiveUserProfileOFormFormGroupArrayBuilder({ + Key? key, + this.extended, + this.getExtended, + this.builder, + required this.itemBuilder, + }) : assert(extended != null || getExtended != null, + "You have to specify `control` or `formControl`!"), + super(key: key); + + final ExtendedControl?>, + List>? extended; + + final ExtendedControl?>, + List> + Function(UserProfileOForm formModel)? getExtended; + + final Widget Function(BuildContext context, List itemList, + UserProfileOForm formModel)? builder; + + final Widget Function( + BuildContext context, + int i, + ReactiveUserProfileOFormFormGroupArrayBuilderT? item, + UserProfileOForm formModel) itemBuilder; + + @override + Widget build(BuildContext context) { + final formModel = ReactiveUserProfileOForm.of(context); + + if (formModel == null) { + throw FormControlParentNotFoundException(this); + } + + final value = (extended ?? getExtended?.call(formModel))!; + + return StreamBuilder?>?>( + stream: value.control.valueChanges, + builder: (context, snapshot) { + final itemList = (value.value() ?? + []) + .asMap() + .map((i, item) => MapEntry( + i, + itemBuilder( + context, + i, + item, + formModel, + ), + )) + .values + .toList(); + + return builder?.call( + context, + itemList, + formModel, + ) ?? + Column(children: itemList); + }, + ); + } +} diff --git a/packages/reactive_forms_generator/example/lib/drawer.dart b/packages/reactive_forms_generator/example/lib/drawer.dart index c005bae4..18ad93bd 100644 --- a/packages/reactive_forms_generator/example/lib/drawer.dart +++ b/packages/reactive_forms_generator/example/lib/drawer.dart @@ -17,6 +17,12 @@ class AppDrawer extends StatelessWidget { Routes.login, ), ), + ListTile( + title: const Text('LoginOutput(beta)'), + onTap: () => Navigator.of(context).pushReplacementNamed( + Routes.loginOutput, + ), + ), ListTile( title: const Text('Annotateless'), onTap: () => Navigator.of(context).pushReplacementNamed( diff --git a/packages/reactive_forms_generator/example/lib/main.dart b/packages/reactive_forms_generator/example/lib/main.dart index fd3b9f75..f908b48d 100644 --- a/packages/reactive_forms_generator/example/lib/main.dart +++ b/packages/reactive_forms_generator/example/lib/main.dart @@ -7,6 +7,7 @@ import 'package:example/docs/freezed/freezed_form.dart'; import 'package:example/docs/generic/generic_form.dart'; import 'package:example/docs/group/group_form.dart'; import 'package:example/docs/login/login_form.dart'; +import 'package:example/docs/login/login_output_form.dart'; import 'package:example/docs/login_extended/login_extended_form.dart'; import 'package:example/docs/login_extended_nullable/login_extended_nullable_form.dart'; import 'package:example/docs/mailing_list/mailing_list_form.dart'; @@ -35,6 +36,7 @@ class MyApp extends StatelessWidget { routes: { Routes.loginExtended: (_) => const LoginExtendedFormWidget(), Routes.login: (_) => const LoginFormWidget(), + Routes.loginOutput: (_) => const LoginOutputFormWidget(), Routes.annotateless: (_) => const AnnotatelessFormWidget(), Routes.mailingList: (_) => const MailingListFormWidget(), Routes.userProfile: (_) => const UserProfileFormWidget(), @@ -52,7 +54,7 @@ class MyApp extends StatelessWidget { Routes.modelImplements: (_) => const ModelImplementsWidget(), Routes.nested: (_) => const NestedFormWidget(), }, - home: const LoginFormWidget(), + home: const UrlListForm(), ); } } @@ -62,6 +64,8 @@ class Routes { static const login = '/login'; + static const loginOutput = '/login-output'; + static const annotateless = '/annotateless'; static const mailingList = '/mailing-list'; diff --git a/packages/reactive_forms_generator/example/macos/.gitignore b/packages/reactive_forms_generator/example/macos/.gitignore deleted file mode 100644 index d2fd3772..00000000 --- a/packages/reactive_forms_generator/example/macos/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -# Flutter-related -**/Flutter/ephemeral/ -**/Pods/ - -# Xcode-related -**/xcuserdata/ diff --git a/packages/reactive_forms_generator/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/reactive_forms_generator/example/macos/Flutter/Flutter-Debug.xcconfig deleted file mode 100644 index c2efd0b6..00000000 --- a/packages/reactive_forms_generator/example/macos/Flutter/Flutter-Debug.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/reactive_forms_generator/example/macos/Flutter/Flutter-Release.xcconfig b/packages/reactive_forms_generator/example/macos/Flutter/Flutter-Release.xcconfig deleted file mode 100644 index c2efd0b6..00000000 --- a/packages/reactive_forms_generator/example/macos/Flutter/Flutter-Release.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/reactive_forms_generator/example/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/reactive_forms_generator/example/macos/Flutter/GeneratedPluginRegistrant.swift deleted file mode 100644 index cccf817a..00000000 --- a/packages/reactive_forms_generator/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ /dev/null @@ -1,10 +0,0 @@ -// -// Generated file. Do not edit. -// - -import FlutterMacOS -import Foundation - - -func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { -} diff --git a/packages/reactive_forms_generator/example/macos/Runner.xcodeproj/project.pbxproj b/packages/reactive_forms_generator/example/macos/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index cc89c878..00000000 --- a/packages/reactive_forms_generator/example/macos/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,572 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 51; - objects = { - -/* Begin PBXAggregateTarget section */ - 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { - isa = PBXAggregateTarget; - buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; - buildPhases = ( - 33CC111E2044C6BF0003C045 /* ShellScript */, - ); - dependencies = ( - ); - name = "Flutter Assemble"; - productName = FLX; - }; -/* End PBXAggregateTarget section */ - -/* Begin PBXBuildFile section */ - 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; - 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; - 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; - 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; - 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 33CC10E52044A3C60003C045 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 33CC111A2044C6BA0003C045; - remoteInfo = FLX; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 33CC110E2044A8840003C045 /* Bundle Framework */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Bundle Framework"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; - 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; - 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; - 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; - 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; - 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; - 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; - 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; - 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; - 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; - 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 33CC10EA2044A3C60003C045 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 33BA886A226E78AF003329D5 /* Configs */ = { - isa = PBXGroup; - children = ( - 33E5194F232828860026EE4D /* AppInfo.xcconfig */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, - ); - path = Configs; - sourceTree = ""; - }; - 33CC10E42044A3C60003C045 = { - isa = PBXGroup; - children = ( - 33FAB671232836740065AC1E /* Runner */, - 33CEB47122A05771004F2AC0 /* Flutter */, - 33CC10EE2044A3C60003C045 /* Products */, - D73912EC22F37F3D000D13A0 /* Frameworks */, - ); - sourceTree = ""; - }; - 33CC10EE2044A3C60003C045 /* Products */ = { - isa = PBXGroup; - children = ( - 33CC10ED2044A3C60003C045 /* example.app */, - ); - name = Products; - sourceTree = ""; - }; - 33CC11242044D66E0003C045 /* Resources */ = { - isa = PBXGroup; - children = ( - 33CC10F22044A3C60003C045 /* Assets.xcassets */, - 33CC10F42044A3C60003C045 /* MainMenu.xib */, - 33CC10F72044A3C60003C045 /* Info.plist */, - ); - name = Resources; - path = ..; - sourceTree = ""; - }; - 33CEB47122A05771004F2AC0 /* Flutter */ = { - isa = PBXGroup; - children = ( - 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, - 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, - 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, - 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, - ); - path = Flutter; - sourceTree = ""; - }; - 33FAB671232836740065AC1E /* Runner */ = { - isa = PBXGroup; - children = ( - 33CC10F02044A3C60003C045 /* AppDelegate.swift */, - 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, - 33E51913231747F40026EE4D /* DebugProfile.entitlements */, - 33E51914231749380026EE4D /* Release.entitlements */, - 33CC11242044D66E0003C045 /* Resources */, - 33BA886A226E78AF003329D5 /* Configs */, - ); - path = Runner; - sourceTree = ""; - }; - D73912EC22F37F3D000D13A0 /* Frameworks */ = { - isa = PBXGroup; - children = ( - ); - name = Frameworks; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 33CC10EC2044A3C60003C045 /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - 33CC10E92044A3C60003C045 /* Sources */, - 33CC10EA2044A3C60003C045 /* Frameworks */, - 33CC10EB2044A3C60003C045 /* Resources */, - 33CC110E2044A8840003C045 /* Bundle Framework */, - 3399D490228B24CF009A79C7 /* ShellScript */, - ); - buildRules = ( - ); - dependencies = ( - 33CC11202044C79F0003C045 /* PBXTargetDependency */, - ); - name = Runner; - productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* example.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 33CC10E52044A3C60003C045 /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0930; - ORGANIZATIONNAME = ""; - TargetAttributes = { - 33CC10EC2044A3C60003C045 = { - CreatedOnToolsVersion = 9.2; - LastSwiftMigration = 1100; - ProvisioningStyle = Automatic; - SystemCapabilities = { - com.apple.Sandbox = { - enabled = 1; - }; - }; - }; - 33CC111A2044C6BA0003C045 = { - CreatedOnToolsVersion = 9.2; - ProvisioningStyle = Manual; - }; - }; - }; - buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 33CC10E42044A3C60003C045; - productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 33CC10EC2044A3C60003C045 /* Runner */, - 33CC111A2044C6BA0003C045 /* Flutter Assemble */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 33CC10EB2044A3C60003C045 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, - 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3399D490228B24CF009A79C7 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; - }; - 33CC111E2044C6BF0003C045 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - Flutter/ephemeral/FlutterInputs.xcfilelist, - ); - inputPaths = ( - Flutter/ephemeral/tripwire, - ); - outputFileListPaths = ( - Flutter/ephemeral/FlutterOutputs.xcfilelist, - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 33CC10E92044A3C60003C045 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, - 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, - 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; - targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { - isa = PBXVariantGroup; - children = ( - 33CC10F52044A3C60003C045 /* Base */, - ); - name = MainMenu.xib; - path = Runner; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 338D0CE9231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - }; - name = Profile; - }; - 338D0CEA231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; - }; - name = Profile; - }; - 338D0CEB231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Manual; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Profile; - }; - 33CC10F92044A3C60003C045 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - 33CC10FA2044A3C60003C045 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - }; - name = Release; - }; - 33CC10FC2044A3C60003C045 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - 33CC10FD2044A3C60003C045 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; - 33CC111C2044C6BA0003C045 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Manual; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 33CC111D2044C6BA0003C045 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC10F92044A3C60003C045 /* Debug */, - 33CC10FA2044A3C60003C045 /* Release */, - 338D0CE9231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC10FC2044A3C60003C045 /* Debug */, - 33CC10FD2044A3C60003C045 /* Release */, - 338D0CEA231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC111C2044C6BA0003C045 /* Debug */, - 33CC111D2044C6BA0003C045 /* Release */, - 338D0CEB231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 33CC10E52044A3C60003C045 /* Project object */; -} diff --git a/packages/reactive_forms_generator/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/reactive_forms_generator/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d98100..00000000 --- a/packages/reactive_forms_generator/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/packages/reactive_forms_generator/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/reactive_forms_generator/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100644 index ae8ff59d..00000000 --- a/packages/reactive_forms_generator/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/reactive_forms_generator/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/reactive_forms_generator/example/macos/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 1d526a16..00000000 --- a/packages/reactive_forms_generator/example/macos/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/packages/reactive_forms_generator/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/reactive_forms_generator/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d98100..00000000 --- a/packages/reactive_forms_generator/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/packages/reactive_forms_generator/example/macos/Runner/AppDelegate.swift b/packages/reactive_forms_generator/example/macos/Runner/AppDelegate.swift deleted file mode 100644 index d53ef643..00000000 --- a/packages/reactive_forms_generator/example/macos/Runner/AppDelegate.swift +++ /dev/null @@ -1,9 +0,0 @@ -import Cocoa -import FlutterMacOS - -@NSApplicationMain -class AppDelegate: FlutterAppDelegate { - override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { - return true - } -} diff --git a/packages/reactive_forms_generator/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/reactive_forms_generator/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index a2ec33f1..00000000 --- a/packages/reactive_forms_generator/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "images" : [ - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_16.png", - "scale" : "1x" - }, - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "2x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "1x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_64.png", - "scale" : "2x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_128.png", - "scale" : "1x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "2x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "1x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "2x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "1x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_1024.png", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/packages/reactive_forms_generator/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/reactive_forms_generator/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png deleted file mode 100644 index 3c4935a7..00000000 Binary files a/packages/reactive_forms_generator/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and /dev/null differ diff --git a/packages/reactive_forms_generator/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/reactive_forms_generator/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png deleted file mode 100644 index ed4cc164..00000000 Binary files a/packages/reactive_forms_generator/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and /dev/null differ diff --git a/packages/reactive_forms_generator/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/reactive_forms_generator/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png deleted file mode 100644 index 483be613..00000000 Binary files a/packages/reactive_forms_generator/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and /dev/null differ diff --git a/packages/reactive_forms_generator/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/reactive_forms_generator/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png deleted file mode 100644 index bcbf36df..00000000 Binary files a/packages/reactive_forms_generator/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and /dev/null differ diff --git a/packages/reactive_forms_generator/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/reactive_forms_generator/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png deleted file mode 100644 index 9c0a6528..00000000 Binary files a/packages/reactive_forms_generator/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and /dev/null differ diff --git a/packages/reactive_forms_generator/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/reactive_forms_generator/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png deleted file mode 100644 index e71a7261..00000000 Binary files a/packages/reactive_forms_generator/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and /dev/null differ diff --git a/packages/reactive_forms_generator/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/reactive_forms_generator/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png deleted file mode 100644 index 8a31fe2d..00000000 Binary files a/packages/reactive_forms_generator/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and /dev/null differ diff --git a/packages/reactive_forms_generator/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/reactive_forms_generator/example/macos/Runner/Base.lproj/MainMenu.xib deleted file mode 100644 index 537341ab..00000000 --- a/packages/reactive_forms_generator/example/macos/Runner/Base.lproj/MainMenu.xib +++ /dev/null @@ -1,339 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/reactive_forms_generator/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/reactive_forms_generator/example/macos/Runner/Configs/AppInfo.xcconfig deleted file mode 100644 index cf9be60c..00000000 --- a/packages/reactive_forms_generator/example/macos/Runner/Configs/AppInfo.xcconfig +++ /dev/null @@ -1,14 +0,0 @@ -// Application-level settings for the Runner target. -// -// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the -// future. If not, the values below would default to using the project name when this becomes a -// 'flutter create' template. - -// The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = example - -// The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.example.example - -// The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. diff --git a/packages/reactive_forms_generator/example/macos/Runner/Configs/Debug.xcconfig b/packages/reactive_forms_generator/example/macos/Runner/Configs/Debug.xcconfig deleted file mode 100644 index 36b0fd94..00000000 --- a/packages/reactive_forms_generator/example/macos/Runner/Configs/Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include "../../Flutter/Flutter-Debug.xcconfig" -#include "Warnings.xcconfig" diff --git a/packages/reactive_forms_generator/example/macos/Runner/Configs/Release.xcconfig b/packages/reactive_forms_generator/example/macos/Runner/Configs/Release.xcconfig deleted file mode 100644 index dff4f495..00000000 --- a/packages/reactive_forms_generator/example/macos/Runner/Configs/Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include "../../Flutter/Flutter-Release.xcconfig" -#include "Warnings.xcconfig" diff --git a/packages/reactive_forms_generator/example/macos/Runner/Configs/Warnings.xcconfig b/packages/reactive_forms_generator/example/macos/Runner/Configs/Warnings.xcconfig deleted file mode 100644 index 42bcbf47..00000000 --- a/packages/reactive_forms_generator/example/macos/Runner/Configs/Warnings.xcconfig +++ /dev/null @@ -1,13 +0,0 @@ -WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings -GCC_WARN_UNDECLARED_SELECTOR = YES -CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES -CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE -CLANG_WARN__DUPLICATE_METHOD_MATCH = YES -CLANG_WARN_PRAGMA_PACK = YES -CLANG_WARN_STRICT_PROTOTYPES = YES -CLANG_WARN_COMMA = YES -GCC_WARN_STRICT_SELECTOR_MATCH = YES -CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES -CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES -GCC_WARN_SHADOW = YES -CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/packages/reactive_forms_generator/example/macos/Runner/DebugProfile.entitlements b/packages/reactive_forms_generator/example/macos/Runner/DebugProfile.entitlements deleted file mode 100644 index dddb8a30..00000000 --- a/packages/reactive_forms_generator/example/macos/Runner/DebugProfile.entitlements +++ /dev/null @@ -1,12 +0,0 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.cs.allow-jit - - com.apple.security.network.server - - - diff --git a/packages/reactive_forms_generator/example/macos/Runner/Info.plist b/packages/reactive_forms_generator/example/macos/Runner/Info.plist deleted file mode 100644 index 4789daa6..00000000 --- a/packages/reactive_forms_generator/example/macos/Runner/Info.plist +++ /dev/null @@ -1,32 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIconFile - - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) - NSHumanReadableCopyright - $(PRODUCT_COPYRIGHT) - NSMainNibFile - MainMenu - NSPrincipalClass - NSApplication - - diff --git a/packages/reactive_forms_generator/example/macos/Runner/MainFlutterWindow.swift b/packages/reactive_forms_generator/example/macos/Runner/MainFlutterWindow.swift deleted file mode 100644 index 2722837e..00000000 --- a/packages/reactive_forms_generator/example/macos/Runner/MainFlutterWindow.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Cocoa -import FlutterMacOS - -class MainFlutterWindow: NSWindow { - override func awakeFromNib() { - let flutterViewController = FlutterViewController.init() - let windowFrame = self.frame - self.contentViewController = flutterViewController - self.setFrame(windowFrame, display: true) - - RegisterGeneratedPlugins(registry: flutterViewController) - - super.awakeFromNib() - } -} diff --git a/packages/reactive_forms_generator/example/macos/Runner/Release.entitlements b/packages/reactive_forms_generator/example/macos/Runner/Release.entitlements deleted file mode 100644 index 852fa1a4..00000000 --- a/packages/reactive_forms_generator/example/macos/Runner/Release.entitlements +++ /dev/null @@ -1,8 +0,0 @@ - - - - - com.apple.security.app-sandbox - - - diff --git a/packages/reactive_forms_generator/lib/builder.dart b/packages/reactive_forms_generator/lib/builder.dart index 9f38de57..e95eea04 100644 --- a/packages/reactive_forms_generator/lib/builder.dart +++ b/packages/reactive_forms_generator/lib/builder.dart @@ -12,7 +12,7 @@ Builder reactiveFormsGenerator(BuilderOptions options) { // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint - // ignore_for_file: + // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark ''', options: options, ); diff --git a/packages/reactive_forms_generator/lib/reactive_forms_generator.dart b/packages/reactive_forms_generator/lib/reactive_forms_generator.dart index 7f5fa56b..66ab0ffc 100644 --- a/packages/reactive_forms_generator/lib/reactive_forms_generator.dart +++ b/packages/reactive_forms_generator/lib/reactive_forms_generator.dart @@ -1,3 +1,5 @@ +// ignore_for_file: implementation_imports + library reactive_forms_generator; import 'dart:async'; @@ -9,8 +11,6 @@ import 'package:dart_style/dart_style.dart'; import 'package:reactive_forms_generator/src/extensions.dart'; import 'package:reactive_forms_generator/src/types.dart'; import 'package:source_gen/source_gen.dart'; - -// ignore: implementation_imports import 'package:source_gen/src/output_helpers.dart'; import 'src/library_builder.dart'; @@ -31,7 +31,7 @@ class ReactiveFormsGenerator extends Generator { for (var annotatedElement in library.rfAnnotated) { specList.addAll( - generateForAnnotatedElement( + await generateForAnnotatedElement( annotatedElement.element, annotatedElement.annotation, buildStep, @@ -42,11 +42,25 @@ class ReactiveFormsGenerator extends Generator { final lib = Library( (b) => b ..body.addAll(specList.mergeDuplicatesBy( - (i) => i is Class ? i.name : i, - (a, b) => a, + (i) { + // if (i is StaticCode) { + // print(i.code.hashCode); + // print(i.code); + // } + return switch (i) { + final Class _ => i.name, + final StaticCode _ => i.code.hashCode, + _ => i, + }; + }, + (a, b) { + return a; + }, )), ); + // final x = lib.accept(emitter).toString(); + final generatedValue = DartFormatter().format(lib.accept(emitter).toString()); @@ -59,17 +73,28 @@ class ReactiveFormsGenerator extends Generator { return values.join('\n\n'); } - List generateForAnnotatedElement( + Future> generateForAnnotatedElement( Element element, ConstantReader annotation, BuildStep buildStep, - ) { + ) async { throwIf( element is! ClassElement, '${element.name} is not a class element', element: element, ); - return generateLibrary(element as ClassElement); + final astElement = element.enclosingElement; + + final ast = await buildStep.resolver.astNodeFor( + astElement ?? element, + resolve: true, + ); + + if (ast == null) { + throw InvalidGenerationSourceError('Ast not found', element: element); + } + + return generateLibrary(element as ClassElement, ast); } } diff --git a/packages/reactive_forms_generator/lib/src/extensions.dart b/packages/reactive_forms_generator/lib/src/extensions.dart index 131f53df..9252735d 100644 --- a/packages/reactive_forms_generator/lib/src/extensions.dart +++ b/packages/reactive_forms_generator/lib/src/extensions.dart @@ -16,6 +16,9 @@ extension ConstructorElementExt on ConstructorElement { extension ClassElementExt on ClassElement { String get fullTypeName => thisType.toString(); + // String get fullTypeNameOutput => + // '${thisType.toString()}${output ? 'Output' : ''}'; + String get generics { final generics = genericTypes.map((e) => e.symbol).join(', '); diff --git a/packages/reactive_forms_generator/lib/src/form_elements/form_array_generator.dart b/packages/reactive_forms_generator/lib/src/form_elements/form_array_generator.dart index 4b6c8d5b..66d3a802 100644 --- a/packages/reactive_forms_generator/lib/src/form_elements/form_array_generator.dart +++ b/packages/reactive_forms_generator/lib/src/form_elements/form_array_generator.dart @@ -89,5 +89,5 @@ class FormArrayGenerator extends FormElementGenerator { } @override - List get typeChecker => [formArrayChecker, formArrayCheckerRf]; + TypeChecker get typeChecker => formArrayChecker; } diff --git a/packages/reactive_forms_generator/lib/src/form_elements/form_control_generator.dart b/packages/reactive_forms_generator/lib/src/form_elements/form_control_generator.dart index 947ed668..b3842b26 100644 --- a/packages/reactive_forms_generator/lib/src/form_elements/form_control_generator.dart +++ b/packages/reactive_forms_generator/lib/src/form_elements/form_control_generator.dart @@ -35,6 +35,5 @@ class FormControlGenerator extends FormElementGenerator { } @override - List get typeChecker => - [formControlChecker, formControlCheckerRf]; + TypeChecker get typeChecker => formControlChecker; } diff --git a/packages/reactive_forms_generator/lib/src/form_elements/form_element_generator.dart b/packages/reactive_forms_generator/lib/src/form_elements/form_element_generator.dart index e10131c2..4cef573f 100644 --- a/packages/reactive_forms_generator/lib/src/form_elements/form_element_generator.dart +++ b/packages/reactive_forms_generator/lib/src/form_elements/form_element_generator.dart @@ -4,16 +4,17 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/nullability_suffix.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:reactive_forms_generator/src/extensions.dart'; +import 'package:reactive_forms_generator/src/types.dart'; import 'package:source_gen/source_gen.dart'; import 'package:recase/recase.dart'; -import 'package:analyzer/src/dart/element/element.dart'; -import 'package:analyzer/src/dart/ast/ast.dart'; abstract class FormElementGenerator { final ClassElement root; final ParameterElement field; final DartType? type; + static const String validatorKey = 'validators'; + FormElementGenerator(this.root, this.field, this.type); String get value { @@ -38,15 +39,12 @@ abstract class FormElementGenerator { return name; } - List get typeChecker; - - TypeChecker? get _typeChecker => typeChecker - .where((e) => e.hasAnnotationOfExact(fieldElement)) - .firstOrNull; + TypeChecker get typeChecker; - DartObject? get annotation => _typeChecker?.firstAnnotationOf(fieldElement); + DartObject? get annotation => typeChecker.firstAnnotationOf(fieldElement); - String get itemValidators => annotationParams['itemValidators'] ?? '[]'; + String get itemValidators => + fieldElement.annotationParams(typeChecker)['itemValidators'] ?? '[]'; String? get annotationType => (annotation?.type as ParameterizedType?)?.typeArguments.first.toString(); @@ -62,62 +60,48 @@ abstract class FormElementGenerator { ConstructorElement get constructorElement => fieldElement.enclosingElement as ConstructorElement; - Map get annotationParams { - final result = {}; - try { - if (annotation != null) { - for (final meta in fieldElement.metadata) { - final obj = meta.computeConstantValue()!; - - if (_typeChecker?.isExactlyType(obj.type!) == true) { - final argumentList = (meta as ElementAnnotationImpl) - .annotationAst - .arguments as ArgumentListImpl; - for (var argument in argumentList.arguments) { - final argumentNamedExpression = argument as NamedExpressionImpl; - result.addEntries( - [ - MapEntry( - argumentNamedExpression.name.label.toSource(), - argumentNamedExpression.expression.toSource(), - ), - ], - ); - } - } - } - } - - return result; - } catch (e) { - return result; - } - } - - // String? param(String name) { - // final index = annotationParams - // .indexWhere((e) => e.startsWith(name) || e.endsWith(name)); - // if (index != -1) { - // final paramItem = annotationParams[index + 1]; - // final regExp = RegExp( - // r'(?\[[\s\S]*\])', - // multiLine: true, - // caseSensitive: true, - // ); + // Map get annotationParams { + // final result = {}; + // try { + // if (annotation != null) { + // for (final meta in fieldElement.metadata) { + // final obj = meta.computeConstantValue()!; // - // final match = regExp.firstMatch(paramItem)?.namedGroup('param'); + // if (typeChecker.isExactlyType(obj.type!) == true) { + // final argumentList = (meta as ElementAnnotationImpl) + // .annotationAst + // .arguments as ArgumentListImpl; + // for (var argument in argumentList.arguments) { + // final argumentNamedExpression = argument as NamedExpressionImpl; + // result.addEntries( + // [ + // MapEntry( + // argumentNamedExpression.name.label.toSource(), + // argumentNamedExpression.expression.toSource(), + // ), + // ], + // ); + // } + // } + // } + // } // - // return match; + // return result; + // } catch (e) { + // return result; // } - // return null; // } - String get validators => annotationParams['validators'] ?? '[]'; + String get validators => + fieldElement + .annotationParams(typeChecker)[FormElementGenerator.validatorKey] ?? + '[]'; String get itemAsyncValidators => - annotationParams['itemAsyncValidators'] ?? '[]'; + fieldElement.annotationParams(typeChecker)['itemAsyncValidators'] ?? '[]'; - String get asyncValidators => annotationParams['asyncValidators'] ?? '[]'; + String get asyncValidators => + fieldElement.annotationParams(typeChecker)['asyncValidators'] ?? '[]'; int get asyncValidatorsDebounceTime => annotation?.getField('asyncValidatorsDebounceTime')?.toIntValue() ?? 250; diff --git a/packages/reactive_forms_generator/lib/src/form_elements/form_group_generator.dart b/packages/reactive_forms_generator/lib/src/form_elements/form_group_generator.dart index 4aa6a829..dfaec123 100644 --- a/packages/reactive_forms_generator/lib/src/form_elements/form_group_generator.dart +++ b/packages/reactive_forms_generator/lib/src/form_elements/form_group_generator.dart @@ -98,5 +98,5 @@ class FormGroupGenerator extends FormElementGenerator { } @override - List get typeChecker => [formGroupChecker, formGroupCheckerRf]; + TypeChecker get typeChecker => formGroupChecker; } diff --git a/packages/reactive_forms_generator/lib/src/form_generator.dart b/packages/reactive_forms_generator/lib/src/form_generator.dart index 5c984225..230cdd7e 100644 --- a/packages/reactive_forms_generator/lib/src/form_generator.dart +++ b/packages/reactive_forms_generator/lib/src/form_generator.dart @@ -1,19 +1,19 @@ +// ignore_for_file: implementation_imports +import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/nullability_suffix.dart'; import 'package:analyzer/dart/element/type.dart'; - -// ignore: implementation_imports import 'package:analyzer/src/dart/element/element.dart' as e; - -// ignore: implementation_imports import 'package:analyzer/src/dart/element/type.dart' as t; - -// ignore: implementation_imports +import 'package:analyzer/src/dart/ast/ast.dart' as u; import 'package:analyzer/src/generated/utilities_dart.dart' as u; import 'package:code_builder/code_builder.dart'; import 'package:reactive_forms_generator/src/extensions.dart'; import 'package:reactive_forms_generator/src/form_elements/form_array_generator.dart'; import 'package:reactive_forms_generator/src/form_elements/form_group_generator.dart'; +import 'package:reactive_forms_generator/src/output/helpers.dart'; +import 'package:reactive_forms_generator/src/output/rf_annotation_arguments_visitor.dart'; +import 'package:reactive_forms_generator/src/output/rf_paramater_visitor.dart'; import 'package:reactive_forms_generator/src/reactive_forms/reactive_form_update_value_method.dart'; import 'package:reactive_forms_generator/src/reactive_forms/reactive_forms_clear_method.dart'; import 'package:reactive_forms_generator/src/reactive_forms/reactive_forms_insert_method.dart'; @@ -46,6 +46,8 @@ class FormGenerator { final DartType? type; + final u.AstNode ast; + final Map formGroupGenerators = {}; final Map nestedFormGroupGenerators = {}; @@ -58,12 +60,22 @@ class FormGenerator { return element.name; } - FormGenerator(this.root, this.element, this.type) { + // bool get hasOutput { + // if (element.hasRfAnnotation && root == element) { + // final annotation = element.rfAnnotation; + // return annotation?.getField('output')?.toBoolValue() ?? false; + // } + // + // return false; + // } + + FormGenerator(this.root, this.element, this.type, this.ast) { for (var e in formGroups) { formGroupGenerators[e.name] = FormGenerator( root, e.type.element! as ClassElement, e.type, + ast, ); } @@ -78,6 +90,7 @@ class FormGenerator { root, typeParameter.element! as ClassElement, e.type, + ast, ); } } @@ -107,6 +120,8 @@ class FormGenerator { String get className => '${baseName}Form'; + String get log => '_log$className'; + String get classNameFull { return '$className${element.generics}'; } @@ -206,7 +221,11 @@ class FormGenerator { final type = field.typeParameter.getDisplayString(withNullability: false); final formGroupGenerator = FormGenerator( - root, field.typeParameter.element as ClassElement, field.typeParameter); + root, + field.typeParameter.element as ClassElement, + field.typeParameter, + ast, + ); return Method( (b) => b @@ -387,18 +406,25 @@ class FormGenerator { return '${e.fieldName}:${e.fieldValueName}'; }).whereType(); + final referenceType = + root.output ? element.toReferenceType : element.fullTypeName; + b ..name = 'model' - ..returns = Reference(element.fullTypeName) + ..returns = Reference(referenceType) ..annotations.add(const CodeExpression(Code('override'))) ..type = MethodType.getter ..body = Code(''' final isValid = !currentForm.hasErrors && currentForm.errors.isEmpty; if (!isValid) { - debugPrintStack(label: '[\${path ?? '$classNameFull'}]\\n┗━ Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); + $log.warning( + 'Avoid calling `model` on invalid form.Possible exceptions for non-nullable fields which should be guarded by `required` validator.', + null, + StackTrace.current, + ); } - return ${element.fullTypeName}(${parameterValues.join(', ')}); + return $referenceType(${parameterValues.join(', ')}); '''); }, ); @@ -423,6 +449,7 @@ class FormGenerator { root, typeArguments.first.element! as ClassElement, e.type, + ast, ); return '${e.name}${generator.className}.forEach((e) => e.toggleDisabled());'; @@ -497,6 +524,28 @@ class FormGenerator { }, ); + Method get equalsToMethod => Method( + (b) { + b + ..name = 'equalsTo' + ..annotations.add(const CodeExpression(Code('override'))) + ..returns = const Reference('bool') + ..requiredParameters.add( + Parameter( + (b) => b + ..name = 'other' + ..type = Reference(_modelDisplayTypeMaybeNullable), + ), + ) + ..body = Code(''' + return const DeepCollectionEquality().equals( + this.currentForm.rawValue, + $className.formElements(other).value, + ); + '''); + }, + ); + Method get submitMethod => Method( (b) { b @@ -511,7 +560,7 @@ class FormGenerator { ..named = true ..required = true ..type = Reference( - 'void Function(${element.fullTypeName} model)'), + 'void Function(${root.output ? element.toReferenceType : element.fullTypeName} model)'), ), Parameter( (b) => b @@ -521,17 +570,21 @@ class FormGenerator { ), ], ) - ..body = const Code(''' + ..body = Code(''' currentForm.markAllAsTouched(); if (currentForm.valid) { onValid(model); } else { + $log.info('Errors'); + $log.info('┗━━ \${form.errors}'); onNotValid?.call(); } '''); }, ); + Code get logging => Code("final $log = Logger.detached('$classNameFull');"); + Constructor get _constructor => Constructor( (b) => b ..requiredParameters.addAll( @@ -582,13 +635,66 @@ class FormGenerator { return '$_modelDisplayTypeNonNullable?'; } + List test() { + final ast = this.ast; + + // final classDeclarationVisitor = ClassDeclarationVisitor(root); + // ast.visitChildren(classDeclarationVisitor); + // + // final classNode = classDeclarationVisitor.classDeclaration; + + if (ast is! CompilationUnit) { + return []; + } + + // var rfParameterVisitor = RfParameterVisitor(); + + // var rfAnnotationCollectorVisitor = RfAnnotationCollectorVisitor(); + // ast.visitChildren(rfParameterVisitor); + // replaceR( + // rfParameterVisitor.fieldDeclaration, + // rfParameterVisitor.fieldFormalParameter, + // ); + + // final astDeclaration = classNode as u.ClassDeclarationImpl; + final renamedClass = ClassRenameVisitor(); + ast.accept(renamedClass); + + final rfParameterVisitor = RfParameterVisitor(); + ast.accept(rfParameterVisitor); + replaceR( + rfParameterVisitor.fieldDeclaration, + rfParameterVisitor.fieldFormalParameter, + ); + + final x = ast.declarations; + + return x + .where((e) => + e is u.ClassDeclarationImpl && + (e.hasRfAnnotation || e.hasRfGroupAnnotation)) + .map((e) { + return Code(e.toSource()); + }).toList(); + } + + List get generate2 { + if (root.output) { + return test(); + } + + return []; + } + List get generate { return [ + logging, Class( (b) => b ..name = className ..types.addAll(element.fullGenericTypes) - ..implements.add(Reference('FormModel<${element.fullTypeName}>')) + ..implements.add(Reference( + 'FormModel<${element.fullTypeName}, ${root.output ? element.toReferenceType : element.fullTypeName}>')) ..fields.addAll( [ ...staticFieldNameList, @@ -639,6 +745,7 @@ class FormGenerator { modelMethod, toggleDisabledMethod, submitMethod, + equalsToMethod, currentFormMethod, updateValueMethod, resetMethod, @@ -736,6 +843,7 @@ class FormGenerator { root, typeParameter.element! as ClassElement, type, + ast, ); return Method( @@ -759,57 +867,66 @@ class FormGenerator { ); } - Iterable get fieldContainsMethodList => - all.map((e) => ContainsMethod(e).method()).whereType(); + Iterable get fieldContainsMethodList => all + .map((e) => ContainsMethod(e, root.output).method()) + .whereType(); - Iterable get fieldValueMethodList => - all.map((e) => FieldValueMethod(e).method()).whereType(); + Iterable get fieldValueMethodList => all + .map((e) => FieldValueMethod(e, root.output).method()) + .whereType(); - Iterable get fieldControlNameMethodList => - all.map((e) => ControlPathMethod(e).method()).whereType(); + Iterable get fieldControlNameMethodList => all + .map((e) => ControlPathMethod(e, root.output).method()) + .whereType(); Iterable get staticFieldNameList => annotatedParameters.map(staticFieldName); Iterable get fieldErrorsMethodList => - all.map((e) => ErrorsMethod(e).method()).whereType(); + all.map((e) => ErrorsMethod(e, root.output).method()).whereType(); Iterable get fieldNameList => annotatedParameters.map(field); Iterable get fieldFocusMethodList => - all.map((e) => FocusMethod(e).method()).whereType(); + all.map((e) => FocusMethod(e, root.output).method()).whereType(); Iterable get fieldRemoveMethodList => - all.map((e) => RemoveMethod(e).method()).whereType(); + all.map((e) => RemoveMethod(e, root.output).method()).whereType(); Iterable get fieldUpdateMethodList => all - .map((e) => ReactiveFormUpdateValueMethod(e).method()) + .map((e) => ReactiveFormUpdateValueMethod(e, root.output).method()) .whereType(); - Iterable get fieldInsertMethodList => - all.map((e) => ReactiveFormInsertMethod(e).method()).whereType(); + Iterable get fieldInsertMethodList => all + .map((e) => ReactiveFormInsertMethod(e, root.output).method()) + .whereType(); - Iterable get fieldClearMethodList => - all.map((e) => ReactiveFormClearMethod(e).method()).whereType(); + Iterable get fieldClearMethodList => all + .map((e) => ReactiveFormClearMethod(e, root.output).method()) + .whereType(); Iterable get fieldPatchMethodList => all - .map((e) => ReactiveFormPatchValueMethod(e).method()) + .map((e) => ReactiveFormPatchValueMethod(e, root.output).method()) .whereType(); Iterable get fieldResetMethodList => - all.map((e) => ResetMethod(e).method()).whereType(); + all.map((e) => ResetMethod(e, root.output).method()).whereType(); - Iterable get controlMethodList => - all.map((e) => ControlMethod(e).method()).whereType(); + Iterable get controlMethodList => all + .map((e) => ControlMethod(e, root.output).method()) + .whereType(); - Iterable get controlPrivateMethodList => - all.map((e) => ControlPrivateMethod(e).method()).whereType(); + Iterable get controlPrivateMethodList => all + .map((e) => ControlPrivateMethod(e, root.output).method()) + .whereType(); - Iterable get controlSetDisabledMethodList => - all.map((e) => ControlSetDisableMethod(e).method()).whereType(); + Iterable get controlSetDisabledMethodList => all + .map((e) => ControlSetDisableMethod(e, root.output).method()) + .whereType(); - Iterable get extendedControlMethodList => - all.map((e) => ExtendedControlMethod(e).method()).whereType(); + Iterable get extendedControlMethodList => all + .map((e) => ExtendedControlMethod(e, root.output).method()) + .whereType(); Iterable get addArrayControlMethodList => formArrays.map(addArrayControl); diff --git a/packages/reactive_forms_generator/lib/src/library_builder.dart b/packages/reactive_forms_generator/lib/src/library_builder.dart index b9b8729b..ffab8293 100644 --- a/packages/reactive_forms_generator/lib/src/library_builder.dart +++ b/packages/reactive_forms_generator/lib/src/library_builder.dart @@ -1,3 +1,4 @@ +import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:code_builder/code_builder.dart'; import 'package:reactive_forms_generator/src/form_generator.dart'; @@ -12,8 +13,11 @@ import 'package:reactive_forms_generator/src/reactive_forms/reactive_inherited_s const stringRef = Reference('String'); const formGroupRef = Reference('FormGroup'); -List generateLibrary(ClassElement element) { - final formGenerator = FormGenerator(element, element, null); +List generateLibrary( + ClassElement element, + AstNode ast, +) { + final formGenerator = FormGenerator(element, element, null, ast); final reactiveInheritedStreamer = ReactiveInheritedStreamer(formGenerator); final reactiveForm = ReactiveForm(reactiveInheritedStreamer); final reactiveFormExtension = @@ -31,6 +35,7 @@ List generateLibrary(ClassElement element) { reactiveFormExtension.generate, ...reactiveFormBuilder.generate, ...formGenerator.generate, + ...formGenerator.generate2, reactiveFormArrayBuilder.generate, reactiveFormGroupArrayBuilder.generate, ]; diff --git a/packages/reactive_forms_generator/lib/src/output/extensions.dart b/packages/reactive_forms_generator/lib/src/output/extensions.dart new file mode 100644 index 00000000..741ddcf3 --- /dev/null +++ b/packages/reactive_forms_generator/lib/src/output/extensions.dart @@ -0,0 +1,183 @@ +// ignore_for_file: implementation_imports +import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/ast/token.dart'; +import 'package:analyzer/src/dart/ast/token.dart'; +import 'package:analyzer/src/dart/ast/ast.dart'; +import 'package:analyzer/src/generated/utilities_dart.dart'; +import 'package:reactive_forms_generator/src/types.dart'; + +extension ElementExt on Element { + // Future clone() async { + // final unit = await session!.getResolvedUnit( + // source!.fullName, + // ); + // unit as ResolvedUnitResult; + // final Object? ast = unit.unit.declarations.firstWhereOrNull( + // (declaration) { + // return declaration.declaredElement?.name == name!; + // }, + // ); + // if (ast == null) { + // throw InvalidGenerationSourceError( + // 'Ast not found', + // element: this, + // ); + // } + // if (ast is! Declaration) { + // throw InvalidGenerationSourceError( + // 'Ast is not a Declaration', + // element: this, + // ); + // } + // + // final clonedSource = parseString(content: ast.toSource()); + // + // final Object? clonedAst = clonedSource.unit.declarations.firstWhereOrNull( + // (declaration) { + // return declaration is ClassDeclaration && + // (declaration).name.lexeme == name; + // }, + // ); + // if (clonedAst == null) { + // throw InvalidGenerationSourceError('Ast not found', element: this); + // } + // if (clonedAst is! Declaration) { + // throw InvalidGenerationSourceError( + // 'Ast is not a Declaration', + // element: this, + // ); + // } + // + // return clonedAst; + // } +} + +extension NormalFormalParameterImplExt on NormalFormalParameterImpl { + NormalFormalParameterImpl get newParameter { + final parameter = this; + switch (parameter) { + case FieldFormalParameterImpl _: + return FieldFormalParameterImpl( + comment: null, + metadata: parameter.metadata, + covariantKeyword: parameter.covariantKeyword, + requiredKeyword: KeywordToken(Keyword.REQUIRED, 0), + name: parameter.name, + keyword: parameter.keyword, + type: parameter.type, + thisKeyword: parameter.thisKeyword, + period: parameter.period, + typeParameters: parameter.typeParameters, + parameters: parameter.parameters, + question: parameter.question, + ); + case FunctionTypedFormalParameterImpl _: + return this; + case SimpleFormalParameterImpl _: + return SimpleFormalParameterImpl( + comment: null, + metadata: parameter.metadata, + covariantKeyword: parameter.covariantKeyword, + requiredKeyword: KeywordToken(Keyword.REQUIRED, 0), + keyword: parameter.keyword, + type: parameter.type?.newType, + name: parameter.name, + ); + case SuperFormalParameterImpl _: + return this; + } + } +} + +extension DefaultFormalParameterImplExt on DefaultFormalParameterImpl { + DefaultFormalParameterImpl get newParameter => DefaultFormalParameterImpl( + parameter: parameter.newParameter, + kind: ParameterKind.NAMED_REQUIRED, + separator: separator, + defaultValue: defaultValue, + ); +} + +extension SimpleFormalParameterImplExt on SimpleFormalParameterImpl { + SimpleFormalParameterImpl get newParameter { + return SimpleFormalParameterImpl( + comment: null, + metadata: metadata, + covariantKeyword: covariantKeyword, + requiredKeyword: requiredKeyword, + keyword: keyword, + type: type?.newType, + name: name, + ); + } +} + +extension TypeAnnotationImplExt on TypeAnnotationImpl { + TypeAnnotationImpl get newType { + final type = this; + return switch (type) { + final GenericFunctionTypeImpl _ => this, + final NamedTypeImpl _ => NamedTypeImpl( + importPrefix: type.importPrefix, + name2: type.name2, + typeArguments: type.typeArguments, + question: null, + ), + final RecordTypeAnnotationImpl _ => this, + }; + } + + TypeAnnotationImpl get newTypeO { + final type = this; + + return switch (type) { + GenericFunctionTypeImpl() => throw UnimplementedError(), + NamedTypeImpl() => NamedTypeImpl( + importPrefix: type.importPrefix, + name2: type.element?.hasRfGroupAnnotation == true + ? StringToken( + TokenType.STRING, + '${type.name2.lexeme}Output', + 0, + ) + : type.name2, + typeArguments: type.typeArguments?.newTypeArguments, + question: type.question, + ), + RecordTypeAnnotationImpl() => throw UnimplementedError(), + }; + } +} + +extension TypeArgumentListImplExt on TypeArgumentListImpl { + TypeArgumentListImpl get newTypeArguments { + return TypeArgumentListImpl( + leftBracket: leftBracket, + arguments: arguments.map((e) { + return e.newTypeO; + }).toList(), + rightBracket: rightBracket, + ); + } +} + +extension FieldDeclarationImplExt on FieldDeclarationImpl { + FieldDeclarationImpl get newField => FieldDeclarationImpl( + comment: null, + metadata: metadata, + abstractKeyword: abstractKeyword, + augmentKeyword: augmentKeyword, + covariantKeyword: covariantKeyword, + externalKeyword: externalKeyword, + staticKeyword: staticKeyword, + fieldList: VariableDeclarationListImpl( + comment: null, + metadata: fields.metadata, + lateKeyword: fields.lateKeyword, + keyword: fields.keyword, + type: fields.type?.newType, + variables: fields.variables, + ), + semicolon: semicolon, + ); +} diff --git a/packages/reactive_forms_generator/lib/src/output/helpers.dart b/packages/reactive_forms_generator/lib/src/output/helpers.dart new file mode 100644 index 00000000..16671697 --- /dev/null +++ b/packages/reactive_forms_generator/lib/src/output/helpers.dart @@ -0,0 +1,52 @@ +// ignore_for_file: implementation_imports +import 'package:analyzer/src/dart/ast/utilities.dart'; +import 'package:analyzer/src/dart/ast/ast.dart'; +import 'package:reactive_forms_generator/src/output/extensions.dart'; + +void replaceR( + Map fieldDeclaration, + Map fieldFormalParameter, +) { + fieldFormalParameter.forEach((key, node) { + if (node is SimpleFormalParameterImpl) { + NodeReplacer.replace(node, node.newParameter); + } else if (node is DefaultFormalParameterImpl) { + final parameter = node.parameter; + + if (parameter is SimpleFormalParameterImpl) { + final field = fieldDeclaration[key]; + if (field != null && field is FieldDeclarationImpl) { + NodeReplacer.replace(field, field.newField); + } + + NodeReplacer.replace(node, node.newParameter); + } + + if (parameter is FieldFormalParameterImpl) { + final field = fieldDeclaration[key]; + if (field != null && field is FieldDeclarationImpl) { + NodeReplacer.replace(field, field.newField); + } + + NodeReplacer.replace(node, node.newParameter); + } + } + }); +} + +String generateModifiedCode(String code, List annotations) { + final buffer = StringBuffer(); + int lastIndex = 0; + + for (var annotation in annotations) { + final offset = annotation.offset; + final end = annotation.end; + + buffer.write(code.substring(lastIndex, offset)); + buffer.write(''); + lastIndex = end; + } + buffer.write(code.substring(lastIndex)); + + return buffer.toString(); +} diff --git a/packages/reactive_forms_generator/lib/src/output/rf_annotation_arguments_visitor.dart b/packages/reactive_forms_generator/lib/src/output/rf_annotation_arguments_visitor.dart new file mode 100644 index 00000000..ffe2b609 --- /dev/null +++ b/packages/reactive_forms_generator/lib/src/output/rf_annotation_arguments_visitor.dart @@ -0,0 +1,247 @@ +// ignore_for_file: implementation_imports +import 'package:analyzer/src/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; +import 'package:analyzer/dart/ast/token.dart'; +import 'package:analyzer/src/dart/ast/token.dart'; +import 'package:analyzer/src/dart/ast/utilities.dart'; +import 'package:reactive_forms_generator/src/output/extensions.dart'; + +class RfAnnotationArgumentsVisitor extends RecursiveAstVisitor { + final arguments = {}; + + @override + visitArgumentList(ArgumentList node) { + for (var argument in node.arguments) { + if (argument is NamedExpression) { + arguments.addEntries( + [ + MapEntry( + argument.name.label.name, + argument.expression.toSource().toString(), + ), + ], + ); + } else { + // For positional arguments + } + } + return super.visitArgumentList(node); + } +} + +class ClassRenameVisitor extends RecursiveAstVisitor { + // List updatedClass = []; + + ClassRenameVisitor(); + + @override + void visitClassDeclaration(ClassDeclaration node) { + if (node is ClassDeclarationImpl) { + final newNode = ClassDeclarationImpl( + comment: null, + metadata: node.metadata + // .where( + // (e) => !e.name.toString().startsWith('Rf'), + // ) + .toList(), + augmentKeyword: node.augmentKeyword, + abstractKeyword: node.abstractKeyword, + macroKeyword: node.macroKeyword, + sealedKeyword: node.sealedKeyword, + baseKeyword: node.baseKeyword, + interfaceKeyword: node.interfaceKeyword, + finalKeyword: node.finalKeyword, + mixinKeyword: node.mixinKeyword, + classKeyword: node.classKeyword, + name: StringToken( + TokenType.STRING, + '${node.name.lexeme}Output', + 0, + ), + typeParameters: node.typeParameters, + extendsClause: node.extendsClause, + withClause: node.withClause != null + ? WithClauseImpl( + withKeyword: node.withClause!.withKeyword, + mixinTypes: node.withClause!.mixinTypes.map( + (e) { + return NamedTypeImpl( + importPrefix: e.importPrefix, + name2: StringToken( + TokenType.STRING, + '${e.name2.lexeme}Output', + 0, + ), + typeArguments: e.typeArguments, + question: e.question, + ); + }, + ).toList(), + ) + : null, + implementsClause: node.implementsClause, + nativeClause: node.nativeClause, + leftBracket: node.leftBracket, + members: node.members.map((e) { + return switch (e) { + final ConstructorDeclarationImpl _ => ConstructorDeclarationImpl( + comment: null, + metadata: e.metadata, + augmentKeyword: e.augmentKeyword, + externalKeyword: e.externalKeyword, + constKeyword: e.constKeyword, + factoryKeyword: e.factoryKeyword, + returnType: SimpleIdentifierImpl( + StringToken( + TokenType.STRING, + '${e.returnType.name}Output', + 0, + ), + ), + period: e.period, + name: e.name, + parameters: e.parameters, + separator: e.separator, + initializers: e.initializers, + redirectedConstructor: e.redirectedConstructor != null + ? ConstructorNameImpl( + type: NamedTypeImpl( + importPrefix: + e.redirectedConstructor!.type.importPrefix, + name2: StringToken( + TokenType.STRING, + '${e.redirectedConstructor!.type.name2}Output', + 0, + ), + typeArguments: + e.redirectedConstructor!.type.typeArguments, + question: e.redirectedConstructor!.type.question, + ), + period: e.redirectedConstructor!.period, + name: e.redirectedConstructor!.name, + ) + : null, + body: switch (e.body) { + final BlockFunctionBody _ => e.body, + final EmptyFunctionBodyImpl _ => e.body, + final ExpressionFunctionBodyImpl _ => + ExpressionFunctionBodyImpl( + keyword: (e.body as ExpressionFunctionBodyImpl).keyword, + star: (e.body as ExpressionFunctionBodyImpl).star, + functionDefinition: (e.body as ExpressionFunctionBodyImpl) + .functionDefinition, + expression: switch ( + (e.body as ExpressionFunctionBodyImpl).expression) { + MethodInvocationImpl() => MethodInvocationImpl( + target: ((e.body as ExpressionFunctionBodyImpl) + .expression as MethodInvocationImpl) + .target, + operator: ((e.body as ExpressionFunctionBodyImpl) + .expression as MethodInvocationImpl) + .operator, + methodName: SimpleIdentifierImpl( + StringToken( + TokenType.STRING, + ((e.body as ExpressionFunctionBodyImpl) + .expression as MethodInvocationImpl) + .methodName + .name + .replaceFirst( + e.returnType.name, + '${e.returnType.name}Output', + ), + 0, + ), + ), + typeArguments: + ((e.body as ExpressionFunctionBodyImpl) + .expression as MethodInvocationImpl) + .typeArguments, + argumentList: + ((e.body as ExpressionFunctionBodyImpl) + .expression as MethodInvocationImpl) + .argumentList, + ), + _ => (e.body as ExpressionFunctionBodyImpl).expression, + }, + semicolon: + (e.body as ExpressionFunctionBodyImpl).semicolon, + ), + final NativeFunctionBodyImpl _ => e.body, + }, + ), + final FieldDeclarationImpl _ => FieldDeclarationImpl( + comment: null, + metadata: e.metadata, + abstractKeyword: e.abstractKeyword, + augmentKeyword: e.augmentKeyword, + covariantKeyword: e.covariantKeyword, + externalKeyword: e.externalKeyword, + staticKeyword: e.staticKeyword, + fieldList: VariableDeclarationListImpl( + comment: null, + metadata: e.fields.metadata, + lateKeyword: e.fields.lateKeyword, + keyword: e.fields.keyword, + type: e.fields.type?.newTypeO, + variables: e.fields.variables.map((e) { + return e; + }).toList(), + ), + //e.fields + semicolon: e.semicolon, + ), + final MethodDeclarationImpl _ => MethodDeclarationImpl( + comment: null, + metadata: e.metadata, + augmentKeyword: e.augmentKeyword, + externalKeyword: e.externalKeyword, + modifierKeyword: e.modifierKeyword, + returnType: e.returnType, + propertyKeyword: e.propertyKeyword, + operatorKeyword: e.operatorKeyword, + name: e.name, + typeParameters: e.typeParameters, + parameters: e.parameters, + body: e.body, + ), + }; + }).toList(), + rightBracket: node.rightBracket, + ); + + NodeReplacer.replace(node, newNode); + } + super.visitClassDeclaration(node); + } + + @override + void visitConstructorDeclaration(ConstructorDeclaration node) { + if (node is ConstructorDeclarationImpl && node.name == null) { + final updatedNode = ConstructorDeclarationImpl( + comment: null, + metadata: node.metadata, + augmentKeyword: node.augmentKeyword, + externalKeyword: node.externalKeyword, + constKeyword: node.constKeyword, + factoryKeyword: node.factoryKeyword, + returnType: SimpleIdentifierImpl( + StringToken( + TokenType.STRING, + '${node.returnType.name}Output', + 0, + ), + ), + period: node.period, + name: node.name, + parameters: node.parameters, + separator: node.separator, + initializers: node.initializers, + redirectedConstructor: node.redirectedConstructor, + body: node.body, + ); + NodeReplacer.replace(node, updatedNode); + } + super.visitConstructorDeclaration(node); + } +} diff --git a/packages/reactive_forms_generator/lib/src/output/rf_annotation_collecotor_visitor.dart b/packages/reactive_forms_generator/lib/src/output/rf_annotation_collecotor_visitor.dart new file mode 100644 index 00000000..5fa5343f --- /dev/null +++ b/packages/reactive_forms_generator/lib/src/output/rf_annotation_collecotor_visitor.dart @@ -0,0 +1,14 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; + +class RfAnnotationCollectorVisitor extends RecursiveAstVisitor { + final List annotationsToRemove = []; + + @override + visitAnnotation(Annotation node) { + if (node.name.toString() == 'Rf') { + annotationsToRemove.add(node); + } + super.visitAnnotation(node); + } +} diff --git a/packages/reactive_forms_generator/lib/src/output/rf_annotation_visitor.dart b/packages/reactive_forms_generator/lib/src/output/rf_annotation_visitor.dart new file mode 100644 index 00000000..f50594bd --- /dev/null +++ b/packages/reactive_forms_generator/lib/src/output/rf_annotation_visitor.dart @@ -0,0 +1,16 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; + +class RfAnnotationVisitor extends RecursiveAstVisitor { + Annotation? rfAnnotation; + + @override + visitAnnotation(Annotation node) { + if (node.name.toString().startsWith('Rf')) { + rfAnnotation = node; + } + node.visitChildren(this); + + return null; + } +} diff --git a/packages/reactive_forms_generator/lib/src/output/rf_declaration_visitor.dart b/packages/reactive_forms_generator/lib/src/output/rf_declaration_visitor.dart new file mode 100644 index 00000000..16ec2823 --- /dev/null +++ b/packages/reactive_forms_generator/lib/src/output/rf_declaration_visitor.dart @@ -0,0 +1,23 @@ +// ignore_for_file: implementation_imports +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/src/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; + +class ClassDeclarationVisitor extends RecursiveAstVisitor { + final ClassElement classElement; + + ClassDeclaration? _classDeclaration; + + ClassDeclarationVisitor(this.classElement); + + ClassDeclaration? get classDeclaration => _classDeclaration; + + @override + void visitClassDeclaration(ClassDeclaration node) { + if (node.name.lexeme == classElement.name) { + _classDeclaration = node; + } + super.visitClassDeclaration(node); + } +} diff --git a/packages/reactive_forms_generator/lib/src/output/rf_paramater_visitor.dart b/packages/reactive_forms_generator/lib/src/output/rf_paramater_visitor.dart new file mode 100644 index 00000000..24db4222 --- /dev/null +++ b/packages/reactive_forms_generator/lib/src/output/rf_paramater_visitor.dart @@ -0,0 +1,80 @@ +// ignore_for_file: implementation_imports +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/visitor.dart'; +import 'package:analyzer/src/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; +import "package:collection/collection.dart"; +import 'package:reactive_forms_generator/src/output/rf_annotation_arguments_visitor.dart'; +import 'package:reactive_forms_generator/src/output/rf_annotation_visitor.dart'; + +class RfParameterVisitor extends RecursiveAstVisitor { + final Map fieldDeclaration = {}; + final Map fieldFormalParameter = {}; + + @override + visitFieldDeclaration(FieldDeclaration node) { + final field = node.fields.variables.firstOrNull; + if (field != null) { + fieldDeclaration[field.name.toString()] = node; + } + super.visitFieldDeclaration(node); + } + + @override + visitFormalParameterList(FormalParameterList node) { + for (var e in node.parameters) { + final rfAnnotationVisitor = RfAnnotationVisitor(); + final rfAnnotationArguments = RfAnnotationArgumentsVisitor(); + e.visitChildren(rfAnnotationVisitor); + + if (rfAnnotationVisitor.rfAnnotation != null) { + e.visitChildren(rfAnnotationArguments); + } + + if (rfAnnotationArguments.arguments.containsKey('validators') && + rfAnnotationArguments.arguments['validators'] + ?.contains('RequiredValidator()') == + true) { + fieldFormalParameter[e.name.toString()] = e; + } + } + + node.visitChildren(this); + return null; + } +} + +class RfEParameterVisitor extends RecursiveElementVisitor { + final Map fieldDeclaration = {}; + final Map fieldFormalParameter = {}; + + @override + visitFieldElement(FieldElement element) { + fieldDeclaration[element.name] = element; + + super.visitFieldElement(element); + return null; + } + + @override + visitFieldFormalParameterElement(FieldFormalParameterElement element) { + // final rfAnnotationVisitor = RfAnnotationVisitor(); + // final rfAnnotationArguments = RfAnnotationArgumentsVisitor(); + // node.accept(rfAnnotationVisitor); + // + // if (rfAnnotationVisitor.rfAnnotation != null) { + // node.accept(rfAnnotationArguments); + // } + // + // if (rfAnnotationArguments.arguments.containsKey('validators') && + // rfAnnotationArguments.arguments['validators'] + // ?.contains('RequiredValidator()') == + // true) { + // fieldFormalParameter[node.name.toString()] = node; + // } + // + element.visitChildren(this); + return null; + } +} diff --git a/packages/reactive_forms_generator/lib/src/output/x.dart b/packages/reactive_forms_generator/lib/src/output/x.dart new file mode 100644 index 00000000..753870f2 --- /dev/null +++ b/packages/reactive_forms_generator/lib/src/output/x.dart @@ -0,0 +1,570 @@ +// ignore_for_file: implementation_imports +import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/nullability_suffix.dart'; +import 'package:analyzer/dart/element/type.dart'; +import 'package:analyzer/src/dart/element/type.dart'; +import 'package:analyzer/src/dart/element/display_string_builder.dart'; +import 'package:analyzer/src/dart/element/element.dart'; +import 'package:analyzer/src/dart/resolver/variance.dart'; +import 'package:analyzer/src/dart/element/type_algebra.dart'; +import 'package:reactive_forms_generator/src/types.dart'; + +class ElementDisplayStringBuilder2 extends ElementDisplayStringBuilder { + final StringBuffer _buffer = StringBuffer(); + + /// Whether to include the nullability ('?' characters) in a display string. + final bool _withNullability; + + /// Whether to allow a display string to be written in multiple lines. + final bool _multiline; + + ElementDisplayStringBuilder2({ + required bool withNullability, + bool multiline = false, + }) : _withNullability = withNullability, + _multiline = multiline, + super(withNullability: false); + + @override + String toString() => _buffer.toString(); + + @override + void writeAbstractElement(ElementImpl element) { + _write(element.name ?? ''); + } + + @override + void writeClassElement(ClassElementImpl element) { + if (element.isAugmentation) { + _write('augment '); + } + + if (element.isSealed) { + _write('sealed '); + } else if (element.isAbstract) { + _write('abstract '); + } + if (element.isBase) { + _write('base '); + } else if (element.isInterface) { + _write('interface '); + } else if (element.isFinal) { + _write('final '); + } + if (element.isMixinClass) { + _write('mixin '); + } + + _write('class '); + _write(element.displayName); + + _writeTypeParameters(element.typeParameters); + + _writeTypeIfNotObject(' extends ', element.supertype); + _writeTypesIfNotEmpty(' with ', element.mixins); + _writeTypesIfNotEmpty(' implements ', element.interfaces); + } + + @override + void writeCompilationUnitElement(CompilationUnitElementImpl element) { + var path = element.source.fullName; + _write(path); + } + + @override + void writeConstructorElement(ConstructorElement element) { + _writeType(element.returnType); + _write(' '); + + _write(element.displayName); + + _writeFormalParameters( + element.parameters, + forElement: true, + allowMultiline: true, + ); + } + + @override + void writeDynamicType() { + _write('dynamic'); + } + + @override + void writeEnumElement(EnumElement element) { + _write('enum '); + _write(element.displayName); + _writeTypeParameters(element.typeParameters); + _writeTypesIfNotEmpty(' with ', element.mixins); + _writeTypesIfNotEmpty(' implements ', element.interfaces); + } + + @override + void writeExecutableElement(ExecutableElement element, String name) { + if (element.isAugmentation) { + _write('augment '); + } + + if (element.kind != ElementKind.SETTER) { + _writeType(element.returnType); + _write(' '); + } + + _write(name); + + if (element.kind != ElementKind.GETTER) { + _writeTypeParameters(element.typeParameters); + _writeFormalParameters( + element.parameters, + forElement: true, + allowMultiline: true, + ); + } + } + + @override + void writeExportElement(LibraryExportElementImpl element) { + _write('export '); + _writeDirectiveUri(element.uri); + } + + @override + void writeExtensionElement(ExtensionElement element) { + _write('extension '); + _write(element.displayName); + _writeTypeParameters(element.typeParameters); + _write(' on '); + _writeType(element.extendedType); + } + + @override + void writeFormalParameter(ParameterElement element) { + if (element.isRequiredPositional) { + _writeWithoutDelimiters(element, forElement: true); + } else if (element.isOptionalPositional) { + _write('['); + _writeWithoutDelimiters(element, forElement: true); + _write(']'); + } else if (element.isNamed) { + _write('{'); + _writeWithoutDelimiters(element, forElement: true); + _write('}'); + } + } + + @override + void writeFunctionType(FunctionType type) { + type = _uniqueTypeParameters(type); + + _writeType(type.returnType); + _write(' Function'); + _writeTypeParameters(type.typeFormals); + _writeFormalParameters(type.parameters, forElement: false); + _writeNullability(type.nullabilitySuffix); + } + + @override + void writeGenericFunctionTypeElement(GenericFunctionTypeElementImpl element) { + _writeType(element.returnType); + _write(' Function'); + _writeTypeParameters(element.typeParameters); + _writeFormalParameters(element.parameters, forElement: true); + } + + @override + void writeImportElement(LibraryImportElementImpl element) { + _write('import '); + _writeDirectiveUri(element.uri); + } + + @override + void writeInterfaceType(InterfaceType type) { + final namePostfix = + type.element.hasRfGroupAnnotation || type.element.hasRfAnnotation + ? 'Output' + : ''; + _write('${type.element.name}$namePostfix'); + _writeTypeArguments(type.typeArguments); + + _writeNullability(type.nullabilitySuffix); + } + + @override + void writeInvalidType() { + _write('InvalidType'); + } + + @override + void writeLibraryElement(LibraryElementImpl element) { + _write('library '); + _write('${element.source.uri}'); + } + + @override + void writeMixinElement(MixinElementImpl element) { + if (element.isAugmentation) { + _write('augment '); + } + if (element.isBase) { + _write('base '); + } + _write('mixin '); + _write(element.displayName); + _writeTypeParameters(element.typeParameters); + _writeTypesIfNotEmpty(' on ', element.superclassConstraints); + _writeTypesIfNotEmpty(' implements ', element.interfaces); + } + + @override + void writeNeverType(NeverType type) { + _write('Never'); + _writeNullability(type.nullabilitySuffix); + } + + @override + void writePartElement(PartElementImpl element) { + _write('part '); + _writeDirectiveUri(element.uri); + } + + @override + void writePrefixElement(PrefixElementImpl element) { + _write('as '); + _write(element.displayName); + } + + @override + void writeRecordType(RecordType type) { + final positionalFields = type.positionalFields; + final namedFields = type.namedFields; + final fieldCount = positionalFields.length + namedFields.length; + _write('('); + + var index = 0; + for (final field in positionalFields) { + _writeType(field.type); + if (index++ < fieldCount - 1) { + _write(', '); + } + } + + if (namedFields.isNotEmpty) { + _write('{'); + for (final field in namedFields) { + _writeType(field.type); + _write(' '); + _write(field.name); + if (index++ < fieldCount - 1) { + _write(', '); + } + } + _write('}'); + } + + // Add trailing comma for record types with only one position field. + if (positionalFields.length == 1 && namedFields.isEmpty) { + _write(','); + } + + _write(')'); + _writeNullability(type.nullabilitySuffix); + } + + @override + void writeTypeAliasElement(TypeAliasElementImpl element) { + _write('typedef '); + _write(element.displayName); + _writeTypeParameters(element.typeParameters); + _write(' = '); + + var aliasedElement = element.aliasedElement; + if (aliasedElement != null) { + aliasedElement.appendTo(this); + } else { + _writeType(element.aliasedType); + } + } + + @override + void writeTypeParameter(TypeParameterElement element) { + if (element is TypeParameterElementImpl) { + var variance = element.variance; + if (!element.isLegacyCovariant && variance != Variance.unrelated) { + _write(variance.toKeywordString()); + _write(' '); + } + } + + _write(element.displayName); + + var bound = element.bound; + if (bound != null) { + _write(' extends '); + _writeType(bound); + } + } + + @override + void writeTypeParameterType(TypeParameterTypeImpl type) { + final promotedBound = type.promotedBound; + if (promotedBound != null) { + final hasSuffix = type.nullabilitySuffix != NullabilitySuffix.none; + if (hasSuffix) { + _write('('); + } + _write(type.element.displayName); + _write(' & '); + _writeType(promotedBound); + if (hasSuffix) { + _write(')'); + } + } else { + _write(type.element.displayName); + } + _writeNullability(type.nullabilitySuffix); + } + + @override + void writeUnknownInferredType() { + _write('_'); + } + + @override + void writeVariableElement(VariableElement element) { + switch (element) { + case FieldElement(isAugmentation: true): + case TopLevelVariableElement(isAugmentation: true): + _write('augment '); + } + + _writeType(element.type); + _write(' '); + _write(element.displayName); + } + + @override + void writeVoidType() { + _write('void'); + } + + void _write(String str) { + _buffer.write(str); + } + + void _writeDirectiveUri(DirectiveUri uri) { + if (uri is DirectiveUriWithUnitImpl) { + _write('unit ${uri.unit.source.uri}'); + } else if (uri is DirectiveUriWithSourceImpl) { + _write('source ${uri.source}'); + } else { + _write(''); + } + } + + void _writeFormalParameters( + List parameters, { + required bool forElement, + bool allowMultiline = false, + }) { + // Assume the display string looks better wrapped when there are at least + // three parameters. This avoids having to pre-compute the single-line + // version and know the length of the function name/return type. + var multiline = allowMultiline && _multiline && parameters.length >= 3; + + // The prefix for open groups is included in separator for single-line but + // not for multiline so must be added explicitly. + var openGroupPrefix = multiline ? ' ' : ''; + var separator = multiline ? ',' : ', '; + var trailingComma = multiline ? ',\n' : ''; + var parameterPrefix = multiline ? '\n ' : ''; + + _write('('); + + _WriteFormalParameterKind? lastKind; + var lastClose = ''; + + void openGroup(_WriteFormalParameterKind kind, String open, String close) { + if (lastKind != kind) { + _write(lastClose); + if (lastKind != null) { + // We only need to include the space before the open group if there + // was a previous parameter, otherwise it goes immediately after the + // open paren. + _write(openGroupPrefix); + } + _write(open); + lastKind = kind; + lastClose = close; + } + } + + for (var i = 0; i < parameters.length; i++) { + if (i != 0) { + _write(separator); + } + + var parameter = parameters[i]; + if (parameter.isRequiredPositional) { + openGroup(_WriteFormalParameterKind.requiredPositional, '', ''); + } else if (parameter.isOptionalPositional) { + openGroup(_WriteFormalParameterKind.optionalPositional, '[', ']'); + } else if (parameter.isNamed) { + openGroup(_WriteFormalParameterKind.named, '{', '}'); + } + _write(parameterPrefix); + _writeWithoutDelimiters(parameter, forElement: forElement); + } + + _write(trailingComma); + _write(lastClose); + _write(')'); + } + + void _writeNullability(NullabilitySuffix nullabilitySuffix) { + if (_withNullability) { + switch (nullabilitySuffix) { + case NullabilitySuffix.question: + _write('?'); + case NullabilitySuffix.star: + _write('*'); + case NullabilitySuffix.none: + } + } + } + + void _writeType(DartType type) { + (type as TypeImpl).appendTo(this); + } + + void _writeTypeArguments(List typeArguments) { + if (typeArguments.isEmpty) { + return; + } + + _write('<'); + for (var i = 0; i < typeArguments.length; i++) { + if (i != 0) { + _write(', '); + } + (typeArguments[i] as TypeImpl).appendTo(this); + } + _write('>'); + } + + void _writeTypeIfNotObject(String prefix, DartType? type) { + if (type != null && !type.isDartCoreObject) { + _write(prefix); + _writeType(type); + } + } + + void _writeTypeParameters(List elements) { + if (elements.isEmpty) return; + + _write('<'); + for (var i = 0; i < elements.length; i++) { + if (i != 0) { + _write(', '); + } + (elements[i] as TypeParameterElementImpl).appendTo(this); + } + _write('>'); + } + + void _writeTypes(List types) { + for (var i = 0; i < types.length; i++) { + if (i != 0) { + _write(', '); + } + _writeType(types[i]); + } + } + + void _writeTypesIfNotEmpty(String prefix, List types) { + if (types.isNotEmpty) { + _write(prefix); + _writeTypes(types); + } + } + + void _writeWithoutDelimiters( + ParameterElement element, { + required bool forElement, + }) { + if (element.isRequiredNamed) { + _write('required '); + } + + _writeType(element.type); + + if (forElement || element.isNamed) { + _write(' '); + _write(element.displayName); + } + + if (forElement) { + var defaultValueCode = element.defaultValueCode; + if (defaultValueCode != null) { + _write(' = '); + _write(defaultValueCode); + } + } + } + + static FunctionType _uniqueTypeParameters(FunctionType type) { + if (type.typeFormals.isEmpty) { + return type; + } + + var referencedTypeParameters = {}; + + void collectTypeParameters(DartType? type) { + if (type is TypeParameterType) { + referencedTypeParameters.add(type.element); + } else if (type is FunctionType) { + for (var typeParameter in type.typeFormals) { + collectTypeParameters(typeParameter.bound); + } + for (var parameter in type.parameters) { + collectTypeParameters(parameter.type); + } + collectTypeParameters(type.returnType); + } else if (type is InterfaceType) { + for (var typeArgument in type.typeArguments) { + collectTypeParameters(typeArgument); + } + } + } + + collectTypeParameters(type); + referencedTypeParameters.removeAll(type.typeFormals); + + var namesToAvoid = {}; + for (var typeParameter in referencedTypeParameters) { + namesToAvoid.add(typeParameter.displayName); + } + + var newTypeParameters = []; + for (var typeParameter in type.typeFormals) { + var name = typeParameter.name; + for (var counter = 0; !namesToAvoid.add(name); counter++) { + const unicodeSubscriptZero = 0x2080; + const unicodeZero = 0x30; + + var subscript = String.fromCharCodes('$counter'.codeUnits.map((n) { + return unicodeSubscriptZero + (n - unicodeZero); + })); + + name = typeParameter.name + subscript; + } + + var newTypeParameter = TypeParameterElementImpl(name, -1); + newTypeParameter.bound = typeParameter.bound; + newTypeParameters.add(newTypeParameter); + } + + return replaceTypeParameters(type as FunctionTypeImpl, newTypeParameters); + } +} + +enum _WriteFormalParameterKind { requiredPositional, optionalPositional, named } diff --git a/packages/reactive_forms_generator/lib/src/reactive_form_generator_method.dart b/packages/reactive_forms_generator/lib/src/reactive_form_generator_method.dart index d5f6dc94..c938a74f 100644 --- a/packages/reactive_forms_generator/lib/src/reactive_form_generator_method.dart +++ b/packages/reactive_forms_generator/lib/src/reactive_form_generator_method.dart @@ -1,11 +1,13 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:code_builder/code_builder.dart'; import 'package:reactive_forms_generator/src/extensions.dart'; +import 'package:reactive_forms_generator/src/types.dart'; abstract class ReactiveFormGeneratorMethod { final ParameterElement field; + final bool output; - ReactiveFormGeneratorMethod(this.field); + ReactiveFormGeneratorMethod(this.field, this.output); Method? method() { if (field.isFormGroup) { @@ -23,6 +25,27 @@ abstract class ReactiveFormGeneratorMethod { return formControlMethod(); } + bool get toOutput { + try { + if (field.isFormGroup) { + return false; + } + + if (field.isFormArray) { + return false; + } + + if (field.isFormGroupArray) { + return false; + } + + return field.annotationParams(formControlChecker).hasRequiredValidator && + output; + } catch (e) { + return false; + } + } + Method? formControlMethod() => defaultMethod(); Method? formGroupMethod() => defaultMethod(); diff --git a/packages/reactive_forms_generator/lib/src/reactive_forms/reactive_form_builder.dart b/packages/reactive_forms_generator/lib/src/reactive_forms/reactive_form_builder.dart index 2caa5e81..76850533 100644 --- a/packages/reactive_forms_generator/lib/src/reactive_forms/reactive_form_builder.dart +++ b/packages/reactive_forms_generator/lib/src/reactive_forms/reactive_form_builder.dart @@ -11,6 +11,8 @@ class ReactiveFormBuilder { String get _baseName => reactiveForm.reactiveInheritedStreamer.formGenerator.className; + String get _log => reactiveForm.reactiveInheritedStreamer.formGenerator.log; + ClassElement get _element => reactiveForm.reactiveInheritedStreamer.formGenerator.element; @@ -161,6 +163,34 @@ class ReactiveFormBuilder { widget.initState?.call(context, _formModel); + _logSubscription = $_log.onRecord.listen((LogRecord e) { + // use `dumpErrorToConsole` for severe messages to ensure that severe + // exceptions are formatted consistently with other Flutter examples and + // avoids printing duplicate exceptions + if (e.level >= Level.SEVERE) { + final Object? error = e.error; + FlutterError.dumpErrorToConsole( + FlutterErrorDetails( + exception: error is Exception ? error : Exception(error), + stack: e.stackTrace, + library: e.loggerName, + context: ErrorDescription(e.message), + ), + ); + } else { + log( + e.message, + time: e.time, + sequenceNumber: e.sequenceNumber, + level: e.level.value, + name: e.loggerName, + zone: e.zone, + error: e.error, + stackTrace: e.stackTrace, + ); + } + }); + super.initState(); '''), ), @@ -192,6 +222,7 @@ class ReactiveFormBuilder { ..returns = const Reference('void') ..body = const Code(''' _formModel.form.dispose(); + _logSubscription?.cancel(); super.dispose(); '''), ), @@ -243,6 +274,11 @@ class ReactiveFormBuilder { .reactiveInheritedStreamer.formGenerator.classNameFull, ), ), + Field( + (b) => b + ..name = '_logSubscription' + ..type = const Reference('StreamSubscription?'), + ), ], ) ..methods.addAll(_stateMethods), diff --git a/packages/reactive_forms_generator/lib/src/reactive_forms/reactive_form_update_value_method.dart b/packages/reactive_forms_generator/lib/src/reactive_forms/reactive_form_update_value_method.dart index 90e7885d..0a4591db 100644 --- a/packages/reactive_forms_generator/lib/src/reactive_forms/reactive_form_update_value_method.dart +++ b/packages/reactive_forms_generator/lib/src/reactive_forms/reactive_form_update_value_method.dart @@ -3,7 +3,7 @@ import 'package:reactive_forms_generator/src/extensions.dart'; import 'package:reactive_forms_generator/src/reactive_form_generator_method.dart'; class ReactiveFormUpdateValueMethod extends ReactiveFormGeneratorMethod { - ReactiveFormUpdateValueMethod(super.field); + ReactiveFormUpdateValueMethod(super.field, super.output); @override Method formGroupArrayMethod() { @@ -60,7 +60,7 @@ class ReactiveFormUpdateValueMethod extends ReactiveFormGeneratorMethod { Method defaultMethod() { return methodEntity.rebuild( (b) => b.body = Code( - '${field.fieldControlName}${field.nullabilitySuffix}.updateValue(value, updateParent: updateParent, emitEvent:emitEvent);', + '${field.fieldControlName}${toOutput ? '' : field.nullabilitySuffix}.updateValue(value, updateParent: updateParent, emitEvent:emitEvent);', ), ); } diff --git a/packages/reactive_forms_generator/lib/src/reactive_forms/reactive_forms_clear_method.dart b/packages/reactive_forms_generator/lib/src/reactive_forms/reactive_forms_clear_method.dart index 2025476f..f7f0fe35 100644 --- a/packages/reactive_forms_generator/lib/src/reactive_forms/reactive_forms_clear_method.dart +++ b/packages/reactive_forms_generator/lib/src/reactive_forms/reactive_forms_clear_method.dart @@ -3,7 +3,7 @@ import 'package:reactive_forms_generator/src/extensions.dart'; import 'package:reactive_forms_generator/src/reactive_form_generator_method.dart'; class ReactiveFormClearMethod extends ReactiveFormGeneratorMethod { - ReactiveFormClearMethod(super.field); + ReactiveFormClearMethod(super.field, super.output); @override Method? formGroupArrayMethod() { diff --git a/packages/reactive_forms_generator/lib/src/reactive_forms/reactive_forms_insert_method.dart b/packages/reactive_forms_generator/lib/src/reactive_forms/reactive_forms_insert_method.dart index 07c782f3..3d320685 100644 --- a/packages/reactive_forms_generator/lib/src/reactive_forms/reactive_forms_insert_method.dart +++ b/packages/reactive_forms_generator/lib/src/reactive_forms/reactive_forms_insert_method.dart @@ -3,7 +3,7 @@ import 'package:reactive_forms_generator/src/extensions.dart'; import 'package:reactive_forms_generator/src/reactive_form_generator_method.dart'; class ReactiveFormInsertMethod extends ReactiveFormGeneratorMethod { - ReactiveFormInsertMethod(super.field); + ReactiveFormInsertMethod(super.field, super.output); @override Method? formGroupArrayMethod() { diff --git a/packages/reactive_forms_generator/lib/src/reactive_forms/reactive_forms_patch_value_method.dart b/packages/reactive_forms_generator/lib/src/reactive_forms/reactive_forms_patch_value_method.dart index 36043b88..ad417a32 100644 --- a/packages/reactive_forms_generator/lib/src/reactive_forms/reactive_forms_patch_value_method.dart +++ b/packages/reactive_forms_generator/lib/src/reactive_forms/reactive_forms_patch_value_method.dart @@ -3,7 +3,7 @@ import 'package:reactive_forms_generator/src/extensions.dart'; import 'package:reactive_forms_generator/src/reactive_form_generator_method.dart'; class ReactiveFormPatchValueMethod extends ReactiveFormGeneratorMethod { - ReactiveFormPatchValueMethod(super.field); + ReactiveFormPatchValueMethod(super.field, super.output); @override Method formGroupArrayMethod() { @@ -42,7 +42,7 @@ class ReactiveFormPatchValueMethod extends ReactiveFormGeneratorMethod { Method defaultMethod() { return methodEntity.rebuild( (b) => b.body = Code( - '${field.fieldControlName}${field.nullabilitySuffix}.patchValue(value, updateParent: updateParent, emitEvent:emitEvent);', + '${field.fieldControlName}${toOutput ? '' : field.nullabilitySuffix}.patchValue(value, updateParent: updateParent, emitEvent:emitEvent);', ), ); } diff --git a/packages/reactive_forms_generator/lib/src/reactive_forms_generator/contains_method.dart b/packages/reactive_forms_generator/lib/src/reactive_forms_generator/contains_method.dart index fe1ea878..3adf9cd8 100644 --- a/packages/reactive_forms_generator/lib/src/reactive_forms_generator/contains_method.dart +++ b/packages/reactive_forms_generator/lib/src/reactive_forms_generator/contains_method.dart @@ -3,7 +3,7 @@ import 'package:reactive_forms_generator/src/extensions.dart'; import 'package:reactive_forms_generator/src/reactive_form_generator_method.dart'; class ContainsMethod extends ReactiveFormGeneratorMethod { - ContainsMethod(super.field); + ContainsMethod(super.field, super.output); @override Method? defaultMethod() => Method( diff --git a/packages/reactive_forms_generator/lib/src/reactive_forms_generator/control_method.dart b/packages/reactive_forms_generator/lib/src/reactive_forms_generator/control_method.dart index 7c5cf6d3..a88c9483 100644 --- a/packages/reactive_forms_generator/lib/src/reactive_forms_generator/control_method.dart +++ b/packages/reactive_forms_generator/lib/src/reactive_forms_generator/control_method.dart @@ -5,7 +5,7 @@ import 'package:reactive_forms_generator/src/extensions.dart'; import 'package:reactive_forms_generator/src/reactive_form_generator_method.dart'; class ControlMethod extends ReactiveFormGeneratorMethod { - ControlMethod(super.field); + ControlMethod(super.field, super.output); @override Method? formGroupMethod() { @@ -97,19 +97,22 @@ class ControlMethod extends ReactiveFormGeneratorMethod { @override Method? formControlMethod() { - String displayType = field.type.getDisplayString(withNullability: true); + String displayType = + field.type.getDisplayString(withNullability: !toOutput); // we need to trim last NullabilitySuffix.question cause FormControl modifies // generic T => T? - if (field.type.nullabilitySuffix == NullabilitySuffix.question) { + if (field.type.nullabilitySuffix == NullabilitySuffix.question && + !toOutput) { displayType = displayType.substring(0, displayType.length - 1); } - final reference = 'FormControl<$displayType>${field.nullabilitySuffix}'; + final reference = + 'FormControl<$displayType>${toOutput ? '' : field.nullabilitySuffix}'; String body = 'form.control(${field.fieldControlPath}()) as $reference'; - if (field.isNullable) { + if (field.isNullable && !toOutput) { body = '${field.containsMethodName} ? form.control(${field.fieldControlPath}()) as $reference : null'; } diff --git a/packages/reactive_forms_generator/lib/src/reactive_forms_generator/control_path_method.dart b/packages/reactive_forms_generator/lib/src/reactive_forms_generator/control_path_method.dart index a8cf0336..fd3c5bf4 100644 --- a/packages/reactive_forms_generator/lib/src/reactive_forms_generator/control_path_method.dart +++ b/packages/reactive_forms_generator/lib/src/reactive_forms_generator/control_path_method.dart @@ -4,7 +4,7 @@ import 'package:reactive_forms_generator/src/library_builder.dart'; import 'package:reactive_forms_generator/src/reactive_form_generator_method.dart'; class ControlPathMethod extends ReactiveFormGeneratorMethod { - ControlPathMethod(super.field); + ControlPathMethod(super.field, super.output); @override Method? defaultMethod() => Method( diff --git a/packages/reactive_forms_generator/lib/src/reactive_forms_generator/control_private_method.dart b/packages/reactive_forms_generator/lib/src/reactive_forms_generator/control_private_method.dart index 0293ae28..99d8c455 100644 --- a/packages/reactive_forms_generator/lib/src/reactive_forms_generator/control_private_method.dart +++ b/packages/reactive_forms_generator/lib/src/reactive_forms_generator/control_private_method.dart @@ -5,7 +5,7 @@ import 'package:reactive_forms_generator/src/extensions.dart'; import 'package:reactive_forms_generator/src/reactive_form_generator_method.dart'; class ControlPrivateMethod extends ReactiveFormGeneratorMethod { - ControlPrivateMethod(super.field); + ControlPrivateMethod(super.field, super.output); @override Method? formGroupMethod() { diff --git a/packages/reactive_forms_generator/lib/src/reactive_forms_generator/control_set_enabled_method.dart b/packages/reactive_forms_generator/lib/src/reactive_forms_generator/control_set_enabled_method.dart index 0ff4e799..8bb3a308 100644 --- a/packages/reactive_forms_generator/lib/src/reactive_forms_generator/control_set_enabled_method.dart +++ b/packages/reactive_forms_generator/lib/src/reactive_forms_generator/control_set_enabled_method.dart @@ -3,7 +3,7 @@ import 'package:reactive_forms_generator/src/extensions.dart'; import 'package:reactive_forms_generator/src/reactive_form_generator_method.dart'; class ControlSetDisableMethod extends ReactiveFormGeneratorMethod { - ControlSetDisableMethod(super.field); + ControlSetDisableMethod(super.field, super.output); @override Method? defaultMethod() { @@ -12,13 +12,13 @@ class ControlSetDisableMethod extends ReactiveFormGeneratorMethod { ..body = Code( ''' if(disabled) { - ${field.fieldControlName}${field.nullabilitySuffix}.markAsDisabled( + ${field.fieldControlName}${toOutput ? '' : field.nullabilitySuffix}.markAsDisabled( updateParent: updateParent, emitEvent: emitEvent, ); } else { - ${field.fieldControlName}${field.nullabilitySuffix}.markAsEnabled( + ${field.fieldControlName}${toOutput ? '' : field.nullabilitySuffix}.markAsEnabled( updateParent: updateParent, emitEvent: emitEvent, ); diff --git a/packages/reactive_forms_generator/lib/src/reactive_forms_generator/errors_method.dart b/packages/reactive_forms_generator/lib/src/reactive_forms_generator/errors_method.dart index 1929568a..9bb1975c 100644 --- a/packages/reactive_forms_generator/lib/src/reactive_forms_generator/errors_method.dart +++ b/packages/reactive_forms_generator/lib/src/reactive_forms_generator/errors_method.dart @@ -3,7 +3,7 @@ import 'package:reactive_forms_generator/src/extensions.dart'; import 'package:reactive_forms_generator/src/reactive_form_generator_method.dart'; class ErrorsMethod extends ReactiveFormGeneratorMethod { - ErrorsMethod(super.field); + ErrorsMethod(super.field, super.output); @override Method? defaultMethod() => Method( @@ -13,7 +13,7 @@ class ErrorsMethod extends ReactiveFormGeneratorMethod { ..type = MethodType.getter ..returns = Reference('Map${field.nullabilitySuffix}') ..body = Code( - '${field.fieldControlName}${field.nullabilitySuffix}.errors', + '${field.fieldControlName}${toOutput ? '' : field.nullabilitySuffix}.errors', ), ); } diff --git a/packages/reactive_forms_generator/lib/src/reactive_forms_generator/extended_control_method.dart b/packages/reactive_forms_generator/lib/src/reactive_forms_generator/extended_control_method.dart index 8b0164b1..7b6e64dd 100644 --- a/packages/reactive_forms_generator/lib/src/reactive_forms_generator/extended_control_method.dart +++ b/packages/reactive_forms_generator/lib/src/reactive_forms_generator/extended_control_method.dart @@ -4,7 +4,7 @@ import 'package:reactive_forms_generator/src/extensions.dart'; import 'package:reactive_forms_generator/src/reactive_form_generator_method.dart'; class ExtendedControlMethod extends ReactiveFormGeneratorMethod { - ExtendedControlMethod(super.field); + ExtendedControlMethod(super.field, super.output); @override Method? formGroupArrayMethod() { diff --git a/packages/reactive_forms_generator/lib/src/reactive_forms_generator/field_value_method.dart b/packages/reactive_forms_generator/lib/src/reactive_forms_generator/field_value_method.dart index 7f041847..d91e0140 100644 --- a/packages/reactive_forms_generator/lib/src/reactive_forms_generator/field_value_method.dart +++ b/packages/reactive_forms_generator/lib/src/reactive_forms_generator/field_value_method.dart @@ -1,11 +1,17 @@ +// ignore_for_file: implementation_imports + +import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/nullability_suffix.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:code_builder/code_builder.dart'; +import 'package:analyzer/src/dart/element/type.dart'; import 'package:reactive_forms_generator/src/extensions.dart'; +import 'package:reactive_forms_generator/src/output/x.dart'; import 'package:reactive_forms_generator/src/reactive_form_generator_method.dart'; +import 'package:reactive_forms_generator/src/types.dart'; class FieldValueMethod extends ReactiveFormGeneratorMethod { - FieldValueMethod(super.field); + FieldValueMethod(super.field, super.output); @override Method? formGroupMethod() { @@ -38,12 +44,14 @@ class FieldValueMethod extends ReactiveFormGeneratorMethod { @override Method? defaultMethod() { - String code = '${field.fieldControlName}${field.nullabilitySuffix}.value'; - String codeTypeCast = ' as ${field.type}'; + String code = + '${field.fieldControlName}${toOutput ? '' : field.nullabilitySuffix}.value'; + String codeTypeCast = + ' as ${field.type.getDisplayString(withNullability: !toOutput)}'; // do not add additional cast if the field is nullable to avoid // unnecessary_cast notes - if (field.type.nullabilitySuffix == NullabilitySuffix.none) { + if (field.type.nullabilitySuffix == NullabilitySuffix.none || toOutput) { if (field.hasDefaultValue) { final constantValueObject = field.computeConstantValue(); if (constantValueObject?.type?.isDartCoreString ?? false) { @@ -63,10 +71,46 @@ class FieldValueMethod extends ReactiveFormGeneratorMethod { } Method get methodEntity => Method( - (b) => b - ..name = field.fieldValueName - ..lambda = true - ..type = MethodType.getter - ..returns = Reference(field.type.toString()), + (b) { + b + ..name = field.fieldValueName + ..lambda = true + ..type = MethodType.getter + ..returns = Reference( + output ? field.toReferenceType : field.type.toString(), + ); + }, ); } + +extension Care on ClassElement { + String get toReferenceType { + var builder = ElementDisplayStringBuilder2(withNullability: true); + (thisType as TypeImpl).appendTo(builder); + return builder.toString(); + } +} + +extension Pare on ParameterElement { + String get toReferenceType { + if (hasRfControlAnnotation && + annotationParams(formControlChecker).hasRequiredValidator) { + return type.getDisplayString(withNullability: false); + } + + var builder = ElementDisplayStringBuilder2(withNullability: true); + (type as TypeImpl).appendTo(builder); + return builder.toString(); + } +} + +// class RfElementDisplayStringBuilder extends ElementDisplayStringBuilder { +// RfElementDisplayStringBuilder({required super.withNullability}); +// +// @override +// void writeInterfaceType(InterfaceType type) { +// _write(type.element.name); +// _writeTypeArguments(type.typeArguments); +// _writeNullability(type.nullabilitySuffix); +// } +// } diff --git a/packages/reactive_forms_generator/lib/src/reactive_forms_generator/focus_method.dart b/packages/reactive_forms_generator/lib/src/reactive_forms_generator/focus_method.dart index 73f453cc..d3757a8d 100644 --- a/packages/reactive_forms_generator/lib/src/reactive_forms_generator/focus_method.dart +++ b/packages/reactive_forms_generator/lib/src/reactive_forms_generator/focus_method.dart @@ -3,7 +3,7 @@ import 'package:reactive_forms_generator/src/extensions.dart'; import 'package:reactive_forms_generator/src/reactive_form_generator_method.dart'; class FocusMethod extends ReactiveFormGeneratorMethod { - FocusMethod(super.field); + FocusMethod(super.field, super.output); @override Method? defaultMethod() => Method( diff --git a/packages/reactive_forms_generator/lib/src/reactive_forms_generator/remove_method.dart b/packages/reactive_forms_generator/lib/src/reactive_forms_generator/remove_method.dart index 1b800173..9a63b8e4 100644 --- a/packages/reactive_forms_generator/lib/src/reactive_forms_generator/remove_method.dart +++ b/packages/reactive_forms_generator/lib/src/reactive_forms_generator/remove_method.dart @@ -4,7 +4,7 @@ import 'package:reactive_forms_generator/src/extensions.dart'; import 'package:reactive_forms_generator/src/reactive_form_generator_method.dart'; class RemoveMethod extends ReactiveFormGeneratorMethod { - RemoveMethod(super.field); + RemoveMethod(super.field, super.output); @override Method? defaultMethod() { diff --git a/packages/reactive_forms_generator/lib/src/reactive_forms_generator/reset_method.dart b/packages/reactive_forms_generator/lib/src/reactive_forms_generator/reset_method.dart index d927d533..c9d9af14 100644 --- a/packages/reactive_forms_generator/lib/src/reactive_forms_generator/reset_method.dart +++ b/packages/reactive_forms_generator/lib/src/reactive_forms_generator/reset_method.dart @@ -3,7 +3,7 @@ import 'package:reactive_forms_generator/src/extensions.dart'; import 'package:reactive_forms_generator/src/reactive_form_generator_method.dart'; class ResetMethod extends ReactiveFormGeneratorMethod { - ResetMethod(super.field); + ResetMethod(super.field, super.output); @override Method? formGroupArrayMethod() { @@ -40,7 +40,7 @@ class ResetMethod extends ReactiveFormGeneratorMethod { return methodEntity.rebuild( (b) => b ..body = Code( - '''${field.fieldControlName}${field.nullabilitySuffix}.reset( + '''${field.fieldControlName}${toOutput ? '' : field.nullabilitySuffix}.reset( value: value, updateParent: updateParent, emitEvent:emitEvent) diff --git a/packages/reactive_forms_generator/lib/src/types.dart b/packages/reactive_forms_generator/lib/src/types.dart index 6d652d17..3ac525bf 100644 --- a/packages/reactive_forms_generator/lib/src/types.dart +++ b/packages/reactive_forms_generator/lib/src/types.dart @@ -1,85 +1,137 @@ +// ignore_for_file: implementation_imports import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/element/element.dart'; +import 'package:reactive_forms_generator/src/form_elements/form_element_generator.dart'; import 'package:source_gen/source_gen.dart'; +import 'package:analyzer/src/dart/element/element.dart'; +import 'package:analyzer/src/dart/ast/ast.dart'; const _formChecker = TypeChecker.fromUrl( 'package:reactive_forms_annotations/src/reactive_form_annotation.dart#ReactiveFormAnnotation', ); -const _formCheckerRf = TypeChecker.fromUrl( - 'package:reactive_forms_annotations/src/reactive_form_annotation.dart#Rf', -); - const formControlChecker = TypeChecker.fromUrl( 'package:reactive_forms_annotations/src/form_control_annotation.dart#FormControlAnnotation', ); -const formControlCheckerRf = TypeChecker.fromUrl( - 'package:reactive_forms_annotations/src/form_control_annotation.dart#RfControl', -); - const formArrayChecker = TypeChecker.fromUrl( 'package:reactive_forms_annotations/src/form_array_annotation.dart#FormArrayAnnotation', ); -const formArrayCheckerRf = TypeChecker.fromUrl( - 'package:reactive_forms_annotations/src/form_array_annotation.dart#RfArray', -); - const formGroupChecker = TypeChecker.fromUrl( 'package:reactive_forms_annotations/src/form_group_annotation.dart#FormGroupAnnotation', ); -const formGroupCheckerRf = TypeChecker.fromUrl( - 'package:reactive_forms_annotations/src/form_group_annotation.dart#RfGroup', -); - extension LibraryReaderExt on LibraryReader { Iterable get rfAnnotated { return annotatedWith(_formChecker); } } +extension MapExt on Map { + bool get hasRequiredValidator { + return containsKey(FormElementGenerator.validatorKey) && + this[FormElementGenerator.validatorKey] + ?.contains('RequiredValidator()') == + true; + } +} + +extension ClassDeclarationImplExt on ClassDeclarationImpl { + bool get hasRfGroupAnnotation { + return metadata.any((e) => e.name.toString() == 'RfGroup'); + } + + bool get hasRfAnnotation { + return metadata.any((e) => e.name.toString() == 'Rf'); + } +} + extension ElementRfExt on Element { bool get hasRfGroupAnnotation { - return formGroupChecker.hasAnnotationOfExact(this) || - formGroupCheckerRf.hasAnnotationOfExact(this); + return formGroupChecker.hasAnnotationOfExact(this); + } + + bool get hasRfAnnotation { + return _formChecker.hasAnnotationOfExact(this); + } + + bool get hasRfControlAnnotation { + return formControlChecker.hasAnnotationOfExact(this); + } + + Map annotationParams(TypeChecker? typeChecker) { + final result = {}; + final annotation = typeChecker?.firstAnnotationOf(this); + try { + if (annotation != null) { + for (final meta in metadata) { + final obj = meta.computeConstantValue()!; + + if (typeChecker?.isExactlyType(obj.type!) == true) { + final argumentList = (meta as ElementAnnotationImpl) + .annotationAst + .arguments as ArgumentListImpl; + for (var argument in argumentList.arguments) { + final argumentNamedExpression = argument as NamedExpressionImpl; + result.addEntries( + [ + MapEntry( + argumentNamedExpression.name.label.toSource(), + argumentNamedExpression.expression.toSource(), + ), + ], + ); + } + } + } + } + + return result; + } catch (e) { + return result; + } } } extension ParameterElementAnnotationExt on ParameterElement { bool get hasRfAnnotation { - return _formChecker.hasAnnotationOfExact(this) || - _formCheckerRf.hasAnnotationOfExact(this); + return _formChecker.hasAnnotationOfExact(this); } bool get hasRfArrayAnnotation { return formArrayChecker.hasAnnotationOfExact( - this, - throwOnUnresolved: false, - ) || - formArrayCheckerRf.hasAnnotationOfExact( - this, - throwOnUnresolved: false, - ); + this, + throwOnUnresolved: false, + ); } } extension FieldElementAnnotationExt on FieldElement { bool get hasRfAnnotation { - return _formChecker.hasAnnotationOfExact(this) || - _formCheckerRf.hasAnnotationOfExact(this); + return _formChecker.hasAnnotationOfExact(this); } } extension ClassElementAnnotationExt on ClassElement { bool get hasRfAnnotation { - return _formChecker.hasAnnotationOfExact(this) || - _formCheckerRf.hasAnnotationOfExact(this); + return _formChecker.hasAnnotationOfExact(this); } DartObject? get rfAnnotation { - return _formChecker.firstAnnotationOfExact(this) ?? - _formCheckerRf.firstAnnotationOfExact(this); + return _formChecker.firstAnnotationOfExact(this); + } + + bool get output { + try { + if (hasRfAnnotation) { + final annotation = rfAnnotation; + return annotation?.getField('output')?.toBoolValue() ?? false; + } + + return false; + } catch (e) { + return false; + } } } diff --git a/packages/reactive_forms_generator/pubspec.yaml b/packages/reactive_forms_generator/pubspec.yaml index c2ae1ffe..258c0ebb 100644 --- a/packages/reactive_forms_generator/pubspec.yaml +++ b/packages/reactive_forms_generator/pubspec.yaml @@ -15,7 +15,7 @@ repository: https://github.com/artflutter/reactive_forms_generator # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 5.0.5 +version: 6.0.0-beta.10 environment: sdk: ">=3.0.0 <4.0.0" @@ -23,10 +23,11 @@ environment: dependencies: build: ^2.3.1 source_gen: ^1.5.0 - analyzer: ">=5.12.0 <7.0.0" + analyzer: ">=6.4.1 <7.0.0" path: ^1.8.1 build_runner: ^2.4.9 code_builder: ^4.10.0 + collection: ">=1.0.0 <2.0.0" dart_style: ^2.3.6 recase: ^4.1.0