capacitor: bug(android): overlaying status bar does not affect safe area insets

Bug Report

Capacitor Version

npx cap doctor output:

$ npx cap doctor πŸ’Š Capacitor Doctor πŸ’Š

Latest Dependencies:

@capacitor/cli: 2.0.1

@capacitor/core: 2.0.1

@capacitor/android: 2.0.1

@capacitor/electron: 2.0.1

@capacitor/ios: 2.0.1

Installed Dependencies:

@capacitor/electron not installed

@capacitor/cli 2.0.1

@capacitor/core 2.0.1

@capacitor/android 2.0.1

@capacitor/ios 2.0.1

[success] Android looking great! πŸ‘Œ Found 10 Capacitor plugins for ios: @mauron85/cordova-plugin-background-geolocation (3.1.0) capacitor-data-storage-sqlite (2.0.0-1) capacitor-voice-recorder (0.0.9) cordova-plugin-android-permissions (1.0.2) cordova-plugin-app-version (0.1.9) cordova-plugin-file (6.0.2) cordova-plugin-file-transfer (1.7.1) cordova-plugin-screen-orientation (3.0.2) cordova-plugin-whitelist (1.3.4) es6-promise-plugin (4.2.2) [success] iOS looking great! πŸ‘Œ

Affected Platform(s)

  • Android
  • iOS
  • Electron
  • Web

Current Behavior

On iOS, I leave space for the iPhone X notch & iPhone 6 status bar using <meta name="viewport" content="viewport-fit=cover" /> along with env(safe-area-inset-top), as described in #2100 .

I would love to do the same thing in Android, however it appears that overlaying the status bar via StatusBar.setOverlaysWebView({ overlay: true }) does not affect env(safe-area-inset-top) in the CSS. env(safe-area-inset-*) has officially been supported since Chrome 69 (announcement) though how extensively I don’t know.

Expected Behavior

I would expect env(safe-area-inset-top) to include the height of the status bar if it is overlaid.

Reproduction Steps

    // Display content under transparent status bar (Android only)
    StatusBar.setOverlaysWebView({
      overlay: true
    });

Other Technical Details

npm --version output: 6.14.4

node --version output: v12.16.1

Other Information

If fixing env(safe-area-inset-top) is not possible, I would be satisfied being able to access the safe area insets via a Plugin, or injected CSS variables (e.g. --android-safe-area-inset-top or similar).

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 21
  • Comments: 20 (1 by maintainers)

Most upvoted comments

I’m developing a plugin that sends the device insets, at the moment works only on android, and send only top, its for a emergency. https://www.npmjs.com/package/capacitor-insets-plugin

const { StatusBar, InsetsPlugin } = Plugins;

StatusBar.setOverlaysWebView({ overlay: true });
StatusBar.setStyle({ style: StatusBarStyle.Dark });

InsetsPlugin.top()
    .then((resp: { value: number }) => {
        document.documentElement.style.setProperty('--top-inset', `${resp.value}px`);
    });

@haschu Nope. Tons of apps do this, seems very common - but still haven’t found a way with Capacitor/Ionic

I published a plugin, based on other peoples repos, to get the value of status bar, regardless if it’s ionic bug or chromium.

https://github.com/owlsdepartment/capacitor-plugin-android-insets

I ended up making my own workaround but it’s hacky and somewhat complicated.

In onCreate in your MainActivity, make the app draw behind the system status bar and nav bar: (and make them transparent)

    // Use a transparent status bar and nav bar, and place the window behind the status bar
    // and nav bar. Due to a chromium bug, we need to get the height of both bars
    // and add it to the safe area insets. The native plugin is used to get this info.
    // See https://bugs.chromium.org/p/chromium/issues/detail?id=1094366
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
      getWindow().setDecorFitsSystemWindows(false);
      getWindow().setStatusBarColor(0);
      getWindow().setNavigationBarColor(0);
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
      // On older versions of android setDecorFitsSystemWindows doesn't exist yet, but it can
      // be emulated with flags.
      // It still must be P or greater, as that is the min version for getting the insets
      // through the native plugin.
      getWindow().getDecorView().setSystemUiVisibility(
              View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
                      View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
                      View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
      getWindow().setStatusBarColor(0);
      getWindow().setNavigationBarColor(0);
    }

Then I use a plugin to get the display insets:

    @PluginMethod
    public void getDisplayInsets(PluginCall call) {
        Activity activity = getBridge().getActivity();
        Window window = activity.getWindow();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            // See https://bugs.chromium.org/p/chromium/issues/detail?id=1094366, which is
            // the bug that makes getting the display inset necessary.
            final WindowInsets cutout = window.getDecorView().getRootWindowInsets();
            final float density = activity.getResources().getDisplayMetrics().density;

            JSObject ret = new JSObject();
            ret.put("top", cutout.getStableInsetTop() / density);
            ret.put("bottom", cutout.getStableInsetBottom() / density);
            call.resolve(ret);
        }
    }

And set the safe area to the insets upon startup in index.tsx: (Native is the name of the plugin I created for this)

    if (isPlatform('android')) {
        Native.getDisplayInsets().then(i => {
            const style = document.documentElement.style;
            style.setProperty('--ion-safe-area-top', i.top + 'px');
            style.setProperty('--ion-safe-area-bottom', i.bottom + 'px');
        });
    }

Lastly, ion-modals (without a top header) don’t seem to respect the safe area, so I had to add the following to my CSS:

ion-modal {
  --ion-content-scroll-safe-area: var(--ion-safe-area-bottom, 0);
}

ion-content::part(scroll) {
  margin-bottom: calc(var(--ion-content-scroll-safe-area, 0));
}

Once the Chromium bug is fixed, only the CSS part should be needed. That part appears to be a bug in ionic.