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