Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/dev-build.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Dev Build Android APK

# Lightweight dev build: builds debug + release APKs, uploads them as
# Lightweight dev build: builds debug + release APKs, uploads them as
# GitHub Actions artifacts, and optionally mirrors release APKs to Cloudflare
# R2 when the commit message contains [r2].
#
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,82 @@
package org.electerm.electerm;

import android.os.Bundle;
import android.view.View;
import androidx.core.view.WindowCompat;
import com.getcapacitor.BridgeActivity;

/**
* electerm custom MainActivity.
*
* Problem: On Android 15+ (API 35+, e.g. Google Pixel 9), edge-to-edge display
* is enforced. Capacitor's BridgeActivity.onCreate() calls
* Problem: On Android 15+ (API 35+, e.g. Google Pixel 9), Capacitor's
* BridgeActivity enables edge-to-edge display by calling
* WindowCompat.setDecorFitsSystemWindows(getWindow(), false)
* which opts INTO edge-to-edge, causing the WebView content to extend under the
* system status bar. This makes the tab bar area (top 36px of the app)
* unclickable — the status bar overlays it.
* and setting up its own OnApplyWindowInsetsListener that consumes system
* bar insets without applying them as padding. This causes the WebView
* content to extend under the status bar, making the tab bar area
* unclickable.
*
* On some devices (e.g. Huawei Mate 40 running EMUI / older Android) this does
* not happen because the manufacturer's system does not enforce edge-to-edge or
* handles insets differently.
* Simply calling setDecorFitsSystemWindows(true) is NOT enough because
* Capacitor's listener still consumes the insets without applying padding.
*
* Fix: Override BridgeActivity.onCreate() and call
* WindowCompat.setDecorFitsSystemWindows(getWindow(), true)
* AFTER super.onCreate(). This tells the system to apply automatic padding for
* status-bar and navigation-bar insets, so the WebView content stays within the
* safe area and the tab bar is fully clickable on all devices.
*
* This is the app-level (native) fix — no CSS safe-area-inset hacks needed.
* Fix: Override the content view's OnApplyWindowInsetsListener to apply
* system bar insets (status bar + navigation bar) as padding on the root
* content FrameLayout. This constrains the WebView within the safe area.
* The listener is re-applied in onPostCreate/onResume and via post() to
* ensure it runs after Capacitor's bridge initialization.
*/
public class MainActivity extends BridgeActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Override Capacitor's edge-to-edge default so content does not extend
// under system bars (status bar at top, navigation bar at bottom).
WindowCompat.setDecorFitsSystemWindows(getWindow(), true);
applySafeAreaPadding();
}

@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
applySafeAreaPadding();
}

@Override
public void onResume() {
super.onResume();
WindowCompat.setDecorFitsSystemWindows(getWindow(), true);
applySafeAreaPadding();
}

/**
* Apply system bar insets (status bar + navigation bar) as padding to
* the root content view (android.R.id.content) so the WebView stays
* within the safe area.
*
* We override any OnApplyWindowInsetsListener that Capacitor's Bridge
* may have set on the content view, and apply the insets as padding.
* The listener is set both synchronously and via post() to ensure it
* runs after Capacitor's (possibly async) bridge initialization.
*/
private void applySafeAreaPadding() {
View contentView = findViewById(android.R.id.content);
if (contentView == null) return;

setInsetListener(contentView);
// Also post to run after all pending main-thread messages,
// which is after Capacitor's async bridge setup.
contentView.post(() -> setInsetListener(contentView));
}

private void setInsetListener(View contentView) {
contentView.setOnApplyWindowInsetsListener((v, insets) -> {
int top = insets.getSystemWindowInsetTop();
int bottom = insets.getSystemWindowInsetBottom();
v.setPadding(0, top, 0, bottom);
// Consume system window insets so the WebView (child) doesn't
// double-apply them. The keyboard is handled via
// android:windowSoftInputMode=adjustResize in the theme, which
// resizes the window rather than relying on insets dispatch.
return insets.consumeSystemWindowInsets();
});
contentView.requestApplyInsets();
}
}
Loading
Loading