1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// @docImport 'date_picker.dart';
6/// @docImport 'text_field.dart';
7library;
8
9import 'package:flutter/widgets.dart';
10
11import 'date.dart';
12import 'date_picker_theme.dart';
13import 'input_border.dart';
14import 'input_decorator.dart';
15import 'material_localizations.dart';
16import 'text_form_field.dart';
17import 'theme.dart';
18
19/// A [TextFormField] configured to accept and validate a date entered by a user.
20///
21/// When the field is saved or submitted, the text will be parsed into a
22/// [DateTime] according to the ambient locale's compact date format. If the
23/// input text doesn't parse into a date, the [errorFormatText] message will
24/// be displayed under the field.
25///
26/// [firstDate], [lastDate], and [selectableDayPredicate] provide constraints on
27/// what days are valid. If the input date isn't in the date range or doesn't pass
28/// the given predicate, then the [errorInvalidText] message will be displayed
29/// under the field.
30///
31/// See also:
32///
33/// * [showDatePicker], which shows a dialog that contains a Material Design
34/// date picker which includes support for text entry of dates.
35/// * [MaterialLocalizations.parseCompactDate], which is used to parse the text
36/// input into a [DateTime].
37///
38class InputDatePickerFormField extends StatefulWidget {
39 /// Creates a [TextFormField] configured to accept and validate a date.
40 ///
41 /// If the optional [initialDate] is provided, then it will be used to populate
42 /// the text field. If the [fieldHintText] is provided, it will be shown.
43 ///
44 /// If [initialDate] is provided, it must not be before [firstDate] or after
45 /// [lastDate]. If [selectableDayPredicate] is provided, it must return `true`
46 /// for [initialDate].
47 ///
48 /// [firstDate] must be on or before [lastDate].
49 InputDatePickerFormField({
50 super.key,
51 DateTime? initialDate,
52 required DateTime firstDate,
53 required DateTime lastDate,
54 this.onDateSubmitted,
55 this.onDateSaved,
56 this.selectableDayPredicate,
57 this.errorFormatText,
58 this.errorInvalidText,
59 this.fieldHintText,
60 this.fieldLabelText,
61 this.keyboardType,
62 this.autofocus = false,
63 this.acceptEmptyDate = false,
64 this.focusNode,
65 this.calendarDelegate = const GregorianCalendarDelegate(),
66 }) : initialDate = initialDate != null ? calendarDelegate.dateOnly(initialDate) : null,
67 firstDate = calendarDelegate.dateOnly(firstDate),
68 lastDate = calendarDelegate.dateOnly(lastDate) {
69 assert(
70 !this.lastDate.isBefore(this.firstDate),
71 'lastDate ${this.lastDate} must be on or after firstDate ${this.firstDate}.',
72 );
73 assert(
74 initialDate == null || !this.initialDate!.isBefore(this.firstDate),
75 'initialDate ${this.initialDate} must be on or after firstDate ${this.firstDate}.',
76 );
77 assert(
78 initialDate == null || !this.initialDate!.isAfter(this.lastDate),
79 'initialDate ${this.initialDate} must be on or before lastDate ${this.lastDate}.',
80 );
81 assert(
82 selectableDayPredicate == null ||
83 initialDate == null ||
84 selectableDayPredicate!(this.initialDate!),
85 'Provided initialDate ${this.initialDate} must satisfy provided selectableDayPredicate.',
86 );
87 }
88
89 /// If provided, it will be used as the default value of the field.
90 final DateTime? initialDate;
91
92 /// The earliest allowable [DateTime] that the user can input.
93 final DateTime firstDate;
94
95 /// The latest allowable [DateTime] that the user can input.
96 final DateTime lastDate;
97
98 /// An optional method to call when the user indicates they are done editing
99 /// the text in the field. Will only be called if the input represents a valid
100 /// [DateTime].
101 final ValueChanged<DateTime>? onDateSubmitted;
102
103 /// An optional method to call with the final date when the form is
104 /// saved via [FormState.save]. Will only be called if the input represents
105 /// a valid [DateTime].
106 final ValueChanged<DateTime>? onDateSaved;
107
108 /// Function to provide full control over which [DateTime] can be selected.
109 final SelectableDayPredicate? selectableDayPredicate;
110
111 /// The error text displayed if the entered date is not in the correct format.
112 final String? errorFormatText;
113
114 /// The error text displayed if the date is not valid.
115 ///
116 /// A date is not valid if it is earlier than [firstDate], later than
117 /// [lastDate], or doesn't pass the [selectableDayPredicate].
118 final String? errorInvalidText;
119
120 /// The hint text displayed in the [TextField].
121 ///
122 /// If this is null, it will default to the date format string. For example,
123 /// 'mm/dd/yyyy' for en_US.
124 final String? fieldHintText;
125
126 /// The label text displayed in the [TextField].
127 ///
128 /// If this is null, it will default to the words representing the date format
129 /// string. For example, 'Month, Day, Year' for en_US.
130 final String? fieldLabelText;
131
132 /// The keyboard type of the [TextField].
133 ///
134 /// If this is null, it will default to [TextInputType.datetime]
135 final TextInputType? keyboardType;
136
137 /// {@macro flutter.widgets.editableText.autofocus}
138 final bool autofocus;
139
140 /// Determines if an empty date would show [errorFormatText] or not.
141 ///
142 /// Defaults to false.
143 ///
144 /// If true, [errorFormatText] is not shown when the date input field is empty.
145 final bool acceptEmptyDate;
146
147 /// {@macro flutter.widgets.Focus.focusNode}
148 final FocusNode? focusNode;
149
150 /// {@macro flutter.material.calendar_date_picker.calendarDelegate}
151 final CalendarDelegate<DateTime> calendarDelegate;
152
153 @override
154 State<InputDatePickerFormField> createState() => _InputDatePickerFormFieldState();
155}
156
157class _InputDatePickerFormFieldState extends State<InputDatePickerFormField> {
158 final TextEditingController _controller = TextEditingController();
159 DateTime? _selectedDate;
160 String? _inputText;
161 bool _autoSelected = false;
162
163 @override
164 void initState() {
165 super.initState();
166 _selectedDate = widget.initialDate;
167 }
168
169 @override
170 void dispose() {
171 _controller.dispose();
172 super.dispose();
173 }
174
175 @override
176 void didChangeDependencies() {
177 super.didChangeDependencies();
178 _updateValueForSelectedDate();
179 }
180
181 @override
182 void didUpdateWidget(InputDatePickerFormField oldWidget) {
183 super.didUpdateWidget(oldWidget);
184 if (widget.initialDate != oldWidget.initialDate) {
185 // Can't update the form field in the middle of a build, so do it next frame
186 WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) {
187 setState(() {
188 _selectedDate = widget.initialDate;
189 _updateValueForSelectedDate();
190 });
191 }, debugLabel: 'InputDatePickerFormField.update');
192 }
193 }
194
195 void _updateValueForSelectedDate() {
196 if (_selectedDate != null) {
197 final MaterialLocalizations localizations = MaterialLocalizations.of(context);
198 _inputText = widget.calendarDelegate.formatCompactDate(_selectedDate!, localizations);
199 TextEditingValue textEditingValue = TextEditingValue(text: _inputText!);
200 // Select the new text if we are auto focused and haven't selected the text before.
201 if (widget.autofocus && !_autoSelected) {
202 textEditingValue = textEditingValue.copyWith(
203 selection: TextSelection(baseOffset: 0, extentOffset: _inputText!.length),
204 );
205 _autoSelected = true;
206 }
207 _controller.value = textEditingValue;
208 } else {
209 _inputText = '';
210 _controller.value = TextEditingValue(text: _inputText!);
211 }
212 }
213
214 DateTime? _parseDate(String? text) {
215 final MaterialLocalizations localizations = MaterialLocalizations.of(context);
216 return widget.calendarDelegate.parseCompactDate(text, localizations);
217 }
218
219 bool _isValidAcceptableDate(DateTime? date) {
220 return date != null &&
221 !date.isBefore(widget.firstDate) &&
222 !date.isAfter(widget.lastDate) &&
223 (widget.selectableDayPredicate == null || widget.selectableDayPredicate!(date));
224 }
225
226 String? _validateDate(String? text) {
227 if ((text == null || text.isEmpty) && widget.acceptEmptyDate) {
228 return null;
229 }
230 final DateTime? date = _parseDate(text);
231 if (date == null) {
232 return widget.errorFormatText ?? MaterialLocalizations.of(context).invalidDateFormatLabel;
233 } else if (!_isValidAcceptableDate(date)) {
234 return widget.errorInvalidText ?? MaterialLocalizations.of(context).dateOutOfRangeLabel;
235 }
236 return null;
237 }
238
239 void _updateDate(String? text, ValueChanged<DateTime>? callback) {
240 final DateTime? date = _parseDate(text);
241 if (_isValidAcceptableDate(date)) {
242 _selectedDate = date;
243 _inputText = text;
244 callback?.call(_selectedDate!);
245 }
246 }
247
248 void _handleSaved(String? text) {
249 _updateDate(text, widget.onDateSaved);
250 }
251
252 void _handleSubmitted(String text) {
253 _updateDate(text, widget.onDateSubmitted);
254 }
255
256 @override
257 Widget build(BuildContext context) {
258 final ThemeData theme = Theme.of(context);
259 final bool useMaterial3 = theme.useMaterial3;
260 final MaterialLocalizations localizations = MaterialLocalizations.of(context);
261 final DatePickerThemeData datePickerTheme = theme.datePickerTheme;
262 final InputDecorationTheme inputTheme = theme.inputDecorationTheme;
263 final InputBorder effectiveInputBorder =
264 datePickerTheme.inputDecorationTheme?.border ??
265 theme.inputDecorationTheme.border ??
266 (useMaterial3 ? const OutlineInputBorder() : const UnderlineInputBorder());
267
268 return Semantics(
269 container: true,
270 child: TextFormField(
271 decoration: InputDecoration(
272 hintText: widget.fieldHintText ?? widget.calendarDelegate.dateHelpText(localizations),
273 labelText: widget.fieldLabelText ?? localizations.dateInputLabel,
274 ).applyDefaults(
275 inputTheme
276 .merge(datePickerTheme.inputDecorationTheme)
277 .copyWith(border: effectiveInputBorder),
278 ),
279 validator: _validateDate,
280 keyboardType: widget.keyboardType ?? TextInputType.datetime,
281 onSaved: _handleSaved,
282 onFieldSubmitted: _handleSubmitted,
283 autofocus: widget.autofocus,
284 controller: _controller,
285 focusNode: widget.focusNode,
286 ),
287 );
288 }
289}
290

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com