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
import 'dart:ui' as ui;

7
import 'package:flutter/cupertino.dart';
8
import 'package:flutter/rendering.dart';
9
import 'package:flutter/widgets.dart';
10

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

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

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

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

205
  /// {@macro flutter.widgets.widgetsApp.navigatorKey}
206
  final GlobalKey<NavigatorState> navigatorKey;
207

208
  /// {@macro flutter.widgets.widgetsApp.home}
209
  final Widget home;
210

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

xster's avatar
xster committed
221
  /// {@macro flutter.widgets.widgetsApp.initialRoute}
222 223
  final String initialRoute;

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

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

xster's avatar
xster committed
230
  /// {@macro flutter.widgets.widgetsApp.navigatorObservers}
231 232
  final List<NavigatorObserver> navigatorObservers;

xster's avatar
xster committed
233
  /// {@macro flutter.widgets.widgetsApp.builder}
234
  ///
235 236
  /// Material specific features such as [showDialog] and [showMenu], and widgets
  /// such as [Tooltip], [PopupMenuButton], also require a [Navigator] to properly
237 238 239
  /// function.
  final TransitionBuilder builder;

xster's avatar
xster committed
240
  /// {@macro flutter.widgets.widgetsApp.title}
241 242 243 244
  ///
  /// This value is passed unmodified to [WidgetsApp.title].
  final String title;

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

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

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

291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
  /// 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:
  ///
311 312 313 314
  ///  * [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.
315 316
  final ThemeMode themeMode;

xster's avatar
xster committed
317
  /// {@macro flutter.widgets.widgetsApp.color}
318 319
  final Color color;

xster's avatar
xster committed
320
  /// {@macro flutter.widgets.widgetsApp.locale}
321 322
  final Locale locale;

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

416 417 418 419 420
  /// {@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
421
  /// {@macro flutter.widgets.widgetsApp.localeResolutionCallback}
422 423 424 425
  ///
  /// This callback is passed along to the [WidgetsApp] built by this widget.
  final LocaleResolutionCallback localeResolutionCallback;

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

440
  /// Turns on a performance overlay.
441 442 443
  ///
  /// See also:
  ///
444
  ///  * <https://flutter.dev/debugging/#performanceoverlay>
445 446
  final bool showPerformanceOverlay;

447 448 449
  /// Turns on checkerboarding of raster cache images.
  final bool checkerboardRasterCacheImages;

450 451 452
  /// Turns on checkerboarding of layers rendered to offscreen bitmaps.
  final bool checkerboardOffscreenLayers;

453 454 455 456
  /// Turns on an overlay that shows the accessibility information
  /// reported by the framework.
  final bool showSemanticsDebugger;

xster's avatar
xster committed
457
  /// {@macro flutter.widgets.widgetsApp.debugShowCheckedModeBanner}
458 459
  final bool debugShowCheckedModeBanner;

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

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

531
  @override
532
  _MaterialAppState createState() => _MaterialAppState();
533 534
}

Adam Barth's avatar
Adam Barth committed
535
class _MaterialScrollBehavior extends ScrollBehavior {
536 537 538 539 540 541
  @override
  TargetPlatform getPlatform(BuildContext context) {
    return Theme.of(context).platform;
  }

  @override
542 543 544 545 546
  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:
547
      case TargetPlatform.macOS:
548 549 550
        return child;
      case TargetPlatform.android:
      case TargetPlatform.fuchsia:
551
        return GlowingOverscrollIndicator(
552 553 554 555 556 557
          child: child,
          axisDirection: axisDirection,
          color: Theme.of(context).accentColor,
        );
    }
    return null;
558 559 560
  }
}

561
class _MaterialAppState extends State<MaterialApp> {
562 563 564 565 566
  HeroController _heroController;

  @override
  void initState() {
    super.initState();
567
    _heroController = HeroController(createRectTween: _createRectTween);
568
    _updateNavigator();
569 570
  }

571 572 573 574 575 576 577 578
  @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).
579
      _heroController = HeroController(createRectTween: _createRectTween);
580
    }
581
    _updateNavigator();
582 583
  }

584 585 586
  List<NavigatorObserver> _navigatorObservers;

  void _updateNavigator() {
587 588 589 590 591 592
    if (widget.home != null ||
        widget.routes.isNotEmpty ||
        widget.onGenerateRoute != null ||
        widget.onUnknownRoute != null) {
      _navigatorObservers = List<NavigatorObserver>.from(widget.navigatorObservers)
        ..add(_heroController);
593
    } else {
594
      _navigatorObservers = const <NavigatorObserver>[];
595 596 597
    }
  }

598 599
  RectTween _createRectTween(Rect begin, Rect end) {
    return MaterialRectArcTween(begin: begin, end: end);
600 601
  }

602 603 604 605 606 607 608 609 610
  // 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;
611
    yield DefaultCupertinoLocalizations.delegate;
612 613
  }

614
  @override
615
  Widget build(BuildContext context) {
616 617 618 619
    Widget result = WidgetsApp(
      key: GlobalObjectKey(this),
      navigatorKey: widget.navigatorKey,
      navigatorObservers: _navigatorObservers,
620 621 622
      pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) {
        return MaterialPageRoute<T>(settings: settings, builder: builder);
      },
623 624 625 626 627 628 629
      home: widget.home,
      routes: widget.routes,
      initialRoute: widget.initialRoute,
      onGenerateRoute: widget.onGenerateRoute,
      onUnknownRoute: widget.onUnknownRoute,
      builder: (BuildContext context, Widget child) {
        // Use a light theme, dark theme, or fallback theme.
630
        final ThemeMode mode = widget.themeMode ?? ThemeMode.system;
631
        ThemeData theme;
632 633 634 635 636 637
        if (widget.darkTheme != null) {
          final ui.Brightness platformBrightness = MediaQuery.platformBrightnessOf(context);
          if (mode == ThemeMode.dark ||
              (mode == ThemeMode.system && platformBrightness == ui.Brightness.dark)) {
            theme = widget.darkTheme;
          }
638
        }
639
        theme ??= widget.theme ?? ThemeData.fallback();
640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 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 690 691

        return AnimatedTheme(
          data: theme,
          isMaterialAppTheme: true,
          child: widget.builder != null
              ? Builder(
                  builder: (BuildContext context) {
                    // Why are we surrounding a builder with a builder?
                    //
                    // The widget.builder may contain code that invokes
                    // Theme.of(), which should return the theme we selected
                    // above in AnimatedTheme. However, if we invoke
                    // widget.builder() directly as the child of AnimatedTheme
                    // then there is no Context separating them, and the
                    // widget.builder() will not find the theme. Therefore, we
                    // surround widget.builder with yet another builder so that
                    // a context separates them and Theme.of() correctly
                    // resolves to the theme we passed to AnimatedTheme.
                    return widget.builder(context, child);
                  },
                )
              : child,
        );
      },
      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,
        );
      },
692 693
      shortcuts: widget.shortcuts,
      actions: widget.actions,
694
    );
695

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

709 710
    return ScrollConfiguration(
      behavior: _MaterialScrollBehavior(),
711
      child: result,
712
    );
713
  }
714
}