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