app.dart 38.1 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/foundation.dart';
9
import 'package:flutter/services.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 'scaffold.dart' show ScaffoldMessenger, ScaffoldMessengerState;
18
import 'scrollbar.dart';
19
import 'theme.dart';
20
import 'tooltip.dart';
21

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

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

53
/// An application that uses Material Design.
54 55
///
/// A convenience widget that wraps a number of widgets that are commonly
56
/// required for Material Design applications. It builds upon a [WidgetsApp] by
57 58 59
/// adding material-design specific functionality, such as [AnimatedTheme] and
/// [GridPaper].
///
60 61 62 63 64
/// [MaterialApp] configures its [WidgetsApp.textStyle] with an ugly red/yellow
/// text style that's intended to warn the developer that their app hasn't defined
/// a default text style. Typically the app's [Scaffold] builds a [Material] widget
/// whose default [Material.textStyle] defines the text style for the entire scaffold.
///
65 66 67 68 69 70 71 72 73 74 75 76
/// 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.
///
77 78 79
/// 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
80
/// Android; see [dart:ui.PlatformDispatcher.defaultRouteName]).
81
///
82 83 84
/// This widget also configures the observer of the top-level [Navigator] (if
/// any) to perform [Hero] animations.
///
85 86 87 88 89 90 91 92
/// {@template flutter.material.MaterialApp.defaultSelectionStyle}
/// The [MaterialApp] automatically creates a [DefaultSelectionStyle]. It uses
/// the colors in the [ThemeData.textSelectionTheme] if they are not null;
/// otherwise, the [MaterialApp] sets [DefaultSelectionStyle.selectionColor] to
/// [ColorScheme.primary] with 0.4 opacity and
/// [DefaultSelectionStyle.cursorColor] to [ColorScheme.primary].
/// {@endtemplate}
///
93 94
/// If [home], [routes], [onGenerateRoute], and [onUnknownRoute] are all null,
/// and [builder] is not null, then no [Navigator] is created.
95
///
96
/// {@tool snippet}
97 98 99
/// 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.
///
100
/// ![The MaterialApp displays a Scaffold ](https://flutter.github.io/assets-for-api-docs/assets/material/basic_material_app.png)
101 102 103 104 105 106 107 108 109 110 111 112 113
///
/// ```dart
/// MaterialApp(
///   home: Scaffold(
///     appBar: AppBar(
///       title: const Text('Home'),
///     ),
///   ),
///   debugShowCheckedModeBanner: false,
/// )
/// ```
/// {@end-tool}
///
114
/// {@tool snippet}
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
/// 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}
///
140
/// {@tool snippet}
141 142 143
/// This example shows how to create a [MaterialApp] that defines a [theme] that
/// will be used for material widgets in the app.
///
144
/// ![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)
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
///
/// ```dart
/// MaterialApp(
///   theme: ThemeData(
///     brightness: Brightness.dark,
///     primaryColor: Colors.blueGrey
///   ),
///   home: Scaffold(
///     appBar: AppBar(
///       title: const Text('MaterialApp Theme'),
///     ),
///   ),
/// )
/// ```
/// {@end-tool}
///
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
/// ## Troubleshooting
///
/// ### Why is my app's text red with yellow underlines?
///
/// [Text] widgets that lack a [Material] ancestor will be rendered with an ugly
/// red/yellow text style.
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/material_app_unspecified_textstyle.png)
///
/// The typical fix is to give the widget a [Scaffold] ancestor. The [Scaffold] creates
/// a [Material] widget that defines its default text style.
///
/// ```dart
/// MaterialApp(
///   title: 'Material App',
///   home: Scaffold(
///     body: Center(
///       child: Text('Hello World'),
///     ),
///   ),
/// )
/// ```
183 184
/// See also:
///
185 186 187 188
///  * [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.
189
///  * The Flutter Internationalization Tutorial,
190
///    <https://flutter.dev/tutorials/internationalization/>.
191
class MaterialApp extends StatefulWidget {
192 193
  /// Creates a MaterialApp.
  ///
194 195
  /// 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
196 197 198
  /// [Navigator.defaultRouteName] (`/`), since that is the route used when the
  /// application is launched with an intent that specifies an otherwise
  /// unsupported route.
199
  ///
200
  /// This class creates an instance of [WidgetsApp].
201 202
  ///
  /// The boolean arguments, [routes], and [navigatorObservers], must not be null.
203
  const MaterialApp({
204
    super.key,
205
    this.navigatorKey,
206
    this.scaffoldMessengerKey,
207
    this.home,
208
    Map<String, WidgetBuilder> this.routes = const <String, WidgetBuilder>{},
209
    this.initialRoute,
210
    this.onGenerateRoute,
211
    this.onGenerateInitialRoutes,
212
    this.onUnknownRoute,
213
    List<NavigatorObserver> this.navigatorObservers = const <NavigatorObserver>[],
214
    this.builder,
215
    this.title = '',
216 217 218
    this.onGenerateTitle,
    this.color,
    this.theme,
219
    this.darkTheme,
220 221
    this.highContrastTheme,
    this.highContrastDarkTheme,
222
    this.themeMode = ThemeMode.system,
223 224
    this.locale,
    this.localizationsDelegates,
225
    this.localeListResolutionCallback,
226
    this.localeResolutionCallback,
227
    this.supportedLocales = const <Locale>[Locale('en', 'US')],
228 229 230 231 232 233
    this.debugShowMaterialGrid = false,
    this.showPerformanceOverlay = false,
    this.checkerboardRasterCacheImages = false,
    this.checkerboardOffscreenLayers = false,
    this.showSemanticsDebugger = false,
    this.debugShowCheckedModeBanner = true,
234 235
    this.shortcuts,
    this.actions,
236
    this.restorationScopeId,
237
    this.scrollBehavior,
238
    this.useInheritedMediaQuery = false,
239
  }) : assert(routes != null),
240
       assert(navigatorObservers != null),
241 242 243 244 245 246 247
       assert(title != null),
       assert(debugShowMaterialGrid != null),
       assert(showPerformanceOverlay != null),
       assert(checkerboardRasterCacheImages != null),
       assert(checkerboardOffscreenLayers != null),
       assert(showSemanticsDebugger != null),
       assert(debugShowCheckedModeBanner != null),
248 249 250
       routeInformationProvider = null,
       routeInformationParser = null,
       routerDelegate = null,
251 252
       backButtonDispatcher = null,
       routerConfig = null;
253 254

  /// Creates a [MaterialApp] that uses the [Router] instead of a [Navigator].
255 256
  ///
  /// {@macro flutter.widgets.WidgetsApp.router}
257
  const MaterialApp.router({
258
    super.key,
259
    this.scaffoldMessengerKey,
260
    this.routeInformationProvider,
261 262 263
    this.routeInformationParser,
    this.routerDelegate,
    this.routerConfig,
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
    this.backButtonDispatcher,
    this.builder,
    this.title = '',
    this.onGenerateTitle,
    this.color,
    this.theme,
    this.darkTheme,
    this.highContrastTheme,
    this.highContrastDarkTheme,
    this.themeMode = ThemeMode.system,
    this.locale,
    this.localizationsDelegates,
    this.localeListResolutionCallback,
    this.localeResolutionCallback,
    this.supportedLocales = const <Locale>[Locale('en', 'US')],
    this.debugShowMaterialGrid = false,
    this.showPerformanceOverlay = false,
    this.checkerboardRasterCacheImages = false,
    this.checkerboardOffscreenLayers = false,
    this.showSemanticsDebugger = false,
    this.debugShowCheckedModeBanner = true,
    this.shortcuts,
    this.actions,
287
    this.restorationScopeId,
288
    this.scrollBehavior,
289
    this.useInheritedMediaQuery = false,
290
  }) : assert(routerDelegate != null || routerConfig != null),
291 292 293 294 295 296 297 298 299 300 301 302 303 304
       assert(title != null),
       assert(debugShowMaterialGrid != null),
       assert(showPerformanceOverlay != null),
       assert(checkerboardRasterCacheImages != null),
       assert(checkerboardOffscreenLayers != null),
       assert(showSemanticsDebugger != null),
       assert(debugShowCheckedModeBanner != null),
       navigatorObservers = null,
       navigatorKey = null,
       onGenerateRoute = null,
       home = null,
       onGenerateInitialRoutes = null,
       onUnknownRoute = null,
       routes = null,
305
       initialRoute = null;
306

307
  /// {@macro flutter.widgets.widgetsApp.navigatorKey}
308
  final GlobalKey<NavigatorState>? navigatorKey;
309

310 311 312 313 314 315 316 317
  /// A key to use when building the [ScaffoldMessenger].
  ///
  /// If a [scaffoldMessengerKey] is specified, the [ScaffoldMessenger] can be
  /// directly manipulated without first obtaining it from a [BuildContext] via
  /// [ScaffoldMessenger.of]: from the [scaffoldMessengerKey], use the
  /// [GlobalKey.currentState] getter.
  final GlobalKey<ScaffoldMessengerState>? scaffoldMessengerKey;

318
  /// {@macro flutter.widgets.widgetsApp.home}
319
  final Widget? home;
320

321 322 323 324
  /// 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
Janice Collins's avatar
Janice Collins committed
325 326 327
  /// [widgets.WidgetBuilder] is used to construct a [MaterialPageRoute] that
  /// performs an appropriate transition, including [Hero] animations, to the
  /// new route.
328
  ///
329
  /// {@macro flutter.widgets.widgetsApp.routes}
330
  final Map<String, WidgetBuilder>? routes;
Ian Hickson's avatar
Ian Hickson committed
331

xster's avatar
xster committed
332
  /// {@macro flutter.widgets.widgetsApp.initialRoute}
333
  final String? initialRoute;
334

xster's avatar
xster committed
335
  /// {@macro flutter.widgets.widgetsApp.onGenerateRoute}
336
  final RouteFactory? onGenerateRoute;
337

338
  /// {@macro flutter.widgets.widgetsApp.onGenerateInitialRoutes}
339
  final InitialRouteListFactory? onGenerateInitialRoutes;
340

xster's avatar
xster committed
341
  /// {@macro flutter.widgets.widgetsApp.onUnknownRoute}
342
  final RouteFactory? onUnknownRoute;
343

xster's avatar
xster committed
344
  /// {@macro flutter.widgets.widgetsApp.navigatorObservers}
345
  final List<NavigatorObserver>? navigatorObservers;
346

347
  /// {@macro flutter.widgets.widgetsApp.routeInformationProvider}
348
  final RouteInformationProvider? routeInformationProvider;
349 350

  /// {@macro flutter.widgets.widgetsApp.routeInformationParser}
351
  final RouteInformationParser<Object>? routeInformationParser;
352 353

  /// {@macro flutter.widgets.widgetsApp.routerDelegate}
354
  final RouterDelegate<Object>? routerDelegate;
355 356

  /// {@macro flutter.widgets.widgetsApp.backButtonDispatcher}
357
  final BackButtonDispatcher? backButtonDispatcher;
358

359 360 361
  /// {@macro flutter.widgets.widgetsApp.routerConfig}
  final RouterConfig<Object>? routerConfig;

xster's avatar
xster committed
362
  /// {@macro flutter.widgets.widgetsApp.builder}
363
  ///
364 365
  /// Material specific features such as [showDialog] and [showMenu], and widgets
  /// such as [Tooltip], [PopupMenuButton], also require a [Navigator] to properly
366
  /// function.
367
  final TransitionBuilder? builder;
368

xster's avatar
xster committed
369
  /// {@macro flutter.widgets.widgetsApp.title}
370 371 372 373
  ///
  /// This value is passed unmodified to [WidgetsApp.title].
  final String title;

xster's avatar
xster committed
374
  /// {@macro flutter.widgets.widgetsApp.onGenerateTitle}
375 376
  ///
  /// This value is passed unmodified to [WidgetsApp.onGenerateTitle].
377
  final GenerateAppTitle? onGenerateTitle;
378

379 380 381
  /// Default visual properties, like colors fonts and shapes, for this app's
  /// material widgets.
  ///
382 383 384
  /// 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.
385 386 387 388 389
  ///
  /// The default value of this property is the value of [ThemeData.light()].
  ///
  /// See also:
  ///
390
  ///  * [themeMode], which controls which theme to use.
391 392 393 394 395
  ///  * [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.
396
  final ThemeData? theme;
397

398
  /// The [ThemeData] to use when a 'dark mode' is requested by the system.
399
  ///
400 401 402 403
  /// 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.
404
  ///
405
  /// This theme should have a [ThemeData.brightness] set to [Brightness.dark].
406 407 408 409 410 411
  ///
  /// Uses [theme] instead when null. Defaults to the value of
  /// [ThemeData.light()] when both [darkTheme] and [theme] are null.
  ///
  /// See also:
  ///
412
  ///  * [themeMode], which controls which theme to use.
413 414 415 416 417
  ///  * [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].
418
  final ThemeData? darkTheme;
419

420 421 422 423 424 425 426 427 428 429 430
  /// The [ThemeData] to use when 'high contrast' is requested by the system.
  ///
  /// Some host platforms (for example, iOS) allow the users to increase
  /// contrast through an accessibility setting.
  ///
  /// Uses [theme] instead when null.
  ///
  /// See also:
  ///
  ///  * [MediaQueryData.highContrast], which indicates the platform's
  ///    desire to increase contrast.
431
  final ThemeData? highContrastTheme;
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446

  /// The [ThemeData] to use when a 'dark mode' and 'high contrast' is requested
  /// by the system.
  ///
  /// Some host platforms (for example, iOS) allow the users to increase
  /// contrast through an accessibility setting.
  ///
  /// This theme should have a [ThemeData.brightness] set to [Brightness.dark].
  ///
  /// Uses [darkTheme] instead when null.
  ///
  /// See also:
  ///
  ///  * [MediaQueryData.highContrast], which indicates the platform's
  ///    desire to increase contrast.
447
  final ThemeData? highContrastDarkTheme;
448

449 450 451 452 453 454
  /// 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],
455
  /// [darkTheme] will be used (unless it is null, in which case [theme]
456 457 458 459 460 461
  /// 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
462
  /// regardless of the user's system preference. If [darkTheme] is null
463 464 465 466 467 468
  /// then it will fallback to using [theme].
  ///
  /// The default value is [ThemeMode.system].
  ///
  /// See also:
  ///
469 470 471 472
  ///  * [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.
473
  final ThemeMode? themeMode;
474

xster's avatar
xster committed
475
  /// {@macro flutter.widgets.widgetsApp.color}
476
  final Color? color;
477

xster's avatar
xster committed
478
  /// {@macro flutter.widgets.widgetsApp.locale}
479
  final Locale? locale;
480

xster's avatar
xster committed
481
  /// {@macro flutter.widgets.widgetsApp.localizationsDelegates}
482
  ///
483
  /// Internationalized apps that require translations for one of the locales
484
  /// listed in [GlobalMaterialLocalizations] should specify this parameter
485 486 487 488 489
  /// and list the [supportedLocales] that the application can handle.
  ///
  /// ```dart
  /// import 'package:flutter_localizations/flutter_localizations.dart';
  /// MaterialApp(
490 491 492 493 494 495
  ///   localizationsDelegates: [
  ///     // ... app-specific localization delegate[s] here
  ///     GlobalMaterialLocalizations.delegate,
  ///     GlobalWidgetsLocalizations.delegate,
  ///   ],
  ///   supportedLocales: [
496 497 498 499 500 501 502 503 504 505 506 507 508 509
  ///     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].
  ///
510 511 512
  /// Delegates that produce [WidgetsLocalizations] and [MaterialLocalizations]
  /// are included automatically. Apps can provide their own versions of these
  /// localizations by creating implementations of
513
  /// [LocalizationsDelegate<WidgetsLocalizations>] or
514
  /// [LocalizationsDelegate<MaterialLocalizations>] whose load methods return
515
  /// custom versions of [WidgetsLocalizations] or [MaterialLocalizations].
516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542
  ///
  /// 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) {
543
  ///     return SynchronousFuture(FooLocalizations(locale));
544 545 546 547 548 549 550 551 552 553 554 555 556
  ///   }
  ///   @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
557
  /// MaterialApp(
558 559 560 561 562 563
  ///   localizationsDelegates: [
  ///     const FooLocalizationsDelegate(),
  ///   ],
  ///   // ...
  /// )
  /// ```
564 565 566 567 568 569 570
  /// 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,
571
  ///    <https://flutter.dev/tutorials/internationalization/>.
572
  final Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates;
573

574 575 576
  /// {@macro flutter.widgets.widgetsApp.localeListResolutionCallback}
  ///
  /// This callback is passed along to the [WidgetsApp] built by this widget.
577
  final LocaleListResolutionCallback? localeListResolutionCallback;
578

579
  /// {@macro flutter.widgets.LocaleResolutionCallback}
580 581
  ///
  /// This callback is passed along to the [WidgetsApp] built by this widget.
582
  final LocaleResolutionCallback? localeResolutionCallback;
583

xster's avatar
xster committed
584
  /// {@macro flutter.widgets.widgetsApp.supportedLocales}
585
  ///
xster's avatar
xster committed
586
  /// It is passed along unmodified to the [WidgetsApp] built by this widget.
587
  ///
588 589 590 591 592 593 594
  /// 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,
595
  ///    <https://flutter.dev/tutorials/internationalization/>.
596 597
  final Iterable<Locale> supportedLocales;

598
  /// Turns on a performance overlay.
599 600 601
  ///
  /// See also:
  ///
602
  ///  * <https://flutter.dev/debugging/#performance-overlay>
603 604
  final bool showPerformanceOverlay;

605 606 607
  /// Turns on checkerboarding of raster cache images.
  final bool checkerboardRasterCacheImages;

608 609 610
  /// Turns on checkerboarding of layers rendered to offscreen bitmaps.
  final bool checkerboardOffscreenLayers;

611 612 613 614
  /// Turns on an overlay that shows the accessibility information
  /// reported by the framework.
  final bool showSemanticsDebugger;

xster's avatar
xster committed
615
  /// {@macro flutter.widgets.widgetsApp.debugShowCheckedModeBanner}
616 617
  final bool debugShowCheckedModeBanner;

618
  /// {@macro flutter.widgets.widgetsApp.shortcuts}
619
  /// {@tool snippet}
620 621 622 623 624 625 626 627 628 629 630
  /// 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(
631
  ///     shortcuts: <ShortcutActivator, Intent>{
632
  ///       ... WidgetsApp.defaultShortcuts,
633
  ///       const SingleActivator(LogicalKeyboardKey.select): const ActivateIntent(),
634 635
  ///     },
  ///     color: const Color(0xFFFF0000),
636
  ///     builder: (BuildContext context, Widget? child) {
637 638 639 640 641 642 643
  ///       return const Placeholder();
  ///     },
  ///   );
  /// }
  /// ```
  /// {@end-tool}
  /// {@macro flutter.widgets.widgetsApp.shortcuts.seeAlso}
644
  final Map<ShortcutActivator, Intent>? shortcuts;
645 646

