mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-17 13:21:44 +01:00
fix native qr code scanner
This commit is contained in:
parent
ceddaf1e9d
commit
81b20f9056
130
pnpm-lock.yaml
generated
130
pnpm-lock.yaml
generated
@ -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)
|
||||
|
112
src/components/qr-code/animated-qr-scanner-button.tsx
Normal file
112
src/components/qr-code/animated-qr-scanner-button.tsx
Normal file
@ -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<IconButtonProps, "icon" | "aria-label">) {
|
||||
const toast = useToast();
|
||||
const modal = useDisclosure();
|
||||
|
||||
const [progress, setProgress] = useState<number>();
|
||||
const [subject, setSubject] = useState<Subject<string>>();
|
||||
|
||||
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 (
|
||||
<>
|
||||
<IconButton onClick={openModal} icon={<QrCodeIcon boxSize={6} />} aria-label="Qr Scanner" {...props} />
|
||||
{modal.isOpen && (
|
||||
<Suspense fallback={null}>
|
||||
<Modal isOpen={modal.isOpen} onClose={closeModal}>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalBody p="2">
|
||||
<BarcodeScannerComponent
|
||||
stopStream={stopStream}
|
||||
onUpdate={(err, result) => {
|
||||
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));
|
||||
}}
|
||||
/>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter px="2" pb="2" pt="0" alignItems="center" gap="2">
|
||||
{progress !== undefined && <Progress hasStripe value={progress * 100} w="full" />}
|
||||
<Button onClick={closeModal}>Cancel</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</Suspense>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
@ -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<BarcodeScannerPlugin> {
|
||||
export async function installNativeScanner(): Promise<boolean> {
|
||||
const { BarcodeScanner, GoogleBarcodeScannerModuleInstallState } = await import("@capacitor-mlkit/barcode-scanning");
|
||||
|
||||
const { available } = await BarcodeScanner.isGoogleBarcodeScannerModuleAvailable();
|
||||
@ -52,27 +51,38 @@ export async function getNativeScanner(): Promise<BarcodeScannerPlugin> {
|
||||
const granted = camera === "granted" || camera === "limited";
|
||||
if (!granted) throw new Error("Camera access denied");
|
||||
|
||||
return BarcodeScanner;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function getNativeScanStream(scanner: BarcodeScannerPlugin): Observable<Barcode> {
|
||||
return new Observable<Barcode>((observer) => {
|
||||
const sub = scanner.addListener("barcodesScanned", (event) => {
|
||||
for (const barcode of event.barcodes) {
|
||||
observer.next(barcode);
|
||||
}
|
||||
});
|
||||
export async function getScanningStream(): Promise<Observable<Barcode>> {
|
||||
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<Barcode>();
|
||||
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<void> {
|
||||
const { BarcodeScanner } = await import("@capacitor-mlkit/barcode-scanning");
|
||||
await BarcodeScanner.startScan();
|
||||
}
|
||||
|
||||
export async function stopScanning(): Promise<void> {
|
||||
const { BarcodeScanner } = await import("@capacitor-mlkit/barcode-scanning");
|
||||
await BarcodeScanner.removeAllListeners();
|
||||
await BarcodeScanner.stopScan();
|
||||
}
|
||||
|
||||
export async function scanSingle(): Promise<ScanResult> {
|
||||
const { BarcodeScanner } = await import("@capacitor-mlkit/barcode-scanning");
|
||||
return await BarcodeScanner.scan();
|
||||
}
|
||||
|
@ -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<number>();
|
||||
const [stream, setStream] = useState<Observable<string> | Subject<string>>();
|
||||
|
||||
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({
|
||||
<ModalBody p="2">
|
||||
<BarcodeScannerComponent
|
||||
stopStream={stopStream}
|
||||
onUpdate={(err, result) => {
|
||||
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());
|
||||
}}
|
||||
/>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter px="2" pb="2" pt="0" alignItems="center" gap="2">
|
||||
{progress !== undefined && <Progress hasStripe value={progress * 100} w="full" />}
|
||||
<Button onClick={closeModal}>Cancel</Button>
|
||||
<Button onClick={() => closeModal()}>Cancel</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
@ -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<CardProps, "children">) {
|
||||
const navigate = useNavigate();
|
||||
@ -39,7 +39,7 @@ export default function WalletBalanceCard({ pubkey, ...props }: { pubkey: string
|
||||
<Button as={RouterLink} w="full" size="lg" to="/wallet/send">
|
||||
Send
|
||||
</Button>
|
||||
<QRCodeScannerButton onResult={handleScan} size="lg" />
|
||||
<AnimatedQRCodeScannerButton onResult={handleScan} size="lg" />
|
||||
<Button as={RouterLink} w="full" size="lg" to="/wallet/receive">
|
||||
Receive
|
||||
</Button>
|
||||
|
Loading…
x
Reference in New Issue
Block a user