app.dart 40.1 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:ui' as ui;

7
import 'package:flutter/cupertino.dart';
8
import 'package:flutter/foundation.dart';
9
import 'package:flutter/services.dart';
10

11
import 'arc.dart';
12
import 'colors.dart';
13 14
import 'floating_action_button.dart';
import 'icons.dart';
15
import 'material_localizations.dart';
16
import 'page.dart';
17
import 'scaffold.dart' show ScaffoldMessenger, ScaffoldMessengerState;
18
import 'scrollbar.dart';
19
import 'theme.dart';
20
import 'tooltip.dart';
21

22 23 24 25
// Examples can assume:
// typedef GlobalWidgetsLocalizations = DefaultWidgetsLocalizations;
// typedef GlobalMaterialLocalizations = DefaultMaterialLocalizations;

26 27 28 29 30 31 32
/// [MaterialApp] uses this [TextStyle] as its [DefaultTextStyle] to encourage
/// developers to be intentional about their [DefaultTextStyle].
///
/// In Material Design, most [Text] widgets are contained in [Material] widgets,
/// which sets a specific [DefaultTextStyle]. If you're seeing text that uses
/// this text style, consider putting your text in a [Material] widget (or
/// another widget that sets a [DefaultTextStyle]).
33 34
const TextStyle _errorTextStyle = TextStyle(
  color: Color(0xD0FF0000),
35 36 37
  fontFamily: 'monospace',
  fontSize: 48.0,
  fontWeight: FontWeight.w900,
38
  decoration: TextDecoration.underline,
39
  decorationColor: Color(0xFFFFFF00),
40 41
  decorationStyle: TextDecorationStyle.double,
  debugLabel: 'fallback style; consider putting your text in a Material',
42 43
);

44 45 46 47 48 49 50 51 52 53 54 55 56
/// Describes which theme will be used by [MaterialApp].
enum ThemeMode {
  /// Use either the light or dark theme based on what the user has selected in
  /// the system settings.
  system,

  /// Always use the light mode regardless of system preference.
  light,

  /// Always use the dark mode (if available) regardless of system preference.
  dark,
}

