mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
Merge pull request #3932 from vitorpamplona/claude/ime-padding-browser-window-ksa26a
Handle IME insets in full-screen napplet hosts
This commit is contained in:
@@ -559,7 +559,9 @@
|
||||
|
||||
<!-- Direct-WebView browser for a single web client. Runs in the isolated, keyless `:napplet`
|
||||
process and hosts the WebView directly (not a streamed surface), so scroll/zoom/keyboard work
|
||||
natively. adjustResize shrinks the window for the soft keyboard. Its own task/recents entry. -->
|
||||
natively. The activity insets its own content for the soft keyboard (adjustResize only still
|
||||
applies below Android 15, where the window is not forced edge-to-edge). Its own task/recents
|
||||
entry. -->
|
||||
<activity
|
||||
android:name="com.vitorpamplona.amethyst.napplethost.NappletBrowserActivity"
|
||||
android:process=":napplet"
|
||||
|
||||
+8
-15
@@ -53,11 +53,8 @@ import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.graphics.scale
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.webkit.JavaScriptReplyProxy
|
||||
import androidx.webkit.ProxyConfig
|
||||
import androidx.webkit.ProxyController
|
||||
@@ -76,9 +73,10 @@ import com.vitorpamplona.amethyst.commons.R as CommonsR
|
||||
* process. Unlike the embedded browser ([NappletBrowserService], which streams its surface to the main
|
||||
* app through SurfaceControlViewHost — a path that, on current Android, forwards taps but drops scroll/
|
||||
* zoom/keyboard gestures), this hosts the WebView **directly** in its own window, so scrolling, pinch
|
||||
* zoom, and the soft keyboard (`adjustResize`) all work natively. It stays just as keyless: the page JS
|
||||
* runs here, every NIP-07 `window.nostr` call is brokered + consent-gated in the main process per origin,
|
||||
* and the keys never leave it.
|
||||
* zoom, and the soft keyboard all work natively — the window insets the content for the IME itself (see
|
||||
* [applyFullScreenHostInsets]). It stays just as keyless: the page JS runs here, every NIP-07
|
||||
* `window.nostr` call is brokered + consent-gated in the main process per origin, and the keys never
|
||||
* leave it.
|
||||
*
|
||||
* Mirrors [NappletHostActivity]'s sandbox scaffolding (trusted chrome, loading screen, foreground hold)
|
||||
* but loads a live URL directly instead of serving verified blobs through a shell.
|
||||
@@ -214,15 +212,10 @@ class NappletBrowserActivity : ComponentActivity() {
|
||||
addView(topProgressBar)
|
||||
}
|
||||
setContentView(root)
|
||||
// Pad by the system bars + cutout, but NOT the IME — windowSoftInputMode=adjustResize shrinks the
|
||||
// window when the keyboard shows, and the WebView (filling contentFrame) resizes so focused inputs
|
||||
// stay visible. Zero the consumed insets before they reach the WebView so it doesn't double-pad.
|
||||
ViewCompat.setOnApplyWindowInsetsListener(root) { view, insets ->
|
||||
val applied = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()
|
||||
val bars = insets.getInsets(applied)
|
||||
view.setPadding(bars.left, bars.top, bars.right, bars.bottom)
|
||||
WindowInsetsCompat.Builder(insets).setInsets(applied, Insets.NONE).build()
|
||||
}
|
||||
// Pad by the system bars + cutout AND the IME: on an edge-to-edge window (enforced for targetSdk
|
||||
// 35+ on Android 15+) windowSoftInputMode=adjustResize no longer shrinks the window, so without
|
||||
// this the keyboard covers the bottom of the page. See applyFullScreenHostInsets.
|
||||
root.applyFullScreenHostInsets()
|
||||
|
||||
contentFrame.addView(webView, FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT))
|
||||
loadingView = buildLoadingView().also { contentFrame.addView(it) }
|
||||
|
||||
+4
-16
@@ -56,9 +56,6 @@ import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.webkit.JavaScriptReplyProxy
|
||||
import androidx.webkit.ProxyConfig
|
||||
import androidx.webkit.ProxyController
|
||||
@@ -304,19 +301,10 @@ class NappletHostActivity : ComponentActivity() {
|
||||
addView(topProgressBar)
|
||||
}
|
||||
setContentView(root)
|
||||
// Activities are edge-to-edge by default on recent Android; pad by the system bar and
|
||||
// display-cutout insets so neither the chrome nor the applet draws under the system bars.
|
||||
ViewCompat.setOnApplyWindowInsetsListener(root) { view, insets ->
|
||||
val applied = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()
|
||||
val bars = insets.getInsets(applied)
|
||||
view.setPadding(bars.left, bars.top, bars.right, bars.bottom)
|
||||
// Zero the insets we just turned into padding before they reach the child WebView: on
|
||||
// targetSdk 35+ the WebView auto-applies any insets it receives to its own web content, so
|
||||
// leaving them un-consumed padded the bottom a SECOND time — a band of the page's own
|
||||
// background above the navigation bar. Keep the other types (notably IME) flowing so the
|
||||
// applet's keyboard-aware resize still works.
|
||||
WindowInsetsCompat.Builder(insets).setInsets(applied, Insets.NONE).build()
|
||||
}
|
||||
// Activities are edge-to-edge by default on recent Android; pad by the system bar, display-cutout
|
||||
// and IME insets so neither the chrome nor the applet draws under the system bars or the soft
|
||||
// keyboard (adjustResize no longer resizes an edge-to-edge window). See applyFullScreenHostInsets.
|
||||
root.applyFullScreenHostInsets()
|
||||
|
||||
probeAndMount()
|
||||
}
|
||||
|
||||
+32
@@ -20,11 +20,43 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.napplethost
|
||||
|
||||
import android.view.View
|
||||
import android.webkit.WebView
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
|
||||
/**
|
||||
* Insets the root of a **full-screen** sandbox host (browser / napplet / nsite) by the system bars,
|
||||
* the display cutout, **and the IME**, then hides those types from its children.
|
||||
*
|
||||
* The IME half is not optional on current Android. These activities used to lean on
|
||||
* `windowSoftInputMode="adjustResize"` to shrink the window when the keyboard opened — the WebView
|
||||
* shrank with it and the focused input stayed visible. That flag is a **no-op for apps targeting SDK
|
||||
* 35+** running on Android 15+: edge-to-edge is enforced, the window keeps its full height, and the
|
||||
* keyboard simply draws over the bottom of the page. Padding the root by the IME inset reproduces the
|
||||
* old resize behaviour on our side of the window.
|
||||
*
|
||||
* The bottom is `max(bars, ime)` rather than a sum because an open IME sits *over* the navigation bar;
|
||||
* adding them would leave a dead band the height of the nav bar above the keyboard.
|
||||
*
|
||||
* Zeroing the consumed types before they reach the children matters for the same reason
|
||||
* [dropSystemBarInsets] exists: on targetSdk 35+ a WebView applies whatever insets it receives to its
|
||||
* own web content, so leaving them un-consumed pads the page a second time.
|
||||
*/
|
||||
fun View.applyFullScreenHostInsets() {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(this) { view, insets ->
|
||||
val barTypes = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()
|
||||
val bars = insets.getInsets(barTypes)
|
||||
val ime = insets.getInsets(WindowInsetsCompat.Type.ime())
|
||||
view.setPadding(bars.left, bars.top, bars.right, maxOf(bars.bottom, ime.bottom))
|
||||
WindowInsetsCompat
|
||||
.Builder(insets)
|
||||
.setInsets(barTypes or WindowInsetsCompat.Type.ime(), Insets.NONE)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops an **embedded** sandbox WebView from inseting its own web content for the system bars.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user