|
| 1 | +import 'dart:async'; |
| 2 | +import 'dart:core'; |
| 3 | + |
| 4 | +import 'package:flutter/material.dart'; |
| 5 | +import 'package:flutter/services.dart'; |
| 6 | +import 'package:flutter/widgets.dart'; |
| 7 | +import 'package:flutter_form_builder/flutter_form_builder.dart'; |
| 8 | +import 'package:intl/intl.dart' as intl; |
| 9 | + |
| 10 | +/// Field for selecting a range of dates |
| 11 | +class FormBuilderDateRangePicker extends FormBuilderField<DateTimeRange> { |
| 12 | + //TODO: Add documentation |
| 13 | + final int maxLines; |
| 14 | + final TextInputType? keyboardType; |
| 15 | + final bool obscureText; |
| 16 | + final TextStyle? style; |
| 17 | + final TextEditingController? controller; |
| 18 | + final TextCapitalization textCapitalization; |
| 19 | + final TextInputAction? textInputAction; |
| 20 | + final StrutStyle? strutStyle; |
| 21 | + final TextDirection? textDirection; |
| 22 | + final TextAlign textAlign; |
| 23 | + final bool autofocus; |
| 24 | + final bool autocorrect; |
| 25 | + final MaxLengthEnforcement? maxLengthEnforcement; |
| 26 | + final int? maxLength; |
| 27 | + final VoidCallback? onEditingComplete; |
| 28 | + final ValueChanged<List<DateTime>>? onFieldSubmitted; |
| 29 | + final List<TextInputFormatter>? inputFormatters; |
| 30 | + final double cursorWidth; |
| 31 | + final Radius? cursorRadius; |
| 32 | + final Color ?cursorColor; |
| 33 | + final Brightness? keyboardAppearance; |
| 34 | + final EdgeInsets scrollPadding; |
| 35 | + final bool enableInteractiveSelection; |
| 36 | + final InputCounterWidgetBuilder? buildCounter; |
| 37 | + final bool expands; |
| 38 | + final int? minLines; |
| 39 | + final bool showCursor; |
| 40 | + final DateTime firstDate; |
| 41 | + final DateTime lastDate; |
| 42 | + final Locale? locale; |
| 43 | + final intl.DateFormat? format; |
| 44 | + final String? cancelText; // widget.cancelText, |
| 45 | + final String? confirmText; // widget.confirmText, |
| 46 | + final DateTime? currentDate; // widget.currentDate, |
| 47 | + final String? errorFormatText; // widget.erroerrorFormatText, |
| 48 | + final Widget Function(BuildContext, Widget?)? pickerBuilder; // widget.builder, |
| 49 | + final String? errorInvalidRangeText; // widget.errorInvalidRangeText, |
| 50 | + final String? errorInvalidText; // widget.errorInvalidText, |
| 51 | + final String? fieldEndHintText; // widget.fieldEndHintText, |
| 52 | + final String? fieldEndLabelText; // widget.fieldEndLabelText, |
| 53 | + final String? fieldStartHintText; // widget.fieldStartHintText, |
| 54 | + final String? fieldStartLabelText; // widget.fieldStartLabelText, |
| 55 | + final String? helpText; // widget.helpText, |
| 56 | + // final DateTimeRange initialDateRange; // widget.initialDateRange, |
| 57 | + final DatePickerEntryMode initialEntryMode; // widget.initialEntryMode, |
| 58 | + final RouteSettings? routeSettings; // widget.routeSettings, |
| 59 | + final String? saveText; // widget.saveText, |
| 60 | + final bool useRootNavigator; // widget.useRootNavigator, |
| 61 | + |
| 62 | + /// Creates field for selecting a range of dates |
| 63 | + FormBuilderDateRangePicker({ |
| 64 | + Key? key, |
| 65 | + //From Super |
| 66 | + required String name, |
| 67 | + FormFieldValidator<DateTimeRange>? validator, |
| 68 | + DateTimeRange? initialValue, |
| 69 | + InputDecoration decoration = const InputDecoration(), |
| 70 | + ValueChanged<DateTimeRange>? onChanged, |
| 71 | + ValueTransformer<DateTimeRange>? valueTransformer, |
| 72 | + bool enabled = true, |
| 73 | + FormFieldSetter<DateTimeRange>? onSaved, |
| 74 | + AutovalidateMode autovalidateMode = AutovalidateMode.disabled, |
| 75 | + VoidCallback? onReset, |
| 76 | + FocusNode? focusNode, |
| 77 | + required this.firstDate, |
| 78 | + required this.lastDate, |
| 79 | + this.format, |
| 80 | + this.maxLines = 1, |
| 81 | + this.obscureText = false, |
| 82 | + this.textCapitalization = TextCapitalization.none, |
| 83 | + this.scrollPadding = const EdgeInsets.all(20.0), |
| 84 | + this.enableInteractiveSelection = true, |
| 85 | + this.maxLengthEnforcement, |
| 86 | + this.textAlign = TextAlign.start, |
| 87 | + this.autofocus = false, |
| 88 | + this.autocorrect = true, |
| 89 | + this.cursorWidth = 2.0, |
| 90 | + this.keyboardType, |
| 91 | + this.style, |
| 92 | + this.controller, |
| 93 | + this.textInputAction, |
| 94 | + this.strutStyle, |
| 95 | + this.textDirection, |
| 96 | + this.maxLength, |
| 97 | + this.onEditingComplete, |
| 98 | + this.onFieldSubmitted, |
| 99 | + this.inputFormatters, |
| 100 | + this.cursorRadius, |
| 101 | + this.cursorColor, |
| 102 | + this.keyboardAppearance, |
| 103 | + this.buildCounter, |
| 104 | + this.expands = false, |
| 105 | + this.minLines, |
| 106 | + this.showCursor = false, |
| 107 | + this.locale, |
| 108 | + this.cancelText, |
| 109 | + this.confirmText, |
| 110 | + this.currentDate, |
| 111 | + this.errorFormatText, |
| 112 | + this.pickerBuilder, |
| 113 | + this.errorInvalidRangeText, |
| 114 | + this.errorInvalidText, |
| 115 | + this.fieldEndHintText, |
| 116 | + this.fieldEndLabelText, |
| 117 | + this.fieldStartHintText, |
| 118 | + this.fieldStartLabelText, |
| 119 | + this.helpText, |
| 120 | + // this.initialDateRange, |
| 121 | + this.initialEntryMode = DatePickerEntryMode.calendar, |
| 122 | + this.routeSettings, |
| 123 | + this.saveText, |
| 124 | + this.useRootNavigator = true, |
| 125 | + }) : super( |
| 126 | + key: key, |
| 127 | + initialValue: initialValue, |
| 128 | + name: name, |
| 129 | + validator: validator, |
| 130 | + valueTransformer: valueTransformer, |
| 131 | + onChanged: onChanged, |
| 132 | + autovalidateMode: autovalidateMode, |
| 133 | + onSaved: onSaved, |
| 134 | + enabled: enabled, |
| 135 | + onReset: onReset, |
| 136 | + decoration: decoration, |
| 137 | + focusNode: focusNode, |
| 138 | + builder: (FormFieldState<DateTimeRange?> field) { |
| 139 | + final state = field as FormBuilderDateRangePickerState; |
| 140 | + |
| 141 | + return TextField( |
| 142 | + enabled: state.enabled, |
| 143 | + style: style, |
| 144 | + focusNode: state.effectiveFocusNode, |
| 145 | + decoration: state.decoration(), |
| 146 | + // initialValue: "${_initialValue ?? ''}", |
| 147 | + maxLines: maxLines, |
| 148 | + keyboardType: keyboardType, |
| 149 | + obscureText: obscureText, |
| 150 | + onEditingComplete: onEditingComplete, |
| 151 | + controller: state._effectiveController, |
| 152 | + autocorrect: autocorrect, |
| 153 | + autofocus: autofocus, |
| 154 | + buildCounter: buildCounter, |
| 155 | + cursorColor: cursorColor, |
| 156 | + cursorRadius: cursorRadius, |
| 157 | + cursorWidth: cursorWidth, |
| 158 | + enableInteractiveSelection: enableInteractiveSelection, |
| 159 | + maxLength: maxLength, |
| 160 | + inputFormatters: inputFormatters, |
| 161 | + keyboardAppearance: keyboardAppearance, |
| 162 | + maxLengthEnforcement: maxLengthEnforcement, |
| 163 | + scrollPadding: scrollPadding, |
| 164 | + textAlign: textAlign, |
| 165 | + textCapitalization: textCapitalization, |
| 166 | + textDirection: textDirection, |
| 167 | + textInputAction: textInputAction, |
| 168 | + strutStyle: strutStyle, |
| 169 | + readOnly: true, |
| 170 | + expands: expands, |
| 171 | + minLines: minLines, |
| 172 | + showCursor: showCursor, |
| 173 | + ); |
| 174 | + }, |
| 175 | + ); |
| 176 | + |
| 177 | + @override |
| 178 | + FormBuilderDateRangePickerState createState() => |
| 179 | + FormBuilderDateRangePickerState(); |
| 180 | + |
| 181 | + static String tryFormat(DateTime date, intl.DateFormat format) { |
| 182 | + if (date != null) { |
| 183 | + try { |
| 184 | + return format.format(date); |
| 185 | + } catch (e) { |
| 186 | + // print('Error formatting date: $e'); |
| 187 | + } |
| 188 | + } |
| 189 | + return ''; |
| 190 | + } |
| 191 | +} |
| 192 | + |
| 193 | +class FormBuilderDateRangePickerState |
| 194 | + extends FormBuilderFieldState<FormBuilderDateRangePicker, DateTimeRange> { |
| 195 | + late TextEditingController _effectiveController; |
| 196 | + |
| 197 | + @override |
| 198 | + void initState() { |
| 199 | + super.initState(); |
| 200 | + _effectiveController = |
| 201 | + widget.controller ?? TextEditingController(text: _valueToText()); |
| 202 | + effectiveFocusNode!.addListener(_handleFocus); |
| 203 | + } |
| 204 | + |
| 205 | + @override |
| 206 | + void dispose() { |
| 207 | + effectiveFocusNode!.removeListener(_handleFocus); |
| 208 | + // Dispose the _effectiveController when initState created it |
| 209 | + if (null == widget.controller) { |
| 210 | + _effectiveController.dispose(); |
| 211 | + } |
| 212 | + super.dispose(); |
| 213 | + } |
| 214 | + |
| 215 | + Future<void> _handleFocus() async { |
| 216 | + if (effectiveFocusNode!.hasFocus && enabled) { |
| 217 | + effectiveFocusNode!.unfocus(); |
| 218 | + /*final initialFirstDate = value?.isEmpty ?? true |
| 219 | + ? (widget.initialFirstDate ?? DateTime.now()) |
| 220 | + : value[0]; |
| 221 | + final initialLastDate = value?.isEmpty ?? true |
| 222 | + ? (widget.initialLastDate ?? initialFirstDate) |
| 223 | + : (value.length < 2 ? initialFirstDate : value[1]);*/ |
| 224 | + final picked = await showDateRangePicker( |
| 225 | + context: context, |
| 226 | + firstDate: widget.firstDate, |
| 227 | + lastDate: widget.lastDate, |
| 228 | + locale: widget.locale, |
| 229 | + textDirection: widget.textDirection, |
| 230 | + cancelText: widget.cancelText, |
| 231 | + confirmText: widget.confirmText, |
| 232 | + currentDate: widget.currentDate, |
| 233 | + errorFormatText: widget.errorFormatText, |
| 234 | + builder: widget.pickerBuilder, |
| 235 | + errorInvalidRangeText: widget.errorInvalidRangeText, |
| 236 | + errorInvalidText: widget.errorInvalidText, |
| 237 | + fieldEndHintText: widget.fieldEndHintText, |
| 238 | + fieldEndLabelText: widget.fieldEndLabelText, |
| 239 | + fieldStartHintText: widget.fieldStartHintText, |
| 240 | + fieldStartLabelText: widget.fieldStartLabelText, |
| 241 | + helpText: widget.helpText, |
| 242 | + initialDateRange: value, |
| 243 | + initialEntryMode: widget.initialEntryMode, |
| 244 | + routeSettings: widget.routeSettings, |
| 245 | + saveText: widget.saveText, |
| 246 | + useRootNavigator: widget.useRootNavigator, |
| 247 | + ); |
| 248 | + if (picked != null) { |
| 249 | + didChange(picked); |
| 250 | + } |
| 251 | + } |
| 252 | + } |
| 253 | + |
| 254 | + String _valueToText() { |
| 255 | + if (value == null) { |
| 256 | + return ''; |
| 257 | + } |
| 258 | + |
| 259 | + return '${format(value!.start)} - ${format(value!.end)}'; |
| 260 | + } |
| 261 | + |
| 262 | + String format(DateTime date) => FormBuilderDateRangePicker.tryFormat( |
| 263 | + date, widget.format ?? intl.DateFormat.yMd()); |
| 264 | + |
| 265 | + void _setTextFieldString() { |
| 266 | + setState(() { |
| 267 | + _effectiveController.text = _valueToText(); |
| 268 | + }); |
| 269 | + } |
| 270 | + |
| 271 | + @override |
| 272 | + void didChange(DateTimeRange? value) { |
| 273 | + super.didChange(value); |
| 274 | + _setTextFieldString(); |
| 275 | + } |
| 276 | + |
| 277 | + @override |
| 278 | + void reset() { |
| 279 | + super.reset(); |
| 280 | + _setTextFieldString(); |
| 281 | + } |
| 282 | +} |
0 commit comments