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