Companion: three-finger menu gesture, native wallet QR scanner, scan/upload chooser #104
@ -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" />
|
||||
</svg>
|
||||
<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
|
||||
</button>
|
||||
<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 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 props = defineProps<{ show: boolean }>()
|
||||
@ -322,9 +341,24 @@ const autoStarting = ref(false)
|
||||
const scanChoice = ref<'unset' | 'camera'>('unset')
|
||||
|
||||
function chooseCamera() {
|
||||
if (startNativeScan()) return
|
||||
scanChoice.value = 'camera'
|
||||
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 scanStatusIsError = ref(false)
|
||||
const cameraError = ref(false)
|
||||
@ -374,8 +408,20 @@ function stopScanning() {
|
||||
qrScanner.value?.destroy()
|
||||
qrScanner.value = null
|
||||
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() {
|
||||
const text = pasteInput.value.trim()
|
||||
if (!text) return
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user