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