diff --git a/App.tsx b/App.tsx index 38811c7..95521f6 100644 --- a/App.tsx +++ b/App.tsx @@ -1,377 +1,15 @@ -import React, { useState, useEffect, createContext, useContext } from 'react'; -import { Text, View, StyleSheet, ActivityIndicator, TouchableOpacity, FlatList } from 'react-native'; -import { NavigationContainer } from '@react-navigation/native'; -import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; -import { CameraView, Camera } from 'expo-camera'; -import { Ionicons } from '@expo/vector-icons'; // Import Axios for HTTP requests for the VT API call -import axios from 'axios'; -// Create a Context for QR code data -const QRCodeContext = createContext(null); +import React, { useState } from 'react'; +import AppNavigator from './navigation/AppNavigator'; +import { QRCodeContext } from './types'; -const Tab = createBottomTabNavigator(); - -// Component for QR Scanner Screen -const QRScannerScreen: React.FC = () => { - const { qrCodes, setQrCodes } = useContext(QRCodeContext); // Access context - const [hasPermission, setHasPermission] = useState(null); // State for camera permission - const [scanned, setScanned] = useState(false); // State for scanned status - const [showSplash, setShowSplash] = useState(true); // State for splash screen - const [scannedData, setScannedData] = useState(''); // State for scanned data - const [scanResult, setScanResult] = useState(null); // State for VirusTotal scan result - - - useEffect(() => { - const initializeApp = async () => { - const { status } = await Camera.requestCameraPermissionsAsync(); // Request camera permissions - setHasPermission(status === 'granted'); // Set permission status - setShowSplash(false); // Hide splash screen - }; - - initializeApp(); // Initialize app - }, []); - - - - // Function to handle barcode scanned event - const handleQRCodeSanned = async ({ type, data }: { type: string; data: string }) => { - setScanned(true);// Mark as scanned - - // Determine the type of data (URL, text, or just numbers) - let dataType; - if (/^(http|https):\/\//.test(data)) { - dataType = 'URL'; - } else if (/^[0-9]+$/.test(data)) { - dataType = 'Numbers'; - } else { - dataType = 'Text'; - } - - // Construct the scanned data with the data type - let newScannedData = `Type: ${dataType}\nData: ${data}`; // Initialize with type and data - - try { - const scanId = await scanWithVirusTotal(data); // Send data to VirusTotal and get scan ID - const positive = await getScanResult(scanId); // Get scan result and extract positive score - newScannedData += `\nScore: ${positive}`; // Append positive score to newScannedData - } catch (error) { - console.error('Error handling barcode scan:', error); // Handle error - } - - setScannedData(newScannedData); // Save scanned data - setQrCodes([...qrCodes, newScannedData]); // Add scanned data to history - }; - - // Function to send data to VirusTotal and get the scan ID - const scanWithVirusTotal = async (data: any) => { - const apiKey = '3566a17933bb36dd97cb35e84d0446e5ab8ad623e6de968d34b655c79485251er'; // 4/min , 500/day - const url = 'https://www.virustotal.com/vtapi/v2/url/scan'; - const params = { - apikey: apiKey, - url: data - }; - - // The axios to handle URL stuff - try { - const response = await axios.post(url, null, { params }); - return response.data.scan_id; // Return scanID - } catch (error) { - console.error('Error scanning with VirusTotal:', error); - throw error; - } - }; - - // Get the full list of scanned result based on scanID from - // response above, Only want response.data.positive - const getScanResult = async (scanId: Int32Array) => { - const apiKey = '3566a17933bb36dd97cb35e84d0446e5ab8ad623e6de968d34b655c79485251er'; - const url = 'https://www.virustotal.com/vtapi/v2/url/report'; - const params = { - apikey: apiKey, - resource: scanId - }; - - // The axios to handle URL stuff - try { - const response = await axios.get(url, { params }); - return response.data.positives; // onlt - } catch (error) { - console.error('Error getting scan result:', error); - throw error; - } - }; - - if (showSplash) { - return ( - - - - ); - } - - if (hasPermission === null) { - return Requesting for camera permission; - } - - if (hasPermission === false) { - return No access to camera; - } - - return ( - - - SafeQR - - Welcome to SafeQR code Scanner - - - - {scannedData !== '' && ( - - {scannedData} - {scanResult && {JSON.stringify(scanResult)}} - - )} - - ); -}; - -function HistoryScreen() { - const { qrCodes } = useContext(QRCodeContext); - - return ( - - History Screen - ( - - {item} - - )} - keyExtractor={(item, index) => index.toString()} - /> - - ); -} - -function SettingsScreen() { - const { setQrCodes } = useContext(QRCodeContext); - - const clearHistory = () => { - setQrCodes([]); - }; - - return ( - - Settings Screen - - Clear History - - - ); -} - -function ProfileScreen() { - return ( - - Profile Screen - - ); -} - -// Custom Tab Bar Component -const CustomTabBar = ({ state, descriptors, navigation }) => { - return ( - - {state.routes.map((route, index) => { - const { options } = descriptors[route.key]; - const label = options.tabBarLabel !== undefined - ? options.tabBarLabel - : options.title !== undefined - ? options.title - : route.name; - - const isFocused = state.index === index; - - const onPress = () => { - const event = navigation.emit({ - type: 'tabPress', - target: route.key, - }); - - if (!isFocused && !event.defaultPrevented) { - navigation.navigate(route.name); - } - }; - - const onLongPress = () => { - navigation.emit({ - type: 'tabLongPress', - target: route.key, - }); - }; - - const iconName = route.name === 'QR Scanner' ? 'camera' : route.name === 'History' ? 'time' : route.name === 'Settings' ? 'settings' : 'person'; - - return ( - - - - {label} - - - ); - })} - - {navigation.navigate('QR Scanner');}}> - - - - - ); -}; - -export default function App() { - const [qrCodes, setQrCodes] = useState([]); +const App: React.FC = () => { + const [qrCodes, setQrCodes] = useState([]); return ( - - }> - - - - - + ); -} +}; - - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#f8f0fc', - padding: 20, - }, - banner:{}, - headerText:{}, - - splashContainer: { - flex: 1, - justifyContent: "center", - alignItems: "center", - backgroundColor: "#f8f0fc", - }, - scanText: { - textAlign: "center", - fontSize: 24, - fontWeight: "bold", - color: "#ff69b4", - marginVertical: 10, - }, - instructionText: { - textAlign: "center", - fontSize: 16, - color: "#000", - marginBottom: 20, - }, - cameraContainer: { - height: '60%', - alignItems: "center", - justifyContent: "center", - borderRadius: 10, - overflow: "hidden", - }, - camera: { - width: '100%', - height: '100%', - }, - button: { - backgroundColor: '#333', - paddingHorizontal: 20, - paddingVertical: 10, - borderRadius: 30, - alignItems: 'center', - justifyContent: 'center', - marginVertical: 10, - }, - buttonText: { - color: 'white', - fontSize: 16, - }, - dataBox: { - marginVertical: 10, - padding: 10, - backgroundColor: "#fff", - borderRadius: 5, - alignItems: "center", - justifyContent: "center", - }, - dataText: { - fontSize: 16, - color: "#000", - }, - welcomeText: { - textAlign: "center", - fontSize: 20, - marginVertical: 10, - color: "black", - }, - tabBar: { - position: 'absolute', - bottom: 20, - left: 20, - right: 20, - height: 70, - borderRadius: 35, - backgroundColor: '#fff', - flexDirection: 'row', - justifyContent: 'space-around', - alignItems: 'center', - shadowColor: '#000', - shadowOpacity: 0.1, - shadowOffset: { width: 0, height: 2 }, - shadowRadius: 10, - elevation: 5, - }, - tabButton: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - }, - floatingButton: { - position: 'absolute', - bottom: 30, - left: '50%', // Position from the left - marginLeft: -30, // Half of the button width to center it - width: 60, - height: 60, - borderRadius: 30, - backgroundColor: '#673ab7', - justifyContent: 'center', - alignItems: 'center', - shadowColor: '#000', - shadowOpacity: 0.1, - shadowOffset: { width: 0, height: 2 }, - shadowRadius: 5, - elevation: 3, - }, - - - -}); +export default App; diff --git a/components/CameraView.tsx b/components/CameraView.tsx new file mode 100644 index 0000000..fdd3e6c --- /dev/null +++ b/components/CameraView.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { Camera } from 'expo-camera'; +import { View, StyleSheet } from 'react-native'; + +interface CameraViewProps { + onBarcodeScanned?: (data: any) => void; + barcodeScannerSettings?: any; + style?: any; +} + +const CameraView: React.FC = ({ onBarcodeScanned, barcodeScannerSettings, style }) => { + return ( + + + + ); +}; + +export default CameraView; diff --git a/components/CustomTabBar.tsx b/components/CustomTabBar.tsx new file mode 100644 index 0000000..7dcae26 --- /dev/null +++ b/components/CustomTabBar.tsx @@ -0,0 +1,115 @@ +import React from 'react'; +import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'; +import { Ionicons } from '@expo/vector-icons'; + +// Custom tab bar component +const CustomTabBar = ({ state, descriptors, navigation }) => { + return ( + + {/* Iterate through each route in the state to render tab buttons */} + {state.routes.map((route, index) => { + const { options } = descriptors[route.key]; + const label = options.tabBarLabel !== undefined + ? options.tabBarLabel + : options.title !== undefined + ? options.title + : route.name; + + // Check if the current route is focused + const isFocused = state.index === index; + + // Define the onPress behavior for each tab button + const onPress = () => { + const event = navigation.emit({ + type: 'tabPress', + target: route.key, + }); + + if (!isFocused && !event.defaultPrevented) { + navigation.navigate(route.name); + } + }; + + // Define the onLongPress behavior for each tab button + const onLongPress = () => { + navigation.emit({ + type: 'tabLongPress', + target: route.key, + }); + }; + + // Determine the icon name based on the route name + const iconName = route.name === 'QR Scanner' ? 'camera' : route.name === 'History' ? 'time' : route.name === 'Settings' ? 'settings' : 'person'; + + return ( + + {/* Render the icon and label for each tab */} + + + {label} + + + ); + })} + {/* Floating button to navigate directly to QR Scanner */} + + { navigation.navigate('QR Scanner'); }}> + + + + + ); +}; + +const styles = StyleSheet.create({ + tabBar: { + position: 'absolute', + bottom: 20, + left: 20, + right: 20, + height: 70, + borderRadius: 35, + backgroundColor: '#fff', + flexDirection: 'row', + justifyContent: 'space-around', + alignItems: 'center', + shadowColor: '#000', + shadowOpacity: 0.1, + shadowOffset: { width: 0, height: 2 }, + shadowRadius: 10, + elevation: 5, + }, + tabButton: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + floatingButton: { + position: 'absolute', + bottom: 30, + left: '50%', + marginLeft: -30, + width: 60, + height: 60, + borderRadius: 30, + backgroundColor: '#673ab7', + justifyContent: 'center', + alignItems: 'center', + shadowColor: '#000', + shadowOpacity: 0.1, + shadowOffset: { width: 0, height: 2 }, + shadowRadius: 5, + elevation: 3, + }, +}); + +export default CustomTabBar; diff --git a/navigation/AppNavigator.tsx b/navigation/AppNavigator.tsx new file mode 100644 index 0000000..e4640e7 --- /dev/null +++ b/navigation/AppNavigator.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { NavigationContainer } from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import QRScannerScreen from '../screens/QRScannerScreen'; +import HistoryScreen from '../screens/HistoryScreen'; +import SettingsScreen from '../screens/SettingsScreen'; +import CustomTabBar from '../components/CustomTabBar'; + +// Create a bottom tab navigator +const Tab = createBottomTabNavigator(); + +// Main navigation component +const AppNavigator = () => { + return ( + // Wrap the navigator in a NavigationContainer to manage the navigation tree + + + {/* Define the tab navigator with custom tab bar and initial route */} + }> + + {/* Define each tab with a name and corresponding component */} + + + + + + ); +}; + +export default AppNavigator; diff --git a/screens/HistoryScreen.tsx b/screens/HistoryScreen.tsx new file mode 100644 index 0000000..91f2663 --- /dev/null +++ b/screens/HistoryScreen.tsx @@ -0,0 +1,50 @@ +import React, { useContext } from 'react'; +import { View, Text, StyleSheet, FlatList } from 'react-native'; +import { QRCodeContext } from '../types'; + +const HistoryScreen: React.FC = () => { + const { qrCodes } = useContext(QRCodeContext); + + return ( + + History Screen + ( + + {item} + + )} + keyExtractor={(item, index) => index.toString()} + /> + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#f8f0fc', + padding: 20, + }, + welcomeText: { + textAlign: 'center', + fontSize: 20, + marginVertical: 10, + color: 'black', + }, + dataBox: { + marginVertical: 10, + padding: 10, + backgroundColor: '#fff', + borderRadius: 5, + alignItems: 'center', + justifyContent: 'center', + }, + dataText: { + fontSize: 16, + color: '#000', + }, +}); + +export default HistoryScreen; diff --git a/screens/QRScannerScreen.tsx b/screens/QRScannerScreen.tsx new file mode 100644 index 0000000..d77b7a6 --- /dev/null +++ b/screens/QRScannerScreen.tsx @@ -0,0 +1,185 @@ +import React, { useState, useEffect, useContext } from 'react'; +import { View, Text, StyleSheet, ActivityIndicator } from 'react-native'; +import { CameraView , Camera } from 'expo-camera'; +import { QRCodeContext } from '../types'; +import axios from 'axios'; + + + +// QR Scanner screen component +const QRScannerScreen: React.FC = () => { + const qrCodeContext = useContext(QRCodeContext); + const { qrCodes, setQrCodes } = qrCodeContext || { qrCodes: [], setQrCodes: () => {} }; + const [hasPermission, setHasPermission] = useState(null); + const [scanned, setScanned] = useState(false); + const [showSplash, setShowSplash] = useState(true); + const [scannedData, setScannedData] = useState(''); + + // Request camera permissions when the component mounts + useEffect(() => { + const initializeApp = async () => { + const { status } = await Camera.requestCameraPermissionsAsync(); + setHasPermission(status === 'granted'); + setShowSplash(false); + }; + + initializeApp(); + }, []); + + + // Handle the QR code scanned event + const handleQRCodeScanned = async ({ type, data }: { type: string; data: string }) => { + setScanned(true); + + let dataType; + if (/^(http|https):\/\//.test(data)) { + dataType = 'URL'; + } else if (/^[0-9]+$/.test(data)) { + dataType = 'Numbers'; + } else { + dataType = 'Text'; + } + + let newScannedData = `Type: ${dataType}\nData: ${data}`; + + try { + const scanId = await scanWithVirusTotal(data); + const positive = await getScanResult(scanId); + newScannedData += `\nScore: ${positive}`; + } catch (error) { + console.error('Error handling barcode scan:', error); + } + + setScannedData(newScannedData); + setQrCodes([...qrCodes, newScannedData]); + }; + + // Send the scanned data to VirusTotal for scanning + const scanWithVirusTotal = async (data: any) => { + const apiKey = 'YOUR_VIRUSTOTAL_API_KEY'; + const url = 'https://www.virustotal.com/vtapi/v2/url/scan'; + const params = { + apikey: apiKey, + url: data, + }; + + try { + const response = await axios.post(url, null, { params }); + return response.data.scan_id; + } catch (error) { + console.error('Error scanning with VirusTotal:', error); + throw error; + } + }; + + // Get the scan result from VirusTotal using the scan ID + const getScanResult = async (scanId: string) => { + const apiKey = 'YOUR_VIRUSTOTAL_API_KEY'; + const url = 'https://www.virustotal.com/vtapi/v2/url/report'; + const params = { + apikey: apiKey, + resource: scanId, + }; + + try { + const response = await axios.get(url, { params }); + return response.data.positives; + } catch (error) { + console.error('Error getting scan result:', error); + throw error; + } + }; + + if (showSplash) { + return ( + + + + ); + } + + if (hasPermission === null) { + return Requesting for camera permission; + } + + if (hasPermission === false) { + return No access to camera; + } + + return ( + + + SafeQR + + Welcome to SafeQR code Scanner + + + {/* Render the CameraView component for QR scanning */} + + + {scannedData !== '' && ( + + {scannedData} + + )} + + ); +}; + +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%', + }, + dataBox: { + marginVertical: 10, + padding: 10, + backgroundColor: '#fff', + borderRadius: 5, + alignItems: 'center', + justifyContent: 'center', + }, + dataText: { + fontSize: 16, + color: '#000', + }, + welcomeText: { + textAlign: 'center', + fontSize: 20, + marginVertical: 10, + color: 'black', + }, +}); + +export default QRScannerScreen; diff --git a/screens/SettingsScreen.tsx b/screens/SettingsScreen.tsx new file mode 100644 index 0000000..fc1e63e --- /dev/null +++ b/screens/SettingsScreen.tsx @@ -0,0 +1,49 @@ +import React, { useContext } from 'react'; +import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; +import { QRCodeContext } from '../types'; + +const SettingsScreen: React.FC = () => { + const { setQrCodes } = useContext(QRCodeContext); + + const clearHistory = () => { + setQrCodes([]); + }; + + return ( + + Settings Screen + + Clear History + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#f8f0fc', + padding: 20, + }, + welcomeText: { + textAlign: 'center', + fontSize: 20, + marginVertical: 10, + color: 'black', + }, + button: { + backgroundColor: '#333', + paddingHorizontal: 20, + paddingVertical: 10, + borderRadius: 30, + alignItems: 'center', + justifyContent: 'center', + marginVertical: 10, + }, + buttonText: { + color: 'white', + fontSize: 16, + }, +}); + +export default SettingsScreen; diff --git a/types.ts b/types.ts new file mode 100644 index 0000000..381c77c --- /dev/null +++ b/types.ts @@ -0,0 +1,8 @@ +import { createContext } from 'react'; + +export type QRCodeContextType = { + qrCodes: string[]; + setQrCodes: (codes: string[]) => void; +}; + +export const QRCodeContext = createContext(null);