From f417fb7331a0acce86203e23c26991ff17bb2a77 Mon Sep 17 00:00:00 2001 From: juli Date: Sun, 9 Aug 2026 22:44:13 +0200 Subject: [PATCH] Fix WebView system bar and keyboard insets --- README.md | 2 +- app/build.gradle.kts | 6 +-- app/src/main/assets/web/styles.css | 14 ++++-- .../monster/autisme/tsjetpiti/MainActivity.kt | 43 +++++++++++++++++++ 4 files changed, 58 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a06e9fb..450ed57 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ conversation and start fresh. - **A build machine** with the Android SDK + NDK. Easiest path: open the project in **Android Studio** (it will offer to install the matching NDK `27.2.12479018` and CMake `3.22.1`). -- **A phone:** 64-bit ARM (`arm64-v8a`), Android 8.0+ (minSdk 26), and enough free +- **A phone:** 64-bit ARM (`arm64-v8a`), Android 11+ (minSdk 30), and enough free RAM to hold a ~1.5 GB Q6 model (~4 GB RAM device recommended). - Network on first launch to download the model. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 02729bc..635e4a3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -13,10 +13,10 @@ android { defaultConfig { applicationId = "monster.autisme.tsjetpiti" - minSdk = 26 + minSdk = 30 targetSdk = 35 - versionCode = 1 - versionName = "0.1.0" + versionCode = 2 + versionName = "0.1.1" // On-device LLM => 64-bit ARM only. Keeps the APK from ballooning. ndk { diff --git a/app/src/main/assets/web/styles.css b/app/src/main/assets/web/styles.css index 3fb8e4d..f7876bb 100644 --- a/app/src/main/assets/web/styles.css +++ b/app/src/main/assets/web/styles.css @@ -7,6 +7,12 @@ --bubble: #141414; --bubble-line: #242424; + /* Window insets (status/nav bar, cutout, keyboard) — set from Android. */ + --inset-top: 0px; + --inset-right: 0px; + --inset-bottom: 0px; + --inset-left: 0px; + /* Generic, corporate/IT sans stack — resolves offline on any device. */ --font: "Segoe UI", "Helvetica Neue", Helvetica, Arial, "Liberation Sans", system-ui, sans-serif; @@ -37,7 +43,8 @@ body { display: flex; align-items: center; justify-content: space-between; - padding: max(env(safe-area-inset-top), 12px) 16px 12px; + padding: calc(var(--inset-top) + 12px) calc(var(--inset-right) + 16px) 12px + calc(var(--inset-left) + 16px); background: var(--panel); border-bottom: 1px solid var(--line); flex: 0 0 auto; @@ -64,7 +71,7 @@ body { #chat { flex: 1 1 auto; overflow-y: auto; - padding: 18px 16px; + padding: 18px calc(var(--inset-right) + 16px) 18px calc(var(--inset-left) + 16px); display: flex; flex-direction: column; gap: 12px; @@ -99,7 +106,8 @@ body { display: flex; gap: 10px; align-items: flex-end; - padding: 12px 14px calc(env(safe-area-inset-bottom) + 12px); + padding: 12px calc(var(--inset-right) + 14px) calc(var(--inset-bottom) + 12px) + calc(var(--inset-left) + 14px); background: var(--panel); border-top: 1px solid var(--line); flex: 0 0 auto; diff --git a/app/src/main/java/monster/autisme/tsjetpiti/MainActivity.kt b/app/src/main/java/monster/autisme/tsjetpiti/MainActivity.kt index fd46986..41d6695 100644 --- a/app/src/main/java/monster/autisme/tsjetpiti/MainActivity.kt +++ b/app/src/main/java/monster/autisme/tsjetpiti/MainActivity.kt @@ -2,7 +2,9 @@ package monster.autisme.tsjetpiti import android.annotation.SuppressLint import android.app.Activity +import android.graphics.Color import android.os.Bundle +import android.view.WindowInsets import android.webkit.JavascriptInterface import android.webkit.WebSettings import android.webkit.WebView @@ -32,6 +34,12 @@ class MainActivity : Activity() { @Volatile private var handle: Long = 0L @Volatile private var setupStarted = false + // Window insets (status/nav bar, cutout, keyboard) in CSS px, pushed to the page. + private var insetTop = 0 + private var insetBottom = 0 + private var insetLeft = 0 + private var insetRight = 0 + companion object { private const val N_CTX = 4096 private const val MAX_TOKENS = 220 @@ -55,6 +63,30 @@ class MainActivity : Activity() { webView = WebView(this) setContentView(webView) + // Edge-to-edge (default on Android 15+): draw behind the system bars, then + // inset the WebView by the status bar, nav bar, display cutout, and IME so + // none of them ever cover the chat UI (portrait or landscape). + window.setDecorFitsSystemWindows(false) + webView.setBackgroundColor(Color.BLACK) + // WebView.setPadding doesn't inset HTML content (Chromium sizes 100dvh to the + // full view), so feed the insets to CSS as variables instead. This also tracks + // the IME: when the keyboard shows, bottom grows and the composer rises. + webView.setOnApplyWindowInsetsListener { _, insets -> + val bars = insets.getInsets( + WindowInsets.Type.systemBars() or + WindowInsets.Type.ime() or + WindowInsets.Type.displayCutout() + ) + val d = resources.displayMetrics.density + insetTop = (bars.top / d).toInt() + insetBottom = (bars.bottom / d).toInt() + insetLeft = (bars.left / d).toInt() + insetRight = (bars.right / d).toInt() + pushInsets() + insets + } + webView.requestApplyInsets() + webView.settings.apply { javaScriptEnabled = true domStorageEnabled = true @@ -63,6 +95,7 @@ class MainActivity : Activity() { webView.addJavascriptInterface(Bridge(), "TsjetNative") webView.webViewClient = object : WebViewClient() { override fun onPageFinished(view: WebView?, url: String?) { + pushInsets() // apply insets now that the DOM exists if (!setupStarted) { setupStarted = true startSetup() @@ -80,6 +113,16 @@ class MainActivity : Activity() { } } + /** Push the current window insets to the page as CSS variables on :root. */ + private fun pushInsets() { + val js = "var s=document.documentElement.style;" + + "s.setProperty('--inset-top','${insetTop}px');" + + "s.setProperty('--inset-bottom','${insetBottom}px');" + + "s.setProperty('--inset-left','${insetLeft}px');" + + "s.setProperty('--inset-right','${insetRight}px');" + runOnUiThread { webView.evaluateJavascript(js, null) } + } + private fun startSetup() = setupExecutor.execute { try { callJs("onStatus", "Waking the tsjet…")