diff --git a/packages/flutter/lib/src/cupertino/date_picker.dart b/packages/flutter/lib/src/cupertino/date_picker.dart index ff4c5349cde..76266b3817a 100644 --- a/packages/flutter/lib/src/cupertino/date_picker.dart +++ b/packages/flutter/lib/src/cupertino/date_picker.dart @@ -70,6 +70,10 @@ const Widget _startSelectionOverlay = CupertinoPickerDefaultSelectionOverlay(cap const Widget _centerSelectionOverlay = CupertinoPickerDefaultSelectionOverlay(capStartEdge: false, capEndEdge: false); const Widget _endSelectionOverlay = CupertinoPickerDefaultSelectionOverlay(capStartEdge: false); +/// Defines a function signature for creating a widget that serves as a selection overlay, +/// given the current context, the selected item's index, and the total number of columns. +typedef SelectionOverlayBuilder = Widget? Function(BuildContext context, { required int columnCount, required int selectedIndex }); + // Lays out the date picker based on how much space each single column needs. // // Each column is a child of this delegate, indexed from 0 to number of columns - 1. @@ -291,6 +295,7 @@ class CupertinoDatePicker extends StatefulWidget { this.backgroundColor, this.showDayOfWeek = false, this.itemExtent = _kItemExtent, + this.selectionOverlayBuilder, }) : initialDateTime = initialDateTime ?? DateTime.now(), assert( itemExtent > 0, @@ -413,6 +418,48 @@ class CupertinoDatePicker extends StatefulWidget { /// Defaults to a value that matches the default iOS date picker wheel. final double itemExtent; + /// A function that returns a widget that is overlaid on the picker + /// to highlight the currently selected entry. + /// + /// If unspecified, it defaults to a [CupertinoPickerDefaultSelectionOverlay] + /// which is a gray rounded rectangle overlay in iOS 14 style. + /// + /// If the selection overlay builder returns null, no overlay will be drawn. + /// + /// {@tool snippet} + /// + /// This example shows how to recreate the default selection overlay + /// with selectionOverlayBuilder. + /// + /// ```dart + /// CupertinoDatePicker( + /// onDateTimeChanged: (DateTime newDateTime) {}, + /// mode: CupertinoDatePickerMode.date, + /// initialDateTime: DateTime(2018, 9, 15), + /// selectionOverlayBuilder: ( + /// BuildContext context, { + /// required int selectedIndex, + /// required int columnCount, + /// }) { + /// if (selectedIndex == 0) { + /// return const CupertinoPickerDefaultSelectionOverlay( + /// capEndEdge: false, + /// ); + /// } else if (selectedIndex == columnCount - 1) { + /// return const CupertinoPickerDefaultSelectionOverlay( + /// capStartEdge: false, + /// ); + /// } + /// return const CupertinoPickerDefaultSelectionOverlay( + /// capStartEdge: false, + /// capEndEdge: false, + /// ); + /// }, + /// ) + /// ``` + /// {@end-tool} + final SelectionOverlayBuilder? selectionOverlayBuilder; + @override State createState() { // ignore: no_logic_in_create_state, https://github.com/flutter/flutter/issues/70499 @@ -511,7 +558,7 @@ class CupertinoDatePicker extends StatefulWidget { } } -typedef _ColumnBuilder = Widget Function(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget selectionOverlay); +typedef _ColumnBuilder = Widget Function(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget? selectionOverlay); class _CupertinoDatePickerDateTimeState extends State { // Fraction of the farthest column's vanishing point vs its width. Eyeballed @@ -716,7 +763,7 @@ class _CupertinoDatePickerDateTimeState extends State { } // Builds the date column. The date is displayed in medium date format (e.g. Fri Aug 31). - Widget _buildMediumDatePicker(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget selectionOverlay) { + Widget _buildMediumDatePicker(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget? selectionOverlay) { return NotificationListener( onNotification: (ScrollNotification notification) { if (notification is ScrollStartNotification) { @@ -794,7 +841,7 @@ class _CupertinoDatePickerDateTimeState extends State { && !(widget.maximumDate?.isBefore(rangeStart) ?? false); } - Widget _buildHourPicker(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget selectionOverlay) { + Widget _buildHourPicker(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget? selectionOverlay) { return NotificationListener( onNotification: (ScrollNotification notification) { if (notification is ScrollStartNotification) { @@ -859,7 +906,7 @@ class _CupertinoDatePickerDateTimeState extends State { ); } - Widget _buildMinutePicker(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget selectionOverlay) { + Widget _buildMinutePicker(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget? selectionOverlay) { return NotificationListener( onNotification: (ScrollNotification notification) { if (notification is ScrollStartNotification) { @@ -909,7 +956,7 @@ class _CupertinoDatePickerDateTimeState extends State { ); } - Widget _buildAmPmPicker(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget selectionOverlay) { + Widget _buildAmPmPicker(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget? selectionOverlay) { return NotificationListener( onNotification: (ScrollNotification notification) { if (notification is ScrollStartNotification) { @@ -1053,10 +1100,21 @@ class _CupertinoDatePickerDateTimeState extends State { for (final (int i, double width) in columnWidths.indexed) { final (bool firstColumn, bool lastColumn) = (i == 0, i == columnWidths.length - 1); double offAxisFraction = 0.0; - Widget selectionOverlay = _centerSelectionOverlay; + Widget? selectionOverlay = _centerSelectionOverlay; + + if (widget.selectionOverlayBuilder != null) { + selectionOverlay = + widget.selectionOverlayBuilder!(context, selectedIndex: i, columnCount: columnWidths.length); + } else { + if (firstColumn) { + selectionOverlay = _startSelectionOverlay; + } else if (lastColumn) { + selectionOverlay = _endSelectionOverlay; + } + } + if (firstColumn) { offAxisFraction = -_kMaximumOffAxisFraction * textDirectionFactor; - selectionOverlay = _startSelectionOverlay; } else if (i >= 2 || columnWidths.length == 2) { offAxisFraction = _kMaximumOffAxisFraction * textDirectionFactor; } @@ -1064,7 +1122,6 @@ class _CupertinoDatePickerDateTimeState extends State { EdgeInsets padding = const EdgeInsets.only(right: _kDatePickerPadSize); if (lastColumn) { padding = padding.flipped; - selectionOverlay = _endSelectionOverlay; } if (textDirectionFactor == -1) { padding = padding.flipped; @@ -1204,7 +1261,7 @@ class _CupertinoDatePickerDateState extends State { // Let `DateTime` handle the year/month overflow. DateTime _lastDayInMonth(int year, int month) => DateTime(year, month + 1, 0); - Widget _buildDayPicker(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget selectionOverlay) { + Widget _buildDayPicker(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget? selectionOverlay) { final int daysInCurrentMonth = _lastDayInMonth(selectedYear, selectedMonth).day; return NotificationListener( onNotification: (ScrollNotification notification) { @@ -1255,7 +1312,7 @@ class _CupertinoDatePickerDateState extends State { ); } - Widget _buildMonthPicker(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget selectionOverlay) { + Widget _buildMonthPicker(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget? selectionOverlay) { return NotificationListener( onNotification: (ScrollNotification notification) { if (notification is ScrollStartNotification) { @@ -1301,7 +1358,7 @@ class _CupertinoDatePickerDateState extends State { ); } - Widget _buildYearPicker(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget selectionOverlay) { + Widget _buildYearPicker(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget? selectionOverlay) { return NotificationListener( onNotification: (ScrollNotification notification) { if (notification is ScrollStartNotification) { @@ -1462,13 +1519,20 @@ class _CupertinoDatePickerDateState extends State { padding = const EdgeInsets.only(left: _kDatePickerPadSize); } - Widget selectionOverlay = _centerSelectionOverlay; - if (firstColumn) { - selectionOverlay = _startSelectionOverlay; - } else if (i == columnWidths.length - 1) { - selectionOverlay = _endSelectionOverlay; + Widget? selectionOverlay = _centerSelectionOverlay; + + if (widget.selectionOverlayBuilder != null) { + selectionOverlay = + widget.selectionOverlayBuilder!(context, selectedIndex: i, columnCount: columnWidths.length); + } else { + if (firstColumn) { + selectionOverlay = _startSelectionOverlay; + } else if (lastColumn) { + selectionOverlay = _endSelectionOverlay; + } } + totalColumnWidths += width + (2 * _kDatePickerPadSize); pickers.add(LayoutId( @@ -1593,7 +1657,7 @@ class _CupertinoDatePickerMonthYearState extends State { estimatedColumnWidths[_PickerColumnType.year.index] = CupertinoDatePicker._getColumnWidth(_PickerColumnType.year, localizations, context, false); } - Widget _buildMonthPicker(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget selectionOverlay) { + Widget _buildMonthPicker(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget? selectionOverlay) { return NotificationListener( onNotification: (ScrollNotification notification) { if (notification is ScrollStartNotification) { @@ -1639,7 +1703,7 @@ class _CupertinoDatePickerMonthYearState extends State { ); } - Widget _buildYearPicker(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget selectionOverlay) { + Widget _buildYearPicker(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget? selectionOverlay) { return NotificationListener( onNotification: (ScrollNotification notification) { if (notification is ScrollStartNotification) { @@ -1772,6 +1836,19 @@ class _CupertinoDatePickerMonthYearState extends State { totalColumnWidths += width + (2 * _kDatePickerPadSize); + Widget? selectionOverlay = _centerSelectionOverlay; + + if (widget.selectionOverlayBuilder != null) { + selectionOverlay = + widget.selectionOverlayBuilder!(context, selectedIndex: i, columnCount: columnWidths.length); + } else { + if (firstColumn) { + selectionOverlay = _startSelectionOverlay; + } else if (lastColumn) { + selectionOverlay = _endSelectionOverlay; + } + } + pickers.add(LayoutId( id: i, child: pickerBuilders[i]( @@ -1797,11 +1874,7 @@ class _CupertinoDatePickerMonthYearState extends State { child: contents, ); }, - switch (lastColumn) { - _ when firstColumn => _startSelectionOverlay, - false => _centerSelectionOverlay, - true => _endSelectionOverlay, - }, + selectionOverlay, ), )); } @@ -1912,6 +1985,7 @@ class CupertinoTimerPicker extends StatefulWidget { this.backgroundColor, this.itemExtent = _kItemExtent, required this.onTimerDurationChanged, + this.selectionOverlayBuilder, }) : assert(initialTimerDuration >= Duration.zero), assert(initialTimerDuration < const Duration(days: 1)), assert(minuteInterval > 0 && 60 % minuteInterval == 0), @@ -1955,6 +2029,46 @@ class CupertinoTimerPicker extends StatefulWidget { /// Defaults to a value that matches the default iOS timer picker wheel. final double itemExtent; + /// A function that returns a widget that is overlaid on the picker + /// to highlight the currently selected entry. + /// + /// If unspecified, it defaults to a [CupertinoPickerDefaultSelectionOverlay] + /// which is a gray rounded rectangle overlay in iOS 14 style. + /// + /// If the selection overlay builder returns null, no overlay will be drawn. + /// + /// {@tool snippet} + /// + /// This example shows how to recreate the default selection overlay + /// with selectionOverlayBuilder. + /// + /// ```dart + /// CupertinoTimerPicker( + /// onTimerDurationChanged: (Duration newDateTime) {}, + /// selectionOverlayBuilder: ( + /// BuildContext context, { + /// required int selectedIndex, + /// required int columnCount, + /// }) { + /// if (selectedIndex == 0) { + /// return const CupertinoPickerDefaultSelectionOverlay( + /// capEndEdge: false, + /// ); + /// } else if (selectedIndex == columnCount - 1) { + /// return const CupertinoPickerDefaultSelectionOverlay( + /// capStartEdge: false, + /// ); + /// } + /// return const CupertinoPickerDefaultSelectionOverlay( + /// capStartEdge: false, + /// capEndEdge: false, + /// ); + /// }, + /// ) + /// ``` + /// {@end-tool} + final SelectionOverlayBuilder? selectionOverlayBuilder; + @override State createState() => _CupertinoTimerPickerState(); } @@ -2182,7 +2296,7 @@ class _CupertinoTimerPickerState extends State { ); } - Widget _buildHourPicker(EdgeInsetsDirectional additionalPadding, Widget selectionOverlay) { + Widget _buildHourPicker(EdgeInsetsDirectional additionalPadding, Widget? selectionOverlay) { _hourScrollController ??= FixedExtentScrollController( initialItem: selectedHour! ); @@ -2221,7 +2335,7 @@ class _CupertinoTimerPickerState extends State { ); } - Widget _buildHourColumn(EdgeInsetsDirectional additionalPadding, Widget selectionOverlay) { + Widget _buildHourColumn(EdgeInsetsDirectional additionalPadding, Widget? selectionOverlay) { additionalPadding = EdgeInsetsDirectional.only( start: math.max(additionalPadding.start, 0), end: math.max(additionalPadding.end, 0), @@ -2244,7 +2358,7 @@ class _CupertinoTimerPickerState extends State { ); } - Widget _buildMinutePicker(EdgeInsetsDirectional additionalPadding, Widget selectionOverlay) { + Widget _buildMinutePicker(EdgeInsetsDirectional additionalPadding, Widget? selectionOverlay) { _minuteScrollController ??= FixedExtentScrollController( initialItem: selectedMinute ~/ widget.minuteInterval, ); @@ -2288,7 +2402,7 @@ class _CupertinoTimerPickerState extends State { ); } - Widget _buildMinuteColumn(EdgeInsetsDirectional additionalPadding, Widget selectionOverlay) { + Widget _buildMinuteColumn(EdgeInsetsDirectional additionalPadding, Widget? selectionOverlay) { additionalPadding = EdgeInsetsDirectional.only( start: math.max(additionalPadding.start, 0), end: math.max(additionalPadding.end, 0), @@ -2311,7 +2425,7 @@ class _CupertinoTimerPickerState extends State { ); } - Widget _buildSecondPicker(EdgeInsetsDirectional additionalPadding, Widget selectionOverlay) { + Widget _buildSecondPicker(EdgeInsetsDirectional additionalPadding, Widget? selectionOverlay) { _secondScrollController ??= FixedExtentScrollController( initialItem: selectedSecond! ~/ widget.secondInterval, ); @@ -2355,7 +2469,7 @@ class _CupertinoTimerPickerState extends State { ); } - Widget _buildSecondColumn(EdgeInsetsDirectional additionalPadding, Widget selectionOverlay) { + Widget _buildSecondColumn(EdgeInsetsDirectional additionalPadding, Widget? selectionOverlay) { additionalPadding = EdgeInsetsDirectional.only( start: math.max(additionalPadding.start, 0), end: math.max(additionalPadding.end, 0), @@ -2447,20 +2561,30 @@ class _CupertinoTimerPickerState extends State { minuteColumnEndPadding = _kTimerPickerMinHorizontalPadding; } + Widget? hourSelectionOverlay = _startSelectionOverlay; + Widget? minuteSelectionOverlay = _endSelectionOverlay; + + if (widget.selectionOverlayBuilder != null) { + hourSelectionOverlay = + widget.selectionOverlayBuilder!(context, selectedIndex: 0, columnCount: 2); + minuteSelectionOverlay = + widget.selectionOverlayBuilder!(context, selectedIndex: 1, columnCount: 2); + } + columns = [ _buildHourColumn( EdgeInsetsDirectional.only( start: hourColumnStartPadding, end: pickerColumnWidth - hourColumnStartPadding - hourLabelContentWidth, ), - _startSelectionOverlay, + hourSelectionOverlay, ), _buildMinuteColumn( EdgeInsetsDirectional.only( start: pickerColumnWidth - minuteColumnEndPadding - minuteLabelContentWidth, end: minuteColumnEndPadding, ), - _endSelectionOverlay, + minuteSelectionOverlay, ), ]; case CupertinoTimerPickerMode.ms: @@ -2477,20 +2601,30 @@ class _CupertinoTimerPickerState extends State { minuteColumnStartPadding = _kTimerPickerMinHorizontalPadding; } + Widget? minuteSelectionOverlay = _startSelectionOverlay; + Widget? secondSelectionOverlay = _endSelectionOverlay; + + if (widget.selectionOverlayBuilder != null) { + minuteSelectionOverlay = + widget.selectionOverlayBuilder!(context, selectedIndex: 0, columnCount: 2); + secondSelectionOverlay = + widget.selectionOverlayBuilder!(context, selectedIndex: 1, columnCount: 2); + } + columns = [ _buildMinuteColumn( EdgeInsetsDirectional.only( start: minuteColumnStartPadding, end: pickerColumnWidth - minuteColumnStartPadding - minuteLabelContentWidth, ), - _startSelectionOverlay, + minuteSelectionOverlay, ), _buildSecondColumn( EdgeInsetsDirectional.only( start: pickerColumnWidth - secondColumnEndPadding - minuteLabelContentWidth, end: secondColumnEndPadding, ), - _endSelectionOverlay, + secondSelectionOverlay, ), ]; case CupertinoTimerPickerMode.hms: @@ -2501,27 +2635,41 @@ class _CupertinoTimerPickerState extends State { final double secondColumnStartPadding = pickerColumnWidth - baseLabelContentWidth - secondLabelWidth - _kTimerPickerMinHorizontalPadding; + + Widget? hourSelectionOverlay = _startSelectionOverlay; + Widget? minuteSelectionOverlay = _centerSelectionOverlay; + Widget? secondSelectionOverlay = _endSelectionOverlay; + + if (widget.selectionOverlayBuilder != null) { + hourSelectionOverlay = + widget.selectionOverlayBuilder!(context, selectedIndex: 0, columnCount: 3); + minuteSelectionOverlay = + widget.selectionOverlayBuilder!(context, selectedIndex: 1, columnCount: 3); + secondSelectionOverlay = + widget.selectionOverlayBuilder!(context, selectedIndex: 2, columnCount: 3); + } + columns = [ _buildHourColumn( EdgeInsetsDirectional.only( start: _kTimerPickerMinHorizontalPadding, end: math.max(hourColumnEndPadding, 0), ), - _startSelectionOverlay, + hourSelectionOverlay, ), _buildMinuteColumn( EdgeInsetsDirectional.only( start: minuteColumnPadding, end: minuteColumnPadding, ), - _centerSelectionOverlay, + minuteSelectionOverlay, ), _buildSecondColumn( EdgeInsetsDirectional.only( start: math.max(secondColumnStartPadding, 0), end: _kTimerPickerMinHorizontalPadding, ), - _endSelectionOverlay, + secondSelectionOverlay, ), ]; } diff --git a/packages/flutter/test/cupertino/date_picker_test.dart b/packages/flutter/test/cupertino/date_picker_test.dart index 1d725f23ace..19ef615703f 100644 --- a/packages/flutter/test/cupertino/date_picker_test.dart +++ b/packages/flutter/test/cupertino/date_picker_test.dart @@ -2114,6 +2114,306 @@ void main() { expect(find.textContaining('15').last, findsOneWidget); expect(find.text('2018'), findsOneWidget); }); + + testWidgets('CupertinoDatePicker selectionOverlayBuilder with monthYear mode', + (WidgetTester tester) async { + + final Widget selectionOverlay = Container( + color: const Color(0x12345678), + ); + + // For mode = CupertinoDatePickerMode.monthYear + await tester.pumpWidget( + CupertinoApp( + home: Center( + child: CupertinoDatePicker( + mode: CupertinoDatePickerMode.monthYear, + onDateTimeChanged: (DateTime date) {}, + initialDateTime: DateTime(2018, 9, 15), + selectionOverlayBuilder: ( + BuildContext context, { + required int selectedIndex, + required int columnCount, + }) { + return selectionOverlay; + }, + ), + ), + ), + ); + + // Find the selection overlay. + expect(find.byWidget(selectionOverlay), findsExactly(2)); + }); + + testWidgets('CupertinoDatePicker selectionOverlayBuilder with date mode', + (WidgetTester tester) async { + + final Widget selectionOverlay = Container( + color: const Color(0x12345678), + ); + + // For mode = CupertinoDatePickerMode.date + await tester.pumpWidget( + CupertinoApp( + home: Center( + child: CupertinoDatePicker( + mode: CupertinoDatePickerMode.date, + onDateTimeChanged: (DateTime date) {}, + initialDateTime: DateTime(2018, 9, 15), + selectionOverlayBuilder: ( + BuildContext context, { + required int selectedIndex, + required int columnCount, + }) { + return selectionOverlay; + }, + ), + ), + ), + ); + + // Find the selection overlay. + expect(find.byWidget(selectionOverlay), findsExactly(3)); + }); + + + testWidgets('CupertinoDatePicker selectionOverlayBuilder with time mode', + (WidgetTester tester) async { + + final Widget selectionOverlay = Container( + color: const Color(0x12345678), + ); + + // For mode = CupertinoDatePickerMode.time + await tester.pumpWidget( + CupertinoApp( + home: Center( + child: CupertinoDatePicker( + mode: CupertinoDatePickerMode.time, + onDateTimeChanged: (DateTime date) {}, + initialDateTime: DateTime(2018, 9, 15), + selectionOverlayBuilder: ( + BuildContext context, { + required int selectedIndex, + required int columnCount, + }) { + return selectionOverlay; + }, + ), + ), + ), + ); + + // Find the selection overlay. + expect(find.byWidget(selectionOverlay), findsExactly(3)); + }); + + testWidgets('CupertinoDatePicker selectionOverlayBuilder with dateAndTime mode', + (WidgetTester tester) async { + + final Widget selectionOverlay = Container( + color: const Color(0x12345678), + ); + + // For mode = CupertinoDatePickerMode.dateAndTime + await tester.pumpWidget( + CupertinoApp( + home: Center( + child: CupertinoDatePicker( + onDateTimeChanged: (DateTime date) {}, + initialDateTime: DateTime(2018, 9, 15), + selectionOverlayBuilder: ( + BuildContext context, { + required int selectedIndex, + required int columnCount, + }) { + return selectionOverlay; + }, + ), + ), + ), + ); + + // Find the selection overlay. + expect(find.byWidget(selectionOverlay), findsExactly(4)); + }); + + testWidgets('CupertinoTimerPicker selectionOverlayBuilder with hms mode', + (WidgetTester tester) async { + + final Widget selectionOverlay = Container( + color: const Color(0x12345678), + ); + + // For mode = CupertinoTimerPickerMode.hms + await tester.pumpWidget( + CupertinoApp( + home: Center( + child: CupertinoTimerPicker( + onTimerDurationChanged: (Duration duration) {}, + initialTimerDuration: const Duration(hours: 1, minutes: 1, seconds: 1), + selectionOverlayBuilder: ( + BuildContext context, { + required int selectedIndex, + required int columnCount, + }) { + return selectionOverlay; + }, + ), + ), + ), + ); + + // Find the selection overlay. + expect(find.byWidget(selectionOverlay), findsExactly(3)); + }); + + testWidgets('CupertinoTimerPicker selectionOverlayBuilder with ms mode', + (WidgetTester tester) async { + + final Widget selectionOverlay = Container( + color: const Color(0x12345678), + ); + + // For mode = CupertinoTimerPickerMode.ms + await tester.pumpWidget( + CupertinoApp( + home: Center( + child: CupertinoTimerPicker( + onTimerDurationChanged: (Duration duration) {}, + mode: CupertinoTimerPickerMode.ms, + initialTimerDuration: const Duration(hours: 1, minutes: 1, seconds: 1), + selectionOverlayBuilder: ( + BuildContext context, { + required int selectedIndex, + required int columnCount, + }) { + return selectionOverlay; + }, + ), + ), + ), + ); + + // Find the selection overlay. + expect(find.byWidget(selectionOverlay), findsExactly(2)); + }); + + testWidgets('CupertinoTimerPicker selectionOverlayBuilder with hm mode', + (WidgetTester tester) async { + + final Widget selectionOverlay = Container( + color: const Color(0x12345678), + ); + + // For mode = CupertinoTimerPickerMode.hm + await tester.pumpWidget( + CupertinoApp( + home: Center( + child: CupertinoTimerPicker( + onTimerDurationChanged: (Duration duration) {}, + mode: CupertinoTimerPickerMode.hm, + initialTimerDuration: const Duration(hours: 1, minutes: 1, seconds: 1), + selectionOverlayBuilder: ( + BuildContext context, { + required int selectedIndex, + required int columnCount, + }) { + return selectionOverlay; + }, + ), + ), + ), + ); + + // Find the selection overlay. + expect(find.byWidget(selectionOverlay), findsExactly(2)); + }); + + testWidgets('CupertinoDatePicker selectionOverlayBuilder returns null', + (WidgetTester tester) async { + + await tester.pumpWidget( + CupertinoApp( + home: Center( + child: CupertinoDatePicker( + onDateTimeChanged: (DateTime date) {}, + initialDateTime: DateTime(2018, 9, 15), + selectionOverlayBuilder: ( + BuildContext context, { + required int selectedIndex, + required int columnCount, + }) { + return null; + }, + ), + ), + ), + ); + + expect(find.byType(CupertinoPicker), isNot(paints..rrect())); + }); + + testWidgets('CupertinoTimerPicker selectionOverlayBuilder returns null', + (WidgetTester tester) async { + + await tester.pumpWidget( + CupertinoApp( + home: Center( + child: CupertinoTimerPicker( + onTimerDurationChanged: (Duration duration) {}, + mode: CupertinoTimerPickerMode.hm, + initialTimerDuration: const Duration(hours: 1, minutes: 1, seconds: 1), + selectionOverlayBuilder: ( + BuildContext context, { + required int selectedIndex, + required int columnCount, + }) { + return null; + }, + ), + ), + ), + ); + + expect(find.byType(CupertinoPicker), isNot(paints..rrect())); + }); + + testWidgets('CupertinoTimerPicker selectionOverlayBuilder is explicitly passed null', + (WidgetTester tester) async { + + await tester.pumpWidget( + CupertinoApp( + home: Center( + child: CupertinoTimerPicker( + onTimerDurationChanged: (Duration duration) {}, + mode: CupertinoTimerPickerMode.hm, + initialTimerDuration: const Duration(hours: 1, minutes: 1, seconds: 1), + ), + ), + ), + ); + + expect(find.byType(CupertinoPickerDefaultSelectionOverlay), findsExactly(2)); + }); + + testWidgets('CupertinoDatePicker selectionOverlayBuilder is explicitly passed null', + (WidgetTester tester) async { + + await tester.pumpWidget( + CupertinoApp( + home: Center( + child: CupertinoDatePicker( + onDateTimeChanged: (DateTime date) {}, + initialDateTime: DateTime(2018, 9, 15), + ), + ), + ), + ); + + expect(find.byType(CupertinoPickerDefaultSelectionOverlay), findsExactly(4)); + }); } Widget _buildPicker({