diff --git a/packages/flutter/lib/src/cupertino/date_picker.dart b/packages/flutter/lib/src/cupertino/date_picker.dart index a03adc503e..add006f8d3 100644 --- a/packages/flutter/lib/src/cupertino/date_picker.dart +++ b/packages/flutter/lib/src/cupertino/date_picker.dart @@ -207,6 +207,8 @@ enum _PickerColumnType { minute, // AM/PM column in time and dateAndTime mode. dayPeriod, + // Time separator column in time and dateAndTime mode. + timeSeparator, } /// A date picker widget in iOS style. @@ -303,6 +305,7 @@ class CupertinoDatePicker extends StatefulWidget { this.dateOrder, this.backgroundColor, this.showDayOfWeek = false, + this.showTimeSeparator = false, this.itemExtent = _kItemExtent, this.selectionOverlayBuilder, }) : initialDateTime = initialDateTime ?? DateTime.now(), @@ -353,6 +356,12 @@ class CupertinoDatePicker extends StatefulWidget { assert( (initialDateTime ?? DateTime.now()).minute % minuteInterval == 0, 'initial minute is not divisible by minute interval', + ), + assert( + !showTimeSeparator || + mode == CupertinoDatePickerMode.dateAndTime || + mode == CupertinoDatePickerMode.time, + 'showTimeSeparator is only supported in time or dateAndTime modes', ); /// The mode of the date picker as one of [CupertinoDatePickerMode]. Defaults @@ -435,6 +444,16 @@ class CupertinoDatePicker extends StatefulWidget { /// Defaults to false. final bool showDayOfWeek; + /// Whether to show the time separator between hour and minute in the time + /// [CupertinoDatePickerMode.time] and datetime [CupertinoDatePickerMode.dateAndTime] + /// picker modes. + /// + /// Throws an error if set to true in [CupertinoDatePickerMode.date] + /// and [CupertinoDatePickerMode.monthYear] mode. + /// + /// Defaults to false. + final bool showTimeSeparator; + /// {@macro flutter.cupertino.picker.itemExtent} /// /// Defaults to a value that matches the default iOS date picker wheel. @@ -549,6 +568,8 @@ class CupertinoDatePicker extends StatefulWidget { } case _PickerColumnType.year: longTexts.add(localizations.datePickerYear(2018)); + case _PickerColumnType.timeSeparator: + longTexts.add(':'); } assert( @@ -1045,6 +1066,29 @@ class _CupertinoDatePickerDateTimeState extends State { ); } + // Builds the time separator column. + Widget _buildTimeSeparatorWidget( + double offAxisFraction, + TransitionBuilder itemPositioningBuilder, + Widget? selectionOverlay, + ) { + return ExcludeSemantics( + child: CupertinoPicker( + offAxisFraction: offAxisFraction, + itemExtent: widget.itemExtent, + useMagnifier: _kUseMagnifier, + magnification: _kMagnification, + backgroundColor: widget.backgroundColor, + squeeze: _kSqueeze, + onSelectedItemChanged: (int index) {}, + selectionOverlay: selectionOverlay, + children: List.generate(1, (int index) { + return itemPositioningBuilder(context, Text(':', style: _themeTextStyle(context))); + }), + ), + ); + } + // One or more pickers have just stopped scrolling. void _pickerDidStopScrolling() { // Call setState to update the greyed out date/hour/minute/meridiem. @@ -1118,6 +1162,11 @@ class _CupertinoDatePickerDateTimeState extends State { ? <_ColumnBuilder>[_buildMinutePicker, _buildHourPicker] : <_ColumnBuilder>[_buildHourPicker, _buildMinutePicker]; + // Adds time separator column if the picker is showing time separator. + if (widget.showTimeSeparator) { + columnWidths.insert(1, _getEstimatedColumnWidth(_PickerColumnType.timeSeparator)); + pickerBuilders.insert(1, _buildTimeSeparatorWidget); + } // Adds am/pm column if the picker is not using 24h format. if (!widget.use24hFormat) { switch (localizations.datePickerDateTimeOrder) { diff --git a/packages/flutter/test/cupertino/date_picker_test.dart b/packages/flutter/test/cupertino/date_picker_test.dart index 0d21093af8..883a664b2d 100644 --- a/packages/flutter/test/cupertino/date_picker_test.dart +++ b/packages/flutter/test/cupertino/date_picker_test.dart @@ -2488,6 +2488,87 @@ void main() { expect(widths.indexOf(largestWidth), equals(1)); }, skip: isBrowser); // https://github.com/flutter/flutter/issues/39998 + test('showTimeSeparator is only supported in time or dateAndTime mode', () async { + expect( + () => CupertinoDatePicker( + mode: CupertinoDatePickerMode.time, + onDateTimeChanged: (DateTime _) {}, + showTimeSeparator: true, + ), + returnsNormally, + ); + + expect( + () => CupertinoDatePicker(onDateTimeChanged: (DateTime _) {}, showTimeSeparator: true), + returnsNormally, + ); + + expect( + () => CupertinoDatePicker( + mode: CupertinoDatePickerMode.date, + onDateTimeChanged: (DateTime _) {}, + showTimeSeparator: true, + ), + throwsA( + isA().having( + (AssertionError e) => e.message ?? 'Unknown error', + 'message', + contains('showTimeSeparator is only supported in time or dateAndTime modes'), + ), + ), + ); + + expect( + () => CupertinoDatePicker( + mode: CupertinoDatePickerMode.monthYear, + onDateTimeChanged: (DateTime _) {}, + showTimeSeparator: true, + ), + throwsA( + isA().having( + (AssertionError e) => e.message ?? 'Unknown error', + 'message', + contains('showTimeSeparator is only supported in time or dateAndTime modes'), + ), + ), + ); + }); + + testWidgets('Time separator widget should be rendered when flag is set to true', ( + WidgetTester tester, + ) async { + await tester.pumpWidget( + CupertinoApp( + home: Center( + child: CupertinoDatePicker( + mode: CupertinoDatePickerMode.time, + onDateTimeChanged: (DateTime dateTime) {}, + showTimeSeparator: true, + ), + ), + ), + ); + + expect(find.text(':'), findsOneWidget); + }); + + testWidgets('Time separator widget should not be rendered when flag is set to false', ( + WidgetTester tester, + ) async { + await tester.pumpWidget( + CupertinoApp( + home: Center( + child: CupertinoDatePicker( + mode: CupertinoDatePickerMode.time, + onDateTimeChanged: (DateTime _) {}, + ), + ), + ), + ); + + expect(find.text(':'), findsNothing); + }); + // Regression test for https://github.com/flutter/flutter/issues/161773 testWidgets('CupertinoDatePicker date value baseline alignment', (WidgetTester tester) async { await tester.pumpWidget(