feat(wallet): QR scan modal — camera + animated QR, wired into Home/Send/Receive
New WalletScanModal scans QR codes with the device camera (BarcodeDetector with jsQR fallback) including animated/multi-frame QRs via qrloop, then routes what it saw: BOLT11 invoices -> lightning pay (amount locked from the invoice when present), bitcoin:/BIP21 URIs -> on-chain send, Cashu tokens -> redeem, Fedimint invites -> join. Entry points: camera button on the wallet card (far right), and a Scan button centered between the Close/Send and Close/Receive buttons of both modals. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
be5122048b
commit
8968d41ca9
@@ -0,0 +1,60 @@
|
||||
import { ref } from 'vue'
|
||||
import {
|
||||
parseFramesReducer,
|
||||
areFramesComplete,
|
||||
framesToData,
|
||||
totalNumberOfFrames,
|
||||
currentNumberOfFrames,
|
||||
} from 'qrloop'
|
||||
|
||||
/**
|
||||
* Collects animated-QR frames (qrloop format, as used by k484 for large
|
||||
* Fedimint tokens) and reassembles them into the original token string.
|
||||
*/
|
||||
export function useAnimatedQRDecoder() {
|
||||
const framesState = ref<ReturnType<typeof parseFramesReducer> | null>(null)
|
||||
const isComplete = ref(false)
|
||||
const decodedData = ref<string | null>(null)
|
||||
const uniqueFrames = ref<Set<string>>(new Set())
|
||||
|
||||
/** Feed one scanned frame; returns true once the full payload is decoded. */
|
||||
function addFrame(frame: string): boolean {
|
||||
if (isComplete.value) return true
|
||||
if (uniqueFrames.value.has(frame)) return false
|
||||
uniqueFrames.value.add(frame)
|
||||
|
||||
try {
|
||||
framesState.value = parseFramesReducer(framesState.value, frame)
|
||||
if (areFramesComplete(framesState.value)) {
|
||||
const dataBuffer = framesToData(framesState.value)
|
||||
// Tokens travel as URL-safe base64
|
||||
decodedData.value = dataBuffer
|
||||
.toString('base64')
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_')
|
||||
isComplete.value = true
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} catch {
|
||||
// A frame that qrloop rejects may just be a different QR format
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function progressText(): string {
|
||||
if (!framesState.value) return ''
|
||||
const total = totalNumberOfFrames(framesState.value)
|
||||
const current = currentNumberOfFrames(framesState.value)
|
||||
return `${current}/${total} frames`
|
||||
}
|
||||
|
||||
function reset() {
|
||||
framesState.value = null
|
||||
uniqueFrames.value.clear()
|
||||
isComplete.value = false
|
||||
decodedData.value = null
|
||||
}
|
||||
|
||||
return { isComplete, decodedData, addFrame, reset, progressText }
|
||||
}
|
||||
Reference in New Issue
Block a user