  /// {@macro flutter.widgets.widgetsApp.actions}
647
  /// {@tool snippet}
648 649 650 651 652 653 654 655 656 657 658
  /// 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(
659
  ///     actions: <Type, Action<Intent>>{
660
  ///       ... WidgetsApp.defaultActions,
661
  ///       ActivateAction: CallbackAction<Intent>(
662
  ///         onInvoke: (Intent intent) {
663
  ///           // Do something here...
664
  ///           return null;
665 666 667 668
  ///         },
  ///       ),
  ///     },
  ///     color: const Color(0xFFFF0000),
669
  ///     builder: (BuildContext context, Widget? child) {
670 671 672 673 674 675 676
  ///       return const Placeholder();
  ///     },
  ///   );
  /// }
  /// ```
  /// {@end-tool}
  /// {@macro flutter.widgets.widgetsApp.actions.seeAlso}
677
  final Map<Type, Action<Intent>>? actions;
678

679
  /// {@macro flutter.widgets.widgetsApp.restorationScopeId}
680
  final String? restorationScopeId;
681

682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698
  /// {@template flutter.material.materialApp.scrollBehavior}
  /// The default [ScrollBehavior] for the application.
  ///
  /// [ScrollBehavior]s describe how [Scrollable] widgets behave. Providing
  /// a [ScrollBehavior] can set the default [ScrollPhysics] across
  /// an application, and manage [Scrollable] decorations like [Scrollbar]s and
  /// [GlowingOverscrollIndicator]s.
  /// {@endtemplate}
  ///
  /// When null, defaults to [MaterialScrollBehavior].
  ///
  /// See also:
  ///
  ///  * [ScrollConfiguration], which controls how [Scrollable] widgets behave
  ///    in a subtree.
  final ScrollBehavior? scrollBehavior;

699
  /// Turns on a [GridPaper] overlay that paints a baseline grid
700 701
  /// Material apps.
  ///
702
  /// Only available in debug mode.
703 704 705
  ///
  /// See also:
  ///
706
  ///  * <https://material.io/design/layout/spacing-methods.html>
Ian Hickson's avatar
Ian Hickson committed
707
  final bool debugShowMaterialGrid;
708

709 710 711
  /// {@macro flutter.widgets.widgetsApp.useInheritedMediaQuery}
  final bool useInheritedMediaQuery;

712
  @override
713
  State<MaterialApp> createState() => _MaterialAppState();
714 715 716 717 718 719

  /// The [HeroController] used for Material page transitions.
  ///
  /// Used by the [MaterialApp].
  static HeroController createMaterialHeroController() {
    return HeroController(
720
      createRectTween: (Rect? begin, Rect? end) {
721 722 723 724
        return MaterialRectArcTween(begin: begin, end: end);
      },
    );
  }
725 726
}

727 728 729 730 731 732 733 734
/// Describes how [Scrollable] widgets behave for [MaterialApp]s.
///
/// {@macro flutter.widgets.scrollBehavior}
///
/// Setting a [MaterialScrollBehavior] will apply a
/// [GlowingOverscrollIndicator] to [Scrollable] descendants when executing on
/// [TargetPlatform.android] and [TargetPlatform.fuchsia].
///
735 736
/// When using the desktop platform, if the [Scrollable] widget scrolls in the
/// [Axis.vertical], a [Scrollbar] is applied.
737
///
738 739 740 741
/// If the scroll direction is [Axis.horizontal] scroll views are less
/// discoverable, so consider adding a Scrollbar in these cases, either directly
/// or through the [buildScrollbar] method.
///
742 743 744
/// [MaterialScrollBehavior.androidOverscrollIndicator] specifies the
/// overscroll indicator that is used on [TargetPlatform.android]. When null,
/// [ThemeData.androidOverscrollIndicator] is used. If also null, the default
745 746 747 748
/// overscroll indicator is the [GlowingOverscrollIndicator]. These properties
/// are deprecated. In order to use the [StretchingOverscrollIndicator], use
/// the [ThemeData.useMaterial3] flag, or override
/// [ScrollBehavior.buildOverscrollIndicator].
749
///
750 751 752 753
/// See also:
///
///  * [ScrollBehavior], the default scrolling behavior extended by this class.
class MaterialScrollBehavior extends ScrollBehavior {
754 755 756
  /// Creates a MaterialScrollBehavior that decorates [Scrollable]s with
  /// [GlowingOverscrollIndicator]s and [Scrollbar]s based on the current
  /// platform and provided [ScrollableDetails].
757 758 759 760 761
  ///
  /// [MaterialScrollBehavior.androidOverscrollIndicator] specifies the
  /// overscroll indicator that is used on [TargetPlatform.android]. When null,
  /// [ThemeData.androidOverscrollIndicator] is used. If also null, the default
  /// overscroll indicator is the [GlowingOverscrollIndicator].
762
  const MaterialScrollBehavior({
763 764 765 766
    @Deprecated(
      'Use ThemeData.useMaterial3 or override ScrollBehavior.buildOverscrollIndicator. '
      'This feature was deprecated after v2.13.0-0.0.pre.'
    )
767 768
    super.androidOverscrollIndicator,
  }) : _androidOverscrollIndicator = androidOverscrollIndicator;
769 770

  final AndroidOverscrollIndicator? _androidOverscrollIndicator;
771 772 773 774

  @override
  TargetPlatform getPlatform(BuildContext context) => Theme.of(context).platform;

775
  @override
776 777 778 779 780
  Widget buildScrollbar(BuildContext context, Widget child, ScrollableDetails details) {
    // When modifying this function, consider modifying the implementation in
    // the base class as well.
    switch (axisDirectionToAxis(details.direction)) {
      case Axis.horizontal:
781
        return child;
782 783 784 785 786 787 788
      case Axis.vertical:
        switch (getPlatform(context)) {
          case TargetPlatform.linux:
          case TargetPlatform.macOS:
          case TargetPlatform.windows:
            return Scrollbar(
              controller: details.controller,
789
              child: child,
790 791 792 793 794 795 796
            );
          case TargetPlatform.android:
          case TargetPlatform.fuchsia:
          case TargetPlatform.iOS:
            return child;
        }
    }
797 798 799
  }

  @override
800
  Widget buildOverscrollIndicator(BuildContext context, Widget child, ScrollableDetails details) {
801 802
    // When modifying this function, consider modifying the implementation in
    // the base class as well.
803 804 805 806 807
    late final AndroidOverscrollIndicator indicator;
    if (Theme.of(context).useMaterial3) {
      indicator = AndroidOverscrollIndicator.stretch;
    } else {
      indicator = _androidOverscrollIndicator
808 809
        ?? Theme.of(context).androidOverscrollIndicator
        ?? androidOverscrollIndicator;
810
    }
811 812
    switch (getPlatform(context)) {
      case TargetPlatform.iOS:
813
      case TargetPlatform.linux:
814
      case TargetPlatform.macOS:
815
      case TargetPlatform.windows:
816 817
        return child;
      case TargetPlatform.android:
818
        switch (indicator) {
819 820 821 822 823 824 825 826 827
          case AndroidOverscrollIndicator.stretch:
            return StretchingOverscrollIndicator(
              axisDirection: details.direction,
              child: child,
            );
          case AndroidOverscrollIndicator.glow:
            continue glow;
        }
      glow:
828
      case TargetPlatform.fuchsia:
829
        return GlowingOverscrollIndicator(
830
          axisDirection: details.direction,
831
          color: Theme.of(context).colorScheme.secondary,
832
          child: child,
833 834
        );
    }
835 836 837
  }
}

838
class _MaterialAppState extends State<MaterialApp> {
839
  late HeroController _heroController;
840

841
  bool get _usesRouter => widget.routerDelegate != null || widget.routerConfig != null;
842

843 844 845
  @override
  void initState() {
    super.initState();
846
    _heroController = MaterialApp.createMaterialHeroController();
847 848
  }

849 850 851 852 853
  // 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.
854 855 856 857 858 859 860
  Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates {
    return <LocalizationsDelegate<dynamic>>[
      if (widget.localizationsDelegates != null)
        ...widget.localizationsDelegates!,
      DefaultMaterialLocalizations.delegate,
      DefaultCupertinoLocalizations.delegate,
    ];
861 862
  }

863 864 865 866
  Widget _inspectorSelectButtonBuilder(BuildContext context, VoidCallback onPressed) {
    return FloatingActionButton(
      onPressed: onPressed,
      mini: true,
867
      child: const Icon(Icons.search),
868 869 870
    );
  }

871
  Widget _materialBuilder(BuildContext context, Widget? child) {
872 873 874 875 876 877
    // Resolve which theme to use based on brightness and high contrast.
    final ThemeMode mode = widget.themeMode ?? ThemeMode.system;
    final Brightness platformBrightness = MediaQuery.platformBrightnessOf(context);
    final bool useDarkTheme = mode == ThemeMode.dark
      || (mode == ThemeMode.system && platformBrightness == ui.Brightness.dark);
    final bool highContrast = MediaQuery.highContrastOf(context);
878
    ThemeData? theme;
879 880 881 882 883 884 885 886 887

    if (useDarkTheme && highContrast && widget.highContrastDarkTheme != null) {
      theme = widget.highContrastDarkTheme;
    } else if (useDarkTheme && widget.darkTheme != null) {
      theme = widget.darkTheme;
    } else if (highContrast && widget.highContrastTheme != null) {
      theme = widget.highContrastTheme;
    }
    theme ??= widget.theme ?? ThemeData.light();
888 889
    final Color effectiveSelectionColor = theme.textSelectionTheme.selectionColor ?? theme.colorScheme.primary.withOpacity(0.40);
    final Color effectiveCursorColor = theme.textSelectionTheme.cursorColor ?? theme.colorScheme.primary;
890

891 892
    return ScaffoldMessenger(
      key: widget.scaffoldMessengerKey,
893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916
      child: DefaultSelectionStyle(
        selectionColor: effectiveSelectionColor,
        cursorColor: effectiveCursorColor,
        child: AnimatedTheme(
          data: theme,
          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 ?? const SizedBox.shrink(),
        ),
917
      ),
918 919 920 921 922 923 924 925 926 927 928 929 930 931
    );
  }

