@@ -18,7 +18,14 @@ final _schema = S.object(
1818 ),
1919 'enableDate' : S .boolean (),
2020 'enableTime' : S .boolean (),
21- 'outputFormat' : S .string (),
21+ 'firstDate' : S .string (
22+ description:
23+ 'The earliest selectable date (YYYY-MM-DD). Defaults to -9999-01-01.' ,
24+ ),
25+ 'lastDate' : S .string (
26+ description:
27+ 'The latest selectable date (YYYY-MM-DD). Defaults to 9999-12-31.' ,
28+ ),
2229 },
2330 required : ['value' ],
2431);
@@ -28,18 +35,24 @@ extension type _DateTimeInputData.fromMap(JsonMap _json) {
2835 required JsonMap value,
2936 bool ? enableDate,
3037 bool ? enableTime,
31- String ? outputFormat,
38+ String ? firstDate,
39+ String ? lastDate,
3240 }) => _DateTimeInputData .fromMap ({
3341 'value' : value,
3442 'enableDate' : enableDate,
3543 'enableTime' : enableTime,
36- 'outputFormat' : outputFormat,
44+ 'firstDate' : firstDate,
45+ 'lastDate' : lastDate,
3746 });
3847
3948 JsonMap get value => _json['value' ] as JsonMap ;
4049 bool get enableDate => (_json['enableDate' ] as bool ? ) ?? true ;
4150 bool get enableTime => (_json['enableTime' ] as bool ? ) ?? true ;
42- String ? get outputFormat => _json['outputFormat' ] as String ? ;
51+ DateTime get firstDate =>
52+ DateTime .tryParse (_json['firstDate' ] as String ? ?? '' ) ?? DateTime (- 9999 );
53+ DateTime get lastDate =>
54+ DateTime .tryParse (_json['lastDate' ] as String ? ?? '' ) ??
55+ DateTime (9999 , 12 , 31 );
4356}
4457
4558/// A catalog item representing a Material Design date and/or time input field.
@@ -69,40 +82,24 @@ final dateTimeInput = CatalogItem(
6982 return ValueListenableBuilder <String ?>(
7083 valueListenable: valueNotifier,
7184 builder: (context, value, child) {
85+ final MaterialLocalizations localizations = MaterialLocalizations .of (
86+ context,
87+ );
88+ final String displayText = _getDisplayText (
89+ value,
90+ dateTimeInputData,
91+ localizations,
92+ );
93+
7294 return ListTile (
73- title: Text (value ?? 'Select a date/time' ),
74- onTap: () async {
75- final path = dateTimeInputData.value['path' ] as String ? ;
76- if (path == null ) {
77- return ;
78- }
79- if (dateTimeInputData.enableDate) {
80- final DateTime ? date = await showDatePicker (
81- context: itemContext.buildContext,
82- initialDate: DateTime .now (),
83- firstDate: DateTime (2000 ),
84- lastDate: DateTime (2100 ),
85- );
86- if (date != null ) {
87- itemContext.dataContext.update (
88- DataPath (path),
89- date.toIso8601String (),
90- );
91- }
92- }
93- if (dateTimeInputData.enableTime) {
94- final TimeOfDay ? time = await showTimePicker (
95- context: itemContext.buildContext,
96- initialTime: TimeOfDay .now (),
97- );
98- if (time != null ) {
99- itemContext.dataContext.update (
100- DataPath (path),
101- time.format (itemContext.buildContext),
102- );
103- }
104- }
105- },
95+ key: Key (itemContext.id),
96+ title: Text (displayText, key: Key ('${itemContext .id }_text' )),
97+ onTap: () => _handleTap (
98+ context: itemContext.buildContext,
99+ dataContext: itemContext.dataContext,
100+ data: dateTimeInputData,
101+ value: value,
102+ ),
106103 );
107104 },
108105 );
@@ -122,5 +119,140 @@ final dateTimeInput = CatalogItem(
122119 }
123120 ]
124121 ''' ,
122+ () => '''
123+ [
124+ {
125+ "id": "root",
126+ "component": {
127+ "DateTimeInput": {
128+ "value": {
129+ "path": "/myDate"
130+ },
131+ "enableTime": false
132+ }
133+ }
134+ }
135+ ]
136+ ''' ,
137+ () => '''
138+ [
139+ {
140+ "id": "root",
141+ "component": {
142+ "DateTimeInput": {
143+ "value": {
144+ "path": "/myTime"
145+ },
146+ "enableDate": false
147+ }
148+ }
149+ }
150+ ]
151+ ''' ,
125152 ],
126153);
154+
155+ Future <void > _handleTap ({
156+ required BuildContext context,
157+ required DataContext dataContext,
158+ required _DateTimeInputData data,
159+ required String ? value,
160+ }) async {
161+ final path = data.value['path' ] as String ? ;
162+ if (path == null ) {
163+ return ;
164+ }
165+
166+ final DateTime initialDate =
167+ DateTime .tryParse (value ?? '' ) ??
168+ DateTime .tryParse ('1970-01-01T$value ' ) ??
169+ DateTime .now ();
170+
171+ var resultDate = initialDate;
172+ var resultTime = TimeOfDay .fromDateTime (initialDate);
173+
174+ if (data.enableDate) {
175+ final DateTime ? pickedDate = await showDatePicker (
176+ context: context,
177+ initialDate: initialDate,
178+ firstDate: data.firstDate,
179+ lastDate: data.lastDate,
180+ );
181+ if (pickedDate == null ) return ; // User cancelled.
182+ resultDate = pickedDate;
183+ }
184+
185+ if (data.enableTime) {
186+ final TimeOfDay ? pickedTime = await showTimePicker (
187+ context: context,
188+ initialTime: TimeOfDay .fromDateTime (initialDate),
189+ );
190+ if (pickedTime == null ) return ; // User cancelled.
191+ resultTime = pickedTime;
192+ }
193+
194+ final finalDateTime = DateTime (
195+ resultDate.year,
196+ resultDate.month,
197+ resultDate.day,
198+ data.enableTime ? resultTime.hour : 0 ,
199+ data.enableTime ? resultTime.minute : 0 ,
200+ );
201+
202+ String formattedValue;
203+
204+ if (data.enableDate && ! data.enableTime) {
205+ formattedValue = finalDateTime.toIso8601String ().split ('T' ).first;
206+ } else if (! data.enableDate && data.enableTime) {
207+ final String hour = finalDateTime.hour.toString ().padLeft (2 , '0' );
208+ final String minute = finalDateTime.minute.toString ().padLeft (2 , '0' );
209+ formattedValue = '$hour :$minute :00' ;
210+ } else {
211+ // Both enabled (or both disabled, which shouldn't happen),
212+ // write full ISO string.
213+ formattedValue = finalDateTime.toIso8601String ();
214+ }
215+
216+ dataContext.update (DataPath (path), formattedValue);
217+ }
218+
219+ String _getDisplayText (
220+ String ? value,
221+ _DateTimeInputData data,
222+ MaterialLocalizations localizations,
223+ ) {
224+ String getPlaceholderText () {
225+ if (data.enableDate && data.enableTime) {
226+ return 'Select a date and time' ;
227+ } else if (data.enableDate) {
228+ return 'Select a date' ;
229+ } else if (data.enableTime) {
230+ return 'Select a time' ;
231+ }
232+ return 'Select a date/time' ;
233+ }
234+
235+ DateTime ? tryParseDateOrTime (String value) {
236+ return DateTime .tryParse (value) ?? DateTime .tryParse ('1970-01-01T$value ' );
237+ }
238+
239+ String formatDateTime (DateTime date) {
240+ final List <String > parts = [
241+ if (data.enableDate) localizations.formatFullDate (date),
242+ if (data.enableTime)
243+ localizations.formatTimeOfDay (TimeOfDay .fromDateTime (date)),
244+ ];
245+ return parts.join (' ' );
246+ }
247+
248+ if (value == null ) {
249+ return getPlaceholderText ();
250+ }
251+
252+ final DateTime ? date = tryParseDateOrTime (value);
253+ if (date == null ) {
254+ return value;
255+ }
256+
257+ return formatDateTime (date);
258+ }
0 commit comments