app.dart 26.8 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/rendering.dart';
11
import 'package:flutter/widgets.dart';
12

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

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

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

52 53 54
/// An application that uses material design.
///
/// A convenience widget that wraps a number of widgets that are commonly
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
/// 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.
///
71 72 73 74
/// 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]).
75
///
76 77 78 79 80
/// 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.
81
///
82
/// {@tool snippet}
83 84 85
/// 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.
///
86
/// ![The MaterialApp displays a Scaffold ](https://flutter.github.io/assets-for-api-docs/assets/material/basic_material_app.png)
87 88 89 90 91 92 93 94 95 96 97 98 99
///
/// ```dart
/// MaterialApp(
///   home: Scaffold(
///     appBar: AppBar(
///       title: const Text('Home'),
///     ),
///   ),
///   debugShowCheckedModeBanner: false,
/// )
/// ```
/// {@end-tool}
///
100
/// {@tool snippet}
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
/// 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}
///
126
/// {@tool snippet}
127 128 129
/// This example shows how to create a [MaterialApp] that defines a [theme] that
/// will be used for material widgets in the app.
///
130
/// ![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)
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
///
/// ```dart
/// MaterialApp(
///   theme: ThemeData(
///     brightness: Brightness.dark,
///     primaryColor: Colors.blueGrey
///   ),
///   home: Scaffold(
///     appBar: AppBar(
///       title: const Text('MaterialApp Theme'),
///     ),
///   ),
/// )
/// ```
/// {@end-tool}
///
147 148
/// See also:
///
149 150 151 152
///  * [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.
153
///  * The Flutter Internationalization Tutorial,
154
///    <https://flutter.dev/tutorials/internationalization/>.
155
class MaterialApp extends StatefulWidget {
156 157
  /// Creates a MaterialApp.
  ///
158 159
  /// 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
160 161 162
  /// [Navigator.defaultRouteName] (`/`), since that is the route used when the
  /// application is launched with an intent that specifies an otherwise
  /// unsupported route.
163
  ///
164
  /// This class creates an instance of [WidgetsApp].
165 166
  ///
  /// The boolean arguments, [routes], and [navigatorObservers], must not be null.
167
  const MaterialApp({
168
    Key key,
169
    this.navigatorKey,
170
    this.home,
171
    this.routes = const <String, WidgetBuilder>{},
172
    this.initialRoute,
173
    this.onGenerateRoute,
174
    this.onGenerateInitialRoutes,
175
    this.onUnknownRoute,
176
    this.navigatorObservers = const <NavigatorObserver>[],
177
    this.builder,
178
    this.title = '',
179 180 181
    this.onGenerateTitle,
    this.color,
    this.theme,
182
    this.darkTheme,
183 184
    this.highContrastTheme,
    this.highContrastDarkTheme,
185
    this.themeMode = ThemeMode.system,
186 187
    this.locale,
    this.localizationsDelegates,
188
    this.localeListResolutionCallback,
189
    this.localeResolutionCallback,
190
    this.supportedLocales = const <Locale>[Locale('en', 'US')],
191 192 193 194 195 196
    this.debugShowMaterialGrid = false,
    this.showPerformanceOverlay = false,
    this.checkerboardRasterCacheImages = false,
    this.checkerboardOffscreenLayers = false,
    this.showSemanticsDebugger = false,
    this.debugShowCheckedModeBanner = true,
197 198
    this.shortcuts,
    this.actions,
199
  }) : assert(routes != null),
200
       assert(navigatorObservers != null),
201 202 203 204 205 206 207
       assert(title != null),
       assert(debugShowMaterialGrid != null),
       assert(showPerformanceOverlay != null),
       assert(checkerboardRasterCacheImages != null),
       assert(checkerboardOffscreenLayers != null),
       assert(showSemanticsDebugger != null),
       assert(debugShowCheckedModeBanner != null),
208
       super(key: key);
209

210
  /// {@macro flutter.widgets.widgetsApp.navigatorKey}
211
  final GlobalKey<NavigatorState> navigatorKey;
212

213
  /// {@macro flutter.widgets.widgetsApp.home}
214
  final Widget home;
215

216 217 218 219 220 221
  /// 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.
222
  ///
223
  /// {@macro flutter.widgets.widgetsApp.routes}
Ian Hickson's avatar
Ian Hickson committed
224 225
  final Map<String, WidgetBuilder> routes;

xster's avatar
xster committed
226
  /// {@macro flutter.widgets.widgetsApp.initialRoute}
227 228
  final String initialRoute;

xster's avatar
xster committed
229
  /// {@macro flutter.widgets.widgetsApp.onGenerateRoute}
230 231
  final RouteFactory onGenerateRoute;

232 233 234
  /// {@macro flutter.widgets.widgetsApp.onGenerateInitialRoutes}
  final InitialRouteListFactory onGenerateInitialRoutes;

xster's avatar
xster committed
235
  /// {@macro flutter.widgets.widgetsApp.onUnknownRoute}
236 237
  final RouteFactory onUnknownRoute;

xster's avatar
xster committed
238
  /// {@macro flutter.widgets.widgetsApp.navigatorObservers}
239 240
  final List<NavigatorObserver> navigatorObservers;

xster's avatar
xster committed
241
  /// {@macro flutter.widgets.widgetsApp.builder}
242
  ///
243 244
  /// Material specific features such as [showDialog] and [showMenu], and widgets
  /// such as [Tooltip], [PopupMenuButton], also require a [Navigator] to properly
245 246 247
  /// function.
  final TransitionBuilder builder;

xster's avatar
xster committed
248
  /// {@macro flutter.widgets.widgetsApp.title}
249 250 251 252
  ///
  /// This value is passed unmodified to [WidgetsApp.title].
  final String title;

xster's avatar
xster committed
253
  /// {@macro flutter.widgets.widgetsApp.onGenerateTitle}
254 255 256 257
  ///
  /// This value is passed unmodified to [WidgetsApp.onGenerateTitle].
  final GenerateAppTitle onGenerateTitle;

258 259 260
  /// Default visual properties, like colors fonts and shapes, for this app's
  /// material widgets.
  ///
261 262 263
  /// 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.
264 265 266 267 268
  ///
  /// The default value of this property is the value of [ThemeData.light()].
  ///
  /// See also:
  ///
269
  ///  * [themeMode], which controls which theme to use.
270 271 272 273 274
  ///  * [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.
275 276
  final ThemeData theme;

277
  /// The [ThemeData] to use when a 'dark mode' is requested by the system.
278
  ///
279 280 281 282
  /// 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.
283
  ///
284
  /// This theme should have a [ThemeData.brightness] set to [Brightness.dark].
285 286 287 288 289 290
  ///
  /// Uses [theme] instead when null. Defaults to the value of
  /// [ThemeData.light()] when both [darkTheme] and [theme] are null.
  ///
  /// See also:
  ///
291
  ///  * [themeMode], which controls which theme to use.
292 293 294 295 296 297 298
  ///  * [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;

299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
  /// 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;

328 329 330 331 332 333
  /// 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],
334
  /// [darkTheme] will be used (unless it is null, in which case [theme]
335 336 337 338 339 340
  /// 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
341
  /// regardless of the user's system preference. If [darkTheme] is null
342 343 344 345 346 347
  /// then it will fallback to using [theme].
  ///
  /// The default value is [ThemeMode.system].
  ///
  /// See also:
  ///
348 349 350 351
  ///  * [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.
352 353
  final ThemeMode themeMode;

xster's avatar
xster committed
354
  /// {@macro flutter.widgets.widgetsApp.color}
355 356
  final Color color;

xster's avatar
xster committed
357
  /// {@macro flutter.widgets.widgetsApp.locale}
358 359
  final Locale locale;

xster's avatar
xster committed
360
  /// {@macro flutter.widgets.widgetsApp.localizationsDelegates}
361
  ///
362
  /// Internationalized apps that require translations for one of the locales
363
  /// listed in [GlobalMaterialLocalizations] should specify this parameter
364 365 366 367 368
  /// and list the [supportedLocales] that the application can handle.
  ///
  /// ```dart
  /// import 'package:flutter_localizations/flutter_localizations.dart';
  /// MaterialApp(
369 370 371 372 373 374
  ///   localizationsDelegates: [
  ///     // ... app-specific localization delegate[s] here
  ///     GlobalMaterialLocalizations.delegate,
  ///     GlobalWidgetsLocalizations.delegate,
  ///   ],
  ///   supportedLocales: [
375 376 377 378 379 380 381 382 383 384 385 386 387 388
  ///     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].
  ///
389 390 391
  /// Delegates that produce [WidgetsLocalizations] and [MaterialLocalizations]
  /// are included automatically. Apps can provide their own versions of these
  /// localizations by creating implementations of
392
  /// [LocalizationsDelegate<WidgetsLocalizations>] or
393
  /// [LocalizationsDelegate<MaterialLocalizations>] whose load methods return
394
  /// custom versions of [WidgetsLocalizations] or [MaterialLocalizations].
395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
  ///
  /// 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) {
422
  ///     return SynchronousFuture(FooLocalizations(locale));
423 424 425 426 427 428 429 430 431 432 433 434 435
  ///   }
  ///   @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
436
  /// MaterialApp(
437 438 439 440 441 442
  ///   localizationsDelegates: [
  ///     const FooLocalizationsDelegate(),
  ///   ],
  ///   // ...
  /// )
  /// ```
443 444 445 446 447 448 449
  /// 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,
450
  ///    <https://flutter.dev/tutorials/internationalization/>.
451
  final Iterable<LocalizationsDelegate<dynamic>> localizationsDelegates;
452

453 454 455 456 457
  /// {@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
458
  /// {@macro flutter.widgets.widgetsApp.localeResolutionCallback}
459 460 461 462
  ///
  /// This callback is passed along to the [WidgetsApp] built by this widget.
  final LocaleResolutionCallback localeResolutionCallback;

xster's avatar
xster committed
463
  /// {@macro flutter.widgets.widgetsApp.supportedLocales}
464
  ///
xster's avatar
xster committed
465
  /// It is passed along unmodified to the [WidgetsApp] built by this widget.
466
  ///
467 468 469 470 471 472 473
  /// 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,
474
  ///    <https://flutter.dev/tutorials/internationalization/>.
475 476
  final Iterable<Locale> supportedLocales;

477
  /// Turns on a performance overlay.
478 479 480
  ///
  /// See also:
  ///
481
  ///  * <https://flutter.dev/debugging/#performanceoverlay>
482 483
  final bool showPerformanceOverlay;

484 485 486
  /// Turns on checkerboarding of raster cache images.
  final bool checkerboardRasterCacheImages;

487 488 489
  /// Turns on checkerboarding of layers rendered to offscreen bitmaps.
  final bool checkerboardOffscreenLayers;

490 491 492 493
  /// Turns on an overlay that shows the accessibility information
  /// reported by the framework.
  final bool showSemanticsDebugger;

xster's avatar
xster committed
494
  /// {@macro flutter.widgets.widgetsApp.debugShowCheckedModeBanner}
495 496
  final bool debugShowCheckedModeBanner;

497
  /// {@macro flutter.widgets.widgetsApp.shortcuts}
498
  /// {@tool snippet}
499 500 501 502 503 504 505 506 507 508 509 510 511
  /// 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,
512
  ///       LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
513 514 515 516 517 518 519 520 521 522 523 524 525
  ///     },
  ///     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}
526
  /// {@tool snippet}
527 528 529 530 531 532 533 534 535 536 537
  /// 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(
538
  ///     actions: <Type, Action<Intent>>{
539
  ///       ... WidgetsApp.defaultActions,
540 541
  ///       ActivateAction: CallbackAction(
  ///         onInvoke: (Intent intent) {
542
  ///           // Do something here...
543
  ///           return null;
544 545 546 547 548 549 550 551 552 553 554 555
  ///         },
  ///       ),
  ///     },
  ///     color: const Color(0xFFFF0000),
  ///     builder: (BuildContext context, Widget child) {
  ///       return const Placeholder();
  ///     },
  ///   );
  /// }
  /// ```
  /// {@end-tool}
  /// {@macro flutter.widgets.widgetsApp.actions.seeAlso}
556
  final Map<Type, Action<Intent>> actions;
557

558
  /// Turns on a [GridPaper] overlay that paints a baseline grid
559 560
  /// Material apps.
  ///
561
  /// Only available in checked mode.
562 563 564
  ///
  /// See also:
  ///
565
  ///  * <https://material.io/design/layout/spacing-methods.html>
Ian Hickson's avatar
Ian Hickson committed
566
  final bool debugShowMaterialGrid;
567

568
  @override
569
  _MaterialAppState createState() => _MaterialAppState();
570 571 572 573 574 575 576 577 578 579 580

