import { CameraView, useCameraPermissions } from 'expo-camera'; import { useRouter } from 'expo-router'; import { useRef, useState } from 'react'; import { KeyboardAvoidingView, Platform, Pressable, Text, TextInput, View, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { Button } from '@/components/ui/Button'; import { ApiError, completePair } from '@/lib/api'; import { colors } from '@/lib/colors'; import { parsePairUrl } from '@/lib/pairing'; import { useSession } from '@/lib/session'; export default function PairScreen() { const router = useRouter(); const setSession = useSession((s) => s.setSession); const [permission, requestPermission] = useCameraPermissions(); const [deviceName, setDeviceName] = useState('iPhone'); const [manualUrl, setManualUrl] = useState(''); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); // Guard against the camera firing onBarcodeScanned multiple times for the // same code while the request is in flight. const scannedRef = useRef(false); async function handlePair(host: string, token: string) { if (busy) return; setBusy(true); setError(null); try { const res = await completePair(host, token, deviceName.trim() || 'Mobile'); await setSession({ host, bearer: res.bearer, deviceId: res.device_id, deviceName: res.device_name, }); router.replace('/'); } catch (e) { const msg = e instanceof ApiError ? e.message : e instanceof Error ? `${e.message} — is the desktop reachable at this address?` : 'Pair failed'; setError(msg); scannedRef.current = false; setBusy(false); } } function handleBarcode({ data }: { data: string }) { if (scannedRef.current) return; const payload = parsePairUrl(data); if (!payload) return; scannedRef.current = true; void handlePair(payload.host, payload.token); } function handleManualPair() { const payload = parsePairUrl(manualUrl); if (!payload) { setError("That doesn't look like a Voicebox pairing URL."); return; } void handlePair(payload.host, payload.token); } return ( router.back()} hitSlop={12} accessibilityRole="button" accessibilityLabel="Cancel pairing" > Cancel Pair device On your desktop, open Voicebox → Settings → Mobile → Pair device, then point your camera at the QR code. Device name {permission?.granted ? ( ) : ( Camera access lets you scan the pairing QR.