Unverified Commit fd7a72ee authored by Kate Lovett's avatar Kate Lovett Committed by GitHub

Ensure FloatingActionButtonLocations are always within safe interactive areas (#60248)

parent a4fa61b4
...@@ -413,15 +413,27 @@ abstract class StandardFabLocation extends FloatingActionButtonLocation { ...@@ -413,15 +413,27 @@ abstract class StandardFabLocation extends FloatingActionButtonLocation {
} }
/// Mixin for a "top" floating action button location, such as [FloatingActionButtonLocation.startTop]. /// Mixin for a "top" floating action button location, such as
/// [FloatingActionButtonLocation.startTop].
///
/// The [adjustment], typically [kMiniButtonOffsetAdjustment], is ignored in the
/// Y axis of "top" positions. For "top" positions, the X offset is adjusted to
/// move closer to the edge of the screen. This is so that a minified floating
/// action button appears to align with [CircleAvatar]s in the
/// [ListTile.leading] slot of a [ListTile] in a [ListView] in the
/// [Scaffold.body].
mixin FabTopOffsetY on StandardFabLocation { mixin FabTopOffsetY on StandardFabLocation {
/// Calculates y-offset for [FloatingActionButtonLocation]s floating over /// Calculates y-offset for [FloatingActionButtonLocation]s floating over
/// the transition between the [Scaffold.appBar] and the [Scaffold.body]. /// the transition between the [Scaffold.appBar] and the [Scaffold.body].
@override @override
double getOffsetY(ScaffoldPrelayoutGeometry scaffoldGeometry, double adjustment) { double getOffsetY(ScaffoldPrelayoutGeometry scaffoldGeometry, double adjustment) {
if (scaffoldGeometry.contentTop > scaffoldGeometry.minViewPadding.top) {
final double fabHalfHeight = scaffoldGeometry.floatingActionButtonSize.height / 2.0; final double fabHalfHeight = scaffoldGeometry.floatingActionButtonSize.height / 2.0;
return scaffoldGeometry.contentTop - fabHalfHeight; return scaffoldGeometry.contentTop - fabHalfHeight;
} }
// Otherwise, ensure we are placed within the bounds of a safe area.
return scaffoldGeometry.minViewPadding.top;
}
} }
/// Mixin for a "float" floating action button location, such as [FloatingActionButtonLocation.centerFloat]. /// Mixin for a "float" floating action button location, such as [FloatingActionButtonLocation.centerFloat].
...@@ -434,8 +446,12 @@ mixin FabFloatOffsetY on StandardFabLocation { ...@@ -434,8 +446,12 @@ mixin FabFloatOffsetY on StandardFabLocation {
final double bottomSheetHeight = scaffoldGeometry.bottomSheetSize.height; final double bottomSheetHeight = scaffoldGeometry.bottomSheetSize.height;
final double fabHeight = scaffoldGeometry.floatingActionButtonSize.height; final double fabHeight = scaffoldGeometry.floatingActionButtonSize.height;
final double snackBarHeight = scaffoldGeometry.snackBarSize.height; final double snackBarHeight = scaffoldGeometry.snackBarSize.height;
final double safeMargin = math.max(
kFloatingActionButtonMargin,
scaffoldGeometry.minViewPadding.bottom,
);
double fabY = contentBottom - fabHeight - kFloatingActionButtonMargin; double fabY = contentBottom - fabHeight - safeMargin;
if (snackBarHeight > 0.0) if (snackBarHeight > 0.0)
fabY = math.min(fabY, contentBottom - snackBarHeight - fabHeight - kFloatingActionButtonMargin); fabY = math.min(fabY, contentBottom - snackBarHeight - fabHeight - kFloatingActionButtonMargin);
if (bottomSheetHeight > 0.0) if (bottomSheetHeight > 0.0)
...@@ -453,19 +469,21 @@ mixin FabDockedOffsetY on StandardFabLocation { ...@@ -453,19 +469,21 @@ mixin FabDockedOffsetY on StandardFabLocation {
@override @override
double getOffsetY(ScaffoldPrelayoutGeometry scaffoldGeometry, double adjustment) { double getOffsetY(ScaffoldPrelayoutGeometry scaffoldGeometry, double adjustment) {
final double contentBottom = scaffoldGeometry.contentBottom; final double contentBottom = scaffoldGeometry.contentBottom;
final double contentMargin = scaffoldGeometry.scaffoldSize.height - contentBottom;
final double bottomViewPadding = scaffoldGeometry.minViewPadding.bottom;
final double bottomSheetHeight = scaffoldGeometry.bottomSheetSize.height; final double bottomSheetHeight = scaffoldGeometry.bottomSheetSize.height;
final double fabHeight = scaffoldGeometry.floatingActionButtonSize.height; final double fabHeight = scaffoldGeometry.floatingActionButtonSize.height;
final double snackBarHeight = scaffoldGeometry.snackBarSize.height; final double snackBarHeight = scaffoldGeometry.snackBarSize.height;
final double safeMargin = bottomViewPadding > contentMargin ? bottomViewPadding : 0.0;
double fabY = contentBottom - fabHeight / 2.0; double fabY = contentBottom - fabHeight / 2.0 - safeMargin;
// The FAB should sit with a margin between it and the snack bar. // The FAB should sit with a margin between it and the snack bar.
if (snackBarHeight > 0.0) if (snackBarHeight > 0.0)
fabY = math.min(fabY, contentBottom - snackBarHeight - fabHeight - kFloatingActionButtonMargin); fabY = math.min(fabY, contentBottom - snackBarHeight - fabHeight - kFloatingActionButtonMargin);
// The FAB should sit with its center in front of the top of the bottom sheet. // The FAB should sit with its center in front of the top of the bottom sheet.
if (bottomSheetHeight > 0.0) if (bottomSheetHeight > 0.0)
fabY = math.min(fabY, contentBottom - bottomSheetHeight - fabHeight / 2.0); fabY = math.min(fabY, contentBottom - bottomSheetHeight - fabHeight / 2.0);
final double maxFabY = scaffoldGeometry.scaffoldSize.height - fabHeight - safeMargin;
final double maxFabY = scaffoldGeometry.scaffoldSize.height - fabHeight;
return math.min(maxFabY, fabY); return math.min(maxFabY, fabY);
} }
} }
......
...@@ -80,6 +80,7 @@ class ScaffoldPrelayoutGeometry { ...@@ -80,6 +80,7 @@ class ScaffoldPrelayoutGeometry {
@required this.contentTop, @required this.contentTop,
@required this.floatingActionButtonSize, @required this.floatingActionButtonSize,
@required this.minInsets, @required this.minInsets,
@required this.minViewPadding,
@required this.scaffoldSize, @required this.scaffoldSize,
@required this.snackBarSize, @required this.snackBarSize,
@required this.textDirection, @required this.textDirection,
...@@ -134,6 +135,16 @@ class ScaffoldPrelayoutGeometry { ...@@ -134,6 +135,16 @@ class ScaffoldPrelayoutGeometry {
/// will be 0.0. /// will be 0.0.
final EdgeInsets minInsets; final EdgeInsets minInsets;
/// The minimum padding to inset interactive elements to be within a safe,
/// un-obscured space.
///
/// This value reflects the [MediaQuery.viewPadding] of the [Scaffold]'s
/// [BuildContext] when [Scaffold.resizeToAvoidBottomInset] is false or and
/// the [MediaQuery.viewInsets] > 0.0. This helps distinguish between
/// different types of obstructions on the screen, such as software keyboards
/// and physical device notches.
final EdgeInsets minViewPadding;
/// The [Size] of the whole [Scaffold]. /// The [Size] of the whole [Scaffold].
/// ///
/// If the [Size] of the [Scaffold]'s contents is modified by values such as /// If the [Size] of the [Scaffold]'s contents is modified by values such as
...@@ -389,6 +400,7 @@ class _BodyBuilder extends StatelessWidget { ...@@ -389,6 +400,7 @@ class _BodyBuilder extends StatelessWidget {
class _ScaffoldLayout extends MultiChildLayoutDelegate { class _ScaffoldLayout extends MultiChildLayoutDelegate {
_ScaffoldLayout({ _ScaffoldLayout({
@required this.minInsets, @required this.minInsets,
@required this.minViewPadding,
@required this.textDirection, @required this.textDirection,
@required this.geometryNotifier, @required this.geometryNotifier,
// for floating action button // for floating action button
...@@ -410,6 +422,7 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate { ...@@ -410,6 +422,7 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
final bool extendBody; final bool extendBody;
final bool extendBodyBehindAppBar; final bool extendBodyBehindAppBar;
final EdgeInsets minInsets; final EdgeInsets minInsets;
final EdgeInsets minViewPadding;
final TextDirection textDirection; final TextDirection textDirection;
final _ScaffoldGeometryNotifier geometryNotifier; final _ScaffoldGeometryNotifier geometryNotifier;
...@@ -536,6 +549,7 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate { ...@@ -536,6 +549,7 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
scaffoldSize: size, scaffoldSize: size,
snackBarSize: snackBarSize, snackBarSize: snackBarSize,
textDirection: textDirection, textDirection: textDirection,
minViewPadding: minViewPadding,
); );
final Offset currentFabOffset = currentFloatingActionButtonLocation.getOffset(currentGeometry); final Offset currentFabOffset = currentFloatingActionButtonLocation.getOffset(currentGeometry);
final Offset previousFabOffset = previousFloatingActionButtonLocation.getOffset(currentGeometry); final Offset previousFabOffset = previousFloatingActionButtonLocation.getOffset(currentGeometry);
...@@ -2497,6 +2511,12 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin { ...@@ -2497,6 +2511,12 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
bottom: _resizeToAvoidBottomInset ? mediaQuery.viewInsets.bottom : 0.0, bottom: _resizeToAvoidBottomInset ? mediaQuery.viewInsets.bottom : 0.0,
); );
// The minimum viewPadding for interactive elements positioned by the
// Scaffold to keep within safe interactive areas.
final EdgeInsets minViewPadding = mediaQuery.viewPadding.copyWith(
bottom: _resizeToAvoidBottomInset && mediaQuery.viewInsets.bottom != 0.0 ? 0.0 : null,
);
// extendBody locked when keyboard is open // extendBody locked when keyboard is open
final bool _extendBody = minInsets.bottom <= 0 && widget.extendBody; final bool _extendBody = minInsets.bottom <= 0 && widget.extendBody;
...@@ -2514,6 +2534,7 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin { ...@@ -2514,6 +2534,7 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
extendBody: _extendBody, extendBody: _extendBody,
extendBodyBehindAppBar: widget.extendBodyBehindAppBar, extendBodyBehindAppBar: widget.extendBodyBehindAppBar,
minInsets: minInsets, minInsets: minInsets,
minViewPadding: minViewPadding,
currentFloatingActionButtonLocation: _floatingActionButtonLocation, currentFloatingActionButtonLocation: _floatingActionButtonLocation,
floatingActionButtonMoveAnimationProgress: _floatingActionButtonMoveController.value, floatingActionButtonMoveAnimationProgress: _floatingActionButtonMoveController.value,
floatingActionButtonMotionAnimator: _floatingActionButtonAnimator, floatingActionButtonMotionAnimator: _floatingActionButtonAnimator,
......
...@@ -609,8 +609,749 @@ void main() { ...@@ -609,8 +609,749 @@ void main() {
expect(tester.binding.transientCallbackCount, 0); expect(tester.binding.transientCallbackCount, 0);
}); });
}); });
}
group('Locations account for safe interactive areas', () {
Widget _buildTest(
FloatingActionButtonLocation location,
MediaQueryData data,
Key key, {
bool mini = false,
bool appBar = false,
bool bottomNavigationBar = false,
bool bottomSheet = false,
bool resizeToAvoidBottomInset = true,
}) {
return MaterialApp(
home: MediaQuery(
data: data,
child: Scaffold(
resizeToAvoidBottomInset: resizeToAvoidBottomInset,
bottomSheet: bottomSheet ? Container(
height: 100,
child: const Center(child: Text('BottomSheet')),
) : null,
appBar: appBar ? AppBar(title: const Text('Demo')) : null,
bottomNavigationBar: bottomNavigationBar ? BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.star),
title: Text('0'),
),
BottomNavigationBarItem(
icon: Icon(Icons.star_border),
title: Text('1'),
),
],
currentIndex: 0,
) : null,
floatingActionButtonLocation: location,
floatingActionButton: Builder(
builder: (BuildContext context) {
return FloatingActionButton(
onPressed: () {
Scaffold.of(context).showSnackBar(
const SnackBar(content: Text('Snacky!')),
);
},
child: const Text('FAB'),
mini: mini,
key: key,
);
}
),
)
)
);
}
// Test float locations, for each (6), keyboard presented or not:
// - Default
// - with resizeToAvoidBottomInset: false
// - with BottomSheet
// - with BottomSheet and resizeToAvoidBottomInset: false
// - with SnackBar
Future<void> _runFloatTests(
WidgetTester tester,
FloatingActionButtonLocation location,
Rect defaultRect,
Rect bottomSheetRect,
Rect snackBarRect, {
bool mini = false,
}) async {
const double keyboardHeight = 200.0;
const double viewPadding = 50.0;
final Key floatingActionButton = UniqueKey();
// Default
await tester.pumpWidget(_buildTest(
location,
const MediaQueryData(viewPadding: EdgeInsets.only(bottom: viewPadding)),
floatingActionButton,
mini: mini,
));
expect(
tester.getRect(find.byKey(floatingActionButton)),
rectMoreOrLessEquals(defaultRect),
);
// Present keyboard and check position, should change
await tester.pumpWidget(_buildTest(
location,
const MediaQueryData(
viewPadding: EdgeInsets.only(bottom: viewPadding),
viewInsets: EdgeInsets.only(bottom: keyboardHeight),
),
floatingActionButton,
mini: mini,
));
expect(
tester.getRect(find.byKey(floatingActionButton)),
rectMoreOrLessEquals(defaultRect.translate(
0.0,
viewPadding - keyboardHeight - kFloatingActionButtonMargin,
)),
);
// With resizeToAvoidBottomInset: false
// With keyboard presented, should maintain default position
await tester.pumpWidget(_buildTest(
location,
const MediaQueryData(
viewPadding: EdgeInsets.only(bottom: viewPadding),
viewInsets: EdgeInsets.only(bottom: keyboardHeight),
),
floatingActionButton,
resizeToAvoidBottomInset: false,
mini: mini,
));
expect(
tester.getRect(find.byKey(floatingActionButton)),
rectMoreOrLessEquals(defaultRect),
);
// BottomSheet default
await tester.pumpWidget(_buildTest(
location,
const MediaQueryData(viewPadding: EdgeInsets.only(bottom: viewPadding)),
floatingActionButton,
bottomSheet: true,
mini: mini,
));
expect(
tester.getRect(find.byKey(floatingActionButton)),
rectMoreOrLessEquals(bottomSheetRect),
);
// Present keyboard and check position, bottomSheet and FAB both resize
await tester.pumpWidget(_buildTest(
location,
const MediaQueryData(
viewPadding: EdgeInsets.only(bottom: viewPadding),
viewInsets: EdgeInsets.only(bottom: keyboardHeight),
),
floatingActionButton,
bottomSheet: true,
mini: mini,
));
expect(
tester.getRect(find.byKey(floatingActionButton)),
rectMoreOrLessEquals(bottomSheetRect.translate(0.0, -keyboardHeight)),
);
// bottomSheet with resizeToAvoidBottomInset: false
// With keyboard presented, should maintain default bottomSheet position
await tester.pumpWidget(_buildTest(
location,
const MediaQueryData(
viewPadding: EdgeInsets.only(bottom: viewPadding),
viewInsets: EdgeInsets.only(bottom: keyboardHeight)
),
floatingActionButton,
bottomSheet: true,
resizeToAvoidBottomInset: false,
mini: mini,
));
expect(
tester.getRect(find.byKey(floatingActionButton)),
rectMoreOrLessEquals(bottomSheetRect),
);
// SnackBar default
await tester.pumpWidget(_buildTest(
location,
const MediaQueryData(viewPadding: EdgeInsets.only(bottom: viewPadding)),
floatingActionButton,
mini: mini,
));
await tester.tap(find.byKey(floatingActionButton));
await tester.pumpAndSettle(); // Show SnackBar
expect(
tester.getRect(find.byKey(floatingActionButton)),
rectMoreOrLessEquals(snackBarRect),
);
// SnackBar when resized for presented keyboard
await tester.pumpWidget(_buildTest(
location,
const MediaQueryData(
viewPadding: EdgeInsets.only(bottom: viewPadding),
viewInsets: EdgeInsets.only(bottom: keyboardHeight)
),
floatingActionButton,
mini: mini,
));
await tester.tap(find.byKey(floatingActionButton));
await tester.pumpAndSettle(); // Show SnackBar
expect(
tester.getRect(find.byKey(floatingActionButton)),
rectMoreOrLessEquals(snackBarRect.translate(0.0, -keyboardHeight)),
);
}
testWidgets('startFloat', (WidgetTester tester) async {
const Rect defaultRect = Rect.fromLTRB(16.0, 494.0, 72.0, 550.0);
// Position relative to BottomSheet
const Rect bottomSheetRect = Rect.fromLTRB(16.0, 472.0, 72.0, 528.0);
// Positioned relative to SnackBar
const Rect snackBarRect = Rect.fromLTRB(16.0, 486.0, 72.0, 542.0);
await _runFloatTests(
tester,
FloatingActionButtonLocation.startFloat,
defaultRect,
bottomSheetRect,
snackBarRect,
);
});
testWidgets('miniStartFloat', (WidgetTester tester) async {
const Rect defaultRect = Rect.fromLTRB(12.0, 506.0, 60.0, 554.0);
// Positioned relative to BottomSheet
const Rect bottomSheetRect = Rect.fromLTRB(12.0, 480.0, 60.0, 528.0);
// Positioned relative to SnackBar
const Rect snackBarRect = Rect.fromLTRB(12.0, 498.0, 60.0, 546.0);
await _runFloatTests(
tester,
FloatingActionButtonLocation.miniStartFloat,
defaultRect,
bottomSheetRect,
snackBarRect,
mini: true,
);
});
testWidgets('centerFloat', (WidgetTester tester) async {
const Rect defaultRect = Rect.fromLTRB(372.0, 494.0, 428.0, 550.0);
// Positioned relative to BottomSheet
const Rect bottomSheetRect = Rect.fromLTRB(372.0, 472.0, 428.0, 528.0);
// Positioned relative to SnackBar
const Rect snackBarRect = Rect.fromLTRB(372.0, 486.0, 428.0, 542.0);
await _runFloatTests(
tester,
FloatingActionButtonLocation.centerFloat,
defaultRect,
bottomSheetRect,
snackBarRect,
);
});
testWidgets('miniCenterFloat', (WidgetTester tester) async {
const Rect defaultRect = Rect.fromLTRB(376.0, 506.0, 424.0, 554.0);
// Positioned relative to BottomSheet
const Rect bottomSheetRect = Rect.fromLTRB(376.0, 480.0, 424.0, 528.0);
// Positioned relative to SnackBar
const Rect snackBarRect = Rect.fromLTRB(376.0, 498.0, 424.0, 546.0);
await _runFloatTests(
tester,
FloatingActionButtonLocation.miniCenterFloat,
defaultRect,
bottomSheetRect,
snackBarRect,
mini: true,
);
});
testWidgets('endFloat', (WidgetTester tester) async {
const Rect defaultRect = Rect.fromLTRB(728.0, 494.0, 784.0, 550.0);
// Positioned relative to BottomSheet
const Rect bottomSheetRect = Rect.fromLTRB(728.0, 472.0, 784.0, 528.0);
// Positioned relative to SnackBar
const Rect snackBarRect = Rect.fromLTRB(728.0, 486.0, 784.0, 542.0);
await _runFloatTests(
tester,
FloatingActionButtonLocation.endFloat,
defaultRect,
bottomSheetRect,
snackBarRect,
);
});
testWidgets('miniEndFloat', (WidgetTester tester) async {
const Rect defaultRect = Rect.fromLTRB(740.0, 506.0, 788.0, 554.0);
// Positioned relative to BottomSheet
const Rect bottomSheetRect = Rect.fromLTRB(740.0, 480.0, 788.0, 528.0);
// Positioned relative to SnackBar
const Rect snackBarRect = Rect.fromLTRB(740.0, 498.0, 788.0, 546.0);
await _runFloatTests(
tester,
FloatingActionButtonLocation.miniEndFloat,
defaultRect,
bottomSheetRect,
snackBarRect,
mini: true,
);
});
// Test docked locations, for each (6), keyboard presented or not:
// - Default
// - Default with resizeToAvoidBottomInset: false
// - docked with BottomNavigationBar
// - docked with BottomNavigationBar and resizeToAvoidBottomInset: false
// - docked with BottomNavigationBar & BottomSheet
// - docked with BottomNavigationBar & BottomSheet, resizeToAvoidBottomInset: false
// - with SnackBar
Future<void> _runDockedTests(
WidgetTester tester,
FloatingActionButtonLocation location,
Rect defaultRect,
Rect bottomNavigationBarRect,
Rect bottomSheetRect,
Rect snackBarRect, {
bool mini = false,
}) async {
const double keyboardHeight = 200.0;
const double viewPadding = 50.0;
const double bottomNavHeight = 99.0;
final Key floatingActionButton = UniqueKey();
final double fabHeight = mini ? 48.0 : 56.0;
// Default
await tester.pumpWidget(_buildTest(
location,
const MediaQueryData(viewPadding: EdgeInsets.only(bottom: viewPadding)),
floatingActionButton,
mini: mini,
));
expect(
tester.getRect(find.byKey(floatingActionButton)),
rectMoreOrLessEquals(defaultRect),
);
// Present keyboard and check position, should change
await tester.pumpWidget(_buildTest(
location,
const MediaQueryData(
viewPadding: EdgeInsets.only(bottom: viewPadding),
viewInsets: EdgeInsets.only(bottom: keyboardHeight),
),
floatingActionButton,
mini: mini,
));
expect(
tester.getRect(find.byKey(floatingActionButton)),
rectMoreOrLessEquals(defaultRect.translate(
0.0,
viewPadding - keyboardHeight + fabHeight / 2.0,
)),
);
// With resizeToAvoidBottomInset: false
// With keyboard presented, should maintain default position
await tester.pumpWidget(_buildTest(
location,
const MediaQueryData(
viewPadding: EdgeInsets.only(bottom: viewPadding),
viewInsets: EdgeInsets.only(bottom: keyboardHeight),
),
floatingActionButton,
resizeToAvoidBottomInset: false,
mini: mini,
));
expect(
tester.getRect(find.byKey(floatingActionButton)),
rectMoreOrLessEquals(defaultRect),
);
// BottomNavigationBar default
await tester.pumpWidget(_buildTest(
location,
const MediaQueryData(
padding: EdgeInsets.only(bottom: viewPadding),
viewPadding: EdgeInsets.only(bottom: viewPadding),
),
floatingActionButton,
bottomNavigationBar: true,
mini: mini,
));
expect(
tester.getRect(find.byKey(floatingActionButton)),
rectMoreOrLessEquals(bottomNavigationBarRect),
);
// Present keyboard and check position, FAB position changes
await tester.pumpWidget(_buildTest(
location,
const MediaQueryData(
padding: EdgeInsets.only(bottom: viewPadding),
viewPadding: EdgeInsets.only(bottom: viewPadding),
viewInsets: EdgeInsets.only(bottom: keyboardHeight),
),
floatingActionButton,
bottomNavigationBar: true,
mini: mini,
));
expect(
tester.getRect(find.byKey(floatingActionButton)),
rectMoreOrLessEquals(bottomNavigationBarRect.translate(
0.0,
-keyboardHeight + bottomNavHeight,
)),
);
// BottomNavigationBar with resizeToAvoidBottomInset: false
// With keyboard presented, should maintain default position
await tester.pumpWidget(_buildTest(
location,
const MediaQueryData(
padding: EdgeInsets.only(bottom: viewPadding),
viewPadding: EdgeInsets.only(bottom: viewPadding),
viewInsets: EdgeInsets.only(bottom: keyboardHeight)
),
floatingActionButton,
bottomNavigationBar: true,
resizeToAvoidBottomInset: false,
mini: mini,
));
expect(
tester.getRect(find.byKey(floatingActionButton)),
rectMoreOrLessEquals(bottomNavigationBarRect),
);
// BottomNavigationBar + BottomSheet default
await tester.pumpWidget(_buildTest(
location,
const MediaQueryData(
padding: EdgeInsets.only(bottom: viewPadding),
viewPadding: EdgeInsets.only(bottom: viewPadding),
),
floatingActionButton,
bottomNavigationBar: true,
bottomSheet: true,
mini: mini,
));
expect(
tester.getRect(find.byKey(floatingActionButton)),
rectMoreOrLessEquals(bottomSheetRect),
);
// Present keyboard and check position, FAB position changes
await tester.pumpWidget(_buildTest(
location,
const MediaQueryData(
padding: EdgeInsets.only(bottom: viewPadding),
viewPadding: EdgeInsets.only(bottom: viewPadding),
viewInsets: EdgeInsets.only(bottom: keyboardHeight),
),
floatingActionButton,
bottomNavigationBar: true,
bottomSheet: true,
mini: mini,
));
expect(
tester.getRect(find.byKey(floatingActionButton)),
rectMoreOrLessEquals(bottomSheetRect.translate(
0.0,
-keyboardHeight + bottomNavHeight,
)),
);
// BottomNavigationBar + BottomSheet with resizeToAvoidBottomInset: false
// With keyboard presented, should maintain default position
await tester.pumpWidget(_buildTest(
location,
const MediaQueryData(
padding: EdgeInsets.only(bottom: viewPadding),
viewPadding: EdgeInsets.only(bottom: viewPadding),
viewInsets: EdgeInsets.only(bottom: keyboardHeight)
),
floatingActionButton,
bottomNavigationBar: true,
bottomSheet: true,
resizeToAvoidBottomInset: false,
mini: mini,
));
expect(
tester.getRect(find.byKey(floatingActionButton)),
rectMoreOrLessEquals(bottomSheetRect),
);
// SnackBar default
await tester.pumpWidget(_buildTest(
location,
const MediaQueryData(viewPadding: EdgeInsets.only(bottom: viewPadding)),
floatingActionButton,
mini: mini,
));
await tester.tap(find.byKey(floatingActionButton));
await tester.pumpAndSettle(); // Show SnackBar
expect(
tester.getRect(find.byKey(floatingActionButton)),
rectMoreOrLessEquals(snackBarRect),
);
// SnackBar with BottomNavigationBar
await tester.pumpWidget(_buildTest(
location,
const MediaQueryData(
padding: EdgeInsets.only(bottom: viewPadding),
viewPadding: EdgeInsets.only(bottom: viewPadding),
),
floatingActionButton,
bottomNavigationBar: true,
mini: mini,
));
await tester.tap(find.byKey(floatingActionButton));
await tester.pumpAndSettle(); // Show SnackBar
expect(
tester.getRect(find.byKey(floatingActionButton)),
rectMoreOrLessEquals(snackBarRect.translate(0.0, -bottomNavHeight)),
);
// SnackBar when resized for presented keyboard
await tester.pumpWidget(_buildTest(
location,
const MediaQueryData(
viewPadding: EdgeInsets.only(bottom: viewPadding),
viewInsets: EdgeInsets.only(bottom: keyboardHeight)
),
floatingActionButton,
mini: mini,
));
await tester.tap(find.byKey(floatingActionButton));
await tester.pumpAndSettle(); // Show SnackBar
expect(
tester.getRect(find.byKey(floatingActionButton)),
rectMoreOrLessEquals(snackBarRect.translate(0.0, -keyboardHeight)),
);
}
testWidgets('startDocked', (WidgetTester tester) async {
const Rect defaultRect = Rect.fromLTRB(16.0, 494.0, 72.0, 550.0);
// Positioned relative to BottomNavigationBar
const Rect bottomNavRect = Rect.fromLTRB(16.0, 473.0, 72.0, 529.0);
// Positioned relative to BottomNavigationBar & BottomSheet
const Rect bottomSheetRect = Rect.fromLTRB(16.0, 373.0, 72.0, 429.0);
// Positioned relative to SnackBar
const Rect snackBarRect = Rect.fromLTRB(16.0, 486.0, 72.0, 542.0);
await _runDockedTests(
tester,
FloatingActionButtonLocation.startDocked,
defaultRect,
bottomNavRect,
bottomSheetRect,
snackBarRect,
);
});
testWidgets('miniStartDocked', (WidgetTester tester) async {
const Rect defaultRect = Rect.fromLTRB(12.0, 502.0, 60.0, 550.0);
// Positioned relative to BottomNavigationBar
const Rect bottomNavRect = Rect.fromLTRB(12.0, 477.0, 60.0, 525.0);
// Positioned relative to BottomNavigationBar & BottomSheet
const Rect bottomSheetRect = Rect.fromLTRB(12.0, 377.0, 60.0, 425.0);
// Positioned relative to SnackBar
const Rect snackBarRect = Rect.fromLTRB(12.0, 494.0, 60.0, 542.0);
await _runDockedTests(
tester,
FloatingActionButtonLocation.miniStartDocked,
defaultRect,
bottomNavRect,
bottomSheetRect,
snackBarRect,
mini: true,
);
});
testWidgets('centerDocked', (WidgetTester tester) async {
const Rect defaultRect = Rect.fromLTRB(372.0, 494.0, 428.0, 550.0);
// Positioned relative to BottomNavigationBar
const Rect bottomNavRect = Rect.fromLTRB(372.0, 473.0, 428.0, 529.0);
// Positioned relative to BottomNavigationBar & BottomSheet
const Rect bottomSheetRect = Rect.fromLTRB(372.0, 373.0, 428.0, 429.0);
// Positioned relative to SnackBar
const Rect snackBarRect = Rect.fromLTRB(372.0, 486.0, 428.0, 542.0);
await _runDockedTests(
tester,
FloatingActionButtonLocation.centerDocked,
defaultRect,
bottomNavRect,
bottomSheetRect,
snackBarRect,
);
});
testWidgets('miniCenterDocked', (WidgetTester tester) async {
const Rect defaultRect = Rect.fromLTRB(376.0, 502.0, 424.0, 550.0);
// Positioned relative to BottomNavigationBar
const Rect bottomNavRect = Rect.fromLTRB(376.0, 477.0, 424.0, 525.0);
// Positioned relative to BottomNavigationBar & BottomSheet
const Rect bottomSheetRect = Rect.fromLTRB(376.0, 377.0, 424.0, 425.0);
// Positioned relative to SnackBar
const Rect snackBarRect = Rect.fromLTRB(376.0, 494.0, 424.0, 542.0);
await _runDockedTests(
tester,
FloatingActionButtonLocation.miniCenterDocked,
defaultRect,
bottomNavRect,
bottomSheetRect,
snackBarRect,
mini: true,
);
});
testWidgets('endDocked', (WidgetTester tester) async {
const Rect defaultRect = Rect.fromLTRB(728.0, 494.0, 784.0, 550.0);
// Positioned relative to BottomNavigationBar
const Rect bottomNavRect = Rect.fromLTRB(728.0, 473.0, 784.0, 529.0);
// Positioned relative to BottomNavigationBar & BottomSheet
const Rect bottomSheetRect = Rect.fromLTRB(728.0, 373.0, 784.0, 429.0);
// Positioned relative to SnackBar
const Rect snackBarRect = Rect.fromLTRB(728.0, 486.0, 784.0, 542.0);
await _runDockedTests(
tester,
FloatingActionButtonLocation.endDocked,
defaultRect,
bottomNavRect,
bottomSheetRect,
snackBarRect,
);
});
testWidgets('miniEndDocked', (WidgetTester tester) async {
const Rect defaultRect = Rect.fromLTRB(740.0, 502.0, 788.0, 550.0);
// Positioned relative to BottomNavigationBar
const Rect bottomNavRect = Rect.fromLTRB(740.0, 477.0, 788.0, 525.0);
// Positioned relative to BottomNavigationBar & BottomSheet
const Rect bottomSheetRect = Rect.fromLTRB(740.0, 377.0, 788.0, 425.0);
// Positioned relative to SnackBar
const Rect snackBarRect = Rect.fromLTRB(740.0, 494.0, 788.0, 542.0);
await _runDockedTests(
tester,
FloatingActionButtonLocation.miniEndDocked,
defaultRect,
bottomNavRect,
bottomSheetRect,
snackBarRect,
mini: true,
);
});
// Test top locations, for each (6):
// - Default
// - with an AppBar
Future<void> _runTopTests(
WidgetTester tester,
FloatingActionButtonLocation location,
Rect defaultRect,
Rect appBarRect, {
bool mini = false,
}) async {
const double viewPadding = 50.0;
final Key floatingActionButton = UniqueKey();
// Default
await tester.pumpWidget(_buildTest(
location,
const MediaQueryData(viewPadding: EdgeInsets.only(top: viewPadding)),
floatingActionButton,
mini: mini,
));
expect(
tester.getRect(find.byKey(floatingActionButton)),
rectMoreOrLessEquals(defaultRect),
);
// AppBar default
await tester.pumpWidget(_buildTest(
location,
const MediaQueryData(viewPadding: EdgeInsets.only(top: viewPadding)),
floatingActionButton,
appBar: true,
mini: mini,
));
expect(
tester.getRect(find.byKey(floatingActionButton)),
rectMoreOrLessEquals(appBarRect),
);
}
testWidgets('startTop', (WidgetTester tester) async {
const Rect defaultRect = Rect.fromLTRB(16.0, 50.0, 72.0, 106.0);
// Positioned relative to AppBar
const Rect appBarRect = Rect.fromLTRB(16.0, 28.0, 72.0, 84.0);
await _runTopTests(
tester,
FloatingActionButtonLocation.startTop,
defaultRect,
appBarRect,
);
});
testWidgets('miniStartTop', (WidgetTester tester) async {
const Rect defaultRect = Rect.fromLTRB(12.0, 50.0, 60.0, 98.0);
// Positioned relative to AppBar
const Rect appBarRect = Rect.fromLTRB(12.0, 32.0, 60.0, 80.0);
await _runTopTests(
tester,
FloatingActionButtonLocation.miniStartTop,
defaultRect,
appBarRect,
mini: true,
);
});
testWidgets('centerTop', (WidgetTester tester) async {
const Rect defaultRect = Rect.fromLTRB(372.0, 50.0, 428.0, 106.0);
// Positioned relative to AppBar
const Rect appBarRect = Rect.fromLTRB(372.0, 28.0, 428.0, 84.0);
await _runTopTests(
tester,
FloatingActionButtonLocation.centerTop,
defaultRect,
appBarRect,
);
});
testWidgets('miniCenterTop', (WidgetTester tester) async {
const Rect defaultRect = Rect.fromLTRB(376.0, 50.0, 424.0, 98.0);
// Positioned relative to AppBar
const Rect appBarRect = Rect.fromLTRB(376.0, 32.0, 424.0, 80.0);
await _runTopTests(
tester,
FloatingActionButtonLocation.miniCenterTop,
defaultRect,
appBarRect,
mini: true,
);
});
testWidgets('endTop', (WidgetTester tester) async {
const Rect defaultRect = Rect.fromLTRB(728.0, 50.0, 784.0, 106.0);
// Positioned relative to AppBar
const Rect appBarRect = Rect.fromLTRB(728.0, 28.0, 784.0, 84.0);
await _runTopTests(
tester,
FloatingActionButtonLocation.endTop,
defaultRect,
appBarRect,
);
});
testWidgets('miniEndTop', (WidgetTester tester) async {
const Rect defaultRect = Rect.fromLTRB(740.0, 50.0, 788.0, 98.0);
// Positioned relative to AppBar
const Rect appBarRect = Rect.fromLTRB(740.0, 32.0, 788.0, 80.0);
await _runTopTests(
tester,
FloatingActionButtonLocation.miniEndTop,
defaultRect,
appBarRect,
mini: true,
);
});
});
}
class _GeometryListener extends StatefulWidget { class _GeometryListener extends StatefulWidget {
@override @override
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment