Skip to content

Commit da1de1d

Browse files
authored
fix(genui): fix DateTimeInput core catalog item (#622)
1 parent 4ef7ec6 commit da1de1d

File tree

2 files changed

+417
-71
lines changed

2 files changed

+417
-71
lines changed

packages/genui/lib/src/catalog/core_widgets/date_time_input.dart

Lines changed: 169 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)