diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e0c5632..131bbda0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +# 16.0.4 + +## Fixes + +- Add missing properties to `ReactiveSwitchListTile.adaptative()` widget. +- Add `showError()` to `ReactiveCheckbox` and `ReactiveCheckboxListTile` widgets. This does not +display any error messages but it is now compatible with the Flutter builtin behavior of Checkboxes +when Material 3 is enabled (`ThemeData(useMaterial3: true)`) in the active App Theme. + +## Enhances + +- Update `Readme.md` file with testing examples in the section +`ReactiveForm vs ReactiveFormBuilder which one?` + # 16.0.3 ## Fixes diff --git a/example/android/build.gradle b/example/android/build.gradle index 2ed02f9a..b5ae9fa3 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -26,6 +26,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/example/lib/main.dart b/example/lib/main.dart index b5954542..8717eda8 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -40,7 +40,14 @@ class ReactiveFormsApp extends StatelessWidget { 'uniqueEmail': (_) => 'This email is already in use', }, child: MaterialApp( - theme: customTheme, + theme: ThemeData( + useMaterial3: true, + inputDecorationTheme: const InputDecorationTheme( + border: OutlineInputBorder(), + floatingLabelBehavior: FloatingLabelBehavior.auto, + alignLabelWithHint: true, + ), + ), routes: { Routes.complex: (_) => ComplexSample(), Routes.simple: (_) => SimpleSample(), @@ -55,11 +62,3 @@ class ReactiveFormsApp extends StatelessWidget { ); } } - -final customTheme = ThemeData.light().copyWith( - inputDecorationTheme: const InputDecorationTheme( - border: OutlineInputBorder(), - floatingLabelBehavior: FloatingLabelBehavior.auto, - alignLabelWithHint: true, - ), -); diff --git a/example/lib/samples/complex_sample.dart b/example/lib/samples/complex_sample.dart index c38093b3..1078a5b0 100644 --- a/example/lib/samples/complex_sample.dart +++ b/example/lib/samples/complex_sample.dart @@ -49,6 +49,7 @@ class ComplexSample extends StatelessWidget { form: buildForm, builder: (context, form, child) { return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ ReactiveTextField( formControlName: 'email', diff --git a/example/lib/samples/login_sample.dart b/example/lib/samples/login_sample.dart index 95bb56a3..21aedec1 100644 --- a/example/lib/samples/login_sample.dart +++ b/example/lib/samples/login_sample.dart @@ -8,7 +8,10 @@ class LoginSample extends StatelessWidget { validators: [Validators.required, Validators.email], ), 'password': ['', Validators.required, Validators.minLength(8)], - 'rememberMe': false, + 'acceptTerms': FormControl( + value: false, + validators: [Validators.requiredTrue], + ), }); @override @@ -19,6 +22,7 @@ class LoginSample extends StatelessWidget { form: buildForm, builder: (context, form, child) { return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ ReactiveTextField( formControlName: 'email', @@ -55,11 +59,10 @@ class LoginSample extends StatelessWidget { errorStyle: TextStyle(height: 0.7), ), ), - Row( - children: [ - ReactiveCheckbox(formControlName: 'rememberMe'), - const Text('Remember me') - ], + const SizedBox(height: 16.0), + ReactiveCheckboxListTile( + formControlName: 'acceptTerms', + title: const Text('Accept terms & conditions'), ), const SizedBox(height: 16.0), ElevatedButton( @@ -72,12 +75,16 @@ class LoginSample extends StatelessWidget { }, child: const Text('Sign Up'), ), + const SizedBox(height: 16.0), ElevatedButton( - onPressed: () => form.resetState({ - 'email': ControlState(value: null), - 'password': ControlState(value: null), - 'rememberMe': ControlState(value: false), - }, removeFocus: true), + onPressed: () => form.resetState( + { + 'email': ControlState(value: null), + 'password': ControlState(value: null), + 'acceptTerms': ControlState(value: false), + }, + removeFocus: true, + ), child: const Text('Reset all'), ), ], diff --git a/lib/src/widgets/reactive_checkbox.dart b/lib/src/widgets/reactive_checkbox.dart index c8577f30..42cc0541 100644 --- a/lib/src/widgets/reactive_checkbox.dart +++ b/lib/src/widgets/reactive_checkbox.dart @@ -7,21 +7,19 @@ import 'package:reactive_forms/reactive_forms.dart'; /// This is a convenience widget that wraps a [Checkbox] widget in a /// [ReactiveCheckbox]. -/// -/// Can optionally provide a [formControl] to bind this widget to a control. -/// -/// Can optionally provide a [formControlName] to bind this ReactiveFormField -/// to a [FormControl]. -/// -/// Must provide one of the arguments [formControl] or a [formControlName], -/// but not both at the same time. -/// -/// For documentation about the various parameters, see the [Checkbox] class -/// and [Checkbox], the constructor. class ReactiveCheckbox extends ReactiveFocusableFormField { /// Create an instance of a [ReactiveCheckbox]. /// - /// The [formControlName] arguments must not be null. + /// Can optionally provide a [formControl] to bind this widget to a control. + /// + /// Can optionally provide a [formControlName] to bind this ReactiveFormField + /// to a [FormControl]. + /// + /// Must provide one of the arguments [formControl] or a [formControlName], + /// but not both at the same time. + /// + /// For documentation about the various parameters, see the [Checkbox] class + /// and the [Checkbox] constructor. ReactiveCheckbox({ Key? key, String? formControlName, @@ -42,11 +40,15 @@ class ReactiveCheckbox extends ReactiveFocusableFormField { OutlinedBorder? shape, BorderSide? side, ReactiveFormFieldCallback? onChanged, + ShowErrorsFunction? showErrors, }) : super( key: key, formControl: formControl, formControlName: formControlName, focusNode: focusNode, + showErrors: showErrors ?? + (control) => + control.invalid && (control.dirty || control.touched), builder: (field) { return Checkbox( value: tristate ? field.value : field.value ?? false, @@ -65,7 +67,7 @@ class ReactiveCheckbox extends ReactiveFocusableFormField { focusNode: field.focusNode, shape: shape, side: side, - isError: !field.control.valid, + isError: field.errorText != null, onChanged: field.control.enabled ? (value) { field.didChange(value); diff --git a/lib/src/widgets/reactive_checkbox_list_tile.dart b/lib/src/widgets/reactive_checkbox_list_tile.dart index af3a8460..3417d22f 100644 --- a/lib/src/widgets/reactive_checkbox_list_tile.dart +++ b/lib/src/widgets/reactive_checkbox_list_tile.dart @@ -7,23 +7,19 @@ import 'package:reactive_forms/reactive_forms.dart'; /// This is a convenience widget that wraps a [CheckboxListTile] widget in a /// [ReactiveCheckboxListTile]. -/// -/// Can optionally provide a [formControl] to bind this widget to a control. -/// -/// Can optionally provide a [formControlName] to bind this ReactiveFormField -/// to a [FormControl]. -/// -/// Must provide one of the arguments [formControl] or a [formControlName], -/// but not both at the same time. -/// -/// For documentation about the various parameters, see the [CheckboxListTile] -/// class and [CheckboxListTile], the constructor. class ReactiveCheckboxListTile extends ReactiveFocusableFormField { /// Create an instance of a [ReactiveCheckbox]. /// - /// The [formControlName] arguments must not be null. + /// Can optionally provide a [formControl] to bind this widget to a control. /// - /// See also [CheckboxListTile] + /// Can optionally provide a [formControlName] to bind this ReactiveFormField + /// to a [FormControl]. + /// + /// Must provide one of the arguments [formControl] or a [formControlName], + /// but not both at the same time. + /// + /// For documentation about the various parameters, see the [CheckboxListTile] + /// class and the [CheckboxListTile] constructor. ReactiveCheckboxListTile({ Key? key, String? formControlName, @@ -56,11 +52,15 @@ class ReactiveCheckboxListTile extends ReactiveFocusableFormField { double? splashRadius, MaterialTapTargetSize? materialTapTargetSize, ValueChanged? onFocusChange, + ShowErrorsFunction? showErrors, }) : super( key: key, formControl: formControl, formControlName: formControlName, focusNode: focusNode, + showErrors: showErrors ?? + (control) => + control.invalid && (control.dirty || control.touched), builder: (field) { return CheckboxListTile( value: tristate ? field.value : field.value ?? false, @@ -73,7 +73,7 @@ class ReactiveCheckboxListTile extends ReactiveFocusableFormField { activeColor: activeColor, checkColor: checkColor, onFocusChange: onFocusChange, - isError: !field.control.valid, + isError: field.errorText != null, title: title, subtitle: subtitle, isThreeLine: isThreeLine, @@ -92,6 +92,7 @@ class ReactiveCheckboxListTile extends ReactiveFocusableFormField { enableFeedback: enableFeedback, checkboxShape: checkboxShape, side: side, + enabled: field.control.enabled, onChanged: field.control.enabled ? (value) { field.didChange(value); diff --git a/lib/src/widgets/reactive_dropdown_field.dart b/lib/src/widgets/reactive_dropdown_field.dart index 0f6f873a..353a37e7 100644 --- a/lib/src/widgets/reactive_dropdown_field.dart +++ b/lib/src/widgets/reactive_dropdown_field.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:reactive_forms/reactive_forms.dart'; -/// A reactive widget that wraps a [DropdownButton]. +/// A reactive widget that wraps a [DropdownButtonFormField]. class ReactiveDropdownField extends ReactiveFocusableFormField { /// Creates a [DropdownButton] widget wrapped in an [InputDecorator]. /// @@ -20,7 +20,10 @@ class ReactiveDropdownField extends ReactiveFocusableFormField { /// If [readOnly] is true, the button will be disabled, the down arrow will /// be grayed out, and the disabledHint will be shown (if provided). /// - /// The [DropdownButton] [items] parameters must not be null. + /// The [items] parameter must not be null. + /// + /// For more information about all various parameters, + /// see [DropdownButtonFormField] constructor. ReactiveDropdownField({ Key? key, String? formControlName, @@ -50,6 +53,7 @@ class ReactiveDropdownField extends ReactiveFocusableFormField { bool? enableFeedback, AlignmentGeometry alignment = AlignmentDirectional.centerStart, BorderRadius? borderRadius, + EdgeInsetsGeometry? padding, ReactiveFormFieldCallback? onTap, ReactiveFormFieldCallback? onChanged, }) : assert(itemHeight == null || itemHeight > 0), @@ -111,6 +115,7 @@ class ReactiveDropdownField extends ReactiveFocusableFormField { enableFeedback: enableFeedback, alignment: alignment, borderRadius: borderRadius, + padding: padding, onTap: onTap != null ? () => onTap(field.control) : null, onChanged: isDisabled ? null diff --git a/lib/src/widgets/reactive_switch_list_tile.dart b/lib/src/widgets/reactive_switch_list_tile.dart index d999b5a0..fd230ce3 100644 --- a/lib/src/widgets/reactive_switch_list_tile.dart +++ b/lib/src/widgets/reactive_switch_list_tile.dart @@ -136,18 +136,31 @@ class ReactiveSwitchListTile extends ReactiveFocusableFormField { String? formControlName, FormControl? formControl, Color? activeColor, - ImageProvider? activeThumbImage, Color? activeTrackColor, + Color? inactiveThumbColor, + Color? inactiveTrackColor, + ImageProvider? activeThumbImage, + ImageErrorListener? onActiveThumbImageError, + ImageProvider? inactiveThumbImage, + ImageErrorListener? onInactiveThumbImageError, + MaterialStateProperty? thumbColor, + MaterialStateProperty? trackColor, + MaterialStateProperty? trackOutlineColor, + MaterialStateProperty? thumbIcon, + MaterialTapTargetSize? materialTapTargetSize, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, + MouseCursor? mouseCursor, + MaterialStateProperty? overlayColor, + double? splashRadius, bool autofocus = false, + bool? applyCupertinoTheme, EdgeInsetsGeometry? contentPadding, ListTileControlAffinity controlAffinity = ListTileControlAffinity.platform, bool? dense, bool? enableFeedback, FocusNode? focusNode, + ValueChanged? onFocusChange, Color? hoverColor, - Color? inactiveThumbColor, - ImageProvider? inactiveThumbImage, - Color? inactiveTrackColor, bool isThreeLine = false, Widget? secondary, bool selected = false, @@ -167,18 +180,31 @@ class ReactiveSwitchListTile extends ReactiveFocusableFormField { return SwitchListTile.adaptive( value: field.value ?? false, activeColor: activeColor, - activeThumbImage: activeThumbImage, activeTrackColor: activeTrackColor, + inactiveThumbColor: inactiveThumbColor, + inactiveTrackColor: inactiveTrackColor, + activeThumbImage: activeThumbImage, + onActiveThumbImageError: onActiveThumbImageError, + inactiveThumbImage: inactiveThumbImage, + onInactiveThumbImageError: onInactiveThumbImageError, + thumbColor: thumbColor, + trackColor: trackColor, + trackOutlineColor: trackOutlineColor, + thumbIcon: thumbIcon, + materialTapTargetSize: materialTapTargetSize, + dragStartBehavior: dragStartBehavior, + mouseCursor: mouseCursor, + overlayColor: overlayColor, + splashRadius: splashRadius, autofocus: autofocus, + applyCupertinoTheme: applyCupertinoTheme, contentPadding: contentPadding, controlAffinity: controlAffinity, dense: dense, enableFeedback: enableFeedback, focusNode: field.focusNode, + onFocusChange: onFocusChange, hoverColor: hoverColor, - inactiveThumbColor: inactiveThumbColor, - inactiveThumbImage: inactiveThumbImage, - inactiveTrackColor: inactiveTrackColor, isThreeLine: isThreeLine, secondary: secondary, selected: selected,