Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ public class SystemBars extends Plugin {
static final String INSETS_HANDLING_DISABLE = "disable";
static final String INSETS_HANDLING_NATIVE = "native";

static final String KEYBOARD_INSETS_HANDLING_RESIZE = "resize";
static final String KEYBOARD_INSETS_HANDLING_NONE = "none";

// https://issues.chromium.org/issues/40699457
private static final int WEBVIEW_VERSION_WITH_SAFE_AREA_FIX = 140;
// https://issues.chromium.org/issues/457682720
Expand All @@ -54,6 +57,7 @@ function capacitorSystemBarsCheckMetaViewport() {
""";

private String insetsHandling = INSETS_HANDLING_CSS;
private boolean resizeForKeyboard = true;
private boolean hasViewportCover = false;

private String currentStatusBarStyle = STYLE_DEFAULT;
Expand Down Expand Up @@ -137,6 +141,20 @@ private void initSystemBars() {
insetsHandling = INSETS_HANDLING_CSS;
}

String configuredKeyboardInsetsHandling = getConfig().getString("keyboardInsetsHandling", KEYBOARD_INSETS_HANDLING_RESIZE);
if (KEYBOARD_INSETS_HANDLING_NONE.equals(configuredKeyboardInsetsHandling)) {
resizeForKeyboard = false;
} else if (!KEYBOARD_INSETS_HANDLING_RESIZE.equals(configuredKeyboardInsetsHandling)) {
Logger.warn(
"SystemBars",
"Unknown keyboardInsetsHandling value '" +
configuredKeyboardInsetsHandling +
"'. Falling back to '" +
KEYBOARD_INSETS_HANDLING_RESIZE +
"'."
);
}

warnAboutUnsupportedConfigurationValues();

initWindowInsetsListener();
Expand Down Expand Up @@ -195,14 +213,14 @@ private void initWindowInsetsListener() {

Insets systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout());
Insets imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime());
boolean keyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime());
boolean keyboardVisible = resizeForKeyboard && insets.isVisible(WindowInsetsCompat.Type.ime());

if (shouldPassthroughInsets) {
// We need to correct for a possible shown IME
v.setPadding(0, 0, 0, keyboardVisible ? imeInsets.bottom : 0);

WindowInsetsCompat newInsets = new WindowInsetsCompat.Builder(insets)
.setInsets(
WindowInsetsCompat newInsets = withKeyboardInsetsHandling(
new WindowInsetsCompat.Builder(insets).setInsets(
WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(),
Insets.of(
systemBarsInsets.left,
Expand All @@ -211,7 +229,7 @@ private void initWindowInsetsListener() {
getBottomInset(systemBarsInsets, keyboardVisible)
)
)
.build();
).build();

injectSafeAreaCSS(newInsets);

Expand All @@ -229,16 +247,30 @@ private void initWindowInsetsListener() {
// Returning `WindowInsetsCompat.CONSUMED` breaks recalculation of safe area insets
// So we have to explicitly set insets to `0`
// See: https://issues.chromium.org/issues/461332423
WindowInsetsCompat newInsets = new WindowInsetsCompat.Builder(insets)
.setInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), Insets.of(0, 0, 0, 0))
.build();
WindowInsetsCompat newInsets = withKeyboardInsetsHandling(
new WindowInsetsCompat.Builder(insets).setInsets(
WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(),
Insets.of(0, 0, 0, 0)
)
).build();

injectSafeAreaCSS(newInsets);

return newInsets;
});
}

private WindowInsetsCompat.Builder withKeyboardInsetsHandling(WindowInsetsCompat.Builder builder) {
if (resizeForKeyboard) {
return builder;
}

// WebView >= 139 resizes its visual viewport for the IME insets it receives, which would make the page pannable.
// Setting them to `0` rather than consuming them keeps the WebView's inset state up to date.
// See: https://developer.android.com/develop/ui/views/layout/webapps/understand-window-insets
return builder.setInsets(WindowInsetsCompat.Type.ime(), Insets.NONE).setVisible(WindowInsetsCompat.Type.ime(), false);
}

private void injectSafeAreaCSS(WindowInsetsCompat insets) {
if (!INSETS_HANDLING_CSS.equals(insetsHandling)) {
return;
Expand Down
15 changes: 15 additions & 0 deletions cli/src/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,21 @@ export interface PluginsConfig {
*/
insetsHandling?: 'native' | 'css' | 'disable';

/**
* Specifies how to handle the keyboard insets on Android.
*
* This option is only supported on Android and has no effect if `insetsHandling` is set to `disable`.
*
* `resize` = Resizes the webview when the keyboard is shown, so the keyboard never covers it.
*
* `none` = Leaves the webview untouched when the keyboard is shown, so the keyboard overlays it.
* This shifts the responsibility of keeping content clear of the keyboard to your own code,
* for example by using the keyboard height reported by `@capacitor/keyboard`.
*
* @default "resize"
*/
keyboardInsetsHandling?: 'resize' | 'none';

/**
* Set an initial value for the to be detected `viewport-fit=` meta tag value.
* For most apps that support edge-to-edge this value will eventually be `cover`.
Expand Down
1 change: 1 addition & 0 deletions core/system-bars.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ const setStatusBarAnimation = async () => {
| Prop | Type | Description | Default |
| ------------- | -------------------- | ------------------------------------------------------------------------- | ------------------ |
| **`insetsHandling`** | <code>string</code> | Specifies how to handle problematic insets on Android.<br>This option is only supported on Android.<br><br>`native` = (recommended) For older Chromium versions (< v140) this embeds the webview with padding and sets the `env(safe-area-inset-*)` variables to `0px`. For newer Chromium versions (>= v140) this makes sure the webview adheres to the `viewport-fit` meta tag. If set to `viewport-fit="cover"` this will make the webview edge-to-edge and the `env(safe-area-inset-*)` variables will contain the correct values. With those values you could set padding for example so make sure the webview is shown correctly.<br><br>`css` = This is the same as `native`, but it also injects CSS variables (`--safe-area-inset-*`) containing correct safe area inset values into the webview.<br><br>`disable` = (not recommended) Disable safe area insets handling completely.<br>This shifts the responsibility from Capacitor to your own code to handle the insets.<br>Be aware that this might result in a visually broken UI if your native app code and the content loaded into the webview do not correctly handle safe area insets.<br>| <code>css</code> |
| **`keyboardInsetsHandling`** | <code>string</code> | Specifies how to handle the keyboard insets on Android.<br>This option is only supported on Android and has no effect if `insetsHandling` is set to `disable`.<br><br>`resize` = Resizes the webview when the keyboard is shown, so the keyboard never covers it.<br><br>`none` = Leaves the webview untouched when the keyboard is shown, so the keyboard overlays it.<br>This shifts the responsibility of keeping content clear of the keyboard to your own code,<br>for example by using the keyboard height reported by `@capacitor/keyboard`. | <code>resize</code> |
| **`initialViewportFitValueHint`** | <code>string</code> | Set an initial value for the to be detected `viewport-fit=` meta tag value.<br>For most apps that support edge-to-edge this value will eventually be `cover`.<br>Therefore you might want to set this value to `cover` to help prevent layout jumps and glitches.<br>If you know the value to be `cover` initially, you can set it here.<br>The value will always end up correctly, no matter what you set here,<br>as long as `insetsHandling` is set to `native` or `css`.<br>It only exists to help prevent layout jumps and glitches.<br><br>This option is only supported on Android. | <code>undefined</code> |
| **`style`** | <code>string</code> | The style of the text and icons of the system bars. | <code>DEFAULT</code> |
| **`hidden`** | <code>boolean</code> | Hide the system bars on start. | <code>false</code> |
Expand Down