app.dart 29 KB
Newer Older
1 2 3 4
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'package:flutter/rendering.dart';
6
import 'package:flutter/foundation.dart';
7
import 'package:flutter/widgets.dart';
8

9
import 'arc.dart';
10
import 'colors.dart';
11 12
import 'floating_action_button.dart';
import 'icons.dart';
13
import 'material_localizations.dart';
14
import 'page.dart';
15
import 'theme.dart';
16

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

35 36 37
/// An application that uses material design.
///
/// A convenience widget that wraps a number of widgets that are commonly
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
/// 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.
///
54 55 56 57
/// 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]).
58
///
59 60 61 62 63
/// 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.
64 65 66
///
/// See also:
///
67 68 69 70
///  * [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.
71
class MaterialApp extends StatefulWidget {
72 73
  /// Creates a MaterialApp.
  ///
74 75
  /// 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
76 77 78
  /// [Navigator.defaultRouteName] (`/`), since that is the route used when the
  /// application is launched with an intent that specifies an otherwise
  /// unsupported route.
79
  ///
80
  /// This class creates an instance of [WidgetsApp].
81 82 83
  ///
  /// The boolean arguments, [routes], and [navigatorObservers], must not be null.
  MaterialApp({ // can't be const because the asserts use methods on Map :-(
84
    Key key,
85
    this.navigatorKey,
86 87
    this.home,
    this.routes: const <String, WidgetBuilder>{},
88
    this.initialRoute,
89
    this.onGenerateRoute,
90
    this.onUnknownRoute,
91 92 93 94 95 96
    this.navigatorObservers: const <NavigatorObserver>[],
    this.builder,
    this.title: '',
    this.onGenerateTitle,
    this.color,
    this.theme,
97 98
    this.locale,
    this.localizationsDelegates,
99 100
    this.localeResolutionCallback,
    this.supportedLocales: const <Locale>[const Locale('en', 'US')],
101
    this.debugShowMaterialGrid: false,
102
    this.showPerformanceOverlay: false,
103
    this.checkerboardRasterCacheImages: false,
104
    this.checkerboardOffscreenLayers: false,
105
    this.showSemanticsDebugger: false,
106 107
    this.debugShowCheckedModeBanner: true,
  }) : assert(routes != null),
108 109 110 111 112 113 114 115
       assert(navigatorObservers != null),
       assert(
         home == null ||
         !routes.containsKey(Navigator.defaultRouteName),
         'If the home property is specified, the routes table '
         'cannot include an entry for "/", since it would be redundant.'
       ),
       assert(
116
         builder != null ||
117 118 119 120 121 122 123 124
         home != null ||
         routes.containsKey(Navigator.defaultRouteName) ||
         onGenerateRoute != null ||
         onUnknownRoute != null,
         'Either the home property must be specified, '
         'or the routes table must include an entry for "/", '
         'or there must be on onGenerateRoute callback specified, '
         'or there must be an onUnknownRoute callback specified, '
125
         'or the builder property must be specified, '
126 127 128
         'because otherwise there is nothing to fall back on if the '
         'app is started with an intent that specifies an unknown route.'
       ),
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
       assert(
         (home != null ||
          routes.isNotEmpty ||
          onGenerateRoute != null ||
          onUnknownRoute != null)
         ||
         (builder != null &&
          navigatorKey == null &&
          initialRoute == null &&
          navigatorObservers.isEmpty),
         'If no route is provided using '
         'home, routes, onGenerateRoute, or onUnknownRoute, '
         'a non-null callback for the builder property must be provided, '
         'and the other navigator-related properties, '
         'navigatorKey, initialRoute, and navigatorObservers, '
         'must have their initial values '
         '(null, null, and the empty list, respectively).'
       ),
       assert(title != null),
       assert(debugShowMaterialGrid != null),
       assert(showPerformanceOverlay != null),
       assert(checkerboardRasterCacheImages != null),
       assert(checkerboardOffscreenLayers != null),
       assert(showSemanticsDebugger != null),
       assert(debugShowCheckedModeBanner != null),
154
       super(key: key);
155

156 157 158 159 160 161 162 163 164 165 166
  /// A key to use when building the [Navigator].
  ///
  /// If a [navigatorKey] is specified, the [Navigator] can be directly
  /// manipulated without first obtaining it from a [BuildContext] via
  /// [Navigator.of]: from the [navigatorKey], use the [GlobalKey.currentState]
  /// getter.
  ///
  /// If this is changed, a new [Navigator] will be created, losing all the
  /// application state in the process; in that case, the [navigatorObservers]
  /// must also be changed, since the previous observers will be attached to the
  /// previous navigator.
167
  ///
168 169 170 171
  /// The [Navigator] is only built if routes are provided (either via [home],
  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
  /// [navigatorKey] must be null and [builder] must not be null.
  final GlobalKey<NavigatorState> navigatorKey;
172

173 174
  /// The widget for the default route of the app ([Navigator.defaultRouteName],
  /// which is `/`).
175
  ///
176 177 178
  /// This is the route that is displayed first when the application is started
  /// normally, unless [initialRoute] is specified. It's also the route that's
  /// displayed if the [initialRoute] can't be displayed.
179
  ///
180 181 182
  /// To be able to directly call [Theme.of], [MediaQuery.of], etc, in the code
  /// that sets the [home] argument in the constructor, you can use a [Builder]
  /// widget to get a [BuildContext].
183
  ///
184 185
  /// If [home] is specified, then [routes] must not include an entry for `/`,
  /// as [home] takes its place.
186
  ///
187 188 189 190 191 192 193 194 195 196 197 198
  /// The [Navigator] is only built if routes are provided (either via [home],
  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
  /// [builder] must not be null.
  ///
  /// The difference between using [home] and using [builder] is that the [home]
  /// subtree is inserted into the application below a [Navigator] (and thus
  /// below an [Overlay], which [Navigator] uses). With [home], therefore,
  /// dialog boxes will work automatically, [Tooltip]s will work, the [routes]
  /// table will be used, and APIs such as [Navigator.push] and [Navigator.pop]
  /// will work as expected. In contrast, the widget returned from [builder] is
  /// inserted _above_ the [MaterialApp]'s [Navigator] (if any).
  final Widget home;
199

200 201 202 203 204 205
  /// 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.
206 207 208
  ///
  /// If the app only has one page, then you can specify it using [home] instead.
  ///
209 210 211
  /// If [home] is specified, then it implies an entry in this table for the
  /// [Navigator.defaultRouteName] route (`/`), and it is an error to
  /// redundantly provide such a route in the [routes] table.
212
  ///
213 214 215
  /// If a route is requested that is not specified in this table (or by
  /// [home]), then the [onGenerateRoute] callback is called to build the page
  /// instead.
216 217 218 219
  ///
  /// The [Navigator] is only built if routes are provided (either via [home],
  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
  /// [builder] must not be null.
Ian Hickson's avatar
Ian Hickson committed
220 221
  final Map<String, WidgetBuilder> routes;

222
  /// The name of the first route to show, if a [Navigator] is built.
223
  ///
224 225 226 227 228 229 230 231 232 233 234 235 236
  /// Defaults to [Window.defaultRouteName], which may be overridden by the code
  /// that launched the application.
  ///
  /// If the route contains slashes, then it is treated as a "deep link", and
  /// before this route is pushed, the routes leading to this one are pushed
  /// also. For example, if the route was `/a/b/c`, then the app would start
  /// with the three routes `/a`, `/a/b`, and `/a/b/c` loaded, in that order.
  ///
  /// If any part of this process fails to generate routes, then the
  /// [initialRoute] is ignored and [Navigator.defaultRouteName] is used instead
  /// (`/`). This can happen if the app is started with an intent that specifies
  /// a non-existent route.
  ///
237 238 239 240
  /// The [Navigator] is only built if routes are provided (either via [home],
  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
  /// [initialRoute] must be null and [builder] must not be null.
  ///
241 242 243 244 245
  /// See also:
  ///
  ///  * [Navigator.initialRoute], which is used to implement this property.
  ///  * [Navigator.push], for pushing additional routes.
  ///  * [Navigator.pop], for removing a route from the stack.
246 247
  final String initialRoute;

248 249
  /// The route generator callback used when the app is navigated to a
  /// named route.
250 251 252 253 254 255 256 257 258 259
  ///
  /// This is used if [routes] does not contain the requested route.
  ///
  /// If this returns null when building the routes to handle the specified
  /// [initialRoute], then all the routes are discarded and
  /// [Navigator.defaultRouteName] is used instead (`/`). See [initialRoute].
  ///
  /// During normal app operation, the [onGenerateRoute] callback will only be
  /// applied to route names pushed by the application, and so should never
  /// return null.
260 261 262 263
  ///
  /// The [Navigator] is only built if routes are provided (either via [home],
  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
  /// [builder] must not be null.
264 265
  final RouteFactory onGenerateRoute;

266 267 268 269 270 271 272 273 274
  /// Called when [onGenerateRoute] fails to generate a route, except for the
  /// [initialRoute].
  ///
  /// This callback is typically used for error handling. For example, this
  /// callback might always generate a "not found" page that describes the route
  /// that wasn't found.
  ///
  /// The default implementation pushes a route that displays an ugly error
  /// message.
275 276 277 278
  ///
  /// The [Navigator] is only built if routes are provided (either via [home],
  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
  /// [builder] must not be null.
279 280
  final RouteFactory onUnknownRoute;

281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 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 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370
  /// The list of observers for the [Navigator] created for this app.
  ///
  /// This list must be replaced by a list of newly-created observers if the
  /// [navigatorKey] is changed.
  ///
  /// The [Navigator] is only built if routes are provided (either via [home],
  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
  /// [navigatorObservers] must be the empty list and [builder] must not be null.
  final List<NavigatorObserver> navigatorObservers;

  /// A builder for inserting widgets above the [Navigator] but below the other
  /// widgets created by the [MaterialApp] widget, or for replacing the
  /// [Navigator] entirely.
  ///
  /// For example, from the [BuildContext] passed to this method, the
  /// [Directionality], [Localizations], [DefaultTextStyle], [MediaQuery], etc,
  /// are all available. They can also be overridden in a way that impacts all
  /// the routes in the [Navigator].
  ///
  /// This is rarely useful, but can be used in applications that wish to
  /// override those defaults, e.g. to force the application into right-to-left
  /// mode despite being in English, or to override the [MediaQuery] metrics
  /// (e.g. to leave a gap for advertisements shown by a plugin from OEM code).
  ///
  /// The [builder] callback is passed two arguments, the [BuildContext] (as
  /// `context`) and a [Navigator] widget (as `child`).
  ///
  /// If no routes are provided using [home], [routes], [onGenerateRoute], or
  /// [onUnknownRoute], the `child` will be null, and it is the responsibility
  /// of the [builder] to provide the application's routing machinery.
  ///
  /// If routes _are_ provided using one or more of those properties, then
  /// `child` is not null, and the returned value should include the `child` in
  /// the widget subtree; if it does not, then the application will have no
  /// navigator and the [navigatorKey], [home], [routes], [onGenerateRoute],
  /// [onUnknownRoute], [initialRoute], and [navigatorObservers] properties will
  /// have no effect.
  ///
  /// If [builder] is null, it is as if a builder was specified that returned
  /// the `child` directly. If it is null, routes must be provided using one of
  /// the other properties listed above.
  ///
  /// Unless a [Navigator] is provided, either implicitly from [builder] being
  /// null, or by a [builder] including its `child` argument, or by a [builder]
  /// explicitly providing a [Navigator] of its own, features such as
  /// [showDialog] and [showMenu], widgets such as [Tooltip], [PopupMenuButton],
  /// or [Hero], and APIs such as [Navigator.push] and [Navigator.pop], will not
  /// function.
  ///
  /// For specifically overriding the [title] with a value based on the
  /// [Localizations], consider [onGenerateTitle] instead.
  final TransitionBuilder builder;

  /// A one-line description used by the device to identify the app for the user.
  ///
  /// On Android the titles appear above the task manager's app snapshots which are
  /// displayed when the user presses the "recent apps" button. Similarly, on
  /// iOS the titles appear in the App Switcher when the user double presses the
  /// home button.
  ///
  /// To provide a localized title instead, use [onGenerateTitle].
  ///
  /// This value is passed unmodified to [WidgetsApp.title].
  final String title;

  /// If non-null this callback is called to produce the app's
  /// title string, otherwise [title] is used.
  ///
  /// The [onGenerateTitle] `context` parameter includes the [WidgetsApp]'s
  /// [Localizations] widget so that this callback can be used to produce a
  /// localized title.
  ///
  /// This callback function must not return null.
  ///
  /// The [onGenerateTitle] callback is called each time the [MaterialApp]
  /// rebuilds.
  ///
  /// This value is passed unmodified to [WidgetsApp.onGenerateTitle].
  final GenerateAppTitle onGenerateTitle;

  /// The colors to use for the application's widgets.
  final ThemeData theme;

  /// The primary color to use for the application in the operating system
  /// interface.
  ///
  /// For example, on Android this is the color used for the application in the
  /// application switcher.
  final Color color;

371 372 373 374 375 376 377 378 379
  /// The initial locale for this app's [Localizations] widget.
  ///
  /// If the `locale` is null the system's locale value is used.
  final Locale locale;

  /// The delegates for this app's [Localizations] widget.
  ///
  /// The delegates collectively define all of the localized resources
  /// for this application's [Localizations] widget.
380 381 382 383
  ///
  /// Delegates that produce [WidgetsLocalizations] and [MaterialLocalizations]
  /// are included automatically. Apps can provide their own versions of these
  /// localizations by creating implementations of
384
  /// [LocalizationsDelegate<WidgetsLocalizations>] or
385
  /// [LocalizationsDelegate<MaterialLocalizations>] whose load methods return
386
  /// custom versions of [WidgetsLocalizations] or [MaterialLocalizations].
387 388 389 390 391 392 393 394 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 422 423 424 425 426 427 428 429 430 431 432 433 434
  ///
  /// 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) {
  ///     return new SynchronousFuture(new FooLocalizations(locale));
  ///   }
  ///   @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
  /// new MaterialApp(
  ///   localizationsDelegates: [
  ///     const FooLocalizationsDelegate(),
  ///   ],
  ///   // ...
  /// )
  /// ```
435
  final Iterable<LocalizationsDelegate<dynamic>> localizationsDelegates;
436

437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465
  /// This callback is responsible for choosing the app's locale
  /// when the app is started, and when the user changes the
  /// device's locale.
  ///
  /// The returned value becomes the locale of this app's [Localizations]
  /// widget. The callback's `locale` parameter is the device's locale when
  /// the app started, or the device locale the user selected after the app was
  /// started. The callback's `supportedLocales` parameter is just the value
  /// [supportedLocales].
  ///
  /// An app could use this callback to substitute locales based on the app's
  /// intended audience. If the device's OS provides a prioritized
  /// list of locales, this callback could be used to defer to it.
  ///
  /// If the callback is null then the resolved locale is:
  /// - The callback's `locale` parameter if it's equal to a supported locale.
  /// - The first supported locale with the same [Locale.languageCode] as the
  ///   callback's `locale` parameter.
  /// - The first supported locale.
  ///
  /// This callback is passed along to the [WidgetsApp] built by this widget.
  final LocaleResolutionCallback localeResolutionCallback;

  /// The list of locales that this app has been localized for.
  ///
  /// By default only the American English locale is supported. Apps should
  /// configure this list to match the locales they support.
  ///
  /// This list must not null. It's default value is just
466
  /// `[const Locale('en', 'US')]`. It is passed along unmodified to the
467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
  /// [WidgetsApp] built by this widget.
  ///
  /// The order of the list matters. By default, if the device's locale doesn't
  /// exactly match a locale in [supportedLocales] then the first locale in
  /// [supportedLocales] with a matching [Locale.languageCode] is used. If that
  /// fails then the first locale in [supportedLocales] is used. The default
  /// locale resolution algorithm can be overridden with [localeResolutionCallback].
  ///
  /// The material widgets include translations for locales with the following
  /// language codes:
  /// ```
  /// ar - Arabic
  /// de - German
  /// en - English
  /// es - Spanish
  /// fa - Farsi (Persian)
  /// fr - French
  /// he - Hebrew
  /// it - Italian
  /// ja - Japanese
  /// ps - Pashto
  /// pt - Portugese
489
  /// ro - Romanian
490 491 492 493 494 495 496
  /// ru - Russian
  /// sd - Sindhi
  /// ur - Urdu
  /// zh - Chinese (simplified)
  /// ```
  final Iterable<Locale> supportedLocales;

497
  /// Turns on a performance overlay.
498 499 500 501
  ///
  /// See also:
  ///
  ///  * <https://flutter.io/debugging/#performanceoverlay>
502 503
  final bool showPerformanceOverlay;

504 505 506
  /// Turns on checkerboarding of raster cache images.
  final bool checkerboardRasterCacheImages;

507 508 509
  /// Turns on checkerboarding of layers rendered to offscreen bitmaps.
  final bool checkerboardOffscreenLayers;

510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528
  /// Turns on an overlay that shows the accessibility information
  /// reported by the framework.
  final bool showSemanticsDebugger;

  /// Turns on a little "SLOW MODE" banner in checked mode to indicate
  /// that the app is in checked mode. This is on by default (in
  /// checked mode), to turn it off, set the constructor argument to
  /// false. In release mode this has no effect.
  ///
  /// To get this banner in your application if you're not using
  /// WidgetsApp, include a [CheckedModeBanner] widget in your app.
  ///
  /// This banner is intended to deter people from complaining that your
  /// app is slow when it's in checked mode. In checked mode, Flutter
  /// enables a large number of expensive diagnostics to aid in
  /// development, and so performance in checked mode is not
  /// representative of what will happen in release mode.
  final bool debugShowCheckedModeBanner;

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

539
  @override
Adam Barth's avatar
Adam Barth committed
540
  _MaterialAppState createState() => new _MaterialAppState();
541 542
}

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

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

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

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

578 579 580 581 582 583 584 585 586 587
  @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).
      _heroController = new HeroController(createRectTween: _createRectTween);
    }
588
    _updateNavigator();
589 590
  }

591 592 593 594 595 596 597 598 599 600
  bool _haveNavigator;
  List<NavigatorObserver> _navigatorObservers;

  void _updateNavigator() {
    _haveNavigator = widget.home != null ||
                     widget.routes.isNotEmpty ||
                     widget.onGenerateRoute != null ||
                     widget.onUnknownRoute != null;
    _navigatorObservers = new List<NavigatorObserver>.from(widget.navigatorObservers)
      ..add(_heroController);
601 602
  }

603 604 605
  RectTween _createRectTween(Rect begin, Rect end) {
    return new MaterialRectArcTween(begin: begin, end: end);
  }
606

607
  Route<dynamic> _onGenerateRoute(RouteSettings settings) {
608 609 610
    final String name = settings.name;
    WidgetBuilder builder;
    if (name == Navigator.defaultRouteName && widget.home != null)
611
      builder = (BuildContext context) => widget.home;
612 613
    else
      builder = widget.routes[name];
614
    if (builder != null) {
615
      return new MaterialPageRoute<dynamic>(
616
        builder: builder,
617
        settings: settings,
618 619
      );
    }
620 621
    if (widget.onGenerateRoute != null)
      return widget.onGenerateRoute(settings);
622 623
    return null;
  }
624

625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
  Route<dynamic> _onUnknownRoute(RouteSettings settings) {
    assert(() {
      if (widget.onUnknownRoute == null) {
        throw new FlutterError(
          'Could not find a generator for route $settings in the $runtimeType.\n'
          'Generators for routes are searched for in the following order:\n'
          ' 1. For the "/" route, the "home" property, if non-null, is used.\n'
          ' 2. Otherwise, the "routes" table is used, if it has an entry for '
          'the route.\n'
          ' 3. Otherwise, onGenerateRoute is called. It should return a '
          'non-null value for any valid route not handled by "home" and "routes".\n'
          ' 4. Finally if all else fails onUnknownRoute is called.\n'
          'Unfortunately, onUnknownRoute was not set.'
        );
      }
      return true;
641
    }());
642 643 644 645 646 647 648 649 650 651 652
    final Route<dynamic> result = widget.onUnknownRoute(settings);
    assert(() {
      if (result == null) {
        throw new FlutterError(
          'The onUnknownRoute callback returned null.\n'
          'When the $runtimeType requested the route $settings from its '
          'onUnknownRoute callback, the callback returned null. Such callbacks '
          'must never return null.'
        );
      }
      return true;
653
    }());
654 655 656
    return result;
  }

657 658 659 660 661 662 663 664 665 666 667
  // 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;
  }

668
  @override
669
  Widget build(BuildContext context) {
670
    final ThemeData theme = widget.theme ?? new ThemeData.fallback();
671 672
    Widget result = new AnimatedTheme(
      data: theme,
673
      isMaterialAppTheme: true,
674
      child: new WidgetsApp(
675
        key: new GlobalObjectKey(this),
676
        navigatorKey: widget.navigatorKey,
677 678 679 680 681
        navigatorObservers: _haveNavigator ? _navigatorObservers : null,
        initialRoute: widget.initialRoute,
        onGenerateRoute: _haveNavigator ? _onGenerateRoute : null,
        onUnknownRoute: _haveNavigator ? _onUnknownRoute : null,
        builder: widget.builder,
682
        title: widget.title,
683
        onGenerateTitle: widget.onGenerateTitle,
684
        textStyle: _errorTextStyle,
685
        // blue is the primary color of the default theme
686
        color: widget.color ?? theme?.primaryColor ?? Colors.blue,
687
        locale: widget.locale,
688
        localizationsDelegates: _localizationsDelegates,
689 690
        localeResolutionCallback: widget.localeResolutionCallback,
        supportedLocales: widget.supportedLocales,
691 692
        showPerformanceOverlay: widget.showPerformanceOverlay,
        checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
693
        checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
694
        showSemanticsDebugger: widget.showSemanticsDebugger,
695
        debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
696 697 698 699 700 701 702
        inspectorSelectButtonBuilder: (BuildContext context, VoidCallback onPressed) {
          return new FloatingActionButton(
            child: const Icon(Icons.search),
            onPressed: onPressed,
            mini: true,
          );
        },
703
      )
704
    );
705

Ian Hickson's avatar
Ian Hickson committed
706
    assert(() {
707
      if (widget.debugShowMaterialGrid) {
Ian Hickson's avatar
Ian Hickson committed
708 709 710 711
        result = new GridPaper(
          color: const Color(0xE0F9BBE0),
          interval: 8.0,
          divisions: 2,
712 713
          subdivisions: 1,
          child: result,
Ian Hickson's avatar
Ian Hickson committed
714 715 716
        );
      }
      return true;
717
    }());
718

Adam Barth's avatar
Adam Barth committed
719
    return new ScrollConfiguration(
720
      behavior: new _MaterialScrollBehavior(),
721
      child: result,
722
    );
723
  }
724
}