app.dart 30.4 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
// @dart = 2.8

7 8
import 'dart:ui' as ui;

9
import 'package:flutter/cupertino.dart';
10
import 'package:flutter/foundation.dart';
11
import 'package:flutter/rendering.dart';
12
import 'package:flutter/widgets.dart';
13

14
import 'arc.dart';
15
import 'colors.dart';
16 17
import 'floating_action_button.dart';
import 'icons.dart';
18
import 'material_localizations.dart';
19
import 'page.dart';
20
import 'theme.dart';
21

22 23 24 25 26 27 28
/// [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]).
29 30
const TextStyle _errorTextStyle = TextStyle(
  color: Color(0xD0FF0000),
31 32 33
  fontFamily: 'monospace',
  fontSize: 48.0,
  fontWeight: FontWeight.w900,
34
  decoration: TextDecoration.underline,
35
  decorationColor: Color(0xFFFFFF00),
36 37
  decorationStyle: TextDecorationStyle.double,
  debugLabel: 'fallback style; consider putting your text in a Material',
38 39
);

40 41 42 43 44 45 46 47 48 49 50 51 52
/// 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,
}

53 54 55
/// An application that uses material design.
///
/// A convenience widget that wraps a number of widgets that are commonly
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
/// required for material design applications. It builds upon a [WidgetsApp] by
/// adding material-design specific functionality, such as [AnimatedTheme] and
/// [GridPaper].
///
/// 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.
///
72 73 74 75
/// 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
/// Android; see [Window.defaultRouteName]).
76
///
77 78 79 80 81
/// This widget also configures the observer of the top-level [Navigator] (if
/// any) to perform [Hero] animations.
///
/// If [home], [routes], [onGenerateRoute], and [onUnknownRoute] are all null,
/// and [builder] is not null, then no [Navigator] is created.
82
///
83
/// {@tool snippet}
84 85 86
/// 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.
///
87
/// ![The MaterialApp displays a Scaffold ](https://flutter.github.io/assets-for-api-docs/assets/material/basic_material_app.png)
88 89 90 91 92 93 94 95 96 97 98 99 100
///
/// ```dart
/// MaterialApp(
///   home: Scaffold(
///     appBar: AppBar(
///       title: const Text('Home'),
///     ),
///   ),
///   debugShowCheckedModeBanner: false,
/// )
/// ```
/// {@end-tool}
///
101
/// {@tool snippet}
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
/// 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}
///
127
/// {@tool snippet}
128 129 130
/// This example shows how to create a [MaterialApp] that defines a [theme] that
/// will be used for material widgets in the app.
///
131
/// ![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)
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
///
/// ```dart
/// MaterialApp(
///   theme: ThemeData(
///     brightness: Brightness.dark,
///     primaryColor: Colors.blueGrey
///   ),
///   home: Scaffold(
///     appBar: AppBar(
///       title: const Text('MaterialApp Theme'),
///     ),
///   ),
/// )
/// ```
/// {@end-tool}
///
148 149
/// See also:
///
150 151 152 153
///  * [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.
154
///  * The Flutter Internationalization Tutorial,
155
///    <https://flutter.dev/tutorials/internationalization/>.
156
class MaterialApp extends StatefulWidget {
157 158
  /// Creates a MaterialApp.
  ///
159 160
  /// 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
161 162 163
  /// [Navigator.defaultRouteName] (`/`), since that is the route used when the
  /// application is launched with an intent that specifies an otherwise
  /// unsupported route.
164
  ///
165
  /// This class creates an instance of [WidgetsApp].
166 167
  ///
  /// The boolean arguments, [routes], and [navigatorObservers], must not be null.
168
  const MaterialApp({
169
    Key key,
170
    this.navigatorKey,
171
    this.home,
172
    this.routes = const <String, WidgetBuilder>{},
173
    this.initialRoute,
174
    this.onGenerateRoute,
175
    this.onGenerateInitialRoutes,
176
    this.onUnknownRoute,
177
    this.navigatorObservers = const <NavigatorObserver>[],
178
    this.builder,
179
    this.title = '',
180 181 182
    this.onGenerateTitle,
    this.color,
    this.theme,
183
    this.darkTheme,
184 185
    this.highContrastTheme,
    this.highContrastDarkTheme,
186
    this.themeMode = ThemeMode.system,
187 188
    this.locale,
    this.localizationsDelegates,
189
    this.localeListResolutionCallback,
190
    this.localeResolutionCallback,
191
    this.supportedLocales = const <Locale>[Locale('en', 'US')],
192 193 194 195 196 197
    this.debugShowMaterialGrid = false,
    this.showPerformanceOverlay = false,
    this.checkerboardRasterCacheImages = false,
    this.checkerboardOffscreenLayers = false,
    this.showSemanticsDebugger = false,
    this.debugShowCheckedModeBanner = true,
198 199
    this.shortcuts,
    this.actions,
200
  }) : assert(routes != null),
201
       assert(navigatorObservers != null),
202 203 204 205 206 207 208
       assert(title != null),
       assert(debugShowMaterialGrid != null),
       assert(showPerformanceOverlay != null),
       assert(checkerboardRasterCacheImages != null),
       assert(checkerboardOffscreenLayers != null),
       assert(showSemanticsDebugger != null),
       assert(debugShowCheckedModeBanner != null),
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
       routeInformationProvider = null,
       routeInformationParser = null,
       routerDelegate = null,
       backButtonDispatcher = null,
       super(key: key);

  /// Creates a [MaterialApp] that uses the [Router] instead of a [Navigator].
  const MaterialApp.router({
    Key key,
    this.routeInformationProvider,
    @required this.routeInformationParser,
    @required this.routerDelegate,
    this.backButtonDispatcher,
    this.builder,
    this.title = '',
    this.onGenerateTitle,
    this.color,
    this.theme,
    this.darkTheme,
    this.highContrastTheme,
    this.highContrastDarkTheme,
    this.themeMode = ThemeMode.system,
    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,
  }) : assert(routeInformationParser != null),
       assert(routerDelegate != null),
       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,
       initialRoute = null,
261
       super(key: key);
262

263
  /// {@macro flutter.widgets.widgetsApp.navigatorKey}
264
  final GlobalKey<NavigatorState> navigatorKey;
265

266
  /// {@macro flutter.widgets.widgetsApp.home}
267
  final Widget home;
268

269 270 271 272 273 274
  /// 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
  /// [WidgetBuilder] is used to construct a [MaterialPageRoute] that performs
  /// an appropriate transition, including [Hero] animations, to the new route.
275
  ///
276
  /// {@macro flutter.widgets.widgetsApp.routes}
Ian Hickson's avatar
Ian Hickson committed
277 278
  final Map<String, WidgetBuilder> routes;

xster's avatar
xster committed
279
  /// {@macro flutter.widgets.widgetsApp.initialRoute}
280 281
  final String initialRoute;

xster's avatar
xster committed
282
  /// {@macro flutter.widgets.widgetsApp.onGenerateRoute}
283 284
  final RouteFactory onGenerateRoute;

285 286 287
  /// {@macro flutter.widgets.widgetsApp.onGenerateInitialRoutes}
  final InitialRouteListFactory onGenerateInitialRoutes;

xster's avatar
xster committed
288
  /// {@macro flutter.widgets.widgetsApp.onUnknownRoute}
289 290
  final RouteFactory onUnknownRoute;

xster's avatar
xster committed
291
  /// {@macro flutter.widgets.widgetsApp.navigatorObservers}
292 293
  final List<NavigatorObserver> navigatorObservers;

294 295 296 297 298 299 300 301 302 303 304 305
  /// {@macro flutter.widgets.widgetsApp.routeInformationProvider}
  final RouteInformationProvider routeInformationProvider;

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

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

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

xster's avatar
xster committed
306
  /// {@macro flutter.widgets.widgetsApp.builder}
307
  ///
308 309
  /// Material specific features such as [showDialog] and [showMenu], and widgets
  /// such as [Tooltip], [PopupMenuButton], also require a [Navigator] to properly
310 311 312
  /// function.
  final TransitionBuilder builder;

xster's avatar
xster committed
313
  /// {@macro flutter.widgets.widgetsApp.title}
314 315 316 317
  ///
  /// This value is passed unmodified to [WidgetsApp.title].
  final String title;

xster's avatar
xster committed
318
  /// {@macro flutter.widgets.widgetsApp.onGenerateTitle}
319 320 321 322
  ///
  /// This value is passed unmodified to [WidgetsApp.onGenerateTitle].
  final GenerateAppTitle onGenerateTitle;

323 324 325
  /// Default visual properties, like colors fonts and shapes, for this app's
  /// material widgets.
  ///
326 327 328
  /// 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.
329 330 331 332 333
  ///
  /// The default value of this property is the value of [ThemeData.light()].
  ///
  /// See also:
  ///
334
  ///  * [themeMode], which controls which theme to use.
335 336 337 338 339
  ///  * [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.
340 341
  final ThemeData theme;

342
  /// The [ThemeData] to use when a 'dark mode' is requested by the system.
343
  ///
344 345 346 347
  /// 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.
348
  ///
349
  /// This theme should have a [ThemeData.brightness] set to [Brightness.dark].
350 351 352 353 354 355
  ///
  /// Uses [theme] instead when null. Defaults to the value of
  /// [ThemeData.light()] when both [darkTheme] and [theme] are null.
  ///
  /// See also:
  ///
356
  ///  * [themeMode], which controls which theme to use.
357 358 359 360 361 362 363
  ///  * [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].
  final ThemeData darkTheme;

364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
  /// 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.
  final ThemeData highContrastTheme;

