Anis Alibegić 81d80c587d
Fixed a lot of typos (#141431)
Fair amount of typos spotted and fixed. Some of them are in comments, some of them are in code and some of them are in nondart files.

There is no need for issues since it's a typo fix.

I have doubts about [packages/flutter_tools/lib/src/ios/core_devices.dart](https://github.com/flutter/flutter/compare/master...anisalibegic:flutter:master#diff-fdbc1496b4bbe7e2b445a567fd385677af861c0093774e3d8cc460fdd5b794fa), I have a feeling it might broke some things on the other end, even though it's a typo.
2024-01-12 22:10:25 +00:00

1695 lines
56 KiB
Dart

// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
class StateMarker extends StatefulWidget {
const StateMarker({ super.key, this.child });
final Widget? child;
@override
StateMarkerState createState() => StateMarkerState();
}
class StateMarkerState extends State<StateMarker> {
late String marker;
@override
Widget build(BuildContext context) {
if (widget.child != null) {
return widget.child!;
}
return Container();
}
}
void main() {
testWidgets('Can nest apps', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: MaterialApp(
home: Text('Home sweet home'),
),
),
);
expect(find.text('Home sweet home'), findsOneWidget);
});
testWidgets('Focus handling', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode();
addTearDown(focusNode.dispose);
await tester.pumpWidget(MaterialApp(
home: Material(
child: Center(
child: TextField(focusNode: focusNode, autofocus: true),
),
),
));
expect(focusNode.hasFocus, isTrue);
});
testWidgets('Can place app inside FocusScope', (WidgetTester tester) async {
final FocusScopeNode focusScopeNode = FocusScopeNode();
await tester.pumpWidget(FocusScope(
autofocus: true,
node: focusScopeNode,
child: const MaterialApp(
home: Text('Home'),
),
));
expect(find.text('Home'), findsOneWidget);
focusScopeNode.dispose();
});
testWidgets('Can show grid without losing sync', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: StateMarker(),
),
);
final StateMarkerState state1 = tester.state(find.byType(StateMarker));
state1.marker = 'original';
await tester.pumpWidget(
const MaterialApp(
debugShowMaterialGrid: true,
home: StateMarker(),
),
);
final StateMarkerState state2 = tester.state(find.byType(StateMarker));
expect(state1, equals(state2));
expect(state2.marker, equals('original'));
});
testWidgets('Do not rebuild page during a route transition', (WidgetTester tester) async {
int buildCounter = 0;
await tester.pumpWidget(
MaterialApp(
home: Builder(
builder: (BuildContext context) {
return Material(
child: ElevatedButton(
child: const Text('X'),
onPressed: () { Navigator.of(context).pushNamed('/next'); },
),
);
},
),
routes: <String, WidgetBuilder>{
'/next': (BuildContext context) {
return Builder(
builder: (BuildContext context) {
++buildCounter;
return const Text('Y');
},
);
},
},
),
);
expect(buildCounter, 0);
await tester.tap(find.text('X'));
expect(buildCounter, 0);
await tester.pump();
expect(buildCounter, 1);
await tester.pump(const Duration(milliseconds: 10));
expect(buildCounter, 1);
await tester.pump(const Duration(milliseconds: 10));
expect(buildCounter, 1);
await tester.pump(const Duration(milliseconds: 10));
expect(buildCounter, 1);
await tester.pump(const Duration(milliseconds: 10));
expect(buildCounter, 1);
await tester.pump(const Duration(seconds: 1));
expect(buildCounter, 1);
expect(find.text('Y'), findsOneWidget);
});
testWidgets('Do rebuild the home page if it changes', (WidgetTester tester) async {
int buildCounter = 0;
await tester.pumpWidget(
MaterialApp(
home: Builder(
builder: (BuildContext context) {
++buildCounter;
return const Text('A');
},
),
),
);
expect(buildCounter, 1);
expect(find.text('A'), findsOneWidget);
await tester.pumpWidget(
MaterialApp(
home: Builder(
builder: (BuildContext context) {
++buildCounter;
return const Text('B');
},
),
),
);
expect(buildCounter, 2);
expect(find.text('B'), findsOneWidget);
});
testWidgets('Do not rebuild the home page if it does not actually change', (WidgetTester tester) async {
int buildCounter = 0;
final Widget home = Builder(
builder: (BuildContext context) {
++buildCounter;
return const Placeholder();
},
);
await tester.pumpWidget(
MaterialApp(
home: home,
),
);
expect(buildCounter, 1);
await tester.pumpWidget(
MaterialApp(
home: home,
),
);
expect(buildCounter, 1);
});
testWidgets('Do rebuild pages that come from the routes table if the MaterialApp changes', (WidgetTester tester) async {
int buildCounter = 0;
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) {
++buildCounter;
return const Placeholder();
},
};
await tester.pumpWidget(
MaterialApp(
routes: routes,
),
);
expect(buildCounter, 1);
await tester.pumpWidget(
MaterialApp(
routes: routes,
),
);
expect(buildCounter, 2);
});
testWidgets('Cannot pop the initial route', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(home: Text('Home')));
expect(find.text('Home'), findsOneWidget);
final NavigatorState navigator = tester.state(find.byType(Navigator));
final bool result = await navigator.maybePop();
expect(result, isFalse);
expect(find.text('Home'), findsOneWidget);
});
testWidgets('Default initialRoute', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(routes: <String, WidgetBuilder>{
'/': (BuildContext context) => const Text('route "/"'),
}));
expect(find.text('route "/"'), findsOneWidget);
});
testWidgets('One-step initial route', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
initialRoute: '/a',
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => const Text('route "/"'),
'/a': (BuildContext context) => const Text('route "/a"'),
'/a/b': (BuildContext context) => const Text('route "/a/b"'),
'/b': (BuildContext context) => const Text('route "/b"'),
},
),
);
expect(find.text('route "/"', skipOffstage: false), findsOneWidget);
expect(find.text('route "/a"'), findsOneWidget);
expect(find.text('route "/a/b"', skipOffstage: false), findsNothing);
expect(find.text('route "/b"', skipOffstage: false), findsNothing);
});
testWidgets('Return value from pop is correct', (WidgetTester tester) async {
late Future<Object?> result;
await tester.pumpWidget(
MaterialApp(
home: Builder(
builder: (BuildContext context) {
return Material(
child: ElevatedButton(
child: const Text('X'),
onPressed: () async {
result = Navigator.of(context).pushNamed<Object?>('/a');
},
),
);
},
),
routes: <String, WidgetBuilder>{
'/a': (BuildContext context) {
return Material(
child: ElevatedButton(
child: const Text('Y'),
onPressed: () {
Navigator.of(context).pop('all done');
},
),
);
},
},
),
);
await tester.tap(find.text('X'));
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(find.text('Y'), findsOneWidget);
await tester.tap(find.text('Y'));
await tester.pump();
expect(await result, equals('all done'));
});
testWidgets('Two-step initial route', (WidgetTester tester) async {
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => const Text('route "/"'),
'/a': (BuildContext context) => const Text('route "/a"'),
'/a/b': (BuildContext context) => const Text('route "/a/b"'),
'/b': (BuildContext context) => const Text('route "/b"'),
};
await tester.pumpWidget(
MaterialApp(
initialRoute: '/a/b',
routes: routes,
),
);
expect(find.text('route "/"', skipOffstage: false), findsOneWidget);
expect(find.text('route "/a"', skipOffstage: false), findsOneWidget);
expect(find.text('route "/a/b"'), findsOneWidget);
expect(find.text('route "/b"', skipOffstage: false), findsNothing);
});
testWidgets('Initial route with missing step', (WidgetTester tester) async {
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => const Text('route "/"'),
'/a': (BuildContext context) => const Text('route "/a"'),
'/a/b': (BuildContext context) => const Text('route "/a/b"'),
'/b': (BuildContext context) => const Text('route "/b"'),
};
await tester.pumpWidget(
MaterialApp(
initialRoute: '/a/b/c',
routes: routes,
),
);
final dynamic exception = tester.takeException();
expect(exception, isA<String>());
if (exception is String) {
expect(exception.startsWith('Could not navigate to initial route.'), isTrue);
expect(find.text('route "/"'), findsOneWidget);
expect(find.text('route "/a"'), findsNothing);
expect(find.text('route "/a/b"'), findsNothing);
expect(find.text('route "/b"'), findsNothing);
}
});
testWidgets('Make sure initialRoute is only used the first time', (WidgetTester tester) async {
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => const Text('route "/"'),
'/a': (BuildContext context) => const Text('route "/a"'),
'/b': (BuildContext context) => const Text('route "/b"'),
};
await tester.pumpWidget(
MaterialApp(
initialRoute: '/a',
routes: routes,
),
);
expect(find.text('route "/"', skipOffstage: false), findsOneWidget);
expect(find.text('route "/a"'), findsOneWidget);
expect(find.text('route "/b"', skipOffstage: false), findsNothing);
// changing initialRoute has no effect
await tester.pumpWidget(
MaterialApp(
initialRoute: '/b',
routes: routes,
),
);
expect(find.text('route "/"', skipOffstage: false), findsOneWidget);
expect(find.text('route "/a"'), findsOneWidget);
expect(find.text('route "/b"', skipOffstage: false), findsNothing);
// removing it has no effect
await tester.pumpWidget(MaterialApp(routes: routes));
expect(find.text('route "/"', skipOffstage: false), findsOneWidget);
expect(find.text('route "/a"'), findsOneWidget);
expect(find.text('route "/b"', skipOffstage: false), findsNothing);
});
testWidgets('onGenerateRoute / onUnknownRoute', (WidgetTester tester) async {
final List<String> log = <String>[];
await tester.pumpWidget(
MaterialApp(
onGenerateRoute: (RouteSettings settings) {
log.add('onGenerateRoute ${settings.name}');
return null;
},
onUnknownRoute: (RouteSettings settings) {
log.add('onUnknownRoute ${settings.name}');
return null;
},
),
);
expect(tester.takeException(), isFlutterError);
expect(log, <String>['onGenerateRoute /', 'onUnknownRoute /']);
// Work-around for https://github.com/flutter/flutter/issues/65655.
await tester.pumpWidget(Container());
expect(tester.takeException(), isAssertionError);
});
testWidgets('MaterialApp with builder and no route information works.', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/18904
await tester.pumpWidget(
MaterialApp(
builder: (BuildContext context, Widget? child) {
return const SizedBox();
},
),
);
});
testWidgets("WidgetsApp doesn't rebuild routes when MediaQuery updates", (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/37878
addTearDown(tester.platformDispatcher.clearAllTestValues);
addTearDown(tester.view.reset);
int routeBuildCount = 0;
int dependentBuildCount = 0;
await tester.pumpWidget(WidgetsApp(
color: const Color.fromARGB(255, 255, 255, 255),
onGenerateRoute: (_) {
return PageRouteBuilder<void>(pageBuilder: (_, __, ___) {
routeBuildCount++;
return Builder(
builder: (BuildContext context) {
dependentBuildCount++;
MediaQuery.of(context);
return Container();
},
);
});
},
));
expect(routeBuildCount, equals(1));
expect(dependentBuildCount, equals(1));
// didChangeMetrics
tester.view.physicalSize = const Size(42, 42);
await tester.pump();
expect(routeBuildCount, equals(1));
expect(dependentBuildCount, equals(2));
// didChangeTextScaleFactor
tester.platformDispatcher.textScaleFactorTestValue = 42;
await tester.pump();
expect(routeBuildCount, equals(1));
expect(dependentBuildCount, equals(3));
// didChangePlatformBrightness
tester.platformDispatcher.platformBrightnessTestValue = Brightness.dark;
await tester.pump();
expect(routeBuildCount, equals(1));
expect(dependentBuildCount, equals(4));
// didChangeAccessibilityFeatures
tester.platformDispatcher.accessibilityFeaturesTestValue = FakeAccessibilityFeatures.allOn;
await tester.pump();
expect(routeBuildCount, equals(1));
expect(dependentBuildCount, equals(5));
});
testWidgets('Can get text scale from media query', (WidgetTester tester) async {
TextScaler? textScaler;
await tester.pumpWidget(MaterialApp(
home: Builder(builder:(BuildContext context) {
textScaler = MediaQuery.textScalerOf(context);
return Container();
}),
));
expect(textScaler, TextScaler.noScaling);
});
testWidgets('MaterialApp.navigatorKey', (WidgetTester tester) async {
final GlobalKey<NavigatorState> key = GlobalKey<NavigatorState>();
await tester.pumpWidget(MaterialApp(
navigatorKey: key,
color: const Color(0xFF112233),
home: const Placeholder(),
));
expect(key.currentState, isA<NavigatorState>());
await tester.pumpWidget(const MaterialApp(
color: Color(0xFF112233),
home: Placeholder(),
));
expect(key.currentState, isNull);
await tester.pumpWidget(MaterialApp(
navigatorKey: key,
color: const Color(0xFF112233),
home: const Placeholder(),
));
expect(key.currentState, isA<NavigatorState>());
});
testWidgets('Has default material and cupertino localizations', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Builder(
builder: (BuildContext context) {
return Column(
children: <Widget>[
Text(MaterialLocalizations.of(context).selectAllButtonLabel),
Text(CupertinoLocalizations.of(context).selectAllButtonLabel),
],
);
},
),
),
);
// Default US "select all" text.
expect(find.text('Select all'), findsOneWidget);
// Default Cupertino US "select all" text.
expect(find.text('Select All'), findsOneWidget);
});
testWidgets('MaterialApp uses regular theme when themeMode is light', (WidgetTester tester) async {
addTearDown(tester.platformDispatcher.clearAllTestValues);
// Mock the test to explicitly report a light platformBrightness.
tester.platformDispatcher.platformBrightnessTestValue = Brightness.light;
late ThemeData appliedTheme;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
brightness: Brightness.light,
),
darkTheme: ThemeData(
brightness: Brightness.dark,
),
themeMode: ThemeMode.light,
home: Builder(
builder: (BuildContext context) {
appliedTheme = Theme.of(context);
return const SizedBox();
},
),
),
);
expect(appliedTheme.brightness, Brightness.light);
// Mock the test to explicitly report a dark platformBrightness.
tester.platformDispatcher.platformBrightnessTestValue = Brightness.dark;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
brightness: Brightness.light,
),
darkTheme: ThemeData(
brightness: Brightness.dark,
),
themeMode: ThemeMode.light,
home: Builder(
builder: (BuildContext context) {
appliedTheme = Theme.of(context);
return const SizedBox();
},
),
),
);
expect(appliedTheme.brightness, Brightness.light);
});
testWidgets('MaterialApp uses darkTheme when themeMode is dark', (WidgetTester tester) async {
addTearDown(tester.platformDispatcher.clearAllTestValues);
// Mock the test to explicitly report a light platformBrightness.
tester.platformDispatcher.platformBrightnessTestValue = Brightness.light;
late ThemeData appliedTheme;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
brightness: Brightness.light,
),
darkTheme: ThemeData(
brightness: Brightness.dark,
),
themeMode: ThemeMode.dark,
home: Builder(
builder: (BuildContext context) {
appliedTheme = Theme.of(context);
return const SizedBox();
},
),
),
);
expect(appliedTheme.brightness, Brightness.dark);
// Mock the test to explicitly report a dark platformBrightness.
tester.platformDispatcher.platformBrightnessTestValue = Brightness.dark;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
brightness: Brightness.light,
),
darkTheme: ThemeData(
brightness: Brightness.dark,
),
themeMode: ThemeMode.dark,
home: Builder(
builder: (BuildContext context) {
appliedTheme = Theme.of(context);
return const SizedBox();
},
),
),
);
expect(appliedTheme.brightness, Brightness.dark);
});
testWidgets('MaterialApp uses regular theme when themeMode is system and platformBrightness is light', (WidgetTester tester) async {
addTearDown(tester.platformDispatcher.clearAllTestValues);
// Mock the test to explicitly report a light platformBrightness.
tester.platformDispatcher.platformBrightnessTestValue = Brightness.light;
late ThemeData appliedTheme;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
brightness: Brightness.light,
),
darkTheme: ThemeData(
brightness: Brightness.dark,
),
home: Builder(
builder: (BuildContext context) {
appliedTheme = Theme.of(context);
return const SizedBox();
},
),
),
);
expect(appliedTheme.brightness, Brightness.light);
});
testWidgets('MaterialApp uses darkTheme when themeMode is system and platformBrightness is dark', (WidgetTester tester) async {
addTearDown(tester.platformDispatcher.clearAllTestValues);
// Mock the test to explicitly report a dark platformBrightness.
tester.platformDispatcher.platformBrightnessTestValue = Brightness.dark;
late ThemeData appliedTheme;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
brightness: Brightness.light,
),
darkTheme: ThemeData(
brightness: Brightness.dark,
),
home: Builder(
builder: (BuildContext context) {
appliedTheme = Theme.of(context);
return const SizedBox();
},
),
),
);
expect(appliedTheme.brightness, Brightness.dark);
});
testWidgets('MaterialApp uses light theme when platformBrightness is dark but no dark theme is provided', (WidgetTester tester) async {
addTearDown(tester.platformDispatcher.clearAllTestValues);
// Mock the test to explicitly report a dark platformBrightness.
tester.platformDispatcher.platformBrightnessTestValue = Brightness.dark;
late ThemeData appliedTheme;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
brightness: Brightness.light,
),
home: Builder(
builder: (BuildContext context) {
appliedTheme = Theme.of(context);
return const SizedBox();
},
),
),
);
expect(appliedTheme.brightness, Brightness.light);
});
testWidgets('MaterialApp uses fallback light theme when platformBrightness is dark but no theme is provided at all', (WidgetTester tester) async {
addTearDown(tester.platformDispatcher.clearAllTestValues);
// Mock the test to explicitly report a dark platformBrightness.
tester.platformDispatcher.platformBrightnessTestValue = Brightness.dark;
late ThemeData appliedTheme;
await tester.pumpWidget(
MaterialApp(
home: Builder(
builder: (BuildContext context) {
appliedTheme = Theme.of(context);
return const SizedBox();
},
),
),
);
expect(appliedTheme.brightness, Brightness.light);
});
testWidgets('MaterialApp uses fallback light theme when platformBrightness is light and a dark theme is provided', (WidgetTester tester) async {
addTearDown(tester.platformDispatcher.clearAllTestValues);
// Mock the test to explicitly report a dark platformBrightness.
tester.platformDispatcher.platformBrightnessTestValue = Brightness.light;
late ThemeData appliedTheme;
await tester.pumpWidget(
MaterialApp(
darkTheme: ThemeData(
brightness: Brightness.dark,
),
home: Builder(
builder: (BuildContext context) {
appliedTheme = Theme.of(context);
return const SizedBox();
},
),
),
);
expect(appliedTheme.brightness, Brightness.light);
});
testWidgets('MaterialApp uses dark theme when platformBrightness is dark', (WidgetTester tester) async {
addTearDown(tester.platformDispatcher.clearAllTestValues);
// Mock the test to explicitly report a dark platformBrightness.
tester.platformDispatcher.platformBrightnessTestValue = Brightness.dark;
late ThemeData appliedTheme;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
brightness: Brightness.light,
),
darkTheme: ThemeData(
brightness: Brightness.dark,
),
home: Builder(
builder: (BuildContext context) {
appliedTheme = Theme.of(context);
return const SizedBox();
},
),
),
);
expect(appliedTheme.brightness, Brightness.dark);
});
testWidgets('MaterialApp uses high contrast theme when appropriate', (WidgetTester tester) async {
addTearDown(tester.platformDispatcher.clearAllTestValues);
tester.platformDispatcher.platformBrightnessTestValue = Brightness.light;
tester.platformDispatcher.accessibilityFeaturesTestValue = FakeAccessibilityFeatures.allOn;
late ThemeData appliedTheme;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
primaryColor: Colors.lightBlue,
),
highContrastTheme: ThemeData(
primaryColor: Colors.blue,
),
home: Builder(
builder: (BuildContext context) {
appliedTheme = Theme.of(context);
return const SizedBox();
},
),
),
);
expect(appliedTheme.primaryColor, Colors.blue);
});
testWidgets('MaterialApp uses high contrast dark theme when appropriate', (WidgetTester tester) async {
addTearDown(tester.platformDispatcher.clearAllTestValues);
tester.platformDispatcher.platformBrightnessTestValue = Brightness.dark;
tester.platformDispatcher.accessibilityFeaturesTestValue = FakeAccessibilityFeatures.allOn;
late ThemeData appliedTheme;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
primaryColor: Colors.lightBlue,
),
darkTheme: ThemeData(
primaryColor: Colors.lightGreen,
),
highContrastTheme: ThemeData(
primaryColor: Colors.blue,
),
highContrastDarkTheme: ThemeData(
primaryColor: Colors.green,
),
home: Builder(
builder: (BuildContext context) {
appliedTheme = Theme.of(context);
return const SizedBox();
},
),
),
);
expect(appliedTheme.primaryColor, Colors.green);
});
testWidgets('MaterialApp uses dark theme when no high contrast dark theme is provided', (WidgetTester tester) async {
addTearDown(tester.platformDispatcher.clearAllTestValues);
tester.platformDispatcher.platformBrightnessTestValue = Brightness.dark;
tester.platformDispatcher.accessibilityFeaturesTestValue = FakeAccessibilityFeatures.allOn;
late ThemeData appliedTheme;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
primaryColor: Colors.lightBlue,
),
darkTheme: ThemeData(
primaryColor: Colors.lightGreen,
),
home: Builder(
builder: (BuildContext context) {
appliedTheme = Theme.of(context);
return const SizedBox();
},
),
),
);
expect(appliedTheme.primaryColor, Colors.lightGreen);
});
testWidgets('MaterialApp animates theme changes', (WidgetTester tester) async {
final ThemeData lightTheme = ThemeData.light();
final ThemeData darkTheme = ThemeData.dark();
await tester.pumpWidget(
MaterialApp(
theme: lightTheme,
darkTheme: darkTheme,
themeMode: ThemeMode.light,
home: Builder(
builder: (BuildContext context) {
return const Scaffold();
},
),
),
);
expect(tester.widget<Material>(find.byType(Material)).color, lightTheme.scaffoldBackgroundColor);
// Change to dark theme
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
themeMode: ThemeMode.dark,
home: Builder(
builder: (BuildContext context) {
return const Scaffold();
},
),
),
);
// Wait half kThemeAnimationDuration = 200ms.
await tester.pump(const Duration(milliseconds: 100));
// Default curve is linear so background should be half way between
// the two colors.
final Color halfBGColor = Color.lerp(lightTheme.scaffoldBackgroundColor, darkTheme.scaffoldBackgroundColor, 0.5)!;
expect(tester.widget<Material>(find.byType(Material)).color, halfBGColor);
});
testWidgets('MaterialApp theme animation can be turned off', (WidgetTester tester) async {
final ThemeData lightTheme = ThemeData.light();
final ThemeData darkTheme = ThemeData.dark();
int scaffoldRebuilds = 0;
final Widget scaffold = Builder(
builder: (BuildContext context) {
scaffoldRebuilds++;
// Use Theme.of() to ensure we are building when the theme changes.
return Scaffold(backgroundColor: Theme.of(context).scaffoldBackgroundColor);
},
);
await tester.pumpWidget(
MaterialApp(
theme: lightTheme,
darkTheme: darkTheme,
themeMode: ThemeMode.light,
themeAnimationDuration: Duration.zero,
home: scaffold,
),
);
expect(tester.widget<Material>(find.byType(Material)).color, lightTheme.scaffoldBackgroundColor);
expect(scaffoldRebuilds, 1);
// Change to dark theme
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
themeMode: ThemeMode.dark,
themeAnimationDuration: Duration.zero,
home: scaffold,
),
);
// Wait for any animation to finish.
await tester.pumpAndSettle();
expect(tester.widget<Material>(find.byType(Material)).color, darkTheme.scaffoldBackgroundColor);
expect(scaffoldRebuilds, 2);
});
testWidgets('MaterialApp switches themes when the platformBrightness changes.', (WidgetTester tester) async {
addTearDown(tester.platformDispatcher.clearAllTestValues);
// Mock the test to explicitly report a light platformBrightness.
tester.platformDispatcher.platformBrightnessTestValue = Brightness.light;
ThemeData? themeBeforeBrightnessChange;
ThemeData? themeAfterBrightnessChange;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
brightness: Brightness.light,
),
darkTheme: ThemeData(
brightness: Brightness.dark,
),
home: Builder(
builder: (BuildContext context) {
if (themeBeforeBrightnessChange == null) {
themeBeforeBrightnessChange = Theme.of(context);
} else {
themeAfterBrightnessChange = Theme.of(context);
}
return const SizedBox();
},
),
),
);
// Switch the platformBrightness from light to dark and pump the widget tree
// to process changes.
tester.platformDispatcher.platformBrightnessTestValue = Brightness.dark;
await tester.pumpAndSettle();
expect(themeBeforeBrightnessChange!.brightness, Brightness.light);
expect(themeAfterBrightnessChange!.brightness, Brightness.dark);
});
testWidgets('Material2 - MaterialApp provides default overscroll color', (WidgetTester tester) async {
Future<void> slowDrag(WidgetTester tester, Offset start, Offset offset) async {
final TestGesture gesture = await tester.startGesture(start);
for (int index = 0; index < 10; index += 1) {
await gesture.moveBy(offset);
await tester.pump(const Duration(milliseconds: 20));
}
await gesture.up();
}
// The overscroll color should be a transparent version of the colorScheme's
// secondary color.
const Color secondaryColor = Color(0xff008800);
final Color glowSecondaryColor = secondaryColor.withOpacity(0.05);
final ThemeData theme = ThemeData.from(
useMaterial3: false,
colorScheme: const ColorScheme.light().copyWith(secondary: secondaryColor),
);
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: const SingleChildScrollView(
child: SizedBox(height: 2000.0),
),
),
);
final RenderObject painter = tester.renderObject(find.byType(CustomPaint).first);
await slowDrag(tester, const Offset(200.0, 200.0), const Offset(0.0, 5.0));
expect(painter, paints..circle(color: glowSecondaryColor));
});
testWidgets('MaterialApp can customize initial routes', (WidgetTester tester) async {
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
await tester.pumpWidget(
MaterialApp(
navigatorKey: navigatorKey,
onGenerateInitialRoutes: (String initialRoute) {
expect(initialRoute, '/abc');
return <Route<void>>[
PageRouteBuilder<void>(
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
return const Text('non-regular page one');
},
),
PageRouteBuilder<void>(
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
return const Text('non-regular page two');
},
),
];
},
initialRoute: '/abc',
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => const Text('regular page one'),
'/abc': (BuildContext context) => const Text('regular page two'),
},
),
);
expect(find.text('non-regular page two'), findsOneWidget);
expect(find.text('non-regular page one'), findsNothing);
expect(find.text('regular page one'), findsNothing);
expect(find.text('regular page two'), findsNothing);
navigatorKey.currentState!.pop();
await tester.pumpAndSettle();
expect(find.text('non-regular page two'), findsNothing);
expect(find.text('non-regular page one'), findsOneWidget);
expect(find.text('regular page one'), findsNothing);
expect(find.text('regular page two'), findsNothing);
});
testWidgets('MaterialApp does create HeroController with the MaterialRectArcTween', (WidgetTester tester) async {
final HeroController controller = MaterialApp.createMaterialHeroController();
addTearDown(controller.dispose);
final Tween<Rect?> tween = controller.createRectTween!(
const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0),
const Rect.fromLTRB(0.0, 0.0, 20.0, 20.0),
);
expect(tween, isA<MaterialRectArcTween>());
});
testWidgets('MaterialApp.navigatorKey can be updated', (WidgetTester tester) async {
final GlobalKey<NavigatorState> key1 = GlobalKey<NavigatorState>();
await tester.pumpWidget(MaterialApp(
navigatorKey: key1,
home: const Placeholder(),
));
expect(key1.currentState, isA<NavigatorState>());
final GlobalKey<NavigatorState> key2 = GlobalKey<NavigatorState>();
await tester.pumpWidget(MaterialApp(
navigatorKey: key2,
home: const Placeholder(),
));
expect(key2.currentState, isA<NavigatorState>());
expect(key1.currentState, isNull);
});
testWidgets('MaterialApp.router works', (WidgetTester tester) async {
final PlatformRouteInformationProvider provider = PlatformRouteInformationProvider(
initialRouteInformation: RouteInformation(
uri: Uri.parse('initial'),
),
);
addTearDown(provider.dispose);
final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate(
builder: (BuildContext context, RouteInformation information) {
return Text(information.uri.toString());
},
onPopPage: (Route<void> route, void result, SimpleNavigatorRouterDelegate delegate) {
delegate.routeInformation = RouteInformation(
uri: Uri.parse('popped'),
);
return route.didPop(result);
},
);
addTearDown(delegate.dispose);
await tester.pumpWidget(MaterialApp.router(
routeInformationProvider: provider,
routeInformationParser: SimpleRouteInformationParser(),
routerDelegate: delegate,
));
expect(find.text('initial'), findsOneWidget);
// Simulate android back button intent.
final ByteData message = const JSONMethodCodec().encodeMethodCall(const MethodCall('popRoute'));
await tester.binding.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { });
await tester.pumpAndSettle();
expect(find.text('popped'), findsOneWidget);
});
testWidgets('MaterialApp.router route information parser is optional', (WidgetTester tester) async {
final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate(
builder: (BuildContext context, RouteInformation information) {
return Text(information.uri.toString());
},
onPopPage: (Route<void> route, void result, SimpleNavigatorRouterDelegate delegate) {
delegate.routeInformation = RouteInformation(
uri: Uri.parse('popped'),
);
return route.didPop(result);
},
);
addTearDown(delegate.dispose);
delegate.routeInformation = RouteInformation(uri: Uri.parse('initial'));
await tester.pumpWidget(MaterialApp.router(
routerDelegate: delegate,
));
expect(find.text('initial'), findsOneWidget);
// Simulate android back button intent.
final ByteData message = const JSONMethodCodec().encodeMethodCall(const MethodCall('popRoute'));
await tester.binding.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { });
await tester.pumpAndSettle();
expect(find.text('popped'), findsOneWidget);
});
testWidgets('MaterialApp.router throw if route information provider is provided but no route information parser', (WidgetTester tester) async {
final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate(
builder: (BuildContext context, RouteInformation information) {
return Text(information.uri.toString());
},
onPopPage: (Route<void> route, void result, SimpleNavigatorRouterDelegate delegate) {
delegate.routeInformation = RouteInformation(
uri: Uri.parse('popped'),
);
return route.didPop(result);
},
);
addTearDown(delegate.dispose);
delegate.routeInformation = RouteInformation(uri: Uri.parse('initial'));
final PlatformRouteInformationProvider provider = PlatformRouteInformationProvider(
initialRouteInformation: RouteInformation(
uri: Uri.parse('initial'),
),
);
await tester.pumpWidget(MaterialApp.router(
routeInformationProvider: provider,
routerDelegate: delegate,
));
expect(tester.takeException(), isAssertionError);
provider.dispose();
});
testWidgets('MaterialApp.router throw if route configuration is provided along with other delegate', (WidgetTester tester) async {
final SimpleNavigatorRouterDelegate delegate = SimpleNavigatorRouterDelegate(
builder: (BuildContext context, RouteInformation information) {
return Text(information.uri.toString());
},
onPopPage: (Route<void> route, void result, SimpleNavigatorRouterDelegate delegate) {
delegate.routeInformation = RouteInformation(
uri: Uri.parse('popped'),
);
return route.didPop(result);
},
);
addTearDown(delegate.dispose);
delegate.routeInformation = RouteInformation(uri: Uri.parse('initial'));
final RouterConfig<RouteInformation> routerConfig = RouterConfig<RouteInformation>(routerDelegate: delegate);
await tester.pumpWidget(MaterialApp.router(
routerDelegate: delegate,
routerConfig: routerConfig,
));
expect(tester.takeException(), isAssertionError);
});
testWidgets('MaterialApp.router router config works', (WidgetTester tester) async {
late SimpleNavigatorRouterDelegate routerDelegate;
addTearDown(() => routerDelegate.dispose());
late PlatformRouteInformationProvider provider;
addTearDown(() => provider.dispose());
final RouterConfig<RouteInformation> routerConfig = RouterConfig<RouteInformation>(
routeInformationProvider: provider = PlatformRouteInformationProvider(
initialRouteInformation: RouteInformation(
uri: Uri.parse('initial'),
),
),
routeInformationParser: SimpleRouteInformationParser(),
routerDelegate: routerDelegate = SimpleNavigatorRouterDelegate(
builder: (BuildContext context, RouteInformation information) {
return Text(information.uri.toString());
},
onPopPage: (Route<void> route, void result, SimpleNavigatorRouterDelegate delegate) {
delegate.routeInformation = RouteInformation(
uri: Uri.parse('popped'),
);
return route.didPop(result);
},
),
backButtonDispatcher: RootBackButtonDispatcher()
);
await tester.pumpWidget(MaterialApp.router(
routerConfig: routerConfig,
));
expect(find.text('initial'), findsOneWidget);
// Simulate android back button intent.
final ByteData message = const JSONMethodCodec().encodeMethodCall(const MethodCall('popRoute'));
await tester.binding.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { });
await tester.pumpAndSettle();
expect(find.text('popped'), findsOneWidget);
});
testWidgets('MaterialApp.builder can build app without a Navigator', (WidgetTester tester) async {
Widget? builderChild;
await tester.pumpWidget(MaterialApp(
builder: (BuildContext context, Widget? child) {
builderChild = child;
return Container();
},
));
expect(builderChild, isNull);
});
testWidgets('MaterialApp has correct default ScrollBehavior', (WidgetTester tester) async {
late BuildContext capturedContext;
await tester.pumpWidget(
MaterialApp(
home: Builder(
builder: (BuildContext context) {
capturedContext = context;
return const Placeholder();
},
),
),
);
expect(ScrollConfiguration.of(capturedContext).runtimeType, MaterialScrollBehavior);
});
testWidgets('A ScrollBehavior can be set for MaterialApp', (WidgetTester tester) async {
late BuildContext capturedContext;
await tester.pumpWidget(
MaterialApp(
scrollBehavior: const MockScrollBehavior(),
home: Builder(
builder: (BuildContext context) {
capturedContext = context;
return const Placeholder();
},
),
),
);
final ScrollBehavior scrollBehavior = ScrollConfiguration.of(capturedContext);
expect(scrollBehavior.runtimeType, MockScrollBehavior);
expect(scrollBehavior.getScrollPhysics(capturedContext).runtimeType, NeverScrollableScrollPhysics);
});
testWidgets('Material2 - ScrollBehavior default android overscroll indicator', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
theme: ThemeData(useMaterial3: false),
scrollBehavior: const MaterialScrollBehavior(),
home: ListView(
children: const <Widget>[
SizedBox(
height: 1000.0,
width: 1000.0,
child: Text('Test'),
),
],
),
));
expect(find.byType(StretchingOverscrollIndicator), findsNothing);
expect(find.byType(GlowingOverscrollIndicator), findsOneWidget);
}, variant: TargetPlatformVariant.only(TargetPlatform.android));
testWidgets('Material3 - ScrollBehavior default android overscroll indicator', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
theme: ThemeData(useMaterial3: true),
scrollBehavior: const MaterialScrollBehavior(),
home: ListView(
children: const <Widget>[
SizedBox(
height: 1000.0,
width: 1000.0,
child: Text('Test'),
),
],
),
));
expect(find.byType(StretchingOverscrollIndicator), findsOneWidget);
expect(find.byType(GlowingOverscrollIndicator), findsNothing);
}, variant: TargetPlatformVariant.only(TargetPlatform.android));
testWidgets('MaterialScrollBehavior default stretch android overscroll indicator', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
home: ListView(
children: const <Widget>[
SizedBox(
height: 1000.0,
width: 1000.0,
child: Text('Test'),
),
],
),
));
expect(find.byType(StretchingOverscrollIndicator), findsOneWidget);
expect(find.byType(GlowingOverscrollIndicator), findsNothing);
}, variant: TargetPlatformVariant.only(TargetPlatform.android));
testWidgets('Overscroll indicator can be set by theme', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
// The current default is M3 and stretch overscroll, setting via the theme should override.
theme: ThemeData().copyWith(useMaterial3: false),
home: ListView(
children: const <Widget>[
SizedBox(
height: 1000.0,
width: 1000.0,
child: Text('Test'),
),
],
),
));
expect(find.byType(GlowingOverscrollIndicator), findsOneWidget);
expect(find.byType(StretchingOverscrollIndicator), findsNothing);
}, variant: TargetPlatformVariant.only(TargetPlatform.android));
testWidgets('Material3 - ListView clip behavior updates overscroll indicator clip behavior', (WidgetTester tester) async {
Widget buildFrame(Clip clipBehavior) {
return MaterialApp(
theme: ThemeData(useMaterial3: true),
home: Column(
children: <Widget>[
SizedBox(
height: 300,
child: ListView.builder(
itemCount: 20,
clipBehavior: clipBehavior,
itemBuilder: (BuildContext context, int index){
return Padding(
padding: const EdgeInsets.all(10.0),
child: Text('Index $index'),
);
},
),
),
Opacity(
opacity: 0.5,
child: Container(
color: const Color(0xD0FF0000),
height: 100,
),
),
],
),
);
}
// Test default clip behavior.
await tester.pumpWidget(buildFrame(Clip.hardEdge));
expect(find.byType(StretchingOverscrollIndicator), findsOneWidget);
expect(find.byType(GlowingOverscrollIndicator), findsNothing);
expect(find.text('Index 1'), findsOneWidget);
RenderClipRect renderClip = tester.allRenderObjects.whereType<RenderClipRect>().first;
// Currently not clipping
expect(renderClip.clipBehavior, equals(Clip.none));
TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('Index 1')));
// Overscroll the start.
await gesture.moveBy(const Offset(0.0, 200.0));
await tester.pumpAndSettle();
expect(find.text('Index 1'), findsOneWidget);
expect(tester.getCenter(find.text('Index 1')).dy, greaterThan(0));
renderClip = tester.allRenderObjects.whereType<RenderClipRect>().first;
// Now clipping
expect(renderClip.clipBehavior, equals(Clip.hardEdge));
await gesture.up();
await tester.pumpAndSettle();
// Test custom clip behavior.
await tester.pumpWidget(buildFrame(Clip.none));
renderClip = tester.allRenderObjects.whereType<RenderClipRect>().first;
// Currently not clipping
expect(renderClip.clipBehavior, equals(Clip.none));
gesture = await tester.startGesture(tester.getCenter(find.text('Index 1')));
// Overscroll the start.
await gesture.moveBy(const Offset(0.0, 200.0));
await tester.pumpAndSettle();
expect(find.text('Index 1'), findsOneWidget);
expect(tester.getCenter(find.text('Index 1')).dy, greaterThan(0));
renderClip = tester.allRenderObjects.whereType<RenderClipRect>().first;
// Now clipping
expect(renderClip.clipBehavior, equals(Clip.none));
await gesture.up();
await tester.pumpAndSettle();
}, variant: TargetPlatformVariant.only(TargetPlatform.android));
testWidgets('When `useInheritedMediaQuery` is true an existing MediaQuery is used if one is available', (WidgetTester tester) async {
late BuildContext capturedContext;
final UniqueKey uniqueKey = UniqueKey();
await tester.pumpWidget(
MediaQuery(
key: uniqueKey,
data: const MediaQueryData(),
child: MaterialApp(
useInheritedMediaQuery: true,
builder: (BuildContext context, Widget? child) {
capturedContext = context;
return const Placeholder();
},
color: const Color(0xFF123456),
),
),
);
expect(capturedContext.dependOnInheritedWidgetOfExactType<MediaQuery>()?.key, uniqueKey);
});
testWidgets('Assert in buildScrollbar that controller != null when using it (vertical)', (WidgetTester tester) async {
const ScrollBehavior defaultBehavior = MaterialScrollBehavior();
late BuildContext capturedContext;
await tester.pumpWidget(MaterialApp(
home: ScrollConfiguration(
// Avoid the default ones here.
behavior: const MaterialScrollBehavior().copyWith(scrollbars: false),
child: SingleChildScrollView(
child: Builder(
builder: (BuildContext context) {
capturedContext = context;
return Container(height: 1000.0);
},
),
),
),
));
const ScrollableDetails details = ScrollableDetails(
direction: AxisDirection.down,
);
final Widget child = Container();
switch (defaultTargetPlatform) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.iOS:
// Does not throw if we aren't using it.
defaultBehavior.buildScrollbar(capturedContext, child, details);
case TargetPlatform.linux:
case TargetPlatform.macOS:
case TargetPlatform.windows:
expect(
() {
defaultBehavior.buildScrollbar(capturedContext, child, details);
},
throwsA(
isA<AssertionError>().having((AssertionError error) => error.toString(),
'description', contains('details.controller != null')),
),
);
}
}, variant: TargetPlatformVariant.all());
testWidgets('Assert in buildScrollbar that controller != null when using it (horizontal)', (WidgetTester tester) async {
const ScrollBehavior defaultBehavior = MaterialScrollBehavior();
late BuildContext capturedContext;
await tester.pumpWidget(MaterialApp(
home: ScrollConfiguration(
// Avoid the default ones here.
behavior: const MaterialScrollBehavior().copyWith(scrollbars: false),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Builder(
builder: (BuildContext context) {
capturedContext = context;
return Container(height: 1000.0);
},
),
),
),
));
const ScrollableDetails details = ScrollableDetails(
direction: AxisDirection.left,
);
final Widget child = Container();
switch (defaultTargetPlatform) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.iOS:
case TargetPlatform.linux:
case TargetPlatform.macOS:
case TargetPlatform.windows:
// Does not throw if we aren't using it.
// Horizontal axis gets no scrollbars for all platforms.
defaultBehavior.buildScrollbar(capturedContext, child, details);
}
}, variant: TargetPlatformVariant.all());
testWidgets('Override theme animation using AnimationStyle', (WidgetTester tester) async {
final ThemeData lightTheme = ThemeData.light();
final ThemeData darkTheme = ThemeData.dark();
Widget buildWidget({ ThemeMode themeMode = ThemeMode.light, AnimationStyle? animationStyle }) {
return MaterialApp(
theme: lightTheme,
darkTheme: darkTheme,
themeMode: themeMode,
themeAnimationStyle: animationStyle,
home: const Scaffold(body: Text('body')),
);
}
// Test the initial Scaffold background color.
await tester.pumpWidget(buildWidget());
expect(tester.widget<Material>(find.byType(Material)).color, const Color(0xfffffbfe));
// Test the Scaffold background color animation from light to dark theme.
await tester.pumpWidget(buildWidget(themeMode: ThemeMode.dark));
await tester.pump();
await tester.pump(const Duration(milliseconds: 50)); // Advance animation by 50 milliseconds.
// Scaffold background color is slightly updated.
expect(tester.widget<Material>(find.byType(Material)).color, const Color(0xffc6c3c6));
// Let the animation finish.
await tester.pumpAndSettle();
// Scaffold background color is fully updated to dark theme.
expect(tester.widget<Material>(find.byType(Material)).color, const Color(0xff1c1b1f));
// Reset to light theme to compare the Scaffold background color animation
// with the default animation curve.
await tester.pumpWidget(buildWidget());
await tester.pumpAndSettle();
// Switch to dark theme with overridden animation curve.
await tester.pumpWidget(buildWidget(
themeMode: ThemeMode.dark,
animationStyle: AnimationStyle(curve: Curves.easeIn,
)));
await tester.pump();
await tester.pump(const Duration(milliseconds: 50));
// Scaffold background color is slightly updated but with a different
// color than the default animation curve.
expect(tester.widget<Material>(find.byType(Material)).color, const Color(0xffe9e5e9));
// Let the animation finish.
await tester.pumpAndSettle();
// Scaffold background color is fully updated to dark theme.
expect(tester.widget<Material>(find.byType(Material)).color, const Color(0xff1c1b1f));
// Switch from dark to light theme with overridden animation duration.
await tester.pumpWidget(buildWidget(animationStyle: AnimationStyle.noAnimation));
await tester.pump();
await tester.pump(const Duration(milliseconds: 1));
expect(tester.widget<Material>(find.byType(Material)).color, isNot(const Color(0xff1c1b1f)));
expect(tester.widget<Material>(find.byType(Material)).color, const Color(0xfffffbfe));
});
testWidgets('AnimationStyle.noAnimation removes AnimatedTheme from the tree', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(themeAnimationStyle: AnimationStyle()));
expect(find.byType(AnimatedTheme), findsOneWidget);
expect(find.byType(Theme), findsOneWidget);
await tester.pumpWidget(MaterialApp(themeAnimationStyle: AnimationStyle.noAnimation));
expect(find.byType(AnimatedTheme), findsNothing);
expect(find.byType(Theme), findsOneWidget);
});
// Regression test for https://github.com/flutter/flutter/issues/137875.
testWidgets('MaterialApp works in an unconstrained environment', (WidgetTester tester) async {
await tester.pumpWidget(
const UnconstrainedBox(
child: MaterialApp(
home: SizedBox(width: 123, height: 456),
),
),
);
expect(tester.getSize(find.byType(MaterialApp)), const Size(123, 456));
});
}
class MockScrollBehavior extends ScrollBehavior {
const MockScrollBehavior();
@override
ScrollPhysics getScrollPhysics(BuildContext context) => const NeverScrollableScrollPhysics();
}
typedef SimpleRouterDelegateBuilder = Widget Function(BuildContext context, RouteInformation information);
typedef SimpleNavigatorRouterDelegatePopPage<T> = bool Function(Route<T> route, T result, SimpleNavigatorRouterDelegate delegate);
class SimpleRouteInformationParser extends RouteInformationParser<RouteInformation> {
SimpleRouteInformationParser();
@override
Future<RouteInformation> parseRouteInformation(RouteInformation information) {
return SynchronousFuture<RouteInformation>(information);
}
@override
RouteInformation restoreRouteInformation(RouteInformation configuration) {
return configuration;
}
}
class SimpleNavigatorRouterDelegate extends RouterDelegate<RouteInformation> with PopNavigatorRouterDelegateMixin<RouteInformation>, ChangeNotifier {
SimpleNavigatorRouterDelegate({
required this.builder,
required this.onPopPage,
}) {
ChangeNotifier.maybeDispatchObjectCreation(this);
}
@override
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
RouteInformation get routeInformation => _routeInformation;
late RouteInformation _routeInformation;
set routeInformation(RouteInformation newValue) {
_routeInformation = newValue;
notifyListeners();
}
SimpleRouterDelegateBuilder builder;
SimpleNavigatorRouterDelegatePopPage<void> onPopPage;
@override
Future<void> setNewRoutePath(RouteInformation configuration) {
_routeInformation = configuration;
return SynchronousFuture<void>(null);
}
bool _handlePopPage(Route<void> route, void data) {
return onPopPage(route, data, this);
}
@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
onPopPage: _handlePopPage,
pages: <Page<void>>[
// We need at least two pages for the pop to propagate through.
// Otherwise, the navigator will bubble the pop to the system navigator.
const MaterialPage<void>(
child: Text('base'),
),
MaterialPage<void>(
key: ValueKey<String>(routeInformation.uri.toString()),
child: builder(context, routeInformation),
),
],
);
}
}