  Widget _buildWidgetApp(BuildContext context) {
    // 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.
    final Color materialColor = widget.color ?? widget.theme?.primaryColor ?? Colors.blue;
    if (_usesRouter) {
      return WidgetsApp.router(
932
        key: GlobalObjectKey(this),
933
        routeInformationProvider: widget.routeInformationProvider,
934 935 936
        routeInformationParser: widget.routeInformationParser,
        routerDelegate: widget.routerDelegate,
        routerConfig: widget.routerConfig,
937 938
        backButtonDispatcher: widget.backButtonDispatcher,
        builder: _materialBuilder,
939 940 941
        title: widget.title,
        onGenerateTitle: widget.onGenerateTitle,
        textStyle: _errorTextStyle,
942
        color: materialColor,
943 944 945 946 947 948 949 950 951 952
        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,
953
        inspectorSelectButtonBuilder: _inspectorSelectButtonBuilder,
954 955
        shortcuts: widget.shortcuts,
        actions: widget.actions,
956
        restorationScopeId: widget.restorationScopeId,
957
        useInheritedMediaQuery: widget.useInheritedMediaQuery,
958 959 960 961 962 963
      );
    }

    return WidgetsApp(
      key: GlobalObjectKey(this),
      navigatorKey: widget.navigatorKey,
964
      navigatorObservers: widget.navigatorObservers!,
965 966 967 968
      pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) {
        return MaterialPageRoute<T>(settings: settings, builder: builder);
      },
      home: widget.home,
969
      routes: widget.routes!,
970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991
      initialRoute: widget.initialRoute,
      onGenerateRoute: widget.onGenerateRoute,
      onGenerateInitialRoutes: widget.onGenerateInitialRoutes,
      onUnknownRoute: widget.onUnknownRoute,
      builder: _materialBuilder,
      title: widget.title,
      onGenerateTitle: widget.onGenerateTitle,
      textStyle: _errorTextStyle,
      color: materialColor,
      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: _inspectorSelectButtonBuilder,
      shortcuts: widget.shortcuts,
      actions: widget.actions,
992
      restorationScopeId: widget.restorationScopeId,
993
      useInheritedMediaQuery: widget.useInheritedMediaQuery,
994
    );
995 996 997 998 999
  }

  @override
  Widget build(BuildContext context) {
    Widget result = _buildWidgetApp(context);
1000 1001 1002 1003 1004 1005 1006 1007 1008
    result = Focus(
      canRequestFocus: false,
      onKey: (FocusNode node, RawKeyEvent event) {
        if (event is! RawKeyDownEvent || event.logicalKey != LogicalKeyboardKey.escape)
          return KeyEventResult.ignored;
        return Tooltip.dismissAllToolTips() ? KeyEventResult.handled : KeyEventResult.ignored;
      },
      child: result,
    );
Ian Hickson's avatar
Ian Hickson committed
1009
    assert(() {
1010
      if (widget.debugShowMaterialGrid) {
1011
        result = GridPaper(
Ian Hickson's avatar
Ian Hickson committed
1012 1013
          color: const Color(0xE0F9BBE0),
          interval: 8.0,
1014 1015
          subdivisions: 1,
          child: result,
Ian Hickson's avatar
Ian Hickson committed
1016 1017 1018
        );
      }
      return true;
1019
    }());
1020

1021
    return ScrollConfiguration(
1022
      behavior: widget.scrollBehavior ?? const MaterialScrollBehavior(),
1023 1024 1025
      child: HeroControllerScope(
        controller: _heroController,
        child: result,
1026
      ),
1027
    );
1028
  }
1029
}