fix(wallet-android): make in-app QR camera actually show through

Three issues broke the previous commit's "in-app camera" shipping as a
dark non-fullscreen rectangle:

1. WebView was opaque. The @capacitor-mlkit/barcode-scanning plugin's
   startScan() renders a native camera preview UNDER the WebView, but on
   Android the WebView paints an opaque background by default — nothing
   below it ever shows. MainActivity.java now sets
   bridge.getWebView().setBackgroundColor(Color.TRANSPARENT) on create.
   The HTML/CSS keeps painting its own backgrounds, so this is only
   visible when the HTML side ALSO goes transparent (during a scan).

2. The .phone wrapper has an opaque dark-blue gradient
   (linear-gradient(180deg, rgba(19,26,42,.55), rgba(10,15,28,.55))) that
   stayed painted on top of the (now-transparent) WebView, blocking the
   preview. The scanner CSS now null-chains the whole stack
   (html → body → #root → .app-root → .phone) via background: transparent
   + background-image: none + border: 0 while body.barcode-scanner-active
   is set.

3. Modal was pinned to the .phone 420px-max column — on a 1080×2400
   phone the scan frame + Cancel sat inside a narrow central strip while
   the native preview filled the whole screen. The modal now escapes
   with position: fixed; inset: 0; width: 100vw; height: 100dvh. The
   scan frame is centred at min(72vw, 320px), buttons sit at the bottom
   with a backdrop-blur chip so they stay legible against bright scenes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Local Dev 2026-10-22 03:25:47 +03:00
parent 3fddd71acc
commit a5474b11aa
2 changed files with 81 additions and 11 deletions

View file

@ -1,5 +1,28 @@
package cash.deviant.wallet;
import android.graphics.Color;
import android.os.Bundle;
import com.getcapacitor.BridgeActivity;
public class MainActivity extends BridgeActivity {}
/**
* MainActivity — Capacitor BridgeActivity entry point for the Deviant wallet.
*
* Overridden to paint the WebView background transparent so the
* @capacitor-mlkit/barcode-scanning plugin's `startScan()` can render its
* native camera preview UNDER the WebView and have it actually show through.
* The HTML/CSS continues to paint its own background (the dark starfield of
* the `.phone` frame), so this transparency only matters when the HTML side
* ALSO goes transparent — i.e. when `body.barcode-scanner-active` strips the
* backgrounds for the duration of a scan. Outside of that, nothing visible
* changes: HTML paints its own color on every pixel.
*/
public class MainActivity extends BridgeActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (bridge != null && bridge.getWebView() != null) {
bridge.getWebView().setBackgroundColor(Color.TRANSPARENT);
}
}
}

View file

@ -916,15 +916,62 @@
/* ─── In-app QR scanner overlay (native, Capacitor-mlkit startScan) ──────────
The plugin renders the live camera preview UNDER the WebView by making the
platform background transparent. For that to show through, we hide the whole
React app via `body.barcode-scanner-active` and selectively un-hide only the
scanner modal elements (anything tagged `.barcode-scanner-modal`). Result:
the user sees the Deviant-styled scan frame + Cancel button over the native
camera feed — not a Google-branded scanner activity. */
body.barcode-scanner-active { background: transparent !important; visibility: hidden; }
body.barcode-scanner-active #root { background: transparent !important; }
platform background transparent (set in MainActivity.java via
webView.setBackgroundColor(Color.TRANSPARENT)). For that to show through,
EVERY opaque wrapper between the plugin and the pixel has to go transparent
AND the whole React tree must be hidden except our scanner modal.
Chain of transparency (any opaque link breaks the preview):
html → body → #root → .app-root → .phone → (modal elements, visible)
The `.barcode-scanner-modal` opt-out keeps the scan frame + Cancel button
visible while everything else is hidden. */
body.barcode-scanner-active,
body.barcode-scanner-active html,
body.barcode-scanner-active #root,
body.barcode-scanner-active .app-root,
body.barcode-scanner-active .phone {
background: transparent !important;
background-color: transparent !important;
background-image: none !important;
border: 0 !important;
}
body.barcode-scanner-active { visibility: hidden; }
body.barcode-scanner-active .barcode-scanner-modal,
body.barcode-scanner-active .barcode-scanner-modal * { visibility: visible; }
/* Make the backdrop fully transparent during a native scan so the preview is
not dimmed by the modal's usual veil. */
body.barcode-scanner-active .sheet-backdrop.barcode-scanner-modal { background: transparent !important; }
/* Pull the scanner modal out of the `.phone` 420px column so it covers the
WHOLE physical screen — the native camera preview is full-screen, our
overlay has to match. Fixed inset:0 ignores the ancestor's constraints. */
body.barcode-scanner-active .modal.barcode-scanner-modal {
position: fixed; inset: 0; max-width: none; width: 100vw; height: 100vh; height: 100dvh;
background: transparent !important;
display: flex; flex-direction: column; justify-content: space-between;
padding: 24px 20px calc(32px + env(safe-area-inset-bottom)) 20px;
z-index: 9999;
}
body.barcode-scanner-active .sheet-backdrop.barcode-scanner-modal {
background: transparent !important;
position: fixed; inset: 0;
}
/* Centre the scan frame vertically within the fullscreen modal. */
body.barcode-scanner-active .modal.barcode-scanner-modal .scan-frame {
width: min(72vw, 320px); aspect-ratio: 1 / 1; margin: auto;
}
/* Keep the row of Cancel + Paste buttons visible against the preview with a
subtle inset shadow — otherwise they'd disappear over a bright scene. */
body.barcode-scanner-active .modal.barcode-scanner-modal .row-btns {
margin-top: auto;
}
body.barcode-scanner-active .modal.barcode-scanner-modal .row-btns .ghost {
background: rgba(2, 4, 10, 0.72);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
body.barcode-scanner-active .modal.barcode-scanner-modal .flow-head {
background: rgba(2, 4, 10, 0.6);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
border-radius: 12px;
padding: 10px 12px;
}