// Copyright 2015 The Chromium 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 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/gestures.dart' show DragStartBehavior; import '../widgets/semantics_tester.dart'; void main() { testWidgets('Scaffold control test', (WidgetTester tester) async { final Key bodyKey = UniqueKey(); Widget boilerplate(Widget child) { return Localizations( locale: const Locale('en', 'us'), delegates: const <LocalizationsDelegate<dynamic>>[ DefaultWidgetsLocalizations.delegate, DefaultMaterialLocalizations.delegate, ], child: Directionality( textDirection: TextDirection.ltr, child: child, ), ); } await tester.pumpWidget(boilerplate(Scaffold( appBar: AppBar(title: const Text('Title')), body: Container(key: bodyKey), ), )); expect(tester.takeException(), isFlutterError); await tester.pumpWidget(MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('Title')), body: Container(key: bodyKey), ), )); RenderBox bodyBox = tester.renderObject(find.byKey(bodyKey)); expect(bodyBox.size, equals(const Size(800.0, 544.0))); await tester.pumpWidget(boilerplate(MediaQuery( data: const MediaQueryData(viewInsets: EdgeInsets.only(bottom: 100.0)), child: Scaffold( appBar: AppBar(title: const Text('Title')), body: Container(key: bodyKey), ), ), )); bodyBox = tester.renderObject(find.byKey(bodyKey)); expect(bodyBox.size, equals(const Size(800.0, 444.0))); await tester.pumpWidget(boilerplate(MediaQuery( data: const MediaQueryData(viewInsets: EdgeInsets.only(bottom: 100.0)), child: Scaffold( appBar: AppBar(title: const Text('Title')), body: Container(key: bodyKey), resizeToAvoidBottomInset: false, ), ))); bodyBox = tester.renderObject(find.byKey(bodyKey)); expect(bodyBox.size, equals(const Size(800.0, 544.0))); // Backwards compatibility: deprecated resizeToAvoidBottomPadding flag await tester.pumpWidget(boilerplate(MediaQuery( data: const MediaQueryData(viewInsets: EdgeInsets.only(bottom: 100.0)), child: Scaffold( appBar: AppBar(title: const Text('Title')), body: Container(key: bodyKey), resizeToAvoidBottomPadding: false, ), ))); bodyBox = tester.renderObject(find.byKey(bodyKey)); expect(bodyBox.size, equals(const Size(800.0, 544.0))); }); testWidgets('Scaffold large bottom padding test', (WidgetTester tester) async { final Key bodyKey = UniqueKey(); Widget boilerplate(Widget child) { return Localizations( locale: const Locale('en', 'us'), delegates: const <LocalizationsDelegate<dynamic>>[ DefaultWidgetsLocalizations.delegate, DefaultMaterialLocalizations.delegate, ], child: Directionality( textDirection: TextDirection.ltr, child: child, ), ); } await tester.pumpWidget(boilerplate(MediaQuery( data: const MediaQueryData( viewInsets: EdgeInsets.only(bottom: 700.0), ), child: Scaffold( body: Container(key: bodyKey), )) )); final RenderBox bodyBox = tester.renderObject(find.byKey(bodyKey)); expect(bodyBox.size, equals(const Size(800.0, 0.0))); await tester.pumpWidget(boilerplate(MediaQuery( data: const MediaQueryData( viewInsets: EdgeInsets.only(bottom: 500.0), ), child: Scaffold( body: Container(key: bodyKey), ), ), )); expect(bodyBox.size, equals(const Size(800.0, 100.0))); await tester.pumpWidget(boilerplate(MediaQuery( data: const MediaQueryData( viewInsets: EdgeInsets.only(bottom: 580.0), ), child: Scaffold( appBar: AppBar( title: const Text('Title'), ), body: Container(key: bodyKey), ), ), )); expect(bodyBox.size, equals(const Size(800.0, 0.0))); }); testWidgets('Floating action entrance/exit animation', (WidgetTester tester) async { await tester.pumpWidget(const MaterialApp(home: Scaffold( floatingActionButton: FloatingActionButton( key: Key('one'), onPressed: null, child: Text('1'), ), ))); expect(tester.binding.transientCallbackCount, 0); await tester.pumpWidget(const MaterialApp(home: Scaffold( floatingActionButton: FloatingActionButton( key: Key('two'), onPressed: null, child: Text('2'), ), ))); expect(tester.binding.transientCallbackCount, greaterThan(0)); await tester.pumpWidget(Container()); expect(tester.binding.transientCallbackCount, 0); await tester.pumpWidget(const MaterialApp(home: Scaffold())); expect(tester.binding.transientCallbackCount, 0); await tester.pumpWidget(const MaterialApp(home: Scaffold( floatingActionButton: FloatingActionButton( key: Key('one'), onPressed: null, child: Text('1'), ), ))); expect(tester.binding.transientCallbackCount, greaterThan(0)); }); testWidgets('Floating action button directionality', (WidgetTester tester) async { Widget build(TextDirection textDirection) { return Directionality( textDirection: textDirection, child: const MediaQuery( data: MediaQueryData( viewInsets: EdgeInsets.only(bottom: 200.0), ), child: Scaffold( floatingActionButton: FloatingActionButton( onPressed: null, child: Text('1'), ), ), ), ); } await tester.pumpWidget(build(TextDirection.ltr)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 356.0)); await tester.pumpWidget(build(TextDirection.rtl)); expect(tester.binding.transientCallbackCount, 0); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(44.0, 356.0)); }); testWidgets('Floating Action Button bottom padding not consumed by viewInsets', (WidgetTester tester) async { final Widget child = Directionality( textDirection: TextDirection.ltr, child: Scaffold( resizeToAvoidBottomInset: false, body: Container(), floatingActionButton: const Placeholder(), ), ); await tester.pumpWidget( MediaQuery( data: const MediaQueryData( padding: EdgeInsets.only(bottom: 20.0), ), child: child, ) ); final Offset initialPoint = tester.getCenter(find.byType(Placeholder)); // Consume bottom padding - as if by the keyboard opening await tester.pumpWidget( MediaQuery( data: const MediaQueryData( padding: EdgeInsets.zero, viewPadding: EdgeInsets.only(bottom: 20), viewInsets: EdgeInsets.only(bottom: 300), ), child: child, ), ); final Offset finalPoint = tester.getCenter(find.byType(Placeholder)); expect(initialPoint, finalPoint); }); testWidgets('Drawer scrolling', (WidgetTester tester) async { final Key drawerKey = UniqueKey(); const double appBarHeight = 256.0; final ScrollController scrollOffset = ScrollController(); await tester.pumpWidget( MaterialApp( home: Scaffold( drawer: Drawer( key: drawerKey, child: ListView( dragStartBehavior: DragStartBehavior.down, controller: scrollOffset, children: List<Widget>.generate(10, (int index) => SizedBox(height: 100.0, child: Text('D$index')), ), ), ), body: CustomScrollView( slivers: <Widget>[ const SliverAppBar( pinned: true, expandedHeight: appBarHeight, title: Text('Title'), flexibleSpace: FlexibleSpaceBar(title: Text('Title')), ), SliverPadding( padding: const EdgeInsets.only(top: appBarHeight), sliver: SliverList( delegate: SliverChildListDelegate(List<Widget>.generate( 10, (int index) => SizedBox(height: 100.0, child: Text('B$index')), )), ), ), ], ), ), ) ); final ScaffoldState state = tester.firstState(find.byType(Scaffold)); state.openDrawer(); await tester.pump(); await tester.pump(const Duration(seconds: 1)); expect(scrollOffset.offset, 0.0); const double scrollDelta = 80.0; await tester.drag(find.byKey(drawerKey), const Offset(0.0, -scrollDelta)); await tester.pump(); expect(scrollOffset.offset, scrollDelta); final RenderBox renderBox = tester.renderObject(find.byType(AppBar)); expect(renderBox.size.height, equals(appBarHeight)); }); Widget _buildStatusBarTestApp(TargetPlatform platform) { return MaterialApp( theme: ThemeData(platform: platform), home: MediaQuery( data: const MediaQueryData(padding: EdgeInsets.only(top: 25.0)), // status bar child: Scaffold( body: CustomScrollView( primary: true, slivers: <Widget>[ const SliverAppBar( title: Text('Title'), ), SliverList( delegate: SliverChildListDelegate(List<Widget>.generate( 20, (int index) => SizedBox(height: 100.0, child: Text('$index')), )), ), ], ), ), ), ); } testWidgets('Tapping the status bar scrolls to top on iOS', (WidgetTester tester) async { await tester.pumpWidget(_buildStatusBarTestApp(TargetPlatform.iOS)); final ScrollableState scrollable = tester.state(find.byType(Scrollable)); scrollable.position.jumpTo(500.0); expect(scrollable.position.pixels, equals(500.0)); await tester.tapAt(const Offset(100.0, 10.0)); await tester.pumpAndSettle(); expect(scrollable.position.pixels, equals(0.0)); }); testWidgets('Tapping the status bar does not scroll to top on Android', (WidgetTester tester) async { await tester.pumpWidget(_buildStatusBarTestApp(TargetPlatform.android)); final ScrollableState scrollable = tester.state(find.byType(Scrollable)); scrollable.position.jumpTo(500.0); expect(scrollable.position.pixels, equals(500.0)); await tester.tapAt(const Offset(100.0, 10.0)); await tester.pump(); await tester.pump(const Duration(seconds: 1)); expect(scrollable.position.pixels, equals(500.0)); }); testWidgets('Bottom sheet cannot overlap app bar', (WidgetTester tester) async { final Key sheetKey = UniqueKey(); await tester.pumpWidget( MaterialApp( theme: ThemeData(platform: TargetPlatform.android), home: Scaffold( appBar: AppBar( title: const Text('Title'), ), body: Builder( builder: (BuildContext context) { return GestureDetector( onTap: () { Scaffold.of(context).showBottomSheet<void>((BuildContext context) { return Container( key: sheetKey, color: Colors.blue[500], ); }); }, child: const Text('X'), ); }, ), ), ), ); await tester.tap(find.text('X')); await tester.pump(); // start animation await tester.pump(const Duration(seconds: 1)); final RenderBox appBarBox = tester.renderObject(find.byType(AppBar)); final RenderBox sheetBox = tester.renderObject(find.byKey(sheetKey)); final Offset appBarBottomRight = appBarBox.localToGlobal(appBarBox.size.bottomRight(Offset.zero)); final Offset sheetTopRight = sheetBox.localToGlobal(sheetBox.size.topRight(Offset.zero)); expect(appBarBottomRight, equals(sheetTopRight)); }); testWidgets('BottomSheet bottom padding is not consumed by viewInsets', (WidgetTester tester) async { final Widget child = Directionality( textDirection: TextDirection.ltr, child: Scaffold( resizeToAvoidBottomInset: false, body: Container(), bottomSheet: const Placeholder(), ), ); await tester.pumpWidget( MediaQuery( data: const MediaQueryData( padding: EdgeInsets.only(bottom: 20.0), ), child: child, ) ); final Offset initialPoint = tester.getCenter(find.byType(Placeholder)); // Consume bottom padding - as if by the keyboard opening await tester.pumpWidget( MediaQuery( data: const MediaQueryData( padding: EdgeInsets.zero, viewPadding: EdgeInsets.only(bottom: 20), viewInsets: EdgeInsets.only(bottom: 300), ), child: child, ), ); final Offset finalPoint = tester.getCenter(find.byType(Placeholder)); expect(initialPoint, finalPoint); }); testWidgets('Persistent bottom buttons are persistent', (WidgetTester tester) async { bool didPressButton = false; await tester.pumpWidget( MaterialApp( home: Scaffold( body: SingleChildScrollView( child: Container( color: Colors.amber[500], height: 5000.0, child: const Text('body'), ), ), persistentFooterButtons: <Widget>[ FlatButton( onPressed: () { didPressButton = true; }, child: const Text('X'), ), ], ), ), ); await tester.drag(find.text('body'), const Offset(0.0, -1000.0)); expect(didPressButton, isFalse); await tester.tap(find.text('X')); expect(didPressButton, isTrue); }); testWidgets('Persistent bottom buttons apply media padding', (WidgetTester tester) async { await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: MediaQuery( data: const MediaQueryData( padding: EdgeInsets.fromLTRB(10.0, 20.0, 30.0, 40.0), ), child: Scaffold( body: SingleChildScrollView( child: Container( color: Colors.amber[500], height: 5000.0, child: const Text('body'), ), ), persistentFooterButtons: const <Widget>[Placeholder()], ), ), ), ); expect(tester.getBottomLeft(find.byType(ButtonBar)), const Offset(10.0, 560.0)); expect(tester.getBottomRight(find.byType(ButtonBar)), const Offset(770.0, 560.0)); }); testWidgets('Persistent bottom buttons bottom padding is not consumed by viewInsets', (WidgetTester tester) async { final Widget child = Directionality( textDirection: TextDirection.ltr, child: Scaffold( resizeToAvoidBottomInset: false, body: Container(), persistentFooterButtons: const <Widget>[Placeholder()], ), ); await tester.pumpWidget( MediaQuery( data: const MediaQueryData( padding: EdgeInsets.only(bottom: 20.0), ), child: child, ) ); final Offset initialPoint = tester.getCenter(find.byType(Placeholder)); // Consume bottom padding - as if by the keyboard opening await tester.pumpWidget( MediaQuery( data: const MediaQueryData( padding: EdgeInsets.zero, viewPadding: EdgeInsets.only(bottom: 20), viewInsets: EdgeInsets.only(bottom: 300), ), child: child, ), ); final Offset finalPoint = tester.getCenter(find.byType(Placeholder)); expect(initialPoint, finalPoint); }); group('back arrow', () { Future<void> expectBackIcon(WidgetTester tester, TargetPlatform platform, IconData expectedIcon) async { final GlobalKey rootKey = GlobalKey(); final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{ '/': (_) => Container(key: rootKey, child: const Text('Home')), '/scaffold': (_) => Scaffold( appBar: AppBar(), body: const Text('Scaffold'), ), }; await tester.pumpWidget( MaterialApp(theme: ThemeData(platform: platform), routes: routes) ); Navigator.pushNamed(rootKey.currentContext, '/scaffold'); await tester.pump(); await tester.pump(const Duration(seconds: 1)); final Icon icon = tester.widget(find.byType(Icon)); expect(icon.icon, expectedIcon); } testWidgets('Back arrow uses correct default on Android', (WidgetTester tester) async { await expectBackIcon(tester, TargetPlatform.android, Icons.arrow_back); }); testWidgets('Back arrow uses correct default on Fuchsia', (WidgetTester tester) async { await expectBackIcon(tester, TargetPlatform.fuchsia, Icons.arrow_back); }); testWidgets('Back arrow uses correct default on iOS', (WidgetTester tester) async { await expectBackIcon(tester, TargetPlatform.iOS, Icons.arrow_back_ios); }); }); group('close button', () { Future<void> expectCloseIcon(WidgetTester tester, TargetPlatform platform, IconData expectedIcon, PageRoute<void> routeBuilder()) async { await tester.pumpWidget( MaterialApp( theme: ThemeData(platform: platform), home: Scaffold(appBar: AppBar(), body: const Text('Page 1')), ) ); tester.state<NavigatorState>(find.byType(Navigator)).push(routeBuilder()); await tester.pump(); await tester.pump(const Duration(seconds: 1)); final Icon icon = tester.widget(find.byType(Icon)); expect(icon.icon, expectedIcon); expect(find.byType(CloseButton), findsOneWidget); } PageRoute<void> materialRouteBuilder() { return MaterialPageRoute<void>( builder: (BuildContext context) { return Scaffold(appBar: AppBar(), body: const Text('Page 2')); }, fullscreenDialog: true, ); } PageRoute<void> customPageRouteBuilder() { return _CustomPageRoute<void>( builder: (BuildContext context) { return Scaffold(appBar: AppBar(), body: const Text('Page 2')); }, fullscreenDialog: true, ); } testWidgets('Close button shows correctly on Android', (WidgetTester tester) async { await expectCloseIcon(tester, TargetPlatform.android, Icons.close, materialRouteBuilder); }); testWidgets('Close button shows correctly on Fuchsia', (WidgetTester tester) async { await expectCloseIcon(tester, TargetPlatform.fuchsia, Icons.close, materialRouteBuilder); }); testWidgets('Close button shows correctly on iOS', (WidgetTester tester) async { await expectCloseIcon(tester, TargetPlatform.iOS, Icons.close, materialRouteBuilder); }); testWidgets('Close button shows correctly with custom page route on Android', (WidgetTester tester) async { await expectCloseIcon(tester, TargetPlatform.android, Icons.close, customPageRouteBuilder); }); testWidgets('Close button shows correctly with custom page route on Fuchsia', (WidgetTester tester) async { await expectCloseIcon(tester, TargetPlatform.fuchsia, Icons.close, customPageRouteBuilder); }); testWidgets('Close button shows correctly with custom page route on iOS', (WidgetTester tester) async { await expectCloseIcon(tester, TargetPlatform.iOS, Icons.close, customPageRouteBuilder); }); }); group('body size', () { testWidgets('body size with container', (WidgetTester tester) async { final Key testKey = UniqueKey(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: MediaQuery( data: const MediaQueryData(), child: Scaffold( body: Container( key: testKey, ), ), ), )); expect(tester.element(find.byKey(testKey)).size, const Size(800.0, 600.0)); expect(tester.renderObject<RenderBox>(find.byKey(testKey)).localToGlobal(Offset.zero), const Offset(0.0, 0.0)); }); testWidgets('body size with sized container', (WidgetTester tester) async { final Key testKey = UniqueKey(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: MediaQuery( data: const MediaQueryData(), child: Scaffold( body: Container( key: testKey, height: 100.0, ), ), ), )); expect(tester.element(find.byKey(testKey)).size, const Size(800.0, 100.0)); expect(tester.renderObject<RenderBox>(find.byKey(testKey)).localToGlobal(Offset.zero), const Offset(0.0, 0.0)); }); testWidgets('body size with centered container', (WidgetTester tester) async { final Key testKey = UniqueKey(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: MediaQuery( data: const MediaQueryData(), child: Scaffold( body: Center( child: Container( key: testKey, ), ), ), ), )); expect(tester.element(find.byKey(testKey)).size, const Size(800.0, 600.0)); expect(tester.renderObject<RenderBox>(find.byKey(testKey)).localToGlobal(Offset.zero), const Offset(0.0, 0.0)); }); testWidgets('body size with button', (WidgetTester tester) async { final Key testKey = UniqueKey(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: MediaQuery( data: const MediaQueryData(), child: Scaffold( body: FlatButton( key: testKey, onPressed: () { }, child: const Text(''), ), ), ), )); expect(tester.element(find.byKey(testKey)).size, const Size(88.0, 48.0)); expect(tester.renderObject<RenderBox>(find.byKey(testKey)).localToGlobal(Offset.zero), const Offset(0.0, 0.0)); }); testWidgets('body size with extendBody', (WidgetTester tester) async { final Key bodyKey = UniqueKey(); double mediaQueryBottom; Widget buildFrame({ bool extendBody, bool resizeToAvoidBottomInset, double viewInsetBottom = 0.0 }) { return Directionality( textDirection: TextDirection.ltr, child: MediaQuery( data: MediaQueryData( viewInsets: EdgeInsets.only(bottom: viewInsetBottom), ), child: Scaffold( resizeToAvoidBottomInset: resizeToAvoidBottomInset, extendBody: extendBody, body: Builder( builder: (BuildContext context) { mediaQueryBottom = MediaQuery.of(context).padding.bottom; return Container(key: bodyKey); }, ), bottomNavigationBar: const BottomAppBar( child: SizedBox(height: 48.0,), ), ), ), ); } await tester.pumpWidget(buildFrame(extendBody: true)); expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 600.0)); expect(mediaQueryBottom, 48.0); await tester.pumpWidget(buildFrame(extendBody: false)); expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 552.0)); // 552 = 600 - 48 (BAB height) expect(mediaQueryBottom, 0.0); // If resizeToAvoidBottomInsets is false, same results as if it was unspecified (null). await tester.pumpWidget(buildFrame(extendBody: true, resizeToAvoidBottomInset: false, viewInsetBottom: 100.0)); expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 600.0)); expect(mediaQueryBottom, 48.0); await tester.pumpWidget(buildFrame(extendBody: false, resizeToAvoidBottomInset: false, viewInsetBottom: 100.0)); expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 552.0)); expect(mediaQueryBottom, 0.0); // If resizeToAvoidBottomInsets is true and viewInsets.bottom is > the bottom // navigation bar's height then the body always resizes and the MediaQuery // isn't adjusted. This case corresponds to the keyboard appearing. await tester.pumpWidget(buildFrame(extendBody: true, resizeToAvoidBottomInset: true, viewInsetBottom: 100.0)); expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 500.0)); expect(mediaQueryBottom, 0.0); await tester.pumpWidget(buildFrame(extendBody: false, resizeToAvoidBottomInset: true, viewInsetBottom: 100.0)); expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 500.0)); expect(mediaQueryBottom, 0.0); }); }); testWidgets('Open drawer hides underlying semantics tree', (WidgetTester tester) async { const String bodyLabel = 'I am the body'; const String persistentFooterButtonLabel = 'a button on the bottom'; const String bottomNavigationBarLabel = 'a bar in an app'; const String floatingActionButtonLabel = 'I float in space'; const String drawerLabel = 'I am the reason for this test'; final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget(const MaterialApp(home: Scaffold( body: Text(bodyLabel), persistentFooterButtons: <Widget>[Text(persistentFooterButtonLabel)], bottomNavigationBar: Text(bottomNavigationBarLabel), floatingActionButton: Text(floatingActionButtonLabel), drawer: Drawer(child: Text(drawerLabel)), ))); expect(semantics, includesNodeWith(label: bodyLabel)); expect(semantics, includesNodeWith(label: persistentFooterButtonLabel)); expect(semantics, includesNodeWith(label: bottomNavigationBarLabel)); expect(semantics, includesNodeWith(label: floatingActionButtonLabel)); expect(semantics, isNot(includesNodeWith(label: drawerLabel))); final ScaffoldState state = tester.firstState(find.byType(Scaffold)); state.openDrawer(); await tester.pump(); await tester.pump(const Duration(seconds: 1)); expect(semantics, isNot(includesNodeWith(label: bodyLabel))); expect(semantics, isNot(includesNodeWith(label: persistentFooterButtonLabel))); expect(semantics, isNot(includesNodeWith(label: bottomNavigationBarLabel))); expect(semantics, isNot(includesNodeWith(label: floatingActionButtonLabel))); expect(semantics, includesNodeWith(label: drawerLabel)); semantics.dispose(); }); testWidgets('Scaffold and extreme window padding', (WidgetTester tester) async { final Key appBar = UniqueKey(); final Key body = UniqueKey(); final Key floatingActionButton = UniqueKey(); final Key persistentFooterButton = UniqueKey(); final Key drawer = UniqueKey(); final Key bottomNavigationBar = UniqueKey(); final Key insideAppBar = UniqueKey(); final Key insideBody = UniqueKey(); final Key insideFloatingActionButton = UniqueKey(); final Key insidePersistentFooterButton = UniqueKey(); final Key insideDrawer = UniqueKey(); final Key insideBottomNavigationBar = UniqueKey(); await tester.pumpWidget( Localizations( locale: const Locale('en', 'us'), delegates: const <LocalizationsDelegate<dynamic>>[ DefaultWidgetsLocalizations.delegate, DefaultMaterialLocalizations.delegate, ], child: Directionality( textDirection: TextDirection.rtl, child: MediaQuery( data: const MediaQueryData( padding: EdgeInsets.only( left: 20.0, top: 30.0, right: 50.0, bottom: 60.0, ), viewInsets: EdgeInsets.only(bottom: 200.0), ), child: Scaffold( drawerDragStartBehavior: DragStartBehavior.down, appBar: PreferredSize( preferredSize: const Size(11.0, 13.0), child: Container( key: appBar, child: SafeArea( child: Placeholder(key: insideAppBar), ), ), ), body: Container( key: body, child: SafeArea( child: Placeholder(key: insideBody), ), ), floatingActionButton: SizedBox( key: floatingActionButton, width: 77.0, height: 77.0, child: SafeArea( child: Placeholder(key: insideFloatingActionButton), ), ), persistentFooterButtons: <Widget>[ SizedBox( key: persistentFooterButton, width: 100.0, height: 90.0, child: SafeArea( child: Placeholder(key: insidePersistentFooterButton), ), ), ], drawer: Container( key: drawer, width: 204.0, child: SafeArea( child: Placeholder(key: insideDrawer), ), ), bottomNavigationBar: SizedBox( key: bottomNavigationBar, height: 85.0, child: SafeArea( child: Placeholder(key: insideBottomNavigationBar), ), ), ), ), ), ), ); // open drawer await tester.flingFrom(const Offset(795.0, 5.0), const Offset(-200.0, 0.0), 10.0); await tester.pump(); await tester.pump(const Duration(seconds: 1)); expect(tester.getRect(find.byKey(appBar)), const Rect.fromLTRB(0.0, 0.0, 800.0, 43.0)); expect(tester.getRect(find.byKey(body)), const Rect.fromLTRB(0.0, 43.0, 800.0, 348.0)); expect(tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(const Rect.fromLTRB(36.0, 255.0, 113.0, 332.0))); expect(tester.getRect(find.byKey(persistentFooterButton)),const Rect.fromLTRB(28.0, 357.0, 128.0, 447.0)); // Note: has 8px each top/bottom padding. expect(tester.getRect(find.byKey(drawer)), const Rect.fromLTRB(596.0, 0.0, 800.0, 600.0)); expect(tester.getRect(find.byKey(bottomNavigationBar)), const Rect.fromLTRB(0.0, 515.0, 800.0, 600.0)); expect(tester.getRect(find.byKey(insideAppBar)), const Rect.fromLTRB(20.0, 30.0, 750.0, 43.0)); expect(tester.getRect(find.byKey(insideBody)), const Rect.fromLTRB(20.0, 43.0, 750.0, 348.0)); expect(tester.getRect(find.byKey(insideFloatingActionButton)), rectMoreOrLessEquals(const Rect.fromLTRB(36.0, 255.0, 113.0, 332.0))); expect(tester.getRect(find.byKey(insidePersistentFooterButton)), const Rect.fromLTRB(28.0, 357.0, 128.0, 447.0)); expect(tester.getRect(find.byKey(insideDrawer)), const Rect.fromLTRB(596.0, 30.0, 750.0, 540.0)); expect(tester.getRect(find.byKey(insideBottomNavigationBar)), const Rect.fromLTRB(20.0, 515.0, 750.0, 540.0)); }); testWidgets('Scaffold and extreme window padding - persistent footer buttons only', (WidgetTester tester) async { final Key appBar = UniqueKey(); final Key body = UniqueKey(); final Key floatingActionButton = UniqueKey(); final Key persistentFooterButton = UniqueKey(); final Key drawer = UniqueKey(); final Key insideAppBar = UniqueKey(); final Key insideBody = UniqueKey(); final Key insideFloatingActionButton = UniqueKey(); final Key insidePersistentFooterButton = UniqueKey(); final Key insideDrawer = UniqueKey(); await tester.pumpWidget( Localizations( locale: const Locale('en', 'us'), delegates: const <LocalizationsDelegate<dynamic>>[ DefaultWidgetsLocalizations.delegate, DefaultMaterialLocalizations.delegate, ], child: Directionality( textDirection: TextDirection.rtl, child: MediaQuery( data: const MediaQueryData( padding: EdgeInsets.only( left: 20.0, top: 30.0, right: 50.0, bottom: 60.0, ), viewInsets: EdgeInsets.only(bottom: 200.0), ), child: Scaffold( appBar: PreferredSize( preferredSize: const Size(11.0, 13.0), child: Container( key: appBar, child: SafeArea( child: Placeholder(key: insideAppBar), ), ), ), body: Container( key: body, child: SafeArea( child: Placeholder(key: insideBody), ), ), floatingActionButton: SizedBox( key: floatingActionButton, width: 77.0, height: 77.0, child: SafeArea( child: Placeholder(key: insideFloatingActionButton), ), ), persistentFooterButtons: <Widget>[ SizedBox( key: persistentFooterButton, width: 100.0, height: 90.0, child: SafeArea( child: Placeholder(key: insidePersistentFooterButton), ), ), ], drawer: Container( key: drawer, width: 204.0, child: SafeArea( child: Placeholder(key: insideDrawer), ), ), ), ), ), ), ); // open drawer await tester.flingFrom(const Offset(795.0, 5.0), const Offset(-200.0, 0.0), 10.0); await tester.pump(); await tester.pump(const Duration(seconds: 1)); expect(tester.getRect(find.byKey(appBar)), const Rect.fromLTRB(0.0, 0.0, 800.0, 43.0)); expect(tester.getRect(find.byKey(body)), const Rect.fromLTRB(0.0, 43.0, 800.0, 400.0)); expect(tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(const Rect.fromLTRB(36.0, 307.0, 113.0, 384.0))); expect(tester.getRect(find.byKey(persistentFooterButton)), const Rect.fromLTRB(28.0, 442.0, 128.0, 532.0)); // Note: has 8px each top/bottom padding. expect(tester.getRect(find.byKey(drawer)), const Rect.fromLTRB(596.0, 0.0, 800.0, 600.0)); expect(tester.getRect(find.byKey(insideAppBar)), const Rect.fromLTRB(20.0, 30.0, 750.0, 43.0)); expect(tester.getRect(find.byKey(insideBody)), const Rect.fromLTRB(20.0, 43.0, 750.0, 400.0)); expect(tester.getRect(find.byKey(insideFloatingActionButton)), rectMoreOrLessEquals(const Rect.fromLTRB(36.0, 307.0, 113.0, 384.0))); expect(tester.getRect(find.byKey(insidePersistentFooterButton)), const Rect.fromLTRB(28.0, 442.0, 128.0, 532.0)); expect(tester.getRect(find.byKey(insideDrawer)), const Rect.fromLTRB(596.0, 30.0, 750.0, 540.0)); }); group('ScaffoldGeometry', () { testWidgets('bottomNavigationBar', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); await tester.pumpWidget(MaterialApp(home: Scaffold( body: Container(), bottomNavigationBar: ConstrainedBox( key: key, constraints: const BoxConstraints.expand(height: 80.0), child: _GeometryListener(), ), ))); final RenderBox navigationBox = tester.renderObject(find.byKey(key)); final RenderBox appBox = tester.renderObject(find.byType(MaterialApp)); final _GeometryListenerState listenerState = tester.state(find.byType(_GeometryListener)); final ScaffoldGeometry geometry = listenerState.cache.value; expect( geometry.bottomNavigationBarTop, appBox.size.height - navigationBox.size.height, ); }); testWidgets('no bottomNavigationBar', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp(home: Scaffold( body: ConstrainedBox( constraints: const BoxConstraints.expand(height: 80.0), child: _GeometryListener(), ), ))); final _GeometryListenerState listenerState = tester.state(find.byType(_GeometryListener)); final ScaffoldGeometry geometry = listenerState.cache.value; expect( geometry.bottomNavigationBarTop, null, ); }); testWidgets('Scaffold BottomNavigationBar bottom padding is not consumed by viewInsets.', (WidgetTester tester) async { Widget boilerplate(Widget child) { return Localizations( locale: const Locale('en', 'us'), delegates: const <LocalizationsDelegate<dynamic>>[ DefaultWidgetsLocalizations.delegate, DefaultMaterialLocalizations.delegate, ], child: Directionality( textDirection: TextDirection.ltr, child: child, ), ); } final Widget child = boilerplate( Scaffold( resizeToAvoidBottomInset: false, body: const Placeholder(), bottomNavigationBar: BottomNavigationBar( items: const <BottomNavigationBarItem>[ BottomNavigationBarItem( icon: Icon(Icons.add), title: Text('test'), ), BottomNavigationBarItem( icon: Icon(Icons.add), title: Text('test'), ) ] ), ), ); await tester.pumpWidget( MediaQuery( data: const MediaQueryData(padding: EdgeInsets.only(bottom: 20.0)), child: child, ), ); final Offset initialPoint = tester.getCenter(find.byType(Placeholder)); // Consume bottom padding - as if by the keyboard opening await tester.pumpWidget( MediaQuery( data: const MediaQueryData( padding: EdgeInsets.zero, viewPadding: EdgeInsets.only(bottom: 20), viewInsets: EdgeInsets.only(bottom: 300), ), child: child, ), ); final Offset finalPoint = tester.getCenter(find.byType(Placeholder)); expect(initialPoint, finalPoint); }); testWidgets('floatingActionButton', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); await tester.pumpWidget(MaterialApp(home: Scaffold( body: Container(), floatingActionButton: FloatingActionButton( key: key, child: _GeometryListener(), onPressed: () { }, ), ))); final RenderBox floatingActionButtonBox = tester.renderObject(find.byKey(key)); final _GeometryListenerState listenerState = tester.state(find.byType(_GeometryListener)); final ScaffoldGeometry geometry = listenerState.cache.value; final Rect fabRect = floatingActionButtonBox.localToGlobal(Offset.zero) & floatingActionButtonBox.size; expect( geometry.floatingActionButtonArea, fabRect, ); }); testWidgets('no floatingActionButton', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp(home: Scaffold( body: ConstrainedBox( constraints: const BoxConstraints.expand(height: 80.0), child: _GeometryListener(), ), ))); final _GeometryListenerState listenerState = tester.state(find.byType(_GeometryListener)); final ScaffoldGeometry geometry = listenerState.cache.value; expect( geometry.floatingActionButtonArea, null, ); }); testWidgets('floatingActionButton entrance/exit animation', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); await tester.pumpWidget(MaterialApp(home: Scaffold( body: ConstrainedBox( constraints: const BoxConstraints.expand(height: 80.0), child: _GeometryListener(), ), ))); await tester.pumpWidget(MaterialApp(home: Scaffold( body: Container(), floatingActionButton: FloatingActionButton( key: key, child: _GeometryListener(), onPressed: () { }, ), ))); final _GeometryListenerState listenerState = tester.state(find.byType(_GeometryListener)); await tester.pump(const Duration(milliseconds: 50)); ScaffoldGeometry geometry = listenerState.cache.value; final Rect transitioningFabRect = geometry.floatingActionButtonArea; final double transitioningRotation = tester.widget<RotationTransition>( find.byType(RotationTransition), ).turns.value; await tester.pump(const Duration(seconds: 3)); geometry = listenerState.cache.value; final RenderBox floatingActionButtonBox = tester.renderObject(find.byKey(key)); final Rect fabRect = floatingActionButtonBox.localToGlobal(Offset.zero) & floatingActionButtonBox.size; final double completedRotation = tester.widget<RotationTransition>( find.byType(RotationTransition), ).turns.value; expect(transitioningRotation, lessThan(1.0)); expect(completedRotation, equals(1.0)); expect( geometry.floatingActionButtonArea, fabRect, ); expect( geometry.floatingActionButtonArea.center, transitioningFabRect.center, ); expect( geometry.floatingActionButtonArea.width, greaterThan(transitioningFabRect.width), ); expect( geometry.floatingActionButtonArea.height, greaterThan(transitioningFabRect.height), ); }); testWidgets('change notifications', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); int numNotificationsAtLastFrame = 0; await tester.pumpWidget(MaterialApp(home: Scaffold( body: ConstrainedBox( constraints: const BoxConstraints.expand(height: 80.0), child: _GeometryListener(), ), ))); final _GeometryListenerState listenerState = tester.state(find.byType(_GeometryListener)); expect(listenerState.numNotifications, greaterThan(numNotificationsAtLastFrame)); numNotificationsAtLastFrame = listenerState.numNotifications; await tester.pumpWidget(MaterialApp(home: Scaffold( body: Container(), floatingActionButton: FloatingActionButton( key: key, child: _GeometryListener(), onPressed: () { }, ), ))); expect(listenerState.numNotifications, greaterThan(numNotificationsAtLastFrame)); numNotificationsAtLastFrame = listenerState.numNotifications; await tester.pump(const Duration(milliseconds: 50)); expect(listenerState.numNotifications, greaterThan(numNotificationsAtLastFrame)); numNotificationsAtLastFrame = listenerState.numNotifications; await tester.pump(const Duration(seconds: 3)); expect(listenerState.numNotifications, greaterThan(numNotificationsAtLastFrame)); numNotificationsAtLastFrame = listenerState.numNotifications; }); testWidgets('Simultaneous drawers on either side', (WidgetTester tester) async { const String bodyLabel = 'I am the body'; const String drawerLabel = 'I am the label on start side'; const String endDrawerLabel = 'I am the label on end side'; final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget(const MaterialApp(home: Scaffold( body: Text(bodyLabel), drawer: Drawer(child: Text(drawerLabel)), endDrawer: Drawer(child: Text(endDrawerLabel)), ))); expect(semantics, includesNodeWith(label: bodyLabel)); expect(semantics, isNot(includesNodeWith(label: drawerLabel))); expect(semantics, isNot(includesNodeWith(label: endDrawerLabel))); final ScaffoldState state = tester.firstState(find.byType(Scaffold)); state.openDrawer(); await tester.pump(); await tester.pump(const Duration(seconds: 1)); expect(semantics, isNot(includesNodeWith(label: bodyLabel))); expect(semantics, includesNodeWith(label: drawerLabel)); state.openEndDrawer(); await tester.pump(); await tester.pump(const Duration(seconds: 1)); expect(semantics, isNot(includesNodeWith(label: bodyLabel))); expect(semantics, includesNodeWith(label: endDrawerLabel)); semantics.dispose(); }); testWidgets('Drawer state query correctly', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: SafeArea( left: false, top: true, right: false, bottom: false, child: Scaffold( endDrawer: const Drawer( child: Text('endDrawer'), ), drawer: const Drawer( child: Text('drawer'), ), body: const Text('scaffold body'), appBar: AppBar( centerTitle: true, title: const Text('Title'), ), ), ), ), ); final ScaffoldState scaffoldState = tester.state(find.byType(Scaffold)); final Finder drawerOpenButton = find.byType(IconButton).first; final Finder endDrawerOpenButton = find.byType(IconButton).last; await tester.tap(drawerOpenButton); await tester.pumpAndSettle(); expect(true, scaffoldState.isDrawerOpen); await tester.tap(endDrawerOpenButton); await tester.pumpAndSettle(); expect(false, scaffoldState.isDrawerOpen); await tester.tap(endDrawerOpenButton); await tester.pumpAndSettle(); expect(true, scaffoldState.isEndDrawerOpen); await tester.tap(drawerOpenButton); await tester.pumpAndSettle(); expect(false, scaffoldState.isEndDrawerOpen); scaffoldState.openDrawer(); expect(true, scaffoldState.isDrawerOpen); await tester.tap(drawerOpenButton); await tester.pumpAndSettle(); scaffoldState.openEndDrawer(); expect(true, scaffoldState.isEndDrawerOpen); }); testWidgets('Dual Drawer Opening', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: SafeArea( left: false, top: true, right: false, bottom: false, child: Scaffold( endDrawer: const Drawer( child: Text('endDrawer'), ), drawer: const Drawer( child: Text('drawer'), ), body: const Text('scaffold body'), appBar: AppBar( centerTitle: true, title: const Text('Title'), ), ), ), ), ); // Open Drawer, tap on end drawer, which closes the drawer, but does // not open the drawer. await tester.tap(find.byType(IconButton).first); await tester.pumpAndSettle(); await tester.tap(find.byType(IconButton).last); await tester.pumpAndSettle(); expect(find.text('endDrawer'), findsNothing); expect(find.text('drawer'), findsNothing); // Tapping the first opens the first drawer await tester.tap(find.byType(IconButton).first); await tester.pumpAndSettle(); expect(find.text('endDrawer'), findsNothing); expect(find.text('drawer'), findsOneWidget); // Tapping on the end drawer and then on the drawer should close the // drawer and then reopen it. await tester.tap(find.byType(IconButton).last); await tester.pumpAndSettle(); await tester.tap(find.byType(IconButton).first); await tester.pumpAndSettle(); expect(find.text('endDrawer'), findsNothing); expect(find.text('drawer'), findsOneWidget); }); testWidgets('Drawer opens correctly with padding from MediaQuery (LTR)', (WidgetTester tester) async { const double simulatedNotchSize = 40.0; await tester.pumpWidget( MaterialApp( home: Scaffold( drawer: const Drawer( child: Text('Drawer'), ), body: const Text('Scaffold Body'), appBar: AppBar( centerTitle: true, title: const Text('Title'), ), ), ), ); ScaffoldState scaffoldState = tester.state(find.byType(Scaffold)); expect(scaffoldState.isDrawerOpen, false); await tester.dragFrom(const Offset(simulatedNotchSize + 15.0, 100), const Offset(300, 0)); await tester.pumpAndSettle(); expect(scaffoldState.isDrawerOpen, false); await tester.pumpWidget( MaterialApp( home: MediaQuery( data: const MediaQueryData( padding: EdgeInsets.fromLTRB(simulatedNotchSize, 0, 0, 0), ), child: Scaffold( drawer: const Drawer( child: Text('Drawer'), ), body: const Text('Scaffold Body'), appBar: AppBar( centerTitle: true, title: const Text('Title'), ), ), ), ), ); scaffoldState = tester.state(find.byType(Scaffold)); expect(scaffoldState.isDrawerOpen, false); await tester.dragFrom( const Offset(simulatedNotchSize + 15.0, 100), const Offset(300, 0), ); await tester.pumpAndSettle(); expect(scaffoldState.isDrawerOpen, true); }); testWidgets('Drawer opens correctly with padding from MediaQuery (RTL)', (WidgetTester tester) async { const double simulatedNotchSize = 40.0; await tester.pumpWidget( MaterialApp( home: Scaffold( drawer: const Drawer( child: Text('Drawer'), ), body: const Text('Scaffold Body'), appBar: AppBar( centerTitle: true, title: const Text('Title'), ), ), ), ); final double scaffoldWidth = tester.renderObject<RenderBox>( find.byType(Scaffold), ).size.width; ScaffoldState scaffoldState = tester.state(find.byType(Scaffold)); expect(scaffoldState.isDrawerOpen, false); await tester.dragFrom( Offset(scaffoldWidth - simulatedNotchSize - 15.0, 100), const Offset(-300, 0), ); await tester.pumpAndSettle(); expect(scaffoldState.isDrawerOpen, false); await tester.pumpWidget( MaterialApp( home: MediaQuery( data: const MediaQueryData( padding: EdgeInsets.fromLTRB(0, 0, simulatedNotchSize, 0), ), child: Directionality( textDirection: TextDirection.rtl, child: Scaffold( drawer: const Drawer( child: Text('Drawer'), ), body: const Text('Scaffold body'), appBar: AppBar( centerTitle: true, title: const Text('Title'), ), ), ), ), ), ); scaffoldState = tester.state(find.byType(Scaffold)); expect(scaffoldState.isDrawerOpen, false); await tester.dragFrom( Offset(scaffoldWidth - simulatedNotchSize - 15.0, 100), const Offset(-300, 0), ); await tester.pumpAndSettle(); expect(scaffoldState.isDrawerOpen, true); }); }); testWidgets('Drawer opens correctly with custom edgeDragWidth', (WidgetTester tester) async { // The default edge drag width is 20.0. await tester.pumpWidget( MaterialApp( home: Scaffold( drawer: const Drawer( child: Text('Drawer'), ), body: const Text('Scaffold body'), appBar: AppBar( centerTitle: true, title: const Text('Title'), ), ), ), ); ScaffoldState scaffoldState = tester.state(find.byType(Scaffold)); expect(scaffoldState.isDrawerOpen, false); await tester.dragFrom(const Offset(35, 100), const Offset(300, 0)); await tester.pumpAndSettle(); expect(scaffoldState.isDrawerOpen, false); await tester.pumpWidget( MaterialApp( home: Scaffold( drawer: const Drawer( child: Text('Drawer'), ), drawerEdgeDragWidth: 40.0, body: const Text('Scaffold Body'), appBar: AppBar( centerTitle: true, title: const Text('Title'), ), ), ), ); scaffoldState = tester.state(find.byType(Scaffold)); expect(scaffoldState.isDrawerOpen, false); await tester.dragFrom(const Offset(35, 100), const Offset(300, 0)); await tester.pumpAndSettle(); expect(scaffoldState.isDrawerOpen, true); }); testWidgets('Nested scaffold body insets', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/20295 final Key bodyKey = UniqueKey(); Widget buildFrame(bool innerResizeToAvoidBottomInset, bool outerResizeToAvoidBottomInset) { return Directionality( textDirection: TextDirection.ltr, child: MediaQuery( data: const MediaQueryData(viewInsets: EdgeInsets.only(bottom: 100.0)), child: Builder( builder: (BuildContext context) { return Scaffold( resizeToAvoidBottomInset: outerResizeToAvoidBottomInset, body: Builder( builder: (BuildContext context) { return Scaffold( resizeToAvoidBottomInset: innerResizeToAvoidBottomInset, body: Container(key: bodyKey), ); }, ), ); }, ), ), ); } await tester.pumpWidget(buildFrame(true, true)); expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 500.0)); await tester.pumpWidget(buildFrame(false, true)); expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 500.0)); await tester.pumpWidget(buildFrame(true, false)); expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 500.0)); // This is the only case where the body is not bottom inset. await tester.pumpWidget(buildFrame(false, false)); expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 600.0)); await tester.pumpWidget(buildFrame(null, null)); // resizeToAvoidBottomInset default is true expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 500.0)); await tester.pumpWidget(buildFrame(null, false)); expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 500.0)); await tester.pumpWidget(buildFrame(false, null)); expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 500.0)); }); } class _GeometryListener extends StatefulWidget { @override _GeometryListenerState createState() => _GeometryListenerState(); } class _GeometryListenerState extends State<_GeometryListener> { @override Widget build(BuildContext context) { return CustomPaint( painter: cache ); } int numNotifications = 0; ValueListenable<ScaffoldGeometry> geometryListenable; _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; } } // 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; ScaffoldGeometry value; @override void paint(Canvas canvas, Size size) { value = geometryListenable.value; } @override bool shouldRepaint(_GeometryCachePainter oldDelegate) { return true; } } class _CustomPageRoute<T> extends PageRoute<T> { _CustomPageRoute({ @required this.builder, RouteSettings settings = const RouteSettings(), this.maintainState = true, bool fullscreenDialog = false, }) : assert(builder != null), super(settings: settings, fullscreenDialog: fullscreenDialog); final WidgetBuilder builder; @override Duration get transitionDuration => const Duration(milliseconds: 300); @override Color get barrierColor => null; @override String get barrierLabel => null; @override final bool maintainState; @override Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) { return builder(context); } @override Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) { return child; } }