import React, { useCallback, useEffect, useRef, useState } from 'react'; import { AccessibilityInfo, Animated, Easing, Keyboard, Pressable, StyleSheet, View, } from 'react-native'; import { useNavigation, useRoute, type RouteProp } from '@react-navigation/native'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { Camera, useCameraDevice, useCameraPermission, } from 'react-native-vision-camera'; import { useBarcodeScannerOutput } from 'react-native-vision-camera-barcode-scanner'; import { ScanLine, X } from 'lucide-react-native'; import { AppText, Button, ErrorBanner, Field, Header, TextInputBox } from '../components/ui'; import { useAlarm } from '../state/AlarmContext'; import { joinErrorMessage } from '../services/alarmApi'; import { buildDeviceLabel } from '../lib/deviceLabel'; import { canDeliverRealAlarm } from '../lib/takeoverReadiness'; import { useAlarmPermissions } from '../state/useAlarmPermissions'; import { parseQrPayload, parseManualToken, qrErrorMessage } from '../lib/qrPayload'; import { openAppSettings } from '../services/alarmNative'; import type { RootStackParamList } from '../navigation'; import { bg, border, focusRing, minTapTarget, motionSiren, radiusFull, scrim, stateIdle, space2, space3, space4, surface, text, textMuted, textSecondary, typeBodyStrong, typeCaption, typeHeading, typeLabel, warn, } from '../theme/tokens'; /** §7.5: the scan window is 280×280 with 3px corner brackets. */ const VIEWFINDER = 280; type Nav = NativeStackNavigationProp; type Rt = RouteProp; /** * QR scanner. * * The camera is `react-native-vision-camera` 5.x, which ships the object * scanner in core (`useObjectOutput({types:['qr']})`) — there is no separate * code-scanner plugin in v5. The manual paste field below is always available, * including when the camera is denied or unavailable, because a subscribe token * is a plain string. */ export default function ScannerScreen() { const navigation = useNavigation