| 1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | /// @docImport 'package:flutter/cupertino.dart'; |
| 6 | /// @docImport 'package:flutter/material.dart'; |
| 7 | /// |
| 8 | /// @docImport 'heroes.dart'; |
| 9 | /// @docImport 'overlay.dart'; |
| 10 | /// @docImport 'view.dart'; |
| 11 | library; |
| 12 | |
| 13 | import 'dart:collection' show HashMap; |
| 14 | |
| 15 | import 'package:flutter/foundation.dart'; |
| 16 | import 'package:flutter/rendering.dart'; |
| 17 | import 'package:flutter/services.dart'; |
| 18 | |
| 19 | import 'actions.dart'; |
| 20 | import 'banner.dart'; |
| 21 | import 'basic.dart'; |
| 22 | import 'binding.dart'; |
| 23 | import 'default_text_editing_shortcuts.dart'; |
| 24 | import 'focus_scope.dart'; |
| 25 | import 'focus_traversal.dart'; |
| 26 | import 'framework.dart'; |
| 27 | import 'localizations.dart'; |
| 28 | import 'media_query.dart'; |
| 29 | import 'navigator.dart'; |
| 30 | import 'notification_listener.dart'; |
| 31 | import 'pages.dart'; |
| 32 | import 'performance_overlay.dart'; |
| 33 | import 'restoration.dart'; |
| 34 | import 'router.dart'; |
| 35 | import 'scrollable_helpers.dart'; |
| 36 | import 'semantics_debugger.dart'; |
| 37 | import 'shared_app_data.dart'; |
| 38 | import 'shortcuts.dart'; |
| 39 | import 'tap_region.dart'; |
| 40 | import 'text.dart'; |
| 41 | import 'title.dart'; |
| 42 | import 'transitions.dart'; |
| 43 | import 'value_listenable_builder.dart'; |
| 44 | import 'widget_inspector.dart'; |
| 45 | |
| 46 | export 'dart:ui' show Locale; |
| 47 | |
| 48 | // Examples can assume: |
| 49 | // late Widget myWidget; |
| 50 | |
| 51 | /// The signature of [WidgetsApp.localeListResolutionCallback]. |
| 52 | /// |
| 53 | /// A [LocaleListResolutionCallback] is responsible for computing the locale of the app's |
| 54 | /// [Localizations] object when the app starts and when user changes the list of |
| 55 | /// locales for the device. |
| 56 | /// |
| 57 | /// The [locales] list is the device's preferred locales when the app started, or the |
| 58 | /// device's preferred locales the user selected after the app was started. This list |
| 59 | /// is in order of preference. If this list is null or empty, then Flutter has not yet |
| 60 | /// received the locale information from the platform. The [supportedLocales] parameter |
| 61 | /// is just the value of [WidgetsApp.supportedLocales]. |
| 62 | /// |
| 63 | /// See also: |
| 64 | /// |
| 65 | /// * [LocaleResolutionCallback], which takes only one default locale (instead of a list) |
| 66 | /// and is attempted only after this callback fails or is null. [LocaleListResolutionCallback] |
| 67 | /// is recommended over [LocaleResolutionCallback]. |
| 68 | typedef LocaleListResolutionCallback = |
| 69 | Locale? Function(List<Locale>? locales, Iterable<Locale> supportedLocales); |
| 70 | |
| 71 | /// {@template flutter.widgets.LocaleResolutionCallback} |
| 72 | /// The signature of [WidgetsApp.localeResolutionCallback]. |
| 73 | /// |
| 74 | /// It is recommended to provide a [LocaleListResolutionCallback] instead of a |
| 75 | /// [LocaleResolutionCallback] when possible, as [LocaleResolutionCallback] only |
| 76 | /// receives a subset of the information provided in [LocaleListResolutionCallback]. |
| 77 | /// |
| 78 | /// A [LocaleResolutionCallback] is responsible for computing the locale of the app's |
| 79 | /// [Localizations] object when the app starts and when user changes the default |
| 80 | /// locale for the device after [LocaleListResolutionCallback] fails or is not provided. |
| 81 | /// |
| 82 | /// This callback is also used if the app is created with a specific locale using |
| 83 | /// the [WidgetsApp.new] `locale` parameter. |
| 84 | /// |
| 85 | /// The [locale] is either the value of [WidgetsApp.locale], or the device's default |
| 86 | /// locale when the app started, or the device locale the user selected after the app |
| 87 | /// was started. The default locale is the first locale in the list of preferred |
| 88 | /// locales. If [locale] is null, then Flutter has not yet received the locale |
| 89 | /// information from the platform. The [supportedLocales] parameter is just the value of |
| 90 | /// [WidgetsApp.supportedLocales]. |
| 91 | /// |
| 92 | /// See also: |
| 93 | /// |
| 94 | /// * [LocaleListResolutionCallback], which takes a list of preferred locales (instead of one locale). |
| 95 | /// Resolutions by [LocaleListResolutionCallback] take precedence over [LocaleResolutionCallback]. |
| 96 | /// {@endtemplate} |
| 97 | typedef LocaleResolutionCallback = |
| 98 | Locale? Function(Locale? locale, Iterable<Locale> supportedLocales); |
| 99 | |
| 100 | /// The default locale resolution algorithm. |
| 101 | /// |
| 102 | /// Custom resolution algorithms can be provided through |
| 103 | /// [WidgetsApp.localeListResolutionCallback] or |
| 104 | /// [WidgetsApp.localeResolutionCallback]. |
| 105 | /// |
| 106 | /// When no custom locale resolution algorithms are provided or if both fail |
| 107 | /// to resolve, Flutter will default to calling this algorithm. |
| 108 | /// |
| 109 | /// This algorithm prioritizes speed at the cost of slightly less appropriate |
| 110 | /// resolutions for edge cases. |
| 111 | /// |
| 112 | /// This algorithm will resolve to the earliest preferred locale that |
| 113 | /// matches the most fields, prioritizing in the order of perfect match, |
| 114 | /// languageCode+countryCode, languageCode+scriptCode, languageCode-only. |
| 115 | /// |
| 116 | /// In the case where a locale is matched by languageCode-only and is not the |
| 117 | /// default (first) locale, the next preferred locale with a |
| 118 | /// perfect match can supersede the languageCode-only match if it exists. |
| 119 | /// |
| 120 | /// When a preferredLocale matches more than one supported locale, it will |
| 121 | /// resolve to the first matching locale listed in the supportedLocales. |
| 122 | /// |
| 123 | /// When all preferred locales have been exhausted without a match, the first |
| 124 | /// countryCode only match will be returned. |
| 125 | /// |
| 126 | /// When no match at all is found, the first (default) locale in |
| 127 | /// [supportedLocales] will be returned. |
| 128 | /// |
| 129 | /// To summarize, the main matching priority is: |
| 130 | /// |
| 131 | /// 1. [Locale.languageCode], [Locale.scriptCode], and [Locale.countryCode] |
| 132 | /// 2. [Locale.languageCode] and [Locale.scriptCode] only |
| 133 | /// 3. [Locale.languageCode] and [Locale.countryCode] only |
| 134 | /// 4. [Locale.languageCode] only (with caveats, see above) |
| 135 | /// 5. [Locale.countryCode] only when all [preferredLocales] fail to match |
| 136 | /// 6. Returns the first element of [supportedLocales] as a fallback |
| 137 | /// |
| 138 | /// This algorithm does not take language distance (how similar languages are to each other) |
| 139 | /// into account, and will not handle edge cases such as resolving `de` to `fr` rather than `zh` |
| 140 | /// when `de` is not supported and `zh` is listed before `fr` (German is closer to French |
| 141 | /// than Chinese). |
| 142 | Locale basicLocaleListResolution( |
| 143 | List<Locale>? preferredLocales, |
| 144 | Iterable<Locale> supportedLocales, |
| 145 | ) { |
| 146 | // preferredLocales can be null when called before the platform has had a chance to |
| 147 | // initialize the locales. Platforms without locale passing support will provide an empty list. |
| 148 | // We default to the first supported locale in these cases. |
| 149 | if (preferredLocales == null || preferredLocales.isEmpty) { |
| 150 | return supportedLocales.first; |
| 151 | } |
| 152 | // Hash the supported locales because apps can support many locales and would |
| 153 | // be expensive to search through them many times. |
| 154 | final Map<String, Locale> allSupportedLocales = HashMap<String, Locale>(); |
| 155 | final Map<String, Locale> languageAndCountryLocales = HashMap<String, Locale>(); |
| 156 | final Map<String, Locale> languageAndScriptLocales = HashMap<String, Locale>(); |
| 157 | final Map<String, Locale> languageLocales = HashMap<String, Locale>(); |
| 158 | final Map<String?, Locale> countryLocales = HashMap<String?, Locale>(); |
| 159 | for (final Locale locale in supportedLocales) { |
| 160 | allSupportedLocales[' ${locale.languageCode}_ ${locale.scriptCode}_ ${locale.countryCode}' ] ??= |
| 161 | locale; |
| 162 | languageAndScriptLocales[' ${locale.languageCode}_ ${locale.scriptCode}' ] ??= locale; |
| 163 | languageAndCountryLocales[' ${locale.languageCode}_ ${locale.countryCode}' ] ??= locale; |
| 164 | languageLocales[locale.languageCode] ??= locale; |
| 165 | countryLocales[locale.countryCode] ??= locale; |
| 166 | } |
| 167 | |
| 168 | // Since languageCode-only matches are possibly low quality, we don't return |
| 169 | // it instantly when we find such a match. We check to see if the next |
| 170 | // preferred locale in the list has a high accuracy match, and only return |
| 171 | // the languageCode-only match when a higher accuracy match in the next |
| 172 | // preferred locale cannot be found. |
| 173 | Locale? matchesLanguageCode; |
| 174 | Locale? matchesCountryCode; |
| 175 | // Loop over user's preferred locales |
| 176 | for (int localeIndex = 0; localeIndex < preferredLocales.length; localeIndex += 1) { |
| 177 | final Locale userLocale = preferredLocales[localeIndex]; |
| 178 | // Look for perfect match. |
| 179 | if (allSupportedLocales.containsKey( |
| 180 | ' ${userLocale.languageCode}_ ${userLocale.scriptCode}_ ${userLocale.countryCode}' , |
| 181 | )) { |
| 182 | return userLocale; |
| 183 | } |
| 184 | // Look for language+script match. |
| 185 | if (userLocale.scriptCode != null) { |
| 186 | final Locale? match = |
| 187 | languageAndScriptLocales[' ${userLocale.languageCode}_ ${userLocale.scriptCode}' ]; |
| 188 | if (match != null) { |
| 189 | return match; |
| 190 | } |
| 191 | } |
| 192 | // Look for language+country match. |
| 193 | if (userLocale.countryCode != null) { |
| 194 | final Locale? match = |
| 195 | languageAndCountryLocales[' ${userLocale.languageCode}_ ${userLocale.countryCode}' ]; |
| 196 | if (match != null) { |
| 197 | return match; |
| 198 | } |
| 199 | } |
| 200 | // If there was a languageCode-only match in the previous iteration's higher |
| 201 | // ranked preferred locale, we return it if the current userLocale does not |
| 202 | // have a better match. |
| 203 | if (matchesLanguageCode != null) { |
| 204 | return matchesLanguageCode; |
| 205 | } |
| 206 | // Look and store language-only match. |
| 207 | Locale? match = languageLocales[userLocale.languageCode]; |
| 208 | if (match != null) { |
| 209 | matchesLanguageCode = match; |
| 210 | // Since first (default) locale is usually highly preferred, we will allow |
| 211 | // a languageCode-only match to be instantly matched. If the next preferred |
| 212 | // languageCode is the same, we defer hastily returning until the next iteration |
| 213 | // since at worst it is the same and at best an improved match. |
| 214 | if (localeIndex == 0 && |
| 215 | !(localeIndex + 1 < preferredLocales.length && |
| 216 | preferredLocales[localeIndex + 1].languageCode == userLocale.languageCode)) { |
| 217 | return matchesLanguageCode; |
| 218 | } |
| 219 | } |
| 220 | // countryCode-only match. When all else except default supported locale fails, |
| 221 | // attempt to match by country only, as a user is likely to be familiar with a |
| 222 | // language from their listed country. |
| 223 | if (matchesCountryCode == null && userLocale.countryCode != null) { |
| 224 | match = countryLocales[userLocale.countryCode]; |
| 225 | if (match != null) { |
| 226 | matchesCountryCode = match; |
| 227 | } |
| 228 | } |
| 229 | } |
| 230 | // When there is no languageCode-only match. Fallback to matching countryCode only. Country |
| 231 | // fallback only applies on iOS. When there is no countryCode-only match, we return first |
| 232 | // supported locale. |
| 233 | final Locale resolvedLocale = matchesLanguageCode ?? matchesCountryCode ?? supportedLocales.first; |
| 234 | return resolvedLocale; |
| 235 | } |
| 236 | |
| 237 | /// The signature of [WidgetsApp.onGenerateTitle]. |
| 238 | /// |
| 239 | /// Used to generate a value for the app's [Title.title], which the device uses |
| 240 | /// to identify the app for the user. The `context` includes the [WidgetsApp]'s |
| 241 | /// [Localizations] widget so that this method can be used to produce a |
| 242 | /// localized title. |
| 243 | /// |
| 244 | /// This function must not return null. |
| 245 | typedef GenerateAppTitle = String Function(BuildContext context); |
| 246 | |
| 247 | /// The signature of [WidgetsApp.pageRouteBuilder]. |
| 248 | /// |
| 249 | /// Creates a [PageRoute] using the given [RouteSettings] and [WidgetBuilder]. |
| 250 | typedef PageRouteFactory = PageRoute<T> Function<T>(RouteSettings settings, WidgetBuilder builder); |
| 251 | |
| 252 | /// The signature of [WidgetsApp.onGenerateInitialRoutes]. |
| 253 | /// |
| 254 | /// Creates a series of one or more initial routes. |
| 255 | typedef InitialRouteListFactory = List<Route<dynamic>> Function(String initialRoute); |
| 256 | |
| 257 | /// A convenience widget that wraps a number of widgets that are commonly |
| 258 | /// required for an application. |
| 259 | /// |
| 260 | /// One of the primary roles that [WidgetsApp] provides is binding the system |
| 261 | /// back button to popping the [Navigator] or quitting the application. |
| 262 | /// |
| 263 | /// It is used by both [MaterialApp] and [CupertinoApp] to implement base |
| 264 | /// functionality for an app. |
| 265 | /// |
| 266 | /// Find references to many of the widgets that [WidgetsApp] wraps in the "See |
| 267 | /// also" section. |
| 268 | /// |
| 269 | /// See also: |
| 270 | /// |
| 271 | /// * [CheckedModeBanner], which displays a [Banner] saying "DEBUG" when |
| 272 | /// running in debug mode. |
| 273 | /// * [DefaultTextStyle], the text style to apply to descendant [Text] widgets |
| 274 | /// without an explicit style. |
| 275 | /// * [MediaQuery], which establishes a subtree in which media queries resolve |
| 276 | /// to a [MediaQueryData]. |
| 277 | /// * [Localizations], which defines the [Locale] for its `child`. |
| 278 | /// * [Title], a widget that describes this app in the operating system. |
| 279 | /// * [Navigator], a widget that manages a set of child widgets with a stack |
| 280 | /// discipline. |
| 281 | /// * [Overlay], a widget that manages a [Stack] of entries that can be managed |
| 282 | /// independently. |
| 283 | /// * [SemanticsDebugger], a widget that visualizes the semantics for the child. |
| 284 | class WidgetsApp extends StatefulWidget { |
| 285 | /// Creates a widget that wraps a number of widgets that are commonly |
| 286 | /// required for an application. |
| 287 | /// |
| 288 | /// Most callers will want to use the [home] or [routes] parameters, or both. |
| 289 | /// The [home] parameter is a convenience for the following [routes] map: |
| 290 | /// |
| 291 | /// ```dart |
| 292 | /// <String, WidgetBuilder>{ '/': (BuildContext context) => myWidget } |
| 293 | /// ``` |
| 294 | /// |
| 295 | /// It is possible to specify both [home] and [routes], but only if [routes] does |
| 296 | /// _not_ contain an entry for `'/'`. Conversely, if [home] is omitted, [routes] |
| 297 | /// _must_ contain an entry for `'/'`. |
| 298 | /// |
| 299 | /// If [home] or [routes] are not null, the routing implementation needs to know how |
| 300 | /// to appropriately build [PageRoute]s. This can be achieved by supplying the |
| 301 | /// [pageRouteBuilder] parameter. The [pageRouteBuilder] is used by [MaterialApp] |
| 302 | /// and [CupertinoApp] to create [MaterialPageRoute]s and [CupertinoPageRoute], |
| 303 | /// respectively. |
| 304 | /// |
| 305 | /// The [builder] parameter is designed to provide the ability to wrap the visible |
| 306 | /// content of the app in some other widget. It is recommended that you use [home] |
| 307 | /// rather than [builder] if you intend to only display a single route in your app. |
| 308 | /// |
| 309 | /// [WidgetsApp] is also able to provide a custom implementation of routing via the |
| 310 | /// [onGenerateRoute] and [onUnknownRoute] parameters. These parameters correspond |
| 311 | /// to [Navigator.onGenerateRoute] and [Navigator.onUnknownRoute]. If [home], [routes], |
| 312 | /// and [builder] are null, or if they fail to create a requested route, |
| 313 | /// [onGenerateRoute] will be invoked. If that fails, [onUnknownRoute] will be invoked. |
| 314 | /// |
| 315 | /// The [pageRouteBuilder] is called to create a [PageRoute] that wraps newly built routes. |
| 316 | /// If the [builder] is non-null and the [onGenerateRoute] argument is null, then the |
| 317 | /// [builder] will be provided only with the context and the child widget, whereas |
| 318 | /// the [pageRouteBuilder] will be provided with [RouteSettings]; in that configuration, |
| 319 | /// the [navigatorKey], [onUnknownRoute], [navigatorObservers], and |
| 320 | /// [initialRoute] properties must have their default values, as they will have no effect. |
| 321 | /// |
| 322 | /// The `supportedLocales` argument must be a list of one or more elements. |
| 323 | /// By default supportedLocales is `[const Locale('en', 'US')]`. |
| 324 | /// |
| 325 | /// {@tool dartpad} |
| 326 | /// This sample shows a basic Flutter application using [WidgetsApp]. |
| 327 | /// |
| 328 | /// ** See code in examples/api/lib/widgets/app/widgets_app.widgets_app.0.dart ** |
| 329 | /// {@end-tool} |
| 330 | WidgetsApp({ |
| 331 | // can't be const because the asserts use methods on Iterable :-( |
| 332 | super.key, |
| 333 | this.navigatorKey, |
| 334 | this.onGenerateRoute, |
| 335 | this.onGenerateInitialRoutes, |
| 336 | this.onUnknownRoute, |
| 337 | this.onNavigationNotification, |
| 338 | List<NavigatorObserver> this.navigatorObservers = const <NavigatorObserver>[], |
| 339 | this.initialRoute, |
| 340 | this.pageRouteBuilder, |
| 341 | this.home, |
| 342 | Map<String, WidgetBuilder> this.routes = const <String, WidgetBuilder>{}, |
| 343 | this.builder, |
| 344 | this.title, |
| 345 | this.onGenerateTitle, |
| 346 | this.textStyle, |
| 347 | required this.color, |
| 348 | this.locale, |
| 349 | this.localizationsDelegates, |
| 350 | this.localeListResolutionCallback, |
| 351 | this.localeResolutionCallback, |
| 352 | this.supportedLocales = const <Locale>[Locale('en' , 'US' )], |
| 353 | this.showPerformanceOverlay = false, |
| 354 | this.showSemanticsDebugger = false, |
| 355 | this.debugShowWidgetInspector = false, |
| 356 | this.debugShowCheckedModeBanner = true, |
| 357 | this.exitWidgetSelectionButtonBuilder, |
| 358 | this.moveExitWidgetSelectionButtonBuilder, |
| 359 | this.tapBehaviorButtonBuilder, |
| 360 | this.shortcuts, |
| 361 | this.actions, |
| 362 | this.restorationScopeId, |
| 363 | @Deprecated( |
| 364 | 'Remove this parameter as it is now ignored. ' |
| 365 | 'WidgetsApp never introduces its own MediaQuery; the View widget takes care of that. ' |
| 366 | 'This feature was deprecated after v3.7.0-29.0.pre.' , |
| 367 | ) |
| 368 | this.useInheritedMediaQuery = false, |
| 369 | }) : assert( |
| 370 | home == null || onGenerateInitialRoutes == null, |
| 371 | 'If onGenerateInitialRoutes is specified, the home argument will be ' |
| 372 | 'redundant.' , |
| 373 | ), |
| 374 | assert( |
| 375 | home == null || !routes.containsKey(Navigator.defaultRouteName), |
| 376 | 'If the home property is specified, the routes table ' |
| 377 | 'cannot include an entry for "/", since it would be redundant.' , |
| 378 | ), |
| 379 | assert( |
| 380 | builder != null || |
| 381 | home != null || |
| 382 | routes.containsKey(Navigator.defaultRouteName) || |
| 383 | onGenerateRoute != null || |
| 384 | onUnknownRoute != null, |
| 385 | 'Either the home property must be specified, ' |
| 386 | 'or the routes table must include an entry for "/", ' |
| 387 | 'or there must be on onGenerateRoute callback specified, ' |
| 388 | 'or there must be an onUnknownRoute callback specified, ' |
| 389 | 'or the builder property must be specified, ' |
| 390 | 'because otherwise there is nothing to fall back on if the ' |
| 391 | 'app is started with an intent that specifies an unknown route.' , |
| 392 | ), |
| 393 | assert( |
| 394 | (home != null || routes.isNotEmpty || onGenerateRoute != null || onUnknownRoute != null) || |
| 395 | (builder != null && |
| 396 | navigatorKey == null && |
| 397 | initialRoute == null && |
| 398 | navigatorObservers.isEmpty), |
| 399 | 'If no route is provided using ' |
| 400 | 'home, routes, onGenerateRoute, or onUnknownRoute, ' |
| 401 | 'a non-null callback for the builder property must be provided, ' |
| 402 | 'and the other navigator-related properties, ' |
| 403 | 'navigatorKey, initialRoute, and navigatorObservers, ' |
| 404 | 'must have their initial values ' |
| 405 | '(null, null, and the empty list, respectively).' , |
| 406 | ), |
| 407 | assert( |
| 408 | builder != null || onGenerateRoute != null || pageRouteBuilder != null, |
| 409 | 'If neither builder nor onGenerateRoute are provided, the ' |
| 410 | 'pageRouteBuilder must be specified so that the default handler ' |
| 411 | 'will know what kind of PageRoute transition to build.' , |
| 412 | ), |
| 413 | assert(supportedLocales.isNotEmpty), |
| 414 | routeInformationProvider = null, |
| 415 | routeInformationParser = null, |
| 416 | routerDelegate = null, |
| 417 | backButtonDispatcher = null, |
| 418 | routerConfig = null; |
| 419 | |
| 420 | /// Creates a [WidgetsApp] that uses the [Router] instead of a [Navigator]. |
| 421 | /// |
| 422 | /// {@template flutter.widgets.WidgetsApp.router} |
| 423 | /// If the [routerConfig] is provided, the other router related delegates, |
| 424 | /// [routeInformationParser], [routeInformationProvider], [routerDelegate], |
| 425 | /// and [backButtonDispatcher], must all be null. |
| 426 | /// {@endtemplate} |
| 427 | WidgetsApp.router({ |
| 428 | super.key, |
| 429 | this.routeInformationProvider, |
| 430 | this.routeInformationParser, |
| 431 | this.routerDelegate, |
| 432 | this.routerConfig, |
| 433 | this.backButtonDispatcher, |
| 434 | this.builder, |
| 435 | this.title, |
| 436 | this.onGenerateTitle, |
| 437 | this.onNavigationNotification, |
| 438 | this.textStyle, |
| 439 | required this.color, |
| 440 | this.locale, |
| 441 | this.localizationsDelegates, |
| 442 | this.localeListResolutionCallback, |
| 443 | this.localeResolutionCallback, |
| 444 | this.supportedLocales = const <Locale>[Locale('en' , 'US' )], |
| 445 | this.showPerformanceOverlay = false, |
| 446 | this.showSemanticsDebugger = false, |
| 447 | this.debugShowWidgetInspector = false, |
| 448 | this.debugShowCheckedModeBanner = true, |
| 449 | this.exitWidgetSelectionButtonBuilder, |
| 450 | this.moveExitWidgetSelectionButtonBuilder, |
| 451 | this.tapBehaviorButtonBuilder, |
| 452 | this.shortcuts, |
| 453 | this.actions, |
| 454 | this.restorationScopeId, |
| 455 | @Deprecated( |
| 456 | 'Remove this parameter as it is now ignored. ' |
| 457 | 'WidgetsApp never introduces its own MediaQuery; the View widget takes care of that. ' |
| 458 | 'This feature was deprecated after v3.7.0-29.0.pre.' , |
| 459 | ) |
| 460 | this.useInheritedMediaQuery = false, |
| 461 | }) : assert(() { |
| 462 | if (routerConfig != null) { |
| 463 | assert( |
| 464 | (routeInformationProvider ?? |
| 465 | routeInformationParser ?? |
| 466 | routerDelegate ?? |
| 467 | backButtonDispatcher) == |
| 468 | null, |
| 469 | 'If the routerConfig is provided, all the other router delegates must not be provided' , |
| 470 | ); |
| 471 | return true; |
| 472 | } |
| 473 | assert( |
| 474 | routerDelegate != null, |
| 475 | 'Either one of routerDelegate or routerConfig must be provided' , |
| 476 | ); |
| 477 | assert( |
| 478 | routeInformationProvider == null || routeInformationParser != null, |
| 479 | 'If routeInformationProvider is provided, routeInformationParser must also be provided' , |
| 480 | ); |
| 481 | return true; |
| 482 | }()), |
| 483 | assert(supportedLocales.isNotEmpty), |
| 484 | navigatorObservers = null, |
| 485 | navigatorKey = null, |
| 486 | onGenerateRoute = null, |
| 487 | pageRouteBuilder = null, |
| 488 | home = null, |
| 489 | onGenerateInitialRoutes = null, |
| 490 | onUnknownRoute = null, |
| 491 | routes = null, |
| 492 | initialRoute = null; |
| 493 | |
| 494 | /// {@template flutter.widgets.widgetsApp.navigatorKey} |
| 495 | /// A key to use when building the [Navigator]. |
| 496 | /// |
| 497 | /// If a [navigatorKey] is specified, the [Navigator] can be directly |
| 498 | /// manipulated without first obtaining it from a [BuildContext] via |
| 499 | /// [Navigator.of]: from the [navigatorKey], use the [GlobalKey.currentState] |
| 500 | /// getter. |
| 501 | /// |
| 502 | /// If this is changed, a new [Navigator] will be created, losing all the |
| 503 | /// application state in the process; in that case, the [navigatorObservers] |
| 504 | /// must also be changed, since the previous observers will be attached to the |
| 505 | /// previous navigator. |
| 506 | /// |
| 507 | /// The [Navigator] is only built if [onGenerateRoute] is not null; if it is |
| 508 | /// null, [navigatorKey] must also be null. |
| 509 | /// {@endtemplate} |
| 510 | final GlobalKey<NavigatorState>? navigatorKey; |
| 511 | |
| 512 | /// {@template flutter.widgets.widgetsApp.onGenerateRoute} |
| 513 | /// The route generator callback used when the app is navigated to a |
| 514 | /// named route. |
| 515 | /// |
| 516 | /// If this returns null when building the routes to handle the specified |
| 517 | /// [initialRoute], then all the routes are discarded and |
| 518 | /// [Navigator.defaultRouteName] is used instead (`/`). See [initialRoute]. |
| 519 | /// |
| 520 | /// During normal app operation, the [onGenerateRoute] callback will only be |
| 521 | /// applied to route names pushed by the application, and so should never |
| 522 | /// return null. |
| 523 | /// |
| 524 | /// This is used if [routes] does not contain the requested route. |
| 525 | /// |
| 526 | /// The [Navigator] is only built if routes are provided (either via [home], |
| 527 | /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not, |
| 528 | /// [builder] must not be null. |
| 529 | /// {@endtemplate} |
| 530 | /// |
| 531 | /// If this property is not set, either the [routes] or [home] properties must |
| 532 | /// be set, and the [pageRouteBuilder] must also be set so that the |
| 533 | /// default handler will know what routes and [PageRoute]s to build. |
| 534 | final RouteFactory? onGenerateRoute; |
| 535 | |
| 536 | /// {@template flutter.widgets.widgetsApp.onGenerateInitialRoutes} |
| 537 | /// The routes generator callback used for generating initial routes if |
| 538 | /// [initialRoute] is provided. |
| 539 | /// |
| 540 | /// If this property is not set, the underlying |
| 541 | /// [Navigator.onGenerateInitialRoutes] will default to |
| 542 | /// [Navigator.defaultGenerateInitialRoutes]. |
| 543 | /// {@endtemplate} |
| 544 | final InitialRouteListFactory? onGenerateInitialRoutes; |
| 545 | |
| 546 | /// The [PageRoute] generator callback used when the app is navigated to a |
| 547 | /// named route. |
| 548 | /// |
| 549 | /// A [PageRoute] represents the page in a [Navigator], so that it can |
| 550 | /// correctly animate between pages, and to represent the "return value" of |
| 551 | /// a route (e.g. which button a user selected in a modal dialog). |
| 552 | /// |
| 553 | /// This callback can be used, for example, to specify that a [MaterialPageRoute] |
| 554 | /// or a [CupertinoPageRoute] should be used for building page transitions. |
| 555 | /// |
| 556 | /// The [PageRouteFactory] type is generic, meaning the provided function must |
| 557 | /// itself be generic. For example (with special emphasis on the `<T>` at the |
| 558 | /// start of the closure): |
| 559 | /// |
| 560 | /// ```dart |
| 561 | /// pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) => PageRouteBuilder<T>( |
| 562 | /// settings: settings, |
| 563 | /// pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) => builder(context), |
| 564 | /// ), |
| 565 | /// ``` |
| 566 | final PageRouteFactory? pageRouteBuilder; |
| 567 | |
| 568 | /// {@template flutter.widgets.widgetsApp.routeInformationParser} |
| 569 | /// A delegate to parse the route information from the |
| 570 | /// [routeInformationProvider] into a generic data type to be processed by |
| 571 | /// the [routerDelegate] at a later stage. |
| 572 | /// |
| 573 | /// This object will be used by the underlying [Router]. |
| 574 | /// |
| 575 | /// The generic type `T` must match the generic type of the [routerDelegate]. |
| 576 | /// |
| 577 | /// See also: |
| 578 | /// |
| 579 | /// * [Router.routeInformationParser], which receives this object when this |
| 580 | /// widget builds the [Router]. |
| 581 | /// {@endtemplate} |
| 582 | final RouteInformationParser<Object>? routeInformationParser; |
| 583 | |
| 584 | /// {@template flutter.widgets.widgetsApp.routerDelegate} |
| 585 | /// A delegate that configures a widget, typically a [Navigator], with |
| 586 | /// parsed result from the [routeInformationParser]. |
| 587 | /// |
| 588 | /// This object will be used by the underlying [Router]. |
| 589 | /// |
| 590 | /// The generic type `T` must match the generic type of the |
| 591 | /// [routeInformationParser]. |
| 592 | /// |
| 593 | /// See also: |
| 594 | /// |
| 595 | /// * [Router.routerDelegate], which receives this object when this widget |
| 596 | /// builds the [Router]. |
| 597 | /// {@endtemplate} |
| 598 | final RouterDelegate<Object>? routerDelegate; |
| 599 | |
| 600 | /// {@template flutter.widgets.widgetsApp.backButtonDispatcher} |
| 601 | /// A delegate that decide whether to handle the Android back button intent. |
| 602 | /// |
| 603 | /// This object will be used by the underlying [Router]. |
| 604 | /// |
| 605 | /// If this is not provided, the widgets app will create a |
| 606 | /// [RootBackButtonDispatcher] by default. |
| 607 | /// |
| 608 | /// See also: |
| 609 | /// |
| 610 | /// * [Router.backButtonDispatcher], which receives this object when this |
| 611 | /// widget builds the [Router]. |
| 612 | /// {@endtemplate} |
| 613 | final BackButtonDispatcher? backButtonDispatcher; |
| 614 | |
| 615 | /// {@template flutter.widgets.widgetsApp.routeInformationProvider} |
| 616 | /// A object that provides route information through the |
| 617 | /// [RouteInformationProvider.value] and notifies its listener when its value |
| 618 | /// changes. |
| 619 | /// |
| 620 | /// This object will be used by the underlying [Router]. |
| 621 | /// |
| 622 | /// If this is not provided, the widgets app will create a |
| 623 | /// [PlatformRouteInformationProvider] with initial route name equal to the |
| 624 | /// [dart:ui.PlatformDispatcher.defaultRouteName] by default. |
| 625 | /// |
| 626 | /// See also: |
| 627 | /// |
| 628 | /// * [Router.routeInformationProvider], which receives this object when this |
| 629 | /// widget builds the [Router]. |
| 630 | /// {@endtemplate} |
| 631 | final RouteInformationProvider? routeInformationProvider; |
| 632 | |
| 633 | /// {@template flutter.widgets.widgetsApp.routerConfig} |
| 634 | /// An object to configure the underlying [Router]. |
| 635 | /// |
| 636 | /// If the [routerConfig] is provided, the other router related delegates, |
| 637 | /// [routeInformationParser], [routeInformationProvider], [routerDelegate], |
| 638 | /// and [backButtonDispatcher], must all be null. |
| 639 | /// |
| 640 | /// See also: |
| 641 | /// |
| 642 | /// * [Router.withConfig], which receives this object when this |
| 643 | /// widget builds the [Router]. |
| 644 | /// {@endtemplate} |
| 645 | final RouterConfig<Object>? routerConfig; |
| 646 | |
| 647 | /// {@template flutter.widgets.widgetsApp.home} |
| 648 | /// The widget for the default route of the app ([Navigator.defaultRouteName], |
| 649 | /// which is `/`). |
| 650 | /// |
| 651 | /// This is the route that is displayed first when the application is started |
| 652 | /// normally, unless [initialRoute] is specified. It's also the route that's |
| 653 | /// displayed if the [initialRoute] can't be displayed. |
| 654 | /// |
| 655 | /// To be able to directly call [Theme.of], [MediaQuery.of], etc, in the code |
| 656 | /// that sets the [home] argument in the constructor, you can use a [Builder] |
| 657 | /// widget to get a [BuildContext]. |
| 658 | /// |
| 659 | /// If [home] is specified, then [routes] must not include an entry for `/`, |
| 660 | /// as [home] takes its place. |
| 661 | /// |
| 662 | /// The [Navigator] is only built if routes are provided (either via [home], |
| 663 | /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not, |
| 664 | /// [builder] must not be null. |
| 665 | /// |
| 666 | /// The difference between using [home] and using [builder] is that the [home] |
| 667 | /// subtree is inserted into the application below a [Navigator] (and thus |
| 668 | /// below an [Overlay], which [Navigator] uses). With [home], therefore, |
| 669 | /// dialog boxes will work automatically, the [routes] table will be used, and |
| 670 | /// APIs such as [Navigator.push] and [Navigator.pop] will work as expected. |
| 671 | /// In contrast, the widget returned from [builder] is inserted _above_ the |
| 672 | /// app's [Navigator] (if any). |
| 673 | /// {@endtemplate} |
| 674 | /// |
| 675 | /// If this property is set, the [pageRouteBuilder] property must also be set |
| 676 | /// so that the default route handler will know what kind of [PageRoute]s to |
| 677 | /// build. |
| 678 | final Widget? home; |
| 679 | |
| 680 | /// The application's top-level routing table. |
| 681 | /// |
| 682 | /// When a named route is pushed with [Navigator.pushNamed], the route name is |
| 683 | /// looked up in this map. If the name is present, the associated |
| 684 | /// [WidgetBuilder] is used to construct a [PageRoute] specified by |
| 685 | /// [pageRouteBuilder] to perform an appropriate transition, including [Hero] |
| 686 | /// animations, to the new route. |
| 687 | /// |
| 688 | /// {@template flutter.widgets.widgetsApp.routes} |
| 689 | /// If the app only has one page, then you can specify it using [home] instead. |
| 690 | /// |
| 691 | /// If [home] is specified, then it implies an entry in this table for the |
| 692 | /// [Navigator.defaultRouteName] route (`/`), and it is an error to |
| 693 | /// redundantly provide such a route in the [routes] table. |
| 694 | /// |
| 695 | /// If a route is requested that is not specified in this table (or by |
| 696 | /// [home]), then the [onGenerateRoute] callback is called to build the page |
| 697 | /// instead. |
| 698 | /// |
| 699 | /// The [Navigator] is only built if routes are provided (either via [home], |
| 700 | /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not, |
| 701 | /// [builder] must not be null. |
| 702 | /// {@endtemplate} |
| 703 | /// |
| 704 | /// If the routes map is not empty, the [pageRouteBuilder] property must be set |
| 705 | /// so that the default route handler will know what kind of [PageRoute]s to |
| 706 | /// build. |
| 707 | final Map<String, WidgetBuilder>? routes; |
| 708 | |
| 709 | /// {@template flutter.widgets.widgetsApp.onUnknownRoute} |
| 710 | /// Called when [onGenerateRoute] fails to generate a route, except for the |
| 711 | /// [initialRoute]. |
| 712 | /// |
| 713 | /// This callback is typically used for error handling. For example, this |
| 714 | /// callback might always generate a "not found" page that describes the route |
| 715 | /// that wasn't found. |
| 716 | /// |
| 717 | /// Unknown routes can arise either from errors in the app or from external |
| 718 | /// requests to push routes, such as from Android intents. |
| 719 | /// |
| 720 | /// The [Navigator] is only built if routes are provided (either via [home], |
| 721 | /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not, |
| 722 | /// [builder] must not be null. |
| 723 | /// {@endtemplate} |
| 724 | final RouteFactory? onUnknownRoute; |
| 725 | |
| 726 | /// {@template flutter.widgets.widgetsApp.onNavigationNotification} |
| 727 | /// The callback to use when receiving a [NavigationNotification]. |
| 728 | /// |
| 729 | /// By default this updates the engine with the navigation status and stops |
| 730 | /// bubbling the notification. |
| 731 | /// |
| 732 | /// See also: |
| 733 | /// |
| 734 | /// * [NotificationListener.onNotification], which uses this callback. |
| 735 | /// {@endtemplate} |
| 736 | final NotificationListenerCallback<NavigationNotification>? onNavigationNotification; |
| 737 | |
| 738 | /// {@template flutter.widgets.widgetsApp.initialRoute} |
| 739 | /// The name of the first route to show, if a [Navigator] is built. |
| 740 | /// |
| 741 | /// Defaults to [dart:ui.PlatformDispatcher.defaultRouteName], which may be |
| 742 | /// overridden by the code that launched the application. |
| 743 | /// |
| 744 | /// If the route name starts with a slash, then it is treated as a "deep link", |
| 745 | /// and before this route is pushed, the routes leading to this one are pushed |
| 746 | /// also. For example, if the route was `/a/b/c`, then the app would start |
| 747 | /// with the four routes `/`, `/a`, `/a/b`, and `/a/b/c` loaded, in that order. |
| 748 | /// Even if the route was just `/a`, the app would start with `/` and `/a` |
| 749 | /// loaded. You can use the [onGenerateInitialRoutes] property to override |
| 750 | /// this behavior. |
| 751 | /// |
| 752 | /// Intermediate routes aren't required to exist. In the example above, `/a` |
| 753 | /// and `/a/b` could be skipped if they have no matching route. But `/a/b/c` is |
| 754 | /// required to have a route, else [initialRoute] is ignored and |
| 755 | /// [Navigator.defaultRouteName] is used instead (`/`). This can happen if the |
| 756 | /// app is started with an intent that specifies a non-existent route. |
| 757 | /// |
| 758 | /// The [Navigator] is only built if routes are provided (either via [home], |
| 759 | /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not, |
| 760 | /// [initialRoute] must be null and [builder] must not be null. |
| 761 | /// |
| 762 | /// Changing the [initialRoute] will have no effect, as it only controls the |
| 763 | /// _initial_ route. To change the route while the application is running, use |
| 764 | /// the [Navigator] or [Router] APIs. |
| 765 | /// |
| 766 | /// See also: |
| 767 | /// |
| 768 | /// * [Navigator.initialRoute], which is used to implement this property. |
| 769 | /// * [Navigator.push], for pushing additional routes. |
| 770 | /// * [Navigator.pop], for removing a route from the stack. |
| 771 | /// |
| 772 | /// {@endtemplate} |
| 773 | final String? initialRoute; |
| 774 | |
| 775 | /// {@template flutter.widgets.widgetsApp.navigatorObservers} |
| 776 | /// The list of observers for the [Navigator] created for this app. |
| 777 | /// |
| 778 | /// This list must be replaced by a list of newly-created observers if the |
| 779 | /// [navigatorKey] is changed. |
| 780 | /// |
| 781 | /// The [Navigator] is only built if routes are provided (either via [home], |
| 782 | /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not, |
| 783 | /// [navigatorObservers] must be the empty list and [builder] must not be null. |
| 784 | /// {@endtemplate} |
| 785 | final List<NavigatorObserver>? navigatorObservers; |
| 786 | |
| 787 | /// {@template flutter.widgets.widgetsApp.builder} |
| 788 | /// A builder for inserting widgets above the [Navigator] or - when the |
| 789 | /// [WidgetsApp.router] constructor is used - above the [Router] but below the |
| 790 | /// other widgets created by the [WidgetsApp] widget, or for replacing the |
| 791 | /// [Navigator]/[Router] entirely. |
| 792 | /// |
| 793 | /// For example, from the [BuildContext] passed to this method, the |
| 794 | /// [Directionality], [Localizations], [DefaultTextStyle], [MediaQuery], etc, |
| 795 | /// are all available. They can also be overridden in a way that impacts all |
| 796 | /// the routes in the [Navigator] or [Router]. |
| 797 | /// |
| 798 | /// This is rarely useful, but can be used in applications that wish to |
| 799 | /// override those defaults, e.g. to force the application into right-to-left |
| 800 | /// mode despite being in English, or to override the [MediaQuery] metrics |
| 801 | /// (e.g. to leave a gap for advertisements shown by a plugin from OEM code). |
| 802 | /// |
| 803 | /// For specifically overriding the [title] with a value based on the |
| 804 | /// [Localizations], consider [onGenerateTitle] instead. |
| 805 | /// |
| 806 | /// The [builder] callback is passed two arguments, the [BuildContext] (as |
| 807 | /// `context`) and a [Navigator] or [Router] widget (as `child`). |
| 808 | /// |
| 809 | /// If no routes are provided to the regular [WidgetsApp] constructor using |
| 810 | /// [home], [routes], [onGenerateRoute], or [onUnknownRoute], the `child` will |
| 811 | /// be null, and it is the responsibility of the [builder] to provide the |
| 812 | /// application's routing machinery. |
| 813 | /// |
| 814 | /// If routes _are_ provided to the regular [WidgetsApp] constructor using one |
| 815 | /// or more of those properties or if the [WidgetsApp.router] constructor is |
| 816 | /// used, then `child` is not null, and the returned value should include the |
| 817 | /// `child` in the widget subtree; if it does not, then the application will |
| 818 | /// have no [Navigator] or [Router] and the routing related properties (i.e. |
| 819 | /// [navigatorKey], [home], [routes], [onGenerateRoute], [onUnknownRoute], |
| 820 | /// [initialRoute], [navigatorObservers], [routeInformationProvider], |
| 821 | /// [backButtonDispatcher], [routerDelegate], and [routeInformationParser]) |
| 822 | /// are ignored. |
| 823 | /// |
| 824 | /// If [builder] is null, it is as if a builder was specified that returned |
| 825 | /// the `child` directly. If it is null, routes must be provided using one of |
| 826 | /// the other properties listed above. |
| 827 | /// |
| 828 | /// Unless a [Navigator] is provided, either implicitly from [builder] being |
| 829 | /// null, or by a [builder] including its `child` argument, or by a [builder] |
| 830 | /// explicitly providing a [Navigator] of its own, or by the [routerDelegate] |
| 831 | /// building one, widgets and APIs such as [Hero], [Navigator.push] and |
| 832 | /// [Navigator.pop], will not function. |
| 833 | /// {@endtemplate} |
| 834 | final TransitionBuilder? builder; |
| 835 | |
| 836 | /// {@template flutter.widgets.widgetsApp.title} |
| 837 | /// A one-line description used by the device to identify the app for the user. |
| 838 | /// |
| 839 | /// On Android the titles appear above the task manager's app snapshots which are |
| 840 | /// displayed when the user presses the "recent apps" button. On iOS this |
| 841 | /// value cannot be used. `CFBundleDisplayName` from the app's `Info.plist` is |
| 842 | /// referred to instead whenever present, `CFBundleName` otherwise. |
| 843 | /// On the web it is used as the page title, which shows up in the browser's list of open tabs. |
| 844 | /// |
| 845 | /// To provide a localized title instead, use [onGenerateTitle]. |
| 846 | /// {@endtemplate} |
| 847 | final String? title; |
| 848 | |
| 849 | /// {@template flutter.widgets.widgetsApp.onGenerateTitle} |
| 850 | /// If non-null this callback function is called to produce the app's |
| 851 | /// title string, otherwise [title] is used. |
| 852 | /// |
| 853 | /// The [onGenerateTitle] `context` parameter includes the [WidgetsApp]'s |
| 854 | /// [Localizations] widget so that this callback can be used to produce a |
| 855 | /// localized title. |
| 856 | /// |
| 857 | /// This callback function must not return null. |
| 858 | /// |
| 859 | /// The [onGenerateTitle] callback is called each time the [WidgetsApp] |
| 860 | /// rebuilds. |
| 861 | /// {@endtemplate} |
| 862 | final GenerateAppTitle? onGenerateTitle; |
| 863 | |
| 864 | /// The default text style for [Text] in the application. |
| 865 | final TextStyle? textStyle; |
| 866 | |
| 867 | /// {@template flutter.widgets.widgetsApp.color} |
| 868 | /// The primary color to use for the application in the operating system |
| 869 | /// interface. |
| 870 | /// |
| 871 | /// For example, on Android this is the color used for the application in the |
| 872 | /// application switcher. |
| 873 | /// {@endtemplate} |
| 874 | final Color color; |
| 875 | |
| 876 | /// {@template flutter.widgets.widgetsApp.locale} |
| 877 | /// The initial locale for this app's [Localizations] widget is based |
| 878 | /// on this value. |
| 879 | /// |
| 880 | /// If the 'locale' is null then the system's locale value is used. |
| 881 | /// |
| 882 | /// The value of [Localizations.locale] will equal this locale if |
| 883 | /// it matches one of the [supportedLocales]. Otherwise it will be |
| 884 | /// the first element of [supportedLocales]. |
| 885 | /// {@endtemplate} |
| 886 | /// |
| 887 | /// See also: |
| 888 | /// |
| 889 | /// * [localeResolutionCallback], which can override the default |
| 890 | /// [supportedLocales] matching algorithm. |
| 891 | /// * [localizationsDelegates], which collectively define all of the localized |
| 892 | /// resources used by this app. |
| 893 | final Locale? locale; |
| 894 | |
| 895 | /// {@template flutter.widgets.widgetsApp.localizationsDelegates} |
| 896 | /// The delegates for this app's [Localizations] widget. |
| 897 | /// |
| 898 | /// The delegates collectively define all of the localized resources |
| 899 | /// for this application's [Localizations] widget. |
| 900 | /// {@endtemplate} |
| 901 | final Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates; |
| 902 | |
| 903 | /// {@template flutter.widgets.widgetsApp.localeListResolutionCallback} |
| 904 | /// This callback is responsible for choosing the app's locale |
| 905 | /// when the app is started, and when the user changes the |
| 906 | /// device's locale. |
| 907 | /// |
| 908 | /// When a [localeListResolutionCallback] is provided, Flutter will first |
| 909 | /// attempt to resolve the locale with the provided |
| 910 | /// [localeListResolutionCallback]. If the callback or result is null, it will |
| 911 | /// fallback to trying the [localeResolutionCallback]. If both |
| 912 | /// [localeResolutionCallback] and [localeListResolutionCallback] are left |
| 913 | /// null or fail to resolve (return null), basic fallback algorithm will |
| 914 | /// be used. |
| 915 | /// |
| 916 | /// The priority of each available fallback is: |
| 917 | /// |
| 918 | /// 1. [localeListResolutionCallback] is attempted. |
| 919 | /// 2. [localeResolutionCallback] is attempted. |
| 920 | /// 3. Flutter's basic resolution algorithm, as described in |
| 921 | /// [supportedLocales], is attempted last. |
| 922 | /// |
| 923 | /// Properly localized projects should provide a more advanced algorithm than |
| 924 | /// the basic method from [supportedLocales], as it does not implement a |
| 925 | /// complete algorithm (such as the one defined in |
| 926 | /// [Unicode TR35](https://unicode.org/reports/tr35/#LanguageMatching)) |
| 927 | /// and is optimized for speed at the detriment of some uncommon edge-cases. |
| 928 | /// {@endtemplate} |
| 929 | /// |
| 930 | /// This callback considers the entire list of preferred locales. |
| 931 | /// |
| 932 | /// This algorithm should be able to handle a null or empty list of preferred locales, |
| 933 | /// which indicates Flutter has not yet received locale information from the platform. |
| 934 | /// |
| 935 | /// See also: |
| 936 | /// |
| 937 | /// * [MaterialApp.localeListResolutionCallback], which sets the callback of the |
| 938 | /// [WidgetsApp] it creates. |
| 939 | /// * [basicLocaleListResolution], the default locale resolution algorithm. |
| 940 | final LocaleListResolutionCallback? localeListResolutionCallback; |
| 941 | |
| 942 | /// {@macro flutter.widgets.widgetsApp.localeListResolutionCallback} |
| 943 | /// |
| 944 | /// This callback considers only the default locale, which is the first locale |
| 945 | /// in the preferred locales list. It is preferred to set [localeListResolutionCallback] |
| 946 | /// over [localeResolutionCallback] as it provides the full preferred locales list. |
| 947 | /// |
| 948 | /// This algorithm should be able to handle a null locale, which indicates |
| 949 | /// Flutter has not yet received locale information from the platform. |
| 950 | /// |
| 951 | /// See also: |
| 952 | /// |
| 953 | /// * [MaterialApp.localeResolutionCallback], which sets the callback of the |
| 954 | /// [WidgetsApp] it creates. |
| 955 | /// * [basicLocaleListResolution], the default locale resolution algorithm. |
| 956 | final LocaleResolutionCallback? localeResolutionCallback; |
| 957 | |
| 958 | /// {@template flutter.widgets.widgetsApp.supportedLocales} |
| 959 | /// The list of locales that this app has been localized for. |
| 960 | /// |
| 961 | /// By default only the American English locale is supported. Apps should |
| 962 | /// configure this list to match the locales they support. |
| 963 | /// |
| 964 | /// This list must not null. Its default value is just |
| 965 | /// `[const Locale('en', 'US')]`. |
| 966 | /// |
| 967 | /// The order of the list matters. The default locale resolution algorithm, |
| 968 | /// [basicLocaleListResolution], attempts to match by the following priority: |
| 969 | /// |
| 970 | /// 1. [Locale.languageCode], [Locale.scriptCode], and [Locale.countryCode] |
| 971 | /// 2. [Locale.languageCode] and [Locale.scriptCode] only |
| 972 | /// 3. [Locale.languageCode] and [Locale.countryCode] only |
| 973 | /// 4. [Locale.languageCode] only |
| 974 | /// 5. [Locale.countryCode] only when all preferred locales fail to match |
| 975 | /// 6. Returns the first element of [supportedLocales] as a fallback |
| 976 | /// |
| 977 | /// When more than one supported locale matches one of these criteria, only |
| 978 | /// the first matching locale is returned. |
| 979 | /// |
| 980 | /// The default locale resolution algorithm can be overridden by providing a |
| 981 | /// value for [localeListResolutionCallback]. The provided |
| 982 | /// [basicLocaleListResolution] is optimized for speed and does not implement |
| 983 | /// a full algorithm (such as the one defined in |
| 984 | /// [Unicode TR35](https://unicode.org/reports/tr35/#LanguageMatching)) that |
| 985 | /// takes distances between languages into account. |
| 986 | /// |
| 987 | /// When supporting languages with more than one script, it is recommended |
| 988 | /// to specify the [Locale.scriptCode] explicitly. Locales may also be defined without |
| 989 | /// [Locale.countryCode] to specify a generic fallback for a particular script. |
| 990 | /// |
| 991 | /// A fully supported language with multiple scripts should define a generic language-only |
| 992 | /// locale (e.g. 'zh'), language+script only locales (e.g. 'zh_Hans' and 'zh_Hant'), |
| 993 | /// and any language+script+country locales (e.g. 'zh_Hans_CN'). Fully defining all of |
| 994 | /// these locales as supported is not strictly required but allows for proper locale resolution in |
| 995 | /// the most number of cases. These locales can be specified with the [Locale.fromSubtags] |
| 996 | /// constructor: |
| 997 | /// |
| 998 | /// ```dart |
| 999 | /// // Full Chinese support for CN, TW, and HK |
| 1000 | /// supportedLocales: <Locale>[ |
| 1001 | /// const Locale.fromSubtags(languageCode: 'zh'), // generic Chinese 'zh' |
| 1002 | /// const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'), // generic simplified Chinese 'zh_Hans' |
| 1003 | /// const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'), // generic traditional Chinese 'zh_Hant' |
| 1004 | /// const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN'), // 'zh_Hans_CN' |
| 1005 | /// const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'TW'), // 'zh_Hant_TW' |
| 1006 | /// const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'HK'), // 'zh_Hant_HK' |
| 1007 | /// ], |
| 1008 | /// ``` |
| 1009 | /// |
| 1010 | /// Omitting some these fallbacks may result in improperly resolved |
| 1011 | /// edge-cases, for example, a simplified Chinese user in Taiwan ('zh_Hans_TW') |
| 1012 | /// may resolve to traditional Chinese if 'zh_Hans' and 'zh_Hans_CN' are |
| 1013 | /// omitted. |
| 1014 | /// {@endtemplate} |
| 1015 | /// |
| 1016 | /// See also: |
| 1017 | /// |
| 1018 | /// * [MaterialApp.supportedLocales], which sets the `supportedLocales` |
| 1019 | /// of the [WidgetsApp] it creates. |
| 1020 | /// * [localeResolutionCallback], an app callback that resolves the app's locale |
| 1021 | /// when the device's locale changes. |
| 1022 | /// * [localizationsDelegates], which collectively define all of the localized |
| 1023 | /// resources used by this app. |
| 1024 | /// * [basicLocaleListResolution], the default locale resolution algorithm. |
| 1025 | final Iterable<Locale> supportedLocales; |
| 1026 | |
| 1027 | /// Turns on a performance overlay. |
| 1028 | /// |
| 1029 | /// See also: |
| 1030 | /// |
| 1031 | /// * <https://flutter.dev/to/performance-overlay> |
| 1032 | final bool showPerformanceOverlay; |
| 1033 | |
| 1034 | /// Turns on an overlay that shows the accessibility information |
| 1035 | /// reported by the framework. |
| 1036 | final bool showSemanticsDebugger; |
| 1037 | |
| 1038 | /// Turns on an overlay that enables inspecting the widget tree. |
| 1039 | /// |
| 1040 | /// The inspector is only available in debug mode as it depends on |
| 1041 | /// [RenderObject.debugDescribeChildren] which should not be called outside of |
| 1042 | /// debug mode. |
| 1043 | final bool debugShowWidgetInspector; |
| 1044 | |
| 1045 | /// Builds the widget the [WidgetInspector] uses to exit selection mode. |
| 1046 | /// |
| 1047 | /// This lets [MaterialApp] and [CupertinoApp] use an appropriately styled |
| 1048 | /// button for their design systems without requiring [WidgetInspector] to |
| 1049 | /// depend on the Material or Cupertino packages. |
| 1050 | final ExitWidgetSelectionButtonBuilder? exitWidgetSelectionButtonBuilder; |
| 1051 | |
| 1052 | /// Builds the widget the [WidgetInspector] uses to move the exit selection |
| 1053 | /// mode button. |
| 1054 | /// |
| 1055 | /// This lets [MaterialApp] and [CupertinoApp] use an appropriately styled |
| 1056 | /// button for their design systems without requiring [WidgetInspector] to |
| 1057 | /// depend on the Material or Cupertino packages. |
| 1058 | final MoveExitWidgetSelectionButtonBuilder? moveExitWidgetSelectionButtonBuilder; |
| 1059 | |
| 1060 | /// Builds the widget the [WidgetInspector] uses to change the default |
| 1061 | /// behavior when tapping on widgets in the app. |
| 1062 | /// |
| 1063 | /// This lets [MaterialApp] and [CupertinoApp] use an appropriately styled |
| 1064 | /// button for their design systems without requiring [WidgetInspector] to |
| 1065 | /// depend on the Material or Cupertino packages. |
| 1066 | final TapBehaviorButtonBuilder? tapBehaviorButtonBuilder; |
| 1067 | |
| 1068 | /// {@template flutter.widgets.widgetsApp.debugShowCheckedModeBanner} |
| 1069 | /// Turns on a little "DEBUG" banner in debug mode to indicate |
| 1070 | /// that the app is in debug mode. This is on by default (in |
| 1071 | /// debug mode), to turn it off, set the constructor argument to |
| 1072 | /// false. In release mode this has no effect. |
| 1073 | /// |
| 1074 | /// To get this banner in your application if you're not using |
| 1075 | /// WidgetsApp, include a [CheckedModeBanner] widget in your app. |
| 1076 | /// |
| 1077 | /// This banner is intended to deter people from complaining that your |
| 1078 | /// app is slow when it's in debug mode. In debug mode, Flutter |
| 1079 | /// enables a large number of expensive diagnostics to aid in |
| 1080 | /// development, and so performance in debug mode is not |
| 1081 | /// representative of what will happen in release mode. |
| 1082 | /// {@endtemplate} |
| 1083 | final bool debugShowCheckedModeBanner; |
| 1084 | |
| 1085 | /// {@template flutter.widgets.widgetsApp.shortcuts} |
| 1086 | /// The default map of keyboard shortcuts to intents for the application. |
| 1087 | /// |
| 1088 | /// By default, this is set to [WidgetsApp.defaultShortcuts]. |
| 1089 | /// |
| 1090 | /// Passing this will not replace [DefaultTextEditingShortcuts]. These can be |
| 1091 | /// overridden by using a [Shortcuts] widget lower in the widget tree. |
| 1092 | /// {@endtemplate} |
| 1093 | /// |
| 1094 | /// {@tool snippet} |
| 1095 | /// This example shows how to add a single shortcut for |
| 1096 | /// [LogicalKeyboardKey.select] to the default shortcuts without needing to |
| 1097 | /// add your own [Shortcuts] widget. |
| 1098 | /// |
| 1099 | /// Alternatively, you could insert a [Shortcuts] widget with just the mapping |
| 1100 | /// you want to add between the [WidgetsApp] and its child and get the same |
| 1101 | /// effect. |
| 1102 | /// |
| 1103 | /// ```dart |
| 1104 | /// Widget build(BuildContext context) { |
| 1105 | /// return WidgetsApp( |
| 1106 | /// shortcuts: <ShortcutActivator, Intent>{ |
| 1107 | /// ... WidgetsApp.defaultShortcuts, |
| 1108 | /// const SingleActivator(LogicalKeyboardKey.select): const ActivateIntent(), |
| 1109 | /// }, |
| 1110 | /// color: const Color(0xFFFF0000), |
| 1111 | /// builder: (BuildContext context, Widget? child) { |
| 1112 | /// return const Placeholder(); |
| 1113 | /// }, |
| 1114 | /// ); |
| 1115 | /// } |
| 1116 | /// ``` |
| 1117 | /// {@end-tool} |
| 1118 | /// |
| 1119 | /// {@template flutter.widgets.widgetsApp.shortcuts.seeAlso} |
| 1120 | /// See also: |
| 1121 | /// |
| 1122 | /// * [SingleActivator], which defines shortcut key combination of a single |
| 1123 | /// key and modifiers, such as "Delete" or "Control+C". |
| 1124 | /// * The [Shortcuts] widget, which defines a keyboard mapping. |
| 1125 | /// * The [Actions] widget, which defines the mapping from intent to action. |
| 1126 | /// * The [Intent] and [Action] classes, which allow definition of new |
| 1127 | /// actions. |
| 1128 | /// {@endtemplate} |
| 1129 | final Map<ShortcutActivator, Intent>? shortcuts; |
| 1130 | |
| 1131 | /// {@template flutter.widgets.widgetsApp.actions} |
| 1132 | /// The default map of intent keys to actions for the application. |
| 1133 | /// |
| 1134 | /// By default, this is the output of [WidgetsApp.defaultActions], called with |
| 1135 | /// [defaultTargetPlatform]. Specifying [actions] for an app overrides the |
| 1136 | /// default, so if you wish to modify the default [actions], you can call |
| 1137 | /// [WidgetsApp.defaultActions] and modify the resulting map, passing it as |
| 1138 | /// the [actions] for this app. You may also add to the bindings, or override |
| 1139 | /// specific bindings for a widget subtree, by adding your own [Actions] |
| 1140 | /// widget. |
| 1141 | /// {@endtemplate} |
| 1142 | /// |
| 1143 | /// {@tool snippet} |
| 1144 | /// This example shows how to add a single action handling an |
| 1145 | /// [ActivateAction] to the default actions without needing to |
| 1146 | /// add your own [Actions] widget. |
| 1147 | /// |
| 1148 | /// Alternatively, you could insert a [Actions] widget with just the mapping |
| 1149 | /// you want to add between the [WidgetsApp] and its child and get the same |
| 1150 | /// effect. |
| 1151 | /// |
| 1152 | /// ```dart |
| 1153 | /// Widget build(BuildContext context) { |
| 1154 | /// return WidgetsApp( |
| 1155 | /// actions: <Type, Action<Intent>>{ |
| 1156 | /// ... WidgetsApp.defaultActions, |
| 1157 | /// ActivateAction: CallbackAction<Intent>( |
| 1158 | /// onInvoke: (Intent intent) { |
| 1159 | /// // Do something here... |
| 1160 | /// return null; |
| 1161 | /// }, |
| 1162 | /// ), |
| 1163 | /// }, |
| 1164 | /// color: const Color(0xFFFF0000), |
| 1165 | /// builder: (BuildContext context, Widget? child) { |
| 1166 | /// return const Placeholder(); |
| 1167 | /// }, |
| 1168 | /// ); |
| 1169 | /// } |
| 1170 | /// ``` |
| 1171 | /// {@end-tool} |
| 1172 | /// |
| 1173 | /// {@template flutter.widgets.widgetsApp.actions.seeAlso} |
| 1174 | /// See also: |
| 1175 | /// |
| 1176 | /// * The [shortcuts] parameter, which defines the default set of shortcuts |
| 1177 | /// for the application. |
| 1178 | /// * The [Shortcuts] widget, which defines a keyboard mapping. |
| 1179 | /// * The [Actions] widget, which defines the mapping from intent to action. |
| 1180 | /// * The [Intent] and [Action] classes, which allow definition of new |
| 1181 | /// actions. |
| 1182 | /// {@endtemplate} |
| 1183 | final Map<Type, Action<Intent>>? actions; |
| 1184 | |
| 1185 | /// {@template flutter.widgets.widgetsApp.restorationScopeId} |
| 1186 | /// The identifier to use for state restoration of this app. |
| 1187 | /// |
| 1188 | /// Providing a restoration ID inserts a [RootRestorationScope] into the |
| 1189 | /// widget hierarchy, which enables state restoration for descendant widgets. |
| 1190 | /// |
| 1191 | /// Providing a restoration ID also enables the [Navigator] or [Router] built |
| 1192 | /// by the [WidgetsApp] to restore its state (i.e. to restore the history |
| 1193 | /// stack of active [Route]s). See the documentation on [Navigator] for more |
| 1194 | /// details around state restoration of [Route]s. |
| 1195 | /// |
| 1196 | /// See also: |
| 1197 | /// |
| 1198 | /// * [RestorationManager], which explains how state restoration works in |
| 1199 | /// Flutter. |
| 1200 | /// {@endtemplate} |
| 1201 | final String? restorationScopeId; |
| 1202 | |
| 1203 | /// {@template flutter.widgets.widgetsApp.useInheritedMediaQuery} |
| 1204 | /// Deprecated. This setting is now ignored. |
| 1205 | /// |
| 1206 | /// The widget never introduces its own [MediaQuery]; the [View] widget takes |
| 1207 | /// care of that. |
| 1208 | /// {@endtemplate} |
| 1209 | @Deprecated( |
| 1210 | 'This setting is now ignored. ' |
| 1211 | 'WidgetsApp never introduces its own MediaQuery; the View widget takes care of that. ' |
| 1212 | 'This feature was deprecated after v3.7.0-29.0.pre.' , |
| 1213 | ) |
| 1214 | final bool useInheritedMediaQuery; |
| 1215 | |
| 1216 | /// If true, forces the performance overlay to be visible in all instances. |
| 1217 | /// |
| 1218 | /// Used by the `showPerformanceOverlay` VM service extension. |
| 1219 | static bool showPerformanceOverlayOverride = false; |
| 1220 | |
| 1221 | /// If true, forces the widget inspector to be visible. |
| 1222 | /// |
| 1223 | /// Deprecated. |
| 1224 | /// Use WidgetsBinding.instance.debugShowWidgetInspectorOverrideNotifier.value |
| 1225 | /// instead. |
| 1226 | /// |
| 1227 | /// Overrides the `debugShowWidgetInspector` value set in [WidgetsApp]. |
| 1228 | /// |
| 1229 | /// Used by the `debugShowWidgetInspector` debugging extension. |
| 1230 | /// |
| 1231 | /// The inspector allows the selection of a location on your device or emulator |
| 1232 | /// and view what widgets and render objects associated with it. An outline of |
| 1233 | /// the selected widget and some summary information is shown on device and |
| 1234 | /// more detailed information is shown in the IDE or DevTools. |
| 1235 | @Deprecated( |
| 1236 | 'Use WidgetsBinding.instance.debugShowWidgetInspectorOverrideNotifier.value instead. ' |
| 1237 | 'This feature was deprecated after v3.20.0-14.0.pre.' , |
| 1238 | ) |
| 1239 | static bool get debugShowWidgetInspectorOverride { |
| 1240 | return WidgetsBinding.instance.debugShowWidgetInspectorOverrideNotifier.value; |
| 1241 | } |
| 1242 | |
| 1243 | @Deprecated( |
| 1244 | 'Use WidgetsBinding.instance.debugShowWidgetInspectorOverrideNotifier.value instead. ' |
| 1245 | 'This feature was deprecated after v3.20.0-14.0.pre.' , |
| 1246 | ) |
| 1247 | static set debugShowWidgetInspectorOverride(bool value) { |
| 1248 | WidgetsBinding.instance.debugShowWidgetInspectorOverrideNotifier.value = value; |
| 1249 | } |
| 1250 | |
| 1251 | /// If false, prevents the debug banner from being visible. |
| 1252 | /// |
| 1253 | /// Used by the `debugAllowBanner` VM service extension. |
| 1254 | /// |
| 1255 | /// This is how `flutter run` turns off the banner when you take a screen shot |
| 1256 | /// with "s". |
| 1257 | static bool debugAllowBannerOverride = true; |
| 1258 | |
| 1259 | static const Map<ShortcutActivator, Intent> _defaultShortcuts = <ShortcutActivator, Intent>{ |
| 1260 | // Activation |
| 1261 | SingleActivator(LogicalKeyboardKey.enter): ActivateIntent(), |
| 1262 | SingleActivator(LogicalKeyboardKey.numpadEnter): ActivateIntent(), |
| 1263 | SingleActivator(LogicalKeyboardKey.space): ActivateIntent(), |
| 1264 | SingleActivator(LogicalKeyboardKey.gameButtonA): ActivateIntent(), |
| 1265 | SingleActivator(LogicalKeyboardKey.select): ActivateIntent(), |
| 1266 | |
| 1267 | // Dismissal |
| 1268 | SingleActivator(LogicalKeyboardKey.escape): DismissIntent(), |
| 1269 | |
| 1270 | // Keyboard traversal. |
| 1271 | SingleActivator(LogicalKeyboardKey.tab): NextFocusIntent(), |
| 1272 | SingleActivator(LogicalKeyboardKey.tab, shift: true): PreviousFocusIntent(), |
| 1273 | SingleActivator(LogicalKeyboardKey.arrowLeft): DirectionalFocusIntent(TraversalDirection.left), |
| 1274 | SingleActivator(LogicalKeyboardKey.arrowRight): DirectionalFocusIntent( |
| 1275 | TraversalDirection.right, |
| 1276 | ), |
| 1277 | SingleActivator(LogicalKeyboardKey.arrowDown): DirectionalFocusIntent(TraversalDirection.down), |
| 1278 | SingleActivator(LogicalKeyboardKey.arrowUp): DirectionalFocusIntent(TraversalDirection.up), |
| 1279 | |
| 1280 | // Scrolling |
| 1281 | SingleActivator(LogicalKeyboardKey.arrowUp, control: true): ScrollIntent( |
| 1282 | direction: AxisDirection.up, |
| 1283 | ), |
| 1284 | SingleActivator(LogicalKeyboardKey.arrowDown, control: true): ScrollIntent( |
| 1285 | direction: AxisDirection.down, |
| 1286 | ), |
| 1287 | SingleActivator(LogicalKeyboardKey.arrowLeft, control: true): ScrollIntent( |
| 1288 | direction: AxisDirection.left, |
| 1289 | ), |
| 1290 | SingleActivator(LogicalKeyboardKey.arrowRight, control: true): ScrollIntent( |
| 1291 | direction: AxisDirection.right, |
| 1292 | ), |
| 1293 | SingleActivator(LogicalKeyboardKey.pageUp): ScrollIntent( |
| 1294 | direction: AxisDirection.up, |
| 1295 | type: ScrollIncrementType.page, |
| 1296 | ), |
| 1297 | SingleActivator(LogicalKeyboardKey.pageDown): ScrollIntent( |
| 1298 | direction: AxisDirection.down, |
| 1299 | type: ScrollIncrementType.page, |
| 1300 | ), |
| 1301 | }; |
| 1302 | |
| 1303 | // Default shortcuts for the web platform. |
| 1304 | static const Map<ShortcutActivator, Intent> _defaultWebShortcuts = <ShortcutActivator, Intent>{ |
| 1305 | // Activation |
| 1306 | SingleActivator(LogicalKeyboardKey.space): PrioritizedIntents( |
| 1307 | orderedIntents: <Intent>[ |
| 1308 | ActivateIntent(), |
| 1309 | ScrollIntent(direction: AxisDirection.down, type: ScrollIncrementType.page), |
| 1310 | ], |
| 1311 | ), |
| 1312 | // On the web, enter activates buttons, but not other controls. |
| 1313 | SingleActivator(LogicalKeyboardKey.enter): ButtonActivateIntent(), |
| 1314 | SingleActivator(LogicalKeyboardKey.numpadEnter): ButtonActivateIntent(), |
| 1315 | |
| 1316 | // Dismissal |
| 1317 | SingleActivator(LogicalKeyboardKey.escape): DismissIntent(), |
| 1318 | |
| 1319 | // Keyboard traversal. |
| 1320 | SingleActivator(LogicalKeyboardKey.tab): NextFocusIntent(), |
| 1321 | SingleActivator(LogicalKeyboardKey.tab, shift: true): PreviousFocusIntent(), |
| 1322 | |
| 1323 | // Scrolling |
| 1324 | SingleActivator(LogicalKeyboardKey.arrowUp): ScrollIntent(direction: AxisDirection.up), |
| 1325 | SingleActivator(LogicalKeyboardKey.arrowDown): ScrollIntent(direction: AxisDirection.down), |
| 1326 | SingleActivator(LogicalKeyboardKey.arrowLeft): ScrollIntent(direction: AxisDirection.left), |
| 1327 | SingleActivator(LogicalKeyboardKey.arrowRight): ScrollIntent(direction: AxisDirection.right), |
| 1328 | SingleActivator(LogicalKeyboardKey.pageUp): ScrollIntent( |
| 1329 | direction: AxisDirection.up, |
| 1330 | type: ScrollIncrementType.page, |
| 1331 | ), |
| 1332 | SingleActivator(LogicalKeyboardKey.pageDown): ScrollIntent( |
| 1333 | direction: AxisDirection.down, |
| 1334 | type: ScrollIncrementType.page, |
| 1335 | ), |
| 1336 | }; |
| 1337 | |
| 1338 | // Default shortcuts for the macOS platform. |
| 1339 | static const Map<ShortcutActivator, Intent> |
| 1340 | _defaultAppleOsShortcuts = <ShortcutActivator, Intent>{ |
| 1341 | // Activation |
| 1342 | SingleActivator(LogicalKeyboardKey.enter): ActivateIntent(), |
| 1343 | SingleActivator(LogicalKeyboardKey.numpadEnter): ActivateIntent(), |
| 1344 | SingleActivator(LogicalKeyboardKey.space): ActivateIntent(), |
| 1345 | |
| 1346 | // Dismissal |
| 1347 | SingleActivator(LogicalKeyboardKey.escape): DismissIntent(), |
| 1348 | |
| 1349 | // Keyboard traversal |
| 1350 | SingleActivator(LogicalKeyboardKey.tab): NextFocusIntent(), |
| 1351 | SingleActivator(LogicalKeyboardKey.tab, shift: true): PreviousFocusIntent(), |
| 1352 | SingleActivator(LogicalKeyboardKey.arrowLeft): DirectionalFocusIntent(TraversalDirection.left), |
| 1353 | SingleActivator(LogicalKeyboardKey.arrowRight): DirectionalFocusIntent( |
| 1354 | TraversalDirection.right, |
| 1355 | ), |
| 1356 | SingleActivator(LogicalKeyboardKey.arrowDown): DirectionalFocusIntent(TraversalDirection.down), |
| 1357 | SingleActivator(LogicalKeyboardKey.arrowUp): DirectionalFocusIntent(TraversalDirection.up), |
| 1358 | |
| 1359 | // Scrolling |
| 1360 | SingleActivator(LogicalKeyboardKey.arrowUp, meta: true): ScrollIntent( |
| 1361 | direction: AxisDirection.up, |
| 1362 | ), |
| 1363 | SingleActivator(LogicalKeyboardKey.arrowDown, meta: true): ScrollIntent( |
| 1364 | direction: AxisDirection.down, |
| 1365 | ), |
| 1366 | SingleActivator(LogicalKeyboardKey.arrowLeft, meta: true): ScrollIntent( |
| 1367 | direction: AxisDirection.left, |
| 1368 | ), |
| 1369 | SingleActivator(LogicalKeyboardKey.arrowRight, meta: true): ScrollIntent( |
| 1370 | direction: AxisDirection.right, |
| 1371 | ), |
| 1372 | SingleActivator(LogicalKeyboardKey.pageUp): ScrollIntent( |
| 1373 | direction: AxisDirection.up, |
| 1374 | type: ScrollIncrementType.page, |
| 1375 | ), |
| 1376 | SingleActivator(LogicalKeyboardKey.pageDown): ScrollIntent( |
| 1377 | direction: AxisDirection.down, |
| 1378 | type: ScrollIncrementType.page, |
| 1379 | ), |
| 1380 | }; |
| 1381 | |
| 1382 | /// Generates the default shortcut key bindings based on the |
| 1383 | /// [defaultTargetPlatform]. |
| 1384 | /// |
| 1385 | /// Used by [WidgetsApp] to assign a default value to [WidgetsApp.shortcuts]. |
| 1386 | static Map<ShortcutActivator, Intent> get defaultShortcuts { |
| 1387 | if (kIsWeb) { |
| 1388 | return _defaultWebShortcuts; |
| 1389 | } |
| 1390 | |
| 1391 | switch (defaultTargetPlatform) { |
| 1392 | case TargetPlatform.android: |
| 1393 | case TargetPlatform.fuchsia: |
| 1394 | case TargetPlatform.linux: |
| 1395 | case TargetPlatform.windows: |
| 1396 | return _defaultShortcuts; |
| 1397 | case TargetPlatform.iOS: |
| 1398 | case TargetPlatform.macOS: |
| 1399 | return _defaultAppleOsShortcuts; |
| 1400 | } |
| 1401 | } |
| 1402 | |
| 1403 | /// The default value of [WidgetsApp.actions]. |
| 1404 | static Map<Type, Action<Intent>> defaultActions = <Type, Action<Intent>>{ |
| 1405 | DoNothingIntent: DoNothingAction(), |
| 1406 | DoNothingAndStopPropagationIntent: DoNothingAction(consumesKey: false), |
| 1407 | RequestFocusIntent: RequestFocusAction(), |
| 1408 | NextFocusIntent: NextFocusAction(), |
| 1409 | PreviousFocusIntent: PreviousFocusAction(), |
| 1410 | DirectionalFocusIntent: DirectionalFocusAction(), |
| 1411 | ScrollIntent: ScrollAction(), |
| 1412 | PrioritizedIntents: PrioritizedAction(), |
| 1413 | VoidCallbackIntent: VoidCallbackAction(), |
| 1414 | }; |
| 1415 | |
| 1416 | @override |
| 1417 | State<WidgetsApp> createState() => _WidgetsAppState(); |
| 1418 | } |
| 1419 | |
| 1420 | class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver { |
| 1421 | // STATE LIFECYCLE |
| 1422 | |
| 1423 | // If window.defaultRouteName isn't '/', we should assume it was set |
| 1424 | // intentionally via `setInitialRoute`, and should override whatever is in |
| 1425 | // [widget.initialRoute]. |
| 1426 | String get _initialRouteName => |
| 1427 | WidgetsBinding.instance.platformDispatcher.defaultRouteName != Navigator.defaultRouteName |
| 1428 | ? WidgetsBinding.instance.platformDispatcher.defaultRouteName |
| 1429 | : widget.initialRoute ?? WidgetsBinding.instance.platformDispatcher.defaultRouteName; |
| 1430 | |
| 1431 | AppLifecycleState? _appLifecycleState; |
| 1432 | |
| 1433 | /// The default value for [WidgetsApp.onNavigationNotification]. |
| 1434 | /// |
| 1435 | /// Does nothing and stops bubbling if the app is detached. Otherwise, updates |
| 1436 | /// the platform with [NavigationNotification.canHandlePop] and stops |
| 1437 | /// bubbling. |
| 1438 | bool _defaultOnNavigationNotification(NavigationNotification notification) { |
| 1439 | switch (_appLifecycleState) { |
| 1440 | case null: |
| 1441 | case AppLifecycleState.detached: |
| 1442 | // Avoid updating the engine when the app isn't ready. |
| 1443 | return true; |
| 1444 | case AppLifecycleState.inactive: |
| 1445 | case AppLifecycleState.resumed: |
| 1446 | case AppLifecycleState.hidden: |
| 1447 | case AppLifecycleState.paused: |
| 1448 | SystemNavigator.setFrameworkHandlesBack(notification.canHandlePop); |
| 1449 | return true; |
| 1450 | } |
| 1451 | } |
| 1452 | |
| 1453 | @override |
| 1454 | void didChangeAppLifecycleState(AppLifecycleState state) { |
| 1455 | _appLifecycleState = state; |
| 1456 | super.didChangeAppLifecycleState(state); |
| 1457 | } |
| 1458 | |
| 1459 | @override |
| 1460 | void initState() { |
| 1461 | super.initState(); |
| 1462 | _updateRouting(); |
| 1463 | WidgetsBinding.instance.addObserver(this); |
| 1464 | _appLifecycleState = WidgetsBinding.instance.lifecycleState; |
| 1465 | } |
| 1466 | |
| 1467 | @override |
| 1468 | void didUpdateWidget(WidgetsApp oldWidget) { |
| 1469 | super.didUpdateWidget(oldWidget); |
| 1470 | _updateRouting(oldWidget: oldWidget); |
| 1471 | _updateLocalizations(oldWidget: oldWidget); |
| 1472 | } |
| 1473 | |
| 1474 | @override |
| 1475 | void dispose() { |
| 1476 | WidgetsBinding.instance.removeObserver(this); |
| 1477 | _defaultRouteInformationProvider?.dispose(); |
| 1478 | _localizationsResolver.dispose(); |
| 1479 | super.dispose(); |
| 1480 | } |
| 1481 | |
| 1482 | void _clearRouterResource() { |
| 1483 | _defaultRouteInformationProvider?.dispose(); |
| 1484 | _defaultRouteInformationProvider = null; |
| 1485 | _defaultBackButtonDispatcher = null; |
| 1486 | } |
| 1487 | |
| 1488 | void _clearNavigatorResource() { |
| 1489 | _navigator = null; |
| 1490 | } |
| 1491 | |
| 1492 | void _updateRouting({WidgetsApp? oldWidget}) { |
| 1493 | if (_usesRouterWithDelegates) { |
| 1494 | assert(!_usesNavigator && !_usesRouterWithConfig); |
| 1495 | _clearNavigatorResource(); |
| 1496 | if (widget.routeInformationProvider == null && widget.routeInformationParser != null) { |
| 1497 | _defaultRouteInformationProvider ??= PlatformRouteInformationProvider( |
| 1498 | initialRouteInformation: RouteInformation(uri: Uri.parse(_initialRouteName)), |
| 1499 | ); |
| 1500 | } else { |
| 1501 | _defaultRouteInformationProvider?.dispose(); |
| 1502 | _defaultRouteInformationProvider = null; |
| 1503 | } |
| 1504 | if (widget.backButtonDispatcher == null) { |
| 1505 | _defaultBackButtonDispatcher ??= RootBackButtonDispatcher(); |
| 1506 | } |
| 1507 | } else if (_usesNavigator) { |
| 1508 | assert(!_usesRouterWithDelegates && !_usesRouterWithConfig); |
| 1509 | _clearRouterResource(); |
| 1510 | if (_navigator == null || widget.navigatorKey != oldWidget!.navigatorKey) { |
| 1511 | _navigator = widget.navigatorKey ?? GlobalObjectKey<NavigatorState>(this); |
| 1512 | } |
| 1513 | assert(_navigator != null); |
| 1514 | } else { |
| 1515 | assert(widget.builder != null || _usesRouterWithConfig); |
| 1516 | assert(!_usesRouterWithDelegates && !_usesNavigator); |
| 1517 | _clearRouterResource(); |
| 1518 | _clearNavigatorResource(); |
| 1519 | } |
| 1520 | // If we use a navigator, we have a navigator key. |
| 1521 | assert(_usesNavigator == (_navigator != null)); |
| 1522 | } |
| 1523 | |
| 1524 | bool get _usesRouterWithDelegates => widget.routerDelegate != null; |
| 1525 | bool get _usesRouterWithConfig => widget.routerConfig != null; |
| 1526 | bool get _usesNavigator => |
| 1527 | widget.home != null || |
| 1528 | (widget.routes?.isNotEmpty ?? false) || |
| 1529 | widget.onGenerateRoute != null || |
| 1530 | widget.onUnknownRoute != null; |
| 1531 | |
| 1532 | // ROUTER |
| 1533 | |
| 1534 | RouteInformationProvider? get _effectiveRouteInformationProvider => |
| 1535 | widget.routeInformationProvider ?? _defaultRouteInformationProvider; |
| 1536 | PlatformRouteInformationProvider? _defaultRouteInformationProvider; |
| 1537 | BackButtonDispatcher get _effectiveBackButtonDispatcher => |
| 1538 | widget.backButtonDispatcher ?? _defaultBackButtonDispatcher!; |
| 1539 | RootBackButtonDispatcher? _defaultBackButtonDispatcher; |
| 1540 | |
| 1541 | // NAVIGATOR |
| 1542 | |
| 1543 | GlobalKey<NavigatorState>? _navigator; |
| 1544 | |
| 1545 | Route<dynamic>? _onGenerateRoute(RouteSettings settings) { |
| 1546 | final String? name = settings.name; |
| 1547 | final WidgetBuilder? pageContentBuilder = |
| 1548 | name == Navigator.defaultRouteName && widget.home != null |
| 1549 | ? (BuildContext context) => widget.home! |
| 1550 | : widget.routes![name]; |
| 1551 | |
| 1552 | if (pageContentBuilder != null) { |
| 1553 | assert( |
| 1554 | widget.pageRouteBuilder != null, |
| 1555 | 'The default onGenerateRoute handler for WidgetsApp must have a ' |
| 1556 | 'pageRouteBuilder set if the home or routes properties are set.' , |
| 1557 | ); |
| 1558 | final Route<dynamic> route = widget.pageRouteBuilder!<dynamic>(settings, pageContentBuilder); |
| 1559 | return route; |
| 1560 | } |
| 1561 | if (widget.onGenerateRoute != null) { |
| 1562 | return widget.onGenerateRoute!(settings); |
| 1563 | } |
| 1564 | return null; |
| 1565 | } |
| 1566 | |
| 1567 | Route<dynamic> _onUnknownRoute(RouteSettings settings) { |
| 1568 | assert(() { |
| 1569 | if (widget.onUnknownRoute == null) { |
| 1570 | throw FlutterError( |
| 1571 | 'Could not find a generator for route $settings in the $runtimeType.\n' |
| 1572 | 'Make sure your root app widget has provided a way to generate \n' |
| 1573 | 'this route.\n' |
| 1574 | 'Generators for routes are searched for in the following order:\n' |
| 1575 | ' 1. For the "/" route, the "home" property, if non-null, is used.\n' |
| 1576 | ' 2. Otherwise, the "routes" table is used, if it has an entry for ' |
| 1577 | 'the route.\n' |
| 1578 | ' 3. Otherwise, onGenerateRoute is called. It should return a ' |
| 1579 | 'non-null value for any valid route not handled by "home" and "routes".\n' |
| 1580 | ' 4. Finally if all else fails onUnknownRoute is called.\n' |
| 1581 | 'Unfortunately, onUnknownRoute was not set.' , |
| 1582 | ); |
| 1583 | } |
| 1584 | return true; |
| 1585 | }()); |
| 1586 | final Route<dynamic>? result = widget.onUnknownRoute!(settings); |
| 1587 | assert(() { |
| 1588 | if (result == null) { |
| 1589 | throw FlutterError( |
| 1590 | 'The onUnknownRoute callback returned null.\n' |
| 1591 | 'When the $runtimeType requested the route $settings from its ' |
| 1592 | 'onUnknownRoute callback, the callback returned null. Such callbacks ' |
| 1593 | 'must never return null.' , |
| 1594 | ); |
| 1595 | } |
| 1596 | return true; |
| 1597 | }()); |
| 1598 | return result!; |
| 1599 | } |
| 1600 | |
| 1601 | // On Android: the user has pressed the back button. |
| 1602 | @override |
| 1603 | Future<bool> didPopRoute() async { |
| 1604 | assert(mounted); |
| 1605 | // The back button dispatcher should handle the pop route if we use a |
| 1606 | // router. |
| 1607 | if (_usesRouterWithDelegates) { |
| 1608 | return false; |
| 1609 | } |
| 1610 | |
| 1611 | final NavigatorState? navigator = _navigator?.currentState; |
| 1612 | if (navigator == null) { |
| 1613 | return false; |
| 1614 | } |
| 1615 | return navigator.maybePop(); |
| 1616 | } |
| 1617 | |
| 1618 | @override |
| 1619 | Future<bool> didPushRouteInformation(RouteInformation routeInformation) async { |
| 1620 | assert(mounted); |
| 1621 | // The route name provider should handle the push route if we uses a |
| 1622 | // router. |
| 1623 | if (_usesRouterWithDelegates) { |
| 1624 | return false; |
| 1625 | } |
| 1626 | |
| 1627 | final NavigatorState? navigator = _navigator?.currentState; |
| 1628 | if (navigator == null) { |
| 1629 | return false; |
| 1630 | } |
| 1631 | final Uri uri = routeInformation.uri; |
| 1632 | navigator.pushNamed( |
| 1633 | Uri.decodeComponent( |
| 1634 | Uri( |
| 1635 | path: uri.path.isEmpty ? '/' : uri.path, |
| 1636 | queryParameters: uri.queryParametersAll.isEmpty ? null : uri.queryParametersAll, |
| 1637 | fragment: uri.fragment.isEmpty ? null : uri.fragment, |
| 1638 | ).toString(), |
| 1639 | ), |
| 1640 | ); |
| 1641 | return true; |
| 1642 | } |
| 1643 | |
| 1644 | // LOCALIZATION |
| 1645 | late final LocalizationsResolver _localizationsResolver = LocalizationsResolver( |
| 1646 | locale: widget.locale, |
| 1647 | localeListResolutionCallback: widget.localeListResolutionCallback, |
| 1648 | localeResolutionCallback: widget.localeResolutionCallback, |
| 1649 | localizationsDelegates: widget.localizationsDelegates, |
| 1650 | supportedLocales: widget.supportedLocales, |
| 1651 | ); |
| 1652 | |
| 1653 | void _updateLocalizations({WidgetsApp? oldWidget}) { |
| 1654 | _localizationsResolver.update( |
| 1655 | locale: widget.locale, |
| 1656 | localeListResolutionCallback: widget.localeListResolutionCallback, |
| 1657 | localeResolutionCallback: widget.localeResolutionCallback, |
| 1658 | supportedLocales: widget.supportedLocales, |
| 1659 | localizationsDelegates: widget.localizationsDelegates, |
| 1660 | ); |
| 1661 | } |
| 1662 | |
| 1663 | // BUILDER |
| 1664 | |
| 1665 | @override |
| 1666 | Widget build(BuildContext context) { |
| 1667 | Widget? routing; |
| 1668 | if (_usesRouterWithDelegates) { |
| 1669 | routing = Router<Object>( |
| 1670 | restorationScopeId: 'router' , |
| 1671 | routeInformationProvider: _effectiveRouteInformationProvider, |
| 1672 | routeInformationParser: widget.routeInformationParser, |
| 1673 | routerDelegate: widget.routerDelegate!, |
| 1674 | backButtonDispatcher: _effectiveBackButtonDispatcher, |
| 1675 | ); |
| 1676 | } else if (_usesNavigator) { |
| 1677 | assert(_navigator != null); |
| 1678 | routing = FocusScope( |
| 1679 | debugLabel: 'Navigator Scope' , |
| 1680 | autofocus: true, |
| 1681 | child: Navigator( |
| 1682 | clipBehavior: Clip.none, |
| 1683 | restorationScopeId: 'nav' , |
| 1684 | key: _navigator, |
| 1685 | initialRoute: _initialRouteName, |
| 1686 | onGenerateRoute: _onGenerateRoute, |
| 1687 | onGenerateInitialRoutes: widget.onGenerateInitialRoutes == null |
| 1688 | ? Navigator.defaultGenerateInitialRoutes |
| 1689 | : (NavigatorState navigator, String initialRouteName) { |
| 1690 | return widget.onGenerateInitialRoutes!(initialRouteName); |
| 1691 | }, |
| 1692 | onUnknownRoute: _onUnknownRoute, |
| 1693 | observers: widget.navigatorObservers!, |
| 1694 | routeTraversalEdgeBehavior: kIsWeb |
| 1695 | ? TraversalEdgeBehavior.leaveFlutterView |
| 1696 | : TraversalEdgeBehavior.parentScope, |
| 1697 | reportsRouteUpdateToEngine: true, |
| 1698 | ), |
| 1699 | ); |
| 1700 | } else if (_usesRouterWithConfig) { |
| 1701 | routing = Router<Object>.withConfig( |
| 1702 | restorationScopeId: 'router' , |
| 1703 | config: widget.routerConfig!, |
| 1704 | ); |
| 1705 | } |
| 1706 | |
| 1707 | Widget result; |
| 1708 | if (widget.builder != null) { |
| 1709 | result = Builder( |
| 1710 | builder: (BuildContext context) { |
| 1711 | return widget.builder!(context, routing); |
| 1712 | }, |
| 1713 | ); |
| 1714 | } else { |
| 1715 | assert(routing != null); |
| 1716 | result = routing!; |
| 1717 | } |
| 1718 | |
| 1719 | if (widget.textStyle != null) { |
| 1720 | result = DefaultTextStyle(style: widget.textStyle!, child: result); |
| 1721 | } |
| 1722 | |
| 1723 | if (widget.showPerformanceOverlay || WidgetsApp.showPerformanceOverlayOverride) { |
| 1724 | result = Stack( |
| 1725 | children: <Widget>[ |
| 1726 | result, |
| 1727 | Positioned(top: 0.0, left: 0.0, right: 0.0, child: PerformanceOverlay.allEnabled()), |
| 1728 | ], |
| 1729 | ); |
| 1730 | } |
| 1731 | |
| 1732 | if (widget.showSemanticsDebugger) { |
| 1733 | result = SemanticsDebugger(child: result); |
| 1734 | } |
| 1735 | |
| 1736 | assert(() { |
| 1737 | if (!WidgetsBinding.instance.debugExcludeRootWidgetInspector) { |
| 1738 | result = ValueListenableBuilder<bool>( |
| 1739 | valueListenable: WidgetsBinding.instance.debugShowWidgetInspectorOverrideNotifier, |
| 1740 | builder: (BuildContext context, bool debugShowWidgetInspectorOverride, Widget? child) { |
| 1741 | if (widget.debugShowWidgetInspector || debugShowWidgetInspectorOverride) { |
| 1742 | return WidgetInspector( |
| 1743 | exitWidgetSelectionButtonBuilder: widget.exitWidgetSelectionButtonBuilder, |
| 1744 | moveExitWidgetSelectionButtonBuilder: widget.moveExitWidgetSelectionButtonBuilder, |
| 1745 | tapBehaviorButtonBuilder: widget.tapBehaviorButtonBuilder, |
| 1746 | child: child!, |
| 1747 | ); |
| 1748 | } |
| 1749 | return child!; |
| 1750 | }, |
| 1751 | child: result, |
| 1752 | ); |
| 1753 | } |
| 1754 | if (widget.debugShowCheckedModeBanner && WidgetsApp.debugAllowBannerOverride) { |
| 1755 | result = CheckedModeBanner(child: result); |
| 1756 | } |
| 1757 | return true; |
| 1758 | }()); |
| 1759 | |
| 1760 | final Widget? title; |
| 1761 | if (widget.onGenerateTitle != null) { |
| 1762 | title = Builder( |
| 1763 | // This Builder exists to provide a context below the Localizations widget. |
| 1764 | // The onGenerateTitle callback can refer to Localizations via its context |
| 1765 | // parameter. |
| 1766 | builder: (BuildContext context) { |
| 1767 | final String title = widget.onGenerateTitle!(context); |
| 1768 | return Title(title: title, color: widget.color.withOpacity(1.0), child: result); |
| 1769 | }, |
| 1770 | ); |
| 1771 | } else if (widget.title == null && kIsWeb) { |
| 1772 | // Updating the element in the DOM is problematic in embedded |
| 1773 | // and multiview modes as title should be managed by host apps. |
| 1774 | // Refer to https://github.com/flutter/flutter/pull/152003 for more info. |
| 1775 | title = null; |
| 1776 | } else { |
| 1777 | title = Title(title: widget.title ?? '' , color: widget.color.withOpacity(1.0), child: result); |
| 1778 | } |
| 1779 | |
| 1780 | return RootRestorationScope( |
| 1781 | restorationId: widget.restorationScopeId, |
| 1782 | child: SharedAppData( |
| 1783 | child: NotificationListener<NavigationNotification>( |
| 1784 | onNotification: widget.onNavigationNotification ?? _defaultOnNavigationNotification, |
| 1785 | child: Shortcuts( |
| 1786 | debugLabel: '<Default WidgetsApp Shortcuts>' , |
| 1787 | shortcuts: widget.shortcuts ?? WidgetsApp.defaultShortcuts, |
| 1788 | // DefaultTextEditingShortcuts is nested inside Shortcuts so that it can |
| 1789 | // fall through to the defaultShortcuts. |
| 1790 | child: DefaultTextEditingShortcuts( |
| 1791 | child: Actions( |
| 1792 | actions: |
| 1793 | widget.actions ?? |
| 1794 | <Type, Action<Intent>>{ |
| 1795 | ...WidgetsApp.defaultActions, |
| 1796 | ScrollIntent: Action<ScrollIntent>.overridable( |
| 1797 | context: context, |
| 1798 | defaultAction: ScrollAction(), |
| 1799 | ), |
| 1800 | }, |
| 1801 | child: FocusTraversalGroup( |
| 1802 | policy: ReadingOrderTraversalPolicy(), |
| 1803 | child: TapRegionSurface( |
| 1804 | child: ShortcutRegistrar( |
| 1805 | child: ListenableBuilder( |
| 1806 | listenable: _localizationsResolver, |
| 1807 | builder: (BuildContext context, _) { |
| 1808 | return Localizations( |
| 1809 | isApplicationLevel: true, |
| 1810 | locale: _localizationsResolver.locale, |
| 1811 | delegates: _localizationsResolver.localizationsDelegates.toList(), |
| 1812 | child: title ?? result, |
| 1813 | ); |
| 1814 | }, |
| 1815 | ), |
| 1816 | ), |
| 1817 | ), |
| 1818 | ), |
| 1819 | ), |
| 1820 | ), |
| 1821 | ), |
| 1822 | ), |
| 1823 | ), |
| 1824 | ); |
| 1825 | } |
| 1826 | } |
| 1827 | |