app.dart 25.9 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
    this.themeMode = ThemeMode.system,
184 185
    this.locale,
    this.localizationsDelegates,
186
    this.localeListResolutionCallback,
187
    this.localeResolutionCallback,
188
    this.supportedLocales = const <Locale>[Locale('en', 'US')],
189 190 191 192 193 194
    this.debugShowMaterialGrid = false,
    this.showPerformanceOverlay = false,
    this.checkerboardRasterCacheImages = false,
    this.checkerboardOffscreenLayers = false,
    this.showSemanticsDebugger = false,
    this.debugShowCheckedModeBanner = true,
195 196
    this.shortcuts,
    this.actions,
197
  }) : assert(routes != null),
198
       assert(navigatorObservers != null),
199 200 201 202 203 204 205
       assert(title != null),
       assert(debugShowMaterialGrid != null),
       assert(showPerformanceOverlay != null),
       assert(checkerboardRasterCacheImages != null),
       assert(checkerboardOffscreenLayers != null),
       assert(showSemanticsDebugger != null),
       assert(debugShowCheckedModeBanner != null),
206
       super(key: key);
207

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

211
  /// {@macro flutter.widgets.widgetsApp.home}
212
  final Widget home;
213

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

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

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

230 231 232
  /// {@macro flutter.widgets.widgetsApp.onGenerateInitialRoutes}
  final InitialRouteListFactory onGenerateInitialRoutes;

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

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

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

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

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

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

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

297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
  /// 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],
  /// [darkTheme] will be used (unless it is [null], in which case [theme]
  /// 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
  /// regardless of the user's system preference. If [darkTheme] is [null]
  /// then it will fallback to using [theme].
  ///
  /// The default value is [ThemeMode.system].
  ///
  /// See also:
  ///
317 318 319 320
  ///  * [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.
321 322
  final ThemeMode themeMode;

xster's avatar
xster committed
323
  /// {@macro flutter.widgets.widgetsApp.color}
324 325
  final Color color;

xster's avatar
xster committed
326
  /// {@macro flutter.widgets.widgetsApp.locale}
327 328
  final Locale locale;

xster's avatar
xster committed
329
  /// {@macro flutter.widgets.widgetsApp.localizationsDelegates}
330
  ///
331
  /// Internationalized apps that require translations for one of the locales
332
  /// listed in [GlobalMaterialLocalizations] should specify this parameter
333 334 335 336 337
  /// and list the [supportedLocales] that the application can handle.
  ///
  /// ```dart
  /// import 'package:flutter_localizations/flutter_localizations.dart';
  /// MaterialApp(
338 339 340 341 342 343
  ///   localizationsDelegates: [
  ///     // ... app-specific localization delegate[s] here
  ///     GlobalMaterialLocalizations.delegate,
  ///     GlobalWidgetsLocalizations.delegate,
  ///   ],
  ///   supportedLocales: [
344 345 346 347 348 349 350 351 352 353 354 355 356 357
  ///     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].
  ///
358 359 360
  /// Delegates that produce [WidgetsLocalizations] and [MaterialLocalizations]
  /// are included automatically. Apps can provide their own versions of these
  /// localizations by creating implementations of
361
  /// [LocalizationsDelegate<WidgetsLocalizations>] or
362
  /// [LocalizationsDelegate<MaterialLocalizations>] whose load methods return
363
  /// custom versions of [WidgetsLocalizations] or [MaterialLocalizations].
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
  ///
  /// 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) {
391
  ///     return SynchronousFuture(FooLocalizations(locale));
392 393 394 395 396 397 398 399 400 401 402 403 404
  ///   }
  ///   @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
405
  /// MaterialApp(
406 407 408 409 410 411
  ///   localizationsDelegates: [
  ///     const FooLocalizationsDelegate(),
  ///   ],
  ///   // ...
  /// )
  /// ```
412 413 414 415 416 417 418
  /// 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,
419
  ///    <https://flutter.dev/tutorials/internationalization/>.
420
  final Iterable<LocalizationsDelegate<dynamic>> localizationsDelegates;
421

422 423 424 425 426
  /// {@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
427
  /// {@macro flutter.widgets.widgetsApp.localeResolutionCallback}
428 429 430 431
  ///
  /// This callback is passed along to the [WidgetsApp] built by this widget.
  final LocaleResolutionCallback localeResolutionCallback;

xster's avatar
xster committed
432
  /// {@macro flutter.widgets.widgetsApp.supportedLocales}
433
  ///
xster's avatar
xster committed
434
  /// It is passed along unmodified to the [WidgetsApp] built by this widget.
435
  ///
436 437 438 439 440 441 442
  /// 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,
443
  ///    <https://flutter.dev/tutorials/internationalization/>.
444 445
  final Iterable<Locale> supportedLocales;

446
  /// Turns on a performance overlay.
447 448 449
  ///
  /// See also:
  ///
450
  ///  * <https://flutter.dev/debugging/#performanceoverlay>
451 452
  final bool showPerformanceOverlay;

453 454 455
  /// Turns on checkerboarding of raster cache images.
  final bool checkerboardRasterCacheImages;

456 457 458
  /// Turns on checkerboarding of layers rendered to offscreen bitmaps.
  final bool checkerboardOffscreenLayers;

