251 lines
7.2 KiB
TypeScript
251 lines
7.2 KiB
TypeScript
import React, { useState, useEffect, useContext } from 'react';
|
|
import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert, Image } from 'react-native';
|
|
import { Camera, CameraView, scanFromURLAsync } from 'expo-camera';
|
|
import { QRCodeContext } from '../types';
|
|
import axios from 'axios'; // For URL calls
|
|
import { Ionicons } from '@expo/vector-icons'; // For icons
|
|
import { useNavigation } from '@react-navigation/native';
|
|
import * as ImagePicker from 'expo-image-picker';
|
|
import ScannedDataBox from '../components/ScannedDataBox';
|
|
|
|
const QRScannerScreen: React.FC = () => {
|
|
const navigation = useNavigation(); // call Navigation bar
|
|
const [showSplash, setShowSplash] = useState<boolean>(true); // call splash screen
|
|
|
|
const qrCodeContext = useContext(QRCodeContext); // From ./types.ts
|
|
const { qrCodes, setQrCodes } = qrCodeContext || { qrCodes: [], setQrCodes: () => {} };
|
|
|
|
const [hasPermission, setHasPermission] = useState<boolean | null>(null);
|
|
const [scanned, setScanned] = useState<boolean>(false);
|
|
const [scannedData, setScannedData] = useState<string>(''); // State for QR scanned Data
|
|
const [dataType, setDataType] = useState<string>(''); // State for data type
|
|
const [enableTorch, setEnableTorch] = useState<boolean>(false); // State for torch
|
|
|
|
useEffect(() => {
|
|
const initializeApp = async () => {
|
|
const { status } = await Camera.requestCameraPermissionsAsync();
|
|
setHasPermission(status === 'granted');
|
|
setShowSplash(false);
|
|
};
|
|
|
|
initializeApp();
|
|
}, []);
|
|
|
|
const clearScanDataInternal = () => {
|
|
setScannedData('');
|
|
setScanned(false);
|
|
setDataType('');
|
|
};
|
|
|
|
const handlePayload = async (payload: string) => {
|
|
setScanned(true);
|
|
const type = await sendToAPIServer(payload);
|
|
|
|
const qrCode = {
|
|
data: payload,
|
|
type,
|
|
scanResult: {
|
|
secureConnection: true, // Placeholder, replace with actual logic
|
|
virusTotalCheck: true, // Placeholder, replace with actual logic
|
|
redirects: 0 // Placeholder, replace with actual logic
|
|
}
|
|
};
|
|
|
|
setScannedData(payload);
|
|
console.log("handlePayload -> payload", payload);
|
|
console.log("handlePayload -> type", type);
|
|
setDataType(type);
|
|
setQrCodes([...qrCodes, qrCode]);
|
|
};
|
|
|
|
const sendToAPIServer = async (data: string): Promise<string> => {
|
|
console.log('Sending QR code data to backend:', data);
|
|
|
|
try {
|
|
const response = await axios.post('http://192.168.10.247:8080/v1/api/qrcodetypes/detect', {
|
|
data,
|
|
}, {
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
});
|
|
console.log('Response from backend:', response.data);
|
|
return response.data;
|
|
} catch (error) {
|
|
console.error('Error detecting QR code type:', error);
|
|
return 'UNKNOWN';
|
|
}
|
|
};
|
|
|
|
const toggleTorch = () => {
|
|
setEnableTorch((prev) => !prev);
|
|
};
|
|
|
|
const handleTestScan = () => {
|
|
handlePayload('TEST123');
|
|
};
|
|
|
|
const readQRFromImage = async () => {
|
|
clearScanDataInternal();
|
|
console.log("readingQRFromImage");
|
|
const result = await ImagePicker.launchImageLibraryAsync({
|
|
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
allowsEditing: false, // Don't ask user to crop images
|
|
quality: 1,
|
|
});
|
|
|
|
if (result && result.assets && result.assets.length > 0 && result.assets[0].uri) { // Ensure the uri is not empty
|
|
try {
|
|
const scannedResult = await scanFromURLAsync(result.assets[0].uri);
|
|
if (scannedResult && scannedResult[0] && scannedResult[0].data) {
|
|
handlePayload(scannedResult[0].data);
|
|
// Not sure why scannedResult.data is undefined but access as array work, KIV
|
|
console.log('readingQRFromImage -> scannedResult[0].data:', scannedResult[0].data);
|
|
} else {
|
|
setScannedData("No QR Code Found");
|
|
setTimeout(() => setScannedData(""), 4000);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error scanning QR code from image:', error);
|
|
Alert.alert('Failed to scan QR code from image.');
|
|
}
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
const unsubscribe = navigation.addListener('focus', () => {
|
|
clearScanDataInternal();
|
|
});
|
|
return unsubscribe;
|
|
}, [navigation]);
|
|
|
|
if (showSplash) {
|
|
return (
|
|
<View style={styles.splashContainer}>
|
|
<ActivityIndicator size="large" color="#ff69b4" />
|
|
</View>
|
|
);
|
|
}
|
|
|
|
if (hasPermission === null) {
|
|
return <Text>Requesting for camera permission</Text>;
|
|
}
|
|
|
|
if (hasPermission === false) {
|
|
return <Text>No access to camera</Text>;
|
|
}
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<View style={styles.banner}>
|
|
<Text style={styles.headerText}>SafeQR v0.89</Text>
|
|
</View>
|
|
<Text style={styles.welcomeText}>Welcome to SafeQR code Scanner</Text>
|
|
|
|
<View style={styles.cameraContainer}>
|
|
<CameraView
|
|
onBarcodeScanned={scanned ? undefined : ({ data }) => handlePayload(data)}
|
|
barcodeScannerSettings={{ barcodeTypes: ['qr', 'pdf417'] }}
|
|
style={styles.camera}
|
|
enableTorch={enableTorch}
|
|
/>
|
|
|
|
<TouchableOpacity onPress={toggleTorch} style={styles.flashButton}>
|
|
<Ionicons name="flashlight" size={24} color="#fff" />
|
|
</TouchableOpacity>
|
|
<TouchableOpacity onPress={handleTestScan} style={styles.testButton}>
|
|
<Ionicons name="bug" size={24} color="#fff" />
|
|
</TouchableOpacity>
|
|
<TouchableOpacity onPress={readQRFromImage} style={styles.galleryButton}>
|
|
<Ionicons name="image" size={24} color="#fff" />
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{scannedData !== '' && (
|
|
<View style={styles.scannedDataBox}>
|
|
<ScannedDataBox data={scannedData} dataType={dataType} clearScanData={clearScanDataInternal} />
|
|
</View>
|
|
)}
|
|
</View>
|
|
);
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: '#f8f0fc',
|
|
padding: 20,
|
|
},
|
|
banner: {
|
|
alignItems: 'center',
|
|
marginBottom: 20,
|
|
},
|
|
headerText: {
|
|
fontSize: 24,
|
|
fontWeight: 'bold',
|
|
color: '#ff69b4',
|
|
},
|
|
splashContainer: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
backgroundColor: '#f8f0fc',
|
|
},
|
|
cameraContainer: {
|
|
height: '60%',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
borderRadius: 10,
|
|
overflow: 'hidden',
|
|
},
|
|
camera: {
|
|
width: '100%',
|
|
height: '100%',
|
|
},
|
|
flashButton: {
|
|
position: 'absolute',
|
|
bottom: 20,
|
|
left: 100,
|
|
width: 50,
|
|
height: 50,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
backgroundColor: '#000',
|
|
borderRadius: 25,
|
|
},
|
|
testButton: {
|
|
position: 'absolute',
|
|
bottom: 1,
|
|
alignSelf: 'stretch',
|
|
backgroundColor: '#000',
|
|
padding: 10,
|
|
borderRadius: 5,
|
|
},
|
|
galleryButton: {
|
|
position: 'absolute',
|
|
bottom: 20,
|
|
right: 100,
|
|
width: 50,
|
|
height: 50,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
backgroundColor: '#000',
|
|
borderRadius: 25,
|
|
},
|
|
scannedDataBox: {
|
|
position: 'absolute',
|
|
top: '10%',
|
|
left: '5%',
|
|
right: '5%',
|
|
zIndex: 2,
|
|
},
|
|
welcomeText: {
|
|
textAlign: 'center',
|
|
fontSize: 20,
|
|
marginVertical: 10,
|
|
color: 'black',
|
|
},
|
|
});
|
|
|
|
export default QRScannerScreen;
|