feat(wallet): hand live scanning to the companion's native camera when present
The scan/upload chooser (already on main) keeps the first move; when the Android companion's window.ArchipelagoQr bridge exists, 'Scan with camera' opens the native CameraX modal instead of getUserMedia — which the companion's plain-http origin doesn't even have, so the button now shows there too. Decodes come back via window.__archyQrResult, status lines mirror out via setStatus, and stopScanning closes the native modal once a code is accepted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
87c324e36c
commit
fbed32f95d
@ -50,7 +50,9 @@
|
|||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M3 8V6a2 2 0 012-2h2M3 16v2a2 2 0 002 2h2m10-16h2a2 2 0 012 2v2m-4 12h2a2 2 0 002-2v-2M7 12h10" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M3 8V6a2 2 0 012-2h2M3 16v2a2 2 0 002 2h2m10-16h2a2 2 0 012 2v2m-4 12h2a2 2 0 002-2v-2M7 12h10" />
|
||||||
</svg>
|
</svg>
|
||||||
<p class="text-sm text-white/60 text-center">How do you want to read the QR?</p>
|
<p class="text-sm text-white/60 text-center">How do you want to read the QR?</p>
|
||||||
<button v-if="!liveCameraUnavailable" @click="chooseCamera" class="glass-button w-full px-4 py-2.5 rounded-lg text-sm font-medium">
|
<!-- hasNativeQr: on the companion (plain http, no getUserMedia)
|
||||||
|
the native bridge still provides a live camera -->
|
||||||
|
<button v-if="!liveCameraUnavailable || hasNativeQr" @click="chooseCamera" class="glass-button w-full px-4 py-2.5 rounded-lg text-sm font-medium">
|
||||||
Scan with camera
|
Scan with camera
|
||||||
</button>
|
</button>
|
||||||
<button @click="photoInput?.click()" class="glass-button w-full px-4 py-2.5 rounded-lg text-sm font-medium">
|
<button @click="photoInput?.click()" class="glass-button w-full px-4 py-2.5 rounded-lg text-sm font-medium">
|
||||||
@ -271,6 +273,23 @@ type Rail = 'onchain' | 'lightning' | 'cashu' | 'fedimint'
|
|||||||
type Action = 'pay-invoice' | 'send-onchain' | 'redeem-token' | 'fedimint-join'
|
type Action = 'pay-invoice' | 'send-onchain' | 'redeem-token' | 'fedimint-join'
|
||||||
type Pane = 'scan' | 'amount' | 'success'
|
type Pane = 'scan' | 'amount' | 'success'
|
||||||
|
|
||||||
|
// JS bridge the Android companion injects: when present, live scanning is
|
||||||
|
// delegated to a native camera modal (styled like this one) — the WebView's
|
||||||
|
// getUserMedia preview lags, and over plain http it doesn't exist at all.
|
||||||
|
// Decodes come back through the window.__archyQr* callbacks; status lines
|
||||||
|
// (animated-QR progress, errors) mirror out to the native modal's strip.
|
||||||
|
interface ArchipelagoQrBridge {
|
||||||
|
open(): void
|
||||||
|
setStatus(message: string, isError: boolean): void
|
||||||
|
close(): void
|
||||||
|
}
|
||||||
|
interface NativeWindow extends Window {
|
||||||
|
ArchipelagoQr?: ArchipelagoQrBridge
|
||||||
|
__archyQrResult?: (text: string) => void
|
||||||
|
__archyQrCancelled?: () => void
|
||||||
|
}
|
||||||
|
const nativeWin = window as NativeWindow
|
||||||
|
|
||||||
const PRESETS = [21, 2100, 21000, 100000]
|
const PRESETS = [21, 2100, 21000, 100000]
|
||||||
|
|
||||||
const props = defineProps<{ show: boolean }>()
|
const props = defineProps<{ show: boolean }>()
|
||||||
@ -322,9 +341,24 @@ const autoStarting = ref(false)
|
|||||||
const scanChoice = ref<'unset' | 'camera'>('unset')
|
const scanChoice = ref<'unset' | 'camera'>('unset')
|
||||||
|
|
||||||
function chooseCamera() {
|
function chooseCamera() {
|
||||||
|
if (startNativeScan()) return
|
||||||
scanChoice.value = 'camera'
|
scanChoice.value = 'camera'
|
||||||
void nextTick(() => startScanning())
|
void nextTick(() => startScanning())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Native scanner (companion app) ---
|
||||||
|
const hasNativeQr = !!nativeWin.ArchipelagoQr
|
||||||
|
const nativeScanActive = ref(false)
|
||||||
|
|
||||||
|
function startNativeScan(): boolean {
|
||||||
|
const bridge = nativeWin.ArchipelagoQr
|
||||||
|
if (!bridge) return false
|
||||||
|
nativeWin.__archyQrResult = (text: string) => handleScanned(text)
|
||||||
|
nativeWin.__archyQrCancelled = () => { nativeScanActive.value = false }
|
||||||
|
nativeScanActive.value = true
|
||||||
|
bridge.open()
|
||||||
|
return true
|
||||||
|
}
|
||||||
const scanStatus = ref('')
|
const scanStatus = ref('')
|
||||||
const scanStatusIsError = ref(false)
|
const scanStatusIsError = ref(false)
|
||||||
const cameraError = ref(false)
|
const cameraError = ref(false)
|
||||||
@ -374,8 +408,20 @@ function stopScanning() {
|
|||||||
qrScanner.value?.destroy()
|
qrScanner.value?.destroy()
|
||||||
qrScanner.value = null
|
qrScanner.value = null
|
||||||
isScanning.value = false
|
isScanning.value = false
|
||||||
|
if (nativeScanActive.value) {
|
||||||
|
nativeScanActive.value = false
|
||||||
|
nativeWin.ArchipelagoQr?.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mirror status lines onto the native modal while it's up — it covers the
|
||||||
|
// page, so this strip is the only feedback the user can see.
|
||||||
|
watch([scanStatus, scanStatusIsError], () => {
|
||||||
|
if (nativeScanActive.value) {
|
||||||
|
nativeWin.ArchipelagoQr?.setStatus(scanStatus.value, scanStatusIsError.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function submitPaste() {
|
function submitPaste() {
|
||||||
const text = pasteInput.value.trim()
|
const text = pasteInput.value.trim()
|
||||||
if (!text) return
|
if (!text) return
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user