Unverified Commit df7f05f7 authored by Andrei Diaconu's avatar Andrei Diaconu Committed by GitHub

Add Foldable support for modal routes (#92909)

parent ffe64c63
......@@ -999,8 +999,12 @@ class _CupertinoEdgeShadowPainter extends BoxPainter {
/// The `routeSettings` argument is used to provide [RouteSettings] to the
/// created Route.
///
/// {@macro flutter.widgets.RawDialogRoute}
///
/// See also:
///
/// * [DisplayFeatureSubScreen], which documents the specifics of how
/// [DisplayFeature]s can split the screen into sub-screens.
/// * [CupertinoActionSheet], which is the widget usually returned by the
/// `builder` argument.
/// * <https://developer.apple.com/design/human-interface-guidelines/ios/views/action-sheets/>
......@@ -1015,6 +1019,7 @@ class CupertinoModalPopupRoute<T> extends PopupRoute<T> {
bool? semanticsDismissible,
ImageFilter? filter,
RouteSettings? settings,
this.anchorPoint,
}) : super(
filter: filter,
settings: settings,
......@@ -1056,6 +1061,9 @@ class CupertinoModalPopupRoute<T> extends PopupRoute<T> {
late Tween<Offset> _offsetTween;
/// {@macro flutter.widgets.DisplayFeatureSubScreen.anchorPoint}
final Offset? anchorPoint;
@override
Animation<double> createAnimation() {
assert(_animation == null);
......@@ -1078,7 +1086,10 @@ class CupertinoModalPopupRoute<T> extends PopupRoute<T> {
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return CupertinoUserInterfaceLevel(
data: CupertinoUserInterfaceLevelData.elevated,
child: DisplayFeatureSubScreen(
anchorPoint: anchorPoint,
child: Builder(builder: builder),
),
);
}
......@@ -1127,6 +1138,8 @@ class CupertinoModalPopupRoute<T> extends PopupRoute<T> {
/// [StatefulBuilder] or a custom [StatefulWidget] if the widget needs to
/// update dynamically.
///
/// {@macro flutter.widgets.RawDialogRoute}
///
/// Returns a `Future` that resolves to the value that was passed to
/// [Navigator.pop] when the popup was closed.
///
......@@ -1151,6 +1164,8 @@ class CupertinoModalPopupRoute<T> extends PopupRoute<T> {
///
/// See also:
///
/// * [DisplayFeatureSubScreen], which documents the specifics of how
/// [DisplayFeature]s can split the screen into sub-screens.
/// * [CupertinoActionSheet], which is the widget usually returned by the
/// `builder` argument to [showCupertinoModalPopup].
/// * <https://developer.apple.com/design/human-interface-guidelines/ios/views/action-sheets/>
......@@ -1163,6 +1178,7 @@ Future<T?> showCupertinoModalPopup<T>({
bool useRootNavigator = true,
bool? semanticsDismissible,
RouteSettings? routeSettings,
Offset? anchorPoint,
}) {
assert(useRootNavigator != null);
return Navigator.of(context, rootNavigator: useRootNavigator).push(
......@@ -1173,6 +1189,7 @@ Future<T?> showCupertinoModalPopup<T>({
barrierDismissible: barrierDismissible,
semanticsDismissible: semanticsDismissible,
settings: routeSettings,
anchorPoint: anchorPoint,
),
);
}
......@@ -1223,6 +1240,8 @@ Widget _buildCupertinoDialogTransitions(BuildContext context, Animation<double>
/// By default, `useRootNavigator` is `true` and the dialog route created by
/// this method is pushed to the root navigator.
///
/// {@macro flutter.widgets.RawDialogRoute}
///
/// If the application has multiple [Navigator] objects, it may be necessary to
/// call `Navigator.of(context, rootNavigator: true).pop(result)` to close the
/// dialog rather than just `Navigator.pop(context, result)`.
......@@ -1254,6 +1273,8 @@ Widget _buildCupertinoDialogTransitions(BuildContext context, Animation<double>
/// * [CupertinoAlertDialog], an iOS-style alert dialog.
/// * [showDialog], which displays a Material-style dialog.
/// * [showGeneralDialog], which allows for customization of the dialog popup.
/// * [DisplayFeatureSubScreen], which documents the specifics of how
/// [DisplayFeature]s can split the screen into sub-screens.
/// * <https://developer.apple.com/ios/human-interface-guidelines/views/alerts/>
Future<T?> showCupertinoDialog<T>({
required BuildContext context,
......@@ -1262,6 +1283,7 @@ Future<T?> showCupertinoDialog<T>({
bool useRootNavigator = true,
bool barrierDismissible = false,
RouteSettings? routeSettings,
Offset? anchorPoint,
}) {
assert(builder != null);
assert(useRootNavigator != null);
......@@ -1273,6 +1295,7 @@ Future<T?> showCupertinoDialog<T>({
barrierLabel: barrierLabel,
barrierColor: CupertinoDynamicColor.resolve(kCupertinoModalBarrierColor, context),
settings: routeSettings,
anchorPoint: anchorPoint,
));
}
......@@ -1303,12 +1326,16 @@ Future<T?> showCupertinoDialog<T>({
/// The `settings` argument define the settings for this route. See
/// [RouteSettings] for details.
///
/// {@macro flutter.widgets.RawDialogRoute}
///
/// See also:
///
/// * [showCupertinoDialog], which is a way to display
/// an iOS-style dialog.
/// * [showGeneralDialog], which allows for customization of the dialog popup.
/// * [showDialog], which displays a Material dialog.
/// * [DisplayFeatureSubScreen], which documents the specifics of how
/// [DisplayFeature]s can split the screen into sub-screens.
class CupertinoDialogRoute<T> extends RawDialogRoute<T> {
/// A dialog route that shows an iOS-style dialog.
CupertinoDialogRoute({
......@@ -1321,6 +1348,7 @@ class CupertinoDialogRoute<T> extends RawDialogRoute<T> {
Duration transitionDuration = const Duration(milliseconds: 250),
RouteTransitionsBuilder? transitionBuilder = _buildCupertinoDialogTransitions,
RouteSettings? settings,
Offset? anchorPoint,
}) : assert(barrierDismissible != null),
super(
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
......@@ -1332,5 +1360,6 @@ class CupertinoDialogRoute<T> extends RawDialogRoute<T> {
transitionDuration: transitionDuration,
transitionBuilder: transitionBuilder,
settings: settings,
anchorPoint: anchorPoint,
);
}
......@@ -167,8 +167,9 @@ class AboutListTile extends StatelessWidget {
/// The licenses shown on the [LicensePage] are those returned by the
/// [LicenseRegistry] API, which can be used to add more licenses to the list.
///
/// The [context], [useRootNavigator] and [routeSettings] arguments are passed to
/// [showDialog], the documentation for which discusses how it is used.
/// The [context], [useRootNavigator], [routeSettings] and [anchorPoint]
/// arguments are passed to [showDialog], the documentation for which discusses
/// how it is used.
void showAboutDialog({
required BuildContext context,
String? applicationName,
......@@ -178,6 +179,7 @@ void showAboutDialog({
List<Widget>? children,
bool useRootNavigator = true,
RouteSettings? routeSettings,
Offset? anchorPoint,
}) {
assert(context != null);
assert(useRootNavigator != null);
......@@ -194,6 +196,7 @@ void showAboutDialog({
);
},
routeSettings: routeSettings,
anchorPoint: anchorPoint,
);
}
......
......@@ -466,6 +466,7 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
required this.isScrollControlled,
RouteSettings? settings,
this.transitionAnimationController,
this.anchorPoint,
}) : assert(isScrollControlled != null),
assert(isDismissible != null),
assert(enableDrag != null),
......@@ -483,6 +484,7 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
final bool isDismissible;
final bool enableDrag;
final AnimationController? transitionAnimationController;
final Offset? anchorPoint;
@override
Duration get transitionDuration => _bottomSheetEnterDuration;
......@@ -520,6 +522,8 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
final Widget bottomSheet = MediaQuery.removePadding(
context: context,
removeTop: true,
child: DisplayFeatureSubScreen(
anchorPoint: anchorPoint,
child: Builder(
builder: (BuildContext context) {
final BottomSheetThemeData sheetTheme = Theme.of(context).bottomSheetTheme;
......@@ -535,6 +539,7 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
);
},
),
),
);
return capturedThemes.wrap(bottomSheet);
}
......@@ -645,6 +650,8 @@ class _BottomSheetSuspendedCurve extends ParametricCurve<double> {
/// sheet. This is particularly useful in the case that a user wants to observe
/// [PopupRoute]s within a [NavigatorObserver].
///
/// {@macro flutter.widgets.RawDialogRoute}
///
/// Returns a `Future` that resolves to the value (if any) that was passed to
/// [Navigator.pop] when the modal bottom sheet was closed.
///
......@@ -665,6 +672,8 @@ class _BottomSheetSuspendedCurve extends ParametricCurve<double> {
/// non-modal bottom sheets.
/// * [DraggableScrollableSheet], which allows you to create a bottom sheet
/// that grows and then becomes scrollable once it reaches its maximum size.
/// * [DisplayFeatureSubScreen], which documents the specifics of how
/// [DisplayFeature]s can split the screen into sub-screens.
/// * <https://material.io/design/components/sheets-bottom.html#modal-bottom-sheet>
Future<T?> showModalBottomSheet<T>({
required BuildContext context,
......@@ -681,6 +690,7 @@ Future<T?> showModalBottomSheet<T>({
bool enableDrag = true,
RouteSettings? routeSettings,
AnimationController? transitionAnimationController,
Offset? anchorPoint,
}) {
assert(context != null);
assert(builder != null);
......@@ -707,6 +717,7 @@ Future<T?> showModalBottomSheet<T>({
enableDrag: enableDrag,
settings: routeSettings,
transitionAnimationController: transitionAnimationController,
anchorPoint: anchorPoint,
));
}
......
......@@ -102,6 +102,8 @@ const double _inputFormLandscapeHeight = 108.0;
/// [DatePickerMode.day] mode. It defaults to [DatePickerMode.day], and
/// must be non-null.
///
/// {@macro flutter.widgets.RawDialogRoute}
///
/// ### State Restoration
///
/// Using this method will not enable state restoration for the date picker.
......@@ -128,6 +130,8 @@ const double _inputFormLandscapeHeight = 108.0;
/// used to select a range of dates.
/// * [CalendarDatePicker], which provides the calendar grid used by the date picker dialog.
/// * [InputDatePickerFormField], which provides a text input field for entering dates.
/// * [DisplayFeatureSubScreen], which documents the specifics of how
/// [DisplayFeature]s can split the screen into sub-screens.
/// * [showTimePicker], which shows a dialog that contains a material design time picker.
///
Future<DateTime?> showDatePicker({
......@@ -152,6 +156,7 @@ Future<DateTime?> showDatePicker({
String? fieldHintText,
String? fieldLabelText,
TextInputType? keyboardType,
Offset? anchorPoint,
}) async {
assert(context != null);
assert(initialDate != null);
......@@ -221,6 +226,7 @@ Future<DateTime?> showDatePicker({
builder: (BuildContext context) {
return builder == null ? dialog : builder(context, dialog);
},
anchorPoint: anchorPoint,
);
}
......@@ -898,6 +904,8 @@ class _DatePickerHeader extends StatelessWidget {
/// The [builder] parameter can be used to wrap the dialog widget
/// to add inherited widgets like [Theme].
///
/// {@macro flutter.widgets.RawDialogRoute}
///
/// ### State Restoration
///
/// Using this method will not enable state restoration for the date range picker.
......@@ -923,7 +931,8 @@ class _DatePickerHeader extends StatelessWidget {
/// * [showDatePicker], which shows a material design date picker used to
/// select a single date.
/// * [DateTimeRange], which is used to describe a date range.
///
/// * [DisplayFeatureSubScreen], which documents the specifics of how
/// [DisplayFeature]s can split the screen into sub-screens.
Future<DateTimeRange?> showDateRangePicker({
required BuildContext context,
DateTimeRange? initialDateRange,
......@@ -947,6 +956,7 @@ Future<DateTimeRange?> showDateRangePicker({
RouteSettings? routeSettings,
TextDirection? textDirection,
TransitionBuilder? builder,
Offset? anchorPoint,
}) async {
assert(context != null);
assert(
......@@ -1029,6 +1039,7 @@ Future<DateTimeRange?> showDateRangePicker({
builder: (BuildContext context) {
return builder == null ? dialog : builder(context, dialog);
},
anchorPoint: anchorPoint,
);
}
......
......@@ -1007,6 +1007,8 @@ Widget _buildMaterialDialogTransitions(BuildContext context, Animation<double> a
/// The `routeSettings` argument is passed to [showGeneralDialog],
/// see [RouteSettings] for details.
///
/// {@macro flutter.widgets.RawDialogRoute}
///
/// If the application has multiple [Navigator] objects, it may be necessary to
/// call `Navigator.of(context, rootNavigator: true).pop(result)` to close the
/// dialog rather than just `Navigator.pop(context, result)`.
......@@ -1041,6 +1043,8 @@ Widget _buildMaterialDialogTransitions(BuildContext context, Animation<double> a
/// * [Dialog], on which [SimpleDialog] and [AlertDialog] are based.
/// * [showCupertinoDialog], which displays an iOS-style dialog.
/// * [showGeneralDialog], which allows for customization of the dialog popup.
/// * [DisplayFeatureSubScreen], which documents the specifics of how
/// [DisplayFeature]s can split the screen into sub-screens.
/// * <https://material.io/design/components/dialogs.html>
Future<T?> showDialog<T>({
required BuildContext context,
......@@ -1051,6 +1055,7 @@ Future<T?> showDialog<T>({
bool useSafeArea = true,
bool useRootNavigator = true,
RouteSettings? routeSettings,
Offset? anchorPoint,
}) {
assert(builder != null);
assert(barrierDismissible != null);
......@@ -1075,6 +1080,7 @@ Future<T?> showDialog<T>({
useSafeArea: useSafeArea,
settings: routeSettings,
themes: themes,
anchorPoint: anchorPoint,
));
}
......@@ -1113,11 +1119,15 @@ Future<T?> showDialog<T>({
/// The `settings` argument define the settings for this route. See
/// [RouteSettings] for details.
///
/// {@macro flutter.widgets.RawDialogRoute}
///
/// See also:
///
/// * [showDialog], which is a way to display a DialogRoute.
/// * [showGeneralDialog], which allows for customization of the dialog popup.
/// * [showCupertinoDialog], which displays an iOS-style dialog.
/// * [DisplayFeatureSubScreen], which documents the specifics of how
/// [DisplayFeature]s can split the screen into sub-screens.
class DialogRoute<T> extends RawDialogRoute<T> {
/// A dialog route with Material entrance and exit animations,
/// modal barrier color, and modal barrier behavior (dialog is dismissible
......@@ -1131,6 +1141,7 @@ class DialogRoute<T> extends RawDialogRoute<T> {
String? barrierLabel,
bool useSafeArea = true,
RouteSettings? settings,
Offset? anchorPoint,
}) : assert(barrierDismissible != null),
super(
pageBuilder: (BuildContext buildContext, Animation<double> animation, Animation<double> secondaryAnimation) {
......@@ -1147,6 +1158,7 @@ class DialogRoute<T> extends RawDialogRoute<T> {
transitionDuration: const Duration(milliseconds: 150),
transitionBuilder: _buildMaterialDialogTransitions,
settings: settings,
anchorPoint: anchorPoint,
);
}
......
......@@ -2365,6 +2365,8 @@ class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMix
/// [hourLabelText], [minuteLabelText] and [confirmText] can be provided to
/// override the default values.
///
/// {@macro flutter.widgets.RawDialogRoute}
///
/// By default, the time picker gets its colors from the overall theme's
/// [ColorScheme]. The time picker can be further customized by providing a
/// [TimePickerThemeData] to the overall theme.
......@@ -2409,6 +2411,8 @@ class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMix
/// date picker.
/// * [TimePickerThemeData], which allows you to customize the colors,
/// typography, and shape of the time picker.
/// * [DisplayFeatureSubScreen], which documents the specifics of how
/// [DisplayFeature]s can split the screen into sub-screens.
Future<TimeOfDay?> showTimePicker({
required BuildContext context,
required TimeOfDay initialTime,
......@@ -2423,6 +2427,7 @@ Future<TimeOfDay?> showTimePicker({
String? minuteLabelText,
RouteSettings? routeSettings,
EntryModeChangeCallback? onEntryModeChanged,
Offset? anchorPoint,
}) async {
assert(context != null);
assert(initialTime != null);
......@@ -2448,6 +2453,7 @@ Future<TimeOfDay?> showTimePicker({
return builder == null ? dialog : builder(context, dialog);
},
routeSettings: routeSettings,
anchorPoint: anchorPoint,
);
}
......
......@@ -27,14 +27,14 @@ import 'media_query.dart';
/// bottom sub-screen
///
/// After determining the sub-screens, the closest one to [anchorPoint] is used
/// to render the [child].
/// to render the content.
///
/// If no [anchorPoint] is provided, then [Directionality] is used:
///
/// * for [TextDirection.ltr], [anchorPoint] is `Offset.zero`, which will
/// cause the [child] to appear in the top-left sub-screen.
/// cause the content to appear in the top-left sub-screen.
/// * for [TextDirection.rtl], [anchorPoint] is `Offset(double.maxFinite, 0)`,
/// which will cause the [child] to appear in the top-right sub-screen.
/// which will cause the content to appear in the top-right sub-screen.
///
/// If no [anchorPoint] is provided, and there is no [Directionality] ancestor
/// widget in the tree, then the widget asserts during build in debug mode.
......@@ -58,6 +58,7 @@ class DisplayFeatureSubScreen extends StatelessWidget {
required this.child,
}) : super(key: key);
/// {@template flutter.widgets.DisplayFeatureSubScreen.anchorPoint}
/// The anchor point used to pick the closest sub-screen.
///
/// If the anchor point sits inside one of these sub-screens, then that
......@@ -75,6 +76,7 @@ class DisplayFeatureSubScreen extends StatelessWidget {
/// * for [TextDirection.rtl], [anchorPoint] is
/// `Offset(double.maxFinite, 0)`, which will cause the top-right
/// sub-screen to be picked.
/// {@endtemplate}
final Offset? anchorPoint;
/// The widget below this widget in the tree.
......
......@@ -13,6 +13,7 @@ import 'package:flutter/services.dart';
import 'actions.dart';
import 'basic.dart';
import 'display_feature_sub_screen.dart';
import 'focus_manager.dart';
import 'focus_scope.dart';
import 'framework.dart';
......@@ -1864,8 +1865,25 @@ abstract class RouteAware {
/// The `settings` argument define the settings for this route. See
/// [RouteSettings] for details.
///
/// {@template flutter.widgets.RawDialogRoute}
/// A [DisplayFeature] can split the screen into sub-screens. The closest one to
/// [anchorPoint] is used to render the content.
///
/// If no [anchorPoint] is provided, then [Directionality] is used:
///
/// * for [TextDirection.ltr], [anchorPoint] is `Offset.zero`, which will
/// cause the content to appear in the top-left sub-screen.
/// * for [TextDirection.rtl], [anchorPoint] is `Offset(double.maxFinite, 0)`,
/// which will cause the content to appear in the top-right sub-screen.
///
/// If no [anchorPoint] is provided, and there is no [Directionality] ancestor
/// widget in the tree, then the widget asserts during build in debug mode.
/// {@endtemplate}
///
/// See also:
///
/// * [DisplayFeatureSubScreen], which documents the specifics of how
/// [DisplayFeature]s can split the screen into sub-screens.
/// * [showGeneralDialog], which is a way to display a RawDialogRoute.
/// * [showDialog], which is a way to display a DialogRoute.
/// * [showCupertinoDialog], which displays an iOS-style dialog.
......@@ -1879,6 +1897,7 @@ class RawDialogRoute<T> extends PopupRoute<T> {
Duration transitionDuration = const Duration(milliseconds: 200),
RouteTransitionsBuilder? transitionBuilder,
RouteSettings? settings,
this.anchorPoint,
}) : assert(barrierDismissible != null),
_pageBuilder = pageBuilder,
_barrierDismissible = barrierDismissible,
......@@ -1908,12 +1927,18 @@ class RawDialogRoute<T> extends PopupRoute<T> {
final RouteTransitionsBuilder? _transitionBuilder;
/// {@macro flutter.widgets.DisplayFeatureSubScreen.anchorPoint}
final Offset? anchorPoint;
@override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return Semantics(
scopesRoute: true,
explicitChildNodes: true,
child: DisplayFeatureSubScreen(
anchorPoint: anchorPoint,
child: _pageBuilder(context, animation, secondaryAnimation),
),
);
}
......@@ -1979,6 +2004,8 @@ class RawDialogRoute<T> extends PopupRoute<T> {
/// The `routeSettings` will be used in the construction of the dialog's route.
/// See [RouteSettings] for more details.
///
/// {@macro flutter.widgets.RawDialogRoute}
///
/// Returns a [Future] that resolves to the value (if any) that was passed to
/// [Navigator.pop] when the dialog was closed.
///
......@@ -2003,6 +2030,8 @@ class RawDialogRoute<T> extends PopupRoute<T> {
///
/// See also:
///
/// * [DisplayFeatureSubScreen], which documents the specifics of how
/// [DisplayFeature]s can split the screen into sub-screens.
/// * [showDialog], which displays a Material-style dialog.
/// * [showCupertinoDialog], which displays an iOS-style dialog.
Future<T?> showGeneralDialog<T extends Object?>({
......@@ -2015,6 +2044,7 @@ Future<T?> showGeneralDialog<T extends Object?>({
RouteTransitionsBuilder? transitionBuilder,
bool useRootNavigator = true,
RouteSettings? routeSettings,
Offset? anchorPoint,
}) {
assert(pageBuilder != null);
assert(useRootNavigator != null);
......@@ -2027,6 +2057,7 @@ Future<T?> showGeneralDialog<T extends Object?>({
transitionDuration: transitionDuration,
transitionBuilder: transitionBuilder,
settings: routeSettings,
anchorPoint: anchorPoint,
));
}
......
......@@ -7,6 +7,7 @@
@Tags(<String>['reduced-test-set'])
import 'dart:math';
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
......@@ -1317,6 +1318,123 @@ void main() {
expect(scrollbars.length, 2);
expect(scrollbars[0].controller != scrollbars[1].controller, isTrue);
});
group('showCupertinoDialog avoids overlapping display features', () {
testWidgets('positioning using anchorPoint', (WidgetTester tester) async {
await tester.pumpWidget(
CupertinoApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: child!,
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
showCupertinoDialog<void>(
context: context,
builder: (BuildContext context) {
return const Placeholder();
},
anchorPoint: const Offset(1000, 0),
);
await tester.pumpAndSettle();
// Should take the right side of the screen
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(410.0, 0.0));
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(800.0, 600.0));
});
testWidgets('positioning using Directionality', (WidgetTester tester) async {
await tester.pumpWidget(
CupertinoApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: Directionality(
textDirection: TextDirection.rtl,
child: child!,
),
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
showCupertinoDialog<void>(
context: context,
builder: (BuildContext context) {
return const Placeholder();
},
);
await tester.pumpAndSettle();
// Should take the right side of the screen
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(410.0, 0.0));
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(800.0, 600.0));
});
testWidgets('default positioning', (WidgetTester tester) async {
await tester.pumpWidget(
CupertinoApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: child!,
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
showCupertinoDialog<void>(
context: context,
builder: (BuildContext context) {
return const Placeholder();
},
);
await tester.pumpAndSettle();
// By default it should place the dialog on the left screen
expect(tester.getTopLeft(find.byType(Placeholder)), Offset.zero);
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(390.0, 600.0));
});
});
}
RenderBox findActionButtonRenderBoxByTitle(WidgetTester tester, String title) {
......
......@@ -3,6 +3,8 @@
// found in the LICENSE file.
@TestOn('!chrome')
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
......@@ -1881,6 +1883,240 @@ void main() {
await tester.restoreFrom(restorationData);
expect(find.byType(CupertinoActionSheet), findsOneWidget);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615
group('showCupertinoDialog avoids overlapping display features', () {
testWidgets('positioning with anchorPoint', (WidgetTester tester) async {
await tester.pumpWidget(
CupertinoApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: child!,
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
showCupertinoDialog<void>(
context: context,
builder: (BuildContext context) {
return const Placeholder();
},
anchorPoint: const Offset(1000, 0),
);
await tester.pumpAndSettle();
// Should take the right side of the screen
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(410.0, 0.0));
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(800.0, 600.0));
});
testWidgets('positioning with Directionality', (WidgetTester tester) async {
await tester.pumpWidget(
CupertinoApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: Directionality(
textDirection: TextDirection.rtl,
child: child!,
),
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
showCupertinoDialog<void>(
context: context,
builder: (BuildContext context) {
return const Placeholder();
},
);
await tester.pumpAndSettle();
// Since this is RTL, it should place the dialog on the right screen
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(410.0, 0.0));
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(800.0, 600.0));
});
testWidgets('positioning by default', (WidgetTester tester) async {
await tester.pumpWidget(
CupertinoApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: child!,
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
showCupertinoDialog<void>(
context: context,
builder: (BuildContext context) {
return const Placeholder();
},
);
await tester.pumpAndSettle();
// By default it should place the dialog on the left screen
expect(tester.getTopLeft(find.byType(Placeholder)), Offset.zero);
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(390.0, 600.0));
});
});
group('showCupertinoModalPopup avoids overlapping display features', () {
testWidgets('positioning using anchorPoint', (WidgetTester tester) async {
await tester.pumpWidget(
CupertinoApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: child!,
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) {
return const Placeholder();
},
anchorPoint: const Offset(1000, 0),
);
await tester.pumpAndSettle();
// Should take the right side of the screen
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 410);
expect(tester.getBottomRight(find.byType(Placeholder)).dx, 800);
});
testWidgets('positioning using Directionality', (WidgetTester tester) async {
await tester.pumpWidget(
CupertinoApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: Directionality(
textDirection: TextDirection.rtl,
child: child!,
),
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) {
return const Placeholder();
},
);
await tester.pumpAndSettle();
// This is RTL, so it should place the dialog on the right screen
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 410);
expect(tester.getBottomRight(find.byType(Placeholder)).dx, 800);
});
testWidgets('default positioning', (WidgetTester tester) async {
await tester.pumpWidget(
CupertinoApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: child!,
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) {
return const Placeholder();
},
);
await tester.pumpAndSettle();
// By default it should place the dialog on the left screen
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
expect(tester.getBottomRight(find.byType(Placeholder)).dx, 390.0);
});
});
}
class MockNavigatorObserver extends NavigatorObserver {
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
......@@ -593,6 +594,135 @@ void main() {
expect(nestedObserver.dialogCount, 1);
});
group('showAboutDialog avoids overlapping display features', () {
testWidgets('default positioning', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: child!,
);
},
home: Builder(
builder: (BuildContext context) => ElevatedButton(
onPressed: () {
showAboutDialog(
context: context,
useRootNavigator: false,
applicationName: 'A',
);
},
child: const Text('Show About Dialog'),
),
),
));
// Open the dialog.
await tester.tap(find.byType(ElevatedButton));
await tester.pumpAndSettle();
// By default it should place the dialog on the left screen
expect(tester.getTopLeft(find.byType(AboutDialog)), Offset.zero);
expect(tester.getBottomRight(find.byType(AboutDialog)), const Offset(390.0, 600.0));
});
testWidgets('positioning using anchorPoint', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: child!,
);
},
home: Builder(
builder: (BuildContext context) => ElevatedButton(
onPressed: () {
showAboutDialog(
context: context,
useRootNavigator: false,
applicationName: 'A',
anchorPoint: const Offset(1000, 0),
);
},
child: const Text('Show About Dialog'),
),
),
));
// Open the dialog.
await tester.tap(find.byType(ElevatedButton));
await tester.pumpAndSettle();
// The anchorPoint hits the right side of the display
expect(tester.getTopLeft(find.byType(AboutDialog)), const Offset(410.0, 0.0));
expect(tester.getBottomRight(find.byType(AboutDialog)), const Offset(800.0, 600.0));
});
testWidgets('positioning using Directionality', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: Directionality(
textDirection: TextDirection.rtl,
child: child!,
),
);
},
home: Builder(
builder: (BuildContext context) => ElevatedButton(
onPressed: () {
showAboutDialog(
context: context,
useRootNavigator: false,
applicationName: 'A',
);
},
child: const Text('Show About Dialog'),
),
),
));
// Open the dialog.
await tester.tap(find.byType(ElevatedButton));
await tester.pumpAndSettle();
// Since this is rtl, the first screen is the on the right
expect(tester.getTopLeft(find.byType(AboutDialog)), const Offset(410.0, 0.0));
expect(tester.getBottomRight(find.byType(AboutDialog)), const Offset(800.0, 600.0));
});
});
testWidgets("AboutListTile's child should not be offset when the icon is not specified.", (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
......@@ -1256,6 +1258,123 @@ void main() {
expect(find.text('BottomSheet 2'), findsOneWidget);
});
group('Modal BottomSheet avoids overlapping display features', () {
testWidgets('positioning using anchorPoint', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: child!,
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return const Placeholder();
},
anchorPoint: const Offset(1000, 0),
);
await tester.pumpAndSettle();
// Should take the right side of the screen
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 410);
expect(tester.getBottomRight(find.byType(Placeholder)).dx, 800);
});
testWidgets('positioning using Directionality', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: Directionality(
textDirection: TextDirection.rtl,
child: child!,
),
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return const Placeholder();
},
);
await tester.pumpAndSettle();
// This is RTL, so it should place the dialog on the right screen
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 410);
expect(tester.getBottomRight(find.byType(Placeholder)).dx, 800);
});
testWidgets('default positioning', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: child!,
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return const Placeholder();
},
);
await tester.pumpAndSettle();
// By default it should place the dialog on the left screen
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
expect(tester.getBottomRight(find.byType(Placeholder)).dx, 390.0);
});
});
group('constraints', () {
testWidgets('No constraints by default for bottomSheet property', (WidgetTester tester) async {
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
......@@ -1136,6 +1138,123 @@ void main() {
});
});
group('showDatePicker avoids overlapping display features', () {
testWidgets('positioning with anchorPoint', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: child!,
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2018),
lastDate: DateTime(2030),
anchorPoint: const Offset(1000, 0),
);
await tester.pumpAndSettle();
// Should take the right side of the screen
expect(tester.getTopLeft(find.byType(DatePickerDialog)), const Offset(410.0, 0.0));
expect(tester.getBottomRight(find.byType(DatePickerDialog)), const Offset(800.0, 600.0));
});
testWidgets('positioning with Directionality', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: Directionality(
textDirection: TextDirection.rtl,
child: child!,
),
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2018),
lastDate: DateTime(2030),
);
await tester.pumpAndSettle();
// By default it should place the dialog on the right screen
expect(tester.getTopLeft(find.byType(DatePickerDialog)), const Offset(410.0, 0.0));
expect(tester.getBottomRight(find.byType(DatePickerDialog)), const Offset(800.0, 600.0));
});
testWidgets('positioning with defaults', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: child!,
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2018),
lastDate: DateTime(2030),
);
await tester.pumpAndSettle();
// By default it should place the dialog on the left screen
expect(tester.getTopLeft(find.byType(DatePickerDialog)), Offset.zero);
expect(tester.getBottomRight(find.byType(DatePickerDialog)), const Offset(390.0, 600.0));
});
});
testWidgets('DatePickerDialog is state restorable', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
......@@ -959,6 +961,121 @@ void main() {
expect(find.byType(TextField), findsNothing);
expect(find.byIcon(Icons.edit), findsNothing);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615
group('showDateRangePicker avoids overlapping display features', () {
testWidgets('positioning with anchorPoint', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: child!,
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
showDateRangePicker(
context: context,
firstDate: DateTime(2018),
lastDate: DateTime(2030),
anchorPoint: const Offset(1000, 0),
);
await tester.pumpAndSettle();
// Should take the right side of the screen
expect(tester.getTopLeft(find.byType(DateRangePickerDialog)), const Offset(410.0, 0.0));
expect(tester.getBottomRight(find.byType(DateRangePickerDialog)), const Offset(800.0, 600.0));
});
testWidgets('positioning with Directionality', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: Directionality(
textDirection: TextDirection.rtl,
child: child!,
),
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
showDateRangePicker(
context: context,
firstDate: DateTime(2018),
lastDate: DateTime(2030),
anchorPoint: const Offset(1000, 0),
);
await tester.pumpAndSettle();
// By default it should place the dialog on the right screen
expect(tester.getTopLeft(find.byType(DateRangePickerDialog)), const Offset(410.0, 0.0));
expect(tester.getBottomRight(find.byType(DateRangePickerDialog)), const Offset(800.0, 600.0));
});
testWidgets('positioning with defaults', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: child!,
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
showDateRangePicker(
context: context,
firstDate: DateTime(2018),
lastDate: DateTime(2030),
);
await tester.pumpAndSettle();
// By default it should place the dialog on the left screen
expect(tester.getTopLeft(find.byType(DateRangePickerDialog)), Offset.zero);
expect(tester.getBottomRight(find.byType(DateRangePickerDialog)), const Offset(390.0, 600.0));
});
});
}
class _RestorableDateRangePickerDialogTestWidget extends StatefulWidget {
......
......@@ -1921,6 +1921,123 @@ void main() {
expect(nestedObserver.dialogCount, 1);
});
group('showDialog avoids overlapping display features', () {
testWidgets('positioning with anchorPoint', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: child!,
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
showDialog<void>(
context: context,
builder: (BuildContext context) {
return const Placeholder();
},
anchorPoint: const Offset(1000, 0),
);
await tester.pumpAndSettle();
// Should take the right side of the screen
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(410.0, 0.0));
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(800.0, 600.0));
});
testWidgets('positioning with Directionality', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: Directionality(
textDirection: TextDirection.rtl,
child: child!,
),
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
showDialog<void>(
context: context,
builder: (BuildContext context) {
return const Placeholder();
},
);
await tester.pumpAndSettle();
// Since this is RTL, it should place the dialog on the right screen
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(410.0, 0.0));
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(800.0, 600.0));
});
testWidgets('positioning by default', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: child!,
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
showDialog<void>(
context: context,
builder: (BuildContext context) {
return const Placeholder();
},
);
await tester.pumpAndSettle();
// By default it should place the dialog on the left screen
expect(tester.getTopLeft(find.byType(Placeholder)), Offset.zero);
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(390.0, 600.0));
});
});
group('AlertDialog.scrollable: ', () {
testWidgets('Title is scrollable', (WidgetTester tester) async {
final Key titleKey = UniqueKey();
......
......@@ -3,6 +3,8 @@
// found in the LICENSE file.
@TestOn('!chrome')
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
......@@ -849,6 +851,117 @@ void _tests() {
expect(tester.getSize(find.text('41')).height, equals(minutesDisplayHeight));
expect(tester.getSize(find.text('AM')).height, equals(amHeight2x));
});
group('showTimePicker avoids overlapping display features', () {
testWidgets('positioning with anchorPoint', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: child!,
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
showTimePicker(
context: context,
initialTime: const TimeOfDay(hour: 7, minute: 0),
anchorPoint: const Offset(1000, 0),
);
await tester.pumpAndSettle();
// Should take the right side of the screen
expect(tester.getTopLeft(find.byType(TimePickerDialog)), const Offset(410.0, 0.0));
expect(tester.getBottomRight(find.byType(TimePickerDialog)), const Offset(800.0, 600.0));
});
testWidgets('positioning with Directionality', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: Directionality(
textDirection: TextDirection.rtl,
child: child!,
),
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
// By default it should place the dialog on the right screen
showTimePicker(
context: context,
initialTime: const TimeOfDay(hour: 7, minute: 0),
);
await tester.pumpAndSettle();
expect(tester.getTopLeft(find.byType(TimePickerDialog)), const Offset(410.0, 0.0));
expect(tester.getBottomRight(find.byType(TimePickerDialog)), const Offset(800.0, 600.0));
});
testWidgets('positioning with defaults', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: child!,
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
// By default it should place the dialog on the left screen
showTimePicker(
context: context,
initialTime: const TimeOfDay(hour: 7, minute: 0),
);
await tester.pumpAndSettle();
expect(tester.getTopLeft(find.byType(TimePickerDialog)), Offset.zero);
expect(tester.getBottomRight(find.byType(TimePickerDialog)), const Offset(390.0, 600.0));
});
});
}
void _testsInput() {
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:collection';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
......@@ -1190,6 +1191,123 @@ void main() {
expect(route.transitionDuration, isNotNull);
});
group('showGeneralDialog avoids overlapping display features', () {
testWidgets('positioning with anchorPoint', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: child!,
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
showGeneralDialog<void>(
context: context,
pageBuilder: (BuildContext context, _, __) {
return const Placeholder();
},
anchorPoint: const Offset(1000, 0),
);
await tester.pumpAndSettle();
// Should take the right side of the screen
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(410.0, 0.0));
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(800.0, 600.0));
});
testWidgets('positioning with Directionality', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: Directionality(
textDirection: TextDirection.rtl,
child: child!,
),
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
showGeneralDialog<void>(
context: context,
pageBuilder: (BuildContext context, _, __) {
return const Placeholder();
},
);
await tester.pumpAndSettle();
// Since this is RTL, it should place the dialog on the right screen
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(410.0, 0.0));
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(800.0, 600.0));
});
testWidgets('positioning by default', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: child!,
);
},
home: const Center(child: Text('Test')),
),
);
final BuildContext context = tester.element(find.text('Test'));
showGeneralDialog<void>(
context: context,
pageBuilder: (BuildContext context, _, __) {
return const Placeholder();
},
);
await tester.pumpAndSettle();
// By default it should place the dialog on the left screen
expect(tester.getTopLeft(find.byType(Placeholder)), Offset.zero);
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(390.0, 600.0));
});
});
testWidgets('reverseTransitionDuration defaults to transitionDuration', (WidgetTester tester) async {
final GlobalKey containerKey = GlobalKey();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment