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 }) : initialDate = initialDate != null ? DateUtils.dateOnly(initialDate) : null,
66 firstDate = DateUtils.dateOnly(firstDate),
67 lastDate = DateUtils.dateOnly(lastDate) {
68 assert(
69 !this.lastDate.isBefore(this.firstDate),
70 'lastDate ${this.lastDate} must be on or after firstDate ${this.firstDate}.',
71 );
72 assert(
73 initialDate == null || !this.initialDate!.isBefore(this.firstDate),
74 'initialDate ${this.initialDate} must be on or after firstDate ${this.firstDate}.',
75 );
76 assert(
77 initialDate == null || !this.initialDate!.isAfter(this.lastDate),
78 'initialDate ${this.initialDate} must be on or before lastDate ${this.lastDate}.',
79 );
80 assert(
81 selectableDayPredicate == null || initialDate == null || selectableDayPredicate!(this.initialDate!),
82 'Provided initialDate ${this.initialDate} must satisfy provided selectableDayPredicate.',
83 );
84 }
85
86 /// If provided, it will be used as the default value of the field.
87 final DateTime? initialDate;
88
89 /// The earliest allowable [DateTime] that the user can input.
90 final DateTime firstDate;
91
92 /// The latest allowable [DateTime] that the user can input.
93 final DateTime lastDate;
94
95 /// An optional method to call when the user indicates they are done editing
96 /// the text in the field. Will only be called if the input represents a valid
97 /// [DateTime].
98 final ValueChanged<DateTime>? onDateSubmitted;
99
100 /// An optional method to call with the final date when the form is
101 /// saved via [FormState.save]. Will only be called if the input represents
102 /// a valid [DateTime].
103 final ValueChanged<DateTime>? onDateSaved;
104
105 /// Function to provide full control over which [DateTime] can be selected.
106 final SelectableDayPredicate? selectableDayPredicate;
107
108 /// The error text displayed if the entered date is not in the correct format.
109 final String? errorFormatText;
110
111 /// The error text displayed if the date is not valid.
112 ///
113 /// A date is not valid if it is earlier than [firstDate], later than
114 /// [lastDate], or doesn't pass the [selectableDayPredicate].
115 final String? errorInvalidText;
116
117 /// The hint text displayed in the [TextField].
118 ///
119 /// If this is null, it will default to the date format string. For example,
120 /// 'mm/dd/yyyy' for en_US.
121 final String? fieldHintText;
122
123 /// The label text displayed in the [TextField].
124 ///
125 /// If this is null, it will default to the words representing the date format
126 /// string. For example, 'Month, Day, Year' for en_US.
127 final String? fieldLabelText;
128
129 /// The keyboard type of the [TextField].
130 ///
131 /// If this is null, it will default to [TextInputType.datetime]
132 final TextInputType? keyboardType;
133
134 /// {@macro flutter.widgets.editableText.autofocus}
135 final bool autofocus;
136
137 /// Determines if an empty date would show [errorFormatText] or not.
138 ///
139 /// Defaults to false.
140 ///
141 /// If true, [errorFormatText] is not shown when the date input field is empty.
142 final bool acceptEmptyDate;
143
144 /// {@macro flutter.widgets.Focus.focusNode}
145 final FocusNode? focusNode;
146
147 @override
148 State<InputDatePickerFormField> createState() => _InputDatePickerFormFieldState();
149}
150
151class _InputDatePickerFormFieldState extends State<InputDatePickerFormField> {
152 final TextEditingController _controller = TextEditingController();
153 DateTime? _selectedDate;
154 String? _inputText;
155 bool _autoSelected = false;
156
157 @override
158 void initState() {
159 super.initState();
160 _selectedDate = widget.initialDate;
161 }
162
163 @override
164 void dispose() {
165 _controller.dispose();
166 super.dispose();
167 }
168
169 @override
170 void didChangeDependencies() {
171 super.didChangeDependencies();
172 _updateValueForSelectedDate();
173 }
174
175 @override
176 void didUpdateWidget(InputDatePickerFormField oldWidget) {
177 super.didUpdateWidget(oldWidget);
178 if (widget.initialDate != oldWidget.initialDate) {
179 // Can't update the form field in the middle of a build, so do it next frame
180 WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) {
181 setState(() {
182 _selectedDate = widget.initialDate;
183 _updateValueForSelectedDate();
184 });
185 }, debugLabel: 'InputDatePickerFormField.update');
186 }
187 }
188
189 void _updateValueForSelectedDate() {
190 if (_selectedDate != null) {
191 final MaterialLocalizations localizations = MaterialLocalizations.of(context);
192 _inputText = localizations.formatCompactDate(_selectedDate!);
193 TextEditingValue textEditingValue = TextEditingValue(text: _inputText!);
194 // Select the new text if we are auto focused and haven't selected the text before.
195 if (widget.autofocus && !_autoSelected) {
196 textEditingValue = textEditingValue.copyWith(selection: TextSelection(
197 baseOffset: 0,
198 extentOffset: _inputText!.length,
199 ));
200 _autoSelected = true;
201 }
202 _controller.value = textEditingValue;
203 } else {
204 _inputText = '';
205 _controller.value = TextEditingValue(text: _inputText!);
206 }
207 }
208
209 DateTime? _parseDate(String? text) {
210 final MaterialLocalizations localizations = MaterialLocalizations.of(context);
211 return localizations.parseCompactDate(text);
212 }
213
214 bool _isValidAcceptableDate(DateTime? date) {
215 return
216 date != null &&
217 !date.isBefore(widget.firstDate) &&
218 !date.isAfter(widget.lastDate) &&
219 (widget.selectableDayPredicate == null || widget.selectableDayPredicate!(date));
220 }
221
222 String? _validateDate(String? text) {
223 if ((text == null || text.isEmpty) && widget.acceptEmptyDate) {
224 return null;
225 }
226 final DateTime? date = _parseDate(text);
227 if (date == null) {
228 return widget.errorFormatText ?? MaterialLocalizations.of(context).invalidDateFormatLabel;
229 } else if (!_isValidAcceptableDate(date)) {
230 return widget.errorInvalidText ?? MaterialLocalizations.of(context).dateOutOfRangeLabel;
231 }
232 return null;
233 }
234
235 void _updateDate(String? text, ValueChanged<DateTime>? callback) {
236 final DateTime? date = _parseDate(text);
237 if (_isValidAcceptableDate(date)) {
238 _selectedDate = date;
239 _inputText = text;
240 callback?.call(_selectedDate!);
241 }
242 }
243
244 void _handleSaved(String? text) {
245 _updateDate(text, widget.onDateSaved);
246 }
247
248 void _handleSubmitted(String text) {
249 _updateDate(text, widget.onDateSubmitted);
250 }
251
252 @override
253 Widget build(BuildContext context) {
254 final ThemeData theme = Theme.of(context);
255 final bool useMaterial3 = theme.useMaterial3;
256 final MaterialLocalizations localizations = MaterialLocalizations.of(context);
257 final DatePickerThemeData datePickerTheme = theme.datePickerTheme;
258 final InputDecorationTheme inputTheme = theme.inputDecorationTheme;
259 final InputBorder effectiveInputBorder = datePickerTheme.inputDecorationTheme?.border
260 ?? theme.inputDecorationTheme.border
261 ?? (useMaterial3 ? const OutlineInputBorder() : const UnderlineInputBorder());
262
263 return Semantics(
264 container: true,
265 child: TextFormField(
266 decoration: InputDecoration(
267 hintText: widget.fieldHintText ?? localizations.dateHelpText,
268 labelText: widget.fieldLabelText ?? localizations.dateInputLabel,
269 ).applyDefaults(inputTheme
270 .merge(datePickerTheme.inputDecorationTheme)
271 .copyWith(border: effectiveInputBorder),
272 ),
273 validator: _validateDate,
274 keyboardType: widget.keyboardType ?? TextInputType.datetime,
275 onSaved: _handleSaved,
276 onFieldSubmitted: _handleSubmitted,
277 autofocus: widget.autofocus,
278 controller: _controller,
279 focusNode: widget.focusNode,
280 ),
281 );
282 }
283}
284

Provided by KDAB

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