57
/// An application that uses Material Design.
58 59
///
/// A convenience widget that wraps a number of widgets that are commonly
60
/// required for Material Design applications. It builds upon a [WidgetsApp] by
61 62 63
/// adding material-design specific functionality, such as [AnimatedTheme] and
/// [GridPaper].
///
64 65 66 67 68
/// [MaterialApp] configures its [WidgetsApp.textStyle] with an ugly red/yellow
/// text style that's intended to warn the developer that their app hasn't defined
/// a default text style. Typically the app's [Scaffold] builds a [Material] widget
/// whose default [Material.textStyle] defines the text style for the entire scaffold.
///
69 70 71 72 73 74 75 76 77 78 79 80
/// The [MaterialApp] configures the top-level [Navigator] to search for routes
/// in the following order:
///
///  1. For the `/` route, the [home] property, if non-null, is used.
///
///  2. Otherwise, the [routes] table is used, if it has an entry for the route.
///
///  3. Otherwise, [onGenerateRoute] is called, if provided. It should return a
///     non-null value for any _valid_ route not handled by [home] and [routes].
///
///  4. Finally if all else fails [onUnknownRoute] is called.
///
81 82 83
/// If a [Navigator] is created, at least one of these options must handle the
/// `/` route, since it is used when an invalid [initialRoute] is specified on
/// startup (e.g. by another application launching this one with an intent on
84
/// Android; see [dart:ui.PlatformDispatcher.defaultRouteName]).
85
///
86 87 88
/// This widget also configures the observer of the top-level [Navigator] (if
/// any) to perform [Hero] animations.
///
89 90 91 92 93 94 95 96
/// {@template flutter.material.MaterialApp.defaultSelectionStyle}
/// The [MaterialApp] automatically creates a [DefaultSelectionStyle]. It uses
/// the colors in the [ThemeData.textSelectionTheme] if they are not null;
/// otherwise, the [MaterialApp] sets [DefaultSelectionStyle.selectionColor] to
/// [ColorScheme.primary] with 0.4 opacity and
/// [DefaultSelectionStyle.cursorColor] to [ColorScheme.primary].
/// {@endtemplate}
///
97 98
/// If [home], [routes], [onGenerateRoute], and [onUnknownRoute] are all null,
/// and [builder] is not null, then no [Navigator] is created.
99
///
100
/// {@tool snippet}
101 102 103
/// This example shows how to create a [MaterialApp] that disables the "debug"
/// banner with a [home] route that will be displayed when the app is launched.
///
104
/// ![The MaterialApp displays a Scaffold ](https://flutter.github.io/assets-for-api-docs/assets/material/basic_material_app.png)
105 106 107 108 109 110 111 112 113 114 115 116 117
///
/// ```dart
/// MaterialApp(
///   home: Scaffold(
///     appBar: AppBar(
///       title: const Text('Home'),
///     ),
///   ),
///   debugShowCheckedModeBanner: false,
/// )
/// ```
/// {@end-tool}
///
118
/// {@tool snippet}
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
/// This example shows how to create a [MaterialApp] that uses the [routes]
/// `Map` to define the "home" route and an "about" route.
///
/// ```dart
/// MaterialApp(
///   routes: <String, WidgetBuilder>{
///     '/': (BuildContext context) {
///       return Scaffold(
///         appBar: AppBar(
///           title: const Text('Home Route'),
///         ),
///       );
///     },
///     '/about': (BuildContext context) {
///       return Scaffold(
///         appBar: AppBar(
///           title: const Text('About Route'),
///         ),
///       );
///      }
///    },
/// )
/// ```
/// {@end-tool}
///
144
/// {@tool snippet}
145 146 147
/// This example shows how to create a [MaterialApp] that defines a [theme] that
/// will be used for material widgets in the app.
///
148
/// ![The MaterialApp displays a Scaffold with a dark background and a blue / grey AppBar at the top](https://flutter.github.io/assets-for-api-docs/assets/material/theme_material_app.png)
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
///
/// ```dart
/// MaterialApp(
///   theme: ThemeData(
///     brightness: Brightness.dark,
///     primaryColor: Colors.blueGrey
///   ),
///   home: Scaffold(
///     appBar: AppBar(
///       title: const Text('MaterialApp Theme'),
///     ),
///   ),
/// )
/// ```
/// {@end-tool}
///
165 166 167 168 169 170 171 172 173 174 175 176 177
/// ## Troubleshooting
///
/// ### Why is my app's text red with yellow underlines?
///
/// [Text] widgets that lack a [Material] ancestor will be rendered with an ugly
/// red/yellow text style.
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/material_app_unspecified_textstyle.png)
///
/// The typical fix is to give the widget a [Scaffold] ancestor. The [Scaffold] creates
/// a [Material] widget that defines its default text style.
///
/// ```dart
178
/// const MaterialApp(
179 180 181 182 183 184 185 186
///   title: 'Material App',
///   home: Scaffold(
///     body: Center(
///       child: Text('Hello World'),
///     ),
///   ),
/// )
/// ```
187 188
/// See also:
///
189 190 191 192
///  * [Scaffold], which provides standard app elements like an [AppBar] and a [Drawer].
///  * [Navigator], which is used to manage the app's stack of pages.
///  * [MaterialPageRoute], which defines an app page that transitions in a material-specific way.
///  * [WidgetsApp], which defines the basic app elements but does not depend on the material library.
193
///  * The Flutter Internationalization Tutorial,
194
///    <https://flutter.dev/tutorials/internationalization/>.
195
class MaterialApp extends StatefulWidget {
196 197
  /// Creates a MaterialApp.
  ///
198 199
  /// At least one of [home], [routes], [onGenerateRoute], or [builder] must be
  /// non-null. If only [routes] is given, it must include an entry for the
200 201 202
  /// [Navigator.defaultRouteName] (`/`), since that is the route used when the
  /// application is launched with an intent that specifies an otherwise
  /// unsupported route.
203
  ///
204
  /// This class creates an instance of [WidgetsApp].
205 206
  ///
  /// The boolean arguments, [routes], and [navigatorObservers], must not be null.
207
  const MaterialApp({
208
    super.key,
209
    this.navigatorKey,
210
    this.scaffoldMessengerKey,
211
    this.home,
212
    Map<String, WidgetBuilder> this.routes = const <String, WidgetBuilder>{},
213
    this.initialRoute,
214
    this.onGenerateRoute,
215
    this.onGenerateInitialRoutes,
216
    this.onUnknownRoute,
217
    List<NavigatorObserver> this.navigatorObservers = const <NavigatorObserver>[],
218
    this.builder,
219
    this.title = '',
220 221 222
    this.onGenerateTitle,
    this.color,
    this.theme,
223
    this.darkTheme,
224 225
    this.highContrastTheme,
    this.highContrastDarkTheme,
226
    this.themeMode = ThemeMode.system,
227 228
    this.themeAnimationDuration = kThemeAnimationDuration,
    this.themeAnimationCurve = Curves.linear,
229 230
    this.locale,
    this.localizationsDelegates,
231
    this.localeListResolutionCallback,
232
    this.localeResolutionCallback,
233
    this.supportedLocales = const <Locale>[Locale('en', 'US')],
234 235 236 237 238 239
    this.debugShowMaterialGrid = false,
    this.showPerformanceOverlay = false,
    this.checkerboardRasterCacheImages = false,
    this.checkerboardOffscreenLayers = false,
    this.showSemanticsDebugger = false,
    this.debugShowCheckedModeBanner = true,
240 241
    this.shortcuts,
    this.actions,
242
    this.restorationScopeId,
243
    this.scrollBehavior,
244
    this.useInheritedMediaQuery = false,
245
  }) : assert(routes != null),
246
       assert(navigatorObservers != null),
247 248 249 250 251 252 253
       assert(title != null),
       assert(debugShowMaterialGrid != null),
       assert(showPerformanceOverlay != null),
       assert(checkerboardRasterCacheImages != null),
       assert(checkerboardOffscreenLayers != null),
       assert(showSemanticsDebugger != null),
       assert(debugShowCheckedModeBanner != null),
254 255 256
       routeInformationProvider = null,
       routeInformationParser = null,
       routerDelegate = null,
257 258
       backButtonDispatcher = null,
       routerConfig = null;
259 260

