From 7d7789c8c9e71b81e5b56c6e1ab1cbe5525d7c3e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 30 Jun 2026 00:14:19 +0000 Subject: [PATCH] fix(browser): console toggle + on-top pull tab in full-screen browser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The full-screen direct-WebView browser (NappletBrowserActivity, launched when you type an address) and the sandbox host (NappletHostActivity) used an older console design that diverged from the embedded tabs' Compose chrome: - The Console row in NappletControlSheet was a plain action row and the bottom pull-up grabber was always visible. Make Console a Switch toggle (like the Tor row / the Compose TopControlSheet), and hide the whole NappletConsolePanel until the toggle is on — turning it on reveals the sheet already pulled up, mirroring BottomConsoleSheet. - The console grabber/panel sat at elevation 0 while the top sheet's panel is at 6dp, so an open top sheet drew over the console when they overlapped (e.g. in landscape). Elevate the console sheet above the top sheet so its pull tab and log render on top, matching the Compose layer where BottomConsoleSheet is composed after TopControlSheet. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01A9kfpNrBRB8WJNi67NHGGX --- .../napplethost/NappletBrowserActivity.kt | 2 +- .../napplethost/NappletConsolePanel.kt | 43 +++++++++++++++---- .../napplethost/NappletControlSheet.kt | 36 ++++++++++++---- .../napplethost/NappletHostActivity.kt | 2 +- 4 files changed, 63 insertions(+), 20 deletions(-) diff --git a/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletBrowserActivity.kt b/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletBrowserActivity.kt index ef053427ea..c79b04f2b7 100644 --- a/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletBrowserActivity.kt +++ b/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletBrowserActivity.kt @@ -631,7 +631,7 @@ class NappletBrowserActivity : ComponentActivity() { onInfo = null, liveUrl = startUrl, onNavigate = { loadAddress(it) }, - onConsole = { consolePanel?.toggle() }, + onConsole = { show -> consolePanel?.setShowing(show) }, isFavoriteInitially = intent.getBooleanExtra(EXTRA_IS_FAVORITE, false), onFavoriteToggle = { url, _ -> sendFavoriteToggle(url) }, ).also { controlSheet = it } diff --git a/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletConsolePanel.kt b/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletConsolePanel.kt index d9e6d386d0..8c461caf09 100644 --- a/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletConsolePanel.kt +++ b/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletConsolePanel.kt @@ -37,11 +37,17 @@ import androidx.core.content.ContextCompat import com.vitorpamplona.amethyst.commons.R as CommonsR /** - * The full-screen browser's **bottom pull-up sheet** for JavaScript console output. Collapsed it's - * a small grabber at the bottom edge, symmetric to [NappletControlSheet]'s top grabber. Pull it up - * (or tap) to reveal a scrollable log of [console.log / warn / error / debug] messages captured - * from the page via `WebChromeClient.onConsoleMessage`. Capped at [MAX_ENTRIES] entries (oldest - * dropped on overflow). Built in plain Views like [NappletControlSheet] — no Compose/Material. + * The full-screen browser's **bottom pull-up sheet** for JavaScript console output. The whole sheet is + * hidden until the user flips the Console **toggle** in [NappletControlSheet] ([setShowing]); turned on, + * it reveals a small grabber at the bottom edge (symmetric to that sheet's top grabber) already pulled + * up. Pull it down/up (or tap) to collapse/expand the scrollable log of + * [console.log / warn / error / debug] messages captured from the page via + * `WebChromeClient.onConsoleMessage`. Capped at [MAX_ENTRIES] entries (oldest dropped on overflow). + * Built in plain Views like [NappletControlSheet] — no Compose/Material. + * + * Its grabber + panel are elevated above [NappletControlSheet]'s panel so that, when both are open at + * once (e.g. in landscape), this bottom sheet draws on top of the top pull-down sheet — mirroring the + * Compose layer, where `BottomConsoleSheet` is composed after `TopControlSheet`. */ @SuppressLint("UseSwitchCompatOrMaterialCode") class NappletConsolePanel( @@ -52,6 +58,7 @@ class NappletConsolePanel( private val surface = resolveThemeColor(android.R.attr.colorBackground) private var expanded = false + private var showing = false private val panel: LinearLayout private lateinit var logContainer: LinearLayout private lateinit var scrollView: ScrollView @@ -59,6 +66,9 @@ class NappletConsolePanel( init { orientation = VERTICAL gravity = Gravity.CENTER_HORIZONTAL + // Hidden until the Console toggle turns it on; matches the Compose `BottomConsoleSheet`, which is + // only composed while the toggle is on. + visibility = View.GONE panel = buildPanel().also { addView(it) } addView(buildGrabber()) @@ -68,7 +78,8 @@ class NappletConsolePanel( LinearLayout(context).apply { orientation = VERTICAL visibility = View.GONE - elevation = dp(6).toFloat() + // Above NappletControlSheet's panel (6dp) so an open console draws over an open top sheet. + elevation = dp(8).toFloat() background = GradientDrawable().apply { cornerRadii = floatArrayOf(dp(16).toFloat(), dp(16).toFloat(), dp(16).toFloat(), dp(16).toFloat(), 0f, 0f, 0f, 0f) @@ -123,9 +134,21 @@ class NappletConsolePanel( var entryCount: Int = 0 private set - /** Toggles the panel's expanded/collapsed state; used by the control sheet's Console row. */ - fun toggle() { - if (expanded) collapse() else expand() + /** + * Shows or hides the entire sheet (grabber + log), driven by the control sheet's Console **toggle**: + * off hides everything, on reveals the sheet already pulled up — mirroring the Compose + * `BottomConsoleSheet`, which is only composed while the toggle is on and opens expanded. + */ + fun setShowing(show: Boolean) { + if (show == showing) return + showing = show + if (show) { + visibility = View.VISIBLE + expand() + } else { + collapse() + visibility = View.GONE + } } fun appendLog( @@ -205,6 +228,8 @@ class NappletConsolePanel( gravity = Gravity.CENTER_HORIZONTAL } setPadding(dp(16), dp(7), dp(16), dp(7)) + // Above NappletControlSheet's panel (6dp) so the grabber stays on top of an open top sheet. + elevation = dp(8).toFloat() background = GradientDrawable().apply { cornerRadii = floatArrayOf(dp(12).toFloat(), dp(12).toFloat(), dp(12).toFloat(), dp(12).toFloat(), 0f, 0f, 0f, 0f) diff --git a/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletControlSheet.kt b/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletControlSheet.kt index dc329efa66..605909c7a6 100644 --- a/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletControlSheet.kt +++ b/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletControlSheet.kt @@ -65,9 +65,9 @@ class NappletControlSheet( // nsite/napplet), where it renders an editable address row; [onNavigate] loads what the user types. liveUrl: String? = null, private val onNavigate: ((String) -> Unit)? = null, - // When non-null, a "Console" row is added to the pull-down sheet. The callback toggles the - // browser's console log panel; the count label is updated via [updateConsoleCount]. - private val onConsole: (() -> Unit)? = null, + // When non-null, a "Console" toggle row is added to the pull-down sheet. The callback is invoked with + // the new visibility each time the user flips it; the count label is updated via [updateConsoleCount]. + private val onConsole: ((Boolean) -> Unit)? = null, // When non-null, a favorite toggle row is shown; called with the current URL and new isFavorite state. isFavoriteInitially: Boolean = false, private val onFavoriteToggle: ((url: String, isFavorite: Boolean) -> Unit)? = null, @@ -80,6 +80,7 @@ class NappletControlSheet( private var torOn = torInitiallyOn private var currentUrl = liveUrl private var isFavorite = isFavoriteInitially + private var consoleShowing = false private val panel: LinearLayout private var torLabel: TextView? = null @@ -87,6 +88,7 @@ class NappletControlSheet( private var addressField: EditText? = null private var securityGlyph: TextView? = null private var consoleLabel: TextView? = null + private var consoleSwitch: Switch? = null private var favoriteLabel: TextView? = null init { @@ -129,25 +131,32 @@ class NappletControlSheet( }, ) } - onConsole?.let { console -> + onConsole?.let { val label = TextView(context).apply { text = context.getString(CommonsR.string.browser_console_title_short) setTextColor(onSurface) textSize = 15f setPadding(dp(8), 0, 0, 0) + // Weight 1 so the label fills and shoves the Switch to the end, like the Tor row. + layoutParams = LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f) } consoleLabel = label + // Display-only switch (the whole row is the touch target), matching the Tor row + Compose twin. + val toggle = + Switch(context).apply { + isChecked = consoleShowing + isClickable = false + isFocusable = false + } + consoleSwitch = toggle addView( LinearLayout(context).apply { orientation = HORIZONTAL gravity = Gravity.CENTER_VERTICAL setPadding(dp(8), dp(10), dp(8), dp(10)) isClickable = true - setOnClickListener { - collapse() - console() - } + setOnClickListener { toggleConsole() } addView( TextView(context).apply { text = ">" @@ -155,10 +164,11 @@ class NappletControlSheet( textSize = 18f width = dp(28) gravity = Gravity.CENTER - typeface = android.graphics.Typeface.MONOSPACE + typeface = Typeface.MONOSPACE }, ) addView(label) + addView(toggle) }, ) } @@ -367,6 +377,14 @@ class NappletControlSheet( onToggleTor(next) } + private fun toggleConsole() { + consoleShowing = !consoleShowing + consoleSwitch?.isChecked = consoleShowing + // Collapse the top sheet on toggle, like the Compose twin, so the bottom console isn't hidden behind it. + collapse() + onConsole?.invoke(consoleShowing) + } + private fun actionRow( glyph: String, label: String, diff --git a/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletHostActivity.kt b/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletHostActivity.kt index 4861ab7798..3a486e9407 100644 --- a/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletHostActivity.kt +++ b/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletHostActivity.kt @@ -838,7 +838,7 @@ class NappletHostActivity : ComponentActivity() { torInitiallyOn = if (profile.exposesNetwork && proxyPort > 0) useTor else null, onNetworkTap = if (profile.exposesNetwork && proxyPort > 0) ({ setNetworkMode(!useTor) }) else null, onInfo = { showAccessDialog() }, - onConsole = { consolePanel?.toggle() }, + onConsole = { show -> consolePanel?.setShowing(show) }, ).also { controlSheet = it } private fun buildConsolePanel(): View =