app.dart 40.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/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
// Examples can assume:
// typedef GlobalWidgetsLocalizations = DefaultWidgetsLocalizations;
// typedef GlobalMaterialLocalizations = DefaultMaterialLocalizations;

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

44 45 46 47 48 49 50 51 52 53 54 55 56
/// 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,
}

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

  /// Creates a [MaterialApp] that uses the [Router] instead of a [Navigator].
258 259
  ///
  /// {@macro flutter.widgets.WidgetsApp.router}
260
  const MaterialApp.router({
261
    super.key,
262
    this.scaffoldMessengerKey,
263
    this.routeInformationProvider,
264 265 266
    this.routeInformationParser,
    this.routerDelegate,
    this.routerConfig,
267 268 269 270
    this.backButtonDispatcher,
    this.builder,
    this.title = '',
    this.onGenerateTitle,
271
    this.onNavigationNotification,
272 273 274 275 276 277
    this.color,
    this.theme,
    this.darkTheme,
    this.highContrastTheme,
    this.highContrastDarkTheme,
    this.themeMode = ThemeMode.system,
278 279
    this.themeAnimationDuration = kThemeAnimationDuration,
    this.themeAnimationCurve = Curves.linear,
280 281 282 283 284 285 286 287 288 289 290 291 292
    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,
293
    this.restorationScopeId,
294
    this.scrollBehavior,
295 296 297 298 299
    @Deprecated(
      'Remove this parameter as it is now ignored. '
      'MaterialApp never introduces its own MediaQuery; the View widget takes care of that. '
      'This feature was deprecated after v3.7.0-29.0.pre.'
    )
300
    this.useInheritedMediaQuery = false,
301
    this.themeAnimationStyle,
302
  }) : assert(routerDelegate != null || routerConfig != null),
303 304 305 306 307 308 309
       navigatorObservers = null,
       navigatorKey = null,
       onGenerateRoute = null,
       home = null,
       onGenerateInitialRoutes = null,
       onUnknownRoute = null,
       routes = null,
310
       initialRoute = null;
311

312
  /// {@macro flutter.widgets.widgetsApp.navigatorKey}
313
  final GlobalKey<NavigatorState>? navigatorKey;
314

315 316 317 318 319 320 321 322
  /// 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;

323
  /// {@macro flutter.widgets.widgetsApp.home}
324
  final Widget? home;
325

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

xster's avatar
xster committed
337
  /// {@macro flutter.widgets.widgetsApp.initialRoute}
338
  final String? initialRoute;
339

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

343
  /// {@macro flutter.widgets.widgetsApp.onGenerateInitialRoutes}
344
  final InitialRouteListFactory? onGenerateInitialRoutes;
345

xster's avatar
xster committed
346
  /// {@macro flutter.widgets.widgetsApp.onUnknownRoute}
347
  final RouteFactory? onUnknownRoute;
348

349 350 351
  /// {@macro flutter.widgets.widgetsApp.onNavigationNotification}
  final NotificationListenerCallback<NavigationNotification>? onNavigationNotification;

xster's avatar
xster committed
352
  /// {@macro flutter.widgets.widgetsApp.navigatorObservers}
353
  final List<NavigatorObserver>? navigatorObservers;
354

355
  /// {@macro flutter.widgets.widgetsApp.routeInformationProvider}
356
  final RouteInformationProvider? routeInformationProvider;
357 358

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

  /// {@macro flutter.widgets.widgetsApp.routerDelegate}
362
  final RouterDelegate<Object>? routerDelegate;
363 364

  /// {@macro flutter.widgets.widgetsApp.backButtonDispatcher}
365
  final BackButtonDispatcher? backButtonDispatcher;
366

367 368 369
  /// {@macro flutter.widgets.widgetsApp.routerConfig}
  final RouterConfig<Object>? routerConfig;

xster's avatar
xster committed
370
  /// {@macro flutter.widgets.widgetsApp.builder}
371
  ///
372 373
  /// Material specific features such as [showDialog] and [showMenu], and widgets
  /// such as [Tooltip], [PopupMenuButton], also require a [Navigator] to properly
374
  /// function.
375
  final TransitionBuilder? builder;
376

xster's avatar
xster committed
377
  /// {@macro flutter.widgets.widgetsApp.title}
378 379 380 381
  ///
  /// This value is passed unmodified to [WidgetsApp.title].
  final String title;