  /// 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.
  final ThemeData highContrastDarkTheme;

393 394 395 396 397 398
  /// 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],
399
  /// [darkTheme] will be used (unless it is null, in which case [theme]
400 401 402 403 404 405
  /// 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
406
  /// regardless of the user's system preference. If [darkTheme] is null
407 408 409 410 411 412
  /// then it will fallback to using [theme].
  ///
  /// The default value is [ThemeMode.system].
  ///
  /// See also:
  ///
413 414 415 416
  ///  * [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.
417 418
  final ThemeMode themeMode;

xster's avatar
xster committed
419
  /// {@macro flutter.widgets.widgetsApp.color}
420 421
  final Color color;

xster's avatar
xster committed
422
  /// {@macro flutter.widgets.widgetsApp.locale}
423 424
  final Locale locale;

xster's avatar
xster committed
425
  /// {@macro flutter.widgets.widgetsApp.localizationsDelegates}
426
  ///
427
  /// Internationalized apps that require translations for one of the locales
428
  /// listed in [GlobalMaterialLocalizations] should specify this parameter
429 430 431 432 433
  /// and list the [supportedLocales] that the application can handle.
  ///
  /// ```dart
  /// import 'package:flutter_localizations/flutter_localizations.dart';
  /// MaterialApp(
434 435 436 437 438 439
  ///   localizationsDelegates: [
  ///     // ... app-specific localization delegate[s] here
  ///     GlobalMaterialLocalizations.delegate,
  ///     GlobalWidgetsLocalizations.delegate,
  ///   ],
  ///   supportedLocales: [
440 441 442 443 444 445 446 447 448 449 450 451 452 453
  ///     const Locale('en', 'US'), // English
  ///     const Locale('he', 'IL'), // Hebrew
  ///     // ... 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].
  ///
454 455 456
  /// Delegates that produce [WidgetsLocalizations] and [MaterialLocalizations]
  /// are included automatically. Apps can provide their own versions of these
  /// localizations by creating implementations of
457
  /// [LocalizationsDelegate<WidgetsLocalizations>] or
458
  /// [LocalizationsDelegate<MaterialLocalizations>] whose load methods return
459
  /// custom versions of [WidgetsLocalizations] or [MaterialLocalizations].
460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486
  ///
  /// For example: to add support to [MaterialLocalizations] for a
  /// locale it doesn't already support, say `const Locale('foo', 'BR')`,
  /// one could just extend [DefaultMaterialLocalizations]:
  ///
  /// ```dart
  /// class FooLocalizations extends DefaultMaterialLocalizations {
  ///   FooLocalizations(Locale locale) : super(locale);
  ///   @override
  ///   String get okButtonLabel {
  ///     if (locale == const Locale('foo', 'BR'))
  ///       return 'foo';
  ///     return super.okButtonLabel;
  ///   }
  /// }
  ///
  /// ```
  ///
  /// A `FooLocalizationsDelegate` is essentially just a method that constructs
  /// a `FooLocalizations` object. We return a [SynchronousFuture] here because
  /// no asynchronous work takes place upon "loading" the localizations object.
  ///
  /// ```dart
  /// class FooLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
  ///   const FooLocalizationsDelegate();
  ///   @override
  ///   Future<FooLocalizations> load(Locale locale) {
487
  ///     return SynchronousFuture(FooLocalizations(locale));
488 489 490 491 492 493 494 495 496 497 498 499 500
  ///   }
  ///   @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
501
  /// MaterialApp(
502 503 504 505 506 507
  ///   localizationsDelegates: [
  ///     const FooLocalizationsDelegate(),
  ///   ],
  ///   // ...
  /// )
  /// ```
508 509 510 511 512 513 514
  /// 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,
515
  ///    <https://flutter.dev/tutorials/internationalization/>.
516
  final Iterable<LocalizationsDelegate<dynamic>> localizationsDelegates;
517

518 519 520 521 522
  /// {@macro flutter.widgets.widgetsApp.localeListResolutionCallback}
  ///
  /// This callback is passed along to the [WidgetsApp] built by this widget.
  final LocaleListResolutionCallback localeListResolutionCallback;

xster's avatar
xster committed
523
  /// {@macro flutter.widgets.widgetsApp.localeResolutionCallback}
524 525 526 527
  ///
  /// This callback is passed along to the [WidgetsApp] built by this widget.
  final LocaleResolutionCallback localeResolutionCallback;

xster's avatar
xster committed
528
  /// {@macro flutter.widgets.widgetsApp.supportedLocales}
529
  ///
xster's avatar
xster committed
530
  /// It is passed along unmodified to the [WidgetsApp] built by this widget.
531
  ///
532 533 534 535 536 537 538
  /// 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,
539
  ///    <https://flutter.dev/tutorials/internationalization/>.
540 541
  final Iterable<Locale> supportedLocales;

542
  /// Turns on a performance overlay.
543 544 545
  ///
  /// See also:
  ///
546
  ///  * <https://flutter.dev/debugging/#performanceoverlay>
547 548
  final bool showPerformanceOverlay;

549 550 551
  /// Turns on checkerboarding of raster cache images.
  final bool checkerboardRasterCacheImages;

552 553 554
  /// Turns on checkerboarding of layers rendered to offscreen bitmaps.
  final bool checkerboardOffscreenLayers;

555 556 557 558
  /// Turns on an overlay that shows the accessibility information
  /// reported by the framework.
  final bool showSemanticsDebugger;

xster's avatar
xster committed
559
  /// {@macro flutter.widgets.widgetsApp.debugShowCheckedModeBanner}
560 561
  final bool debugShowCheckedModeBanner;

562
  /// {@macro flutter.widgets.widgetsApp.shortcuts}
563
  /// {@tool snippet}
564 565 566 567 568 569 570 571 572 573 574 575 576
  /// 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(
  ///     shortcuts: <LogicalKeySet, Intent>{
  ///       ... WidgetsApp.defaultShortcuts,
577
  ///       LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
578 579 580 581 582 583 584 585 586 587 588 589 590
  ///     },
  ///     color: const Color(0xFFFF0000),
  ///     builder: (BuildContext context, Widget child) {
  ///       return const Placeholder();
  ///     },
  ///   );
  /// }
  /// ```
  /// {@end-tool}
  /// {@macro flutter.widgets.widgetsApp.shortcuts.seeAlso}
  final Map<LogicalKeySet, Intent> shortcuts;

  /// {@macro flutter.widgets.widgetsApp.actions}
591
  /// {@tool snippet}
592 593 594 595 596 597 598 599 600 601 602
  /// 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(
603
  ///     actions: <Type, Action<Intent>>{
604
  ///       ... WidgetsApp.defaultActions,
605 606
  ///       ActivateAction: CallbackAction(
  ///         onInvoke: (Intent intent) {
607
  ///           // Do something here...
608
  ///           return null;
609 610 611 612 613 614 615 616 617 618 619 620
  ///         },
  ///       ),
  ///     },
  ///     color: const Color(0xFFFF0000),
  ///     builder: (BuildContext context, Widget child) {
  ///       return const Placeholder();
  ///     },
  ///   );
  /// }
  /// ```
  /// {@end-tool}
  /// {@macro flutter.widgets.widgetsApp.actions.seeAlso}
621
  final Map<Type, Action<Intent>> actions;
622

623
  /// Turns on a [GridPaper] overlay that paints a baseline grid
624 625
  /// Material apps.
  ///
626
  /// Only available in checked mode.
627 628 629
  ///
  /// See also:
  ///
630
  ///  * <https://material.io/design/layout/spacing-methods.html>
Ian Hickson's avatar
Ian Hickson committed
631
  final bool debugShowMaterialGrid;
632

633
  @override
634
  _MaterialAppState createState() => _MaterialAppState();
635 636 637 638 639 640 641 642 643 644 645

