mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 03:38:23 +00:00
fix: inset the full-screen browser and napplet host for the IME
windowSoftInputMode=adjustResize no longer resizes the window: it is a no-op for apps targeting SDK 35+ on Android 15+, where edge-to-edge is enforced and Theme.Amethyst does not opt out. The full-screen browser padded its root by the system bars and display cutout only, relying on that resize to keep focused inputs visible, so the soft keyboard simply covered the bottom of the page. Pad the root by the IME inset as well — max(bars, ime) on the bottom, since an open IME sits over the navigation bar — and zero the consumed types before they reach the WebView, which would otherwise apply them to its own web content a second time. Shared as applyFullScreenHostInsets between NappletBrowserActivity and NappletHostActivity, which carried the same listener and the same defect. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwZuom4fFXjr85aGajJhSo
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