From ad52fd9c331417cfb7ccd98b68a987b82c735e74 Mon Sep 17 00:00:00 2001 From: charlieforward9 Date: Wed, 19 Feb 2025 23:12:18 -0500 Subject: [PATCH 1/5] feat: add FormBuilderCupertinoDateTimePicker --- example/lib/main.dart | 8 +- lib/form_builder_cupertino_fields.dart | 1 + ...orm_builder_cupertino_datetime_picker.dart | 472 ++++++++++++++++++ ...uilder_cupertino_datetime_picker_test.dart | 105 ++++ 4 files changed, 585 insertions(+), 1 deletion(-) create mode 100644 lib/src/fields/form_builder_cupertino_datetime_picker.dart create mode 100644 test/src/fields/form_builder_cupertino_datetime_picker_test.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 171c6e1..1939fe7 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -100,7 +100,13 @@ class _MyHomePageState extends State { autovalidateMode: AutovalidateMode.onUserInteraction, validator: (value) => value != null && value.length > 4 ? null : 'Write a text', - ) + ), + const SizedBox(height: 16), + FormBuilderCupertinoDateTimePicker( + name: 'date', + autovalidateMode: AutovalidateMode.onUserInteraction, + validator: (value) => value != null ? null : 'Required date', + ), ], ), ), diff --git a/lib/form_builder_cupertino_fields.dart b/lib/form_builder_cupertino_fields.dart index da027e6..9ae5049 100644 --- a/lib/form_builder_cupertino_fields.dart +++ b/lib/form_builder_cupertino_fields.dart @@ -1,4 +1,5 @@ export 'src/fields/form_builder_cupertino_checkbox.dart'; +export 'src/fields/form_builder_cupertino_datetime_picker.dart'; export 'src/fields/form_builder_cupertino_segmented_control.dart'; export 'src/fields/form_builder_cupertino_slider.dart'; export 'src/fields/form_builder_cupertino_sliding_segmented_control.dart'; diff --git a/lib/src/fields/form_builder_cupertino_datetime_picker.dart b/lib/src/fields/form_builder_cupertino_datetime_picker.dart new file mode 100644 index 0000000..ab9b73f --- /dev/null +++ b/lib/src/fields/form_builder_cupertino_datetime_picker.dart @@ -0,0 +1,472 @@ +import 'dart:ui' as ui; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart' show TimeOfDay; +import 'package:flutter/services.dart'; + +import 'package:intl/intl.dart'; + +import 'package:flutter_form_builder/flutter_form_builder.dart'; + +/// Initial display of a calendar date picker. +/// +/// Either a grid of available years or a monthly calendar. +/// +/// See also: +/// +/// * [showDatePicker], which shows a dialog that contains a Material Design +/// date picker. +/// * [CalendarDatePicker], widget which implements the Material Design date picker. +enum DatePickerMode { + /// Choosing a month and day. + day, + + /// Choosing a year. + year, +} + +/// Interactive input mode of the time picker dialog. +/// +/// In [TimePickerEntryMode.dial] mode, a clock dial is displayed and the user +/// taps or drags the time they wish to select. In TimePickerEntryMode.input] +/// mode, [TextField]s are displayed and the user types in the time they wish to +/// select. +/// +/// See also: +/// +/// * [showTimePicker], a function that shows a [TimePickerDialog] and returns +/// the selected time as a [Future]. +enum TimePickerEntryMode { + /// User picks time from a clock dial. + /// + /// Can switch to [input] by activating a mode button in the dialog. + dial, + + /// User can input the time by typing it into text fields. + /// + /// Can switch to [dial] by activating a mode button in the dialog. + input, + + /// User can only pick time from a clock dial. + /// + /// There is no user interface to switch to another mode. + dialOnly, + + /// User can only input the time by typing it into text fields. + /// + /// There is no user interface to switch to another mode. + inputOnly, +} + +/// Mode of date entry method for the date picker dialog. +/// +/// In [calendar] mode, a calendar grid is displayed and the user taps the +/// day they wish to select. In [input] mode, a [TextField] is displayed and +/// the user types in the date they wish to select. +/// +/// [calendarOnly] and [inputOnly] are variants of the above that don't +/// allow the user to change to the mode. +/// +/// See also: +/// +/// * [showDatePicker] and [showDateRangePicker], which use this to control +/// the initial entry mode of their dialogs. +enum DatePickerEntryMode { + /// User picks a date from calendar grid. Can switch to [input] by activating + /// a mode button in the dialog. + calendar, + + /// User can input the date by typing it into a text field. + /// + /// Can switch to [calendar] by activating a mode button in the dialog. + input, + + /// User can only pick a date from calendar grid. + /// + /// There is no user interface to switch to another mode. + calendarOnly, + + /// User can only input the date by typing it into a text field. + /// + /// There is no user interface to switch to another mode. + inputOnly, +} + +enum InputType { date, time, both } + +/// Field for `Date`, `Time` and `DateTime` input +class FormBuilderCupertinoDateTimePicker extends FormBuilderField { + /// The date/time picker dialogs to show. + final InputType inputType; + + /// Allow manual editing of the date/time. Defaults to true. If false, the + /// picker(s) will be shown every time the field gains focus. + // final bool editable; + + /// For representing the date as a string e.g. + /// `DateFormat("EEEE, MMMM d, yyyy 'at' h:mma")` + /// (Sunday, June 3, 2018 at 9:24pm) + final DateFormat? format; + + /// The date the calendar opens to when displayed. Defaults to null. + /// + /// To preset the widget's value, use [initialValue] instead. + final DateTime? initialDate; + + /// The earliest choosable date. Defaults to 1900. + final DateTime? firstDate; + + /// The latest choosable date. Defaults to 2100. + final DateTime? lastDate; + + final DateTime? currentDate; + + /// The initial time prefilled in the picker dialog when it is shown. Defaults + /// to noon. Explicitly set this to `null` to use the current time. + final TimeOfDay initialTime; + + /// Called when an enclosing form is saved. The value passed will be `null` + /// if [format] fails to parse the text. + // final FormFieldSetter onSaved; + + /// Corresponds to the [showDatePicker()] parameter. Defaults to + /// [DatePickerMode.day]. + final DatePickerMode initialDatePickerMode; + + /// Corresponds to the [showDatePicker()] parameter. + /// + /// See [GlobalMaterialLocalizations](https://docs.flutter.io/flutter/flutter_localizations/GlobalMaterialLocalizations-class.html) + /// for acceptable values. + final Locale? locale; + + /// Corresponds to the [showDatePicker()] parameter. + final ui.TextDirection? textDirection; + + /// Corresponds to the [showDatePicker()] parameter. + final bool useRootNavigator; + + /// Called when an enclosing form is submitted. The value passed will be + /// `null` if [format] fails to parse the text. + final ValueChanged? onFieldSubmitted; + final TextEditingController? controller; + final TextInputType? keyboardType; + final TextStyle? style; + final TextAlign textAlign; + final TextAlignVertical? textAlignVertical; + + /// Preset the widget's value. + final bool autofocus; + final bool obscureText; + final bool autocorrect; + final MaxLengthEnforcement maxLengthEnforcement; + final int? maxLines; + final int? maxLength; + final List? inputFormatters; + final TransitionBuilder? transitionBuilder; + + /// Called whenever the state's value changes, e.g. after picker value(s) + /// have been selected or when the field loses focus. To listen for all text + /// changes, use the [controller] and [focusNode]. + // final ValueChanged onChanged; + + final bool showCursor; + + final int? minLines; + + final bool expands; + + final TextInputAction? textInputAction; + + final VoidCallback? onEditingComplete; + + final Radius cursorRadius; + final Color? cursorColor; + final Brightness? keyboardAppearance; + final EdgeInsets scrollPadding; + final bool enableInteractiveSelection; + + final double cursorWidth; + final TextCapitalization textCapitalization; + + final String? cancelText; + final String? confirmText; + final String? errorFormatText; + final String? errorInvalidText; + final String? fieldHintText; + final String? fieldLabelText; + final String? helpText; + final DatePickerEntryMode initialEntryMode; + final RouteSettings? routeSettings; + + final TimePickerEntryMode timePickerInitialEntryMode; + final StrutStyle? strutStyle; + final bool Function(DateTime day)? selectableDayPredicate; + final Offset? anchorPoint; + final void Function(TimePickerEntryMode mode)? onEntryModeChanged; + final bool barrierDismissible; + + /// Creates field for `Date`, `Time` and `DateTime` input + FormBuilderCupertinoDateTimePicker({ + super.key, + required super.name, + super.validator, + super.initialValue, + super.onChanged, + super.valueTransformer, + super.enabled, + super.onSaved, + super.autovalidateMode = AutovalidateMode.disabled, + super.onReset, + super.focusNode, + super.restorationId, + this.inputType = InputType.both, + this.scrollPadding = const EdgeInsets.all(20.0), + this.cursorWidth = 2.0, + this.enableInteractiveSelection = true, + this.initialTime = const TimeOfDay(hour: 12, minute: 0), + this.keyboardType, + this.textAlign = TextAlign.start, + this.autofocus = false, + this.obscureText = false, + this.autocorrect = true, + this.maxLines = 1, + this.expands = false, + this.initialDatePickerMode = DatePickerMode.day, + this.transitionBuilder, + this.textCapitalization = TextCapitalization.none, + this.useRootNavigator = true, + this.initialEntryMode = DatePickerEntryMode.calendar, + this.timePickerInitialEntryMode = TimePickerEntryMode.dial, + this.format, + this.initialDate, + this.firstDate, + this.lastDate, + this.currentDate, + this.locale, + this.maxLength, + this.textDirection, + this.textAlignVertical, + this.onFieldSubmitted, + this.controller, + this.style, + this.maxLengthEnforcement = MaxLengthEnforcement.none, + this.inputFormatters, + this.showCursor = false, + this.minLines, + this.textInputAction, + this.onEditingComplete, + this.cursorRadius = const Radius.circular(2.0), + this.cursorColor, + this.keyboardAppearance, + this.cancelText, + this.confirmText, + this.errorFormatText, + this.errorInvalidText, + this.fieldHintText, + this.fieldLabelText, + this.helpText, + this.routeSettings, + this.strutStyle, + this.selectableDayPredicate, + this.anchorPoint, + this.onEntryModeChanged, + this.barrierDismissible = true, + }) : super( + builder: (FormFieldState field) { + final state = field as _FormBuilderCupertinoDateTimePickerState; + + return FocusTraversalGroup( + policy: ReadingOrderTraversalPolicy(), + child: CupertinoTextField( + onTap: () => state.showPicker(), + textDirection: textDirection, + textAlign: textAlign, + textAlignVertical: textAlignVertical, + maxLength: maxLength, + autofocus: autofocus, + // FIXME: decoration: state.decoration, + readOnly: true, + enabled: state.enabled, + autocorrect: autocorrect, + controller: state._textFieldController, + focusNode: state.effectiveFocusNode, + inputFormatters: inputFormatters, + keyboardType: keyboardType, + maxLines: maxLines, + obscureText: obscureText, + showCursor: showCursor, + minLines: minLines, + expands: expands, + style: style, + onEditingComplete: onEditingComplete, + cursorColor: cursorColor, + cursorRadius: cursorRadius, + cursorWidth: cursorWidth, + enableInteractiveSelection: enableInteractiveSelection, + keyboardAppearance: keyboardAppearance, + scrollPadding: scrollPadding, + strutStyle: strutStyle, + textCapitalization: textCapitalization, + textInputAction: textInputAction, + maxLengthEnforcement: maxLengthEnforcement, + ), + ); + }, + ); + + @override + FormBuilderFieldState + createState() => _FormBuilderCupertinoDateTimePickerState(); +} + +class _FormBuilderCupertinoDateTimePickerState extends FormBuilderFieldState< + FormBuilderCupertinoDateTimePicker, DateTime> { + late TextEditingController _textFieldController; + + late DateFormat _dateFormat; + + @override + void initState() { + super.initState(); + _textFieldController = widget.controller ?? TextEditingController(); + _dateFormat = widget.format ?? _getDefaultDateTimeFormat(); + //setting this to value instead of initialValue here is OK since we handle initial value in the parent class + final initVal = value; + _textFieldController.text = + initVal == null ? '' : _dateFormat.format(initVal); + + effectiveFocusNode.onKeyEvent = (node, event) { + if (event is KeyDownEvent && + event.logicalKey == LogicalKeyboardKey.space && + node.hasFocus) { + showPicker(); + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + }; + } + + @override + void dispose() { + // Dispose the _textFieldController when initState created it + if (null == widget.controller) { + _textFieldController.dispose(); + } + super.dispose(); + } + + DateFormat _getDefaultDateTimeFormat() { + final languageCode = widget.locale?.languageCode; + return switch (widget.inputType) { + InputType.time => DateFormat.Hm(languageCode), + InputType.date => DateFormat.yMd(languageCode), + InputType.both => DateFormat.yMd(languageCode).add_Hms() + }; + } + + Future showPicker() async { + await onShowPicker(value); + } + + Future onShowPicker(DateTime? currentValue) async { + currentValue = value; + DateTime? newValue; + switch (widget.inputType) { + case InputType.date: + newValue = await _showDatePicker(currentValue); + break; + case InputType.time: + if (!context.mounted) break; + newValue = convert(await _showTimePicker(currentValue)); + break; + case InputType.both: + if (!context.mounted) break; + final date = await _showDatePicker(currentValue); + if (date != null) { + if (!mounted) break; + final time = await _showTimePicker(currentValue); + if (time == null) { + newValue = null; + } else { + newValue = combine(date, time); + } + } + break; + } + if (!mounted) return null; + final finalValue = newValue ?? currentValue; + didChange(finalValue); + return finalValue; + } + + Future _showDatePicker(DateTime? currentValue) async { + DateTime? pickedDate; + await showCupertinoModalPopup( + context: context, + builder: (BuildContext context) { + return SizedBox( + height: 250, + child: Container( + color: CupertinoColors.systemBackground.resolveFrom(context), + child: SafeArea( + top: false, + child: CupertinoDatePicker( + mode: CupertinoDatePickerMode.date, + initialDateTime: currentValue ?? widget.initialDate, + minimumDate: widget.firstDate, + maximumDate: widget.lastDate, + onDateTimeChanged: (DateTime newDateTime) { + pickedDate = newDateTime; + }, + ), + ), + ), + ); + }, + ); + return pickedDate; + } + + Future _showTimePicker(DateTime? currentValue) async { + TimeOfDay? pickedTime; + await showCupertinoModalPopup( + context: context, + builder: (BuildContext context) { + return SizedBox( + height: 250, + child: Container( + color: CupertinoColors.systemBackground.resolveFrom(context), + child: SafeArea( + top: false, + child: CupertinoDatePicker( + mode: CupertinoDatePickerMode.time, + initialDateTime: currentValue ?? + DateTime(0, 0, 0, widget.initialTime.hour, + widget.initialTime.minute), + onDateTimeChanged: (DateTime newDateTime) { + pickedTime = TimeOfDay( + hour: newDateTime.hour, minute: newDateTime.minute); + }, + ), + ), + ), + ); + }, + ); + return pickedTime; + } + + /// Sets the hour and minute of a [DateTime] from a [TimeOfDay]. + DateTime combine(DateTime date, TimeOfDay? time) => DateTime( + date.year, date.month, date.day, time?.hour ?? 0, time?.minute ?? 0); + + DateTime? convert(TimeOfDay? time) => + time == null ? null : DateTime(1, 1, 1, time.hour, time.minute); + + @override + void didChange(DateTime? value) { + super.didChange(value); + _textFieldController.text = + (value == null) ? '' : _dateFormat.format(value); + } +} diff --git a/test/src/fields/form_builder_cupertino_datetime_picker_test.dart b/test/src/fields/form_builder_cupertino_datetime_picker_test.dart new file mode 100644 index 0000000..d639fdd --- /dev/null +++ b/test/src/fields/form_builder_cupertino_datetime_picker_test.dart @@ -0,0 +1,105 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:form_builder_cupertino_fields/form_builder_cupertino_fields.dart'; +import 'package:intl/date_symbol_data_local.dart'; +import 'package:intl/intl.dart'; + +import '../form_builder_tester.dart'; + +void main() { + group('FormBuilderCupertinoDateTimePicker Tests -', () { + // Test for initial value + testWidgets('should initialize with the given initial value', + (WidgetTester tester) async { + const widgetName = 'dateTimePicker'; + final pickerKey = GlobalKey(); + + final testWidget = FormBuilderCupertinoDateTimePicker( + name: widgetName, + key: pickerKey, + initialValue: DateTime(2025, 2, 19, 11, 56), + ); + + await tester.pumpWidget(buildTestableFieldWidget(testWidget)); + + expect(pickerKey.currentState!.value, DateTime(2025, 2, 19, 11, 56)); + }); + + // Test for reset functionality + testWidgets('should reset to initial value when reset is called', + (WidgetTester tester) async { + const widgetName = 'dateTimePicker'; + final pickerKey = GlobalKey(); + final initialValue = DateTime(2025, 2, 19, 11, 56); + + final testWidget = FormBuilderCupertinoDateTimePicker( + name: widgetName, + key: pickerKey, + initialValue: initialValue, + ); + + await tester.pumpWidget(buildTestableFieldWidget(testWidget)); + + // Change the value + pickerKey.currentState?.setValue(DateTime(2025, 2, 20, 12, 0)); + await tester.pumpAndSettle(); + + // Reset the field + pickerKey.currentState?.reset(); + await tester.pumpAndSettle(); + + expect(pickerKey.currentState?.value, equals(initialValue)); + }); + + // Test for date and time selection + testWidgets('should update value when a new date and time is selected', + (WidgetTester tester) async { + const widgetName = 'dateTimePicker'; + final pickerKey = GlobalKey(); + + final testWidget = FormBuilderCupertinoDateTimePicker( + name: widgetName, + key: pickerKey, + initialValue: DateTime(2025, 2, 19, 11, 56), + ); + + await tester.pumpWidget(buildTestableFieldWidget(testWidget)); + + // Simulate tapping the text field to show the picker + await tester.tap(find.byType(CupertinoTextField)); + await tester.pumpAndSettle(); + + // Simulate selecting a new date and time + final newDateTime = DateTime(2025, 2, 20, 12, 0); + pickerKey.currentState?.didChange(newDateTime); + await tester.pumpAndSettle(); + + expect(pickerKey.currentState?.value, equals(newDateTime)); + }); + + // Test for locale and format + testWidgets('should display date in the correct format and locale', + (WidgetTester tester) async { + initializeDateFormatting(); + const widgetName = 'dateTimePicker'; + final pickerKey = GlobalKey(); + final initialValue = DateTime(2025, 2, 19, 11, 56); + final dateFormat = DateFormat.yMMMMd('fr_FR'); // French locale + + final testWidget = FormBuilderCupertinoDateTimePicker( + name: widgetName, + key: pickerKey, + initialValue: initialValue, + format: dateFormat, + locale: const Locale('fr', 'FR'), + ); + + await tester.pumpWidget(buildTestableFieldWidget(testWidget)); + + // Verify that the displayed text matches the French date format + final expectedText = dateFormat.format(initialValue); + expect(find.text(expectedText), findsOneWidget); + }); + }); +} From 018cfabda6fb85e44fb653db51ab364d5951682b Mon Sep 17 00:00:00 2001 From: charlieforward9 Date: Thu, 20 Feb 2025 00:28:28 -0500 Subject: [PATCH 2/5] chore: update example config --- example/.gitignore | 2 + example/ios/Runner.xcodeproj/project.pbxproj | 6 +- .../xcshareddata/xcschemes/Runner.xcscheme | 1 + example/pubspec.lock | 74 +++++++++---------- 4 files changed, 43 insertions(+), 40 deletions(-) diff --git a/example/.gitignore b/example/.gitignore index 24476c5..6c31954 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index ce3bc8f..eeb3331 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -368,7 +368,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_BUNDLE_IDENTIFIER = "com.form-builder-cupertino-fields.example"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -547,7 +547,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_BUNDLE_IDENTIFIER = "com.form-builder-cupertino-fields.example"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -569,7 +569,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_BUNDLE_IDENTIFIER = "com.form-builder-cupertino-fields.example"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 8e3ca5d..15cada4 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -59,6 +59,7 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" + enableGPUValidationMode = "1" allowLocationSimulation = "YES"> diff --git a/example/pubspec.lock b/example/pubspec.lock index b9c82d0..06228f5 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,42 +5,42 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.12.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.19.1" cupertino_icons: dependency: "direct main" description: @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" flutter: dependency: "direct main" description: flutter @@ -102,18 +102,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "10.0.7" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: @@ -134,10 +134,10 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: @@ -150,18 +150,18 @@ packages: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.16.0" path: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" sky_engine: dependency: transitive description: flutter @@ -171,50 +171,50 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.4" vector_math: dependency: transitive description: @@ -227,10 +227,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "14.3.0" + version: "14.3.1" sdks: - dart: ">=3.6.0 <4.0.0" + dart: ">=3.7.0-0 <4.0.0" flutter: ">=3.27.0" From 04c0cfbf40bd40982c36381d922701a43d5bf780 Mon Sep 17 00:00:00 2001 From: charlieforward9 Date: Thu, 20 Feb 2025 00:43:16 -0500 Subject: [PATCH 3/5] docs: add FormBuilderCupertinoDateTimePicker to ReadME --- README.md | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 57592e8..2a9f4ec 100644 --- a/README.md +++ b/README.md @@ -6,20 +6,22 @@ Additional form inputs fields with Cupertino style for [flutter_form_builder](ht [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/flutter-form-builder-ecosystem/form_builder_cupertino_fields/base.yaml?branch=main&logo=github&style=for-the-badge)](https://github.com/flutter-form-builder-ecosystem/form_builder_cupertino_fields/actions/workflows/base.yaml) [![Codecov](https://img.shields.io/codecov/c/github/flutter-form-builder-ecosystem/form_builder_cupertino_fields?logo=codecov&style=for-the-badge)](https://codecov.io/gh/flutter-form-builder-ecosystem/form_builder_cupertino_fields/) [![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/flutter-form-builder-ecosystem/form_builder_cupertino_fields?logo=codefactor&style=for-the-badge)](https://www.codefactor.io/repository/github/flutter-form-builder-ecosystem/form_builder_cupertino_fields) -___ - -- [Features](#features) -- [Inputs](#inputs) -- [Use](#use) - - [Setup](#setup) - - [Basic use](#basic-use) -- [Support](#support) - - [Contribute](#contribute) - - [Questions and answers](#questions-and-answers) - - [Donations](#donations) -- [Roadmap](#roadmap) -- [Ecosystem](#ecosystem) -- [Thanks to](#thanks-to) + +--- + +- [Form Builder Cupertino Fields](#form-builder-cupertino-fields) + - [Features](#features) + - [Inputs](#inputs) + - [Use](#use) + - [Setup](#setup) + - [Basic use](#basic-use) + - [Support](#support) + - [Contribute](#contribute) + - [Questions and answers](#questions-and-answers) + - [Donations](#donations) + - [Roadmap](#roadmap) + - [Ecosystem](#ecosystem) + - [Thanks to](#thanks-to) ## Features @@ -31,6 +33,7 @@ ___ The currently supported fields include: - `FormBuilderCupertinoCheckbox` - Single checkbox field +- `FormBuilderCupertinoDateTimePicker` - Date picker field - `FormBuilderCupertinoSegmentedControl` - Segmented control using `CupertinoSegmentedControl` - `FormBuilderCupertinoSlidingSegmentedControl` - Segmented control bar using `CupertinoSlidingSegmentedControl` - `FormBuilderCupertinoSlider` - For selection of a numerical value on a slider @@ -71,7 +74,7 @@ You have some ways to contribute to this packages - Intermediate: Implement new features (from issues or not) and created pull requests - Advanced: Join to [organization](#ecosystem) like a member and help coding, manage issues, dicuss new features and other things - See [contribution file](https://github.com/flutter-form-builder-ecosystem/.github/blob/main/CONTRIBUTING.md) for more details +See [contribution file](https://github.com/flutter-form-builder-ecosystem/.github/blob/main/CONTRIBUTING.md) for more details ### Questions and answers From 367ccaaf541581faa1375f078313a4a1fc7a728e Mon Sep 17 00:00:00 2001 From: charlieforward9 Date: Thu, 20 Feb 2025 01:42:42 -0500 Subject: [PATCH 4/5] fix: FormBuilderCupertinoDateTimePicker layout --- example/lib/main.dart | 2 + ...orm_builder_cupertino_datetime_picker.dart | 90 ++++++++++++++----- 2 files changed, 71 insertions(+), 21 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 1939fe7..3db049e 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -104,8 +104,10 @@ class _MyHomePageState extends State { const SizedBox(height: 16), FormBuilderCupertinoDateTimePicker( name: 'date', + prefix: const Icon(CupertinoIcons.calendar), autovalidateMode: AutovalidateMode.onUserInteraction, validator: (value) => value != null ? null : 'Required date', + initialValue: DateTime.now(), ), ], ), diff --git a/lib/src/fields/form_builder_cupertino_datetime_picker.dart b/lib/src/fields/form_builder_cupertino_datetime_picker.dart index ab9b73f..f73ceb2 100644 --- a/lib/src/fields/form_builder_cupertino_datetime_picker.dart +++ b/lib/src/fields/form_builder_cupertino_datetime_picker.dart @@ -8,6 +8,8 @@ import 'package:intl/intl.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; +/**************** START Imported from flutter/material.dart ****************/ + /// Initial display of a calendar date picker. /// /// Either a grid of available years or a monthly calendar. @@ -91,10 +93,9 @@ enum DatePickerEntryMode { /// There is no user interface to switch to another mode. inputOnly, } +/**************** START Imported from flutter/material.dart ****************/ -enum InputType { date, time, both } - -/// Field for `Date`, `Time` and `DateTime` input +/// A [CupertinoDateTimePicker] that integrates with [FormBuilder]. class FormBuilderCupertinoDateTimePicker extends FormBuilderField { /// The date/time picker dialogs to show. final InputType inputType; @@ -154,6 +155,49 @@ class FormBuilderCupertinoDateTimePicker extends FormBuilderField { final TextAlign textAlign; final TextAlignVertical? textAlignVertical; + /// Defines whether the field input expands to fill the entire width + /// of the row field. + /// + /// By default `false` + final bool shouldExpandedField; + + /// Controls the [BoxDecoration] of the box behind the text input. + /// + /// Defaults to having a rounded rectangle grey border and can be null to have + /// no box decoration. + final BoxDecoration? decoration; + + /// A widget that is displayed at the start of the row. + /// + /// The [prefix] parameter is displayed at the start of the row. Standard iOS + /// guidelines encourage passing a [Text] widget to [prefix] to detail the + /// nature of the row's [child] widget. If null, the [child] widget will take + /// up all horizontal space in the row. + final Widget? prefix; + + /// Content padding for the row. + /// + /// Defaults to the standard iOS padding for form rows. If no edge insets are + /// intended, explicitly pass [EdgeInsets.zero] to [contentPadding]. + final EdgeInsetsGeometry? contentPadding; + + /// A widget that is displayed underneath the [prefix] and [child] widgets. + /// + /// The [helper] appears in primary label coloring, and is meant to inform the + /// user about interaction with the child widget. The row becomes taller in + /// order to display the [helper] widget underneath [prefix] and [child]. If + /// null, the row is shorter. + final Widget? helper; + + /// A builder widget that is displayed underneath the [prefix] and [child] widgets. + /// + /// The [error] widget is primarily used to inform users of input errors. When + /// a [Text] is given to [error], it will be shown in + /// [CupertinoColors.destructiveRed] coloring and medium-weighted font. The + /// row becomes taller in order to display the [helper] widget underneath + /// [prefix] and [child]. If null, the row is shorter. + final Widget? Function(String error)? errorBuilder; + /// Preset the widget's value. final bool autofocus; final bool obscureText; @@ -188,17 +232,9 @@ class FormBuilderCupertinoDateTimePicker extends FormBuilderField { final double cursorWidth; final TextCapitalization textCapitalization; - final String? cancelText; - final String? confirmText; - final String? errorFormatText; - final String? errorInvalidText; - final String? fieldHintText; - final String? fieldLabelText; - final String? helpText; final DatePickerEntryMode initialEntryMode; final RouteSettings? routeSettings; - final TimePickerEntryMode timePickerInitialEntryMode; final StrutStyle? strutStyle; final bool Function(DateTime day)? selectableDayPredicate; final Offset? anchorPoint; @@ -226,6 +262,12 @@ class FormBuilderCupertinoDateTimePicker extends FormBuilderField { this.initialTime = const TimeOfDay(hour: 12, minute: 0), this.keyboardType, this.textAlign = TextAlign.start, + this.shouldExpandedField = false, + this.decoration, + this.prefix, + this.contentPadding, + this.helper, + this.errorBuilder, this.autofocus = false, this.obscureText = false, this.autocorrect = true, @@ -236,7 +278,6 @@ class FormBuilderCupertinoDateTimePicker extends FormBuilderField { this.textCapitalization = TextCapitalization.none, this.useRootNavigator = true, this.initialEntryMode = DatePickerEntryMode.calendar, - this.timePickerInitialEntryMode = TimePickerEntryMode.dial, this.format, this.initialDate, this.firstDate, @@ -258,13 +299,6 @@ class FormBuilderCupertinoDateTimePicker extends FormBuilderField { this.cursorRadius = const Radius.circular(2.0), this.cursorColor, this.keyboardAppearance, - this.cancelText, - this.confirmText, - this.errorFormatText, - this.errorInvalidText, - this.fieldHintText, - this.fieldLabelText, - this.helpText, this.routeSettings, this.strutStyle, this.selectableDayPredicate, @@ -275,7 +309,7 @@ class FormBuilderCupertinoDateTimePicker extends FormBuilderField { builder: (FormFieldState field) { final state = field as _FormBuilderCupertinoDateTimePickerState; - return FocusTraversalGroup( + final fieldWidget = FocusTraversalGroup( policy: ReadingOrderTraversalPolicy(), child: CupertinoTextField( onTap: () => state.showPicker(), @@ -284,7 +318,7 @@ class FormBuilderCupertinoDateTimePicker extends FormBuilderField { textAlignVertical: textAlignVertical, maxLength: maxLength, autofocus: autofocus, - // FIXME: decoration: state.decoration, + decoration: decoration, readOnly: true, enabled: state.enabled, autocorrect: autocorrect, @@ -311,6 +345,20 @@ class FormBuilderCupertinoDateTimePicker extends FormBuilderField { maxLengthEnforcement: maxLengthEnforcement, ), ); + + return CupertinoFormRow( + error: state.hasError + ? errorBuilder != null + ? errorBuilder(state.errorText ?? '') + : Text(state.errorText ?? '') + : null, + helper: helper, + padding: contentPadding, + prefix: prefix, + child: shouldExpandedField + ? SizedBox(width: double.infinity, child: fieldWidget) + : fieldWidget, + ); }, ); From 3593610b7a346f1fde0d24b3e765a5b1118077f5 Mon Sep 17 00:00:00 2001 From: charlieforward9 Date: Fri, 11 Apr 2025 17:15:15 -0500 Subject: [PATCH 5/5] chore(deps): bump intl --- pubspec.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index a035605..20eaac7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,17 +1,17 @@ name: form_builder_cupertino_fields description: Additional form inputs fields with Cupertino style for flutter_form_builder package -version: 0.5.0 +version: 0.6.0 repository: https://github.com/flutter-form-builder-ecosystem/form_builder_cupertino_fields issue_tracker: https://github.com/flutter-form-builder-ecosystem/form_builder_cupertino_fields/issues homepage: https://github.com/flutter-form-builder-ecosystem -topics: +topics: - form - cupertino funding: - https://opencollective.com/flutter-form-builder-ecosystem environment: - sdk: '>=3.7.0 <4.0.0' + sdk: ">=3.7.0 <4.0.0" flutter: ">=3.29.0" dependencies: @@ -20,7 +20,7 @@ dependencies: flutter_form_builder: ^10.0.1 # This version would be max, the same version used on flutter_localizations # https://github.com/flutter/flutter/blob/17025dd88227cd9532c33fa78f5250d548d87e9a/packages/flutter_localizations/pubspec.yaml#L14 - intl: ">=0.19.0 <0.21.0" + intl: ">=0.20.0 <0.21.0" dev_dependencies: flutter_test: