Skip to content

fix(qr-scan): gate CameraView on permission + surface unrecognized QR#85

Draft
epicexcelsior wants to merge 4 commits into
anonmesh:stagingfrom
epicexcelsior:epic/iou-qr-camera-fix
Draft

fix(qr-scan): gate CameraView on permission + surface unrecognized QR#85
epicexcelsior wants to merge 4 commits into
anonmesh:stagingfrom
epicexcelsior:epic/iou-qr-camera-fix

Conversation

@epicexcelsior
Copy link
Copy Markdown
Collaborator

Summary

Two related iOS QR-scan bugs, both reproduced on personal-team dev build (no public team certs). Critical because "impossible to find peers" was the user's symptom — QR scan is the primary peer-discovery path.

Bug 1 — Black camera preview until app restart

Cause: `CameraView` from expo-camera caches its permission state at mount time on iOS. If we render it before the OS prompt has resolved (the natural flow when a first-time user taps the scanner button), the preview shows black and never recovers, even after the user taps "Allow." Only an app restart fixes it, because a fresh mount captures the now-granted permission.

Fix: gate `CameraView` rendering on `permission?.granted === true`. While permission is pending or null, show a "Requesting camera access…" placeholder. When the OS grants permission, the conditional flips and `CameraView` mounts fresh with the right state.

Bug 2 — Scan succeeds, modal closes, nothing happens

Cause: `PeersDrawer` onResult only handled `result.type === 'lxmf'`. Any other type (`lxmf-group`, `solana`, `unknown`) silently closed the modal. From the user's perspective: scanner opened, recognized the QR, then nothing — looks broken.

Fix: explicit feedback for every result type:

  • `lxmf-group` → "Looks like a channel QR, open Join channel"
  • `solana` → "Wallet address, use Send screen"
  • `unknown` → alert with first 64 chars of raw payload so the user can see what they scanned

Diagnostic

Added a `DEV` console.log dump of the parsed scan result inside PeersDrawer onResult. Lets us adb logcat-trace exactly what the parser returned, which helps if any peer QR formats in the wild don't match the existing regex.

Scope

  • `components/messages/QRScannerModal.tsx` — permission gate
  • `components/messages/PeersDrawer.tsx` — onResult feedback + dev log

Two-file diff, conservative. No native config touched.

Test plan (iOS dev build)

  • Cold-launch app → open Messages → tap camera icon in peers drawer
  • Expect: "Requesting camera access…" placeholder, then OS permission prompt
  • Tap Allow → expect: camera preview becomes live WITHOUT app restart
  • Scan a peer's QR (raw hex from Settings → Mesh ID): expect chat thread opens
  • Scan a channel QR (`lxmf://group/…`): expect "Looks like a channel QR" alert
  • Scan a Solana wallet QR: expect "Wallet address" alert
  • Scan any other QR (e.g. a URL or a random image): expect "Unrecognized QR" alert with the raw payload preview
  • Deny camera permission, return to Settings, grant manually, return to app: scanner should pick up the change without restart

Rollback

`git revert` — two-file diff, no permission descriptor changes.

Two related iOS bugs observed on a personal-team dev build:

1. CameraView mounted before permission resolved → black preview that
   never recovers until app restart. On iOS the component caches its
   permission state at mount; flipping null→granted at runtime doesn't
   re-init the preview. Gate the mount on `permission.granted === true`
   so the conditional flip causes a fresh mount with permission in place.

2. PeersDrawer's QR onResult silently dropped any QR that wasn't a
   plain LXMF hash — group QRs, Solana addresses, malformed/empty
   payloads all closed the modal with no feedback, leaving users
   thinking "scanner is broken." Surface explicit alerts:
   - lxmf-group → "looks like a channel QR, use Join channel"
   - solana    → "wallet address, use Send screen"
   - unknown   → show first 64 chars of raw payload so user can debug

Also adds a __DEV__ console.log of the parsed result so we can adb
logcat-trace exactly what came out of the scanner.
@epicexcelsior epicexcelsior force-pushed the epic/iou-qr-camera-fix branch from d78415f to c5767b7 Compare May 19, 2026 11:13
three.js (pulled in by the mesh-map renderer) uses ES2022 class static
blocks. babel-preset-expo's default target list doesn't include the
transform, so iOS bundling fails with "Static class blocks are not
enabled" — Metro returns 500 on every bundle request and the dev-client
can't fetch JS at all. No babel.config.js existed; this adds one with
preset-expo + the missing plugin.

The plugin was already in node_modules transitively (pulled in by
@babel/preset-env), so this is config-only — no new dep, no native rebuild.
expo-camera@17 split audio recording into a separate package (expo-audio).
iOS native side hard-links the ExpoAudio module even when the JS-level use
is barcode-scanning only, so the dev-client build fails with "Cannot find
native module expoAudio" without the peer dep installed.

`npx expo install expo-audio` added 1.1.1 to deps and registered the
config plugin in app.json. No source code changes — this is pure native
bridge availability.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant