Add Foldable support for modal routes (#92909)
This commit is contained in:
parent
ffe64c6336
commit
df7f05f710
@ -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();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user