// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:collection' show HashMap; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'actions.dart'; import 'banner.dart'; import 'basic.dart'; import 'binding.dart'; import 'default_text_editing_shortcuts.dart'; import 'focus_scope.dart'; import 'focus_traversal.dart'; import 'framework.dart'; import 'localizations.dart'; import 'media_query.dart'; import 'navigator.dart'; import 'pages.dart'; import 'performance_overlay.dart'; import 'restoration.dart'; import 'router.dart'; import 'scrollable.dart'; import 'semantics_debugger.dart'; import 'shared_app_data.dart'; import 'shortcuts.dart'; import 'tap_region.dart'; import 'text.dart'; import 'title.dart'; import 'widget_inspector.dart'; export 'dart:ui' show Locale; // Examples can assume: // late Widget myWidget; /// The signature of [WidgetsApp.localeListResolutionCallback]. /// /// A [LocaleListResolutionCallback] is responsible for computing the locale of the app's /// [Localizations] object when the app starts and when user changes the list of /// locales for the device. /// /// The [locales] list is the device's preferred locales when the app started, or the /// device's preferred locales the user selected after the app was started. This list /// is in order of preference. If this list is null or empty, then Flutter has not yet /// received the locale information from the platform. The [supportedLocales] parameter /// is just the value of [WidgetsApp.supportedLocales]. /// /// See also: /// /// * [LocaleResolutionCallback], which takes only one default locale (instead of a list) /// and is attempted only after this callback fails or is null. [LocaleListResolutionCallback] /// is recommended over [LocaleResolutionCallback]. typedef LocaleListResolutionCallback = Locale? Function(List<Locale>? locales, Iterable<Locale> supportedLocales); /// {@template flutter.widgets.LocaleResolutionCallback} /// The signature of [WidgetsApp.localeResolutionCallback]. /// /// It is recommended to provide a [LocaleListResolutionCallback] instead of a /// [LocaleResolutionCallback] when possible, as [LocaleResolutionCallback] only /// receives a subset of the information provided in [LocaleListResolutionCallback]. /// /// A [LocaleResolutionCallback] is responsible for computing the locale of the app's /// [Localizations] object when the app starts and when user changes the default /// locale for the device after [LocaleListResolutionCallback] fails or is not provided. /// /// This callback is also used if the app is created with a specific locale using /// the [WidgetsApp.new] `locale` parameter. /// /// The [locale] is either the value of [WidgetsApp.locale], or the device's default /// locale when the app started, or the device locale the user selected after the app /// was started. The default locale is the first locale in the list of preferred /// locales. If [locale] is null, then Flutter has not yet received the locale /// information from the platform. The [supportedLocales] parameter is just the value of /// [WidgetsApp.supportedLocales]. /// /// See also: /// /// * [LocaleListResolutionCallback], which takes a list of preferred locales (instead of one locale). /// Resolutions by [LocaleListResolutionCallback] take precedence over [LocaleResolutionCallback]. /// {@endtemplate} typedef LocaleResolutionCallback = Locale? Function(Locale? locale, Iterable<Locale> supportedLocales); /// The default locale resolution algorithm. /// /// Custom resolution algorithms can be provided through /// [WidgetsApp.localeListResolutionCallback] or /// [WidgetsApp.localeResolutionCallback]. /// /// When no custom locale resolution algorithms are provided or if both fail /// to resolve, Flutter will default to calling this algorithm. /// /// This algorithm prioritizes speed at the cost of slightly less appropriate /// resolutions for edge cases. /// /// This algorithm will resolve to the earliest preferred locale that /// matches the most fields, prioritizing in the order of perfect match, /// languageCode+countryCode, languageCode+scriptCode, languageCode-only. /// /// In the case where a locale is matched by languageCode-only and is not the /// default (first) locale, the next preferred locale with a /// perfect match can supersede the languageCode-only match if it exists. /// /// When a preferredLocale matches more than one supported locale, it will /// resolve to the first matching locale listed in the supportedLocales. /// /// When all preferred locales have been exhausted without a match, the first /// countryCode only match will be returned. /// /// When no match at all is found, the first (default) locale in /// [supportedLocales] will be returned. /// /// To summarize, the main matching priority is: /// /// 1. [Locale.languageCode], [Locale.scriptCode], and [Locale.countryCode] /// 2. [Locale.languageCode] and [Locale.scriptCode] only /// 3. [Locale.languageCode] and [Locale.countryCode] only /// 4. [Locale.languageCode] only (with caveats, see above) /// 5. [Locale.countryCode] only when all [preferredLocales] fail to match /// 6. Returns the first element of [supportedLocales] as a fallback /// /// This algorithm does not take language distance (how similar languages are to each other) /// into account, and will not handle edge cases such as resolving `de` to `fr` rather than `zh` /// when `de` is not supported and `zh` is listed before `fr` (German is closer to French /// than Chinese). Locale basicLocaleListResolution(List<Locale>? preferredLocales, Iterable<Locale> supportedLocales) { // preferredLocales can be null when called before the platform has had a chance to // initialize the locales. Platforms without locale passing support will provide an empty list. // We default to the first supported locale in these cases. if (preferredLocales == null || preferredLocales.isEmpty) { return supportedLocales.first; } // Hash the supported locales because apps can support many locales and would // be expensive to search through them many times. final Map<String, Locale> allSupportedLocales = HashMap<String, Locale>(); final Map<String, Locale> languageAndCountryLocales = HashMap<String, Locale>(); final Map<String, Locale> languageAndScriptLocales = HashMap<String, Locale>(); final Map<String, Locale> languageLocales = HashMap<String, Locale>(); final Map<String?, Locale> countryLocales = HashMap<String?, Locale>(); for (final Locale locale in supportedLocales) { allSupportedLocales['${locale.languageCode}_${locale.scriptCode}_${locale.countryCode}'] ??= locale; languageAndScriptLocales['${locale.languageCode}_${locale.scriptCode}'] ??= locale; languageAndCountryLocales['${locale.languageCode}_${locale.countryCode}'] ??= locale; languageLocales[locale.languageCode] ??= locale; countryLocales[locale.countryCode] ??= locale; } // Since languageCode-only matches are possibly low quality, we don't return // it instantly when we find such a match. We check to see if the next // preferred locale in the list has a high accuracy match, and only return // the languageCode-only match when a higher accuracy match in the next // preferred locale cannot be found. Locale? matchesLanguageCode; Locale? matchesCountryCode; // Loop over user's preferred locales for (int localeIndex = 0; localeIndex < preferredLocales.length; localeIndex += 1) { final Locale userLocale = preferredLocales[localeIndex]; // Look for perfect match. if (allSupportedLocales.containsKey('${userLocale.languageCode}_${userLocale.scriptCode}_${userLocale.countryCode}')) { return userLocale; } // Look for language+script match. if (userLocale.scriptCode != null) { final Locale? match = languageAndScriptLocales['${userLocale.languageCode}_${userLocale.scriptCode}']; if (match != null) { return match; } } // Look for language+country match. if (userLocale.countryCode != null) { final Locale? match = languageAndCountryLocales['${userLocale.languageCode}_${userLocale.countryCode}']; if (match != null) { return match; } } // If there was a languageCode-only match in the previous iteration's higher // ranked preferred locale, we return it if the current userLocale does not // have a better match. if (matchesLanguageCode != null) { return matchesLanguageCode; } // Look and store language-only match. Locale? match = languageLocales[userLocale.languageCode]; if (match != null) { matchesLanguageCode = match; // Since first (default) locale is usually highly preferred, we will allow // a languageCode-only match to be instantly matched. If the next preferred // languageCode is the same, we defer hastily returning until the next iteration // since at worst it is the same and at best an improved match. if (localeIndex == 0 && !(localeIndex + 1 < preferredLocales.length && preferredLocales[localeIndex + 1].languageCode == userLocale.languageCode)) { return matchesLanguageCode; } } // countryCode-only match. When all else except default supported locale fails, // attempt to match by country only, as a user is likely to be familiar with a // language from their listed country. if (matchesCountryCode == null && userLocale.countryCode != null) { match = countryLocales[userLocale.countryCode]; if (match != null) { matchesCountryCode = match; } } } // When there is no languageCode-only match. Fallback to matching countryCode only. Country // fallback only applies on iOS. When there is no countryCode-only match, we return first // supported locale. final Locale resolvedLocale = matchesLanguageCode ?? matchesCountryCode ?? supportedLocales.first; return resolvedLocale; } /// The signature of [WidgetsApp.onGenerateTitle]. /// /// Used to generate a value for the app's [Title.title], which the device uses /// to identify the app for the user. The `context` includes the [WidgetsApp]'s /// [Localizations] widget so that this method can be used to produce a /// localized title. /// /// This function must not return null. typedef GenerateAppTitle = String Function(BuildContext context); /// The signature of [WidgetsApp.pageRouteBuilder]. /// /// Creates a [PageRoute] using the given [RouteSettings] and [WidgetBuilder]. typedef PageRouteFactory = PageRoute<T> Function<T>(RouteSettings settings, WidgetBuilder builder); /// The signature of [WidgetsApp.onGenerateInitialRoutes]. /// /// Creates a series of one or more initial routes. typedef InitialRouteListFactory = List<Route<dynamic>> Function(String initialRoute); /// A convenience widget that wraps a number of widgets that are commonly /// required for an application. /// /// One of the primary roles that [WidgetsApp] provides is binding the system /// back button to popping the [Navigator] or quitting the application. /// /// It is used by both [MaterialApp] and [CupertinoApp] to implement base /// functionality for an app. /// /// Builds a [MediaQuery] using [MediaQuery.fromWindow]. To use an inherited /// [MediaQuery] instead, set [useInheritedMediaQuery] to true. /// /// Find references to many of the widgets that [WidgetsApp] wraps in the "See /// also" section. /// /// See also: /// /// * [CheckedModeBanner], which displays a [Banner] saying "DEBUG" when /// running in debug mode. /// * [DefaultTextStyle], the text style to apply to descendant [Text] widgets /// without an explicit style. /// * [MediaQuery], which establishes a subtree in which media queries resolve /// to a [MediaQueryData]. /// * [MediaQuery.fromWindow], which builds a [MediaQuery] with data derived /// from [WidgetsBinding.window]. /// * [Localizations], which defines the [Locale] for its `child`. /// * [Title], a widget that describes this app in the operating system. /// * [Navigator], a widget that manages a set of child widgets with a stack /// discipline. /// * [Overlay], a widget that manages a [Stack] of entries that can be managed /// independently. /// * [SemanticsDebugger], a widget that visualizes the semantics for the child. class WidgetsApp extends StatefulWidget { /// Creates a widget that wraps a number of widgets that are commonly /// required for an application. /// /// The boolean arguments, [color], and [navigatorObservers] must not be null. /// /// Most callers will want to use the [home] or [routes] parameters, or both. /// The [home] parameter is a convenience for the following [routes] map: /// /// ```dart /// <String, WidgetBuilder>{ '/': (BuildContext context) => myWidget } /// ``` /// /// It is possible to specify both [home] and [routes], but only if [routes] does /// _not_ contain an entry for `'/'`. Conversely, if [home] is omitted, [routes] /// _must_ contain an entry for `'/'`. /// /// If [home] or [routes] are not null, the routing implementation needs to know how /// appropriately build [PageRoute]s. This can be achieved by supplying the /// [pageRouteBuilder] parameter. The [pageRouteBuilder] is used by [MaterialApp] /// and [CupertinoApp] to create [MaterialPageRoute]s and [CupertinoPageRoute], /// respectively. /// /// The [builder] parameter is designed to provide the ability to wrap the visible /// content of the app in some other widget. It is recommended that you use [home] /// rather than [builder] if you intend to only display a single route in your app. /// /// [WidgetsApp] is also possible to provide a custom implementation of routing via the /// [onGenerateRoute] and [onUnknownRoute] parameters. These parameters correspond /// to [Navigator.onGenerateRoute] and [Navigator.onUnknownRoute]. If [home], [routes], /// and [builder] are null, or if they fail to create a requested route, /// [onGenerateRoute] will be invoked. If that fails, [onUnknownRoute] will be invoked. /// /// The [pageRouteBuilder] is called to create a [PageRoute] that wraps newly built routes. /// If the [builder] is non-null and the [onGenerateRoute] argument is null, then the /// [builder] will be provided only with the context and the child widget, whereas /// the [pageRouteBuilder] will be provided with [RouteSettings]; in that configuration, /// the [navigatorKey], [onUnknownRoute], [navigatorObservers], and /// [initialRoute] properties must have their default values, as they will have no effect. /// /// The `supportedLocales` argument must be a list of one or more elements. /// By default supportedLocales is `[const Locale('en', 'US')]`. /// /// {@tool dartpad} /// This sample shows a basic Flutter application using [WidgetsApp]. /// /// ** See code in examples/api/lib/widgets/app/widgets_app.widgets_app.0.dart ** /// {@end-tool} WidgetsApp({ // can't be const because the asserts use methods on Iterable :-( super.key, this.navigatorKey, this.onGenerateRoute, this.onGenerateInitialRoutes, this.onUnknownRoute, List<NavigatorObserver> this.navigatorObservers = const <NavigatorObserver>[], this.initialRoute, this.pageRouteBuilder, this.home, Map<String, WidgetBuilder> this.routes = const <String, WidgetBuilder>{}, this.builder, this.title = '', this.onGenerateTitle, this.textStyle, required this.color, this.locale, this.localizationsDelegates, this.localeListResolutionCallback, this.localeResolutionCallback, this.supportedLocales = const <Locale>[Locale('en', 'US')], this.showPerformanceOverlay = false, this.checkerboardRasterCacheImages = false, this.checkerboardOffscreenLayers = false, this.showSemanticsDebugger = false, this.debugShowWidgetInspector = false, this.debugShowCheckedModeBanner = true, this.inspectorSelectButtonBuilder, this.shortcuts, this.actions, this.restorationScopeId, this.useInheritedMediaQuery = false, }) : assert(navigatorObservers != null), assert(routes != null), assert( home == null || onGenerateInitialRoutes == null, 'If onGenerateInitialRoutes is specified, the home argument will be ' 'redundant.', ), 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( builder != null || 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, ' 'or the builder property must be specified, ' 'because otherwise there is nothing to fall back on if the ' 'app is started with an intent that specifies an unknown route.', ), 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( builder != null || onGenerateRoute != null || pageRouteBuilder != null, 'If neither builder nor onGenerateRoute are provided, the ' 'pageRouteBuilder must be specified so that the default handler ' 'will know what kind of PageRoute transition to build.', ), assert(title != null), assert(color != null), assert(supportedLocales != null && supportedLocales.isNotEmpty), assert(showPerformanceOverlay != null), assert(checkerboardRasterCacheImages != null), assert(checkerboardOffscreenLayers != null), assert(showSemanticsDebugger != null), assert(debugShowCheckedModeBanner != null), assert(debugShowWidgetInspector != null), routeInformationProvider = null, routeInformationParser = null, routerDelegate = null, backButtonDispatcher = null, routerConfig = null; /// Creates a [WidgetsApp] that uses the [Router] instead of a [Navigator]. /// /// {@template flutter.widgets.WidgetsApp.router} /// If the [routerConfig] is provided, the other router related delegates, /// [routeInformationParser], [routeInformationProvider], [routerDelegate], /// and [backButtonDispatcher], must all be null. /// {@endtemplate} WidgetsApp.router({ super.key, this.routeInformationProvider, this.routeInformationParser, this.routerDelegate, this.routerConfig, this.backButtonDispatcher, this.builder, this.title = '', this.onGenerateTitle, this.textStyle, required this.color, this.locale, this.localizationsDelegates, this.localeListResolutionCallback, this.localeResolutionCallback, this.supportedLocales = const <Locale>[Locale('en', 'US')], this.showPerformanceOverlay = false, this.checkerboardRasterCacheImages = false, this.checkerboardOffscreenLayers = false, this.showSemanticsDebugger = false, this.debugShowWidgetInspector = false, this.debugShowCheckedModeBanner = true, this.inspectorSelectButtonBuilder, this.shortcuts, this.actions, this.restorationScopeId, this.useInheritedMediaQuery = false, }) : assert((){ if (routerConfig != null) { assert( (routeInformationProvider ?? routeInformationParser ?? routerDelegate ?? backButtonDispatcher) == null, 'If the routerConfig is provided, all the other router delegates must not be provided', ); return true; } assert(routerDelegate != null, 'Either one of routerDelegate or routerConfig must be provided'); assert( routeInformationProvider == null || routeInformationParser != null, 'If routeInformationProvider is provided, routeInformationParser must also be provided', ); return true; }()), assert(title != null), assert(color != null), assert(supportedLocales != null && supportedLocales.isNotEmpty), assert(showPerformanceOverlay != null), assert(checkerboardRasterCacheImages != null), assert(checkerboardOffscreenLayers != null), assert(showSemanticsDebugger != null), assert(debugShowCheckedModeBanner != null), assert(debugShowWidgetInspector != null), navigatorObservers = null, navigatorKey = null, onGenerateRoute = null, pageRouteBuilder = null, home = null, onGenerateInitialRoutes = null, onUnknownRoute = null, routes = null, initialRoute = null; /// {@template flutter.widgets.widgetsApp.navigatorKey} /// 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. /// /// The [Navigator] is only built if [onGenerateRoute] is not null; if it is /// null, [navigatorKey] must also be null. /// {@endtemplate} final GlobalKey<NavigatorState>? navigatorKey; /// {@template flutter.widgets.widgetsApp.onGenerateRoute} /// The route generator callback used when the app is navigated to a /// named route. /// /// If this returns null when building the routes to handle the specified /// [initialRoute], then all the routes are discarded and /// [Navigator.defaultRouteName] is used instead (`/`). See [initialRoute]. /// /// During normal app operation, the [onGenerateRoute] callback will only be /// applied to route names pushed by the application, and so should never /// return null. /// /// This is used if [routes] does not contain the requested route. /// /// 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. /// {@endtemplate} /// /// If this property is not set, either the [routes] or [home] properties must /// be set, and the [pageRouteBuilder] must also be set so that the /// default handler will know what routes and [PageRoute]s to build. final RouteFactory? onGenerateRoute; /// {@template flutter.widgets.widgetsApp.onGenerateInitialRoutes} /// The routes generator callback used for generating initial routes if /// [initialRoute] is provided. /// /// If this property is not set, the underlying /// [Navigator.onGenerateInitialRoutes] will default to /// [Navigator.defaultGenerateInitialRoutes]. /// {@endtemplate} final InitialRouteListFactory? onGenerateInitialRoutes; /// The [PageRoute] generator callback used when the app is navigated to a /// named route. /// /// A [PageRoute] represents the page in a [Navigator], so that it can /// correctly animate between pages, and to represent the "return value" of /// a route (e.g. which button a user selected in a modal dialog). /// /// This callback can be used, for example, to specify that a [MaterialPageRoute] /// or a [CupertinoPageRoute] should be used for building page transitions. /// /// The [PageRouteFactory] type is generic, meaning the provided function must /// itself be generic. For example (with special emphasis on the `<T>` at the /// start of the closure): /// /// ```dart /// pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) => PageRouteBuilder<T>( /// settings: settings, /// pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) => builder(context), /// ), /// ``` final PageRouteFactory? pageRouteBuilder; /// {@template flutter.widgets.widgetsApp.routeInformationParser} /// A delegate to parse the route information from the /// [routeInformationProvider] into a generic data type to be processed by /// the [routerDelegate] at a later stage. /// /// This object will be used by the underlying [Router]. /// /// The generic type `T` must match the generic type of the [routerDelegate]. /// /// See also: /// /// * [Router.routeInformationParser], which receives this object when this /// widget builds the [Router]. /// {@endtemplate} final RouteInformationParser<Object>? routeInformationParser; /// {@template flutter.widgets.widgetsApp.routerDelegate} /// A delegate that configures a widget, typically a [Navigator], with /// parsed result from the [routeInformationParser]. /// /// This object will be used by the underlying [Router]. /// /// The generic type `T` must match the generic type of the /// [routeInformationParser]. /// /// See also: /// /// * [Router.routerDelegate], which receives this object when this widget /// builds the [Router]. /// {@endtemplate} final RouterDelegate<Object>? routerDelegate; /// {@template flutter.widgets.widgetsApp.backButtonDispatcher} /// A delegate that decide whether to handle the Android back button intent. /// /// This object will be used by the underlying [Router]. /// /// If this is not provided, the widgets app will create a /// [RootBackButtonDispatcher] by default. /// /// See also: /// /// * [Router.backButtonDispatcher], which receives this object when this /// widget builds the [Router]. /// {@endtemplate} final BackButtonDispatcher? backButtonDispatcher; /// {@template flutter.widgets.widgetsApp.routeInformationProvider} /// A object that provides route information through the /// [RouteInformationProvider.value] and notifies its listener when its value /// changes. /// /// This object will be used by the underlying [Router]. /// /// If this is not provided, the widgets app will create a /// [PlatformRouteInformationProvider] with initial route name equal to the /// [dart:ui.PlatformDispatcher.defaultRouteName] by default. /// /// See also: /// /// * [Router.routeInformationProvider], which receives this object when this /// widget builds the [Router]. /// {@endtemplate} final RouteInformationProvider? routeInformationProvider; /// {@template flutter.widgets.widgetsApp.routerConfig} /// An object to configure the underlying [Router]. /// /// If the [routerConfig] is provided, the other router related delegates, /// [routeInformationParser], [routeInformationProvider], [routerDelegate], /// and [backButtonDispatcher], must all be null. /// /// See also: /// /// * [Router.withConfig], which receives this object when this /// widget builds the [Router]. /// {@endtemplate} final RouterConfig<Object>? routerConfig; /// {@template flutter.widgets.widgetsApp.home} /// The widget for the default route of the app ([Navigator.defaultRouteName], /// which is `/`). /// /// 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. /// /// 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]. /// /// If [home] is specified, then [routes] must not include an entry for `/`, /// as [home] takes its place. /// /// 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, 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 /// app's [Navigator] (if any). /// {@endtemplate} /// /// If this property is set, the [pageRouteBuilder] property must also be set /// so that the default route handler will know what kind of [PageRoute]s to /// build. final Widget? home; /// 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 /// [widgets.WidgetBuilder] is used to construct a [PageRoute] specified by /// [pageRouteBuilder] to perform an appropriate transition, including [Hero] /// animations, to the new route. /// /// {@template flutter.widgets.widgetsApp.routes} /// If the app only has one page, then you can specify it using [home] instead. /// /// 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. /// /// 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. /// /// 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. /// {@endtemplate} /// /// If the routes map is not empty, the [pageRouteBuilder] property must be set /// so that the default route handler will know what kind of [PageRoute]s to /// build. final Map<String, WidgetBuilder>? routes; /// {@template flutter.widgets.widgetsApp.onUnknownRoute} /// Called when [onGenerateRoute] fails to generate a route, except for the /// [initialRoute]. /// /// This callback is typically used for error handling. For example, this /// callback might always generate a "not found" page that describes the route /// that wasn't found. /// /// Unknown routes can arise either from errors in the app or from external /// requests to push routes, such as from Android intents. /// /// 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. /// {@endtemplate} final RouteFactory? onUnknownRoute; /// {@template flutter.widgets.widgetsApp.initialRoute} /// The name of the first route to show, if a [Navigator] is built. /// /// Defaults to [dart:ui.PlatformDispatcher.defaultRouteName], which may be /// overridden by the code that launched the application. /// /// If the route name starts with a slash, then it is treated as a "deep link", /// and before this route is pushed, the routes leading to this one are pushed /// also. For example, if the route was `/a/b/c`, then the app would start /// with the four routes `/`, `/a`, `/a/b`, and `/a/b/c` loaded, in that order. /// Even if the route was just `/a`, the app would start with `/` and `/a` /// loaded. You can use the [onGenerateInitialRoutes] property to override /// this behavior. /// /// Intermediate routes aren't required to exist. In the example above, `/a` /// and `/a/b` could be skipped if they have no matching route. But `/a/b/c` is /// required to have a route, else [initialRoute] is ignored and /// [Navigator.defaultRouteName] is used instead (`/`). This can happen if the /// app is started with an intent that specifies a non-existent route. /// /// 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. /// /// 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. /// /// {@endtemplate} final String? initialRoute; /// {@template flutter.widgets.widgetsApp.navigatorObservers} /// The list of observers for the [Navigator] created for this app. /// /// This list must be replaced by a list of newly-created observers if the /// [navigatorKey] is changed. /// /// The [Navigator] is only built if routes are provided (either via [home], /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not, /// [navigatorObservers] must be the empty list and [builder] must not be null. /// {@endtemplate} final List<NavigatorObserver>? navigatorObservers; /// {@template flutter.widgets.widgetsApp.builder} /// A builder for inserting widgets above the [Navigator] or - when the /// [WidgetsApp.router] constructor is used - above the [Router] but below the /// other widgets created by the [WidgetsApp] widget, or for replacing the /// [Navigator]/[Router] entirely. /// /// For example, from the [BuildContext] passed to this method, the /// [Directionality], [Localizations], [DefaultTextStyle], [MediaQuery], etc, /// are all available. They can also be overridden in a way that impacts all /// the routes in the [Navigator] or [Router]. /// /// This is rarely useful, but can be used in applications that wish to /// override those defaults, e.g. to force the application into right-to-left /// mode despite being in English, or to override the [MediaQuery] metrics /// (e.g. to leave a gap for advertisements shown by a plugin from OEM code). /// /// For specifically overriding the [title] with a value based on the /// [Localizations], consider [onGenerateTitle] instead. /// /// The [builder] callback is passed two arguments, the [BuildContext] (as /// `context`) and a [Navigator] or [Router] widget (as `child`). /// /// If no routes are provided to the regular [WidgetsApp] constructor 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 to the regular [WidgetsApp] constructor using one /// or more of those properties or if the [WidgetsApp.router] constructor is /// used, 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] or [Router] and the routing related properties (i.e. /// [navigatorKey], [home], [routes], [onGenerateRoute], [onUnknownRoute], /// [initialRoute], [navigatorObservers], [routeInformationProvider], /// [backButtonDispatcher], [routerDelegate], and [routeInformationParser]) /// are ignored. /// /// 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, or by the [routerDelegate] /// building one, widgets and APIs such as [Hero], [Navigator.push] and /// [Navigator.pop], will not function. /// {@endtemplate} final TransitionBuilder? builder; /// {@template flutter.widgets.widgetsApp.title} /// A one-line description used by the device to identify the app for the user. /// /// On Android the titles appear above the task manager's app snapshots which are /// displayed when the user presses the "recent apps" button. On iOS this /// value cannot be used. `CFBundleDisplayName` from the app's `Info.plist` is /// referred to instead whenever present, `CFBundleName` otherwise. /// On the web it is used as the page title, which shows up in the browser's list of open tabs. /// /// To provide a localized title instead, use [onGenerateTitle]. /// {@endtemplate} final String title; /// {@template flutter.widgets.widgetsApp.onGenerateTitle} /// If non-null this callback function is called to produce the app's /// title string, otherwise [title] is used. /// /// The [onGenerateTitle] `context` parameter includes the [WidgetsApp]'s /// [Localizations] widget so that this callback can be used to produce a /// localized title. /// /// This callback function must not return null. /// /// The [onGenerateTitle] callback is called each time the [WidgetsApp] /// rebuilds. /// {@endtemplate} final GenerateAppTitle? onGenerateTitle; /// The default text style for [Text] in the application. final TextStyle? textStyle; /// {@template flutter.widgets.widgetsApp.color} /// The primary color to use for the application in the operating system /// interface. /// /// For example, on Android this is the color used for the application in the /// application switcher. /// {@endtemplate} final Color color; /// {@template flutter.widgets.widgetsApp.locale} /// The initial locale for this app's [Localizations] widget is based /// on this value. /// /// If the 'locale' is null then the system's locale value is used. /// /// The value of [Localizations.locale] will equal this locale if /// it matches one of the [supportedLocales]. Otherwise it will be /// the first element of [supportedLocales]. /// {@endtemplate} /// /// See also: /// /// * [localeResolutionCallback], which can override the default /// [supportedLocales] matching algorithm. /// * [localizationsDelegates], which collectively define all of the localized /// resources used by this app. final Locale? locale; /// {@template flutter.widgets.widgetsApp.localizationsDelegates} /// The delegates for this app's [Localizations] widget. /// /// The delegates collectively define all of the localized resources /// for this application's [Localizations] widget. /// {@endtemplate} final Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates; /// {@template flutter.widgets.widgetsApp.localeListResolutionCallback} /// This callback is responsible for choosing the app's locale /// when the app is started, and when the user changes the /// device's locale. /// /// When a [localeListResolutionCallback] is provided, Flutter will first /// attempt to resolve the locale with the provided /// [localeListResolutionCallback]. If the callback or result is null, it will /// fallback to trying the [localeResolutionCallback]. If both /// [localeResolutionCallback] and [localeListResolutionCallback] are left /// null or fail to resolve (return null), basic fallback algorithm will /// be used. /// /// The priority of each available fallback is: /// /// 1. [localeListResolutionCallback] is attempted. /// 2. [localeResolutionCallback] is attempted. /// 3. Flutter's basic resolution algorithm, as described in /// [supportedLocales], is attempted last. /// /// Properly localized projects should provide a more advanced algorithm than /// the basic method from [supportedLocales], as it does not implement a /// complete algorithm (such as the one defined in /// [Unicode TR35](https://unicode.org/reports/tr35/#LanguageMatching)) /// and is optimized for speed at the detriment of some uncommon edge-cases. /// {@endtemplate} /// /// This callback considers the entire list of preferred locales. /// /// This algorithm should be able to handle a null or empty list of preferred locales, /// which indicates Flutter has not yet received locale information from the platform. /// /// See also: /// /// * [MaterialApp.localeListResolutionCallback], which sets the callback of the /// [WidgetsApp] it creates. /// * [basicLocaleListResolution], the default locale resolution algorithm. final LocaleListResolutionCallback? localeListResolutionCallback; /// {@macro flutter.widgets.widgetsApp.localeListResolutionCallback} /// /// This callback considers only the default locale, which is the first locale /// in the preferred locales list. It is preferred to set [localeListResolutionCallback] /// over [localeResolutionCallback] as it provides the full preferred locales list. /// /// This algorithm should be able to handle a null locale, which indicates /// Flutter has not yet received locale information from the platform. /// /// See also: /// /// * [MaterialApp.localeResolutionCallback], which sets the callback of the /// [WidgetsApp] it creates. /// * [basicLocaleListResolution], the default locale resolution algorithm. final LocaleResolutionCallback? localeResolutionCallback; /// {@template flutter.widgets.widgetsApp.supportedLocales} /// The list of locales that this app has been localized for. /// /// By default only the American English locale is supported. Apps should /// configure this list to match the locales they support. /// /// This list must not null. Its default value is just /// `[const Locale('en', 'US')]`. /// /// The order of the list matters. The default locale resolution algorithm, /// [basicLocaleListResolution], attempts to match by the following priority: /// /// 1. [Locale.languageCode], [Locale.scriptCode], and [Locale.countryCode] /// 2. [Locale.languageCode] and [Locale.scriptCode] only /// 3. [Locale.languageCode] and [Locale.countryCode] only /// 4. [Locale.languageCode] only /// 5. [Locale.countryCode] only when all preferred locales fail to match /// 6. Returns the first element of [supportedLocales] as a fallback /// /// When more than one supported locale matches one of these criteria, only /// the first matching locale is returned. /// /// The default locale resolution algorithm can be overridden by providing a /// value for [localeListResolutionCallback]. The provided /// [basicLocaleListResolution] is optimized for speed and does not implement /// a full algorithm (such as the one defined in /// [Unicode TR35](https://unicode.org/reports/tr35/#LanguageMatching)) that /// takes distances between languages into account. /// /// When supporting languages with more than one script, it is recommended /// to specify the [Locale.scriptCode] explicitly. Locales may also be defined without /// [Locale.countryCode] to specify a generic fallback for a particular script. /// /// A fully supported language with multiple scripts should define a generic language-only /// locale (e.g. 'zh'), language+script only locales (e.g. 'zh_Hans' and 'zh_Hant'), /// and any language+script+country locales (e.g. 'zh_Hans_CN'). Fully defining all of /// these locales as supported is not strictly required but allows for proper locale resolution in /// the most number of cases. These locales can be specified with the [Locale.fromSubtags] /// constructor: /// /// ```dart /// // Full Chinese support for CN, TW, and HK /// supportedLocales: <Locale>[ /// const Locale.fromSubtags(languageCode: 'zh'), // generic Chinese 'zh' /// const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'), // generic simplified Chinese 'zh_Hans' /// const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'), // generic traditional Chinese 'zh_Hant' /// const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN'), // 'zh_Hans_CN' /// const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'TW'), // 'zh_Hant_TW' /// const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'HK'), // 'zh_Hant_HK' /// ], /// ``` /// /// Omitting some these fallbacks may result in improperly resolved /// edge-cases, for example, a simplified Chinese user in Taiwan ('zh_Hans_TW') /// may resolve to traditional Chinese if 'zh_Hans' and 'zh_Hans_CN' are /// omitted. /// {@endtemplate} /// /// See also: /// /// * [MaterialApp.supportedLocales], which sets the `supportedLocales` /// of the [WidgetsApp] it creates. /// * [localeResolutionCallback], an app callback that resolves the app's locale /// when the device's locale changes. /// * [localizationsDelegates], which collectively define all of the localized /// resources used by this app. /// * [basicLocaleListResolution], the default locale resolution algorithm. final Iterable<Locale> supportedLocales; /// Turns on a performance overlay. /// /// See also: /// /// * <https://flutter.dev/debugging/#performance-overlay> final bool showPerformanceOverlay; /// Checkerboards raster cache images. /// /// See [PerformanceOverlay.checkerboardRasterCacheImages]. final bool checkerboardRasterCacheImages; /// Checkerboards layers rendered to offscreen bitmaps. /// /// See [PerformanceOverlay.checkerboardOffscreenLayers]. final bool checkerboardOffscreenLayers; /// Turns on an overlay that shows the accessibility information /// reported by the framework. final bool showSemanticsDebugger; /// Turns on an overlay that enables inspecting the widget tree. /// /// The inspector is only available in debug mode as it depends on /// [RenderObject.debugDescribeChildren] which should not be called outside of /// debug mode. final bool debugShowWidgetInspector; /// Builds the widget the [WidgetInspector] uses to switch between view and /// inspect modes. /// /// This lets [MaterialApp] to use a Material Design button to toggle the /// inspector select mode without requiring [WidgetInspector] to depend on the /// material package. final InspectorSelectButtonBuilder? inspectorSelectButtonBuilder; /// {@template flutter.widgets.widgetsApp.debugShowCheckedModeBanner} /// Turns on a little "DEBUG" banner in debug mode to indicate /// that the app is in debug mode. This is on by default (in /// debug mode), to turn it off, set the constructor argument to /// false. In release mode this has no effect. /// /// To get this banner in your application if you're not using /// WidgetsApp, include a [CheckedModeBanner] widget in your app. /// /// This banner is intended to deter people from complaining that your /// app is slow when it's in debug mode. In debug mode, Flutter /// enables a large number of expensive diagnostics to aid in /// development, and so performance in debug mode is not /// representative of what will happen in release mode. /// {@endtemplate} final bool debugShowCheckedModeBanner; /// {@template flutter.widgets.widgetsApp.shortcuts} /// The default map of keyboard shortcuts to intents for the application. /// /// By default, this is set to [WidgetsApp.defaultShortcuts]. /// /// Passing this will not replace [DefaultTextEditingShortcuts]. These can be /// overridden by using a [Shortcuts] widget lower in the widget tree. /// {@endtemplate} /// /// {@tool snippet} /// This example shows how to add a single shortcut for /// [LogicalKeyboardKey.select] to the default shortcuts without needing to /// add your own [Shortcuts] widget. /// /// Alternatively, you could insert a [Shortcuts] widget with just the mapping /// you want to add between the [WidgetsApp] and its child and get the same /// effect. /// /// ```dart /// Widget build(BuildContext context) { /// return WidgetsApp( /// shortcuts: <ShortcutActivator, Intent>{ /// ... WidgetsApp.defaultShortcuts, /// const SingleActivator(LogicalKeyboardKey.select): const ActivateIntent(), /// }, /// color: const Color(0xFFFF0000), /// builder: (BuildContext context, Widget? child) { /// return const Placeholder(); /// }, /// ); /// } /// ``` /// {@end-tool} /// /// {@template flutter.widgets.widgetsApp.shortcuts.seeAlso} /// See also: /// /// * [SingleActivator], which defines shortcut key combination of a single /// key and modifiers, such as "Delete" or "Control+C". /// * The [Shortcuts] widget, which defines a keyboard mapping. /// * The [Actions] widget, which defines the mapping from intent to action. /// * The [Intent] and [Action] classes, which allow definition of new /// actions. /// {@endtemplate} final Map<ShortcutActivator, Intent>? shortcuts; /// {@template flutter.widgets.widgetsApp.actions} /// The default map of intent keys to actions for the application. /// /// By default, this is the output of [WidgetsApp.defaultActions], called with /// [defaultTargetPlatform]. Specifying [actions] for an app overrides the /// default, so if you wish to modify the default [actions], you can call /// [WidgetsApp.defaultActions] and modify the resulting map, passing it as /// the [actions] for this app. You may also add to the bindings, or override /// specific bindings for a widget subtree, by adding your own [Actions] /// widget. /// {@endtemplate} /// /// {@tool snippet} /// This example shows how to add a single action handling an /// [ActivateAction] to the default actions without needing to /// add your own [Actions] widget. /// /// Alternatively, you could insert a [Actions] widget with just the mapping /// you want to add between the [WidgetsApp] and its child and get the same /// effect. /// /// ```dart /// Widget build(BuildContext context) { /// return WidgetsApp( /// actions: <Type, Action<Intent>>{ /// ... WidgetsApp.defaultActions, /// ActivateAction: CallbackAction<Intent>( /// onInvoke: (Intent intent) { /// // Do something here... /// return null; /// }, /// ), /// }, /// color: const Color(0xFFFF0000), /// builder: (BuildContext context, Widget? child) { /// return const Placeholder(); /// }, /// ); /// } /// ``` /// {@end-tool} /// /// {@template flutter.widgets.widgetsApp.actions.seeAlso} /// See also: /// /// * The [shortcuts] parameter, which defines the default set of shortcuts /// for the application. /// * The [Shortcuts] widget, which defines a keyboard mapping. /// * The [Actions] widget, which defines the mapping from intent to action. /// * The [Intent] and [Action] classes, which allow definition of new /// actions. /// {@endtemplate} final Map<Type, Action<Intent>>? actions; /// {@template flutter.widgets.widgetsApp.restorationScopeId} /// The identifier to use for state restoration of this app. /// /// Providing a restoration ID inserts a [RootRestorationScope] into the /// widget hierarchy, which enables state restoration for descendant widgets. /// /// Providing a restoration ID also enables the [Navigator] or [Router] built /// by the [WidgetsApp] to restore its state (i.e. to restore the history /// stack of active [Route]s). See the documentation on [Navigator] for more /// details around state restoration of [Route]s. /// /// See also: /// /// * [RestorationManager], which explains how state restoration works in /// Flutter. /// {@endtemplate} final String? restorationScopeId; /// {@template flutter.widgets.widgetsApp.useInheritedMediaQuery} /// If true, an inherited MediaQuery will be used. If one is not available, /// or this is false, one will be built from the window. /// /// Cannot be null, defaults to false. /// {@endtemplate} final bool useInheritedMediaQuery; /// If true, forces the performance overlay to be visible in all instances. /// /// Used by the `showPerformanceOverlay` observatory extension. static bool showPerformanceOverlayOverride = false; /// If true, forces the widget inspector to be visible. /// /// Used by the `debugShowWidgetInspector` debugging extension. /// /// The inspector allows you to select a location on your device or emulator /// and view what widgets and render objects associated with it. An outline of /// the selected widget and some summary information is shown on device and /// more detailed information is shown in the IDE or Observatory. static bool debugShowWidgetInspectorOverride = false; /// If false, prevents the debug banner from being visible. /// /// Used by the `debugAllowBanner` observatory extension. /// /// This is how `flutter run` turns off the banner when you take a screen shot /// with "s". static bool debugAllowBannerOverride = true; static const Map<ShortcutActivator, Intent> _defaultShortcuts = <ShortcutActivator, Intent>{ // Activation SingleActivator(LogicalKeyboardKey.enter): ActivateIntent(), SingleActivator(LogicalKeyboardKey.numpadEnter): ActivateIntent(), SingleActivator(LogicalKeyboardKey.space): ActivateIntent(), SingleActivator(LogicalKeyboardKey.gameButtonA): ActivateIntent(), // Dismissal SingleActivator(LogicalKeyboardKey.escape): DismissIntent(), // Keyboard traversal. SingleActivator(LogicalKeyboardKey.tab): NextFocusIntent(), SingleActivator(LogicalKeyboardKey.tab, shift: true): PreviousFocusIntent(), SingleActivator(LogicalKeyboardKey.arrowLeft): DirectionalFocusIntent(TraversalDirection.left), SingleActivator(LogicalKeyboardKey.arrowRight): DirectionalFocusIntent(TraversalDirection.right), SingleActivator(LogicalKeyboardKey.arrowDown): DirectionalFocusIntent(TraversalDirection.down), SingleActivator(LogicalKeyboardKey.arrowUp): DirectionalFocusIntent(TraversalDirection.up), // Scrolling SingleActivator(LogicalKeyboardKey.arrowUp, control: true): ScrollIntent(direction: AxisDirection.up), SingleActivator(LogicalKeyboardKey.arrowDown, control: true): ScrollIntent(direction: AxisDirection.down), SingleActivator(LogicalKeyboardKey.arrowLeft, control: true): ScrollIntent(direction: AxisDirection.left), SingleActivator(LogicalKeyboardKey.arrowRight, control: true): ScrollIntent(direction: AxisDirection.right), SingleActivator(LogicalKeyboardKey.pageUp): ScrollIntent(direction: AxisDirection.up, type: ScrollIncrementType.page), SingleActivator(LogicalKeyboardKey.pageDown): ScrollIntent(direction: AxisDirection.down, type: ScrollIncrementType.page), }; // Default shortcuts for the web platform. static const Map<ShortcutActivator, Intent> _defaultWebShortcuts = <ShortcutActivator, Intent>{ // Activation SingleActivator(LogicalKeyboardKey.space): PrioritizedIntents( orderedIntents: <Intent>[ ActivateIntent(), ScrollIntent(direction: AxisDirection.down, type: ScrollIncrementType.page), ], ), // On the web, enter activates buttons, but not other controls. SingleActivator(LogicalKeyboardKey.enter): ButtonActivateIntent(), SingleActivator(LogicalKeyboardKey.numpadEnter): ButtonActivateIntent(), // Dismissal SingleActivator(LogicalKeyboardKey.escape): DismissIntent(), // Keyboard traversal. SingleActivator(LogicalKeyboardKey.tab): NextFocusIntent(), SingleActivator(LogicalKeyboardKey.tab, shift: true): PreviousFocusIntent(), // Scrolling SingleActivator(LogicalKeyboardKey.arrowUp): ScrollIntent(direction: AxisDirection.up), SingleActivator(LogicalKeyboardKey.arrowDown): ScrollIntent(direction: AxisDirection.down), SingleActivator(LogicalKeyboardKey.arrowLeft): ScrollIntent(direction: AxisDirection.left), SingleActivator(LogicalKeyboardKey.arrowRight): ScrollIntent(direction: AxisDirection.right), SingleActivator(LogicalKeyboardKey.pageUp): ScrollIntent(direction: AxisDirection.up, type: ScrollIncrementType.page), SingleActivator(LogicalKeyboardKey.pageDown): ScrollIntent(direction: AxisDirection.down, type: ScrollIncrementType.page), }; // Default shortcuts for the macOS platform. static const Map<ShortcutActivator, Intent> _defaultAppleOsShortcuts = <ShortcutActivator, Intent>{ // Activation SingleActivator(LogicalKeyboardKey.enter): ActivateIntent(), SingleActivator(LogicalKeyboardKey.numpadEnter): ActivateIntent(), SingleActivator(LogicalKeyboardKey.space): ActivateIntent(), // Dismissal SingleActivator(LogicalKeyboardKey.escape): DismissIntent(), // Keyboard traversal SingleActivator(LogicalKeyboardKey.tab): NextFocusIntent(), SingleActivator(LogicalKeyboardKey.tab, shift: true): PreviousFocusIntent(), SingleActivator(LogicalKeyboardKey.arrowLeft): DirectionalFocusIntent(TraversalDirection.left), SingleActivator(LogicalKeyboardKey.arrowRight): DirectionalFocusIntent(TraversalDirection.right), SingleActivator(LogicalKeyboardKey.arrowDown): DirectionalFocusIntent(TraversalDirection.down), SingleActivator(LogicalKeyboardKey.arrowUp): DirectionalFocusIntent(TraversalDirection.up), // Scrolling SingleActivator(LogicalKeyboardKey.arrowUp, meta: true): ScrollIntent(direction: AxisDirection.up), SingleActivator(LogicalKeyboardKey.arrowDown, meta: true): ScrollIntent(direction: AxisDirection.down), SingleActivator(LogicalKeyboardKey.arrowLeft, meta: true): ScrollIntent(direction: AxisDirection.left), SingleActivator(LogicalKeyboardKey.arrowRight, meta: true): ScrollIntent(direction: AxisDirection.right), SingleActivator(LogicalKeyboardKey.pageUp): ScrollIntent(direction: AxisDirection.up, type: ScrollIncrementType.page), SingleActivator(LogicalKeyboardKey.pageDown): ScrollIntent(direction: AxisDirection.down, type: ScrollIncrementType.page), }; /// Generates the default shortcut key bindings based on the /// [defaultTargetPlatform]. /// /// Used by [WidgetsApp] to assign a default value to [WidgetsApp.shortcuts]. static Map<ShortcutActivator, Intent> get defaultShortcuts { if (kIsWeb) { return _defaultWebShortcuts; } switch (defaultTargetPlatform) { case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: return _defaultShortcuts; case TargetPlatform.iOS: case TargetPlatform.macOS: return _defaultAppleOsShortcuts; } } /// The default value of [WidgetsApp.actions]. static Map<Type, Action<Intent>> defaultActions = <Type, Action<Intent>>{ DoNothingIntent: DoNothingAction(), DoNothingAndStopPropagationIntent: DoNothingAction(consumesKey: false), RequestFocusIntent: RequestFocusAction(), NextFocusIntent: NextFocusAction(), PreviousFocusIntent: PreviousFocusAction(), DirectionalFocusIntent: DirectionalFocusAction(), ScrollIntent: ScrollAction(), PrioritizedIntents: PrioritizedAction(), VoidCallbackIntent: VoidCallbackAction(), }; @override State<WidgetsApp> createState() => _WidgetsAppState(); } class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver { // STATE LIFECYCLE // If window.defaultRouteName isn't '/', we should assume it was set // intentionally via `setInitialRoute`, and should override whatever is in // [widget.initialRoute]. String get _initialRouteName => WidgetsBinding.instance.platformDispatcher.defaultRouteName != Navigator.defaultRouteName ? WidgetsBinding.instance.platformDispatcher.defaultRouteName : widget.initialRoute ?? WidgetsBinding.instance.platformDispatcher.defaultRouteName; @override void initState() { super.initState(); _updateRouting(); _locale = _resolveLocales(WidgetsBinding.instance.platformDispatcher.locales, widget.supportedLocales); WidgetsBinding.instance.addObserver(this); } @override void didUpdateWidget(WidgetsApp oldWidget) { super.didUpdateWidget(oldWidget); _updateRouting(oldWidget: oldWidget); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); _defaultRouteInformationProvider?.dispose(); super.dispose(); } void _clearRouterResource() { _defaultRouteInformationProvider?.dispose(); _defaultRouteInformationProvider = null; _defaultBackButtonDispatcher = null; } void _clearNavigatorResource() { _navigator = null; } void _updateRouting({WidgetsApp? oldWidget}) { if (_usesRouterWithDelegates) { assert(!_usesNavigator && !_usesRouterWithConfig); _clearNavigatorResource(); if (widget.routeInformationProvider == null && widget.routeInformationParser != null) { _defaultRouteInformationProvider ??= PlatformRouteInformationProvider( initialRouteInformation: RouteInformation( location: _initialRouteName, ), ); } else { _defaultRouteInformationProvider?.dispose(); _defaultRouteInformationProvider = null; } if (widget.backButtonDispatcher == null) { _defaultBackButtonDispatcher ??= RootBackButtonDispatcher(); } } else if (_usesNavigator) { assert(!_usesRouterWithDelegates && !_usesRouterWithConfig); _clearRouterResource(); if (_navigator == null || widget.navigatorKey != oldWidget!.navigatorKey) { _navigator = widget.navigatorKey ?? GlobalObjectKey<NavigatorState>(this); } assert(_navigator != null); } else { assert(widget.builder != null || _usesRouterWithConfig); assert(!_usesRouterWithDelegates && !_usesNavigator); _clearRouterResource(); _clearNavigatorResource(); } // If we use a navigator, we have a navigator key. assert(_usesNavigator == (_navigator != null)); } bool get _usesRouterWithDelegates => widget.routerDelegate != null; bool get _usesRouterWithConfig => widget.routerConfig != null; bool get _usesNavigator => widget.home != null || (widget.routes?.isNotEmpty ?? false) || widget.onGenerateRoute != null || widget.onUnknownRoute != null; // ROUTER RouteInformationProvider? get _effectiveRouteInformationProvider => widget.routeInformationProvider ?? _defaultRouteInformationProvider; PlatformRouteInformationProvider? _defaultRouteInformationProvider; BackButtonDispatcher get _effectiveBackButtonDispatcher => widget.backButtonDispatcher ?? _defaultBackButtonDispatcher!; RootBackButtonDispatcher? _defaultBackButtonDispatcher; // NAVIGATOR GlobalKey<NavigatorState>? _navigator; Route<dynamic>? _onGenerateRoute(RouteSettings settings) { final String? name = settings.name; final WidgetBuilder? pageContentBuilder = name == Navigator.defaultRouteName && widget.home != null ? (BuildContext context) => widget.home! : widget.routes![name]; if (pageContentBuilder != null) { assert( widget.pageRouteBuilder != null, 'The default onGenerateRoute handler for WidgetsApp must have a ' 'pageRouteBuilder set if the home or routes properties are set.', ); final Route<dynamic> route = widget.pageRouteBuilder!<dynamic>( settings, pageContentBuilder, ); assert(route != null, 'The pageRouteBuilder for WidgetsApp must return a valid non-null Route.'); return route; } if (widget.onGenerateRoute != null) { return widget.onGenerateRoute!(settings); } return null; } Route<dynamic> _onUnknownRoute(RouteSettings settings) { assert(() { if (widget.onUnknownRoute == null) { throw FlutterError( 'Could not find a generator for route $settings in the $runtimeType.\n' 'Make sure your root app widget has provided a way to generate \n' 'this route.\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; }()); final Route<dynamic>? result = widget.onUnknownRoute!(settings); assert(() { if (result == null) { throw 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; }()); return result!; } // On Android: the user has pressed the back button. @override Future<bool> didPopRoute() async { assert(mounted); // The back button dispatcher should handle the pop route if we use a // router. if (_usesRouterWithDelegates) { return false; } final NavigatorState? navigator = _navigator?.currentState; if (navigator == null) { return false; } return navigator.maybePop(); } @override Future<bool> didPushRoute(String route) async { assert(mounted); // The route name provider should handle the push route if we uses a // router. if (_usesRouterWithDelegates) { return false; } final NavigatorState? navigator = _navigator?.currentState; if (navigator == null) { return false; } navigator.pushNamed(route); return true; } // LOCALIZATION /// This is the resolved locale, and is one of the supportedLocales. Locale? _locale; Locale _resolveLocales(List<Locale>? preferredLocales, Iterable<Locale> supportedLocales) { // Attempt to use localeListResolutionCallback. if (widget.localeListResolutionCallback != null) { final Locale? locale = widget.localeListResolutionCallback!(preferredLocales, widget.supportedLocales); if (locale != null) { return locale; } } // localeListResolutionCallback failed, falling back to localeResolutionCallback. if (widget.localeResolutionCallback != null) { final Locale? locale = widget.localeResolutionCallback!( preferredLocales != null && preferredLocales.isNotEmpty ? preferredLocales.first : null, widget.supportedLocales, ); if (locale != null) { return locale; } } // Both callbacks failed, falling back to default algorithm. return basicLocaleListResolution(preferredLocales, supportedLocales); } @override void didChangeLocales(List<Locale>? locales) { final Locale newLocale = _resolveLocales(locales, widget.supportedLocales); if (newLocale != _locale) { setState(() { _locale = newLocale; }); } } // Combine the Localizations for Widgets 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 // WidgetsLocalizations.delegate. Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates { return <LocalizationsDelegate<dynamic>>[ if (widget.localizationsDelegates != null) ...widget.localizationsDelegates!, DefaultWidgetsLocalizations.delegate, ]; } // BUILDER bool _debugCheckLocalizations(Locale appLocale) { assert(() { final Set<Type> unsupportedTypes = _localizationsDelegates.map<Type>((LocalizationsDelegate<dynamic> delegate) => delegate.type).toSet(); for (final LocalizationsDelegate<dynamic> delegate in _localizationsDelegates) { if (!unsupportedTypes.contains(delegate.type)) { continue; } if (delegate.isSupported(appLocale)) { unsupportedTypes.remove(delegate.type); } } if (unsupportedTypes.isEmpty) { return true; } FlutterError.reportError(FlutterErrorDetails( exception: "Warning: This application's locale, $appLocale, is not supported by all of its localization delegates.", library: 'widgets', informationCollector: () => <DiagnosticsNode>[ for (final Type unsupportedType in unsupportedTypes) ErrorDescription( '• A $unsupportedType delegate that supports the $appLocale locale was not found.', ), ErrorSpacer(), if (unsupportedTypes.length == 1 && unsupportedTypes.single.toString() == 'CupertinoLocalizations') // We previously explicitly avoided checking for this class so it's not uncommon for applications // to have omitted importing the required delegate. ...<DiagnosticsNode>[ ErrorHint( 'If the application is built using GlobalMaterialLocalizations.delegate, consider using ' 'GlobalMaterialLocalizations.delegates (plural) instead, as that will automatically declare ' 'the appropriate Cupertino localizations.' ), ErrorSpacer(), ], ErrorHint( 'The declared supported locales for this app are: ${widget.supportedLocales.join(", ")}' ), ErrorSpacer(), ErrorDescription( 'See https://flutter.dev/tutorials/internationalization/ for more ' "information about configuring an app's locale, supportedLocales, " 'and localizationsDelegates parameters.', ), ], )); return true; }()); return true; } @override Widget build(BuildContext context) { Widget? routing; if (_usesRouterWithDelegates) { routing = Router<Object>( restorationScopeId: 'router', routeInformationProvider: _effectiveRouteInformationProvider, routeInformationParser: widget.routeInformationParser, routerDelegate: widget.routerDelegate!, backButtonDispatcher: _effectiveBackButtonDispatcher, ); } else if (_usesNavigator) { assert(_navigator != null); routing = FocusScope( debugLabel: 'Navigator Scope', autofocus: true, child: Navigator( restorationScopeId: 'nav', key: _navigator, initialRoute: _initialRouteName, onGenerateRoute: _onGenerateRoute, onGenerateInitialRoutes: widget.onGenerateInitialRoutes == null ? Navigator.defaultGenerateInitialRoutes : (NavigatorState navigator, String initialRouteName) { return widget.onGenerateInitialRoutes!(initialRouteName); }, onUnknownRoute: _onUnknownRoute, observers: widget.navigatorObservers!, reportsRouteUpdateToEngine: true, ), ); } else if (_usesRouterWithConfig) { routing = Router<Object>.withConfig( restorationScopeId: 'router', config: widget.routerConfig!, ); } Widget result; if (widget.builder != null) { result = Builder( builder: (BuildContext context) { return widget.builder!(context, routing); }, ); } else { assert(routing != null); result = routing!; } if (widget.textStyle != null) { result = DefaultTextStyle( style: widget.textStyle!, child: result, ); } PerformanceOverlay? performanceOverlay; // We need to push a performance overlay if any of the display or checkerboarding // options are set. if (widget.showPerformanceOverlay || WidgetsApp.showPerformanceOverlayOverride) { performanceOverlay = PerformanceOverlay.allEnabled( checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages, checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers, ); } else if (widget.checkerboardRasterCacheImages || widget.checkerboardOffscreenLayers) { performanceOverlay = PerformanceOverlay( checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages, checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers, ); } if (performanceOverlay != null) { result = Stack( children: <Widget>[ result, Positioned(top: 0.0, left: 0.0, right: 0.0, child: performanceOverlay), ], ); } if (widget.showSemanticsDebugger) { result = SemanticsDebugger( child: result, ); } assert(() { if (widget.debugShowWidgetInspector || WidgetsApp.debugShowWidgetInspectorOverride) { result = WidgetInspector( selectButtonBuilder: widget.inspectorSelectButtonBuilder, child: result, ); } if (widget.debugShowCheckedModeBanner && WidgetsApp.debugAllowBannerOverride) { result = CheckedModeBanner( child: result, ); } return true; }()); final Widget title; if (widget.onGenerateTitle != null) { title = Builder( // This Builder exists to provide a context below the Localizations widget. // The onGenerateTitle callback can refer to Localizations via its context // parameter. builder: (BuildContext context) { final String title = widget.onGenerateTitle!(context); assert(title != null, 'onGenerateTitle must return a non-null String'); return Title( title: title, color: widget.color.withOpacity(1.0), child: result, ); }, ); } else { title = Title( title: widget.title, color: widget.color.withOpacity(1.0), child: result, ); } final Locale appLocale = widget.locale != null ? _resolveLocales(<Locale>[widget.locale!], widget.supportedLocales) : _locale!; assert(_debugCheckLocalizations(appLocale)); Widget child = Localizations( locale: appLocale, delegates: _localizationsDelegates.toList(), child: title, ); final MediaQueryData? data = MediaQuery.maybeOf(context); if (!widget.useInheritedMediaQuery || data == null) { child = MediaQuery.fromWindow( child: child, ); } return RootRestorationScope( restorationId: widget.restorationScopeId, child: SharedAppData( child: Shortcuts( debugLabel: '<Default WidgetsApp Shortcuts>', shortcuts: widget.shortcuts ?? WidgetsApp.defaultShortcuts, // DefaultTextEditingShortcuts is nested inside Shortcuts so that it can // fall through to the defaultShortcuts. child: DefaultTextEditingShortcuts( child: Actions( actions: widget.actions ?? WidgetsApp.defaultActions, child: FocusTraversalGroup( policy: ReadingOrderTraversalPolicy(), child: TapRegionSurface( child: ShortcutRegistrar( child: child, ), ), ), ), ), ), ), ); } }