blob: 99ade3bc7c610393a16c76774ec15d6e97a2aa42 [file] [log] [blame]
// 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.
// @dart = 2.8
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import '../widgets/semantics_tester.dart';
void main() {
MockNavigatorObserver navigatorObserver;
setUp(() {
navigatorObserver = MockNavigatorObserver();
});
testWidgets(
'Throws FlutterError with correct message when route builder returns null',
(WidgetTester tester) async {
await tester.pumpWidget(
const CupertinoApp(
home: Placeholder(),
),
);
tester.state<NavigatorState>(find.byType(Navigator)).push(
CupertinoPageRoute<void>(
title: 'Route 1',
builder: (_) => null,
),
);
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
final dynamic error = tester.takeException();
expect(error, isFlutterError);
expect(error.toStringDeep(), equalsIgnoringHashCodes(
'FlutterError\n'
' The builder for route "null" returned null.\n'
' Route builders must never return null.\n'
));
});
testWidgets('Middle auto-populates with title', (WidgetTester tester) async {
await tester.pumpWidget(
const CupertinoApp(
home: Placeholder(),
),
);
tester.state<NavigatorState>(find.byType(Navigator)).push(
CupertinoPageRoute<void>(
title: 'An iPod',
builder: (BuildContext context) {
return const CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Placeholder(),
);
},
),
);
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
// There should be a Text widget with the title in the nav bar even though
// we didn't specify anything in the nav bar constructor.
expect(find.widgetWithText(CupertinoNavigationBar, 'An iPod'), findsOneWidget);
// As a title, it should also be centered.
expect(tester.getCenter(find.text('An iPod')).dx, 400.0);
});
testWidgets('Large title auto-populates with title', (WidgetTester tester) async {
await tester.pumpWidget(
const CupertinoApp(
home: Placeholder(),
),
);
tester.state<NavigatorState>(find.byType(Navigator)).push(
CupertinoPageRoute<void>(
title: 'An iPod',
builder: (BuildContext context) {
return const CupertinoPageScaffold(
child: CustomScrollView(
slivers: <Widget>[
CupertinoSliverNavigationBar(),
],
),
);
},
),
);
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
// There should be 2 Text widget with the title in the nav bar. One in the
// large title position and one in the middle position (though the middle
// position Text is initially invisible while the sliver is expanded).
expect(
find.widgetWithText(CupertinoSliverNavigationBar, 'An iPod'),
findsNWidgets(2),
);
final List<Element> titles = tester.elementList(find.text('An iPod'))
.toList()
..sort((Element a, Element b) {
final RenderParagraph aParagraph = a.renderObject as RenderParagraph;
final RenderParagraph bParagraph = b.renderObject as RenderParagraph;
return aParagraph.text.style.fontSize.compareTo(
bParagraph.text.style.fontSize
);
});
final Iterable<double> opacities = titles.map<double>((Element element) {
final RenderAnimatedOpacity renderOpacity =
element.findAncestorRenderObjectOfType<RenderAnimatedOpacity>();
return renderOpacity.opacity.value;
});
expect(opacities, <double> [
0.0, // Initially the smaller font title is invisible.
1.0, // The larger font title is visible.
]);
// Check that the large font title is at the right spot.
expect(
tester.getTopLeft(find.byWidget(titles[1].widget)),
const Offset(16.0, 54.0),
);
// The smaller, initially invisible title, should still be positioned in the
// center.
expect(tester.getCenter(find.byWidget(titles[0].widget)).dx, 400.0);
});
testWidgets('Leading auto-populates with back button with previous title', (WidgetTester tester) async {
await tester.pumpWidget(
const CupertinoApp(
home: Placeholder(),
),
);
tester.state<NavigatorState>(find.byType(Navigator)).push(
CupertinoPageRoute<void>(
title: 'An iPod',
builder: (BuildContext context) {
return const CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Placeholder(),
);
},
),
);
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
tester.state<NavigatorState>(find.byType(Navigator)).push(
CupertinoPageRoute<void>(
title: 'A Phone',
builder: (BuildContext context) {
return const CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Placeholder(),
);
},
),
);
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
expect(find.widgetWithText(CupertinoNavigationBar, 'A Phone'), findsOneWidget);
expect(tester.getCenter(find.text('A Phone')).dx, 400.0);
// Also shows the previous page's title next to the back button.
expect(find.widgetWithText(CupertinoButton, 'An iPod'), findsOneWidget);
// 2 paddings + 1 ahem character at font size 34.0.
expect(tester.getTopLeft(find.text('An iPod')).dx, 8.0 + 34.0 + 6.0);
});
testWidgets('Previous title is correct on first transition frame', (WidgetTester tester) async {
await tester.pumpWidget(
const CupertinoApp(
home: Placeholder(),
),
);
tester.state<NavigatorState>(find.byType(Navigator)).push(
CupertinoPageRoute<void>(
title: 'An iPod',
builder: (BuildContext context) {
return const CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Placeholder(),
);
},
),
);
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
tester.state<NavigatorState>(find.byType(Navigator)).push(
CupertinoPageRoute<void>(
title: 'A Phone',
builder: (BuildContext context) {
return const CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Placeholder(),
);
},
),
);
// Trigger the route push
await tester.pump();
// Draw the first frame.
await tester.pump();
// Also shows the previous page's title next to the back button.
expect(find.widgetWithText(CupertinoButton, 'An iPod'), findsOneWidget);
});
testWidgets('Previous title stays up to date with changing routes', (WidgetTester tester) async {
await tester.pumpWidget(
const CupertinoApp(
home: Placeholder(),
),
);
final CupertinoPageRoute<void> route2 = CupertinoPageRoute<void>(
title: 'An iPod',
builder: (BuildContext context) {
return const CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Placeholder(),
);
},
);
final CupertinoPageRoute<void> route3 = CupertinoPageRoute<void>(
title: 'A Phone',
builder: (BuildContext context) {
return const CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Placeholder(),
);
},
);
tester.state<NavigatorState>(find.byType(Navigator)).push(route2);
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
tester.state<NavigatorState>(find.byType(Navigator)).push(route3);
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
tester.state<NavigatorState>(find.byType(Navigator)).replace(
oldRoute: route2,
newRoute: CupertinoPageRoute<void>(
title: 'An Internet communicator',
builder: (BuildContext context) {
return const CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Placeholder(),
);
},
),
);
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
expect(find.widgetWithText(CupertinoNavigationBar, 'A Phone'), findsOneWidget);
expect(tester.getCenter(find.text('A Phone')).dx, 400.0);
// After swapping the route behind the top one, the previous label changes
// from An iPod to Back (since An Internet communicator is too long to
// fit in the back button).
expect(find.widgetWithText(CupertinoButton, 'Back'), findsOneWidget);
expect(tester.getTopLeft(find.text('Back')).dx, 8.0 + 34.0 + 6.0);
});
testWidgets('Back swipe dismiss interrupted by route push', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/28728
final GlobalKey scaffoldKey = GlobalKey();
await tester.pumpWidget(
CupertinoApp(
home: CupertinoPageScaffold(
key: scaffoldKey,
child: Center(
child: CupertinoButton(
onPressed: () {
Navigator.push<void>(scaffoldKey.currentContext, CupertinoPageRoute<void>(
builder: (BuildContext context) {
return const CupertinoPageScaffold(
child: Center(child: Text('route')),
);
},
));
},
child: const Text('push'),
),
),
),
),
);
// Check the basic iOS back-swipe dismiss transition. Dragging the pushed
// route halfway across the screen will trigger the iOS dismiss animation
await tester.tap(find.text('push'));
await tester.pumpAndSettle();
expect(find.text('route'), findsOneWidget);
expect(find.text('push'), findsNothing);
TestGesture gesture = await tester.startGesture(const Offset(5, 300));
await gesture.moveBy(const Offset(400, 0));
await gesture.up();
await tester.pump();
expect( // The 'route' route has been dragged to the right, halfway across the screen
tester.getTopLeft(find.ancestor(of: find.text('route'), matching: find.byType(CupertinoPageScaffold))),
const Offset(400, 0),
);
expect( // The 'push' route is sliding in from the left.
tester.getTopLeft(find.ancestor(of: find.text('push'), matching: find.byType(CupertinoPageScaffold))).dx,
lessThan(0),
);
await tester.pumpAndSettle();
expect(find.text('push'), findsOneWidget);
expect(
tester.getTopLeft(find.ancestor(of: find.text('push'), matching: find.byType(CupertinoPageScaffold))),
Offset.zero,
);
expect(find.text('route'), findsNothing);
// Run the dismiss animation 60%, which exposes the route "push" button,
// and then press the button.
await tester.tap(find.text('push'));
await tester.pumpAndSettle();
expect(find.text('route'), findsOneWidget);
expect(find.text('push'), findsNothing);
gesture = await tester.startGesture(const Offset(5, 300));
await gesture.moveBy(const Offset(400, 0)); // Drag halfway.
await gesture.up();
// Trigger the snapping animation.
// Since the back swipe drag was brought to >=50% of the screen, it will
// self snap to finish the pop transition as the gesture is lifted.
//
// This drag drop animation is 400ms when dropped exactly halfway
// (800 / [pixel distance remaining], see
// _CupertinoBackGestureController.dragEnd). It follows a curve that is very
// steep initially.
await tester.pump();
expect(
tester.getTopLeft(find.ancestor(of: find.text('route'), matching: find.byType(CupertinoPageScaffold))),
const Offset(400, 0),
);
// Let the dismissing snapping animation go 60%.
await tester.pump(const Duration(milliseconds: 240));
expect(
tester.getTopLeft(find.ancestor(of: find.text('route'), matching: find.byType(CupertinoPageScaffold))).dx,
moreOrLessEquals(798, epsilon: 1),
);
// Use the navigator to push a route instead of tapping the 'push' button.
// The topmost route (the one that's animating away), ignores input while
// the pop is underway because route.navigator.userGestureInProgress.
Navigator.push<void>(scaffoldKey.currentContext, CupertinoPageRoute<void>(
builder: (BuildContext context) {
return const CupertinoPageScaffold(
child: Center(child: Text('route')),
);
},
));
await tester.pumpAndSettle();
expect(find.text('route'), findsOneWidget);
expect(find.text('push'), findsNothing);
expect(
tester.state<NavigatorState>(find.byType(Navigator)).userGestureInProgress,
false,
);
});
testWidgets('Fullscreen route animates correct transform values over time', (WidgetTester tester) async {
await tester.pumpWidget(
CupertinoApp(
home: Builder(
builder: (BuildContext context) {
return CupertinoButton(
child: const Text('Button'),
onPressed: () {
Navigator.push<void>(context, CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (BuildContext context) {
return Column(
children: <Widget>[
const Placeholder(),
CupertinoButton(
child: const Text('Close'),
onPressed: () {
Navigator.pop<void>(context);
},
),
],
);
},
));
},
);
}
),
),
);
// Enter animation.
await tester.tap(find.text('Button'));
await tester.pump();
// We use a higher number of intervals since the animation has to scale the
// entire screen.
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(443.7, epsilon: 0.1));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(291.9, epsilon: 0.1));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(168.2, epsilon: 0.1));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(89.5, epsilon: 0.1));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(48.1, epsilon: 0.1));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(26.1, epsilon: 0.1));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(14.3, epsilon: 0.1));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(7.41, epsilon: 0.1));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(3.0, epsilon: 0.1));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(0.0, epsilon: 0.1));
// Exit animation
await tester.tap(find.text('Close'));
await tester.pump();
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(156.3, epsilon: 0.1));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(308.1, epsilon: 0.1));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(431.7, epsilon: 0.1));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(510.4, epsilon: 0.1));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(551.8, epsilon: 0.1));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(573.8, epsilon: 0.1));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(585.6, epsilon: 0.1));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(592.6, epsilon: 0.1));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(596.9, epsilon: 0.1));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(600.0, epsilon: 0.1));
});
Future<void> testParallax(WidgetTester tester, {@required bool fromFullscreenDialog}) async {
await tester.pumpWidget(
CupertinoApp(
onGenerateRoute: (RouteSettings settings) => CupertinoPageRoute<void>(
fullscreenDialog: fromFullscreenDialog,
settings: settings,
builder: (BuildContext context) {
return Column(
children: <Widget>[
const Placeholder(),
CupertinoButton(
child: const Text('Button'),
onPressed: () {
Navigator.push<void>(context, CupertinoPageRoute<void>(
builder: (BuildContext context) {
return CupertinoButton(
child: const Text('Close'),
onPressed: () {
Navigator.pop<void>(context);
},
);
},
));
},
),
],
);
}
),
),
);
// Enter animation.
await tester.tap(find.text('Button'));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(0.0, epsilon: 0.1));
await tester.pump();
// We use a higher number of intervals since the animation has to scale the
// entire screen.
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-70.0, epsilon: 1.0));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-137.0, epsilon: 1.0));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-192.0, epsilon: 1.0));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-227.0, epsilon: 1.0));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-246.0, epsilon: 1.0));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-255.0, epsilon: 1.0));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-260.0, epsilon: 1.0));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-264.0, epsilon: 1.0));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-266.0, epsilon: 1.0));
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-267.0, epsilon: 1.0));
// Exit animation
await tester.tap(find.text('Button'));
await tester.pump();
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-198.0, epsilon: 1.0));
await tester.pump(const Duration(milliseconds: 360));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-0.0, epsilon: 1.0));
}
testWidgets('CupertinoPageRoute has parallax when non fullscreenDialog route is pushed on top', (WidgetTester tester) async {
await testParallax(tester, fromFullscreenDialog: false);
});
testWidgets('FullscreenDialog CupertinoPageRoute has parallax when non fullscreenDialog route is pushed on top', (WidgetTester tester) async {
await testParallax(tester, fromFullscreenDialog: true);
});
Future<void> testNoParallax(WidgetTester tester, {@required bool fromFullscreenDialog}) async{
await tester.pumpWidget(
CupertinoApp(
onGenerateRoute: (RouteSettings settings) => CupertinoPageRoute<void>(
fullscreenDialog: fromFullscreenDialog,
builder: (BuildContext context) {
return Column(
children: <Widget>[
const Placeholder(),
CupertinoButton(
child: const Text('Button'),
onPressed: () {
Navigator.push<void>(context, CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (BuildContext context) {
return CupertinoButton(
child: const Text('Close'),
onPressed: () {
Navigator.pop<void>(context);
},
);
},
));
},
),
],
);
}
),
),
);
// Enter animation.
await tester.tap(find.text('Button'));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(0.0, epsilon: 0.1));
await tester.pump();
// We use a higher number of intervals since the animation has to scale the
// entire screen.
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
// Exit animation
await tester.tap(find.text('Button'));
await tester.pump();
await tester.pump(const Duration(milliseconds: 40));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
await tester.pump(const Duration(milliseconds: 360));
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
}
testWidgets('CupertinoPageRoute has no parallax when fullscreenDialog route is pushed on top', (WidgetTester tester) async {
await testNoParallax(tester, fromFullscreenDialog: false);
});
testWidgets('FullscreenDialog CupertinoPageRoute has no parallax when fullscreenDialog route is pushed on top', (WidgetTester tester) async {
await testNoParallax(tester, fromFullscreenDialog: true);
});
testWidgets('Animated push/pop is not linear', (WidgetTester tester) async {
await tester.pumpWidget(
const CupertinoApp(
home: Text('1'),
),
);
final CupertinoPageRoute<void> route2 = CupertinoPageRoute<void>(
builder: (BuildContext context) {
return const CupertinoPageScaffold(
child: Text('2'),
);
}
);
tester.state<NavigatorState>(find.byType(Navigator)).push(route2);
// The whole transition is 400ms based on CupertinoPageRoute.transitionDuration.
// Break it up into small chunks.
await tester.pump();
await tester.pump(const Duration(milliseconds: 50));
expect(tester.getTopLeft(find.text('1')).dx, moreOrLessEquals(-87, epsilon: 1));
expect(tester.getTopLeft(find.text('2')).dx, moreOrLessEquals(537, epsilon: 1));
await tester.pump(const Duration(milliseconds: 50));
expect(tester.getTopLeft(find.text('1')).dx, moreOrLessEquals(-166, epsilon: 1));
expect(tester.getTopLeft(find.text('2')).dx, moreOrLessEquals(301, epsilon: 1));
await tester.pump(const Duration(milliseconds: 50));
// Translation slows down as time goes on.
expect(tester.getTopLeft(find.text('1')).dx, moreOrLessEquals(-220, epsilon: 1));
expect(tester.getTopLeft(find.text('2')).dx, moreOrLessEquals(141, epsilon: 1));
// Finish the rest of the animation
await tester.pump(const Duration(milliseconds: 250));
tester.state<NavigatorState>(find.byType(Navigator)).pop();
await tester.pump();
await tester.pump(const Duration(milliseconds: 50));
expect(tester.getTopLeft(find.text('1')).dx, moreOrLessEquals(-179, epsilon: 1));
expect(tester.getTopLeft(find.text('2')).dx, moreOrLessEquals(262, epsilon: 1));
await tester.pump(const Duration(milliseconds: 50));
expect(tester.getTopLeft(find.text('1')).dx, moreOrLessEquals(-100, epsilon: 1));
expect(tester.getTopLeft(find.text('2')).dx, moreOrLessEquals(499, epsilon: 1));
await tester.pump(const Duration(milliseconds: 50));
// Translation slows down as time goes on.
expect(tester.getTopLeft(find.text('1')).dx, moreOrLessEquals(-47, epsilon: 1));
expect(tester.getTopLeft(find.text('2')).dx, moreOrLessEquals(659, epsilon: 1));
});
testWidgets('Dragged pop gesture is linear', (WidgetTester tester) async {
await tester.pumpWidget(
const CupertinoApp(
home: Text('1'),
),
);
final CupertinoPageRoute<void> route2 = CupertinoPageRoute<void>(
builder: (BuildContext context) {
return const CupertinoPageScaffold(
child: Text('2'),
);
}
);
tester.state<NavigatorState>(find.byType(Navigator)).push(route2);
await tester.pumpAndSettle();
expect(find.text('1'), findsNothing);
expect(tester.getTopLeft(find.text('2')).dx, moreOrLessEquals(0));
final TestGesture swipeGesture = await tester.startGesture(const Offset(5, 100));
await swipeGesture.moveBy(const Offset(100, 0));
await tester.pump();
expect(tester.getTopLeft(find.text('1')).dx, moreOrLessEquals(-233, epsilon: 1));
expect(tester.getTopLeft(find.text('2')).dx, moreOrLessEquals(100));
expect(
tester.state<NavigatorState>(find.byType(Navigator)).userGestureInProgress,
true,
);
await swipeGesture.moveBy(const Offset(100, 0));
await tester.pump();
expect(tester.getTopLeft(find.text('1')).dx, moreOrLessEquals(-200));
expect(tester.getTopLeft(find.text('2')).dx, moreOrLessEquals(200));
// Moving by the same distance each time produces linear movements on both
// routes.
await swipeGesture.moveBy(const Offset(100, 0));
await tester.pump();
expect(tester.getTopLeft(find.text('1')).dx, moreOrLessEquals(-166, epsilon: 1));
expect(tester.getTopLeft(find.text('2')).dx, moreOrLessEquals(300));
});
testWidgets('Pop gesture snapping is not linear', (WidgetTester tester) async {
await tester.pumpWidget(
const CupertinoApp(
home: Text('1'),
),
);
final CupertinoPageRoute<void> route2 = CupertinoPageRoute<void>(
builder: (BuildContext context) {
return const CupertinoPageScaffold(
child: Text('2'),
);
}
);
tester.state<NavigatorState>(find.byType(Navigator)).push(route2);
await tester.pumpAndSettle();
final TestGesture swipeGesture = await tester.startGesture(const Offset(5, 100));
await swipeGesture.moveBy(const Offset(500, 0));
await swipeGesture.up();
await tester.pump();
expect(tester.getTopLeft(find.text('1')).dx, moreOrLessEquals(-100));
expect(tester.getTopLeft(find.text('2')).dx, moreOrLessEquals(500));
expect(
tester.state<NavigatorState>(find.byType(Navigator)).userGestureInProgress,
true,
);
await tester.pump(const Duration(milliseconds: 50));
expect(tester.getTopLeft(find.text('1')).dx, moreOrLessEquals(-19, epsilon: 1));
expect(tester.getTopLeft(find.text('2')).dx, moreOrLessEquals(744, epsilon: 1));
await tester.pump(const Duration(milliseconds: 50));
// Rate of change is slowing down.
expect(tester.getTopLeft(find.text('1')).dx, moreOrLessEquals(-4, epsilon: 1));
expect(tester.getTopLeft(find.text('2')).dx, moreOrLessEquals(787, epsilon: 1));
await tester.pumpAndSettle();
expect(
tester.state<NavigatorState>(find.byType(Navigator)).userGestureInProgress,
false,
);
});
testWidgets('Snapped drags forwards and backwards should signal didStart/StopUserGesture', (WidgetTester tester) async {
final GlobalKey<NavigatorState> navigatorKey = GlobalKey();
await tester.pumpWidget(
CupertinoApp(
navigatorObservers: <NavigatorObserver>[navigatorObserver],
navigatorKey: navigatorKey,
home: const Text('1'),
),
);
final CupertinoPageRoute<void> route2 = CupertinoPageRoute<void>(
builder: (BuildContext context) {
return const CupertinoPageScaffold(
child: Text('2'),
);
}
);
navigatorKey.currentState.push(route2);
await tester.pumpAndSettle();
expect(navigatorObserver.invocations.removeLast(), NavigatorInvocation.didPush);
await tester.dragFrom(const Offset(5, 100), const Offset(100, 0));
expect(navigatorObserver.invocations.removeLast(), NavigatorInvocation.didStartUserGesture);
await tester.pump();
expect(tester.getTopLeft(find.text('2')).dx, moreOrLessEquals(100));
expect(navigatorKey.currentState.userGestureInProgress, true);
// Didn't drag far enough to snap into dismissing this route.
// Each 100px distance takes 100ms to snap back.
await tester.pump(const Duration(milliseconds: 101));
// Back to the page covering the whole screen.
expect(tester.getTopLeft(find.text('2')).dx, moreOrLessEquals(0));
expect(navigatorKey.currentState.userGestureInProgress, false);
expect(navigatorObserver.invocations.removeLast(), NavigatorInvocation.didStopUserGesture);
expect(navigatorObserver.invocations.removeLast(), isNot(NavigatorInvocation.didPop));
await tester.dragFrom(const Offset(5, 100), const Offset(500, 0));
await tester.pump();
expect(tester.getTopLeft(find.text('2')).dx, moreOrLessEquals(500));
expect(navigatorKey.currentState.userGestureInProgress, true);
expect(navigatorObserver.invocations.removeLast(), NavigatorInvocation.didPop);
// Did go far enough to snap out of this route.
await tester.pump(const Duration(milliseconds: 301));
// Back to the page covering the whole screen.
expect(find.text('2'), findsNothing);
// First route covers the whole screen.
expect(tester.getTopLeft(find.text('1')).dx, moreOrLessEquals(0));
expect(navigatorKey.currentState.userGestureInProgress, false);
});
/// Regression test for https://github.com/flutter/flutter/issues/29596.
testWidgets('test edge swipe then drop back at ending point works', (WidgetTester tester) async {
await tester.pumpWidget(
CupertinoApp(
navigatorObservers: <NavigatorObserver>[navigatorObserver],
onGenerateRoute: (RouteSettings settings) {
return CupertinoPageRoute<void>(
settings: settings,
builder: (BuildContext context) {
final String pageNumber = settings.name == '/' ? '1' : '2';
return Center(child: Text('Page $pageNumber'));
},
);
},
),
);
tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/next');
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(find.text('Page 1'), findsNothing);
expect(find.text('Page 2'), isOnstage);
final TestGesture gesture = await tester.startGesture(const Offset(5, 200));
// The width of the page.
await gesture.moveBy(const Offset(800, 0));
expect(navigatorObserver.invocations.removeLast(), NavigatorInvocation.didStartUserGesture);
await gesture.up();
await tester.pump();
expect(find.text('Page 1'), isOnstage);
expect(find.text('Page 2'), findsNothing);
expect(navigatorObserver.invocations.removeLast(), NavigatorInvocation.didStopUserGesture);
expect(navigatorObserver.invocations.removeLast(), NavigatorInvocation.didPop);
});
testWidgets('test edge swipe then drop back at starting point works', (WidgetTester tester) async {
await tester.pumpWidget(
CupertinoApp(
navigatorObservers: <NavigatorObserver>[navigatorObserver],
onGenerateRoute: (RouteSettings settings) {
return CupertinoPageRoute<void>(
settings: settings,
builder: (BuildContext context) {
final String pageNumber = settings.name == '/' ? '1' : '2';
return Center(child: Text('Page $pageNumber'));
},
);
},
),
);
tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/next');
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(find.text('Page 1'), findsNothing);
expect(find.text('Page 2'), isOnstage);
final TestGesture gesture = await tester.startGesture(const Offset(5, 200));
// Move right a bit
await gesture.moveBy(const Offset(300, 0));
expect(navigatorObserver.invocations.removeLast(), NavigatorInvocation.didStartUserGesture);
expect(
tester.state<NavigatorState>(find.byType(Navigator)).userGestureInProgress,
true,
);
await tester.pump();
// Move back to where we started.
await gesture.moveBy(const Offset(-300, 0));
await gesture.up();
await tester.pump();
expect(find.text('Page 1'), findsNothing);
expect(find.text('Page 2'), isOnstage);
expect(navigatorObserver.invocations.removeLast(), NavigatorInvocation.didStopUserGesture);
expect(navigatorObserver.invocations.removeLast(), isNot(NavigatorInvocation.didPop));
expect(
tester.state<NavigatorState>(find.byType(Navigator)).userGestureInProgress,
false,
);
});
testWidgets('ModalPopup overlay dark mode', (WidgetTester tester) async {
StateSetter stateSetter;
Brightness brightness = Brightness.light;
await tester.pumpWidget(
StatefulBuilder(
builder: (BuildContext context, StateSetter setter) {
stateSetter = setter;
return CupertinoApp(
theme: CupertinoThemeData(brightness: brightness),
home: CupertinoPageScaffold(
child: Builder(builder: (BuildContext context) {
return GestureDetector(
onTap: () async {
await showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) => const SizedBox(),
);
},
child: const Text('tap'),
);
}),
),
);
},
),
);
await tester.tap(find.text('tap'));
await tester.pumpAndSettle();
expect(
tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).color.value,
0x33000000,
);
stateSetter(() { brightness = Brightness.dark; });
await tester.pump();
// TODO(LongCatIsLooong): The background overlay SHOULD switch to dark color.
expect(
tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).color.value,
0x33000000,
);
await tester.pumpWidget(
CupertinoApp(
theme: const CupertinoThemeData(brightness: Brightness.dark),
home: CupertinoPageScaffold(
child: Builder(builder: (BuildContext context) {
return GestureDetector(
onTap: () async {
await showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) => const SizedBox(),
);
},
child: const Text('tap'),
);
}),
),
),
);
await tester.tap(find.text('tap'));
await tester.pumpAndSettle();
expect(
tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).color.value,
0x7A000000,
);
});
testWidgets('During back swipe the route ignores input', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/39989
final GlobalKey homeScaffoldKey = GlobalKey();
final GlobalKey pageScaffoldKey = GlobalKey();
int homeTapCount = 0;
int pageTapCount = 0;
await tester.pumpWidget(
CupertinoApp(
home: CupertinoPageScaffold(
key: homeScaffoldKey,
child: GestureDetector(
onTap: () {
homeTapCount += 1;
}
),
),
),
);
await tester.tap(find.byKey(homeScaffoldKey));
expect(homeTapCount, 1);
expect(pageTapCount, 0);
Navigator.push<void>(homeScaffoldKey.currentContext, CupertinoPageRoute<void>(
builder: (BuildContext context) {
return CupertinoPageScaffold(
key: pageScaffoldKey,
child: Padding(
padding: const EdgeInsets.all(16),
child: GestureDetector(
onTap: () {
pageTapCount += 1;
}
),
),
);
},
));
await tester.pumpAndSettle();
await tester.tap(find.byKey(pageScaffoldKey));
expect(homeTapCount, 1);
expect(pageTapCount, 1);
// Start the basic iOS back-swipe dismiss transition. Drag the pushed
// "page" route halfway across the screen. The underlying "home" will
// start sliding in from the left.
final TestGesture gesture = await tester.startGesture(const Offset(5, 300));
await gesture.moveBy(const Offset(400, 0));
await tester.pump();
expect(tester.getTopLeft(find.byKey(pageScaffoldKey)), const Offset(400, 0));
expect(tester.getTopLeft(find.byKey(homeScaffoldKey)).dx, lessThan(0));
// Tapping on the "page" route doesn't trigger the GestureDetector because
// it's being dragged.
await tester.tap(find.byKey(pageScaffoldKey));
expect(homeTapCount, 1);
expect(pageTapCount, 1);
});
testWidgets('showCupertinoModalPopup uses root navigator by default', (WidgetTester tester) async {
final PopupObserver rootObserver = PopupObserver();
final PopupObserver nestedObserver = PopupObserver();
await tester.pumpWidget(CupertinoApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
home: Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return PageRouteBuilder<dynamic>(
pageBuilder: (BuildContext context, Animation<double> _, Animation<double> __) {
return GestureDetector(
onTap: () async {
await showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) => const SizedBox(),
);
},
child: const Text('tap'),
);
},
);
},
),
));
// Open the dialog.
await tester.tap(find.text('tap'));
expect(rootObserver.popupCount, 1);
expect(nestedObserver.popupCount, 0);
});
testWidgets('back swipe to screen edges does not dismiss the hero animation', (WidgetTester tester) async {
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
final UniqueKey container = UniqueKey();
await tester.pumpWidget(CupertinoApp(
navigatorKey: navigator,
routes: <String, WidgetBuilder>{
'/': (BuildContext context) {
return CupertinoPageScaffold(
child: Center(
child: Hero(
tag: 'tag',
transitionOnUserGestures: true,
child: Container(key: container, height: 150.0, width: 150.0)
),
)
);
},
'/page2': (BuildContext context) {
return CupertinoPageScaffold(
child: Center(
child: Padding(
padding: const EdgeInsets.fromLTRB(100.0, 0.0, 0.0, 0.0),
child: Hero(
tag: 'tag',
transitionOnUserGestures: true,
child: Container(key: container, height: 150.0, width: 150.0)
)
),
)
);
}
},
));
RenderBox box = tester.renderObject(find.byKey(container)) as RenderBox;
final double initialPosition = box.localToGlobal(Offset.zero).dx;
navigator.currentState.pushNamed('/page2');
await tester.pumpAndSettle();
box = tester.renderObject(find.byKey(container)) as RenderBox;
final double finalPosition = box.localToGlobal(Offset.zero).dx;
final TestGesture gesture = await tester.startGesture(const Offset(5, 300));
await gesture.moveBy(const Offset(200, 0));
await tester.pump();
box = tester.renderObject(find.byKey(container)) as RenderBox;
final double firstPosition = box.localToGlobal(Offset.zero).dx;
// Checks the hero is in-transit.
expect(finalPosition, greaterThan(firstPosition));
expect(firstPosition, greaterThan(initialPosition));
// Goes back to final position.
await gesture.moveBy(const Offset(-200, 0));
await tester.pump();
box = tester.renderObject(find.byKey(container)) as RenderBox;
final double secondPosition = box.localToGlobal(Offset.zero).dx;
// There will be a small difference.
expect(finalPosition - secondPosition, lessThan(0.001));
await gesture.moveBy(const Offset(400, 0));
await tester.pump();
box = tester.renderObject(find.byKey(container)) as RenderBox;
final double thirdPosition = box.localToGlobal(Offset.zero).dx;
// Checks the hero is still in-transit and moves further away from the first
// position.
expect(finalPosition, greaterThan(thirdPosition));
expect(thirdPosition, greaterThan(initialPosition));
expect(firstPosition, greaterThan(thirdPosition));
});
testWidgets('showCupertinoModalPopup uses nested navigator if useRootNavigator is false', (WidgetTester tester) async {
final PopupObserver rootObserver = PopupObserver();
final PopupObserver nestedObserver = PopupObserver();
await tester.pumpWidget(CupertinoApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
home: Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return PageRouteBuilder<dynamic>(
pageBuilder: (BuildContext context, Animation<double> _, Animation<double> __) {
return GestureDetector(
onTap: () async {
await showCupertinoModalPopup<void>(
context: context,
useRootNavigator: false,
builder: (BuildContext context) => const SizedBox(),
);
},
child: const Text('tap'),
);
},
);
},
),
));
// Open the dialog.
await tester.tap(find.text('tap'));
expect(rootObserver.popupCount, 0);
expect(nestedObserver.popupCount, 1);
});
testWidgets('showCupertinoDialog uses root navigator by default', (WidgetTester tester) async {
final DialogObserver rootObserver = DialogObserver();
final DialogObserver nestedObserver = DialogObserver();
await tester.pumpWidget(CupertinoApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
home: Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return PageRouteBuilder<dynamic>(
pageBuilder: (BuildContext context, Animation<double> _, Animation<double> __) {
return GestureDetector(
onTap: () async {
await showCupertinoDialog<void>(
context: context,
builder: (BuildContext context) => const SizedBox(),
);
},
child: const Text('tap'),
);
},
);
},
),
));
// Open the dialog.
await tester.tap(find.text('tap'));
expect(rootObserver.dialogCount, 1);
expect(nestedObserver.dialogCount, 0);
});
testWidgets('showCupertinoDialog uses nested navigator if useRootNavigator is false', (WidgetTester tester) async {
final DialogObserver rootObserver = DialogObserver();
final DialogObserver nestedObserver = DialogObserver();
await tester.pumpWidget(CupertinoApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
home: Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return PageRouteBuilder<dynamic>(
pageBuilder: (BuildContext context, Animation<double> _, Animation<double> __) {
return GestureDetector(
onTap: () async {
await showCupertinoDialog<void>(
context: context,
useRootNavigator: false,
builder: (BuildContext context) => const SizedBox(),
);
},
child: const Text('tap'),
);
},
);
},
),
));
// Open the dialog.
await tester.tap(find.text('tap'));
expect(rootObserver.dialogCount, 0);
expect(nestedObserver.dialogCount, 1);
});
testWidgets('showCupertinoModalPopup does not allow for semantics dismiss by default', (WidgetTester tester) async {
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(CupertinoApp(
home: Navigator(
onGenerateRoute: (RouteSettings settings) {
return PageRouteBuilder<dynamic>(
pageBuilder: (BuildContext context, Animation<double> _, Animation<double> __) {
return GestureDetector(
onTap: () async {
await showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) => const SizedBox(),
);
},
child: const Text('tap'),
);
},
);
},
),
));
// Push the route.
await tester.tap(find.text('tap'));
await tester.pumpAndSettle();
expect(semantics, isNot(includesNodeWith(
actions: <SemanticsAction>[SemanticsAction.tap],
label: 'Dismiss',
)));
debugDefaultTargetPlatformOverride = null;
});
testWidgets('showCupertinoModalPopup allows for semantics dismiss when set', (WidgetTester tester) async {
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(CupertinoApp(
home: Navigator(
onGenerateRoute: (RouteSettings settings) {
return PageRouteBuilder<dynamic>(
pageBuilder: (BuildContext context, Animation<double> _, Animation<double> __) {
return GestureDetector(
onTap: () async {
await showCupertinoModalPopup<void>(
context: context,
semanticsDismissible: true,
builder: (BuildContext context) => const SizedBox(),
);
},
child: const Text('tap'),
);
},
);
},
),
));
// Push the route.
await tester.tap(find.text('tap'));
await tester.pumpAndSettle();
expect(semantics, includesNodeWith(
actions: <SemanticsAction>[SemanticsAction.tap],
label: 'Dismiss',
));
debugDefaultTargetPlatformOverride = null;
});
testWidgets('CupertinoPage works', (WidgetTester tester) async {
final LocalKey pageKey = UniqueKey();
final TransitionDetector detector = TransitionDetector();
List<Page<void>> myPages = <Page<void>>[
CupertinoPage<void>(
key: pageKey,
title: 'title one',
child: CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(key: UniqueKey()),
child: const Text('first'),
),
),
];
await tester.pumpWidget(
buildNavigator(
pages: myPages,
onPopPage: (Route<dynamic> route, dynamic result) => null,
transitionDelegate: detector,
)
);
expect(detector.hasTransition, isFalse);
expect(find.widgetWithText(CupertinoNavigationBar, 'title one'), findsOneWidget);
expect(find.text('first'), findsOneWidget);
myPages = <Page<void>>[
CupertinoPage<void>(
key: pageKey,
title: 'title two',
child: CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(key: UniqueKey()),
child: const Text('second'),
),
),
];
await tester.pumpWidget(
buildNavigator(
pages: myPages,
onPopPage: (Route<dynamic> route, dynamic result) => null,
transitionDelegate: detector,
)
);
// There should be no transition because the page has the same key.
expect(detector.hasTransition, isFalse);
// The content does update.
expect(find.text('first'), findsNothing);
expect(find.widgetWithText(CupertinoNavigationBar, 'title one'), findsNothing);
expect(find.text('second'), findsOneWidget);
expect(find.widgetWithText(CupertinoNavigationBar, 'title two'), findsOneWidget);
});
testWidgets('CupertinoPage can toggle MaintainState', (WidgetTester tester) async {
final LocalKey pageKeyOne = UniqueKey();
final LocalKey pageKeyTwo = UniqueKey();
final TransitionDetector detector = TransitionDetector();
List<Page<void>> myPages = <Page<void>>[
CupertinoPage<void>(key: pageKeyOne, maintainState: false, child: const Text('first')),
CupertinoPage<void>(key: pageKeyTwo, child: const Text('second')),
];
await tester.pumpWidget(
buildNavigator(
pages: myPages,
onPopPage: (Route<dynamic> route, dynamic result) => null,
transitionDelegate: detector,
)
);
expect(detector.hasTransition, isFalse);
// Page one does not maintain state.
expect(find.text('first', skipOffstage: false), findsNothing);
expect(find.text('second'), findsOneWidget);
myPages = <Page<void>>[
CupertinoPage<void>(key: pageKeyOne, maintainState: true, child: const Text('first')),
CupertinoPage<void>(key: pageKeyTwo, child: const Text('second')),
];
await tester.pumpWidget(
buildNavigator(
pages: myPages,
onPopPage: (Route<dynamic> route, dynamic result) => null,
transitionDelegate: detector,
)
);
// There should be no transition because the page has the same key.
expect(detector.hasTransition, isFalse);
// Page one sets the maintain state to be true, its widget tree should be
// built.
expect(find.text('first', skipOffstage: false), findsOneWidget);
expect(find.text('second'), findsOneWidget);
});
}
class MockNavigatorObserver extends NavigatorObserver {
final List<NavigatorInvocation> invocations = <NavigatorInvocation>[];
@override
void didStartUserGesture(Route<Object> route, Route<Object> previousRoute) {
invocations.add(NavigatorInvocation.didStartUserGesture);
}
@override
void didPop(Route<Object> route, Route<Object> previousRoute) {
invocations.add(NavigatorInvocation.didPop);
}
@override
void didPush(Route<Object> route, Route<Object> previousRoute) {
invocations.add(NavigatorInvocation.didPush);
}
@override
void didStopUserGesture() {
invocations.add(NavigatorInvocation.didStopUserGesture);
}
}
enum NavigatorInvocation {
didStartUserGesture,
didPop,
didPush,
didStopUserGesture,
}
class PopupObserver extends NavigatorObserver {
int popupCount = 0;
@override
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
if (route.toString().contains('_CupertinoModalPopupRoute')) {
popupCount++;
}
super.didPush(route, previousRoute);
}
}
class DialogObserver extends NavigatorObserver {
int dialogCount = 0;
@override
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
if (route.toString().contains('_DialogRoute')) {
dialogCount++;
}
super.didPush(route, previousRoute);
}
}
class TransitionDetector extends DefaultTransitionDelegate<void> {
bool hasTransition = false;
@override
Iterable<RouteTransitionRecord> resolve({
List<RouteTransitionRecord> newPageRouteHistory,
Map<RouteTransitionRecord, RouteTransitionRecord> locationToExitingPageRoute,
Map<RouteTransitionRecord, List<RouteTransitionRecord>> pageRouteToPagelessRoutes
}) {
hasTransition = true;
return super.resolve(
newPageRouteHistory: newPageRouteHistory,
locationToExitingPageRoute: locationToExitingPageRoute,
pageRouteToPagelessRoutes: pageRouteToPagelessRoutes
);
}
}
Widget buildNavigator({
List<Page<dynamic>> pages,
PopPageCallback onPopPage,
GlobalKey<NavigatorState> key,
TransitionDelegate<dynamic> transitionDelegate
}) {
return MediaQuery(
data: MediaQueryData.fromWindow(WidgetsBinding.instance.window),
child: Localizations(
locale: const Locale('en', 'US'),
delegates: const <LocalizationsDelegate<dynamic>>[
DefaultCupertinoLocalizations.delegate,
DefaultWidgetsLocalizations.delegate
],
child: Directionality(
textDirection: TextDirection.ltr,
child: Navigator(
key: key,
pages: pages,
onPopPage: onPopPage,
transitionDelegate: transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(),
),
),
),
);
}