459 460 461 462
  /// Turns on an overlay that shows the accessibility information
  /// reported by the framework.
  final bool showSemanticsDebugger;

xster's avatar
xster committed
463
  /// {@macro flutter.widgets.widgetsApp.debugShowCheckedModeBanner}
464 465
  final bool debugShowCheckedModeBanner;

466
  /// {@macro flutter.widgets.widgetsApp.shortcuts}
467
  /// {@tool snippet}
468 469 470 471 472 473 474 475 476 477 478 479 480
  /// 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,
481
  ///       LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
482 483 484 485 486 487 488 489 490 491 492 493 494
  ///     },
  ///     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}
495
  /// {@tool snippet}
496 497 498 499 500 501 502 503 504 505 506
  /// 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(
507
  ///     actions: <Type, Action<Intent>>{
508
  ///       ... WidgetsApp.defaultActions,
509 510
  ///       ActivateAction: CallbackAction(
  ///         onInvoke: (Intent intent) {
511
  ///           // Do something here...
512
  ///           return null;
513 514 515 516 517 518 519 520 521 522 523 524
  ///         },
  ///       ),
  ///     },
  ///     color: const Color(0xFFFF0000),
  ///     builder: (BuildContext context, Widget child) {
  ///       return const Placeholder();
  ///     },
  ///   );
  /// }
  /// ```
  /// {@end-tool}
  /// {@macro flutter.widgets.widgetsApp.actions.seeAlso}
525
  final Map<Type, Action<Intent>> actions;
526

527
  /// Turns on a [GridPaper] overlay that paints a baseline grid
528 529
  /// Material apps.
  ///
530
  /// Only available in checked mode.
531 532 533
  ///
  /// See also:
  ///
534
  ///  * <https://material.io/design/layout/spacing-methods.html>
Ian Hickson's avatar
Ian Hickson committed
535
  final bool debugShowMaterialGrid;
536

537
  @override
538
  _MaterialAppState createState() => _MaterialAppState();
539 540
}

Adam Barth's avatar
Adam Barth committed
541
class _MaterialScrollBehavior extends ScrollBehavior {
542 543 544 545 546 547
  @override
  TargetPlatform getPlatform(BuildContext context) {
    return Theme.of(context).platform;
  }

  @override
548 549 550 551 552
  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:
553
      case TargetPlatform.linux:
554
      case TargetPlatform.macOS:
555
      case TargetPlatform.windows:
556 557 558
        return child;
      case TargetPlatform.android:
      case TargetPlatform.fuchsia:
559
        return GlowingOverscrollIndicator(
560 561 562 563 564 565
          child: child,
          axisDirection: axisDirection,
          color: Theme.of(context).accentColor,
        );
    }
    return null;
566 567 568
  }
}

569
class _MaterialAppState extends State<MaterialApp> {
570 571 572 573 574
  HeroController _heroController;

  @override
  void initState() {
    super.initState();
575
    _heroController = HeroController(createRectTween: _createRectTween);
576 577
  }

578 579 580 581 582 583 584 585
  @override
  void didUpdateWidget(MaterialApp oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.navigatorKey != oldWidget.navigatorKey) {
      // If the Navigator changes, we have to create a new observer, because the
      // old Navigator won't be disposed (and thus won't unregister with its
      // observers) until after the new one has been created (because the
      // Navigator has a GlobalKey).
586
      _heroController = HeroController(createRectTween: _createRectTween);
587
    }
588 589
  }

590 591
  RectTween _createRectTween(Rect begin, Rect end) {
    return MaterialRectArcTween(begin: begin, end: end);
592 593
  }

594 595 596 597 598 599 600 601 602
  // 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;
603
    yield DefaultCupertinoLocalizations.delegate;
604 605
  }

606
  @override
607
  Widget build(BuildContext context) {
608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629
    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) {
          // Use a light theme, dark theme, or fallback theme.
          final ThemeMode mode = widget.themeMode ?? ThemeMode.system;
          ThemeData theme;
          if (widget.darkTheme != null) {
            final ui.Brightness platformBrightness = MediaQuery.platformBrightnessOf(context);
            if (mode == ThemeMode.dark ||
630
              (mode == ThemeMode.system && platformBrightness == ui.Brightness.dark)) {
631 632
              theme = widget.darkTheme;
            }
633
          }
634
          theme ??= widget.theme ?? ThemeData.fallback();
635

636 637 638 639
          return AnimatedTheme(
            data: theme,
            isMaterialAppTheme: true,
            child: widget.builder != null
640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656
              ? 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,
657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689
          );
        },
        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,
      ),
690
    );
691

Ian Hickson's avatar
Ian Hickson committed
692
    assert(() {
693
      if (widget.debugShowMaterialGrid) {
694
        result = GridPaper(
Ian Hickson's avatar
Ian Hickson committed
695 696 697
          color: const Color(0xE0F9BBE0),
          interval: 8.0,
          divisions: 2,
698 699
          subdivisions: 1,
          child: result,
Ian Hickson's avatar
Ian Hickson committed
700 701 702
        );
      }
      return true;
703
    }());
704

705 706
    return ScrollConfiguration(
      behavior: _MaterialScrollBehavior(),
707
      child: result,
708
    );
709
  }
710
}