From 1ecb5c9aa3a3a38b8dafeaa78bd2c77c13f4178b Mon Sep 17 00:00:00 2001 From: Dorian Date: Thu, 23 Jul 2026 20:51:57 +0100 Subject: [PATCH] fix(companion): smooth camera preview + no flash on scanner open MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ZXing TRY_HARDER (plus the inverted retry) ran on every camera frame, pegging a core — that CPU contention is what made the preview stutter. Decode attempts are now gated to ~7/s; KEEP_ONLY_LATEST drops the rest. PreviewView switches to TextureView (COMPATIBLE): the SurfaceView default punches a window hole that black-flashes inside Compose fades and ignores rounded-corner clipping. CameraQrPreview is now shared with the wallet scan modal. Co-Authored-By: Claude Fable 5 --- .../app/ui/components/QrScannerOverlay.kt | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/Android/app/src/main/java/com/archipelago/app/ui/components/QrScannerOverlay.kt b/Android/app/src/main/java/com/archipelago/app/ui/components/QrScannerOverlay.kt index 644524ed..e6d531e3 100644 --- a/Android/app/src/main/java/com/archipelago/app/ui/components/QrScannerOverlay.kt +++ b/Android/app/src/main/java/com/archipelago/app/ui/components/QrScannerOverlay.kt @@ -217,13 +217,20 @@ fun QrScannerOverlay( } } +/** Shared by the pairing scanner and the wallet scan modal. */ @Composable -private fun CameraQrPreview(onDecoded: (String) -> Unit) { +internal fun CameraQrPreview(onDecoded: (String) -> Unit) { val context = LocalContext.current val lifecycleOwner = LocalLifecycleOwner.current val currentOnDecoded by rememberUpdatedState(onDecoded) val previewView = remember { - PreviewView(context).apply { scaleType = PreviewView.ScaleType.FILL_CENTER } + PreviewView(context).apply { + scaleType = PreviewView.ScaleType.FILL_CENTER + // TextureView, not the SurfaceView default: SurfaceView punches a + // hole in the window, which black-flashes inside Compose fades and + // ignores rounded-corner clipping (wallet modal). + implementationMode = PreviewView.ImplementationMode.COMPATIBLE + } } DisposableEffect(Unit) { @@ -282,7 +289,19 @@ private class QrCodeAnalyzer(private val onDecoded: (String) -> Unit) : ImageAna ) } + private var lastAttempt = 0L + override fun analyze(image: ImageProxy) { + // Decode ~7x/s, not on every frame: TRY_HARDER (plus the inverted + // retry) pegs a core when run at camera rate, and that CPU contention + // is what made the preview itself stutter. KEEP_ONLY_LATEST means the + // frames skipped here are simply dropped, so decodes stay current. + val now = System.currentTimeMillis() + if (now - lastAttempt < 140) { + image.close() + return + } + lastAttempt = now try { val plane = image.planes[0] val buffer = plane.buffer