xster's avatar
xster committed
382
  /// {@macro flutter.widgets.widgetsApp.onGenerateTitle}
383 384
  ///
  /// This value is passed unmodified to [WidgetsApp.onGenerateTitle].
385
  final GenerateAppTitle? onGenerateTitle;
386

387 388 389
  /// Default visual properties, like colors fonts and shapes, for this app's
  /// material widgets.
  ///
390 391 392
  /// 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.
393 394 395 396 397
  ///
  /// The default value of this property is the value of [ThemeData.light()].
  ///
  /// See also:
  ///
398
  ///  * [themeMode], which controls which theme to use.
399 400 401 402 403
  ///  * [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.
404
  final ThemeData? theme;
405

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

428 429 430 431 432 433 434 435 436 437 438
  /// 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.
439
  final ThemeData? highContrastTheme;
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454

  /// 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.
455
  final ThemeData? highContrastDarkTheme;
456

457 458 459 460 461 462
  /// 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],
463
  /// [darkTheme] will be used (unless it is null, in which case [theme]
464 465 466 467 468 469
  /// 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
470
  /// regardless of the user's system preference. If [darkTheme] is null
471 472 473 474 475 476
  /// then it will fallback to using [theme].
  ///
  /// The default value is [ThemeMode.system].
  ///
  /// See also:
  ///
477 478 479 480
  ///  * [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.
481
  final ThemeMode? themeMode;
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
  /// The duration of animated theme changes.
  ///
  /// When the theme changes (either by the [theme], [darkTheme] or [themeMode]
  /// parameters changing) it is animated to the new theme over time.
  /// The [themeAnimationDuration] determines how long this animation takes.
  ///
  /// To have the theme change immediately, you can set this to [Duration.zero].
  ///
  /// The default is [kThemeAnimationDuration].
  ///
  /// See also:
  ///   [themeAnimationCurve], which defines the curve used for the animation.
  final Duration themeAnimationDuration;

  /// The curve to apply when animating theme changes.
  ///
  /// The default is [Curves.linear].
  ///
  /// This is ignored if [themeAnimationDuration] is [Duration.zero].
  ///
  /// See also:
  ///   [themeAnimationDuration], which defines how long the animation is.
  final Curve themeAnimationCurve;

xster's avatar
xster committed
507
  /// {@macro flutter.widgets.widgetsApp.color}
508
  final Color? color;
509

xster's avatar
xster committed
510
  /// {@macro flutter.widgets.widgetsApp.locale}
511
  final Locale? locale;
512

xster's avatar
xster committed
513
  /// {@macro flutter.widgets.widgetsApp.localizationsDelegates}
514
  ///
515
  /// Internationalized apps that require translations for one of the locales
516
  /// listed in [GlobalMaterialLocalizations] should specify this parameter
517 518 519
  /// and list the [supportedLocales] that the application can handle.
  ///
  /// ```dart
520 521 522 523 524 525 526
  /// // The GlobalMaterialLocalizations and GlobalWidgetsLocalizations
  /// // classes require the following import:
  /// // import 'package:flutter_localizations/flutter_localizations.dart';
  ///
  /// const MaterialApp(
  ///   localizationsDelegates: <LocalizationsDelegate<Object>>[
  ///     // ... app-specific localization delegate(s) here
527 528 529
  ///     GlobalMaterialLocalizations.delegate,
  ///     GlobalWidgetsLocalizations.delegate,
  ///   ],
530 531 532
  ///   supportedLocales: <Locale>[
  ///     Locale('en', 'US'), // English
  ///     Locale('he', 'IL'), // Hebrew
533 534 535 536 537 538 539 540 541 542 543 544
  ///     // ... 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].
  ///
545 546 547
  /// Delegates that produce [WidgetsLocalizations] and [MaterialLocalizations]
  /// are included automatically. Apps can provide their own versions of these
  /// localizations by creating implementations of
548
  /// [LocalizationsDelegate<WidgetsLocalizations>] or
549
  /// [LocalizationsDelegate<MaterialLocalizations>] whose load methods return
550
  /// custom versions of [WidgetsLocalizations] or [MaterialLocalizations].
551
  ///
552 553 554 555
  /// For example: to add support to [MaterialLocalizations] for a locale it
  /// doesn't already support, say `const Locale('foo', 'BR')`, one first
  /// creates a subclass of [MaterialLocalizations] that provides the
  /// translations:
556 557
  ///
  /// ```dart
558 559
  /// class FooLocalizations extends MaterialLocalizations {
  ///   FooLocalizations();
560
  ///   @override
561 562 563
  ///   String get okButtonLabel => 'foo';
  ///   // ...
  ///   // lots of other getters and methods to override!
564 565 566
  /// }
  /// ```
  ///
567 568 569 570 571
  /// One must then create a [LocalizationsDelegate] subclass that can provide
  /// an instance of the [MaterialLocalizations] subclass. In this case, this is
  /// essentially just a method that constructs a `FooLocalizations` object. A
  /// [SynchronousFuture] is used here because no asynchronous work takes place
  /// upon "loading" the localizations object.
572 573
  ///
  /// ```dart
574
  /// // continuing from previous example...
575 576 577
  /// class FooLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
  ///   const FooLocalizationsDelegate();
  ///   @override
578 579 580 581
  ///   bool isSupported(Locale locale) {
  ///     return locale == const Locale('foo', 'BR');
  ///   }
  ///   @override
582
  ///   Future<FooLocalizations> load(Locale locale) {
583 584
  ///     assert(locale == const Locale('foo', 'BR'));
  ///     return SynchronousFuture<FooLocalizations>(FooLocalizations());
585 586 587 588 589 590 591 592 593 594 595 596 597
  ///   }
  ///   @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
598 599 600 601
  /// // continuing from previous example...
  /// const MaterialApp(
  ///   localizationsDelegates: <LocalizationsDelegate<Object>>[
  ///     FooLocalizationsDelegate(),
602 603 604 605
  ///   ],
  ///   // ...
  /// )
  /// ```
606 607 608 609 610 611 612
  /// 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,
613
  ///    <https://flutter.dev/tutorials/internationalization/>.
614
  final Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates;
615

616 617 618
  /// {@macro flutter.widgets.widgetsApp.localeListResolutionCallback}
  ///
  /// This callback is passed along to the [WidgetsApp] built by this widget.
619
  final LocaleListResolutionCallback? localeListResolutionCallback;
620

621
  /// {@macro flutter.widgets.LocaleResolutionCallback}
622 623
  ///
  /// This callback is passed along to the [WidgetsApp] built by this widget.
624
  final LocaleResolutionCallback? localeResolutionCallback;
625

xster's avatar
xster committed
626
  /// {@macro flutter.widgets.widgetsApp.supportedLocales}
627
  ///
xster's avatar
xster committed
628
  /// It is passed along unmodified to the [WidgetsApp] built by this widget.
629
  ///
630 631 632 633 634 635 636
  /// 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,
637
  ///    <https://flutter.dev/tutorials/internationalization/>.
638 639
  final Iterable<Locale> supportedLocales;

640
  /// Turns on a performance overlay.
641 642 643
  ///
  /// See also:
  ///
644
  ///  * <https://flutter.dev/debugging/#performance-overlay>
645 646
  final bool showPerformanceOverlay;

647 648 649
  /// Turns on checkerboarding of raster cache images.
  final bool checkerboardRasterCacheImages;

650 651 652
  /// Turns on checkerboarding of layers rendered to offscreen bitmaps.
  final bool checkerboardOffscreenLayers;

653 654 655 656
  /// Turns on an overlay that shows the accessibility information
  /// reported by the framework.
  final bool showSemanticsDebugger;

xster's avatar
xster committed
657
  /// {@macro flutter.widgets.widgetsApp.debugShowCheckedModeBanner}
658 659
  final bool debugShowCheckedModeBanner;

660
  /// {@macro flutter.widgets.widgetsApp.shortcuts}
661
  /// {@tool snippet}
662 663 664 665 666 667 668 669 670 671 672
  /// 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(
673
  ///     shortcuts: <ShortcutActivator, Intent>{
674
  ///       ... WidgetsApp.defaultShortcuts,
675
  ///       const SingleActivator(LogicalKeyboardKey.select): const ActivateIntent(),
676 677
  ///     },
  ///     color: const Color(0xFFFF0000),
678
  ///     builder: (BuildContext context, Widget? child) {
679 680 681 682 683 684 685
  ///       return const Placeholder();
  ///     },
  ///   );
  /// }
  /// ```
  /// {@end-tool}
  /// {@macro flutter.widgets.widgetsApp.shortcuts.seeAlso}
686
  final Map<ShortcutActivator, Intent>? shortcuts;
687 688

  /// {@macro flutter.widgets.widgetsApp.actions}
689
  /// {@tool snippet}
690 691 692 693 694 695 696 697 698 699 700
  /// 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(
701
  ///     actions: <Type, Action<Intent>>{
702
  ///       ... WidgetsApp.defaultActions,
703
  ///       ActivateAction: CallbackAction<Intent>(
704
  ///         onInvoke: (Intent intent) {
705
  ///           // Do something here...
706
  ///           return null;
707 708 709 710
  ///         },
  ///       ),
  ///     },
  ///     color: const Color(0xFFFF0000),
711
  ///     builder: (BuildContext context, Widget? child) {
712 713 714 715 716 717 718
  ///       return const Placeholder();
  ///     },
  ///   );
  /// }
  /// ```
  /// {@end-tool}
  /// {@macro flutter.widgets.widgetsApp.actions.seeAlso}
719
  final Map<Type, Action<Intent>>? actions;
720

721
  /// {@macro flutter.widgets.widgetsApp.restorationScopeId}
722
  final String? restorationScopeId;
723

724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740
  /// {@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;

741
  /// Turns on a [GridPaper] overlay that paints a baseline grid
742 743
  /// Material apps.
  ///
744
  /// Only available in debug mode.
745 746 747
  ///
  /// See also:
  ///
748
  ///  * <https://material.io/design/layout/spacing-methods.html>
Ian Hickson's avatar
Ian Hickson committed
749
  final bool debugShowMaterialGrid;
750

751
  /// {@macro flutter.widgets.widgetsApp.useInheritedMediaQuery}
752 753 754 755 756
  @Deprecated(
    'This setting is now ignored. '
    'MaterialApp never introduces its own MediaQuery; the View widget takes care of that. '
    'This feature was deprecated after v3.7.0-29.0.pre.'
  )
757 758
  final bool useInheritedMediaQuery;

759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780
  /// Used to override the theme animation curve and duration.
  ///
  /// If [AnimationStyle.duration] is provided, it will be used to override
  /// the theme animation duration in the underlying [AnimatedTheme] widget.
  /// If it is null, then [themeAnimationDuration] will be used. Otherwise,
  /// defaults to 200ms.
  ///
  /// If [AnimationStyle.curve] is provided, it will be used to override
  /// the theme animation curve in the underlying [AnimatedTheme] widget.
  /// If it is null, then [themeAnimationCurve] will be used. Otherwise,
  /// defaults to [Curves.linear].
  ///
  /// To disable the theme animation, use [AnimationStyle.noAnimation].
  ///
  /// {@tool dartpad}
  /// This sample showcases how to override the theme animation curve and
  /// duration in the [MaterialApp] widget using [AnimationStyle].
  ///
  /// ** See code in examples/api/lib/material/app/app.0.dart **
  /// {@end-tool}
  final AnimationStyle? themeAnimationStyle;

781
  @override
782
  State<MaterialApp> createState() => _MaterialAppState();
783 784 785 786 787 788

  /// The [HeroController] used for Material page transitions.
  ///
  /// Used by the [MaterialApp].
  static HeroController createMaterialHeroController() {
    return HeroController(
789
      createRectTween: (Rect? begin, Rect? end) {
790 791 792 793
        return MaterialRectArcTween(begin: begin, end: end);
      },
    );
  }
794 795
}

796 797 798 799 800 801 802 803
/// 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].
///
804 805
/// When using the desktop platform, if the [Scrollable] widget scrolls in the
/// [Axis.vertical], a [Scrollbar] is applied.
806
///
807 808 809 810
/// 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.
///
811 812 813 814 815
/// [ThemeData.useMaterial3] specifies the
/// overscroll indicator that is used on [TargetPlatform.android], which
/// defaults to true, resulting in a [StretchingOverscrollIndicator]. Setting
/// [ThemeData.useMaterial3] to false will instead use a
/// [GlowingOverscrollIndicator].
816
///
817 818 819 820
/// See also:
///
///  * [ScrollBehavior], the default scrolling behavior extended by this class.
class MaterialScrollBehavior extends ScrollBehavior {
821
  /// Creates a MaterialScrollBehavior that decorates [Scrollable]s with
822
  /// [StretchingOverscrollIndicator]s and [Scrollbar]s based on the current
823
  /// platform and provided [ScrollableDetails].
824
  ///
825 826 827 828 829 830
  /// [ThemeData.useMaterial3] specifies the
  /// overscroll indicator that is used on [TargetPlatform.android], which
  /// defaults to true, resulting in a [StretchingOverscrollIndicator]. Setting
  /// [ThemeData.useMaterial3] to false will instead use a
  /// [GlowingOverscrollIndicator].
  const MaterialScrollBehavior();
831 832 833 834

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

835
  @override
836 837
  Widget buildScrollbar(BuildContext context, Widget child, ScrollableDetails details) {
    // When modifying this function, consider modifying the implementation in
838
    // the base class ScrollBehavior as well.
839 840
    switch (axisDirectionToAxis(details.direction)) {
      case Axis.horizontal:
841
        return child;
842 843 844 845 846
      case Axis.vertical:
        switch (getPlatform(context)) {
          case TargetPlatform.linux:
          case TargetPlatform.macOS:
          case TargetPlatform.windows:
847
            assert(details.controller != null);
848 849
            return Scrollbar(
              controller: details.controller,
850
              child: child,
851 852 853 854 855 856 857
            );
          case TargetPlatform.android:
          case TargetPlatform.fuchsia:
          case TargetPlatform.iOS:
            return child;
        }
    }
858 859 860
  }

  @override
861
  Widget buildOverscrollIndicator(BuildContext context, Widget child, ScrollableDetails details) {
862
    // When modifying this function, consider modifying the implementation in
863
    // the base class ScrollBehavior as well.
864 865 866
    final AndroidOverscrollIndicator indicator = Theme.of(context).useMaterial3
        ? AndroidOverscrollIndicator.stretch
        : AndroidOverscrollIndicator.glow;
867 868
    switch (getPlatform(context)) {
      case TargetPlatform.iOS:
869
      case TargetPlatform.linux:
870
      case TargetPlatform.macOS:
871
      case TargetPlatform.windows:
872 873
        return child;
      case TargetPlatform.android:
874
        switch (indicator) {
875 876 877
          case AndroidOverscrollIndicator.stretch:
            return StretchingOverscrollIndicator(
              axisDirection: details.direction,
878
              clipBehavior: details.clipBehavior ?? Clip.hardEdge,
879 880 881
              child: child,
            );
          case AndroidOverscrollIndicator.glow:
882
            break;
883
        }
884
      case TargetPlatform.fuchsia:
885
        break;
886
    }
887 888 889 890 891 892
    return GlowingOverscrollIndicator(
      axisDirection: details.direction,
      color: Theme.of(context).colorScheme.secondary,
      child: child,
    );
  }
893 894
}

895
class _MaterialAppState extends State<MaterialApp> {
896
  late HeroController _heroController;
897

898
  bool get _usesRouter => widget.routerDelegate != null || widget.routerConfig != null;
899

900 901 902
  @override
  void initState() {
    super.initState();
903
    _heroController = MaterialApp.createMaterialHeroController();
904 905
  }

906 907 908 909 910 911
  @override
  void dispose() {
    _heroController.dispose();
    super.dispose();
  }

912 913 914 915 916
  // 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.
917 918 919 920 921 922 923
  Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates {
    return <LocalizationsDelegate<dynamic>>[
      if (widget.localizationsDelegates != null)
        ...widget.localizationsDelegates!,
      DefaultMaterialLocalizations.delegate,
      DefaultCupertinoLocalizations.delegate,
    ];
924 925
  }

926 927 928 929
  Widget _inspectorSelectButtonBuilder(BuildContext context, VoidCallback onPressed) {
    return FloatingActionButton(
      onPressed: onPressed,
      mini: true,
930
      child: const Icon(Icons.search),
931 932 933
    );
  }

934 935
  ThemeData _themeBuilder(BuildContext context) {
    ThemeData? theme;
936 937 938 939 940 941 942 943 944 945 946 947 948 949
    // 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);
    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();
950 951 952 953 954
    return theme;
  }

