diff --git a/packages/flutter/lib/src/material/date_picker.dart b/packages/flutter/lib/src/material/date_picker.dart index 5d12466c2c..d37e86d8a7 100644 --- a/packages/flutter/lib/src/material/date_picker.dart +++ b/packages/flutter/lib/src/material/date_picker.dart @@ -25,6 +25,9 @@ import 'material_localizations.dart'; import 'text_theme.dart'; import 'theme.dart'; +// Examples can assume: +// BuildContext context; + /// Initial display mode of the date picker dialog. /// /// Date picker UI mode for either showing a list of available years or a @@ -1111,6 +1114,28 @@ typedef SelectableDayPredicate = bool Function(DateTime day); /// The [context] argument is passed to [showDialog], the documentation for /// which discusses how it is used. /// +/// The [builder] parameter can be used to wrap the dialog widget +/// to add inherited widgets like [Theme]. +/// +/// {@tool sample} +/// Show a date picker with the dark theme. +/// +/// ```dart +/// Future selectedDate = showDatePicker( +/// context: context, +/// initialDate: DateTime.now(), +/// firstDate: DateTime(2018), +/// lastDate: DateTime(2030), +/// builder: (BuildContext context, Widget child) { +/// return Theme( +/// data: ThemeData.dark(), +/// child: child, +/// ); +/// }, +/// ); +/// ``` +/// {@end-tool} +/// /// The [context], [initialDate], [firstDate], and [lastDate] parameters must /// not be null. /// @@ -1133,6 +1158,7 @@ Future showDatePicker({ DatePickerMode initialDatePickerMode = DatePickerMode.day, Locale locale, TextDirection textDirection, + TransitionBuilder builder, }) async { assert(initialDate != null); assert(firstDate != null); @@ -1173,6 +1199,8 @@ Future showDatePicker({ return await showDialog( context: context, - builder: (BuildContext context) => child, + builder: (BuildContext context) { + return builder == null ? child : builder(context, child); + }, ); } diff --git a/packages/flutter/lib/src/material/time_picker.dart b/packages/flutter/lib/src/material/time_picker.dart index 526dd54ea8..39d7c13ba0 100644 --- a/packages/flutter/lib/src/material/time_picker.dart +++ b/packages/flutter/lib/src/material/time_picker.dart @@ -22,6 +22,9 @@ import 'theme.dart'; import 'theme_data.dart'; import 'time.dart'; +// Examples can assume: +// BuildContext context; + const Duration _kDialAnimateDuration = Duration(milliseconds: 200); const double _kTwoPi = 2 * math.pi; const Duration _kVibrateCommitDelay = Duration(milliseconds: 100); @@ -1651,33 +1654,77 @@ class _TimePickerDialogState extends State<_TimePickerDialog> { /// The returned Future resolves to the time selected by the user when the user /// closes the dialog. If the user cancels the dialog, null is returned. /// -/// To show a dialog with [initialTime] equal to the current time: +/// {@tool sample} +/// Show a dialog with [initialTime] equal to the current time. /// /// ```dart -/// showTimePicker( +/// Future selectedTime = showTimePicker( /// initialTime: TimeOfDay.now(), /// context: context, /// ); /// ``` +/// {@end-tool} /// -/// The `context` argument is passed to [showDialog], the documentation for +/// The [context] argument is passed to [showDialog], the documentation for /// which discusses how it is used. /// +/// The [builder] parameter can be used to wrap the dialog widget +/// to add inherited widgets like [Localizations.override], +/// [Directionality], or [MediaQuery]. +/// +/// {@tool sample} +/// Show a dialog with the text direction overridden to be [TextDirection.rtl]. +/// +/// ```dart +/// Future selectedTimeRTL = showTimePicker( +/// context: context, +/// initialTime: TimeOfDay.now(), +/// builder: (BuildContext context, Widget child) { +/// return Directionality( +/// textDirection: TextDirection.rtl, +/// child: child, +/// ); +/// }, +/// ); +/// ``` +/// {@end-tool} +/// +/// {@tool sample} +/// Show a dialog with time unconditionally displayed in 24 hour format. +/// +/// ```dart +/// Future selectedTime24Hour = showTimePicker( +/// context: context, +/// initialTime: TimeOfDay(hour: 10, minute: 47), +/// builder: (BuildContext context, Widget child) { +/// return MediaQuery( +/// data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: true), +/// child: child, +/// ); +/// }, +/// ); +/// ``` +/// {@end-tool} +/// /// See also: /// /// * [showDatePicker], which shows a dialog that contains a material design /// date picker. Future showTimePicker({ @required BuildContext context, - @required TimeOfDay initialTime + @required TimeOfDay initialTime, + TransitionBuilder builder, }) async { assert(context != null); assert(initialTime != null); assert(debugCheckHasMaterialLocalizations(context)); + final Widget dialog = _TimePickerDialog(initialTime: initialTime); return await showDialog( context: context, - builder: (BuildContext context) => _TimePickerDialog(initialTime: initialTime), + builder: (BuildContext context) { + return builder == null ? dialog : builder(context, dialog); + }, ); } diff --git a/packages/flutter/test/material/date_picker_test.dart b/packages/flutter/test/material/date_picker_test.dart index 0198f6f091..bd8d1c0387 100644 --- a/packages/flutter/test/material/date_picker_test.dart +++ b/packages/flutter/test/material/date_picker_test.dart @@ -723,4 +723,54 @@ void _tests() { expect(renderer.opacity.status, equals(AnimationStatus.dismissed)); } }); + + testWidgets('builder parameter', (WidgetTester tester) async { + Widget buildFrame(TextDirection textDirection) { + return MaterialApp( + home: Material( + child: Center( + child: Builder( + builder: (BuildContext context) { + return RaisedButton( + child: const Text('X'), + onPressed: () { + showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime(2018), + lastDate: DateTime(2030), + builder: (BuildContext context, Widget child) { + return Directionality( + textDirection: textDirection, + child: child, + ); + }, + ); + }, + ); + }, + ), + ), + ), + ); + } + + await tester.pumpWidget(buildFrame(TextDirection.ltr)); + await tester.tap(find.text('X')); + await tester.pumpAndSettle(); + final double ltrOkRight = tester.getBottomRight(find.text('OK')).dx; + + await tester.tap(find.text('OK')); // dismiss the dialog + await tester.pumpAndSettle(); + + await tester.pumpWidget(buildFrame(TextDirection.rtl)); + await tester.tap(find.text('X')); + await tester.pumpAndSettle(); + + // Verify that the time picker is being laid out RTL. + // We expect the left edge of the 'OK' button in the RTL + // layout to match the gap between right edge of the 'OK' + // button and the right edge of the 800 wide window. + expect(tester.getBottomLeft(find.text('OK')).dx, 800 - ltrOkRight); + }); } diff --git a/packages/flutter/test/material/time_picker_test.dart b/packages/flutter/test/material/time_picker_test.dart index 2167159940..520884e2d5 100644 --- a/packages/flutter/test/material/time_picker_test.dart +++ b/packages/flutter/test/material/time_picker_test.dart @@ -505,6 +505,54 @@ void _tests() { semantics.dispose(); }); + + testWidgets('builder parameter', (WidgetTester tester) async { + Widget buildFrame(TextDirection textDirection) { + return MaterialApp( + home: Material( + child: Center( + child: Builder( + builder: (BuildContext context) { + return RaisedButton( + child: const Text('X'), + onPressed: () { + showTimePicker( + context: context, + initialTime: const TimeOfDay(hour: 7, minute: 0), + builder: (BuildContext context, Widget child) { + return Directionality( + textDirection: textDirection, + child: child, + ); + }, + ); + }, + ); + }, + ), + ), + ), + ); + } + + await tester.pumpWidget(buildFrame(TextDirection.ltr)); + await tester.tap(find.text('X')); + await tester.pumpAndSettle(); + final double ltrOkRight = tester.getBottomRight(find.text('OK')).dx; + + await tester.tap(find.text('OK')); // dismiss the dialog + await tester.pumpAndSettle(); + + await tester.pumpWidget(buildFrame(TextDirection.rtl)); + await tester.tap(find.text('X')); + await tester.pumpAndSettle(); + + // Verify that the time picker is being laid out RTL. + // We expect the left edge of the 'OK' button in the RTL + // layout to match the gap between right edge of the 'OK' + // button and the right edge of the 800 wide window. + expect(tester.getBottomLeft(find.text('OK')).dx, 800 - ltrOkRight); + }); } final Finder findDialPaint = find.descendant(