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';
7library;
8
9import 'dart:async';
10import 'dart:ui';
11
12import 'package:flutter/foundation.dart';
13
14import 'binding.dart';
15import 'system_channels.dart';
16
17export 'dart:ui' show Brightness, Color;
18
19export 'binding.dart' show SystemUiChangeCallback;
20
21/// Specifies a particular device orientation.
22///
23/// To determine which values correspond to which orientations, first position
24/// the device in its default orientation (this is the orientation that the
25/// system first uses for its boot logo, or the orientation in which the
26/// hardware logos or markings are upright, or the orientation in which the
27/// cameras are at the top). If this is a portrait orientation, then this is
28/// [portraitUp]. Otherwise, it's [landscapeLeft]. As you rotate the device by
29/// 90 degrees in a counter-clockwise direction around the axis that pierces the
30/// screen, you step through each value in this enum in the order given.
31///
32/// For a device with a landscape default orientation, the orientation obtained
33/// by rotating the device 90 degrees clockwise from its default orientation is
34/// [portraitUp].
35///
36/// Used by [SystemChrome.setPreferredOrientations].
37enum DeviceOrientation {
38 /// If the device shows its boot logo in portrait, then the boot logo is shown
39 /// in [portraitUp]. Otherwise, the device shows its boot logo in landscape
40 /// and this orientation is obtained by rotating the device 90 degrees
41 /// clockwise from its boot orientation.
42 portraitUp,
43
44 /// The orientation that is 90 degrees counterclockwise from [portraitUp].
45 ///
46 /// If the device shows its boot logo in landscape, then the boot logo is
47 /// shown in [landscapeLeft].
48 landscapeLeft,
49
50 /// The orientation that is 180 degrees from [portraitUp].
51 portraitDown,
52
53 /// The orientation that is 90 degrees clockwise from [portraitUp].
54 landscapeRight,
55}
56
57/// Specifies a description of the application that is pertinent to the
58/// embedder's application switcher (also known as "recent tasks") user
59/// interface.
60///
61/// Used by [SystemChrome.setApplicationSwitcherDescription].
62@immutable
63class ApplicationSwitcherDescription {
64 /// Creates an ApplicationSwitcherDescription.
65 const ApplicationSwitcherDescription({ this.label, this.primaryColor });
66
67 /// A label and description of the current state of the application.
68 final String? label;
69
70 /// The application's primary color.
71 ///
72 /// This may influence the color that the operating system uses to represent
73 /// the application.
74 final int? primaryColor;
75}
76
77/// Specifies a system overlay at a particular location.
78///
79/// Used by [SystemChrome.setEnabledSystemUIMode].
80enum SystemUiOverlay {
81 /// The status bar provided by the embedder on the top of the application
82 /// surface, if any.
83 top,
84
85 /// The status bar provided by the embedder on the bottom of the application
86 /// surface, if any.
87 bottom,
88}
89
90/// Describes different display configurations for both Android and iOS.
91///
92/// These modes mimic Android-specific display setups.
93///
94/// Used by [SystemChrome.setEnabledSystemUIMode].
95///
96/// Flutter apps use [SystemUiMode.edgeToEdge] by default and setting any
97/// of the other [SystemUiMode]s will NOT work unless you perform the migration
98/// detailed in
99/// https://docs.flutter.dev/release/breaking-changes/default-systemuimode-edge-to-edge.
100enum SystemUiMode {
101 /// Fullscreen display with status and navigation bars presentable by tapping
102 /// anywhere on the display.
103 ///
104 /// Available starting at Android SDK 4.1 (API 16). Earlier versions of Android
105 /// will not be affected by this setting. However, if your app targets Android
106 /// SDK 15 (API 35) or later (Flutter does this by default), then you must
107 /// migrate using the instructions in
108 /// https://docs.flutter.dev/release/breaking-changes/default-systemuimode-edge-to-edge
109 /// to use this mode.
110 ///
111 /// For applications running on iOS, the status bar and home indicator will be
112 /// hidden for a similar fullscreen experience.
113 ///
114 /// Tapping on the screen displays overlays, this gesture is not received by
115 /// the application.
116 ///
117 /// See also:
118 ///
119 /// * [SystemUiChangeCallback], used to listen and respond to the change in
120 /// system overlays.
121 leanBack,
122
123 /// Fullscreen display with status and navigation bars presentable through a
124 /// swipe gesture at the edges of the display.
125 ///
126 /// Available starting at Android SDK 4.4 (API 19). Earlier versions of
127 /// Android will not be affected by this setting. However, if your app targets
128 /// Android SDK 15 (API 35) or later (Flutter does this by default), then you
129 /// must migrate using the instructions in
130 /// https://docs.flutter.dev/release/breaking-changes/default-systemuimode-edge-to-edge
131 /// to use this mode.
132 ///
133 /// For applications running on iOS, the status bar and home indicator will be
134 /// hidden for a similar fullscreen experience.
135 ///
136 /// A swipe gesture from the edge of the screen displays overlays. In contrast
137 /// to [SystemUiMode.immersiveSticky], this gesture is not received by the
138 /// application.
139 ///
140 /// See also:
141 ///
142 /// * [SystemUiChangeCallback], used to listen and respond to the change in
143 /// system overlays.
144 immersive,
145
146 /// Fullscreen display with status and navigation bars presentable through a
147 /// swipe gesture at the edges of the display.
148 ///
149 /// Available starting at Android SDK 4.4 (API 19). Earlier versions of
150 /// Android will not be affected by this setting. However, if your app targets
151 /// Android SDK 15 (API 35) or later (Flutter does this by default), then you
152 /// must migrate using the instructions in
153 /// https://docs.flutter.dev/release/breaking-changes/default-systemuimode-edge-to-edge
154 /// to use this mode.
155 ///
156 /// For applications running on iOS, the status bar and home indicator will be
157 /// hidden for a similar fullscreen experience.
158 ///
159 /// A swipe gesture from the edge of the screen displays overlays. In contrast
160 /// to [SystemUiMode.immersive], this gesture is received by the application.
161 ///
162 /// See also:
163 ///
164 /// * [SystemUiChangeCallback], used to listen and respond to the change in
165 /// system overlays.
166 immersiveSticky,
167
168 /// Fullscreen display with status and navigation elements rendered over the
169 /// application.
170 ///
171 /// Available starting at Android SDK 10 (API 29). Earlier versions of Android
172 /// will not be affected by this setting.
173 ///
174 /// If your app targets Android SDK 15 (API 35) or later (Flutter does this by
175 /// default), then this mode is used by default on Android. This mode is also
176 /// used by default on iOS.
177 ///
178 /// For applications running on iOS, the status bar and home indicator will be
179 /// visible.
180 ///
181 /// The system overlays will not disappear or reappear in this mode as they
182 /// are permanently displayed on top of the application.
183 ///
184 /// See also:
185 ///
186 /// * [SystemUiOverlayStyle], can be used to configure transparent status and
187 /// navigation bars with or without a contrast scrim.
188 edgeToEdge,
189
190 /// Declares manually configured [SystemUiOverlay]s.
191 ///
192 /// When using this mode with [SystemChrome.setEnabledSystemUIMode], the
193 /// preferred overlays must be set by the developer.
194 ///
195 /// When [SystemUiOverlay.top] is enabled, the status bar will remain visible
196 /// on all platforms. Omitting this overlay will hide the status bar on iOS &
197 /// Android.
198 ///
199 /// When [SystemUiOverlay.bottom] is enabled, the navigation bar and home
200 /// indicator of Android and iOS applications will remain visible. Omitting this
201 /// overlay will hide them.
202 ///
203 /// Omitting both overlays will result in the same configuration as
204 /// [SystemUiMode.leanBack].
205 ///
206 /// If your app targets Android SDK 15 (API 35) or later, then you must
207 /// migrate using the instructions in
208 /// https://docs.flutter.dev/release/breaking-changes/default-systemuimode-edge-to-edge
209 /// to use this mode.
210 manual,
211}
212
213/// Specifies a preference for the style of the system overlays.
214///
215/// Used by [AppBar.systemOverlayStyle] for declaratively setting the style of
216/// the system overlays, and by [SystemChrome.setSystemUIOverlayStyle] for
217/// imperatively setting the style of the system overlays.
218@immutable
219class SystemUiOverlayStyle {
220 /// Creates a new [SystemUiOverlayStyle].
221 const SystemUiOverlayStyle({
222 this.systemNavigationBarColor,
223 this.systemNavigationBarDividerColor,
224 this.systemNavigationBarIconBrightness,
225 this.systemNavigationBarContrastEnforced,
226 this.statusBarColor,
227 this.statusBarBrightness,
228 this.statusBarIconBrightness,
229 this.systemStatusBarContrastEnforced,
230 });
231
232 /// The color of the system bottom navigation bar.
233 ///
234 /// Only honored in Android versions O and greater.
235 final Color? systemNavigationBarColor;
236
237 /// The color of the divider between the system's bottom navigation bar and the app's content.
238 ///
239 /// Only honored in Android versions P and greater.
240 final Color? systemNavigationBarDividerColor;
241
242 /// The brightness of the system navigation bar icons.
243 ///
244 /// Only honored in Android versions O and greater.
245 /// When set to [Brightness.light], the system navigation bar icons are light.
246 /// When set to [Brightness.dark], the system navigation bar icons are dark.
247 final Brightness? systemNavigationBarIconBrightness;
248
249 /// Overrides the contrast enforcement when setting a transparent navigation
250 /// bar.
251 ///
252 /// When setting a transparent navigation bar in SDK 29+, or Android 10 and up,
253 /// a translucent body scrim may be applied behind the button navigation bar
254 /// to ensure contrast with buttons and the background of the application.
255 ///
256 /// SDK 28-, or Android P and lower, will not apply this body scrim.
257 ///
258 /// Setting this to false overrides the default body scrim.
259 ///
260 /// See also:
261 ///
262 /// * [SystemUiOverlayStyle.systemNavigationBarColor], which is overridden
263 /// when transparent to enforce this contrast policy.
264 final bool? systemNavigationBarContrastEnforced;
265
266 /// The color of top status bar.
267 ///
268 /// Only honored in Android version M and greater.
269 final Color? statusBarColor;
270
271 /// The brightness of top status bar.
272 ///
273 /// Only honored in iOS.
274 final Brightness? statusBarBrightness;
275
276 /// The brightness of the top status bar icons.
277 ///
278 /// Only honored in Android version M and greater.
279 final Brightness? statusBarIconBrightness;
280
281 /// Overrides the contrast enforcement when setting a transparent status
282 /// bar.
283 ///
284 /// When setting a transparent status bar in SDK 29+, or Android 10 and up,
285 /// a translucent body scrim may be applied to ensure contrast with icons and
286 /// the background of the application.
287 ///
288 /// SDK 28-, or Android P and lower, will not apply this body scrim.
289 ///
290 /// Setting this to false overrides the default body scrim.
291 ///
292 /// See also:
293 ///
294 /// * [SystemUiOverlayStyle.statusBarColor], which is overridden
295 /// when transparent to enforce this contrast policy.
296 final bool? systemStatusBarContrastEnforced;
297
298 /// System overlays should be drawn with a light color. Intended for
299 /// applications with a dark background.
300 static const SystemUiOverlayStyle light = SystemUiOverlayStyle(
301 systemNavigationBarColor: Color(0xFF000000),
302 systemNavigationBarIconBrightness: Brightness.light,
303 statusBarIconBrightness: Brightness.light,
304 statusBarBrightness: Brightness.dark,
305 );
306
307 /// System overlays should be drawn with a dark color. Intended for
308 /// applications with a light background.
309 static const SystemUiOverlayStyle dark = SystemUiOverlayStyle(
310 systemNavigationBarColor: Color(0xFF000000),
311 systemNavigationBarIconBrightness: Brightness.light,
312 statusBarIconBrightness: Brightness.dark,
313 statusBarBrightness: Brightness.light,
314 );
315
316 /// Convert this event to a map for serialization.
317 Map<String, dynamic> _toMap() {
318 return <String, dynamic>{
319 'systemNavigationBarColor': systemNavigationBarColor?.value,
320 'systemNavigationBarDividerColor': systemNavigationBarDividerColor?.value,
321 'systemStatusBarContrastEnforced': systemStatusBarContrastEnforced,
322 'statusBarColor': statusBarColor?.value,
323 'statusBarBrightness': statusBarBrightness?.toString(),
324 'statusBarIconBrightness': statusBarIconBrightness?.toString(),
325 'systemNavigationBarIconBrightness': systemNavigationBarIconBrightness?.toString(),
326 'systemNavigationBarContrastEnforced': systemNavigationBarContrastEnforced,
327 };
328 }
329
330 @override
331 String toString() => '${objectRuntimeType(this, 'SystemUiOverlayStyle')}(${_toMap()})';
332
333 /// Creates a copy of this theme with the given fields replaced with new values.
334 SystemUiOverlayStyle copyWith({
335 Color? systemNavigationBarColor,
336 Color? systemNavigationBarDividerColor,
337 bool? systemNavigationBarContrastEnforced,
338 Color? statusBarColor,
339 Brightness? statusBarBrightness,
340 Brightness? statusBarIconBrightness,
341 bool? systemStatusBarContrastEnforced,
342 Brightness? systemNavigationBarIconBrightness,
343 }) {
344 return SystemUiOverlayStyle(
345 systemNavigationBarColor: systemNavigationBarColor ?? this.systemNavigationBarColor,
346 systemNavigationBarDividerColor: systemNavigationBarDividerColor ?? this.systemNavigationBarDividerColor,
347 systemNavigationBarContrastEnforced: systemNavigationBarContrastEnforced ?? this.systemNavigationBarContrastEnforced,
348 statusBarColor: statusBarColor ?? this.statusBarColor,
349 statusBarIconBrightness: statusBarIconBrightness ?? this.statusBarIconBrightness,
350 statusBarBrightness: statusBarBrightness ?? this.statusBarBrightness,
351 systemStatusBarContrastEnforced: systemStatusBarContrastEnforced ?? this.systemStatusBarContrastEnforced,
352 systemNavigationBarIconBrightness: systemNavigationBarIconBrightness ?? this.systemNavigationBarIconBrightness,
353 );
354 }
355
356 @override
357 int get hashCode => Object.hash(
358 systemNavigationBarColor,
359 systemNavigationBarDividerColor,
360 systemNavigationBarContrastEnforced,
361 statusBarColor,
362 statusBarBrightness,
363 statusBarIconBrightness,
364 systemStatusBarContrastEnforced,
365 systemNavigationBarIconBrightness,
366 );
367
368 @override
369 bool operator ==(Object other) {
370 if (other.runtimeType != runtimeType) {
371 return false;
372 }
373 return other is SystemUiOverlayStyle
374 && other.systemNavigationBarColor == systemNavigationBarColor
375 && other.systemNavigationBarDividerColor == systemNavigationBarDividerColor
376 && other.systemNavigationBarContrastEnforced == systemNavigationBarContrastEnforced
377 && other.statusBarColor == statusBarColor
378 && other.statusBarIconBrightness == statusBarIconBrightness
379 && other.statusBarBrightness == statusBarBrightness
380 && other.systemStatusBarContrastEnforced == systemStatusBarContrastEnforced
381 && other.systemNavigationBarIconBrightness == systemNavigationBarIconBrightness;
382 }
383}
384
385List<String> _stringify(List<dynamic> list) => <String>[
386 for (final dynamic item in list) item.toString(),
387];
388
389/// Controls specific aspects of the operating system's graphical interface and
390/// how it interacts with the application.
391abstract final class SystemChrome {
392 /// Specifies the set of orientations the application interface can
393 /// be displayed in.
394 ///
395 /// The `orientation` argument is a list of [DeviceOrientation] enum values.
396 /// The empty list causes the application to defer to the operating system
397 /// default.
398 ///
399 /// ## Limitations
400 ///
401 /// ### Android
402 ///
403 /// Android screens may choose to [letterbox](https://developer.android.com/guide/practices/enhanced-letterboxing)
404 /// applications that lock orientation, particularly on larger screens. When
405 /// letterboxing occurs on Android, the [MediaQueryData.size] reports the
406 /// letterboxed size, not the full screen size. Applications that make
407 /// decisions about whether to lock orientation based on the screen size
408 /// must use the `display` property of the current [FlutterView].
409 ///
410 /// ```dart
411 /// // A widget that locks the screen to portrait if it is less than 600
412 /// // logical pixels wide.
413 /// class MyApp extends StatefulWidget {
414 /// const MyApp({ super.key });
415 ///
416 /// @override
417 /// State<MyApp> createState() => _MyAppState();
418 /// }
419 ///
420 /// class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
421 /// ui.FlutterView? _view;
422 /// static const double kOrientationLockBreakpoint = 600;
423 ///
424 /// @override
425 /// void initState() {
426 /// super.initState();
427 /// WidgetsBinding.instance.addObserver(this);
428 /// }
429 ///
430 /// @override
431 /// void didChangeDependencies() {
432 /// super.didChangeDependencies();
433 /// _view = View.maybeOf(context);
434 /// }
435 ///
436 /// @override
437 /// void dispose() {
438 /// WidgetsBinding.instance.removeObserver(this);
439 /// _view = null;
440 /// super.dispose();
441 /// }
442 ///
443 /// @override
444 /// void didChangeMetrics() {
445 /// final ui.Display? display = _view?.display;
446 /// if (display == null) {
447 /// return;
448 /// }
449 /// if (display.size.width / display.devicePixelRatio < kOrientationLockBreakpoint) {
450 /// SystemChrome.setPreferredOrientations(<DeviceOrientation>[
451 /// DeviceOrientation.portraitUp,
452 /// ]);
453 /// } else {
454 /// SystemChrome.setPreferredOrientations(<DeviceOrientation>[]);
455 /// }
456 /// }
457 ///
458 /// @override
459 /// Widget build(BuildContext context) {
460 /// return const MaterialApp(
461 /// home: Placeholder(),
462 /// );
463 /// }
464 /// }
465 /// ```
466 ///
467 /// ### iOS
468 ///
469 /// This setting will only be respected on iPad if multitasking is disabled.
470 ///
471 /// You can decide to opt out of multitasking on iPad, then
472 /// setPreferredOrientations will work but your app will not
473 /// support Slide Over and Split View multitasking anymore.
474 ///
475 /// Should you decide to opt out of multitasking you can do this by
476 /// setting "Requires full screen" to true in the Xcode Deployment Info.
477 static Future<void> setPreferredOrientations(List<DeviceOrientation> orientations) async {
478 await SystemChannels.platform.invokeMethod<void>(
479 'SystemChrome.setPreferredOrientations',
480 _stringify(orientations),
481 );
482 }
483
484 /// Specifies the description of the current state of the application as it
485 /// pertains to the application switcher (also known as "recent tasks").
486 ///
487 /// Any part of the description that is unsupported on the current platform
488 /// will be ignored.
489 static Future<void> setApplicationSwitcherDescription(ApplicationSwitcherDescription description) async {
490 await SystemChannels.platform.invokeMethod<void>(
491 'SystemChrome.setApplicationSwitcherDescription',
492 <String, dynamic>{
493 'label': description.label,
494 'primaryColor': description.primaryColor,
495 },
496 );
497 }
498
499 /// Specifies the [SystemUiMode] to have visible when the application
500 /// is running.
501 ///
502 /// The `overlays` argument is a list of [SystemUiOverlay] enum values
503 /// denoting the overlays to show when configured with [SystemUiMode.manual].
504 ///
505 /// If a particular mode is unsupported on the platform, enabling or
506 /// disabling that mode will be ignored.
507 ///
508 /// The settings here can be overridden by the platform when System UI becomes
509 /// necessary for functionality.
510 ///
511 /// For example, on Android, when the keyboard becomes visible, it will enable the
512 /// navigation bar and status bar system UI overlays. When the keyboard is closed,
513 /// Android will not restore the previous UI visibility settings, and the UI
514 /// visibility cannot be changed until 1 second after the keyboard is closed to
515 /// prevent malware locking users from navigation buttons.
516 ///
517 /// To regain "fullscreen" after text entry, the UI overlays can be set again
518 /// after a delay of at least 1 second through [restoreSystemUIOverlays] or
519 /// calling this again. Otherwise, the original UI overlay settings will be
520 /// automatically restored only when the application loses and regains focus.
521 ///
522 /// Alternatively, a [SystemUiChangeCallback] can be provided to respond to
523 /// changes in the System UI. This will be called, for example, when in
524 /// [SystemUiMode.leanBack] and the user taps the screen to bring up the
525 /// system overlays. The callback provides a boolean to represent if the
526 /// application is currently in a fullscreen mode or not, so that the
527 /// application can respond to these changes. When `systemOverlaysAreVisible`
528 /// is true, the application is not fullscreen. See
529 /// [SystemChrome.setSystemUIChangeCallback] to respond to these changes in a
530 /// fullscreen application.
531 ///
532 /// If your app targets Android SDK 15 (API 35) or later (Flutter does this by
533 /// default), then your Flutter app uses [SystemUiMode.edgeToEdge] by default
534 /// on Android and setting any of the other [SystemUiMode]s will NOT work
535 /// unless you perform the migration detailed in
536 /// https://docs.flutter.dev/release/breaking-changes/default-systemuimode-edge-to-edge.
537 static Future<void> setEnabledSystemUIMode(SystemUiMode mode, { List<SystemUiOverlay>? overlays }) async {
538 if (mode != SystemUiMode.manual) {
539 await SystemChannels.platform.invokeMethod<void>(
540 'SystemChrome.setEnabledSystemUIMode',
541 mode.toString(),
542 );
543 } else {
544 assert(mode == SystemUiMode.manual && overlays != null);
545 await SystemChannels.platform.invokeMethod<void>(
546 'SystemChrome.setEnabledSystemUIOverlays',
547 _stringify(overlays!),
548 );
549 }
550 }
551
552 /// Sets the callback method for responding to changes in the system UI.
553 ///
554 /// This is relevant when using [SystemUiMode.leanBack]
555 /// and [SystemUiMode.immersive] and [SystemUiMode.immersiveSticky] on Android
556 /// platforms, where the [SystemUiOverlay]s can appear and disappear based on
557 /// user interaction.
558 ///
559 /// This will be called, for example, when in [SystemUiMode.leanBack] and the
560 /// user taps the screen to bring up the system overlays. The callback
561 /// provides a boolean to represent if the application is currently in a
562 /// fullscreen mode or not, so that the application can respond to these
563 /// changes. When `systemOverlaysAreVisible` is true, the application is not
564 /// fullscreen.
565 ///
566 /// When using [SystemUiMode.edgeToEdge], system overlays are always visible
567 /// and do not change. When manually configuring [SystemUiOverlay]s with
568 /// [SystemUiMode.manual], this callback will only be triggered when all
569 /// overlays have been disabled. This results in the same behavior as
570 /// [SystemUiMode.leanBack].
571 ///
572 static Future<void> setSystemUIChangeCallback(SystemUiChangeCallback? callback) async {
573 ServicesBinding.instance.setSystemUiChangeCallback(callback);
574 // Skip setting up the listener if there is no callback.
575 if (callback != null) {
576 await SystemChannels.platform.invokeMethod<void>(
577 'SystemChrome.setSystemUIChangeListener',
578 );
579 }
580 }
581
582 /// Restores the system overlays to the last settings provided via
583 /// [setEnabledSystemUIMode]. May be used when the platform force enables/disables
584 /// UI elements.
585 ///
586 /// For example, when the Android keyboard disables hidden status and navigation bars,
587 /// this can be called to re-disable the bars when the keyboard is closed.
588 ///
589 /// On Android, the system UI cannot be changed until 1 second after the previous
590 /// change. This is to prevent malware from permanently hiding navigation buttons.
591 static Future<void> restoreSystemUIOverlays() async {
592 await SystemChannels.platform.invokeMethod<void>(
593 'SystemChrome.restoreSystemUIOverlays',
594 );
595 }
596
597 /// Specifies the style to use for the system overlays (e.g. the status bar on
598 /// Android or iOS, the system navigation bar on Android) that are visible (if any).
599 ///
600 /// This method will schedule the embedder update to be run in a microtask.
601 /// Any subsequent calls to this method during the current event loop will
602 /// overwrite the pending value, such that only the last specified value takes
603 /// effect.
604 ///
605 /// Call this API in code whose lifecycle matches that of the desired
606 /// system UI styles. For instance, to change the system UI style on a new
607 /// page, consider calling when pushing/popping a new [PageRoute].
608 ///
609 /// The [AppBar] widget automatically sets the system overlay style based on
610 /// its [AppBar.systemOverlayStyle], so configure that instead of calling this
611 /// method directly. Likewise, do the same for [CupertinoNavigationBar] via
612 /// [CupertinoNavigationBar.backgroundColor].
613 ///
614 /// If a particular style is not supported on the platform, selecting it will
615 /// have no effect.
616 ///
617 /// {@tool sample}
618 /// The following example uses an `AppBar` to set the system status bar color and
619 /// the system navigation bar color.
620 ///
621 /// ** See code in examples/api/lib/services/system_chrome/system_chrome.set_system_u_i_overlay_style.0.dart **
622 /// {@end-tool}
623 ///
624 /// For more complex control of the system overlay styles, consider using
625 /// an [AnnotatedRegion] widget instead of calling [setSystemUIOverlayStyle]
626 /// directly. This widget places a value directly into the layer tree where
627 /// it can be hit-tested by the framework. On every frame, the framework will
628 /// hit-test and select the annotated region it finds under the status and
629 /// navigation bar and synthesize them into a single style. This can be used
630 /// to configure the system styles when an app bar is not used. When an app
631 /// bar is used, apps should not enclose the app bar in an annotated region
632 /// because one is automatically created. If an app bar is used and the app
633 /// bar is enclosed in an annotated region, the app bar overlay style supersedes
634 /// the status bar properties defined in the enclosing annotated region overlay
635 /// style and the enclosing annotated region overlay style supersedes the app bar
636 /// overlay style navigation bar properties.
637 ///
638 /// {@tool sample}
639 /// The following example uses an `AnnotatedRegion<SystemUiOverlayStyle>` to set
640 /// the system status bar color and the system navigation bar color.
641 ///
642 /// ** See code in examples/api/lib/services/system_chrome/system_chrome.set_system_u_i_overlay_style.1.dart **
643 /// {@end-tool}
644 ///
645 /// See also:
646 ///
647 /// * [AppBar.systemOverlayStyle], a convenient property for declaratively setting
648 /// the style of the system overlays.
649 /// * [AnnotatedRegion], the widget used to place a `SystemUiOverlayStyle` into
650 /// the layer tree.
651 static void setSystemUIOverlayStyle(SystemUiOverlayStyle style) {
652 if (_pendingStyle != null) {
653 // The microtask has already been queued; just update the pending value.
654 _pendingStyle = style;
655 return;
656 }
657 if (style == _latestStyle) {
658 // Trivial success: no microtask has been queued and the given style is
659 // already in effect, so no need to queue a microtask.
660 return;
661 }
662 _pendingStyle = style;
663 scheduleMicrotask(() {
664 assert(_pendingStyle != null);
665 if (_pendingStyle != _latestStyle) {
666 SystemChannels.platform.invokeMethod<void>(
667 'SystemChrome.setSystemUIOverlayStyle',
668 _pendingStyle!._toMap(),
669 );
670 _latestStyle = _pendingStyle;
671 }
672 _pendingStyle = null;
673 });
674 }
675
676 /// Called by the binding during a transition to a new app lifecycle state.
677 static void handleAppLifecycleStateChanged(AppLifecycleState state) {
678 // When the app is detached, clear the record of the style sent to the host
679 // so that it will be sent again when the app is reattached.
680 if (state == AppLifecycleState.detached) {
681 scheduleMicrotask(() {
682 _latestStyle = null;
683 });
684 }
685 }
686
687 static SystemUiOverlayStyle? _pendingStyle;
688
689 /// The last style that was set using [SystemChrome.setSystemUIOverlayStyle].
690 @visibleForTesting
691 static SystemUiOverlayStyle? get latestStyle => _latestStyle;
692 static SystemUiOverlayStyle? _latestStyle;
693}
694