  /// 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);
      },
    );
  }
581 582
}

Adam Barth's avatar
Adam Barth committed
583
class _MaterialScrollBehavior extends ScrollBehavior {
584 585 586 587 588 589
  @override
  TargetPlatform getPlatform(BuildContext context) {
    return Theme.of(context).platform;
  }

  @override
590 591 592 593 594
  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:
595
      case TargetPlatform.linux:
596
      case TargetPlatform.macOS:
597
      case TargetPlatform.windows:
598 599 600
        return child;
      case TargetPlatform.android:
      case TargetPlatform.fuchsia:
601
        return GlowingOverscrollIndicator(
602 603 604 605 606 607
          child: child,
          axisDirection: axisDirection,
          color: Theme.of(context).accentColor,
        );
    }
    return null;
608 609 610
  }
}

611
class _MaterialAppState extends State<MaterialApp> {
612 613 614 615 616
  HeroController _heroController;

  @override
  void initState() {
    super.initState();
617
    _heroController = MaterialApp.createMaterialHeroController();
618 619
  }

620 621 622 623 624 625 626 627 628
  // 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;
629
    yield DefaultCupertinoLocalizations.delegate;
630 631
  }

632
  @override
633
  Widget build(BuildContext context) {
634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649
    Widget result = HeroControllerScope(
      controller: _heroController,
      child: 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: (BuildContext context, Widget child) {
650
          // Resolve which theme to use based on brightness and high contrast.
651
          final ThemeMode mode = widget.themeMode ?? ThemeMode.system;
652 653 654 655
          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);
656
          ThemeData theme;
657 658 659 660 661 662 663

          if (useDarkTheme && highContrast) {
            theme = widget.highContrastDarkTheme;
          } else if (useDarkTheme) {
            theme = widget.darkTheme;
          } else if (highContrast) {
            theme = widget.highContrastTheme;
664
          }
665
          theme ??= widget.theme ?? ThemeData.light();
666

667 668 669 670
          return AnimatedTheme(
            data: theme,
            isMaterialAppTheme: true,
            child: widget.builder != null
671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687
              ? 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,
688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720
          );
        },
        title: widget.title,
        onGenerateTitle: widget.onGenerateTitle,
        textStyle: _errorTextStyle,
        // 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
        color: widget.color ?? widget.theme?.primaryColor ?? Colors.blue,
        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: (BuildContext context, VoidCallback onPressed) {
          return FloatingActionButton(
            child: const Icon(Icons.search),
            onPressed: onPressed,
            mini: true,
          );
        },
        shortcuts: widget.shortcuts,
        actions: widget.actions,
      ),
721
    );
722

Ian Hickson's avatar
Ian Hickson committed
723
    assert(() {
724
      if (widget.debugShowMaterialGrid) {
725
        result = GridPaper(
Ian Hickson's avatar
Ian Hickson committed
726 727 728
          color: const Color(0xE0F9BBE0),
          interval: 8.0,
          divisions: 2,
729 730
          subdivisions: 1,
          child: result,
Ian Hickson's avatar
Ian Hickson committed
731 732 733
        );
      }
      return true;
734
    }());
735

736 737
    return ScrollConfiguration(
      behavior: _MaterialScrollBehavior(),
738
      child: result,
739
    );
740
  }
741
}