| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:math'; |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| void main() { |
| group('Basic floating action button locations', () { |
| testWidgets('still animates motion when the floating action button is null', (WidgetTester tester) async { |
| await tester.pumpWidget(buildFrame(fab: null, location: null)); |
| |
| expect(find.byType(FloatingActionButton), findsNothing); |
| expect(tester.binding.transientCallbackCount, 0); |
| |
| await tester.pumpWidget(buildFrame(fab: null, location: FloatingActionButtonLocation.endFloat)); |
| |
| expect(find.byType(FloatingActionButton), findsNothing); |
| expect(tester.binding.transientCallbackCount, greaterThan(0)); |
| |
| await tester.pumpWidget(buildFrame(fab: null, location: FloatingActionButtonLocation.centerFloat)); |
| |
| expect(find.byType(FloatingActionButton), findsNothing); |
| expect(tester.binding.transientCallbackCount, greaterThan(0)); |
| }); |
| |
| testWidgets('moves fab from center to end and back', (WidgetTester tester) async { |
| await tester.pumpWidget(buildFrame(location: FloatingActionButtonLocation.endFloat)); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 356.0)); |
| expect(tester.binding.transientCallbackCount, 0); |
| |
| await tester.pumpWidget(buildFrame(location: FloatingActionButtonLocation.centerFloat)); |
| |
| expect(tester.binding.transientCallbackCount, greaterThan(0)); |
| |
| await tester.pumpAndSettle(); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(400.0, 356.0)); |
| expect(tester.binding.transientCallbackCount, 0); |
| |
| await tester.pumpWidget(buildFrame(location: FloatingActionButtonLocation.endFloat)); |
| |
| expect(tester.binding.transientCallbackCount, greaterThan(0)); |
| |
| await tester.pumpAndSettle(); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 356.0)); |
| expect(tester.binding.transientCallbackCount, 0); |
| }); |
| |
| testWidgets('moves to and from custom-defined positions', (WidgetTester tester) async { |
| await tester.pumpWidget(buildFrame(location: const _StartTopFloatingActionButtonLocation())); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(44.0, 56.0)); |
| |
| await tester.pumpWidget(buildFrame(location: FloatingActionButtonLocation.centerFloat)); |
| expect(tester.binding.transientCallbackCount, greaterThan(0)); |
| |
| await tester.pumpAndSettle(); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(400.0, 356.0)); |
| expect(tester.binding.transientCallbackCount, 0); |
| |
| await tester.pumpWidget(buildFrame(location: const _StartTopFloatingActionButtonLocation())); |
| |
| expect(tester.binding.transientCallbackCount, greaterThan(0)); |
| |
| await tester.pumpAndSettle(); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(44.0, 56.0)); |
| expect(tester.binding.transientCallbackCount, 0); |
| |
| }); |
| |
| group('interrupts in-progress animations without jumps', () { |
| _GeometryListener? geometryListener; |
| ScaffoldGeometry? geometry; |
| _GeometryListenerState? listenerState; |
| Size? previousRect; |
| Iterable<double>? previousRotations; |
| |
| // The maximum amounts we expect the fab width and height to change |
| // during one step of a transition. |
| const double maxDeltaWidth = 12.5; |
| const double maxDeltaHeight = 12.5; |
| |
| // The maximum amounts we expect the fab icon to rotate during one step |
| // of a transition. |
| const double maxDeltaRotation = 0.09; |
| |
| // We'll listen to the Scaffold's geometry for any 'jumps' to detect |
| // changes in the size and rotation of the fab. |
| void setupListener(WidgetTester tester) { |
| // Measure the delta in width and height of the fab, and check that it never grows |
| // by more than the expected maximum deltas. |
| void check() { |
| geometry = listenerState?.cache.value; |
| final Size? currentRect = geometry?.floatingActionButtonArea?.size; |
| // Measure the delta in width and height of the rect, and check that |
| // it never grows by more than a safe amount. |
| if (previousRect != null && currentRect != null) { |
| final double deltaWidth = currentRect.width - previousRect!.width; |
| final double deltaHeight = currentRect.height - previousRect!.height; |
| expect( |
| deltaWidth.abs(), |
| lessThanOrEqualTo(maxDeltaWidth), |
| reason: "The Floating Action Button's width should not change " |
| 'faster than $maxDeltaWidth per animation step.\n' |
| 'Previous rect: $previousRect, current rect: $currentRect', |
| ); |
| expect( |
| deltaHeight.abs(), |
| lessThanOrEqualTo(maxDeltaHeight), |
| reason: "The Floating Action Button's width should not change " |
| 'faster than $maxDeltaHeight per animation step.\n' |
| 'Previous rect: $previousRect, current rect: $currentRect', |
| ); |
| } |
| previousRect = currentRect; |
| |
| // Measure the delta in rotation. |
| // Check that it never grows by more than a safe amount. |
| // |
| // Note that there may be multiple transitions all active at |
| // the same time. We are concerned only with the closest one. |
| final Iterable<RotationTransition> rotationTransitions = tester.widgetList( |
| find.byType(RotationTransition), |
| ); |
| final Iterable<double> currentRotations = rotationTransitions.map( |
| (RotationTransition t) => t.turns.value); |
| |
| if (previousRotations != null && previousRotations!.isNotEmpty |
| && currentRotations != null && currentRotations.isNotEmpty |
| && previousRect != null && currentRect != null) { |
| final List<double> deltas = <double>[]; |
| for (final double currentRotation in currentRotations) { |
| late double minDelta; |
| for (final double previousRotation in previousRotations!) { |
| final double delta = (previousRotation - currentRotation).abs(); |
| minDelta = delta; |
| minDelta = min(delta, minDelta); |
| } |
| deltas.add(minDelta); |
| } |
| |
| if (deltas.where((double delta) => delta < maxDeltaRotation).isEmpty) { |
| fail("The Floating Action Button's rotation should not change " |
| 'faster than $maxDeltaRotation per animation step.\n' |
| 'Detected deltas were: $deltas\n' |
| 'Previous values: $previousRotations, current values: $currentRotations\n' |
| 'Previous rect: $previousRect, current rect: $currentRect',); |
| } |
| } |
| previousRotations = currentRotations; |
| } |
| |
| listenerState = tester.state(find.byType(_GeometryListener)); |
| listenerState!.geometryListenable!.addListener(check); |
| } |
| |
| setUp(() { |
| // We create the geometry listener here, but it can only be set up |
| // after it is pumped into the widget tree and a tester is |
| // available. |
| geometryListener = _GeometryListener(); |
| geometry = null; |
| listenerState = null; |
| previousRect = null; |
| previousRotations = null; |
| }); |
| |
| testWidgets('moving the fab to centerFloat', (WidgetTester tester) async { |
| // Create a scaffold with the fab at endFloat |
| await tester.pumpWidget(buildFrame(location: FloatingActionButtonLocation.endFloat, listener: geometryListener)); |
| setupListener(tester); |
| |
| // Move the fab to centerFloat' |
| await tester.pumpWidget(buildFrame(location: FloatingActionButtonLocation.centerFloat, listener: geometryListener)); |
| await tester.pumpAndSettle(); |
| }); |
| |
| testWidgets('interrupting motion towards the StartTop location.', (WidgetTester tester) async { |
| await tester.pumpWidget(buildFrame(location: FloatingActionButtonLocation.centerFloat, listener: geometryListener)); |
| setupListener(tester); |
| |
| // Move the fab to the top start after creating the fab. |
| await tester.pumpWidget(buildFrame(location: const _StartTopFloatingActionButtonLocation(), listener: geometryListener)); |
| await tester.pump(kFloatingActionButtonSegue ~/ 2); |
| |
| // Interrupt motion to move to the end float |
| await tester.pumpWidget(buildFrame(location: FloatingActionButtonLocation.endFloat, listener: geometryListener)); |
| await tester.pumpAndSettle(); |
| }); |
| |
| testWidgets('interrupting entrance to remove the fab.', (WidgetTester tester) async { |
| await tester.pumpWidget(buildFrame(fab: null, location: FloatingActionButtonLocation.centerFloat, listener: geometryListener)); |
| setupListener(tester); |
| |
| // Animate the fab in. |
| await tester.pumpWidget(buildFrame(location: FloatingActionButtonLocation.endFloat, listener: geometryListener)); |
| await tester.pump(kFloatingActionButtonSegue ~/ 2); |
| |
| // Remove the fab. |
| await tester.pumpWidget( |
| buildFrame( |
| fab: null, |
| location: FloatingActionButtonLocation.endFloat, |
| listener: geometryListener, |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| }); |
| |
| testWidgets('interrupting entrance of a new fab.', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| buildFrame( |
| fab: null, |
| location: FloatingActionButtonLocation.endFloat, |
| listener: geometryListener, |
| ), |
| ); |
| setupListener(tester); |
| |
| // Bring in a new fab. |
| await tester.pumpWidget(buildFrame(location: FloatingActionButtonLocation.centerFloat, listener: geometryListener)); |
| await tester.pump(kFloatingActionButtonSegue ~/ 2); |
| |
| // Interrupt motion to move the fab. |
| await tester.pumpWidget( |
| buildFrame( |
| location: FloatingActionButtonLocation.endFloat, |
| listener: geometryListener, |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| }); |
| }); |
| }); |
| |
| testWidgets('Docked floating action button locations', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| buildFrame( |
| location: FloatingActionButtonLocation.endDocked, |
| bab: const SizedBox(height: 100.0), |
| viewInsets: EdgeInsets.zero, |
| ), |
| ); |
| |
| // Scaffold 800x600, FAB is 56x56, BAB is 800x100, FAB's center is |
| // at the top of the BAB. |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 500.0)); |
| |
| await tester.pumpWidget( |
| buildFrame( |
| location: FloatingActionButtonLocation.centerDocked, |
| bab: const SizedBox(height: 100.0), |
| viewInsets: EdgeInsets.zero, |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(400.0, 500.0)); |
| |
| |
| await tester.pumpWidget( |
| buildFrame( |
| location: FloatingActionButtonLocation.endDocked, |
| bab: const SizedBox(height: 100.0), |
| viewInsets: EdgeInsets.zero, |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 500.0)); |
| }); |
| |
| testWidgets('Docked floating action button locations: no BAB, small BAB', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| buildFrame( |
| location: FloatingActionButtonLocation.endDocked, |
| viewInsets: EdgeInsets.zero, |
| ), |
| ); |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 572.0)); |
| |
| await tester.pumpWidget( |
| buildFrame( |
| location: FloatingActionButtonLocation.endDocked, |
| bab: const SizedBox(height: 16.0), |
| viewInsets: EdgeInsets.zero, |
| ), |
| ); |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 572.0)); |
| }); |
| |
| testWidgets('Mini-start-top floating action button location', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Scaffold( |
| appBar: AppBar(), |
| floatingActionButton: FloatingActionButton(onPressed: () { }, mini: true), |
| floatingActionButtonLocation: FloatingActionButtonLocation.miniStartTop, |
| body: Column( |
| children: const <Widget>[ |
| ListTile( |
| leading: CircleAvatar(), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ); |
| expect(tester.getCenter(find.byType(FloatingActionButton)).dx, tester.getCenter(find.byType(CircleAvatar)).dx); |
| expect(tester.getCenter(find.byType(FloatingActionButton)).dy, kToolbarHeight); |
| }); |
| |
| testWidgets('Start-top floating action button location LTR', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Scaffold( |
| appBar: AppBar(), |
| floatingActionButton: const FloatingActionButton(onPressed: null), |
| floatingActionButtonLocation: FloatingActionButtonLocation.startTop, |
| ), |
| ), |
| ); |
| expect(tester.getRect(find.byType(FloatingActionButton)), rectMoreOrLessEquals(const Rect.fromLTWH(16.0, 28.0, 56.0, 56.0))); |
| }); |
| |
| testWidgets('End-top floating action button location RTL', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Directionality( |
| textDirection: TextDirection.rtl, |
| child: Scaffold( |
| appBar: AppBar(), |
| floatingActionButton: const FloatingActionButton(onPressed: null), |
| floatingActionButtonLocation: FloatingActionButtonLocation.endTop, |
| ), |
| ), |
| ), |
| ); |
| expect(tester.getRect(find.byType(FloatingActionButton)), rectMoreOrLessEquals(const Rect.fromLTWH(16.0, 28.0, 56.0, 56.0))); |
| }); |
| |
| testWidgets('Start-top floating action button location RTL', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Directionality( |
| textDirection: TextDirection.rtl, |
| child: Scaffold( |
| appBar: AppBar(), |
| floatingActionButton: const FloatingActionButton(onPressed: null), |
| floatingActionButtonLocation: FloatingActionButtonLocation.startTop, |
| ), |
| ), |
| ), |
| ); |
| expect(tester.getRect(find.byType(FloatingActionButton)), rectMoreOrLessEquals(const Rect.fromLTWH(800.0 - 56.0 - 16.0, 28.0, 56.0, 56.0))); |
| }); |
| |
| testWidgets('End-top floating action button location LTR', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Scaffold( |
| appBar: AppBar(), |
| floatingActionButton: const FloatingActionButton(onPressed: null), |
| floatingActionButtonLocation: FloatingActionButtonLocation.endTop, |
| ), |
| ), |
| ); |
| expect(tester.getRect(find.byType(FloatingActionButton)), rectMoreOrLessEquals(const Rect.fromLTWH(800.0 - 56.0 - 16.0, 28.0, 56.0, 56.0))); |
| }); |
| |
| group('New Floating Action Button Locations', () { |
| testWidgets('startTop', (WidgetTester tester) async { |
| await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.startTop)); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_leftOffsetX, _topOffsetY)); |
| }); |
| |
| testWidgets('centerTop', (WidgetTester tester) async { |
| await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.centerTop)); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_centerOffsetX, _topOffsetY)); |
| }); |
| |
| testWidgets('endTop', (WidgetTester tester) async { |
| await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.endTop)); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_rightOffsetX, _topOffsetY)); |
| }); |
| |
| testWidgets('startFloat', (WidgetTester tester) async { |
| await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.startFloat)); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_leftOffsetX, _floatOffsetY)); |
| }); |
| |
| testWidgets('centerFloat', (WidgetTester tester) async { |
| await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.centerFloat)); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_centerOffsetX, _floatOffsetY)); |
| }); |
| |
| testWidgets('endFloat', (WidgetTester tester) async { |
| await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.endFloat)); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_rightOffsetX, _floatOffsetY)); |
| }); |
| |
| testWidgets('startDocked', (WidgetTester tester) async { |
| await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.startDocked)); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_leftOffsetX, _dockedOffsetY)); |
| }); |
| |
| testWidgets('centerDocked', (WidgetTester tester) async { |
| await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.centerDocked)); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_centerOffsetX, _dockedOffsetY)); |
| }); |
| |
| testWidgets('endDocked', (WidgetTester tester) async { |
| await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.endDocked)); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_rightOffsetX, _dockedOffsetY)); |
| }); |
| |
| testWidgets('miniStartTop', (WidgetTester tester) async { |
| await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.miniStartTop)); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_miniLeftOffsetX, _topOffsetY)); |
| }); |
| |
| testWidgets('miniEndTop', (WidgetTester tester) async { |
| await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.miniEndTop)); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_miniRightOffsetX, _topOffsetY)); |
| }); |
| |
| testWidgets('miniStartFloat', (WidgetTester tester) async { |
| await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.miniStartFloat)); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_miniLeftOffsetX, _miniFloatOffsetY)); |
| }); |
| |
| testWidgets('miniCenterFloat', (WidgetTester tester) async { |
| await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.miniCenterFloat)); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_centerOffsetX, _miniFloatOffsetY)); |
| }); |
| |
| testWidgets('miniEndFloat', (WidgetTester tester) async { |
| await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.miniEndFloat)); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_miniRightOffsetX, _miniFloatOffsetY)); |
| }); |
| |
| testWidgets('miniStartDocked', (WidgetTester tester) async { |
| await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.miniStartDocked)); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_miniLeftOffsetX, _dockedOffsetY)); |
| }); |
| |
| testWidgets('miniEndDocked', (WidgetTester tester) async { |
| await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.miniEndDocked)); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_miniRightOffsetX, _dockedOffsetY)); |
| }); |
| |
| // Test a few RTL cases. |
| |
| testWidgets('endTop, RTL', (WidgetTester tester) async { |
| await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.endTop, textDirection: TextDirection.rtl)); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_leftOffsetX, _topOffsetY)); |
| }); |
| |
| testWidgets('miniStartFloat, RTL', (WidgetTester tester) async { |
| await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.miniStartFloat, textDirection: TextDirection.rtl)); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_miniRightOffsetX, _miniFloatOffsetY)); |
| }); |
| }); |
| |
| group('Custom Floating Action Button Locations', () { |
| testWidgets('Almost end float', (WidgetTester tester) async { |
| await tester.pumpWidget(_singleFabScaffold(_AlmostEndFloatFabLocation())); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_rightOffsetX - 50, _floatOffsetY)); |
| }); |
| |
| testWidgets('Almost end float, RTL', (WidgetTester tester) async { |
| await tester.pumpWidget(_singleFabScaffold(_AlmostEndFloatFabLocation(), textDirection: TextDirection.rtl)); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_leftOffsetX + 50, _floatOffsetY)); |
| }); |
| |
| testWidgets('Quarter end top', (WidgetTester tester) async { |
| await tester.pumpWidget(_singleFabScaffold(_QuarterEndTopFabLocation())); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_rightOffsetX * 0.75 + _leftOffsetX * 0.25, _topOffsetY)); |
| }); |
| |
| testWidgets('Quarter end top, RTL', (WidgetTester tester) async { |
| await tester.pumpWidget(_singleFabScaffold(_QuarterEndTopFabLocation(), textDirection: TextDirection.rtl)); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_leftOffsetX * 0.75 + _rightOffsetX * 0.25, _topOffsetY)); |
| }); |
| }); |
| |
| group('Moves involving new locations', () { |
| testWidgets('Moves between new locations and new locations', (WidgetTester tester) async { |
| await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.centerTop)); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_centerOffsetX, _topOffsetY)); |
| |
| await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.startFloat)); |
| |
| expect(tester.binding.transientCallbackCount, greaterThan(0)); |
| await tester.pumpAndSettle(); |
| expect(tester.binding.transientCallbackCount, 0); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_leftOffsetX, _floatOffsetY)); |
| |
| await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.startDocked)); |
| |
| expect(tester.binding.transientCallbackCount, greaterThan(0)); |
| await tester.pumpAndSettle(); |
| expect(tester.binding.transientCallbackCount, 0); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_leftOffsetX, _dockedOffsetY)); |
| }); |
| |
| testWidgets('Moves between new locations and old locations', (WidgetTester tester) async { |
| await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.endDocked)); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_rightOffsetX, _dockedOffsetY)); |
| |
| await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.startDocked)); |
| |
| expect(tester.binding.transientCallbackCount, greaterThan(0)); |
| await tester.pumpAndSettle(); |
| expect(tester.binding.transientCallbackCount, 0); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_leftOffsetX, _dockedOffsetY)); |
| |
| await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.centerFloat)); |
| |
| expect(tester.binding.transientCallbackCount, greaterThan(0)); |
| await tester.pumpAndSettle(); |
| expect(tester.binding.transientCallbackCount, 0); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_centerOffsetX, _floatOffsetY)); |
| |
| await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.centerTop)); |
| |
| expect(tester.binding.transientCallbackCount, greaterThan(0)); |
| await tester.pumpAndSettle(); |
| expect(tester.binding.transientCallbackCount, 0); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_centerOffsetX, _topOffsetY)); |
| }); |
| |
| testWidgets('Moves between new locations and old locations with custom animator', (WidgetTester tester) async { |
| final FloatingActionButtonAnimator animator = _LinearMovementFabAnimator(); |
| const Offset begin = Offset(_centerOffsetX, _topOffsetY); |
| const Offset end = Offset(_rightOffsetX - 50, _floatOffsetY); |
| |
| final Duration animationDuration = kFloatingActionButtonSegue * 2; |
| |
| await tester.pumpWidget(_singleFabScaffold( |
| FloatingActionButtonLocation.centerTop, |
| animator: animator, |
| )); |
| |
| expect(find.byType(FloatingActionButton), findsOneWidget); |
| |
| expect(tester.binding.transientCallbackCount, 0); |
| |
| await tester.pumpWidget(_singleFabScaffold( |
| _AlmostEndFloatFabLocation(), |
| animator: animator, |
| )); |
| |
| expect(tester.binding.transientCallbackCount, greaterThan(0)); |
| |
| await tester.pump(animationDuration * 0.25); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), offsetMoreOrLessEquals(begin * 0.75 + end * 0.25)); |
| |
| await tester.pump(animationDuration * 0.25); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), offsetMoreOrLessEquals(begin * 0.5 + end * 0.5)); |
| |
| await tester.pump(animationDuration * 0.25); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), offsetMoreOrLessEquals(begin * 0.25 + end * 0.75)); |
| |
| await tester.pumpAndSettle(); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), end); |
| |
| 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), |
| label: '0', |
| ), |
| BottomNavigationBarItem( |
| icon: Icon(Icons.star_border), |
| label: '1', |
| ), |
| ], |
| currentIndex: 0, |
| ) : null, |
| floatingActionButtonLocation: location, |
| floatingActionButton: Builder( |
| builder: (BuildContext context) { |
| return FloatingActionButton( |
| onPressed: () { |
| ScaffoldMessenger.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 BottomNavigationBar |
| // - with BottomNavigationBar and resizeToAvoidBottomInset: false |
| // - with BottomNavigationBar & BottomSheet |
| // - with BottomNavigationBar & BottomSheet, resizeToAvoidBottomInset: false |
| // - with BottomSheet |
| // - with BottomSheet and resizeToAvoidBottomInset: false |
| // - with SnackBar |
| Future<void> _runFloatTests( |
| WidgetTester tester, |
| FloatingActionButtonLocation location, { |
| required Rect defaultRect, |
| required Rect bottomNavigationBarRect, |
| required Rect bottomSheetRect, |
| required Rect snackBarRect, |
| bool mini = false, |
| }) async { |
| const double keyboardHeight = 200.0; |
| const double viewPadding = 50.0; |
| final Key floatingActionButton = UniqueKey(); |
| const double bottomNavHeight = 99.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 - 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), |
| ); |
| |
| // 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.translate( |
| 0.0, |
| -bottomNavHeight, |
| )), |
| ); |
| // 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, |
| )), |
| ); |
| |
| // 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.translate( |
| 0.0, |
| -bottomNavHeight, |
| )), |
| ); |
| |
| // 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); |
| // Positioned relative to BottomNavigationBar |
| const Rect bottomNavigationBarRect = Rect.fromLTRB(16.0, 429.0, 72.0, 485.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: defaultRect, |
| bottomNavigationBarRect: bottomNavigationBarRect, |
| bottomSheetRect: bottomSheetRect, |
| snackBarRect: snackBarRect, |
| ); |
| }); |
| |
| testWidgets('miniStartFloat', (WidgetTester tester) async { |
| const Rect defaultRect = Rect.fromLTRB(12.0, 506.0, 60.0, 554.0); |
| // Positioned relative to BottomNavigationBar |
| const Rect bottomNavigationBarRect = Rect.fromLTRB(12.0, 441.0, 60.0, 489.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: defaultRect, |
| bottomNavigationBarRect: bottomNavigationBarRect, |
| bottomSheetRect: bottomSheetRect, |
| snackBarRect: snackBarRect, |
| mini: true, |
| ); |
| }); |
| |
| testWidgets('centerFloat', (WidgetTester tester) async { |
| const Rect defaultRect = Rect.fromLTRB(372.0, 494.0, 428.0, 550.0); |
| // Positioned relative to BottomNavigationBar |
| const Rect bottomNavigationBarRect = Rect.fromLTRB(372.0, 429.0, 428.0, 485.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: defaultRect, |
| bottomNavigationBarRect: bottomNavigationBarRect, |
| bottomSheetRect: bottomSheetRect, |
| snackBarRect: snackBarRect, |
| ); |
| }); |
| |
| testWidgets('miniCenterFloat', (WidgetTester tester) async { |
| const Rect defaultRect = Rect.fromLTRB(376.0, 506.0, 424.0, 554.0); |
| // Positioned relative to BottomNavigationBar |
| const Rect bottomNavigationBarRect = Rect.fromLTRB(376.0, 441.0, 424.0, 489.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: defaultRect, |
| bottomNavigationBarRect: bottomNavigationBarRect, |
| bottomSheetRect: bottomSheetRect, |
| snackBarRect: snackBarRect, |
| mini: true, |
| ); |
| }); |
| |
| testWidgets('endFloat', (WidgetTester tester) async { |
| const Rect defaultRect = Rect.fromLTRB(728.0, 494.0, 784.0, 550.0); |
| // Positioned relative to BottomNavigationBar |
| const Rect bottomNavigationBarRect = Rect.fromLTRB(728.0, 429.0, 784.0, 485.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: defaultRect, |
| bottomNavigationBarRect: bottomNavigationBarRect, |
| bottomSheetRect: bottomSheetRect, |
| snackBarRect: snackBarRect, |
| ); |
| }); |
| |
| testWidgets('miniEndFloat', (WidgetTester tester) async { |
| const Rect defaultRect = Rect.fromLTRB(740.0, 506.0, 788.0, 554.0); |
| // Positioned relative to BottomNavigationBar |
| const Rect bottomNavigationBarRect = Rect.fromLTRB(740.0, 441.0, 788.0, 489.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: defaultRect, |
| bottomNavigationBarRect: bottomNavigationBarRect, |
| bottomSheetRect: bottomSheetRect, |
| snackBarRect: 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, { |
| required Rect defaultRect, |
| required Rect bottomNavigationBarRect, |
| required Rect bottomSheetRect, |
| required 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 bottomNavigationBarRect = 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: defaultRect, |
| bottomNavigationBarRect: bottomNavigationBarRect, |
| bottomSheetRect: bottomSheetRect, |
| snackBarRect: 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 bottomNavigationBarRect = 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: defaultRect, |
| bottomNavigationBarRect: bottomNavigationBarRect, |
| bottomSheetRect: bottomSheetRect, |
| snackBarRect: 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 bottomNavigationBarRect = 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: defaultRect, |
| bottomNavigationBarRect: bottomNavigationBarRect, |
| bottomSheetRect: bottomSheetRect, |
| snackBarRect: 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 bottomNavigationBarRect = 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: defaultRect, |
| bottomNavigationBarRect: bottomNavigationBarRect, |
| bottomSheetRect: bottomSheetRect, |
| snackBarRect: 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 bottomNavigationBarRect = 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: defaultRect, |
| bottomNavigationBarRect: bottomNavigationBarRect, |
| bottomSheetRect: bottomSheetRect, |
| snackBarRect: 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 bottomNavigationBarRect = 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: defaultRect, |
| bottomNavigationBarRect: bottomNavigationBarRect, |
| bottomSheetRect: bottomSheetRect, |
| snackBarRect: snackBarRect, |
| mini: true, |
| ); |
| }); |
| |
| // Test top locations, for each (6): |
| // - Default |
| // - with an AppBar |
| Future<void> _runTopTests( |
| WidgetTester tester, |
| FloatingActionButtonLocation location, { |
| required Rect defaultRect, |
| required 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: defaultRect, |
| appBarRect: 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: defaultRect, |
| appBarRect: 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: defaultRect, |
| appBarRect: 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: defaultRect, |
| appBarRect: 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: defaultRect, |
| appBarRect: 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: defaultRect, |
| appBarRect: appBarRect, |
| mini: true, |
| ); |
| }); |
| }); |
| } |
| |
| class _GeometryListener extends StatefulWidget { |
| @override |
| State createState() => _GeometryListenerState(); |
| } |
| |
| class _GeometryListenerState extends State<_GeometryListener> { |
| @override |
| Widget build(BuildContext context) { |
| return CustomPaint( |
| painter: cache |
| ); |
| } |
| |
| int numNotifications = 0; |
| ValueListenable<ScaffoldGeometry>? geometryListenable; |
| late _GeometryCachePainter cache; |
| |
| @override |
| void didChangeDependencies() { |
| super.didChangeDependencies(); |
| final ValueListenable<ScaffoldGeometry> newListenable = Scaffold.geometryOf(context); |
| if (geometryListenable == newListenable) |
| return; |
| |
| if (geometryListenable != null) |
| geometryListenable!.removeListener(onGeometryChanged); |
| |
| geometryListenable = newListenable; |
| geometryListenable!.addListener(onGeometryChanged); |
| cache = _GeometryCachePainter(geometryListenable!); |
| } |
| |
| void onGeometryChanged() { |
| numNotifications += 1; |
| } |
| } |
| |
| const double _leftOffsetX = 44.0; |
| const double _centerOffsetX = 400.0; |
| const double _rightOffsetX = 756.0; |
| const double _miniLeftOffsetX = _leftOffsetX - kMiniButtonOffsetAdjustment; |
| const double _miniRightOffsetX = _rightOffsetX + kMiniButtonOffsetAdjustment; |
| |
| const double _topOffsetY = 56.0; |
| const double _floatOffsetY = 500.0; |
| const double _dockedOffsetY = 544.0; |
| const double _miniFloatOffsetY = _floatOffsetY + kMiniButtonOffsetAdjustment; |
| |
| Widget _singleFabScaffold( |
| FloatingActionButtonLocation location, |
| { |
| FloatingActionButtonAnimator? animator, |
| bool mini = false, |
| TextDirection textDirection = TextDirection.ltr, |
| } |
| ) { |
| return MaterialApp( |
| home: Directionality( |
| textDirection: textDirection, |
| child: Scaffold( |
| appBar: AppBar( |
| title: const Text('FloatingActionButtonLocation Test.'), |
| ), |
| bottomNavigationBar: BottomNavigationBar( |
| items: const <BottomNavigationBarItem>[ |
| BottomNavigationBarItem( |
| icon: Icon(Icons.home), |
| label: 'Home', |
| ), |
| BottomNavigationBarItem( |
| icon: Icon(Icons.school), |
| label: 'School', |
| ), |
| ], |
| ), |
| floatingActionButton: FloatingActionButton( |
| onPressed: () {}, |
| child: const Icon(Icons.beach_access), |
| mini: mini, |
| ), |
| floatingActionButtonLocation: location, |
| floatingActionButtonAnimator: animator, |
| ), |
| ), |
| ); |
| } |
| |
| // The Scaffold.geometryOf() value is only available at paint time. |
| // To fetch it for the tests we implement this CustomPainter that just |
| // caches the ScaffoldGeometry value in its paint method. |
| class _GeometryCachePainter extends CustomPainter { |
| _GeometryCachePainter(this.geometryListenable) : super(repaint: geometryListenable); |
| |
| final ValueListenable<ScaffoldGeometry> geometryListenable; |
| |
| late ScaffoldGeometry value; |
| @override |
| void paint(Canvas canvas, Size size) { |
| value = geometryListenable.value; |
| } |
| |
| @override |
| bool shouldRepaint(_GeometryCachePainter oldDelegate) { |
| return true; |
| } |
| } |
| |
| Widget buildFrame({ |
| FloatingActionButton? fab = const FloatingActionButton( |
| onPressed: null, |
| child: Text('1'), |
| ), |
| FloatingActionButtonLocation? location, |
| _GeometryListener? listener, |
| TextDirection textDirection = TextDirection.ltr, |
| EdgeInsets viewInsets = const EdgeInsets.only(bottom: 200.0), |
| Widget? bab, |
| }) { |
| return Localizations( |
| locale: const Locale('en', 'us'), |
| delegates: const <LocalizationsDelegate<dynamic>>[ |
| DefaultWidgetsLocalizations.delegate, |
| DefaultMaterialLocalizations.delegate, |
| ], |
| child: Directionality( |
| textDirection: textDirection, |
| child: MediaQuery( |
| data: MediaQueryData(viewInsets: viewInsets), |
| child: Scaffold( |
| appBar: AppBar(title: const Text('FabLocation Test')), |
| floatingActionButtonLocation: location, |
| floatingActionButton: fab, |
| bottomNavigationBar: bab, |
| body: listener, |
| ), |
| ), |
| )); |
| } |
| |
| class _StartTopFloatingActionButtonLocation extends FloatingActionButtonLocation { |
| const _StartTopFloatingActionButtonLocation(); |
| |
| @override |
| Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) { |
| double fabX; |
| assert(scaffoldGeometry.textDirection != null); |
| switch (scaffoldGeometry.textDirection) { |
| case TextDirection.rtl: |
| final double startPadding = kFloatingActionButtonMargin + scaffoldGeometry.minInsets.right; |
| fabX = scaffoldGeometry.scaffoldSize.width - scaffoldGeometry.floatingActionButtonSize.width - startPadding; |
| break; |
| case TextDirection.ltr: |
| final double startPadding = kFloatingActionButtonMargin + scaffoldGeometry.minInsets.left; |
| fabX = startPadding; |
| break; |
| } |
| final double fabY = scaffoldGeometry.contentTop - (scaffoldGeometry.floatingActionButtonSize.height / 2.0); |
| return Offset(fabX, fabY); |
| } |
| } |
| |
| class _AlmostEndFloatFabLocation extends StandardFabLocation |
| with FabEndOffsetX, FabFloatOffsetY { |
| @override |
| double getOffsetX (ScaffoldPrelayoutGeometry scaffoldGeometry, double adjustment) { |
| final double directionalAdjustment = |
| scaffoldGeometry.textDirection == TextDirection.ltr ? -50.0 : 50.0; |
| return super.getOffsetX(scaffoldGeometry, adjustment) + directionalAdjustment; |
| } |
| } |
| |
| class _QuarterEndTopFabLocation extends StandardFabLocation |
| with FabEndOffsetX, FabTopOffsetY { |
| @override |
| double getOffsetX (ScaffoldPrelayoutGeometry scaffoldGeometry, double adjustment) { |
| return super.getOffsetX(scaffoldGeometry, adjustment) * 0.75 |
| + (FloatingActionButtonLocation.startFloat as StandardFabLocation) |
| .getOffsetX(scaffoldGeometry, adjustment) * 0.25; |
| } |
| } |
| |
| class _LinearMovementFabAnimator extends FloatingActionButtonAnimator { |
| @override |
| Offset getOffset({required Offset begin, required Offset end, required double progress}) { |
| return Offset.lerp(begin, end, progress)!; |
| } |
| |
| @override |
| Animation<double> getScaleAnimation({required Animation<double> parent}) { |
| return const AlwaysStoppedAnimation<double>(1.0); |
| } |
| |
| @override |
| Animation<double> getRotationAnimation({required Animation<double> parent}) { |
| return const AlwaysStoppedAnimation<double>(1.0); |
| } |
| } |