diff --git a/example/lib/sources/complete_form.dart b/example/lib/sources/complete_form.dart index 80a273f9d..5bd208a29 100644 --- a/example/lib/sources/complete_form.dart +++ b/example/lib/sources/complete_form.dart @@ -55,15 +55,17 @@ class _CompleteFormState extends State { inputType: InputType.both, decoration: InputDecoration( labelText: 'Appointment Time', - suffixIcon: IconButton( - icon: const Icon(Icons.close), - onPressed: () { - _formKey.currentState!.fields['date']?.didChange(null); - }, - ), + // suffixIcon: IconButton( + // icon: const Icon(Icons.close), + // onPressed: () { + // _formKey.currentState!.fields['date']?.didChange(null); + // }, + // ), ), initialTime: const TimeOfDay(hour: 8, minute: 0), // locale: const Locale.fromSubtags(languageCode: 'fr'), + allowClear: true, + clearIcon: Icon(Icons.clear), ), FormBuilderDateRangePicker( name: 'date_range', diff --git a/example/pubspec.lock b/example/pubspec.lock index cd0eae2cc..3eed21ae7 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -60,7 +60,7 @@ packages: path: ".." relative: true source: path - version: "10.3.0+1" + version: "10.3.0+2" flutter_lints: dependency: "direct dev" description: @@ -131,10 +131,10 @@ packages: dependency: transitive description: name: matcher - sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.18" + version: "0.12.19" material_color_utilities: dependency: transitive description: @@ -208,10 +208,10 @@ packages: dependency: transitive description: name: test_api - sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.9" + version: "0.7.10" vector_math: dependency: transitive description: diff --git a/lib/src/fields/form_builder_date_time_picker.dart b/lib/src/fields/form_builder_date_time_picker.dart index 586205d7a..09af46029 100644 --- a/lib/src/fields/form_builder_date_time_picker.dart +++ b/lib/src/fields/form_builder_date_time_picker.dart @@ -2,10 +2,8 @@ import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; - -import 'package:intl/intl.dart'; - import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:intl/intl.dart'; enum InputType { date, time, both } @@ -123,6 +121,9 @@ class FormBuilderDateTimePicker extends FormBuilderFieldDecoration { final EntryModeChangeCallback? onEntryModeChanged; final bool barrierDismissible; + final bool allowClear; + final Widget? clearIcon; + /// If true, disables the picker so it's not shown when the field is tapped. final bool disablePicker; @@ -197,6 +198,9 @@ class FormBuilderDateTimePicker extends FormBuilderFieldDecoration { this.onEntryModeChanged, this.disablePicker = false, this.barrierDismissible = true, + // TODO: set allClear to true if that's the default behaviour + this.allowClear = false, + this.clearIcon, }) : super( builder: (FormFieldState field) { final state = field as _FormBuilderDateTimePickerState; @@ -404,6 +408,27 @@ class _FormBuilderDateTimePickerState DateTime? convert(TimeOfDay? time) => time == null ? null : DateTime(1, 1, 1, time.hour, time.minute); + /// Sets the [allowClear] property for automatic DateTime reset, and [clearIcon] to a default or user defined icon. + @override + InputDecoration get decoration => widget.allowClear + ? super.decoration.copyWith( + suffix: value == null + ? null + : IconButton( + padding: EdgeInsets.zero, + constraints: const BoxConstraints( + maxWidth: 24, + maxHeight: 24, + ), + onPressed: () { + focus(); + didChange(null); + }, + icon: widget.clearIcon ?? const Icon(Icons.clear), + ), + ) + : super.decoration; + @override void didChange(DateTime? value) { super.didChange(value); diff --git a/pubspec.lock b/pubspec.lock index 86593ec5c..f41c433fa 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -111,10 +111,10 @@ packages: dependency: transitive description: name: matcher - sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.18" + version: "0.12.19" material_color_utilities: dependency: transitive description: @@ -188,10 +188,10 @@ packages: dependency: transitive description: name: test_api - sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.9" + version: "0.7.10" vector_math: dependency: transitive description: diff --git a/test/src/fields/form_builder_date_time_picker_test.dart b/test/src/fields/form_builder_date_time_picker_test.dart index 458a0ee4e..b6f4110c4 100644 --- a/test/src/fields/form_builder_date_time_picker_test.dart +++ b/test/src/fields/form_builder_date_time_picker_test.dart @@ -197,6 +197,194 @@ void main() { ); }); }); + + testWidgets('allowClear properly clears value', ( + WidgetTester tester, + ) async { + const widgetName = 'fdtp_clear'; + final widgetKey = UniqueKey(); + final initialDate = DateTime(2023, 1, 1); + + final testWidget = FormBuilderDateTimePicker( + key: widgetKey, + name: widgetName, + initialValue: initialDate, + allowClear: true, + ); + await tester.pumpWidget(buildTestableFieldWidget(testWidget)); + + expect(formInstantValue(widgetName), initialDate); + expect(find.byIcon(Icons.clear), findsOneWidget); + + await tester.tap(find.byIcon(Icons.clear)); + await tester.pumpAndSettle(); + + expect(formInstantValue(widgetName), isNull); + expect(find.byIcon(Icons.clear), findsNothing); + }); + + testWidgets('custom clearIcon is rendered', (WidgetTester tester) async { + const widgetName = 'fdtp_custom_clear'; + final initialDate = DateTime(2023, 1, 1); + const customIcon = Icons.delete; + + final testWidget = FormBuilderDateTimePicker( + name: widgetName, + initialValue: initialDate, + allowClear: true, + clearIcon: const Icon(customIcon), + ); + await tester.pumpWidget(buildTestableFieldWidget(testWidget)); + + expect(find.byIcon(customIcon), findsOneWidget); + expect(find.byIcon(Icons.clear), findsNothing); + }); + + testWidgets('InputType.date returns midnight time', ( + WidgetTester tester, + ) async { + const widgetName = 'fdtp_date_only'; + final widgetKey = UniqueKey(); + final dateNow = DateTime.now(); + const confirmText = 'OK'; + + final testWidget = FormBuilderDateTimePicker( + key: widgetKey, + name: widgetName, + inputType: InputType.date, + confirmText: confirmText, + ); + await tester.pumpWidget(buildTestableFieldWidget(testWidget)); + + await tester.tap(find.byKey(widgetKey)); + await tester.pumpAndSettle(); + + final testDay = dateNow.day - 1 <= 0 ? dateNow.day + 1 : dateNow.day - 1; + await tester.tap(find.text(testDay.toString())); + await tester.pumpAndSettle(); + await tester.tap(find.text(confirmText)); + await tester.pumpAndSettle(); + + expect(formSave(), isTrue); + expect( + formValue(widgetName), + DateTime(dateNow.year, dateNow.month, testDay), + ); + }); + + testWidgets('InputType.time returns DateTime(1, 1, 1) with time', ( + WidgetTester tester, + ) async { + const widgetName = 'fdtp_time_only'; + final widgetKey = UniqueKey(); + const confirmText = 'OK'; + + final testWidget = FormBuilderDateTimePicker( + key: widgetKey, + name: widgetName, + inputType: InputType.time, + confirmText: confirmText, + ); + await tester.pumpWidget(buildTestableFieldWidget(testWidget)); + + await tester.tap(find.byKey(widgetKey)); + await tester.pumpAndSettle(); + + await tester.tap(find.text(confirmText)); + await tester.pumpAndSettle(); + + expect(formSave(), isTrue); + final value = formValue(widgetName); + expect(value.year, 1); + expect(value.month, 1); + expect(value.day, 1); + expect(value.hour, 12); // Default initialTime is 12:00 + expect(value.minute, 0); + }); + + testWidgets('onChanged is called when value changes', ( + WidgetTester tester, + ) async { + const widgetName = 'fdtp_onChanged'; + int changedCount = 0; + DateTime? valueFromOnChanged; + + final testWidget = FormBuilderDateTimePicker( + name: widgetName, + allowClear: true, + initialValue: DateTime(2023, 1, 1), + onChanged: (val) { + changedCount++; + valueFromOnChanged = val; + }, + ); + await tester.pumpWidget(buildTestableFieldWidget(testWidget)); + + await tester.tap(find.byIcon(Icons.clear)); + await tester.pumpAndSettle(); + + expect(changedCount, 1); + expect(valueFromOnChanged, isNull); + }); + + testWidgets('reset() reverts value to initialValue', ( + WidgetTester tester, + ) async { + const widgetName = 'fdtp_reset'; + final initialDate = DateTime(2023, 1, 1); + + final testWidget = FormBuilderDateTimePicker( + name: widgetName, + initialValue: initialDate, + allowClear: true, + ); + await tester.pumpWidget(buildTestableFieldWidget(testWidget)); + + await tester.tap(find.byIcon(Icons.clear)); + await tester.pumpAndSettle(); + expect(formInstantValue(widgetName), isNull); + + formKey.currentState!.fields[widgetName]!.reset(); + await tester.pumpAndSettle(); + + expect(formInstantValue(widgetName), initialDate); + }); + + testWidgets('initialTime is respected for InputType.both', ( + WidgetTester tester, + ) async { + const widgetName = 'fdtp_initialTime'; + final widgetKey = UniqueKey(); + const confirmText = 'OK'; + final initialTime = const TimeOfDay(hour: 15, minute: 30); + + final testWidget = FormBuilderDateTimePicker( + key: widgetKey, + name: widgetName, + inputType: InputType.both, + confirmText: confirmText, + initialTime: initialTime, + ); + await tester.pumpWidget(buildTestableFieldWidget(testWidget)); + + await tester.tap(find.byKey(widgetKey)); + await tester.pumpAndSettle(); + + // Date picker OK + await tester.tap(find.text(DateTime.now().day.toString())); + await tester.pumpAndSettle(); + await tester.tap(find.text(confirmText)); + await tester.pumpAndSettle(); + + // Time picker should show 15:30. + await tester.tap(find.text(confirmText)); + await tester.pumpAndSettle(); + + expect(formSave(), isTrue); + final value = formValue(widgetName); + expect(value.hour, 15); + expect(value.minute, 30); + }); }); testWidgets('When press tab, field will be focused', (