diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7660bc042..c70591cd8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -105,34 +105,34 @@ importers: version: 0.7.2 applesauce-accounts: specifier: next - version: 0.0.0-next-20250313155042(typescript@5.8.2) + version: 0.0.0-next-20250313225050(typescript@5.8.2) applesauce-actions: specifier: next - version: 0.0.0-next-20250313155042(typescript@5.8.2) + version: 0.0.0-next-20250313225050(typescript@5.8.2) applesauce-content: specifier: next - version: 0.0.0-next-20250313155042(typescript@5.8.2) + version: 0.0.0-next-20250313225050(typescript@5.8.2) applesauce-core: specifier: next - version: 0.0.0-next-20250313155042(typescript@5.8.2) + version: 0.0.0-next-20250313225050(typescript@5.8.2) applesauce-factory: specifier: next - version: 0.0.0-next-20250313155042(typescript@5.8.2) + version: 0.0.0-next-20250313225050(typescript@5.8.2) applesauce-loaders: specifier: next - version: 0.0.0-next-20250313155042(typescript@5.8.2) + version: 0.0.0-next-20250313225050(typescript@5.8.2) applesauce-react: specifier: next - version: 0.0.0-next-20250313155042(react-dom@19.0.0(react@19.0.0))(typescript@5.8.2) + version: 0.0.0-next-20250313225050(react-dom@19.0.0(react@19.0.0))(typescript@5.8.2) applesauce-relay: specifier: next - version: 0.0.0-next-20250313155042(typescript@5.8.2) + version: 0.0.0-next-20250313225050(typescript@5.8.2) applesauce-signers: specifier: next - version: 0.0.0-next-20250313155042(typescript@5.8.2) + version: 0.0.0-next-20250313225050(typescript@5.8.2) applesauce-wallet: specifier: next - version: 0.0.0-next-20250313155042(typescript@5.8.2) + version: 0.0.0-next-20250313225050(typescript@5.8.2) bech32: specifier: ^2.0.0 version: 2.0.0 @@ -2205,35 +2205,35 @@ packages: engines: {node: '>=8.0.0'} hasBin: true - applesauce-accounts@0.0.0-next-20250313155042: - resolution: {integrity: sha512-2bWwif44iIi/3ZQJwgTmMT9lIi+8m43W18FGc3esgnuXdux0gb36Nyk3xMD+4HIvxa1E/KJnl2MeNnQf5/rBFA==} + applesauce-accounts@0.0.0-next-20250313225050: + resolution: {integrity: sha512-vuMJNPAyQosyHLyyzUIVgn03WqheJFrsrc3twkrUoAp53nIOX43TO2H9i487tOmF7ia5HH1C+/KN1vI9fzKxGw==} - applesauce-actions@0.0.0-next-20250313155042: - resolution: {integrity: sha512-F/yQ1su5njzvmC09SbzyCJpgRC/t7URt3ZogEXFas9r1k3rllodXhC5ZM3b+QUU1XQUQQcX1ZBHBK6oTzF20Qw==} + applesauce-actions@0.0.0-next-20250313225050: + resolution: {integrity: sha512-fCDuhPxeUpjSZgz9v4BqGy3eFT0iNU185jwc1pB50dwDAWrnVJLwlUTWalIhQvWCrzRQqyc7Fu5dcwV0Z1ECZg==} - applesauce-content@0.0.0-next-20250313155042: - resolution: {integrity: sha512-JMvpH9a7s5dTFvk5ey4t/wd1K0YRId3IHGa8hMKEoa7ZRm3PHmCDKhK8KFgjAKF2nd6erIIOkCyfDmEO+7p+vQ==} + applesauce-content@0.0.0-next-20250313225050: + resolution: {integrity: sha512-vf4duzqtRKLLV9bDezCm4RASwLJmhqrzzyfH7T+l3ddih9So6mVMpES8FyG8NqDpFoM/0pHMBAXzwh5cUK+ySw==} - applesauce-core@0.0.0-next-20250313155042: - resolution: {integrity: sha512-HZeDganvR9kdAA7qexnkEXzSG+bdqMGOvUWgljZrvhjoAwazwX7XaOyj2vVmyQdmA8jY0V6pz+5RRTDgqCrx5A==} + applesauce-core@0.0.0-next-20250313225050: + resolution: {integrity: sha512-Z8/ukCDFmYbTxehLB0nlD+ewo06OgD1c0jZyDrtNWt9VpXz6PjRKss3UGlx5/IAttEE+wwc9+KHvPOrtVG3udQ==} - applesauce-factory@0.0.0-next-20250313155042: - resolution: {integrity: sha512-4xLAhram5hxgFkw2ATRIrAAg0r7FAVFLbwRfn6rxasYjrC0NMGGURWULWpHs/o3L2VQGM0iDcdhEYKvn+zIIrQ==} + applesauce-factory@0.0.0-next-20250313225050: + resolution: {integrity: sha512-faTv970d9/sLqOsK/s0EDXXY1Wr3N2I3Lub5XY6N4bqQtkoYr4G0TdZHlxW5MkyiSx7HT5IF6nP1ZU/qpzqCsw==} - applesauce-loaders@0.0.0-next-20250313155042: - resolution: {integrity: sha512-oyLU8fNObK/bfrLS099dfE3HlLj2Btc/MDBhN4FGBCnu8E3ya/r6bFyt0NOcUhxelJle4qi/pWMaRNUhiHdaqQ==} + applesauce-loaders@0.0.0-next-20250313225050: + resolution: {integrity: sha512-ebnUfTVxP1Pwm501iogVGC/Q2Rwe8cUIkRPjxr3hfeOYXVXrp+2rKY61gIYTG4grNeaFXomJe1c6k8tszCe9YA==} - applesauce-react@0.0.0-next-20250313155042: - resolution: {integrity: sha512-ry8xatCIBQm/+1SlG8NMchFCeQMOSPZwUAnO/qw1etW4X5c8PuIkYdQCOQYBoLp37YW0nXGgxImNwxhiz5FoGA==} + applesauce-react@0.0.0-next-20250313225050: + resolution: {integrity: sha512-N2fbW23kndMSYBakA/iTdkHDeougtXQLAEjNcvUG6EOSnSvOTQ3YQuP8qPBUc7xAwclZ5wAmdy7JC/EGVB2L4Q==} - applesauce-relay@0.0.0-next-20250313155042: - resolution: {integrity: sha512-0ZL1iHm0FcsFkO2d7Vjl06mBC2QugtD0+coHy1xP0fs92zGQKnoTXyAS6B3M014vg2x+mH1hwHuOCaQ9rjeEbw==} + applesauce-relay@0.0.0-next-20250313225050: + resolution: {integrity: sha512-dqN6ZMfAUmLjJ5A2zCcmxwIEIHX/rV5WwfnChoflujcTRflfTquUg6Q8vpOTkpfEKh2cm0Tci2GZLwEEiShryQ==} - applesauce-signers@0.0.0-next-20250313155042: - resolution: {integrity: sha512-yvaxUFgmZhbN28B/6skhgoZxdF4/aU9Y2DXoh4ZvmSiJaNJLv1MVE2s7c/vblcsyO7OqQCYKA78jryDLHh3l+w==} + applesauce-signers@0.0.0-next-20250313225050: + resolution: {integrity: sha512-o+kuvGp2eCtpEoVz3uGpDBgzq/vXp028lhRGDI+wL3nwI/ki77i7uADTzd8ylrq7eNvc3rdnebjZM9CpP1Y0vw==} - applesauce-wallet@0.0.0-next-20250313155042: - resolution: {integrity: sha512-a5wk1LgHk4dr0B9+Jc2aIy4SglWv7y95Y/jtArWFLA75K3J0Y00NgJ69xsp5ZOQfwBV/iDfQTfOlIqSD9u+HLA==} + applesauce-wallet@0.0.0-next-20250313225050: + resolution: {integrity: sha512-wpaJOYJTXBuYkDA20m86+rhBaKStUDWxqoMsSX+Cd0oPg1bS7wfpKcn/U353kqjvCU4osInzGwhCupg362khAg==} arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -3059,8 +3059,8 @@ packages: engines: {node: '>=0.10.0'} hasBin: true - electron-to-chromium@1.5.116: - resolution: {integrity: sha512-mufxTCJzLBQVvSdZzX1s5YAuXsN1M4tTyYxOOL1TcSKtIzQ9rjIrm7yFK80rN5dwGTePgdoABDSHpuVtRQh0Zw==} + electron-to-chromium@1.5.117: + resolution: {integrity: sha512-G4+CYIJBiQ72N0gi868tmG4WsD8bwLE9XytBdfgXO5zdlTlvOP2ABzWYILYxCIHmsbm2HjBSgm/E/H/QfcnIyQ==} elementtree@0.1.7: resolution: {integrity: sha512-wkgGT6kugeQk/P6VZ/f4T+4HB41BVgNBq5CDIZVbQ02nvTVqAiVTbskxxu3eA/X96lMlfYOwnLQpN2v5E1zDEg==} @@ -3310,8 +3310,8 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} - force-graph@1.49.3: - resolution: {integrity: sha512-blBqeFq3vdIzqGgvWrML9xA2R0nS5nvjHsEt9lcWVZ29IcdWQ6wa4G0CG/Uv8bP9olwpsJPZSJe3W8vNhiMCnQ==} + force-graph@1.49.4: + resolution: {integrity: sha512-TMbbXg3n0pjI8cmgNlv1IKEGewnd9LdwKVJ4cj4XzZXqP/Q5aSjsyuxzIITtkfDJ+KDsiLql1FHu19Lqrq41uQ==} engines: {node: '>=12'} formidable@3.5.2: @@ -8480,10 +8480,10 @@ snapshots: dependencies: entities: 2.2.0 - applesauce-accounts@0.0.0-next-20250313155042(typescript@5.8.2): + applesauce-accounts@0.0.0-next-20250313225050(typescript@5.8.2): dependencies: '@noble/hashes': 1.7.1 - applesauce-signers: 0.0.0-next-20250313155042(typescript@5.8.2) + applesauce-signers: 0.0.0-next-20250313225050(typescript@5.8.2) nanoid: 5.1.3 nostr-tools: 2.10.4(typescript@5.8.2) rxjs: 7.8.2 @@ -8491,22 +8491,22 @@ snapshots: - supports-color - typescript - applesauce-actions@0.0.0-next-20250313155042(typescript@5.8.2): + applesauce-actions@0.0.0-next-20250313225050(typescript@5.8.2): dependencies: - applesauce-core: 0.0.0-next-20250313155042(typescript@5.8.2) - applesauce-factory: 0.0.0-next-20250313155042(typescript@5.8.2) + applesauce-core: 0.0.0-next-20250313225050(typescript@5.8.2) + applesauce-factory: 0.0.0-next-20250313225050(typescript@5.8.2) nostr-tools: 2.10.4(typescript@5.8.2) transitivePeerDependencies: - supports-color - typescript - applesauce-content@0.0.0-next-20250313155042(typescript@5.8.2): + applesauce-content@0.0.0-next-20250313225050(typescript@5.8.2): dependencies: '@cashu/cashu-ts': 2.0.0-rc1 '@types/hast': 3.0.4 '@types/mdast': 4.0.4 '@types/unist': 3.0.3 - applesauce-core: 0.0.0-next-20250313155042(typescript@5.8.2) + applesauce-core: 0.0.0-next-20250313225050(typescript@5.8.2) mdast-util-find-and-replace: 3.0.2 nostr-tools: 2.10.4(typescript@5.8.2) remark: 15.0.1 @@ -8517,7 +8517,7 @@ snapshots: - supports-color - typescript - applesauce-core@0.0.0-next-20250313155042(typescript@5.8.2): + applesauce-core@0.0.0-next-20250313225050(typescript@5.8.2): dependencies: '@noble/hashes': 1.7.1 '@scure/base': 1.2.4 @@ -8532,19 +8532,19 @@ snapshots: - supports-color - typescript - applesauce-factory@0.0.0-next-20250313155042(typescript@5.8.2): + applesauce-factory@0.0.0-next-20250313225050(typescript@5.8.2): dependencies: - applesauce-content: 0.0.0-next-20250313155042(typescript@5.8.2) - applesauce-core: 0.0.0-next-20250313155042(typescript@5.8.2) + applesauce-content: 0.0.0-next-20250313225050(typescript@5.8.2) + applesauce-core: 0.0.0-next-20250313225050(typescript@5.8.2) nanoid: 5.1.3 nostr-tools: 2.10.4(typescript@5.8.2) transitivePeerDependencies: - supports-color - typescript - applesauce-loaders@0.0.0-next-20250313155042(typescript@5.8.2): + applesauce-loaders@0.0.0-next-20250313225050(typescript@5.8.2): dependencies: - applesauce-core: 0.0.0-next-20250313155042(typescript@5.8.2) + applesauce-core: 0.0.0-next-20250313225050(typescript@5.8.2) nanoid: 5.1.3 nostr-tools: 2.10.4(typescript@5.8.2) rx-nostr: 3.5.0 @@ -8553,13 +8553,13 @@ snapshots: - supports-color - typescript - applesauce-react@0.0.0-next-20250313155042(react-dom@19.0.0(react@19.0.0))(typescript@5.8.2): + applesauce-react@0.0.0-next-20250313225050(react-dom@19.0.0(react@19.0.0))(typescript@5.8.2): dependencies: - applesauce-accounts: 0.0.0-next-20250313155042(typescript@5.8.2) - applesauce-actions: 0.0.0-next-20250313155042(typescript@5.8.2) - applesauce-content: 0.0.0-next-20250313155042(typescript@5.8.2) - applesauce-core: 0.0.0-next-20250313155042(typescript@5.8.2) - applesauce-factory: 0.0.0-next-20250313155042(typescript@5.8.2) + applesauce-accounts: 0.0.0-next-20250313225050(typescript@5.8.2) + applesauce-actions: 0.0.0-next-20250313225050(typescript@5.8.2) + applesauce-content: 0.0.0-next-20250313225050(typescript@5.8.2) + applesauce-core: 0.0.0-next-20250313225050(typescript@5.8.2) + applesauce-factory: 0.0.0-next-20250313225050(typescript@5.8.2) nostr-tools: 2.10.4(typescript@5.8.2) observable-hooks: 4.2.4(react-dom@19.0.0(react@19.0.0))(react@18.3.1)(rxjs@7.8.2) react: 18.3.1 @@ -8569,9 +8569,9 @@ snapshots: - supports-color - typescript - applesauce-relay@0.0.0-next-20250313155042(typescript@5.8.2): + applesauce-relay@0.0.0-next-20250313225050(typescript@5.8.2): dependencies: - applesauce-core: 0.0.0-next-20250313155042(typescript@5.8.2) + applesauce-core: 0.0.0-next-20250313225050(typescript@5.8.2) nanoid: 5.1.3 nostr-tools: 2.10.4(typescript@5.8.2) rxjs: 7.8.2 @@ -8579,12 +8579,12 @@ snapshots: - supports-color - typescript - applesauce-signers@0.0.0-next-20250313155042(typescript@5.8.2): + applesauce-signers@0.0.0-next-20250313225050(typescript@5.8.2): dependencies: '@noble/hashes': 1.7.1 '@noble/secp256k1': 1.7.1 '@scure/base': 1.2.4 - applesauce-core: 0.0.0-next-20250313155042(typescript@5.8.2) + applesauce-core: 0.0.0-next-20250313225050(typescript@5.8.2) debug: 4.4.0 nanoid: 5.1.3 nostr-tools: 2.10.4(typescript@5.8.2) @@ -8592,14 +8592,14 @@ snapshots: - supports-color - typescript - applesauce-wallet@0.0.0-next-20250313155042(typescript@5.8.2): + applesauce-wallet@0.0.0-next-20250313225050(typescript@5.8.2): dependencies: '@cashu/cashu-ts': 2.0.0-rc1 '@gandlaf21/bc-ur': 1.1.12 '@noble/hashes': 1.7.1 - applesauce-actions: 0.0.0-next-20250313155042(typescript@5.8.2) - applesauce-core: 0.0.0-next-20250313155042(typescript@5.8.2) - applesauce-factory: 0.0.0-next-20250313155042(typescript@5.8.2) + applesauce-actions: 0.0.0-next-20250313225050(typescript@5.8.2) + applesauce-core: 0.0.0-next-20250313225050(typescript@5.8.2) + applesauce-factory: 0.0.0-next-20250313225050(typescript@5.8.2) nostr-tools: 2.10.4(typescript@5.8.2) rxjs: 7.8.2 transitivePeerDependencies: @@ -8825,7 +8825,7 @@ snapshots: browserslist@4.24.4: dependencies: caniuse-lite: 1.0.30001704 - electron-to-chromium: 1.5.116 + electron-to-chromium: 1.5.117 node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.24.4) @@ -9526,7 +9526,7 @@ snapshots: dependencies: jake: 10.9.2 - electron-to-chromium@1.5.116: {} + electron-to-chromium@1.5.117: {} elementtree@0.1.7: dependencies: @@ -9844,7 +9844,7 @@ snapshots: dependencies: is-callable: 1.2.7 - force-graph@1.49.3: + force-graph@1.49.4: dependencies: '@tweenjs/tween.js': 25.0.0 accessor-fn: 1.5.1 @@ -11615,7 +11615,7 @@ snapshots: react-force-graph-2d@1.27.0(react@19.0.0): dependencies: - force-graph: 1.49.3 + force-graph: 1.49.4 prop-types: 15.8.1 react: 19.0.0 react-kapsule: 2.5.6(react@19.0.0) diff --git a/src/components/qr-code/animated-qr-scanner-button.tsx b/src/components/qr-code/animated-qr-scanner-button.tsx new file mode 100644 index 000000000..cd94f3958 --- /dev/null +++ b/src/components/qr-code/animated-qr-scanner-button.tsx @@ -0,0 +1,112 @@ +import { Suspense, lazy, useCallback, useEffect, useRef, useState } from "react"; +import { filter, merge, Subject } from "rxjs"; +import { + Button, + IconButton, + IconButtonProps, + Modal, + ModalBody, + ModalContent, + ModalFooter, + ModalOverlay, + Progress, + useDisclosure, + useToast, +} from "@chakra-ui/react"; +import { receiveAnimated } from "applesauce-wallet/helpers/animated-qr"; + +import { logger } from "../../helpers/debug"; +import { QrCodeIcon } from "../icons"; + +const BarcodeScannerComponent = lazy(() => import("react-qr-barcode-scanner")); +const log = logger.extend("QRCodeScanner"); + +export default function AnimatedQRCodeScannerButton({ + onResult, + ...props +}: { onResult: (data: string) => void } & Omit) { + const toast = useToast(); + const modal = useDisclosure(); + + const [progress, setProgress] = useState(); + const [subject, setSubject] = useState>(); + + const openModal = useCallback(() => { + setSubject(new Subject()); + modal.onOpen(); + }, [modal.onOpen, setSubject]); + + const [stopStream, setStopStream] = useState(false); + const closeModal = useCallback(() => { + // Stop the QR Reader stream (fixes issue where the browser freezes when closing the modal) and then dismiss the modal one tick later + setStopStream(true); + setTimeout(() => modal.onClose(), 0); + }, [setStopStream, modal.onClose]); + + const result = useRef(onResult); + result.current = onResult; + + // listen to the scanning stream + useEffect(() => { + if (subject) { + setProgress(undefined); + + const normal = subject.pipe(filter((part) => !part.startsWith("ur:bytes"))); + const animated = subject.pipe(receiveAnimated); + + const sub = merge(normal, animated).subscribe({ + next: (part) => { + if (typeof part === "number") { + // progress + setProgress(part); + } else if (part) { + // close the javascript scanner + closeModal(); + // wait for steam to be stopped before returning data + setTimeout(() => { + result.current(part); + }, 0); + } + }, + error: (err) => { + if (err instanceof Error) toast({ status: "error", description: err.message }); + closeModal(); + }, + }); + return () => sub.unsubscribe(); + } + }, [subject, closeModal, setProgress]); + + return ( + <> + } aria-label="Qr Scanner" {...props} /> + {modal.isOpen && ( + + + + + + { + if (subject && result && result.getText()) subject.next(result.getText()); + }} + onError={(err) => { + if (!subject) return; + if (err instanceof Error) subject.error(err); + else subject.error(new Error(err)); + }} + /> + + + + {progress !== undefined && } + + + + + + )} + + ); +} diff --git a/src/components/qr-code/native-scanner.ts b/src/components/qr-code/native-scanner.ts index 33733804c..a94b92518 100644 --- a/src/components/qr-code/native-scanner.ts +++ b/src/components/qr-code/native-scanner.ts @@ -1,12 +1,11 @@ -import { Barcode, BarcodeScannerPlugin } from "@capacitor-mlkit/barcode-scanning"; -import { from, Observable, switchMap } from "rxjs"; -import { PluginListenerHandle } from "@capacitor/core"; +import { ScanResult, type Barcode } from "@capacitor-mlkit/barcode-scanning"; +import { Observable, Subject } from "rxjs"; import { logger } from "../../helpers/debug"; const log = logger.extend("NativeQrCodeScanner"); -export async function getNativeScanner(): Promise { +export async function installNativeScanner(): Promise { const { BarcodeScanner, GoogleBarcodeScannerModuleInstallState } = await import("@capacitor-mlkit/barcode-scanning"); const { available } = await BarcodeScanner.isGoogleBarcodeScannerModuleAvailable(); @@ -52,27 +51,38 @@ export async function getNativeScanner(): Promise { const granted = camera === "granted" || camera === "limited"; if (!granted) throw new Error("Camera access denied"); - return BarcodeScanner; + return true; } -export function getNativeScanStream(scanner: BarcodeScannerPlugin): Observable { - return new Observable((observer) => { - const sub = scanner.addListener("barcodesScanned", (event) => { - for (const barcode of event.barcodes) { - observer.next(barcode); - } - }); +export async function getScanningStream(): Promise> { + const { BarcodeScanner } = await import("@capacitor-mlkit/barcode-scanning"); - scanner.startScan(); - - let handle: PluginListenerHandle | undefined = undefined; - sub.then((e) => (handle = e)); - - return () => { - if (handle) handle.remove(); - else sub.then((handle) => handle.remove); - - scanner.stopScan(); - }; + const subject = new Subject(); + await BarcodeScanner.addListener("barcodesScanned", (event) => { + for (const barcode of event.barcodes) { + subject.next(barcode); + } }); + + await BarcodeScanner.addListener("scanError", (event) => { + subject.error(new Error(event.message)); + }); + + return subject; +} + +export async function startScanning(): Promise { + const { BarcodeScanner } = await import("@capacitor-mlkit/barcode-scanning"); + await BarcodeScanner.startScan(); +} + +export async function stopScanning(): Promise { + const { BarcodeScanner } = await import("@capacitor-mlkit/barcode-scanning"); + await BarcodeScanner.removeAllListeners(); + await BarcodeScanner.stopScan(); +} + +export async function scanSingle(): Promise { + const { BarcodeScanner } = await import("@capacitor-mlkit/barcode-scanning"); + return await BarcodeScanner.scan(); } diff --git a/src/components/qr-code/qr-code-scanner-button.tsx b/src/components/qr-code/qr-code-scanner-button.tsx index fc8be9c22..0e2430857 100644 --- a/src/components/qr-code/qr-code-scanner-button.tsx +++ b/src/components/qr-code/qr-code-scanner-button.tsx @@ -1,5 +1,4 @@ -import { Suspense, lazy, useCallback, useEffect, useState } from "react"; -import { filter, map, merge, Observable, Subject } from "rxjs"; +import { Suspense, lazy, useState } from "react"; import { Button, IconButton, @@ -9,16 +8,14 @@ import { ModalContent, ModalFooter, ModalOverlay, - Progress, useDisclosure, useToast, } from "@chakra-ui/react"; -import { receiveAnimated } from "applesauce-wallet/helpers/animated-qr"; import { CAP_IS_NATIVE } from "../../env"; import { logger } from "../../helpers/debug"; import { QrCodeIcon } from "../icons"; -import { getNativeScanner, getNativeScanStream } from "./native-scanner"; +import { installNativeScanner, scanSingle } from "./native-scanner"; const BarcodeScannerComponent = lazy(() => import("react-qr-barcode-scanner")); const log = logger.extend("QRCodeScanner"); @@ -30,70 +27,35 @@ export default function QRCodeScannerButton({ const toast = useToast(); const modal = useDisclosure(); - const [progress, setProgress] = useState(); - const [stream, setStream] = useState | Subject>(); - - const openModal = useCallback(() => { - setStream(new Subject()); - modal.onOpen(); - }, [modal.onOpen, setStream]); - const [stopStream, setStopStream] = useState(false); - const closeModal = useCallback(() => { + const closeModal = (result?: string) => { // Stop the QR Reader stream (fixes issue where the browser freezes when closing the modal) and then dismiss the modal one tick later setStopStream(true); - setTimeout(() => modal.onClose(), 0); - }, [setStopStream, modal.onClose]); + setTimeout(() => { + modal.onClose(); + if (result) onResult(result); + }, 0); + }; - const openNative = useCallback(async () => { - const scanner = await getNativeScanner(); - const stream = getNativeScanStream(scanner); - setStream(stream.pipe(map((barcode) => barcode.rawValue))); - }, [setStream]); - - const handleClick = useCallback(async () => { + const handleClick = async () => { if (CAP_IS_NATIVE) { try { - await openNative(); + await installNativeScanner(); + + try { + const result = await scanSingle(); + onResult(result.barcodes[0].rawValue); + } catch (error) { + // user cancel + } } catch (error) { log(error); if (import.meta.env.DEV && error instanceof Error) toast({ status: "error", description: error.message }); - openModal(); + modal.onOpen(); } - } else openModal(); - }, [openModal, openNative]); - - // listen to the scanning stream - useEffect(() => { - if (stream) { - setProgress(undefined); - - const normal = stream.pipe(filter((part) => !part.startsWith("ur:bytes"))); - const animated = stream.pipe(receiveAnimated); - - const sub = merge(normal, animated).subscribe({ - next: (part) => { - if (typeof part === "number") { - // progress - setProgress(part); - } else if (part) { - // close the javascript scanner - closeModal(); - // wait for steam to be stopped before returning data - setTimeout(() => { - onResult(part); - }, 0); - } - }, - error: (err) => { - if (err instanceof Error) toast({ status: "error", description: err.message }); - closeModal(); - }, - }); - return () => sub.unsubscribe(); - } - }, [stream, closeModal, onResult, setProgress]); + } else modal.onOpen(); + }; return ( <> @@ -106,20 +68,14 @@ export default function QRCodeScannerButton({ { - if (stream instanceof Subject && result && result.getText()) stream.next(result.getText()); - }} - onError={(err) => { - if (!(stream instanceof Subject)) return; - if (err instanceof Error) stream.error(err); - else stream.error(new Error(err)); + onUpdate={(_err, result) => { + if (result && result.getText()) closeModal(result.getText()); }} /> - {progress !== undefined && } - + diff --git a/src/views/wallet/components/balance-card.tsx b/src/views/wallet/components/balance-card.tsx index 9ce17e82e..7a9eeb72d 100644 --- a/src/views/wallet/components/balance-card.tsx +++ b/src/views/wallet/components/balance-card.tsx @@ -8,8 +8,8 @@ import { WALLET_KIND } from "applesauce-wallet/helpers"; import { ECashIcon } from "../../../components/icons"; import useReplaceableEvent from "../../../hooks/use-replaceable-event"; import useEventUpdate from "../../../hooks/use-event-update"; -import QRCodeScannerButton from "../../../components/qr-code/qr-code-scanner-button"; import RouterLink from "../../../components/router-link"; +import AnimatedQRCodeScannerButton from "../../../components/qr-code/animated-qr-scanner-button"; export default function WalletBalanceCard({ pubkey, ...props }: { pubkey: string } & Omit) { const navigate = useNavigate(); @@ -39,7 +39,7 @@ export default function WalletBalanceCard({ pubkey, ...props }: { pubkey: string - +