  /// The [HeroController] used for Material page transitions.
  ///
  /// Used by the [MaterialApp].
  static HeroController createMaterialHeroController() {
    return HeroController(
      createRectTween: (Rect begin, Rect end) {
        return MaterialRectArcTween(begin: begin, end: end);
      },
    );
  }
646 647
}

Adam Barth's avatar
Adam Barth committed
648
class _MaterialScrollBehavior extends ScrollBehavior {
649 650 651 652 653 654
  @override
  TargetPlatform getPlatform(BuildContext context) {
    return Theme.of(context).platform;
  }

  @override
655 656 657 658 659
  Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) {
    // When modifying this function, consider modifying the implementation in
    // the base class as well.
    switch (getPlatform(context)) {
      case TargetPlatform.iOS:
660
      case TargetPlatform.linux:
661
      case TargetPlatform.macOS:
662
      case TargetPlatform.windows:
663 664 665
        return child;
      case TargetPlatform.android:
      case TargetPlatform.fuchsia:
666
        return GlowingOverscrollIndicator(
667 668 669 670 671 672
          child: child,
          axisDirection: axisDirection,
          color: Theme.of(context).accentColor,
        );
    }
    return null;
673 674 675
  }
}

676
class _MaterialAppState extends State<MaterialApp> {
677 678
  HeroController _heroController;

679 680
  bool get _usesRouter => widget.routerDelegate != null;

681 682 683
  @override
  void initState() {
    super.initState();
684
    _heroController = MaterialApp.createMaterialHeroController();
685 686
  }

687 688 689 690 691 692 693 694 695
  // 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.
  Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates sync* {
    if (widget.localizationsDelegates != null)
      yield* widget.localizationsDelegates;
    yield DefaultMaterialLocalizations.delegate;
696
    yield DefaultCupertinoLocalizations.delegate;
697 698
  }

699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759
  Widget _inspectorSelectButtonBuilder(BuildContext context, VoidCallback onPressed) {
    return FloatingActionButton(
      child: const Icon(Icons.search),
      onPressed: onPressed,
      mini: true,
    );
  }

  Widget _materialBuilder(BuildContext context, Widget child) {
    // 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);
    ThemeData theme;

    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();

    return AnimatedTheme(
      data: theme,
      isMaterialAppTheme: true,
      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,
    );
  }

  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(
760
        key: GlobalObjectKey(this),
761 762 763 764 765
        routeInformationProvider: widget.routeInformationProvider,
        routeInformationParser: widget.routeInformationParser,
        routerDelegate: widget.routerDelegate,
        backButtonDispatcher: widget.backButtonDispatcher,
        builder: _materialBuilder,
766 767 768
        title: widget.title,
        onGenerateTitle: widget.onGenerateTitle,
        textStyle: _errorTextStyle,
769
        color: materialColor,
770 771 772 773 774 775 776 777 778 779
        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,
780
        inspectorSelectButtonBuilder: _inspectorSelectButtonBuilder,
781 782
        shortcuts: widget.shortcuts,
        actions: widget.actions,
783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816
      );
    }

    return WidgetsApp(
      key: GlobalObjectKey(this),
      navigatorKey: widget.navigatorKey,
      navigatorObservers: widget.navigatorObservers,
      pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) {
        return MaterialPageRoute<T>(settings: settings, builder: builder);
      },
      home: widget.home,
      routes: widget.routes,
      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,
817
    );
818 819 820 821 822
  }

  @override
  Widget build(BuildContext context) {
    Widget result = _buildWidgetApp(context);
823

Ian Hickson's avatar
Ian Hickson committed
824
    assert(() {
825
      if (widget.debugShowMaterialGrid) {
826
        result = GridPaper(
Ian Hickson's avatar
Ian Hickson committed
827 828 829
          color: const Color(0xE0F9BBE0),
          interval: 8.0,
          divisions: 2,
830 831
          subdivisions: 1,
          child: result,
Ian Hickson's avatar
Ian Hickson committed
832 833 834
        );
      }
      return true;
835
    }());
836

837 838
    return ScrollConfiguration(
      behavior: _MaterialScrollBehavior(),
839 840 841 842
      child: HeroControllerScope(
        controller: _heroController,
        child: result,
      )
843
    );
844
  }
845
}