  Widget _materialBuilder(BuildContext context, Widget? child) {
    final ThemeData theme = _themeBuilder(context);
955 956
    final Color effectiveSelectionColor = theme.textSelectionTheme.selectionColor ?? theme.colorScheme.primary.withOpacity(0.40);
    final Color effectiveCursorColor = theme.textSelectionTheme.cursorColor ?? theme.colorScheme.primary;
957

958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991
    Widget childWidget = child ?? const SizedBox.shrink();

    if (widget.themeAnimationStyle != AnimationStyle.noAnimation) {
      if (widget.builder != null) {
        childWidget = 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);
          },
        );
      }
      childWidget = AnimatedTheme(
        data: theme,
        duration: widget.themeAnimationStyle?.duration ?? widget.themeAnimationDuration,
        curve: widget.themeAnimationStyle?.curve ?? widget.themeAnimationCurve,
        child: childWidget,
      );
    } else {
      childWidget = Theme(
        data: theme,
        child: childWidget,
      );
    }

992 993
    return ScaffoldMessenger(
      key: widget.scaffoldMessengerKey,
994 995 996
      child: DefaultSelectionStyle(
        selectionColor: effectiveSelectionColor,
        cursorColor: effectiveCursorColor,
997
        child: childWidget,
998
      ),
999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012
    );
  }

  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(
1013
        key: GlobalObjectKey(this),
1014
        routeInformationProvider: widget.routeInformationProvider,
1015 1016 1017
        routeInformationParser: widget.routeInformationParser,
        routerDelegate: widget.routerDelegate,
        routerConfig: widget.routerConfig,
1018 1019
        backButtonDispatcher: widget.backButtonDispatcher,
        builder: _materialBuilder,
1020 1021 1022
        title: widget.title,
        onGenerateTitle: widget.onGenerateTitle,
        textStyle: _errorTextStyle,
1023
        color: materialColor,
1024 1025 1026 1027 1028 1029 1030 1031 1032 1033
        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,
1034
        inspectorSelectButtonBuilder: _inspectorSelectButtonBuilder,
1035 1036
        shortcuts: widget.shortcuts,
        actions: widget.actions,
1037
        restorationScopeId: widget.restorationScopeId,
1038 1039 1040 1041 1042 1043
      );
    }

    return WidgetsApp(
      key: GlobalObjectKey(this),
      navigatorKey: widget.navigatorKey,
1044
      navigatorObservers: widget.navigatorObservers!,
1045 1046 1047 1048
      pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) {
        return MaterialPageRoute<T>(settings: settings, builder: builder);
      },
      home: widget.home,
1049
      routes: widget.routes!,
1050 1051 1052 1053
      initialRoute: widget.initialRoute,
      onGenerateRoute: widget.onGenerateRoute,
      onGenerateInitialRoutes: widget.onGenerateInitialRoutes,
      onUnknownRoute: widget.onUnknownRoute,
1054
      onNavigationNotification: widget.onNavigationNotification,
1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072
      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,
1073
      restorationScopeId: widget.restorationScopeId,
1074
    );
1075 1076 1077 1078 1079
  }

  @override
  Widget build(BuildContext context) {
    Widget result = _buildWidgetApp(context);
1080 1081 1082
    result = Focus(
      canRequestFocus: false,
      onKey: (FocusNode node, RawKeyEvent event) {
1083
        if (event is! RawKeyDownEvent || event.logicalKey != LogicalKeyboardKey.escape) {
1084
          return KeyEventResult.ignored;
1085
        }
1086 1087 1088 1089
        return Tooltip.dismissAllToolTips() ? KeyEventResult.handled : KeyEventResult.ignored;
      },
      child: result,
    );
Ian Hickson's avatar
Ian Hickson committed
1090
    assert(() {
1091
      if (widget.debugShowMaterialGrid) {
1092
        result = GridPaper(
Ian Hickson's avatar
Ian Hickson committed
1093 1094
          color: const Color(0xE0F9BBE0),
          interval: 8.0,
1095 1096
          subdivisions: 1,
          child: result,
Ian Hickson's avatar
Ian Hickson committed
1097 1098 1099
        );
      }
      return true;
1100
    }());
1101

1102
    return ScrollConfiguration(
1103
      behavior: widget.scrollBehavior ?? const MaterialScrollBehavior(),
1104 1105 1106
      child: HeroControllerScope(
        controller: _heroController,
        child: result,
1107
      ),
1108
    );
1109
  }
1110
}