  /// Creates a [MaterialApp] that uses the [Router] instead of a [Navigator].
261 262
  ///
  /// {@macro flutter.widgets.WidgetsApp.router}
263
  const MaterialApp.router({
264
    super.key,
265
    this.scaffoldMessengerKey,
266
    this.routeInformationProvider,
267 268 269
    this.routeInformationParser,
    this.routerDelegate,
    this.routerConfig,
270 271 272 273 274 275 276 277 278 279
    this.backButtonDispatcher,
    this.builder,
    this.title = '',
    this.onGenerateTitle,
    this.color,
    this.theme,
    this.darkTheme,
    this.highContrastTheme,
    this.highContrastDarkTheme,
    this.themeMode = ThemeMode.system,
280 281
    this.themeAnimationDuration = kThemeAnimationDuration,
    this.themeAnimationCurve = Curves.linear,
282 283 284 285 286 287 288 289 290 291 292 293 294
    this.locale,
    this.localizationsDelegates,
    this.localeListResolutionCallback,
    this.localeResolutionCallback,
    this.supportedLocales = const <Locale>[Locale('en', 'US')],
    this.debugShowMaterialGrid = false,
    this.showPerformanceOverlay = false,
    this.checkerboardRasterCacheImages = false,
    this.checkerboardOffscreenLayers = false,
    this.showSemanticsDebugger = false,
    this.debugShowCheckedModeBanner = true,
    this.shortcuts,
    this.actions,
295
    this.restorationScopeId,
296
    this.scrollBehavior,
297
    this.useInheritedMediaQuery = false,
298
  }) : assert(routerDelegate != null || routerConfig != null),
299 300 301 302 303 304 305 306 307 308 309 310 311 312
       assert(title != null),
       assert(debugShowMaterialGrid != null),
       assert(showPerformanceOverlay != null),
       assert(checkerboardRasterCacheImages != null),
       assert(checkerboardOffscreenLayers != null),
       assert(showSemanticsDebugger != null),
       assert(debugShowCheckedModeBanner != null),
       navigatorObservers = null,
       navigatorKey = null,
       onGenerateRoute = null,
       home = null,
       onGenerateInitialRoutes = null,
       onUnknownRoute = null,
       routes = null,
313
       initialRoute = null;
314

315
  /// {@macro flutter.widgets.widgetsApp.navigatorKey}
316
  final GlobalKey<NavigatorState>? navigatorKey;
317

318 319 320 321 322 323 324 325
  /// A key to use when building the [ScaffoldMessenger].
  ///
  /// If a [scaffoldMessengerKey] is specified, the [ScaffoldMessenger] can be
  /// directly manipulated without first obtaining it from a [BuildContext] via
  /// [ScaffoldMessenger.of]: from the [scaffoldMessengerKey], use the
  /// [GlobalKey.currentState] getter.
  final GlobalKey<ScaffoldMessengerState>? scaffoldMessengerKey;

326
  /// {@macro flutter.widgets.widgetsApp.home}
327
  final Widget? home;
328

329 330 331 332
  /// The application's top-level routing table.
  ///
  /// When a named route is pushed with [Navigator.pushNamed], the route name is
  /// looked up in this map. If the name is present, the associated
Janice Collins's avatar
Janice Collins committed
333 334 335
  /// [widgets.WidgetBuilder] is used to construct a [MaterialPageRoute] that
  /// performs an appropriate transition, including [Hero] animations, to the
  /// new route.
336
  ///
337
  /// {@macro flutter.widgets.widgetsApp.routes}
338
  final Map<String, WidgetBuilder>? routes;
Ian Hickson's avatar
Ian Hickson committed
339

xster's avatar
xster committed
340
  /// {@macro flutter.widgets.widgetsApp.initialRoute}
341
  final String? initialRoute;
342

xster's avatar
xster committed
343
  /// {@macro flutter.widgets.widgetsApp.onGenerateRoute}
344
  final RouteFactory? onGenerateRoute;
345

346
  /// {@macro flutter.widgets.widgetsApp.onGenerateInitialRoutes}
347
  final InitialRouteListFactory? onGenerateInitialRoutes;
348

xster's avatar
xster committed
349
  /// {@macro flutter.widgets.widgetsApp.onUnknownRoute}
350
  final RouteFactory? onUnknownRoute;
351

xster's avatar
xster committed
352
  /// {@macro flutter.widgets.widgetsApp.navigatorObservers}
353
  final List<NavigatorObserver>? navigatorObservers;
354

355
  /// {@macro flutter.widgets.widgetsApp.routeInformationProvider}
356
  final RouteInformationProvider? routeInformationProvider;
357 358

  /// {@macro flutter.widgets.widgetsApp.routeInformationParser}
359
  final RouteInformationParser<Object>? routeInformationParser;
360 361

  /// {@macro flutter.widgets.widgetsApp.routerDelegate}
362
  final RouterDelegate<Object>? routerDelegate;
363 364

  /// {@macro flutter.widgets.widgetsApp.backButtonDispatcher}
365
  final BackButtonDispatcher? backButtonDispatcher;
366

367 368 369
  /// {@macro flutter.widgets.widgetsApp.routerConfig}
  final RouterConfig<Object>? routerConfig;

xster's avatar
xster committed
370
  /// {@macro flutter.widgets.widgetsApp.builder}
371
  ///
372 373
  /// Material specific features such as [showDialog] and [showMenu], and widgets
  /// such as [Tooltip], [PopupMenuButton], also require a [Navigator] to properly
374
  /// function.
375
  final TransitionBuilder? builder;
376

xster's avatar
xster committed
377
  /// {@macro flutter.widgets.widgetsApp.title}
378 379 380 381
  ///
  /// This value is passed unmodified to [WidgetsApp.title].
  final String title;

xster's avatar
xster committed
382
  /// {@macro flutter.widgets.widgetsApp.onGenerateTitle}
383 384
  ///
  /// This value is passed unmodified to [WidgetsApp.onGenerateTitle].
385
  final GenerateAppTitle? onGenerateTitle;
386

387 388 389
  /// Default visual properties, like colors fonts and shapes, for this app's
  /// material widgets.
  ///
390 391 392
  /// A second [darkTheme] [ThemeData] value, which is used to provide a dark
  /// version of the user interface can also be specified. [themeMode] will
  /// control which theme will be used if a [darkTheme] is provided.
393 394 395 396 397
  ///
  /// The default value of this property is the value of [ThemeData.light()].
  ///
  /// See also:
  ///
398
  ///  * [themeMode], which controls which theme to use.
399 400 401 402 403
  ///  * [MediaQueryData.platformBrightness], which indicates the platform's
  ///    desired brightness and is used to automatically toggle between [theme]
  ///    and [darkTheme] in [MaterialApp].
  ///  * [ThemeData.brightness], which indicates the [Brightness] of a theme's
  ///    colors.
404
  final ThemeData? theme;
405

406
  /// The [ThemeData] to use when a 'dark mode' is requested by the system.
407
  ///
408 409 410 411
  /// Some host platforms allow the users to select a system-wide 'dark mode',
  /// or the application may want to offer the user the ability to choose a
  /// dark theme just for this application. This is theme that will be used for
  /// such cases. [themeMode] will control which theme will be used.
412
  ///
413
  /// This theme should have a [ThemeData.brightness] set to [Brightness.dark].
414 415 416 417 418 419
  ///
  /// Uses [theme] instead when null. Defaults to the value of
  /// [ThemeData.light()] when both [darkTheme] and [theme] are null.
  ///
  /// See also:
  ///
420
  ///  * [themeMode], which controls which theme to use.
421 422 423 424 425
  ///  * [MediaQueryData.platformBrightness], which indicates the platform's
  ///    desired brightness and is used to automatically toggle between [theme]
  ///    and [darkTheme] in [MaterialApp].
  ///  * [ThemeData.brightness], which is typically set to the value of
  ///    [MediaQueryData.platformBrightness].
426
  final ThemeData? darkTheme;
427

428 429 430 431 432 433 434 435 436 437 438
  /// The [ThemeData] to use when 'high contrast' is requested by the system.
  ///
  /// Some host platforms (for example, iOS) allow the users to increase
  /// contrast through an accessibility setting.
  ///
  /// Uses [theme] instead when null.
  ///
  /// See also:
  ///
  ///  * [MediaQueryData.highContrast], which indicates the platform's
  ///    desire to increase contrast.
439
  final ThemeData? highContrastTheme;
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454

  /// The [ThemeData] to use when a 'dark mode' and 'high contrast' is requested
  /// by the system.
  ///
  /// Some host platforms (for example, iOS) allow the users to increase
  /// contrast through an accessibility setting.
  ///
  /// This theme should have a [ThemeData.brightness] set to [Brightness.dark].
  ///
  /// Uses [darkTheme] instead when null.
  ///
  /// See also:
  ///
  ///  * [MediaQueryData.highContrast], which indicates the platform's
  ///    desire to increase contrast.
455
  final ThemeData? highContrastDarkTheme;
456

457 458 459 460 461 462
  /// Determines which theme will be used by the application if both [theme]
  /// and [darkTheme] are provided.
  ///
  /// If set to [ThemeMode.system], the choice of which theme to use will
  /// be based on the user's system preferences. If the [MediaQuery.platformBrightnessOf]
  /// is [Brightness.light], [theme] will be used. If it is [Brightness.dark],
463
  /// [darkTheme] will be used (unless it is null, in which case [theme]
464 465 466 467 468 469
  /// will be used.
  ///
  /// If set to [ThemeMode.light] the [theme] will always be used,
  /// regardless of the user's system preference.
  ///
  /// If set to [ThemeMode.dark] the [darkTheme] will be used
470
  /// regardless of the user's system preference. If [darkTheme] is null
471 472 473 474 475 476
  /// then it will fallback to using [theme].
  ///
  /// The default value is [ThemeMode.system].
  ///
  /// See also:
  ///
477 478 479 480
  ///  * [theme], which is used when a light mode is selected.
  ///  * [darkTheme], which is used when a dark mode is selected.
  ///  * [ThemeData.brightness], which indicates to various parts of the
  ///    system what kind of theme is being used.
481
  final ThemeMode? themeMode;
482

483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506
  /// The duration of animated theme changes.
  ///
  /// When the theme changes (either by the [theme], [darkTheme] or [themeMode]
  /// parameters changing) it is animated to the new theme over time.
  /// The [themeAnimationDuration] determines how long this animation takes.
  ///
  /// To have the theme change immediately, you can set this to [Duration.zero].
  ///
  /// The default is [kThemeAnimationDuration].
  ///
  /// See also:
  ///   [themeAnimationCurve], which defines the curve used for the animation.
  final Duration themeAnimationDuration;

