From 488af67233ea7da8a98b75d7faebd683e9166c18 Mon Sep 17 00:00:00 2001 From: PeterXMR Date: Tue, 26 May 2026 18:22:01 -0300 Subject: [PATCH] (android) Block pointer events from falling through the screen-lock overlay When the wallet is locked and showing "Unlock to continue", pointer events in empty regions of the lock overlay could reach interactive elements drawn on the destination underneath. The original report (#733) describes opening the "Add a custom description to this payment" sheet through the lock screen. Wrap the lock-prompt content in a Box whose pointerInput consumes any pointer event still unconsumed during the Final pass. Children with their own pointer handlers (the biometric / PIN code buttons inside the lock prompt) consume their events first during the Main pass, so the unlock controls remain interactive while empty-space taps no longer leak through. Set contentAlignment = Alignment.Center on the new Box so the snowflake icon and "Unlock to continue" text stay visually centered (matching the parent StartupView's contentAlignment). Closes #733. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../phoenix/android/startup/StartupView.kt | 42 +++++++++++++++---- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/startup/StartupView.kt b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/startup/StartupView.kt index 9dce48fdf..ac01e6217 100644 --- a/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/startup/StartupView.kt +++ b/phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/startup/StartupView.kt @@ -51,6 +51,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -354,15 +356,37 @@ private fun BoxScope.LoadWallet( WalletStartupView(text = stringResource(R.string.utils_loading_prefs), metadata = metadata) } true -> { - WalletStartupView(text = stringResource(id = R.string.lockprompt_title), metadata = metadata) - ScreenLockPrompt( - walletId = userWallet.walletId, - walletName = metadata.nameOrDefault(), - promptScreenLockImmediately = promptScreenLockImmediately, - onUnlock = { doLoadWallet(userWallet) }, - onLock = { }, - goToWalletSelector = goToWalletSelector, - ) + // The lock screen is rendered above the previous destination via Compose Navigation, + // but pointer events in empty regions of this overlay would otherwise reach whatever + // is drawn underneath (e.g. an active dialog trigger on the payment details screen). + // Wrap the lock prompt in a Box that consumes every pointer event left unhandled by + // its own children — the biometric/PIN buttons consume their taps first during the + // Main pass, so this Final-pass sink only catches "empty space" taps. + Box( + modifier = Modifier + .fillMaxSize() + .pointerInput(Unit) { + awaitPointerEventScope { + while (true) { + val event = awaitPointerEvent(PointerEventPass.Final) + event.changes.forEach { change -> + if (!change.isConsumed) change.consume() + } + } + } + }, + contentAlignment = Alignment.Center, + ) { + WalletStartupView(text = stringResource(id = R.string.lockprompt_title), metadata = metadata) + ScreenLockPrompt( + walletId = userWallet.walletId, + walletName = metadata.nameOrDefault(), + promptScreenLockImmediately = promptScreenLockImmediately, + onUnlock = { doLoadWallet(userWallet) }, + onLock = { }, + goToWalletSelector = goToWalletSelector, + ) + } } false -> { WalletStartupView(text = stringResource(id = R.string.startup_starting), metadata = metadata)