import React, { useCallback, useEffect } from 'react'; import { StyleSheet, View, Platform } from 'react-native'; import { Button, Text } from 'react-native-paper'; import type { ScanScreenProps } from '../types/navigation'; import { DTColors } from '../theme'; import { useScan } from '../hooks'; import { getScanInstructions } from '../services/nfc'; import { ScanAnimation } from '../components'; export function ScanScreen({ navigation }: ScanScreenProps) { const { state, tag, transponder, error, nfcStatus, scanProgress, startScan, cancelScan, openSettings, } = useScan(); // Track if we've already navigated to prevent double navigation const hasNavigated = React.useRef(false); // Navigate to results when scan succeeds AND transponder is set // We need transponder to be set (even if null for failed detection) before navigating useEffect(() => { // Only navigate once per scan session if (hasNavigated.current) return; // Wait for success state, tag data, AND transponder to be set // transponder: undefined = not yet set, null = detection failed, Transponder = success if (state === 'success' && tag && transponder !== undefined) { hasNavigated.current = true; console.log('[ScanScreen] Navigating to Result:', { hasTag: !!tag, hasTransponder: !!transponder, transponderType: transponder?.type, transponderChipName: transponder?.chipName, }); navigation.replace('Result', { tagData: { uid: tag.uid, techTypes: tag.techTypes, sak: tag.sak, atqa: tag.atqa, ats: tag.ats, historicalBytes: tag.historicalBytes, }, transponder: transponder ?? undefined, }); } }, [state, tag, transponder, navigation]); // Reset navigation flag when starting a new scan useEffect(() => { if (state === 'scanning') { hasNavigated.current = false; } }, [state]); // Auto-start scan when screen loads useEffect(() => { // Small delay to ensure screen is mounted const timeout = setTimeout(() => { startScan(); }, 300); return () => clearTimeout(timeout); // Only run once on mount // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const handleCancel = useCallback(async () => { await cancelScan(); navigation.goBack(); }, [cancelScan, navigation]); const handleRetry = useCallback(() => { startScan(); }, [startScan]); // Get contextual error hints based on error type const getErrorHint = () => { if (!error?.type) return null; switch (error.type) { case 'TAG_LOST': return 'Hold the tag steady against the back of your phone until scanning completes.'; case 'TIMEOUT': return 'The tag took too long to respond. Try repositioning it closer to the NFC reader.'; case 'SCAN_CANCELLED': return null; // User cancelled, no hint needed case 'NFC_NOT_ENABLED': return Platform.OS === 'ios' ? 'Go to Settings > NFC to enable NFC scanning.' : 'Enable NFC in your device settings.'; case 'PERMISSION_DENIED': return 'NFC permission was denied. Please grant NFC access in your device settings.'; case 'UNKNOWN': return 'An unexpected error occurred. Make sure the tag is positioned correctly and try again.'; default: return 'Make sure the tag is positioned correctly and try again.'; } }; // Render NFC not supported state if (!nfcStatus.isSupported && state !== 'scanning') { return ( NFC NOT SUPPORTED This device does not have NFC hardware. NFC scanning requires a device with NFC capability. Most modern smartphones have NFC, but it may need to be enabled in settings. ); } // Render NFC disabled state if (!nfcStatus.isEnabled && error?.type === 'NFC_NOT_ENABLED') { return ( NFC DISABLED NFC is turned off on this device. {Platform.OS === 'ios' ? 'Go to Settings and enable NFC scanning to continue.' : 'Enable NFC in your device settings to scan tags.'} {Platform.OS === 'android' && ( )} ); } return ( {state === 'scanning' && ( <> SCANNING {getScanInstructions()} Not Scanning? Phones can only read high frequency (13.56 MHz) -- low and ultra high frequency transponders cannot be scanned. {scanProgress && scanProgress.current > 1 && ( {scanProgress.step} )} )} {state === 'error' && ( <> ! {error?.type === 'TAG_LOST' ? 'TAG LOST' : error?.type === 'TIMEOUT' ? 'TIMEOUT' : error?.type === 'PERMISSION_DENIED' ? 'PERMISSION DENIED' : 'SCAN FAILED'} {error?.message || 'Unable to read NFC tag'} {getErrorHint() && ( {getErrorHint()} )} )} {state === 'idle' && ( <> READY TO SCAN )} {state === 'processing' && ( <> IDENTIFYING Analyzing chip type... )} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: DTColors.dark, padding: 24, }, content: { flex: 1, justifyContent: 'center', alignItems: 'center', }, scanningText: { color: DTColors.modeNormal, letterSpacing: 4, marginTop: 32, marginBottom: 16, }, instructionText: { color: DTColors.light, opacity: 0.8, textAlign: 'center', paddingHorizontal: 20, }, progressText: { color: DTColors.modeNormal, opacity: 0.7, marginTop: 16, textAlign: 'center', fontStyle: 'italic', }, errorIcon: { width: 80, height: 80, borderRadius: 40, borderWidth: 3, borderColor: DTColors.modeWarning, justifyContent: 'center', alignItems: 'center', marginBottom: 24, }, errorIconText: { color: DTColors.modeWarning, fontSize: 48, fontWeight: 'bold', }, errorText: { color: DTColors.modeWarning, letterSpacing: 2, marginBottom: 16, }, warningIcon: { width: 80, height: 80, borderRadius: 40, borderWidth: 3, borderColor: DTColors.modeEmphasis, justifyContent: 'center', alignItems: 'center', marginBottom: 24, }, warningIconText: { color: DTColors.modeEmphasis, fontSize: 36, }, warningText: { color: DTColors.modeEmphasis, letterSpacing: 2, marginBottom: 16, }, errorMessage: { color: DTColors.light, opacity: 0.8, textAlign: 'center', marginBottom: 24, paddingHorizontal: 20, }, hintText: { color: DTColors.modeNormal, opacity: 0.7, textAlign: 'center', marginBottom: 24, paddingHorizontal: 20, fontStyle: 'italic', }, retryButton: { borderColor: DTColors.modeNormal, borderWidth: 2, }, settingsButton: { borderColor: DTColors.modeEmphasis, borderWidth: 2, marginTop: 16, }, settingsButtonLabel: { color: DTColors.modeEmphasis, letterSpacing: 2, }, readyText: { color: DTColors.modeEmphasis, letterSpacing: 2, marginBottom: 32, }, startButton: { borderColor: DTColors.modeNormal, borderWidth: 2, }, buttonLabel: { color: DTColors.modeNormal, letterSpacing: 2, }, processingText: { color: DTColors.modeEmphasis, letterSpacing: 4, marginTop: 32, }, detectingHint: { color: DTColors.light, opacity: 0.7, marginTop: 12, }, footer: { alignItems: 'center', paddingBottom: 20, }, cancelLabel: { color: DTColors.light, opacity: 0.6, }, retryLabel: { color: DTColors.modeNormal, marginBottom: 8, }, });