Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions example/lib/sources/complete_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,17 @@ class _CompleteFormState extends State<CompleteForm> {
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',
Expand Down
10 changes: 5 additions & 5 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
31 changes: 28 additions & 3 deletions lib/src/fields/form_builder_date_time_picker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand Down Expand Up @@ -123,6 +121,9 @@ class FormBuilderDateTimePicker extends FormBuilderFieldDecoration<DateTime> {
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;

Expand Down Expand Up @@ -197,6 +198,9 @@ class FormBuilderDateTimePicker extends FormBuilderFieldDecoration<DateTime> {
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<DateTime?> field) {
final state = field as _FormBuilderDateTimePickerState;
Expand Down Expand Up @@ -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);
Expand Down
8 changes: 4 additions & 4 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
188 changes: 188 additions & 0 deletions test/src/fields/form_builder_date_time_picker_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<DateTime>(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<DateTime>(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<DateTime>(widgetName);
expect(value.hour, 15);
expect(value.minute, 30);
});
});

testWidgets('When press tab, field will be focused', (
Expand Down
Loading