themedSystemNavigationBar static method

SystemUiOverlayStyle themedSystemNavigationBar(
  1. BuildContext? context, {
  2. bool? useDivider,
  3. double opacity = 1,
  4. bool noAppBar = false,
  5. bool invertStatusIcons = false,
  6. FlexSystemNavBarStyle systemNavBarStyle = FlexSystemNavBarStyle.background,
  7. Color? systemNavigationBarColor,
  8. Color? systemNavigationBarDividerColor,
  9. Brightness nullContextBrightness = Brightness.light,
  10. @Deprecated('This property is deprecated use systemNavigationBarColor ' 'instead. Deprecated in v2.0.0.') Color? nullContextBackground,
})

Returns a SystemUiOverlayStyle that by default has a system navigation bar style that matches the current theme.

For its default behavior it requires a build context with access to the inherited theme. This static function is a convenience wrapper for making a SystemUiOverlayStyle that follows current theme. For very customized styles consider using SystemUiOverlayStyle directly.

By default when calling themedSystemNavigationBar with context, it creates a SystemUiOverlayStyle where the system navigator bar uses current theme's ´colorScheme.background´ as its background color and icon colors that match this background, without any divider.

The background color can be modified with systemNavBarStyle that can use: system, surface, background, scaffoldBackground or transparent options as background color options. It defaults to background. See FlexSystemNavBarStyle for more info.

In standard Flutter themes, the surface, background, scaffoldBackground and in light theme, even system are all the same color. For such themes this convenience property does not make so much sense. However, if you use FlexColorScheme and its primary color surface branding, these colors are not the same. This then offers a convenient way to switch the background color of your system navigation bar in a way that matches your theme's surface branded background color and to choose which one to use.

The always sets systemNavigationBarContrastEnforced: false to try to avoid the system scrim on Android version where it is supported. This is done because the selected background color is the scrim itself when used with the opacity parameter and we never want an extra scrim. If we set opacity very low and loose contrast due to that, it is because it is our intent.

An optional divider on the navigation bar, which is only respected on Android P (= Pie = API 28 = Android 9) or higher, can be turned on by setting useDivider to true. This produces a divider on top of the system navigation bar that in light theme mode uses color 0xFF2C2C2C and in dark mode and 0xFFDDDDDD.

You can modify the default color of the divider with the optional systemNavigationBarDividerColor. The call to set and use the divider color is only made once a none null value has been given to useDivider. Android SDK < 29 does not respect provided alpha value on the color of the divider color, and calling it with null again will not remove it.

Be aware that once you have enabled the divider by setting it to true that there is no convenient way to get rid of it. You can set the value to false, but that will just make the divider same color as your current nav bar background color to make it invisible, it is still there, but still this implementation trick works well.

Use and support for the opacity value on the system navigation bar is now supported starting from Flutter 2.5. This PR once it lands in stable will also for more predictable and consistent behavior limit its functionality to SDK >= 29: https://github.com/flutter/engine/pull/28616

By default themedSystemNavigationBar does not set any system overlay for the status bar. In Flutter SDK the top status bar has its own built in SystemUiOverlayStyle as a part of AppBar and AppBarTheme. FlexColorScheme also manages the SystemUiOverlayStyle for the status bar via it. However, if your screen has no AppBar you can use the property noAppBar and invertStatusIcons to affect the look of the status icons when there is no AppBar present on the page, this is useful e.g. for splash and intro screens.

Implementation

