Summary
The shared QrScannerView (lib/widgets/scanner/qr_scanner_view.dart) forwards every frame detection to its consumer, and both consumers navigate on the first valid code while the camera keeps running behind the pushed route. This allows a duplicate navigation (the same page pushed twice) when a valid QR code stays in the camera frame for more than one detection cycle.
Affected flows (both, via the shared widget)
- Send (W2W):
lib/screens/send/send_recipient_page.dart:46-52 — Navigator.push(SendAmountPage) immediately followed by SendRecipientCubit.reset(), still inside the same BlocListener. reset() re-arms detection; the next frame delivers the same rawValue → onCodeDetected fires again → a second SendAmountPage is pushed.
- Pay (OCP):
lib/screens/pay/pay_scan_page.dart:34-41 — identical push + reset() pattern (PayScanCubit/PayQuotePage). Pre-existing, introduced with the OCP-pay flow.
Root cause
QrScannerView has no route-lifecycle awareness and no single-emit/debounce guard: there is no RouteAware/RouteObserver, no MobileScannerController.pause(), and no dedup of repeated identical detections. Verified: grep -rn "RouteAware|RouteObserver|didPushNext|pause()|stop()" lib/widgets/scanner lib/screens/pay lib/screens/send → 0 hits.
Repro
- Open Send (or Pay) scan.
- Hold a valid QR code steadily in frame (realistic — the phone is not pulled away instantly).
- First detection navigates; the still-running scanner detects the same code again on the next frame → a duplicate destination page is pushed onto the stack.
Recommended robust fix (once, in the shared widget — benefits both flows, no consumer duplication)
Add a single-emit / debounce guard to QrScannerView (e.g. ignore repeated detections until the raw value changes or a short debounce window elapses, and/or pause the controller while a route is pushed on top via RouteAware). Fixing it in QrScannerView avoids touching either consumer's business logic and covers Pay and Send together.
Not doing this in the current PRs
Not fixed in #687 (W2W rebase) or #674 (OCP) because it is a pre-existing, shared behavioral issue; the robust fix is a cross-flow change to QrScannerView best done in a focused PR rather than smuggled into a rebase / feature PR.
Summary
The shared
QrScannerView(lib/widgets/scanner/qr_scanner_view.dart) forwards every frame detection to its consumer, and both consumers navigate on the first valid code while the camera keeps running behind the pushed route. This allows a duplicate navigation (the same page pushed twice) when a valid QR code stays in the camera frame for more than one detection cycle.Affected flows (both, via the shared widget)
lib/screens/send/send_recipient_page.dart:46-52—Navigator.push(SendAmountPage)immediately followed bySendRecipientCubit.reset(), still inside the sameBlocListener.reset()re-arms detection; the next frame delivers the samerawValue→onCodeDetectedfires again → a secondSendAmountPageis pushed.lib/screens/pay/pay_scan_page.dart:34-41— identicalpush+reset()pattern (PayScanCubit/PayQuotePage). Pre-existing, introduced with the OCP-pay flow.Root cause
QrScannerViewhas no route-lifecycle awareness and no single-emit/debounce guard: there is noRouteAware/RouteObserver, noMobileScannerController.pause(), and no dedup of repeated identical detections. Verified:grep -rn "RouteAware|RouteObserver|didPushNext|pause()|stop()" lib/widgets/scanner lib/screens/pay lib/screens/send→ 0 hits.Repro
Recommended robust fix (once, in the shared widget — benefits both flows, no consumer duplication)
Add a single-emit / debounce guard to
QrScannerView(e.g. ignore repeated detections until the raw value changes or a short debounce window elapses, and/or pause the controller while a route is pushed on top viaRouteAware). Fixing it inQrScannerViewavoids touching either consumer's business logic and covers Pay and Send together.Not doing this in the current PRs
Not fixed in #687 (W2W rebase) or #674 (OCP) because it is a pre-existing, shared behavioral issue; the robust fix is a cross-flow change to
QrScannerViewbest done in a focused PR rather than smuggled into a rebase / feature PR.