  /// The curve to apply when animating theme changes.
  ///
  /// The default is [Curves.linear].
  ///
  /// This is ignored if [themeAnimationDuration] is [Duration.zero].
  ///
  /// See also:
  ///   [themeAnimationDuration], which defines how long the animation is.
  final Curve themeAnimationCurve;

xster's avatar
xster committed
507
  /// {@macro flutter.widgets.widgetsApp.color}
508
  final Color? color;
509

xster's avatar
xster committed
510
  /// {@macro flutter.widgets.widgetsApp.locale}
511
  final Locale? locale;
512

xster's avatar
xster committed
513
  /// {@macro flutter.widgets.widgetsApp.localizationsDelegates}
514
  ///
515
  /// Internationalized apps that require translations for one of the locales
516
  /// listed in [GlobalMaterialLocalizations] should specify this parameter
517 518 519
  /// and list the [supportedLocales] that the application can handle.
  ///
  /// ```dart
520 521 522 523 524 525 526
  /// // The GlobalMaterialLocalizations and GlobalWidgetsLocalizations
  /// // classes require the following import:
  /// // import 'package:flutter_localizations/flutter_localizations.dart';
  ///
  /// const MaterialApp(
  ///   localizationsDelegates: <LocalizationsDelegate<Object>>[
  ///     // ... app-specific localization delegate(s) here
527 528 529
  ///     GlobalMaterialLocalizations.delegate,
  ///     GlobalWidgetsLocalizations.delegate,
  ///   ],
530 531 532
  ///   supportedLocales: <Locale>[
  ///     Locale('en', 'US'), // English
  ///     Locale('he', 'IL'), // Hebrew
533 534 535 536 537 538 539 540 541 542 543 544
  ///     // ... other locales the app supports
  ///   ],
  ///   // ...
  /// )
  /// ```
  ///
  /// ## Adding localizations for a new locale
  ///
  /// The information that follows applies to the unusual case of an app
  /// adding translations for a language not already supported by
  /// [GlobalMaterialLocalizations].
  ///
545 546 547
  /// Delegates that produce [WidgetsLocalizations] and [MaterialLocalizations]
  /// are included automatically. Apps can provide their own versions of these
  /// localizations by creating implementations of
548
  /// [LocalizationsDelegate<WidgetsLocalizations>] or
549
  /// [LocalizationsDelegate<MaterialLocalizations>] whose load methods return
550
  /// custom versions of [WidgetsLocalizations] or [MaterialLocalizations].
551
  ///
552 553 554 555
  /// For example: to add support to [MaterialLocalizations] for a locale it
  /// doesn't already support, say `const Locale('foo', 'BR')`, one first
  /// creates a subclass of [MaterialLocalizations] that provides the
  /// translations:
556 557
  ///
  /// ```dart
558 559
  /// class FooLocalizations extends MaterialLocalizations {
  ///   FooLocalizations();
560
  ///   @override
561 562 563
  ///   String get okButtonLabel => 'foo';
  ///   // ...
  ///   // lots of other getters and methods to override!
564 565 566
  /// }
  /// ```
  ///
567 568 569 570 571
  /// One must then create a [LocalizationsDelegate] subclass that can provide
  /// an instance of the [MaterialLocalizations] subclass. In this case, this is
  /// essentially just a method that constructs a `FooLocalizations` object. A
  /// [SynchronousFuture] is used here because no asynchronous work takes place
  /// upon "loading" the localizations object.
572 573
  ///
  /// ```dart
574
  /// // continuing from previous example...
575 576 577
  /// class FooLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
  ///   const FooLocalizationsDelegate();
  ///   @override
578 579 580 581
  ///   bool isSupported(Locale locale) {
  ///     return locale == const Locale('foo', 'BR');
  ///   }
  ///   @override
582
  ///   Future<FooLocalizations> load(Locale locale) {
583 584
  ///     assert(locale == const Locale('foo', 'BR'));
  ///     return SynchronousFuture<FooLocalizations>(FooLocalizations());
585 586 587 588 589 590 591 592 593 594 595 596 597
  ///   }
  ///   @override
  ///   bool shouldReload(FooLocalizationsDelegate old) => false;
  /// }
  /// ```
  ///
  /// Constructing a [MaterialApp] with a `FooLocalizationsDelegate` overrides
  /// the automatically included delegate for [MaterialLocalizations] because
  /// only the first delegate of each [LocalizationsDelegate.type] is used and
  /// the automatically included delegates are added to the end of the app's
  /// [localizationsDelegates] list.
  ///
  /// ```dart
598 599 600 601
  /// // continuing from previous example...
  /// const MaterialApp(
  ///   localizationsDelegates: <LocalizationsDelegate<Object>>[
  ///     FooLocalizationsDelegate(),
602 603 604 605
  ///   ],
  ///   // ...
  /// )
  /// ```
606 607 608 609 610 611 612
  /// See also:
  ///
  ///  * [supportedLocales], which must be specified along with
  ///    [localizationsDelegates].
  ///  * [GlobalMaterialLocalizations], a [localizationsDelegates] value
  ///    which provides material localizations for many languages.
  ///  * The Flutter Internationalization Tutorial,
613
  ///    <https://flutter.dev/tutorials/internationalization/>.
614
  final Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates;
615

616 617 618
  /// {@macro flutter.widgets.widgetsApp.localeListResolutionCallback}
  ///
  /// This callback is passed along to the [WidgetsApp] built by this widget.
619
  final LocaleListResolutionCallback? localeListResolutionCallback;
620

621
  /// {@macro flutter.widgets.LocaleResolutionCallback}
622 623
  ///
  /// This callback is passed along to the [WidgetsApp] built by this widget.
624
  final LocaleResolutionCallback? localeResolutionCallback;
625

xster's avatar
xster committed
626
  /// {@macro flutter.widgets.widgetsApp.supportedLocales}
627
  ///
xster's avatar
xster committed
628
  /// It is passed along unmodified to the [WidgetsApp] built by this widget.
629
  ///
630 631 632 633 634 635 636
  /// See also:
  ///
  ///  * [localizationsDelegates], which must be specified for localized
  ///    applications.
  ///  * [GlobalMaterialLocalizations], a [localizationsDelegates] value
  ///    which provides material localizations for many languages.
  ///  * The Flutter Internationalization Tutorial,
637
  ///    <https://flutter.dev/tutorials/internationalization/>.
638 639
  final Iterable<Locale> supportedLocales;

640
  /// Turns on a performance overlay.
641 642 643
  ///
  /// See also:
  ///
644
  ///  * <https://flutter.dev/debugging/#performance-overlay>
645 646
  final bool showPerformanceOverlay;

647 648 649
  /// Turns on checkerboarding of raster cache images.
  final bool checkerboardRasterCacheImages;

650 651 652
  /// Turns on checkerboarding of layers rendered to offscreen bitmaps.
  final bool checkerboardOffscreenLayers;

653 654 655 656
  /// Turns on an overlay that shows the accessibility information
  /// reported by the framework.
  final bool showSemanticsDebugger;

xster's avatar
xster committed
657
  /// {@macro flutter.widgets.widgetsApp.debugShowCheckedModeBanner}
658 659
  final bool debugShowCheckedModeBanner;

660
  /// {@macro flutter.widgets.widgetsApp.shortcuts}
661
  /// {@tool snippet}
662 663 664 665 666 667 668 669 670 671 672
  /// This example shows how to add a single shortcut for
  /// [LogicalKeyboardKey.select] to the default shortcuts without needing to
  /// add your own [Shortcuts] widget.
  ///
  /// Alternatively, you could insert a [Shortcuts] widget with just the mapping
  /// you want to add between the [WidgetsApp] and its child and get the same
  /// effect.
  ///
  /// ```dart
  /// Widget build(BuildContext context) {
  ///   return WidgetsApp(
673
  ///     shortcuts: <ShortcutActivator, Intent>{
674
  ///       ... WidgetsApp.defaultShortcuts,
675
  ///       const SingleActivator(LogicalKeyboardKey.select): const ActivateIntent(),
676 677
  ///     },
  ///     color: const Color(0xFFFF0000),
678
  ///     builder: (BuildContext context, Widget? child) {
679 680 681 682 683 684 685
  ///       return const Placeholder();
  ///     },
  ///   );
  /// }
  /// ```
  /// {@end-tool}
  /// {@macro flutter.widgets.widgetsApp.shortcuts.seeAlso}
686
  final Map<ShortcutActivator, Intent>? shortcuts;
687 688

  /// {@macro flutter.widgets.widgetsApp.actions}
689
  /// {@tool snippet}
690 691 692 693 694 695 696 697 698 699 700
  /// This example shows how to add a single action handling an
  /// [ActivateAction] to the default actions without needing to
  /// add your own [Actions] widget.
  ///
  /// Alternatively, you could insert a [Actions] widget with just the mapping
  /// you want to add between the [WidgetsApp] and its child and get the same
  /// effect.
  ///
  /// ```dart
  /// Widget build(BuildContext context) {
  ///   return WidgetsApp(
701
  ///     actions: <Type, Action<Intent>>{
702
  ///       ... WidgetsApp.defaultActions,
703
  ///       ActivateAction: CallbackAction<Intent>(
704
  ///         onInvoke: (Intent intent) {
705
  ///           // Do something here...
706
  ///           return null;
707 708 709 710
  ///         },
  ///       ),
  ///     },
  ///     color: const Color(0xFFFF0000),
711
  ///     builder: (BuildContext context, Widget? child) {
712 713 714 715 716 717 718
  ///       return const Placeholder();
  ///     },
  ///   );
  /// }
  /// ```
  /// {@end-tool}
  /// {@macro flutter.widgets.widgetsApp.actions.seeAlso}
719
  final Map<Type, Action<Intent>>? actions;
720

721
  /// {@macro flutter.widgets.widgetsApp.restorationScopeId}
722
  final String? restorationScopeId;
723

724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740
  /// {@template flutter.material.materialApp.scrollBehavior}
  /// The default [ScrollBehavior] for the application.
  ///
  /// [ScrollBehavior]s describe how [Scrollable] widgets behave. Providing
  /// a [ScrollBehavior] can set the default [ScrollPhysics] across
  /// an application, and manage [Scrollable] decorations like [Scrollbar]s and
  /// [GlowingOverscrollIndicator]s.
  /// {@endtemplate}
  ///
  /// When null, defaults to [MaterialScrollBehavior].
  ///
  /// See also:
  ///
  ///  * [ScrollConfiguration], which controls how [Scrollable] widgets behave
  ///    in a subtree.
  final ScrollBehavior? scrollBehavior;

741
  /// Turns on a [GridPaper] overlay that paints a baseline grid
742 743
  /// Material apps.
  ///
744
  /// Only available in debug mode.
745 746 747
  ///
  /// See also:
  ///
748
  ///  * <https://material.io/design/layout/spacing-methods.html>
Ian Hickson's avatar
Ian Hickson committed
749
  final bool debugShowMaterialGrid;
750

751 752 753
  /// {@macro flutter.widgets.widgetsApp.useInheritedMediaQuery}
  final bool useInheritedMediaQuery;

754
  @override
755
  State<MaterialApp> createState() => _MaterialAppState();
756 757 758 759 760 761

  /// The [HeroController] used for Material page transitions.
  ///
  /// Used by the [MaterialApp].
  static HeroController createMaterialHeroController() {
    return HeroController(
762
      createRectTween: (Rect? begin, Rect? end) {
763 764 765 766
        return MaterialRectArcTween(begin: begin, end: end);
      },
    );
  }
767 768
}

769 770 771 772 773 774 775 776
/// Describes how [Scrollable] widgets behave for [MaterialApp]s.
///
/// {@macro flutter.widgets.scrollBehavior}
///
/// Setting a [MaterialScrollBehavior] will apply a
/// [GlowingOverscrollIndicator] to [Scrollable] descendants when executing on
/// [TargetPlatform.android] and [TargetPlatform.fuchsia].
///
777 778
/// When using the desktop platform, if the [Scrollable] widget scrolls in the
/// [Axis.vertical], a [Scrollbar] is applied.
779
///
780 781 782 783
/// If the scroll direction is [Axis.horizontal] scroll views are less
/// discoverable, so consider adding a Scrollbar in these cases, either directly
/// or through the [buildScrollbar] method.
///
784 785 786
/// [MaterialScrollBehavior.androidOverscrollIndicator] specifies the
/// overscroll indicator that is used on [TargetPlatform.android]. When null,
/// [ThemeData.androidOverscrollIndicator] is used. If also null, the default
787 788 789 790
/// overscroll indicator is the [GlowingOverscrollIndicator]. These properties
/// are deprecated. In order to use the [StretchingOverscrollIndicator], use
/// the [ThemeData.useMaterial3] flag, or override
/// [ScrollBehavior.buildOverscrollIndicator].
791
///
792 793 794 795
/// See also:
///
///  * [ScrollBehavior], the default scrolling behavior extended by this class.
class MaterialScrollBehavior extends ScrollBehavior {
796 797 798
  /// Creates a MaterialScrollBehavior that decorates [Scrollable]s with
  /// [GlowingOverscrollIndicator]s and [Scrollbar]s based on the current
  /// platform and provided [ScrollableDetails].
799 800 801 802 803
  ///
  /// [MaterialScrollBehavior.androidOverscrollIndicator] specifies the
  /// overscroll indicator that is used on [TargetPlatform.android]. When null,
  /// [ThemeData.androidOverscrollIndicator] is used. If also null, the default
  /// overscroll indicator is the [GlowingOverscrollIndicator].
804
  const MaterialScrollBehavior({
805 806 807 808
    @Deprecated(
      'Use ThemeData.useMaterial3 or override ScrollBehavior.buildOverscrollIndicator. '
      'This feature was deprecated after v2.13.0-0.0.pre.'
    )
809 810
    super.androidOverscrollIndicator,
  }) : _androidOverscrollIndicator = androidOverscrollIndicator;
811 812

  final AndroidOverscrollIndicator? _androidOverscrollIndicator;
813 814 815 816

  @override
  TargetPlatform getPlatform(BuildContext context) => Theme.of(context).platform;

817
  @override
818 819 820 821 822
  Widget buildScrollbar(BuildContext context, Widget child, ScrollableDetails details) {
    // When modifying this function, consider modifying the implementation in
    // the base class as well.
    switch (axisDirectionToAxis(details.direction)) {
      case Axis.horizontal:
823
        return child;
824 825 826 827 828 829 830
      case Axis.vertical:
        switch (getPlatform(context)) {
          case TargetPlatform.linux:
          case TargetPlatform.macOS:
          case TargetPlatform.windows:
            return Scrollbar(
              controller: details.controller,
831
              child: child,
832 833 834 835 836 837 838
            );
          case TargetPlatform.android:
          case TargetPlatform.fuchsia:
          case TargetPlatform.iOS:
            return child;
        }
    }
839 840 841
  }

  @override
842
  Widget buildOverscrollIndicator(BuildContext context, Widget child, ScrollableDetails details) {
843 844
    // When modifying this function, consider modifying the implementation in
    // the base class as well.
845 846 847 848 849
    late final AndroidOverscrollIndicator indicator;
    if (Theme.of(context).useMaterial3) {
      indicator = AndroidOverscrollIndicator.stretch;
    } else {
      indicator = _androidOverscrollIndicator
850 851
        ?? Theme.of(context).androidOverscrollIndicator
        ?? androidOverscrollIndicator;
852
    }
853 854
    switch (getPlatform(context)) {
      case TargetPlatform.iOS:
855
      case TargetPlatform.linux:
856
      case TargetPlatform.macOS:
857
      case TargetPlatform.windows:
858 859
        return child;
      case TargetPlatform.android:
860
        switch (indicator) {
861 862 863
          case AndroidOverscrollIndicator.stretch:
            return StretchingOverscrollIndicator(
              axisDirection: details.direction,
864
              clipBehavior: details.clipBehavior ?? Clip.hardEdge,
865 866 867 868 869 870
              child: child,
            );
          case AndroidOverscrollIndicator.glow:
            continue glow;
        }
      glow:
871
      case TargetPlatform.fuchsia:
872
        return GlowingOverscrollIndicator(
873
          axisDirection: details.direction,
874
          color: Theme.of(context).colorScheme.secondary,
875
          child: child,
876 877
        );
    }
878 879 880
  }
}

881
class _MaterialAppState extends State<MaterialApp> {
882
  late HeroController _heroController;
883

884
  bool get _usesRouter => widget.routerDelegate != null || widget.routerConfig != null;
885

886 887 888
  @override
  void initState() {
    super.initState();
889
    _heroController = MaterialApp.createMaterialHeroController();
890 891
  }

892 893 894 895 896
  // Combine the Localizations for Material with the ones contributed
  // by the localizationsDelegates parameter, if any. Only the first delegate
  // of a particular LocalizationsDelegate.type is loaded so the
  // localizationsDelegate parameter can be used to override
  // _MaterialLocalizationsDelegate.
897 898 899 900 901 902 903
  Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates {
    return <LocalizationsDelegate<dynamic>>[
      if (widget.localizationsDelegates != null)
        ...widget.localizationsDelegates!,
      DefaultMaterialLocalizations.delegate,
      DefaultCupertinoLocalizations.delegate,
    ];
904 905
  }

906 907 908 909
  Widget _inspectorSelectButtonBuilder(BuildContext context, VoidCallback onPressed) {
    return FloatingActionButton(
      onPressed: onPressed,
      mini: true,
910
      child: const Icon(Icons.search),
911 912 913
    );
  }

914
  Widget _materialBuilder(BuildContext context, Widget? child) {
915 916 917 918 919 920
    // Resolve which theme to use based on brightness and high contrast.
    final ThemeMode mode = widget.themeMode ?? ThemeMode.system;
    final Brightness platformBrightness = MediaQuery.platformBrightnessOf(context);
    final bool useDarkTheme = mode == ThemeMode.dark
      || (mode == ThemeMode.system && platformBrightness == ui.Brightness.dark);
    final bool highContrast = MediaQuery.highContrastOf(context);
921
    ThemeData? theme;
922 923 924 925 926 927 928 929 930

    if (useDarkTheme && highContrast && widget.highContrastDarkTheme != null) {
      theme = widget.highContrastDarkTheme;
    } else if (useDarkTheme && widget.darkTheme != null) {
      theme = widget.darkTheme;
    } else if (highContrast && widget.highContrastTheme != null) {
      theme = widget.highContrastTheme;
    }
    theme ??= widget.theme ?? ThemeData.light();
931 932
    final Color effectiveSelectionColor = theme.textSelectionTheme.selectionColor ?? theme.colorScheme.primary.withOpacity(0.40);
    final Color effectiveCursorColor = theme.textSelectionTheme.cursorColor ?? theme.colorScheme.primary;
933

934 935
    return ScaffoldMessenger(
      key: widget.scaffoldMessengerKey,
936 937 938 939 940
      child: DefaultSelectionStyle(
        selectionColor: effectiveSelectionColor,
        cursorColor: effectiveCursorColor,
        child: AnimatedTheme(
          data: theme,
941 942
          duration: widget.themeAnimationDuration,
          curve: widget.themeAnimationCurve,
943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961
          child: widget.builder != null
            ? Builder(
                builder: (BuildContext context) {
                  // Why are we surrounding a builder with a builder?
                  //
                  // The widget.builder may contain code that invokes
                  // Theme.of(), which should return the theme we selected
                  // above in AnimatedTheme. However, if we invoke
                  // widget.builder() directly as the child of AnimatedTheme
                  // then there is no Context separating them, and the
                  // widget.builder() will not find the theme. Therefore, we
                  // surround widget.builder with yet another builder so that
                  // a context separates them and Theme.of() correctly
                  // resolves to the theme we passed to AnimatedTheme.
                  return widget.builder!(context, child);
                },
              )
            : child ?? const SizedBox.shrink(),
        ),
962
      ),
963 964 965 966 967 968 969 970 971 972 973 974 975 976
    );
  }

