Integrate Redux for state management, replace useState with Redux store

This commit is contained in:
2024-07-07 15:57:29 +08:00
parent 47d1867cab
commit cdac16a723
11 changed files with 196 additions and 129 deletions

35
App.tsx
View File

@@ -1,16 +1,17 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { NavigationContainer } from '@react-navigation/native'; import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Provider } from 'react-redux';
import QRScannerScreen from './screens/QRScannerScreen'; import QRScannerScreen from './screens/QRScannerScreen';
import HistoryScreen from './screens/HistoryScreen'; import HistoryScreen from './screens/HistoryScreen';
import SettingsScreen from './screens/SettingsScreen'; import SettingsScreen from './screens/SettingsScreen';
import { QRCodeContext } from './types'; import { QRCodeContext } from './types';
import CustomTabBar from './components/CustomTabBar'; import CustomTabBar from './components/CustomTabBar';
import store from './store';
const Tab = createBottomTabNavigator(); const Tab = createBottomTabNavigator();
const App: React.FC = () => { const App: React.FC = () => {
const [qrCodes, setQrCodes] = useState<{ data: string, bookmarked: boolean, scanResult: { secureConnection: boolean, virusTotalCheck: boolean, redirects: number } }[]>([]);
const [scannedData, setScannedData] = useState<string>(''); const [scannedData, setScannedData] = useState<string>('');
const clearScanData = () => { const clearScanData = () => {
@@ -18,22 +19,22 @@ const App: React.FC = () => {
}; };
return ( return (
<QRCodeContext.Provider value={{ qrCodes, setQrCodes }}> <Provider store={store}>
<NavigationContainer> <QRCodeContext.Provider value={{ scannedData, setScannedData }}>
<Tab.Navigator <NavigationContainer>
initialRouteName="QRScanner" <Tab.Navigator
tabBar={props => <CustomTabBar {...props} clearScanData={clearScanData} />} initialRouteName="QRScanner"
> tabBar={(props) => <CustomTabBar {...props} clearScanData={clearScanData} />}
<Tab.Screen name="History" component={HistoryScreen} /> >
<Tab.Screen name="QRScanner"> <Tab.Screen name="History" component={HistoryScreen} />
{(props) => <QRScannerScreen clearScanData={function (): void { <Tab.Screen name="QRScanner">
throw new Error('Function not implemented.'); {(props) => <QRScannerScreen {...props} clearScanData={clearScanData} />}
} } {...props} />} </Tab.Screen>
</Tab.Screen> <Tab.Screen name="Settings" component={SettingsScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} /> </Tab.Navigator>
</Tab.Navigator> </NavigationContainer>
</NavigationContainer> </QRCodeContext.Provider>
</QRCodeContext.Provider> </Provider>
); );
}; };

6
actions/qrCodeActions.ts Normal file
View File

@@ -0,0 +1,6 @@
import { createAction } from '@reduxjs/toolkit';
import { QRCode } from '../types';
export const addQRCode = createAction<QRCode>('qrCodes/addQRCode');
export const toggleBookmark = createAction<number>('qrCodes/toggleBookmark');
export const deleteQRCode = createAction<number | null>('qrCodes/deleteQRCode');

View File

@@ -3,14 +3,12 @@ import { View, Text, StyleSheet, Image, TouchableOpacity, Modal } from 'react-na
import QRCode from 'react-native-qrcode-svg'; import QRCode from 'react-native-qrcode-svg';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
// Define Props for ScannedDataBox component
interface ScannedDataBoxProps { interface ScannedDataBoxProps {
data: string; data: string;
dataType: string; dataType: string;
clearScanData: () => void; clearScanData: () => void;
} }
// Define ScanResult interface
interface ScanResult { interface ScanResult {
secureConnection: boolean; secureConnection: boolean;
virusTotalCheck: boolean; virusTotalCheck: boolean;
@@ -20,21 +18,18 @@ interface ScanResult {
const ScannedDataBox: React.FC<ScannedDataBoxProps> = ({ data, dataType, clearScanData }) => { const ScannedDataBox: React.FC<ScannedDataBoxProps> = ({ data, dataType, clearScanData }) => {
const [scanResult, setScanResult] = useState<ScanResult | null>(null); const [scanResult, setScanResult] = useState<ScanResult | null>(null);
const [isModalVisible, setIsModalVisible] = useState(false); const [isModalVisible, setIsModalVisible] = useState(false);
console.log("ScannedDataBox -> Data", data);
console.log("DataType", dataType);
console.log("ScannedDataBox -> Data:", data);
console.log("DataType:", dataType);
// Set scan result based on data
useEffect(() => { useEffect(() => {
// Assuming scanResult is directly related to data
setScanResult({ setScanResult({
secureConnection: data.includes('https'), // Example logic secureConnection: data.includes('https'), // Example logic
virusTotalCheck: !data.includes('danger'), // Example logic virusTotalCheck: !data.includes('danger'), // Example logic
redirects: data.includes('redirect') ? 1 : 0, // Example logic redirects: data.includes('redirect') ? 1 : 0, // Example logic
}); });
console.log("Scan result set:", scanResult);
}, [data]); }, [data]);
// Determine the result text based on scan result
const getResultText = () => { const getResultText = () => {
if (!scanResult) { if (!scanResult) {
return 'UNKNOWN'; return 'UNKNOWN';
@@ -48,7 +43,6 @@ const ScannedDataBox: React.FC<ScannedDataBoxProps> = ({ data, dataType, clearSc
} }
}; };
// Determine the result color based on result text
const getResultColor = () => { const getResultColor = () => {
const result = getResultText(); const result = getResultText();
if (result === 'DANGEROUS') { if (result === 'DANGEROUS') {
@@ -64,12 +58,9 @@ const ScannedDataBox: React.FC<ScannedDataBoxProps> = ({ data, dataType, clearSc
return ( return (
<View style={styles.dataBox}> <View style={styles.dataBox}>
{/* Close button */}
<TouchableOpacity style={styles.closeButton} onPress={clearScanData}> <TouchableOpacity style={styles.closeButton} onPress={clearScanData}>
<Ionicons name="close-circle-outline" size={18} color="#ff69b4" /> <Ionicons name="close-circle-outline" size={18} color="#ff69b4" />
</TouchableOpacity> </TouchableOpacity>
{/* Display scanned data */}
<View style={styles.row}> <View style={styles.row}>
<Image source={require('../assets/ScanIcon3.png')} style={styles.scan_icon} /> <Image source={require('../assets/ScanIcon3.png')} style={styles.scan_icon} />
<Text style={styles.payload}>{data}</Text> <Text style={styles.payload}>{data}</Text>
@@ -83,18 +74,12 @@ const ScannedDataBox: React.FC<ScannedDataBoxProps> = ({ data, dataType, clearSc
</Text> </Text>
</View> </View>
<View style={styles.divider} /> <View style={styles.divider} />
{/* Display data type */}
<Text style={styles.typeText}>Type: {dataType}</Text> <Text style={styles.typeText}>Type: {dataType}</Text>
<Text style={styles.blankLine}>{'\n'}</Text> <Text style={styles.blankLine}>{'\n'}</Text>
{/* Display scan checks */}
<Text style={styles.checksText}>Checks</Text> <Text style={styles.checksText}>Checks</Text>
<Text style={styles.checksText}>Secure Connection: {scanResult?.secureConnection ? '✔️' : '✘'}</Text> <Text style={styles.checksText}>Secure Connection: {scanResult?.secureConnection ? '✔️' : '✘'}</Text>
<Text style={styles.checksText}>Virus Total Check: {scanResult?.virusTotalCheck ? '✔️' : '✘'}</Text> <Text style={styles.checksText}>Virus Total Check: {scanResult?.virusTotalCheck ? '✔️' : '✘'}</Text>
<Text style={styles.checksText}>Redirects: {scanResult ? scanResult.redirects : 'N/A'}</Text> <Text style={styles.checksText}>Redirects: {scanResult ? scanResult.redirects : 'N/A'}</Text>
{/* Action buttons */}
<View style={styles.iconContainer}> <View style={styles.iconContainer}>
<TouchableOpacity style={styles.iconButton}> <TouchableOpacity style={styles.iconButton}>
<Ionicons name="share-social" size={18} color="#2196F3" /> <Ionicons name="share-social" size={18} color="#2196F3" />
@@ -106,16 +91,12 @@ const ScannedDataBox: React.FC<ScannedDataBoxProps> = ({ data, dataType, clearSc
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<View style={styles.divider} /> <View style={styles.divider} />
{/* More information button */}
<Text style={styles.moreInfoText}>More Information</Text> <Text style={styles.moreInfoText}>More Information</Text>
<TouchableOpacity style={styles.moreInfoButton} onPress={() => setIsModalVisible(true)}> <TouchableOpacity style={styles.moreInfoButton} onPress={() => setIsModalVisible(true)}>
<Ionicons name="shield-checkmark" size={18} color="#ff69b4" /> <Ionicons name="shield-checkmark" size={18} color="#ff69b4" />
<Text style={styles.moreInfoButtonText}>Security Headers</Text> <Text style={styles.moreInfoButtonText}>Security Headers</Text>
<Ionicons name="chevron-forward" size={18} color="#ff69b4" /> <Ionicons name="chevron-forward" size={18} color="#ff69b4" />
</TouchableOpacity> </TouchableOpacity>
{/* Modal for security headers */}
<Modal <Modal
visible={isModalVisible} visible={isModalVisible}
transparent={true} transparent={true}

90
package-lock.json generated
View File

@@ -11,6 +11,7 @@
"@expo/vector-icons": "^14.0.2", "@expo/vector-icons": "^14.0.2",
"@react-navigation/bottom-tabs": "^6.5.20", "@react-navigation/bottom-tabs": "^6.5.20",
"@react-navigation/native": "^6.1.17", "@react-navigation/native": "^6.1.17",
"@reduxjs/toolkit": "^2.2.6",
"axios": "^1.7.2", "axios": "^1.7.2",
"expo": "~51.0.11", "expo": "~51.0.11",
"expo-camera": "~15.0.10", "expo-camera": "~15.0.10",
@@ -20,7 +21,9 @@
"react": "18.2.0", "react": "18.2.0",
"react-native": "0.74.2", "react-native": "0.74.2",
"react-native-qrcode-svg": "^6.3.1", "react-native-qrcode-svg": "^6.3.1",
"react-native-safe-area-context": "^4.10.4" "react-native-safe-area-context": "^4.10.4",
"react-redux": "^9.1.2",
"redux": "^5.0.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.20.0", "@babel/core": "^7.20.0",
@@ -5530,6 +5533,29 @@
"nanoid": "^3.1.23" "nanoid": "^3.1.23"
} }
}, },
"node_modules/@reduxjs/toolkit": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.6.tgz",
"integrity": "sha512-kH0r495c5z1t0g796eDQAkYbEQ3a1OLYN9o8jQQVZyKyw367pfRGS+qZLkHYvFHiUUdafpoSlQ2QYObIApjPWA==",
"dependencies": {
"immer": "^10.0.3",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"reselect": "^5.1.0"
},
"peerDependencies": {
"react": "^16.9.0 || ^17.0.0 || ^18",
"react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-redux": {
"optional": true
}
}
},
"node_modules/@rnx-kit/chromium-edge-launcher": { "node_modules/@rnx-kit/chromium-edge-launcher": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/@rnx-kit/chromium-edge-launcher/-/chromium-edge-launcher-1.0.0.tgz", "resolved": "https://registry.npmjs.org/@rnx-kit/chromium-edge-launcher/-/chromium-edge-launcher-1.0.0.tgz",
@@ -5699,6 +5725,11 @@
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
"integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="
}, },
"node_modules/@types/use-sync-external-store": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
"integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
},
"node_modules/@types/yargs": { "node_modules/@types/yargs": {
"version": "17.0.32", "version": "17.0.32",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz",
@@ -8601,6 +8632,15 @@
"node": ">=16.x" "node": ">=16.x"
} }
}, },
"node_modules/immer": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
}
},
"node_modules/import-fresh": { "node_modules/import-fresh": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
@@ -12315,6 +12355,28 @@
"async-limiter": "~1.0.0" "async-limiter": "~1.0.0"
} }
}, },
"node_modules/react-redux": {
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz",
"integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==",
"dependencies": {
"@types/use-sync-external-store": "^0.0.3",
"use-sync-external-store": "^1.0.0"
},
"peerDependencies": {
"@types/react": "^18.2.25",
"react": "^18.0",
"redux": "^5.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"redux": {
"optional": true
}
}
},
"node_modules/react-refresh": { "node_modules/react-refresh": {
"version": "0.14.2", "version": "0.14.2",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
@@ -12376,6 +12438,19 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/redux": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="
},
"node_modules/redux-thunk": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
"peerDependencies": {
"redux": "^5.0.0"
}
},
"node_modules/regenerate": { "node_modules/regenerate": {
"version": "1.4.2", "version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -12505,6 +12580,11 @@
"path-parse": "^1.0.5" "path-parse": "^1.0.5"
} }
}, },
"node_modules/reselect": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="
},
"node_modules/resolve": { "node_modules/resolve": {
"version": "1.22.8", "version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
@@ -13941,6 +14021,14 @@
"react": ">=16.8" "react": ">=16.8"
} }
}, },
"node_modules/use-sync-external-store": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz",
"integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/util-deprecate": { "node_modules/util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

View File

@@ -12,6 +12,7 @@
"@expo/vector-icons": "^14.0.2", "@expo/vector-icons": "^14.0.2",
"@react-navigation/bottom-tabs": "^6.5.20", "@react-navigation/bottom-tabs": "^6.5.20",
"@react-navigation/native": "^6.1.17", "@react-navigation/native": "^6.1.17",
"@reduxjs/toolkit": "^2.2.6",
"axios": "^1.7.2", "axios": "^1.7.2",
"expo": "~51.0.11", "expo": "~51.0.11",
"expo-camera": "~15.0.10", "expo-camera": "~15.0.10",
@@ -21,7 +22,9 @@
"react": "18.2.0", "react": "18.2.0",
"react-native": "0.74.2", "react-native": "0.74.2",
"react-native-qrcode-svg": "^6.3.1", "react-native-qrcode-svg": "^6.3.1",
"react-native-safe-area-context": "^4.10.4" "react-native-safe-area-context": "^4.10.4",
"react-redux": "^9.1.2",
"redux": "^5.0.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.20.0", "@babel/core": "^7.20.0",

View File

@@ -0,0 +1,30 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { QRCode } from '../types';
const qrCodesSlice = createSlice({
name: 'qrCodes',
initialState: [] as QRCode[],
reducers: {
addQRCode(state, action: PayloadAction<QRCode>) {
state.push(action.payload);
console.log('Added QR code to state:', action.payload);
},
toggleBookmark(state, action: PayloadAction<number>) {
const index = state.length - 1 - action.payload;
if (state[index]) {
state[index].bookmarked = !state[index].bookmarked;
console.log('Toggled bookmark for QR code at index:', index);
}
},
deleteQRCode(state, action: PayloadAction<number | null>) {
const index = state.length - 1 - (action.payload as number);
if (state[index]) {
console.log('Deleting QR code at index:', index);
state.splice(index, 1);
}
},
},
});
export const { addQRCode, toggleBookmark, deleteQRCode } = qrCodesSlice.actions;
export default qrCodesSlice.reducer;

View File

@@ -1,18 +1,19 @@
import React, { useContext, useState, useEffect } from 'react'; import React, { useContext, useState, useEffect } from 'react';
import { View, Text, StyleSheet, FlatList, TouchableOpacity, Image, BackHandler, Modal } from 'react-native'; import { View, Text, StyleSheet, FlatList, TouchableOpacity, Image, BackHandler, Modal } from 'react-native';
import { QRCodeContext, QRCode } from '../types'; // Import QRCode type import { useDispatch, useSelector } from 'react-redux';
import ScannedDataBox from '../components/ScannedDataBox'; import ScannedDataBox from '../components/ScannedDataBox';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { RootState } from '../store';
import { QRCode } from '../types';
import { toggleBookmark, deleteQRCode } from '../actions/qrCodeActions';
const HistoryScreen: React.FC = () => { const HistoryScreen: React.FC = () => {
const qrCodeContext = useContext(QRCodeContext); const dispatch = useDispatch();
const qrCodes = useSelector((state: RootState) => state.qrCodes);
const qrCodes = qrCodeContext?.qrCodes || [];
const setQrCodes = qrCodeContext?.setQrCodes || (() => {});
const [selectedData, setSelectedData] = useState<string | null>(null); const [selectedData, setSelectedData] = useState<string | null>(null);
const [selectedScanResult, setSelectedScanResult] = useState<any | null>(null); const [selectedScanResult, setSelectedScanResult] = useState<any | null>(null);
const [selectedType, setSelectedType] = useState<string | null>(null); // Add state for selectedType const [selectedType, setSelectedType] = useState<string | null>(null);
const [showBookmarks, setShowBookmarks] = useState<boolean>(false); const [showBookmarks, setShowBookmarks] = useState<boolean>(false);
const [isModalVisible, setIsModalVisible] = useState<boolean>(false); const [isModalVisible, setIsModalVisible] = useState<boolean>(false);
const [indexToDelete, setIndexToDelete] = useState<number | null>(null); const [indexToDelete, setIndexToDelete] = useState<number | null>(null);
@@ -28,42 +29,17 @@ const HistoryScreen: React.FC = () => {
return false; return false;
}; };
const backHandler = BackHandler.addEventListener( const backHandler = BackHandler.addEventListener('hardwareBackPress', backAction);
'hardwareBackPress',
backAction
);
return () => backHandler.remove(); return () => backHandler.remove();
}, [selectedData]); }, [selectedData]);
const toggleBookmark = (index: number) => { const filteredQrCodes = showBookmarks ? qrCodes.filter(qr => qr.bookmarked) : qrCodes.slice().reverse();
setQrCodes((prev: QRCode[]) => {
const originalIndex = prev.length - 1 - index; // Compute the original index
const newQrCodes = [...prev];
newQrCodes[originalIndex].bookmarked = !newQrCodes[originalIndex].bookmarked;
console.log('Toggled bookmark for QR code at index:', originalIndex);
return newQrCodes;
});
};
const deleteQRCode = () => { const handleItemPress = (item: QRCode) => {
if (indexToDelete !== null) {
setQrCodes((prev: QRCode[]) => {
const originalIndex = prev.length - 1 - indexToDelete; // Compute the original index
console.log('Deleting QR code at index:', originalIndex);
return prev.filter((_, i) => i !== originalIndex);
});
setIndexToDelete(null);
setIsModalVisible(false);
}
};
const filteredQrCodes = (showBookmarks ? qrCodes.filter(qr => qr.bookmarked) : qrCodes.slice().reverse());
const handleItemPress = (item: any) => {
setSelectedData(item.data); setSelectedData(item.data);
setSelectedScanResult(item.scanResult); setSelectedScanResult(item.scanResult);
setSelectedType(item.type); // Set the selected type setSelectedType(item.type);
console.log('Selected QR code data:', item.data); console.log('Selected QR code data:', item.data);
console.log('Selected QR code type:', item.type); console.log('Selected QR code type:', item.type);
}; };
@@ -77,23 +53,17 @@ const HistoryScreen: React.FC = () => {
const clearSelectedData = () => { const clearSelectedData = () => {
setSelectedData(null); setSelectedData(null);
setSelectedScanResult(null); setSelectedScanResult(null);
setSelectedType(null); // Clear the selected type setSelectedType(null);
}; };
return ( return (
<View style={styles.container}> <View style={styles.container}>
{/* Header for toggling between History and Bookmarks */} {/* Header for toggling between History and Bookmarks */}
<View style={styles.headerContainer}> <View style={styles.headerContainer}>
<TouchableOpacity onPress={() => { <TouchableOpacity onPress={() => { setShowBookmarks(false); clearSelectedData(); }}>
setShowBookmarks(false);
clearSelectedData();
}}>
<Text style={!showBookmarks ? styles.headerTextActive : styles.headerTextInactive}>History</Text> <Text style={!showBookmarks ? styles.headerTextActive : styles.headerTextInactive}>History</Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity onPress={() => { <TouchableOpacity onPress={() => { setShowBookmarks(true); clearSelectedData(); }}>
setShowBookmarks(true);
clearSelectedData();
}}>
<Text style={showBookmarks ? styles.headerTextActive : styles.headerTextInactive}>Bookmarks</Text> <Text style={showBookmarks ? styles.headerTextActive : styles.headerTextInactive}>Bookmarks</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
@@ -121,7 +91,7 @@ const HistoryScreen: React.FC = () => {
})}</Text> })}</Text>
</View> </View>
<View style={styles.itemRight}> <View style={styles.itemRight}>
<TouchableOpacity onPress={() => toggleBookmark(index)}> <TouchableOpacity onPress={() => dispatch(toggleBookmark(index))}>
<Ionicons name={item.bookmarked ? "bookmark" : "bookmark-outline"} size={24} color={item.bookmarked ? "#2196F3" : "#ff69b4"} /> <Ionicons name={item.bookmarked ? "bookmark" : "bookmark-outline"} size={24} color={item.bookmarked ? "#2196F3" : "#ff69b4"} />
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity onPress={() => confirmDelete(index)}> <TouchableOpacity onPress={() => confirmDelete(index)}>
@@ -146,7 +116,7 @@ const HistoryScreen: React.FC = () => {
<Text style={styles.modalTitle}>Are you sure?</Text> <Text style={styles.modalTitle}>Are you sure?</Text>
<Text style={styles.modalText}>If bookmarked, this will be removed from both History and Bookmarks.</Text> <Text style={styles.modalText}>If bookmarked, this will be removed from both History and Bookmarks.</Text>
<View style={styles.modalButtons}> <View style={styles.modalButtons}>
<TouchableOpacity style={styles.modalButton} onPress={deleteQRCode}> <TouchableOpacity style={styles.modalButton} onPress={() => dispatch(deleteQRCode(indexToDelete))}>
<Text style={styles.modalButtonText}>Yes, Delete</Text> <Text style={styles.modalButtonText}>Yes, Delete</Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity style={styles.modalButton} onPress={() => setIsModalVisible(false)}> <TouchableOpacity style={styles.modalButton} onPress={() => setIsModalVisible(false)}>

View File

@@ -1,20 +1,18 @@
import React, { useState, useEffect, useContext } from 'react'; import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert, Image } from 'react-native'; import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert } from 'react-native';
import { Camera, CameraView, scanFromURLAsync } from 'expo-camera'; import { Camera, CameraView, scanFromURLAsync } from 'expo-camera';
import { QRCodeContext } from '../types';
import axios from 'axios'; // For URL calls import axios from 'axios'; // For URL calls
import { Ionicons } from '@expo/vector-icons'; // For icons import { Ionicons } from '@expo/vector-icons'; // For icons
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import * as ImagePicker from 'expo-image-picker'; import * as ImagePicker from 'expo-image-picker';
import ScannedDataBox from '../components/ScannedDataBox'; import ScannedDataBox from '../components/ScannedDataBox';
import { useDispatch } from 'react-redux';
import { addQRCode } from '../actions/qrCodeActions';
// Main Function
const QRScannerScreen: React.FC = () => { const QRScannerScreen: React.FC = () => {
const navigation = useNavigation(); // call Navigation bar const navigation = useNavigation(); // call Navigation bar
const [showSplash, setShowSplash] = useState<boolean>(true); // call splash screen const [showSplash, setShowSplash] = useState<boolean>(true); // call splash screen
const dispatch = useDispatch();
const qrCodeContext = useContext(QRCodeContext); // From ./types.ts
const { qrCodes, setQrCodes } = qrCodeContext || { qrCodes: [], setQrCodes: () => {} };
const [hasPermission, setHasPermission] = useState<boolean | null>(null); const [hasPermission, setHasPermission] = useState<boolean | null>(null);
const [scanned, setScanned] = useState<boolean>(false); const [scanned, setScanned] = useState<boolean>(false);
@@ -22,35 +20,31 @@ const QRScannerScreen: React.FC = () => {
const [dataType, setDataType] = useState<string>(''); // State for data type const [dataType, setDataType] = useState<string>(''); // State for data type
const [enableTorch, setEnableTorch] = useState<boolean>(false); // State for torch const [enableTorch, setEnableTorch] = useState<boolean>(false); // State for torch
// Request Camera Permission and initialize the app
useEffect(() => { useEffect(() => {
const initializeApp = async () => { const initializeApp = async () => {
const { status } = await Camera.requestCameraPermissionsAsync(); const { status } = await Camera.requestCameraPermissionsAsync();
setHasPermission(status === 'granted'); setHasPermission(status === 'granted');
setShowSplash(false); setShowSplash(false);
console.log("Camera permissions initialized");
}; };
initializeApp(); initializeApp();
}, []); }, []);
// Clear Scan Data
const clearScanDataInternal = () => { const clearScanDataInternal = () => {
setScannedData(''); setScannedData('');
setScanned(false); setScanned(false);
setDataType(''); setDataType('');
console.log("Scan data cleared");
}; };
// Handle QR Code Payload
const handlePayload = async (payload: string) => { const handlePayload = async (payload: string) => {
setScanned(true); setScanned(true);
console.log("Scanning Completed. Payload is:", payload); console.log("Scanning Competed payload is :", payload);
const type = await sendToAPIServer(payload); const type = await sendToAPIServer(payload);
const qrCode = { const qrCode = {
data: payload, data: payload,
type, type,
bookmarked: false,
scanResult: { scanResult: {
secureConnection: true, // Placeholder, replace with actual logic secureConnection: true, // Placeholder, replace with actual logic
virusTotalCheck: true, // Placeholder, replace with actual logic virusTotalCheck: true, // Placeholder, replace with actual logic
@@ -59,14 +53,12 @@ const QRScannerScreen: React.FC = () => {
}; };
setScannedData(payload); setScannedData(payload);
console.log("Payload received:", payload); console.log("handlePayload -> payload", payload);
console.log("Type received from server:", type); console.log("handlePayload -> type", type);
setDataType(type); setDataType(type);
setQrCodes([...qrCodes, qrCode]); dispatch(addQRCode(qrCode));
console.log("QR code data added to history");
}; };
// Send QR Code Data to Backend Server
const sendToAPIServer = async (payload: string): Promise<string> => { const sendToAPIServer = async (payload: string): Promise<string> => {
console.log('Sending QR code data to backend:', payload); console.log('Sending QR code data to backend:', payload);
@@ -86,23 +78,17 @@ const QRScannerScreen: React.FC = () => {
} }
}; };
// Toggle Torch (Flashlight)
const toggleTorch = () => { const toggleTorch = () => {
setEnableTorch((prev) => !prev); setEnableTorch((prev) => !prev);
console.log("Torch toggled:", enableTorch ? "off" : "on");
}; };
// Handle Test Scan
const handleTestScan = () => { const handleTestScan = () => {
handlePayload('TEST123'); handlePayload('TEST123');
console.log("Test scan executed");
}; };
// Read QR Code from Image
const readQRFromImage = async () => { const readQRFromImage = async () => {
clearScanDataInternal(); clearScanDataInternal();
console.log("Reading QR code from image"); console.log("readingQRFromImage");
const result = await ImagePicker.launchImageLibraryAsync({ const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images, mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: false, // Don't ask user to crop images allowsEditing: false, // Don't ask user to crop images
@@ -115,11 +101,10 @@ const QRScannerScreen: React.FC = () => {
if (scannedResult && scannedResult[0] && scannedResult[0].data) { if (scannedResult && scannedResult[0] && scannedResult[0].data) {
handlePayload(scannedResult[0].data); handlePayload(scannedResult[0].data);
// Not sure why scannedResult.data is undefined but access as array work, KIV // Not sure why scannedResult.data is undefined but access as array work, KIV
console.log('QR code data from image:', scannedResult[0].data); console.log('readingQRFromImage -> scannedResult[0].data:', scannedResult[0].data);
} else { } else {
setScannedData("No QR Code Found"); setScannedData("No QR Code Found");
setTimeout(() => setScannedData(""), 4000); setTimeout(() => setScannedData(""), 4000);
console.log("No QR code found in the selected image");
} }
} catch (error) { } catch (error) {
console.error('Error scanning QR code from image:', error); console.error('Error scanning QR code from image:', error);
@@ -128,11 +113,9 @@ const QRScannerScreen: React.FC = () => {
} }
}; };
// Clear scan data when screen is focused
useEffect(() => { useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => { const unsubscribe = navigation.addListener('focus', () => {
clearScanDataInternal(); clearScanDataInternal();
console.log("Screen focused, scan data cleared");
}); });
return unsubscribe; return unsubscribe;
}, [navigation]); }, [navigation]);

11
store.ts Normal file
View File

@@ -0,0 +1,11 @@
import { configureStore } from '@reduxjs/toolkit';
import qrCodesReducer from './reducers/qrCodesReducer';
const store = configureStore({
reducer: {
qrCodes: qrCodesReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export default store;

View File

@@ -1,6 +1,4 @@
{ {
"extends": "expo/tsconfig.base", "compilerOptions": {},
"compilerOptions": { "extends": "expo/tsconfig.base"
"strict": true
}
} }

View File

@@ -1,7 +1,8 @@
import { createContext } from "react"; import { createContext } from 'react';
export interface QRCode { export interface QRCode {
data: string; data: string;
type: string;
bookmarked: boolean; bookmarked: boolean;
scanResult: { scanResult: {
secureConnection: boolean; secureConnection: boolean;
@@ -10,12 +11,7 @@ export interface QRCode {
}; };
} }
interface QRCodeContextProps { export const QRCodeContext = createContext<{
qrCodes: QRCode[]; qrCodes: QRCode[];
setQrCodes: (codes: QRCode[]) => void; setQrCodes: React.Dispatch<React.SetStateAction<QRCode[]>>;
setCurrentScannedData?: (data: string) => void; } | null>(null);
toggleBookmark?: (index: number) => void;
deleteQRCode?: (index: number) => void;
}
export const QRCodeContext = createContext<QRCodeContextProps | null>(null);