app.dart 23.3 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/widgets.dart';
7

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

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

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

155 156 157 158 159 160 161 162 163 164 165
  /// 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.
166
  ///
167 168 169 170
  /// 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;
171

172 173
  /// The widget for the default route of the app ([Navigator.defaultRouteName],
  /// which is `/`).
174
  ///
175 176 177
  /// 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.
178
  ///
179 180 181
  /// 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].
182
  ///
183 184
  /// If [home] is specified, then [routes] must not include an entry for `/`,
  /// as [home] takes its place.
185
  ///
186 187 188 189 190 191 192 193 194 195 196 197
  /// 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;
198

199 200 201 202 203 204
  /// 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.
205 206 207
  ///
  /// If the app only has one page, then you can specify it using [home] instead.
  ///
208 209 210
  /// 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.
211
  ///
212 213 214
  /// 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.
215 216 217 218
  ///
  /// 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
219 220
  final Map<String, WidgetBuilder> routes;

xster's avatar
xster committed
221
  /// {@macro flutter.widgets.widgetsApp.initialRoute}
222
  ///
223 224 225 226
  /// 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.
  ///
227 228 229 230 231
  /// 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.
232 233
  final String initialRoute;

xster's avatar
xster committed
234
  /// {@macro flutter.widgets.widgetsApp.onGenerateRoute}
235 236 237
  ///
  /// This is used if [routes] does not contain the requested route.
  ///
238 239 240
  /// 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.
241 242
  final RouteFactory onGenerateRoute;

243 244 245
  /// Called when [onGenerateRoute] fails to generate a route, except for the
  /// [initialRoute].
  ///
xster's avatar
xster committed
246
  /// {@macro flutter.widgets.widgetsApp.onUnknownRoute}
247 248 249 250
  ///
  /// 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.
251 252
  final RouteFactory onUnknownRoute;

xster's avatar
xster committed
253
  /// {@macro flutter.widgets.widgetsApp.navigatorObservers}
254 255 256 257 258 259
  ///
  /// 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;

xster's avatar
xster committed
260
  /// {@macro flutter.widgets.widgetsApp.builder}
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
  ///
  /// 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.
  final TransitionBuilder builder;

xster's avatar
xster committed
285
  /// {@macro flutter.widgets.widgetsApp.title}
286 287 288 289
  ///
  /// This value is passed unmodified to [WidgetsApp.title].
  final String title;

xster's avatar
xster committed
290
  /// {@macro flutter.widgets.widgetsApp.onGenerateTitle}
291 292 293 294 295 296 297
  ///
  /// This value is passed unmodified to [WidgetsApp.onGenerateTitle].
  final GenerateAppTitle onGenerateTitle;

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

xster's avatar
xster committed
298
  /// {@macro flutter.widgets.widgetsApp.color}
299 300
  final Color color;

xster's avatar
xster committed
301
  /// {@macro flutter.widgets.widgetsApp.locale}
302 303
  final Locale locale;

xster's avatar
xster committed
304
  /// {@macro flutter.widgets.widgetsApp.localizationsDelegates}
305 306 307 308
  ///
  /// Delegates that produce [WidgetsLocalizations] and [MaterialLocalizations]
  /// are included automatically. Apps can provide their own versions of these
  /// localizations by creating implementations of
309
  /// [LocalizationsDelegate<WidgetsLocalizations>] or
310
  /// [LocalizationsDelegate<MaterialLocalizations>] whose load methods return
311
  /// custom versions of [WidgetsLocalizations] or [MaterialLocalizations].
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
  ///
  /// 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(),
  ///   ],
  ///   // ...
  /// )
  /// ```
360
  final Iterable<LocalizationsDelegate<dynamic>> localizationsDelegates;
361

xster's avatar
xster committed
362
  /// {@macro flutter.widgets.widgetsApp.localeResolutionCallback}
363 364 365 366
  ///
  /// This callback is passed along to the [WidgetsApp] built by this widget.
  final LocaleResolutionCallback localeResolutionCallback;

xster's avatar
xster committed
367
  /// {@macro flutter.widgets.widgetsApp.supportedLocales}
368
  ///
xster's avatar
xster committed
369
  /// It is passed along unmodified to the [WidgetsApp] built by this widget.
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
  ///
  /// 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
385
  /// ro - Romanian
386 387 388 389 390 391 392
  /// ru - Russian
  /// sd - Sindhi
  /// ur - Urdu
  /// zh - Chinese (simplified)
  /// ```
  final Iterable<Locale> supportedLocales;

393
  /// Turns on a performance overlay.
394 395 396 397
  ///
  /// See also:
  ///
  ///  * <https://flutter.io/debugging/#performanceoverlay>
398 399
  final bool showPerformanceOverlay;

400 401 402
  /// Turns on checkerboarding of raster cache images.
  final bool checkerboardRasterCacheImages;

403 404 405
  /// Turns on checkerboarding of layers rendered to offscreen bitmaps.
  final bool checkerboardOffscreenLayers;

406 407 408 409
  /// Turns on an overlay that shows the accessibility information
  /// reported by the framework.
  final bool showSemanticsDebugger;

xster's avatar
xster committed
410
  /// {@macro flutter.widgets.widgetsApp.debugShowCheckedModeBanner}
411 412
  final bool debugShowCheckedModeBanner;

413
  /// Turns on a [GridPaper] overlay that paints a baseline grid
414 415
  /// Material apps.
  ///
416
  /// Only available in checked mode.
417 418 419 420
  ///
  /// See also:
  ///
  ///  * <https://material.google.com/layout/metrics-keylines.html>
Ian Hickson's avatar
Ian Hickson committed
421
  final bool debugShowMaterialGrid;
422

423
  @override
Adam Barth's avatar
Adam Barth committed
424
  _MaterialAppState createState() => new _MaterialAppState();
425 426
}

Adam Barth's avatar
Adam Barth committed
427
class _MaterialScrollBehavior extends ScrollBehavior {
428 429 430 431 432 433
  @override
  TargetPlatform getPlatform(BuildContext context) {
    return Theme.of(context).platform;
  }

  @override
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
  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;
449 450 451
  }
}

452
class _MaterialAppState extends State<MaterialApp> {
453 454 455 456 457 458
  HeroController _heroController;

  @override
  void initState() {
    super.initState();
    _heroController = new HeroController(createRectTween: _createRectTween);
459
    _updateNavigator();
460 461
  }

462 463 464 465 466 467 468 469 470 471
  @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);
    }
472
    _updateNavigator();
473 474
  }

475 476 477 478 479 480 481 482 483 484
  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);
485 486
  }

487 488 489
  RectTween _createRectTween(Rect begin, Rect end) {
    return new MaterialRectArcTween(begin: begin, end: end);
  }
490

491
  Route<dynamic> _onGenerateRoute(RouteSettings settings) {
492 493
    final String name = settings.name;
    WidgetBuilder builder;
494
    if (name == Navigator.defaultRouteName && widget.home != null) {
495
      builder = (BuildContext context) => widget.home;
496
    } else {
497
      builder = widget.routes[name];
498
    }
499
    if (builder != null) {
500
      return new MaterialPageRoute<dynamic>(
501
        builder: builder,
502
        settings: settings,
503 504
      );
    }
505 506
    if (widget.onGenerateRoute != null)
      return widget.onGenerateRoute(settings);
507 508
    return null;
  }
509

510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525
  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;
526
    }());
527 528 529 530 531 532 533 534 535 536 537
    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;
538
    }());
539 540 541
    return result;
  }

542 543 544 545 546 547 548 549 550 551 552
  // 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;
  }

553
  @override
554
  Widget build(BuildContext context) {
555
    final ThemeData theme = widget.theme ?? new ThemeData.fallback();
556 557
    Widget result = new AnimatedTheme(
      data: theme,
558
      isMaterialAppTheme: true,
559
      child: new WidgetsApp(
560
        key: new GlobalObjectKey(this),
561
        navigatorKey: widget.navigatorKey,
562 563 564 565 566
        navigatorObservers: _haveNavigator ? _navigatorObservers : null,
        initialRoute: widget.initialRoute,
        onGenerateRoute: _haveNavigator ? _onGenerateRoute : null,
        onUnknownRoute: _haveNavigator ? _onUnknownRoute : null,
        builder: widget.builder,
567
        title: widget.title,
568
        onGenerateTitle: widget.onGenerateTitle,
569
        textStyle: _errorTextStyle,
570
        // blue is the primary color of the default theme
571
        color: widget.color ?? theme?.primaryColor ?? Colors.blue,
572
        locale: widget.locale,
573
        localizationsDelegates: _localizationsDelegates,
574 575
        localeResolutionCallback: widget.localeResolutionCallback,
        supportedLocales: widget.supportedLocales,
576 577
        showPerformanceOverlay: widget.showPerformanceOverlay,
        checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
578
        checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
579
        showSemanticsDebugger: widget.showSemanticsDebugger,
580
        debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
581 582 583 584 585 586 587
        inspectorSelectButtonBuilder: (BuildContext context, VoidCallback onPressed) {
          return new FloatingActionButton(
            child: const Icon(Icons.search),
            onPressed: onPressed,
            mini: true,
          );
        },
588
      )
589
    );
590

Ian Hickson's avatar
Ian Hickson committed
591
    assert(() {
592
      if (widget.debugShowMaterialGrid) {
Ian Hickson's avatar
Ian Hickson committed
593 594 595 596
        result = new GridPaper(
          color: const Color(0xE0F9BBE0),
          interval: 8.0,
          divisions: 2,
597 598
          subdivisions: 1,
          child: result,
Ian Hickson's avatar
Ian Hickson committed
599 600 601
        );
      }
      return true;
602
    }());
603

Adam Barth's avatar
Adam Barth committed
604
    return new ScrollConfiguration(
605
      behavior: new _MaterialScrollBehavior(),
606
      child: result,
607
    );
608
  }
609
}