  Widget _buildWidgetApp(BuildContext context) {
    // The color property is always pulled from the light theme, even if dark
    // mode is activated. This was done to simplify the technical details
    // of switching themes and it was deemed acceptable because this color
    // property is only used on old Android OSes to color the app bar in
    // Android's switcher UI.
    //
    // blue is the primary color of the default theme.
    final Color materialColor = widget.color ?? widget.theme?.primaryColor ?? Colors.blue;
    if (_usesRouter) {
      return WidgetsApp.router(
977
        key: GlobalObjectKey(this),
978
        routeInformationProvider: widget.routeInformationProvider,
979 980 981
        routeInformationParser: widget.routeInformationParser,
        routerDelegate: widget.routerDelegate,
        routerConfig: widget.routerConfig,
982 983
        backButtonDispatcher: widget.backButtonDispatcher,
        builder: _materialBuilder,
984 985 986
        title: widget.title,
        onGenerateTitle: widget.onGenerateTitle,
        textStyle: _errorTextStyle,
987
        color: materialColor,
988 989 990 991 992 993 994 995 996 997
        locale: widget.locale,
        localizationsDelegates: _localizationsDelegates,
        localeResolutionCallback: widget.localeResolutionCallback,
        localeListResolutionCallback: widget.localeListResolutionCallback,
        supportedLocales: widget.supportedLocales,
        showPerformanceOverlay: widget.showPerformanceOverlay,
        checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
        checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
        showSemanticsDebugger: widget.showSemanticsDebugger,
        debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
998
        inspectorSelectButtonBuilder: _inspectorSelectButtonBuilder,
999 1000
        shortcuts: widget.shortcuts,
        actions: widget.actions,
1001
        restorationScopeId: widget.restorationScopeId,
1002
        useInheritedMediaQuery: widget.useInheritedMediaQuery,
1003 1004 1005 1006 1007 1008
      );
    }

    return WidgetsApp(
      key: GlobalObjectKey(this),
      navigatorKey: widget.navigatorKey,
1009
      navigatorObservers: widget.navigatorObservers!,
1010 1011 1012 1013
      pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) {
        return MaterialPageRoute<T>(settings: settings, builder: builder);
      },
      home: widget.home,
1014
      routes: widget.routes!,
1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036
      initialRoute: widget.initialRoute,
      onGenerateRoute: widget.onGenerateRoute,
      onGenerateInitialRoutes: widget.onGenerateInitialRoutes,
      onUnknownRoute: widget.onUnknownRoute,
      builder: _materialBuilder,
      title: widget.title,
      onGenerateTitle: widget.onGenerateTitle,
      textStyle: _errorTextStyle,
      color: materialColor,
      locale: widget.locale,
      localizationsDelegates: _localizationsDelegates,
      localeResolutionCallback: widget.localeResolutionCallback,
      localeListResolutionCallback: widget.localeListResolutionCallback,
      supportedLocales: widget.supportedLocales,
      showPerformanceOverlay: widget.showPerformanceOverlay,
      checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
      checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
      showSemanticsDebugger: widget.showSemanticsDebugger,
      debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
      inspectorSelectButtonBuilder: _inspectorSelectButtonBuilder,
      shortcuts: widget.shortcuts,
      actions: widget.actions,
1037
      restorationScopeId: widget.restorationScopeId,
1038
      useInheritedMediaQuery: widget.useInheritedMediaQuery,
1039
    );
1040 1041 1042 1043 1044
  }

  @override
  Widget build(BuildContext context) {
    Widget result = _buildWidgetApp(context);
1045 1046 1047
    result = Focus(
      canRequestFocus: false,
      onKey: (FocusNode node, RawKeyEvent event) {
1048
        if (event is! RawKeyDownEvent || event.logicalKey != LogicalKeyboardKey.escape) {
1049
          return KeyEventResult.ignored;
1050
        }
1051 1052 1053 1054
        return Tooltip.dismissAllToolTips() ? KeyEventResult.handled : KeyEventResult.ignored;
      },
      child: result,
    );
Ian Hickson's avatar
Ian Hickson committed
1055
    assert(() {
1056
      if (widget.debugShowMaterialGrid) {
1057
        result = GridPaper(
Ian Hickson's avatar
Ian Hickson committed
1058 1059
          color: const Color(0xE0F9BBE0),
          interval: 8.0,
1060 1061
          subdivisions: 1,
          child: result,
Ian Hickson's avatar
Ian Hickson committed
1062 1063 1064
        );
      }
      return true;
1065
    }());
1066

1067
    return ScrollConfiguration(
1068
      behavior: widget.scrollBehavior ?? const MaterialScrollBehavior(),
1069 1070 1071
      child: HeroControllerScope(
        controller: _heroController,
        child: result,
1072
      ),
1073
    );
1074
  }
1075
}