static SystemUiOverlayStyle themedSystemNavigationBar(
  BuildContext? context, {

  /// Use a divider line on the top edge of the system navigation bar.
  ///
  /// Defaults to null. Keeping it null, by omission or passing null, always
  /// omits the call to set any divider color in the created
  /// [SystemUiOverlayStyle].
  ///
  /// The divider on the navigation bar, is only respected on Android P
  /// (= Pie = API 28 = Android 9) or higher.
  ///
  /// Activating the divider also introduces a system overlay scrim on the
  /// system navigation bar. It becomes visible at high opacity values, but
  /// it removed when opacity is 0. This happens despite that we always set
  /// `systemNavigationBarContrastEnforced: false` in the call.
  final bool? useDivider,

  /// Opacity value for the system navigation bar.
  ///
  /// The opacity value is applied to the provided `systemNavigationBarColor`
  /// or if is null, to the color determined by `systemNavBarStyle`.
  ///
  /// Defaults to 1, fully opaque.
  ///
  /// This feature is now supported starting from Flutter 2.5.
  /// Be aware that it only works on Android SDK >= 29. There may even
  /// may be some issues on Android SDK < 29 until this PR lands in stable:
  /// https://github.com/flutter/engine/pull/28616
  ///
  /// This issue is a good source for more information on current state
  /// of transparent navigation bars in Flutter on Android:
  /// https://github.com/flutter/flutter/issues/90098
  final double opacity = 1,

  /// Set this to true if you do not use a Material AppBar and want
  /// a uniform background where the status bar's icon region is.
  ///
  /// This would typically be true on pages like splash screens and intro
  /// screens that don't use an AppBar. The Material AppBar uses its own
  /// `SystemUiOverlayStyle` so don't use this with an AppBar, set the style
  /// on the AppBar theme instead. However, if you don't have an [AppBar] this
  /// is a convenient way of to remove the system icons scrim for a more
  /// clean look on Android.
  final bool noAppBar = false,

  /// Set to true to invert top status bar icons like, battery, network,
  /// wifi icons etc. in relation to their normal theme brightness related
  /// color.
  ///
  /// Defaults to false.
  ///
  /// This setting works well together with the `noAppBar` flag to make an
  /// even cleaner looking splash screen by making the
  /// top status bar icons less visible or even invisible.
  ///
  /// On a white background the status icons will be invisible, and if a
  /// fully black background is used in dark mode, they will be invisible
  /// in dark mode too. This can be used to create clean screen with no
  /// app bar and no status icons.
  ///
  /// For no status bar and and system navigation bar, you can also try using:
  /// `SystemChrome.setEnabledSystemUIOverlays(<SystemUiOverlay>[]);` to
  /// remove the top status bar and bottom navigation bar completely. When
  /// using that method there are however issues with them showing up again
  /// on navigation and when keyboard becomes visible, or app is restored
  /// from being in the background while using another app. You
  /// also have to manage putting the overlays back yourself manually with
  /// `SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values)` when
  /// moving away from the screen that had removed them. Using an
  /// `AnnotatedRegion` with `themedSystemNavigationBar` and both `noAppBar`
  /// and `invertStatusIcons` set to true, you can avoid these issues. You are
  /// however limited to using background white, in light mode and black in
  /// dark mode, if you want the status bar to be totally invisible and
  /// navigation bar to blend in with the background completely.
  final bool invertStatusIcons = false,

  /// The [FlexSystemNavBarStyle] used to determine the background color
  /// for the system navigation bar. Used when systemNavigationBarColor
  /// is null and context is not null, so theme colors corresponding to it
  /// can be used for the background color.
  ///
  /// Defaults to [FlexSystemNavBarStyle.background].
  final FlexSystemNavBarStyle systemNavBarStyle =
      FlexSystemNavBarStyle.background,

  /// Background color of the system navigation bar. If null the theme of
  /// context `colorScheme.background` will be used as background color.
  ///
  /// The point with this static helper is to give you a background color
  /// themed system navigation bar automatically. If you for some reason
  /// want a different color you can still override it this property.
  ///
  /// If `context` is null, `nullContextBrightness` will be used as brightness
  /// value and it will determine if the background is white (for
  /// Brightness.light) or black (for Brightness.dark) if this property is
  /// also null. The null context is mostly used for simple unit testing
  /// with no context, but can also be used to make a `SystemUiOverlayStyle`
  /// with this helper without having a context.
  final Color? systemNavigationBarColor,

  /// Optional color for the system navigation bar divider. A divider will
  /// only be present if `useDivider` is true and in this color if a
  /// value was given to it.
  ///
  /// If a color is not given the `Color(0xFF2C2C2C)` will be used in
  /// dark mode and the color `Color(0xFFDDDDDD)` will be used in light mode,
  /// as the divider color for the system navigation bar.
  ///
  /// The divider on the navigation bar, is only respected on Android P
  /// (= Pie = API 28 = Android 9) or higher.
  final Color? systemNavigationBarDividerColor,

  /// Brightness used if context is null, mostly used for simple unit testing,
  /// with no context present. However, it can also be used to make a
  /// `SystemUiOverlayStyle` without having a context.
  ///
  /// Defaults to Brightness.light.
  final Brightness nullContextBrightness = Brightness.light,

  /// Deprecated property, use systemNavigationBarColor instead.
  @Deprecated('This property is deprecated use systemNavigationBarColor '
      'instead. Deprecated in v2.0.0.')
      Color? nullContextBackground,
}) {
  double _opacity = opacity;
  if (_opacity < 0) _opacity = 0;
  if (_opacity > 1) _opacity = 1;

  // If systemNavigationBarColor is null, we assign nullContextBackground
  // to it, that may also be null. This is done for backwards compatibility.
  final Color? _systemNavigationBarColor =
      systemNavigationBarColor ?? nullContextBackground;

  // If the systemNavBarStyle is FlexSystemNavBarStyle.transparent we will
  // override opacity to 0.01.
  if (systemNavBarStyle == FlexSystemNavBarStyle.transparent) {
    _opacity = 0.01;
  }
  // If context was null, use nullContextBrightness as brightness value.
  final bool isDark = context != null
      ? Theme.of(context).brightness == Brightness.dark
      : nullContextBrightness == Brightness.dark;

  // Get the defined effective background color for the used style.
  // This is not pretty, but wanted a final for the background.
  final Color flexBackground = (context != null)
      ? systemNavBarStyle == FlexSystemNavBarStyle.system
          ? (isDark ? Colors.black : Colors.white)
          : systemNavBarStyle == FlexSystemNavBarStyle.background
              ? Theme.of(context).colorScheme.background
              : systemNavBarStyle == FlexSystemNavBarStyle.surface
                  ? Theme.of(context).colorScheme.surface
                  : systemNavBarStyle ==
                          FlexSystemNavBarStyle.scaffoldBackground
                      ? Theme.of(context).scaffoldBackgroundColor
                      : Theme.of(context).scaffoldBackgroundColor
      : (isDark ? Colors.black : Colors.white);

  // If a systemNavigationBarColor color is given, it will always be used,
  // If it is not given, we use above flexBackground.
  final Color background = _systemNavigationBarColor ?? flexBackground;

  // A divider will be applied if `useDivider` is true and it will
  // use provided `systemNavigationBarDividerColor` if a value was given
  // or fallback to suitable theme mode default divider colors if no
  // color was given.
  //
  // The logic below is intended to keep the `dividerColor` used in the
  // [SystemUiOverlayStyle] as null as long as `useDivider` is null. As soon
  // as it is not, it will set a Divider color, also with `false` value. With
  // false it will be set to resulting background color in order to hide the
  // divider by making it background colored. This is a way to remove
  // the divider if it has been enabled earlier, since you cannot remove it
  // with a null color value after it has been enabled with any known
  // `SystemUiOverlayStyle` and `SystemChrome` call. The false value then
  // provide means to at least hide it again, but it will still be there.
  //
  // Worth noticing is also that the opacity does not have any effect on
  // divider color in SDK<29 of Android. We apply some opacity anyway because
  // if you are using transparent system navigation bar on Android API30 or
  // higher it does, work and it looks nicer when it has some transparency
  // if the navbar is also transparent.
  Color? dividerColor;

  // If we have opacity on the navbar, we should have some on the divider too
  // when we have a divider, we use some, but not a lot, we do want to keep
  // visible and not fade a way with background opacity, since a divider was
  // requested.
  final double dividerOpacity = _opacity < 1 ? 0.8 : 1;

  if (useDivider == null) {
    // The dividerColor is already null from declaration above with no value,
    // but being explicit that in this case this is the case where we want a
    // null color value for the divider as well in order to not include it
    // in the `SystemUiOverlayStyle`.
    dividerColor = null;
  } else if (useDivider && systemNavigationBarDividerColor == null) {
    // We should have a divider, but have no given color, use defaults.
    dividerColor = isDark
        ? const Color(0xFF2C2C2C).withOpacity(dividerOpacity)
        : const Color(0xFFDDDDDD).withOpacity(dividerOpacity);
  } else if (useDivider && systemNavigationBarDividerColor != null) {
    // We should have a divider, with a given color.
    dividerColor =
        systemNavigationBarDividerColor.withOpacity(dividerOpacity);
  } else {
    // If this branch is reached, then useDivider is false and we must define
    // its color to whatever color the background is, in order to hide it
    // as well as possible.
    dividerColor = background.withOpacity(_opacity);
  }
  // If opacity is specified, we need to enable SystemUiMode.edgeToEdge to
  // be able to see content scrolling behind the transparent bar. We only do
  // so when we have any opacity specified.
  // TODO(rydmike): Follow-up on edgeToEdge issue and adjust if needed.
  //   This is a gold mine: https://github.com/flutter/flutter/issues/90098
  if (_opacity < 1) {
    SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
  }
  return SystemUiOverlayStyle(
    // The top status bar settings.
    // If no params are set that indicates we on purpose want to adjust it
    // because there is no `AppBar`, we must pass null to avoid changing any
    // of its values controlled by the `AppBar` and its theme.
    statusBarColor: noAppBar ? Colors.transparent : null,
    statusBarBrightness:
        noAppBar ? (isDark ? Brightness.dark : Brightness.light) : null,
    statusBarIconBrightness: noAppBar
        ? invertStatusIcons
            ? (isDark ? Brightness.dark : Brightness.light)
            : (isDark ? Brightness.light : Brightness.dark)
        : null,
    // The bottom navigation bar settings.
    systemNavigationBarContrastEnforced: false,
    systemNavigationBarColor: background.withOpacity(_opacity),
    systemNavigationBarDividerColor: dividerColor,
    systemNavigationBarIconBrightness:
        isDark ? Brightness.light : Brightness.dark,
  );
}