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,
},
});