From cdac16a723b2f991bf03b2b70476e49620ddf8c9 Mon Sep 17 00:00:00 2001 From: Isky Date: Sun, 7 Jul 2024 15:57:29 +0800 Subject: [PATCH 1/4] Integrate Redux for state management, replace useState with Redux store --- App.tsx | 35 +++++++------- actions/qrCodeActions.ts | 6 +++ components/ScannedDataBox.tsx | 25 ++-------- package-lock.json | 90 ++++++++++++++++++++++++++++++++++- package.json | 5 +- reducers/qrCodesReducer.ts | 30 ++++++++++++ screens/HistoryScreen.tsx | 62 +++++++----------------- screens/QRScannerScreen.tsx | 41 +++++----------- store.ts | 11 +++++ tsconfig.json | 6 +-- types.ts | 14 ++---- 11 files changed, 196 insertions(+), 129 deletions(-) create mode 100644 actions/qrCodeActions.ts create mode 100644 reducers/qrCodesReducer.ts create mode 100644 store.ts diff --git a/App.tsx b/App.tsx index 7c5c001..bb4933c 100644 --- a/App.tsx +++ b/App.tsx @@ -1,16 +1,17 @@ import React, { useState } from 'react'; import { NavigationContainer } from '@react-navigation/native'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { Provider } from 'react-redux'; import QRScannerScreen from './screens/QRScannerScreen'; import HistoryScreen from './screens/HistoryScreen'; import SettingsScreen from './screens/SettingsScreen'; import { QRCodeContext } from './types'; import CustomTabBar from './components/CustomTabBar'; +import store from './store'; const Tab = createBottomTabNavigator(); const App: React.FC = () => { - const [qrCodes, setQrCodes] = useState<{ data: string, bookmarked: boolean, scanResult: { secureConnection: boolean, virusTotalCheck: boolean, redirects: number } }[]>([]); const [scannedData, setScannedData] = useState(''); const clearScanData = () => { @@ -18,22 +19,22 @@ const App: React.FC = () => { }; return ( - - - } - > - - - {(props) => } - - - - - + + + + } + > + + + {(props) => } + + + + + + ); }; diff --git a/actions/qrCodeActions.ts b/actions/qrCodeActions.ts new file mode 100644 index 0000000..ef6a525 --- /dev/null +++ b/actions/qrCodeActions.ts @@ -0,0 +1,6 @@ +import { createAction } from '@reduxjs/toolkit'; +import { QRCode } from '../types'; + +export const addQRCode = createAction('qrCodes/addQRCode'); +export const toggleBookmark = createAction('qrCodes/toggleBookmark'); +export const deleteQRCode = createAction('qrCodes/deleteQRCode'); diff --git a/components/ScannedDataBox.tsx b/components/ScannedDataBox.tsx index 305a1ff..28ad862 100644 --- a/components/ScannedDataBox.tsx +++ b/components/ScannedDataBox.tsx @@ -3,14 +3,12 @@ import { View, Text, StyleSheet, Image, TouchableOpacity, Modal } from 'react-na import QRCode from 'react-native-qrcode-svg'; import { Ionicons } from '@expo/vector-icons'; -// Define Props for ScannedDataBox component interface ScannedDataBoxProps { data: string; dataType: string; clearScanData: () => void; } -// Define ScanResult interface interface ScanResult { secureConnection: boolean; virusTotalCheck: boolean; @@ -20,21 +18,18 @@ interface ScanResult { const ScannedDataBox: React.FC = ({ data, dataType, clearScanData }) => { const [scanResult, setScanResult] = useState(null); 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(() => { + // Assuming scanResult is directly related to data setScanResult({ secureConnection: data.includes('https'), // Example logic virusTotalCheck: !data.includes('danger'), // Example logic redirects: data.includes('redirect') ? 1 : 0, // Example logic }); - console.log("Scan result set:", scanResult); }, [data]); - // Determine the result text based on scan result const getResultText = () => { if (!scanResult) { return 'UNKNOWN'; @@ -48,7 +43,6 @@ const ScannedDataBox: React.FC = ({ data, dataType, clearSc } }; - // Determine the result color based on result text const getResultColor = () => { const result = getResultText(); if (result === 'DANGEROUS') { @@ -64,12 +58,9 @@ const ScannedDataBox: React.FC = ({ data, dataType, clearSc return ( - {/* Close button */} - - {/* Display scanned data */} {data} @@ -83,18 +74,12 @@ const ScannedDataBox: React.FC = ({ data, dataType, clearSc - - {/* Display data type */} Type: {dataType} {'\n'} - - {/* Display scan checks */} Checks Secure Connection: {scanResult?.secureConnection ? '✔️' : '✘'} Virus Total Check: {scanResult?.virusTotalCheck ? '✔️' : '✘'} Redirects: {scanResult ? scanResult.redirects : 'N/A'} - - {/* Action buttons */} @@ -106,16 +91,12 @@ const ScannedDataBox: React.FC = ({ data, dataType, clearSc - - {/* More information button */} More Information setIsModalVisible(true)}> Security Headers - - {/* Modal for security headers */} =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": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", @@ -12315,6 +12355,28 @@ "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": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -12376,6 +12438,19 @@ "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": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -12505,6 +12580,11 @@ "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": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -13941,6 +14021,14 @@ "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": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 96bd597..f91cc43 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@expo/vector-icons": "^14.0.2", "@react-navigation/bottom-tabs": "^6.5.20", "@react-navigation/native": "^6.1.17", + "@reduxjs/toolkit": "^2.2.6", "axios": "^1.7.2", "expo": "~51.0.11", "expo-camera": "~15.0.10", @@ -21,7 +22,9 @@ "react": "18.2.0", "react-native": "0.74.2", "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": { "@babel/core": "^7.20.0", diff --git a/reducers/qrCodesReducer.ts b/reducers/qrCodesReducer.ts new file mode 100644 index 0000000..6a4b458 --- /dev/null +++ b/reducers/qrCodesReducer.ts @@ -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) { + state.push(action.payload); + console.log('Added QR code to state:', action.payload); + }, + toggleBookmark(state, action: PayloadAction) { + 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) { + 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; diff --git a/screens/HistoryScreen.tsx b/screens/HistoryScreen.tsx index 804a8bb..f840103 100644 --- a/screens/HistoryScreen.tsx +++ b/screens/HistoryScreen.tsx @@ -1,18 +1,19 @@ import React, { useContext, useState, useEffect } from 'react'; 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 { Ionicons } from '@expo/vector-icons'; +import { RootState } from '../store'; +import { QRCode } from '../types'; +import { toggleBookmark, deleteQRCode } from '../actions/qrCodeActions'; const HistoryScreen: React.FC = () => { - const qrCodeContext = useContext(QRCodeContext); - - const qrCodes = qrCodeContext?.qrCodes || []; - const setQrCodes = qrCodeContext?.setQrCodes || (() => {}); + const dispatch = useDispatch(); + const qrCodes = useSelector((state: RootState) => state.qrCodes); const [selectedData, setSelectedData] = useState(null); const [selectedScanResult, setSelectedScanResult] = useState(null); - const [selectedType, setSelectedType] = useState(null); // Add state for selectedType + const [selectedType, setSelectedType] = useState(null); const [showBookmarks, setShowBookmarks] = useState(false); const [isModalVisible, setIsModalVisible] = useState(false); const [indexToDelete, setIndexToDelete] = useState(null); @@ -28,42 +29,17 @@ const HistoryScreen: React.FC = () => { return false; }; - const backHandler = BackHandler.addEventListener( - 'hardwareBackPress', - backAction - ); + const backHandler = BackHandler.addEventListener('hardwareBackPress', backAction); return () => backHandler.remove(); }, [selectedData]); - const toggleBookmark = (index: number) => { - 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 filteredQrCodes = showBookmarks ? qrCodes.filter(qr => qr.bookmarked) : qrCodes.slice().reverse(); - const deleteQRCode = () => { - 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) => { + const handleItemPress = (item: QRCode) => { setSelectedData(item.data); 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 type:', item.type); }; @@ -77,23 +53,17 @@ const HistoryScreen: React.FC = () => { const clearSelectedData = () => { setSelectedData(null); setSelectedScanResult(null); - setSelectedType(null); // Clear the selected type + setSelectedType(null); }; return ( {/* Header for toggling between History and Bookmarks */} - { - setShowBookmarks(false); - clearSelectedData(); - }}> + { setShowBookmarks(false); clearSelectedData(); }}> History - { - setShowBookmarks(true); - clearSelectedData(); - }}> + { setShowBookmarks(true); clearSelectedData(); }}> Bookmarks @@ -121,7 +91,7 @@ const HistoryScreen: React.FC = () => { })} - toggleBookmark(index)}> + dispatch(toggleBookmark(index))}> confirmDelete(index)}> @@ -146,7 +116,7 @@ const HistoryScreen: React.FC = () => { Are you sure? If bookmarked, this will be removed from both History and Bookmarks. - + dispatch(deleteQRCode(indexToDelete))}> Yes, Delete setIsModalVisible(false)}> diff --git a/screens/QRScannerScreen.tsx b/screens/QRScannerScreen.tsx index 787502c..cff9d4f 100644 --- a/screens/QRScannerScreen.tsx +++ b/screens/QRScannerScreen.tsx @@ -1,20 +1,18 @@ -import React, { useState, useEffect, useContext } from 'react'; -import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert, Image } from 'react-native'; +import React, { useState, useEffect } from 'react'; +import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert } 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'; +import { useDispatch } from 'react-redux'; +import { addQRCode } from '../actions/qrCodeActions'; -// Main Function const QRScannerScreen: React.FC = () => { const navigation = useNavigation(); // call Navigation bar const [showSplash, setShowSplash] = useState(true); // call splash screen - - const qrCodeContext = useContext(QRCodeContext); // From ./types.ts - const { qrCodes, setQrCodes } = qrCodeContext || { qrCodes: [], setQrCodes: () => {} }; + const dispatch = useDispatch(); const [hasPermission, setHasPermission] = useState(null); const [scanned, setScanned] = useState(false); @@ -22,35 +20,31 @@ const QRScannerScreen: React.FC = () => { const [dataType, setDataType] = useState(''); // State for data type const [enableTorch, setEnableTorch] = useState(false); // State for torch - // Request Camera Permission and initialize the app useEffect(() => { const initializeApp = async () => { const { status } = await Camera.requestCameraPermissionsAsync(); setHasPermission(status === 'granted'); setShowSplash(false); - console.log("Camera permissions initialized"); }; initializeApp(); }, []); - // Clear Scan Data const clearScanDataInternal = () => { setScannedData(''); setScanned(false); setDataType(''); - console.log("Scan data cleared"); }; - // Handle QR Code Payload const handlePayload = async (payload: string) => { setScanned(true); - console.log("Scanning Completed. Payload is:", payload); + console.log("Scanning Competed payload is :", payload); const type = await sendToAPIServer(payload); const qrCode = { data: payload, type, + bookmarked: false, scanResult: { secureConnection: true, // Placeholder, replace with actual logic virusTotalCheck: true, // Placeholder, replace with actual logic @@ -59,14 +53,12 @@ const QRScannerScreen: React.FC = () => { }; setScannedData(payload); - console.log("Payload received:", payload); - console.log("Type received from server:", type); + console.log("handlePayload -> payload", payload); + console.log("handlePayload -> type", type); setDataType(type); - setQrCodes([...qrCodes, qrCode]); - console.log("QR code data added to history"); + dispatch(addQRCode(qrCode)); }; - // Send QR Code Data to Backend Server const sendToAPIServer = async (payload: string): Promise => { console.log('Sending QR code data to backend:', payload); @@ -86,23 +78,17 @@ const QRScannerScreen: React.FC = () => { } }; - // Toggle Torch (Flashlight) const toggleTorch = () => { setEnableTorch((prev) => !prev); - console.log("Torch toggled:", enableTorch ? "off" : "on"); }; - // Handle Test Scan const handleTestScan = () => { handlePayload('TEST123'); - console.log("Test scan executed"); }; - // Read QR Code from Image const readQRFromImage = async () => { clearScanDataInternal(); - console.log("Reading QR code from image"); - + console.log("readingQRFromImage"); const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.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) { handlePayload(scannedResult[0].data); // 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 { setScannedData("No QR Code Found"); setTimeout(() => setScannedData(""), 4000); - console.log("No QR code found in the selected image"); } } catch (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(() => { const unsubscribe = navigation.addListener('focus', () => { clearScanDataInternal(); - console.log("Screen focused, scan data cleared"); }); return unsubscribe; }, [navigation]); diff --git a/store.ts b/store.ts new file mode 100644 index 0000000..3ae4b7b --- /dev/null +++ b/store.ts @@ -0,0 +1,11 @@ +import { configureStore } from '@reduxjs/toolkit'; +import qrCodesReducer from './reducers/qrCodesReducer'; + +const store = configureStore({ + reducer: { + qrCodes: qrCodesReducer, + }, +}); + +export type RootState = ReturnType; +export default store; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index b9567f6..0e6371f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,4 @@ { - "extends": "expo/tsconfig.base", - "compilerOptions": { - "strict": true - } + "compilerOptions": {}, + "extends": "expo/tsconfig.base" } diff --git a/types.ts b/types.ts index e0dc258..5d76e36 100644 --- a/types.ts +++ b/types.ts @@ -1,7 +1,8 @@ -import { createContext } from "react"; +import { createContext } from 'react'; export interface QRCode { data: string; + type: string; bookmarked: boolean; scanResult: { secureConnection: boolean; @@ -10,12 +11,7 @@ export interface QRCode { }; } -interface QRCodeContextProps { +export const QRCodeContext = createContext<{ qrCodes: QRCode[]; - setQrCodes: (codes: QRCode[]) => void; - setCurrentScannedData?: (data: string) => void; - toggleBookmark?: (index: number) => void; - deleteQRCode?: (index: number) => void; -} - -export const QRCodeContext = createContext(null); + setQrCodes: React.Dispatch>; +} | null>(null); \ No newline at end of file From 19a1230781e5e69fcd1e2b0112a1a49d00de9078 Mon Sep 17 00:00:00 2001 From: Isky Date: Sun, 7 Jul 2024 16:38:44 +0800 Subject: [PATCH 2/4] Restored comments, and added share feature --- components/ScannedDataBox.tsx | 35 ++++++++++++++++++++++++++-- screens/QRScannerScreen.tsx | 43 ++++++++++++++++++++++++++--------- 2 files changed, 65 insertions(+), 13 deletions(-) diff --git a/components/ScannedDataBox.tsx b/components/ScannedDataBox.tsx index 28ad862..07ada36 100644 --- a/components/ScannedDataBox.tsx +++ b/components/ScannedDataBox.tsx @@ -1,14 +1,16 @@ import React, { useEffect, useState } from 'react'; -import { View, Text, StyleSheet, Image, TouchableOpacity, Modal } from 'react-native'; +import { View, Text, StyleSheet, Image, TouchableOpacity, Modal, Share } from 'react-native'; import QRCode from 'react-native-qrcode-svg'; import { Ionicons } from '@expo/vector-icons'; +// Define Props for ScannedDataBox component interface ScannedDataBoxProps { data: string; dataType: string; clearScanData: () => void; } +// Define ScanResult interface interface ScanResult { secureConnection: boolean; virusTotalCheck: boolean; @@ -21,6 +23,7 @@ const ScannedDataBox: React.FC = ({ data, dataType, clearSc console.log("ScannedDataBox -> Data", data); console.log("DataType", dataType); + // Set scan result based on data useEffect(() => { // Assuming scanResult is directly related to data setScanResult({ @@ -28,8 +31,10 @@ const ScannedDataBox: React.FC = ({ data, dataType, clearSc virusTotalCheck: !data.includes('danger'), // Example logic redirects: data.includes('redirect') ? 1 : 0, // Example logic }); + console.log("Scan result set:", scanResult); }, [data]); + // Determine the result text based on scan result const getResultText = () => { if (!scanResult) { return 'UNKNOWN'; @@ -43,6 +48,7 @@ const ScannedDataBox: React.FC = ({ data, dataType, clearSc } }; + // Determine the result color based on result text const getResultColor = () => { const result = getResultText(); if (result === 'DANGEROUS') { @@ -56,11 +62,26 @@ const ScannedDataBox: React.FC = ({ data, dataType, clearSc } }; + // Handle sharing the data + const handleShare = async () => { + try { + await Share.share({ + message: data, + }); + console.log('Data shared:', data); + } catch (error) { + console.error('Error sharing the data:', error); + } + }; + return ( + {/* Close button */} + + {/* Display scanned data */} {data} @@ -74,14 +95,20 @@ const ScannedDataBox: React.FC = ({ data, dataType, clearSc + + {/* Display data type */} Type: {dataType} {'\n'} + + {/* Display scan checks */} Checks Secure Connection: {scanResult?.secureConnection ? '✔️' : '✘'} Virus Total Check: {scanResult?.virusTotalCheck ? '✔️' : '✘'} Redirects: {scanResult ? scanResult.redirects : 'N/A'} + + {/* Action buttons */} - + Share @@ -91,12 +118,16 @@ const ScannedDataBox: React.FC = ({ data, dataType, clearSc + + {/* More information button */} More Information setIsModalVisible(true)}> Security Headers + + {/* Modal for security headers */} { +interface QRScannerScreenProps { + clearScanData: () => void; +} + +// Main Function +const QRScannerScreen: React.FC = ({ clearScanData }) => { const navigation = useNavigation(); // call Navigation bar const [showSplash, setShowSplash] = useState(true); // call splash screen const dispatch = useDispatch(); @@ -20,45 +25,52 @@ const QRScannerScreen: React.FC = () => { const [dataType, setDataType] = useState(''); // State for data type const [enableTorch, setEnableTorch] = useState(false); // State for torch + // Request Camera Permission and initialize the app useEffect(() => { const initializeApp = async () => { const { status } = await Camera.requestCameraPermissionsAsync(); setHasPermission(status === 'granted'); setShowSplash(false); + console.log("Camera permissions initialized"); }; initializeApp(); }, []); + // Clear Scan Data const clearScanDataInternal = () => { setScannedData(''); setScanned(false); setDataType(''); + console.log("Scan data cleared"); }; + // Handle QR Code Payload const handlePayload = async (payload: string) => { setScanned(true); - console.log("Scanning Competed payload is :", payload); + console.log("Scanning Completed. Payload is:", payload); const type = await sendToAPIServer(payload); const qrCode = { data: payload, type, - bookmarked: false, scanResult: { secureConnection: true, // Placeholder, replace with actual logic virusTotalCheck: true, // Placeholder, replace with actual logic redirects: 0 // Placeholder, replace with actual logic - } + }, + bookmarked: false, // Add the bookmarked property }; setScannedData(payload); - console.log("handlePayload -> payload", payload); - console.log("handlePayload -> type", type); + console.log("Payload received:", payload); + console.log("Type received from server:", type); setDataType(type); - dispatch(addQRCode(qrCode)); + dispatch(addQRCode(qrCode)); // Dispatch action to save QR code data + console.log("QR code data added to history"); }; + // Send QR Code Data to Backend Server const sendToAPIServer = async (payload: string): Promise => { console.log('Sending QR code data to backend:', payload); @@ -78,17 +90,23 @@ const QRScannerScreen: React.FC = () => { } }; + // Toggle Torch (Flashlight) const toggleTorch = () => { setEnableTorch((prev) => !prev); + console.log("Torch toggled:", enableTorch ? "off" : "on"); }; + // Handle Test Scan const handleTestScan = () => { handlePayload('TEST123'); + console.log("Test scan executed"); }; + // Read QR Code from Image const readQRFromImage = async () => { clearScanDataInternal(); - console.log("readingQRFromImage"); + console.log("Reading QR code from image"); + const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, allowsEditing: false, // Don't ask user to crop images @@ -101,10 +119,11 @@ const QRScannerScreen: React.FC = () => { 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); + console.log('QR code data from image:', scannedResult[0].data); } else { setScannedData("No QR Code Found"); setTimeout(() => setScannedData(""), 4000); + console.log("No QR code found in the selected image"); } } catch (error) { console.error('Error scanning QR code from image:', error); @@ -113,9 +132,11 @@ const QRScannerScreen: React.FC = () => { } }; + // Clear scan data when screen is focused useEffect(() => { const unsubscribe = navigation.addListener('focus', () => { clearScanDataInternal(); + console.log("Screen focused, scan data cleared"); }); return unsubscribe; }, [navigation]); From 304d5932f76619f13cf82a4452e8fcbbc3185270 Mon Sep 17 00:00:00 2001 From: Isky Date: Sun, 7 Jul 2024 16:53:05 +0800 Subject: [PATCH 3/4] Addes Share functionality, Added SecureWebView for Open Button.Restored Comments for QRScannerScreen --- components/ScannedDataBox.tsx | 40 ++++++++++++++++++++++++++++++++++- components/SecureWebView.tsx | 21 ++++++++++++++++++ package-lock.json | 22 +++++++++++++++++++ package.json | 1 + 4 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 components/SecureWebView.tsx diff --git a/components/ScannedDataBox.tsx b/components/ScannedDataBox.tsx index 07ada36..1889d6b 100644 --- a/components/ScannedDataBox.tsx +++ b/components/ScannedDataBox.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'; import { View, Text, StyleSheet, Image, TouchableOpacity, Modal, Share } from 'react-native'; import QRCode from 'react-native-qrcode-svg'; import { Ionicons } from '@expo/vector-icons'; +import SecureWebView from './SecureWebView'; // Import the SecureWebView component // Define Props for ScannedDataBox component interface ScannedDataBoxProps { @@ -20,6 +21,8 @@ interface ScanResult { const ScannedDataBox: React.FC = ({ data, dataType, clearScanData }) => { const [scanResult, setScanResult] = useState(null); const [isModalVisible, setIsModalVisible] = useState(false); + const [isWebViewVisible, setIsWebViewVisible] = useState(false); // State to control WebView modal visibility + console.log("ScannedDataBox -> Data", data); console.log("DataType", dataType); @@ -74,6 +77,12 @@ const ScannedDataBox: React.FC = ({ data, dataType, clearSc } }; + // Handle opening the data in a sandboxed WebView + const handleOpen = () => { + setIsWebViewVisible(true); + console.log('Opening data in WebView:', data); + }; + return ( {/* Close button */} @@ -112,7 +121,7 @@ const ScannedDataBox: React.FC = ({ data, dataType, clearSc Share - + Open @@ -151,6 +160,21 @@ const ScannedDataBox: React.FC = ({ data, dataType, clearSc + + {/* Modal for SecureWebView */} + setIsWebViewVisible(false)} + > + + setIsWebViewVisible(false)}> + + + + + ); }; @@ -291,6 +315,20 @@ const styles = StyleSheet.create({ fontSize: 12, color: '#fff', }, + webViewContainer: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.5)', + }, + webView: { + flex: 1, + marginTop: 40, + }, + closeWebViewButton: { + position: 'absolute', + top: 10, + right: 10, + zIndex: 1, + }, }); export default ScannedDataBox; diff --git a/components/SecureWebView.tsx b/components/SecureWebView.tsx new file mode 100644 index 0000000..c088be8 --- /dev/null +++ b/components/SecureWebView.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { WebView } from 'react-native-webview'; + +// Define the SecureWebView component +const SecureWebView = ({ url }) => { + return ( + { + // Implement additional URL filtering logic here if needed + return true; // Return true to allow the URL to be loaded + }} + /> + ); +}; + +export default SecureWebView; diff --git a/package-lock.json b/package-lock.json index 033e309..3650bac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "react-native": "0.74.2", "react-native-qrcode-svg": "^6.3.1", "react-native-safe-area-context": "^4.10.4", + "react-native-webview": "^13.10.4", "react-redux": "^9.1.2", "redux": "^5.0.1" }, @@ -12270,6 +12271,27 @@ "react-native": "*" } }, + "node_modules/react-native-webview": { + "version": "13.10.4", + "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.10.4.tgz", + "integrity": "sha512-kRn70M7vyBS3IDaX2KqyF66ovUkrBS6LiHOgrEmRdZFO0i3hYY0wldEv1fJuKvgQIPMfo7GtGAjozFrk2vQdBw==", + "dependencies": { + "escape-string-regexp": "2.0.0", + "invariant": "2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-webview/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "engines": { + "node": ">=8" + } + }, "node_modules/react-native/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", diff --git a/package.json b/package.json index f91cc43..5c27346 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "react-native": "0.74.2", "react-native-qrcode-svg": "^6.3.1", "react-native-safe-area-context": "^4.10.4", + "react-native-webview": "^13.10.4", "react-redux": "^9.1.2", "redux": "^5.0.1" }, From bca444d56d4bb4437aad15fbda7090da5a2215e8 Mon Sep 17 00:00:00 2001 From: Isky Date: Sun, 7 Jul 2024 21:54:10 +0800 Subject: [PATCH 4/4] Added API calls for Secure Connection,Virus Total Check and Redirects --- api/qrCodeAPI.tsx | 31 +++++++++++ components/ScannedDataBox.tsx | 97 ++++++++++++++--------------------- package-lock.json | 9 ++++ package.json | 3 +- screens/QRScannerScreen.tsx | 52 +++++++++++++------ 5 files changed, 118 insertions(+), 74 deletions(-) create mode 100644 api/qrCodeAPI.tsx diff --git a/api/qrCodeAPI.tsx b/api/qrCodeAPI.tsx new file mode 100644 index 0000000..45a50bb --- /dev/null +++ b/api/qrCodeAPI.tsx @@ -0,0 +1,31 @@ +import axios from 'axios'; + +const API_BASE_URL = 'http://192.168.10.247:8080/v1/api/qrcodetypes'; + +export const detectQRCodeType = async (data: string) => { + console.log('API Call - Detect QR Code Type:', data); + const response = await axios.post(`${API_BASE_URL}/detect`, { data }); + console.log('API Response - QR Code Type:', response.data); + return response.data; +}; + +export const verifyURL = async (data: string) => { + console.log('API Call - Verify URL:', data); + const response = await axios.post(`${API_BASE_URL}/verifyURL`, { data }); + console.log('API Response - Verify URL:', response.data); + return response.data; +}; + +export const virusTotalCheck = async (data: string) => { + console.log('API Call - Virus Total Check:', data); + const response = await axios.post(`${API_BASE_URL}/virusTotalCheck`, { data }); + console.log('API Response - Virus Total Check:', response.data); + return response.data; +}; + +export const checkRedirects = async (data: string) => { + console.log('API Call - Check Redirects:', data); + const response = await axios.post(`${API_BASE_URL}/checkRedirects`, { data }); + console.log('API Response - Check Redirects:', response.data); + return response.data; +}; diff --git a/components/ScannedDataBox.tsx b/components/ScannedDataBox.tsx index 1889d6b..afcd238 100644 --- a/components/ScannedDataBox.tsx +++ b/components/ScannedDataBox.tsx @@ -1,47 +1,34 @@ import React, { useEffect, useState } from 'react'; -import { View, Text, StyleSheet, Image, TouchableOpacity, Modal, Share } from 'react-native'; +import { View, Text, StyleSheet, Image, TouchableOpacity, Modal } from 'react-native'; import QRCode from 'react-native-qrcode-svg'; import { Ionicons } from '@expo/vector-icons'; -import SecureWebView from './SecureWebView'; // Import the SecureWebView component +import * as Sharing from 'expo-sharing'; +import { WebView } from 'react-native-webview'; // Define Props for ScannedDataBox component interface ScannedDataBoxProps { data: string; dataType: string; clearScanData: () => void; + scanResult: { + secureConnection: boolean; + virusTotalCheck: boolean; + redirects: number; + }; } -// Define ScanResult interface -interface ScanResult { - secureConnection: boolean; - virusTotalCheck: boolean; - redirects: number; -} - -const ScannedDataBox: React.FC = ({ data, dataType, clearScanData }) => { - const [scanResult, setScanResult] = useState(null); +const ScannedDataBox: React.FC = ({ data, dataType, clearScanData, scanResult }) => { const [isModalVisible, setIsModalVisible] = useState(false); - const [isWebViewVisible, setIsWebViewVisible] = useState(false); // State to control WebView modal visibility - + const [isWebViewVisible, setIsWebViewVisible] = useState(false); console.log("ScannedDataBox -> Data", data); console.log("DataType", dataType); - // Set scan result based on data useEffect(() => { - // Assuming scanResult is directly related to data - setScanResult({ - secureConnection: data.includes('https'), // Example logic - virusTotalCheck: !data.includes('danger'), // Example logic - redirects: data.includes('redirect') ? 1 : 0, // Example logic - }); console.log("Scan result set:", scanResult); }, [data]); // Determine the result text based on scan result const getResultText = () => { - if (!scanResult) { - return 'UNKNOWN'; - } if (!scanResult.secureConnection && !scanResult.virusTotalCheck) { return 'DANGEROUS'; } else if (scanResult.redirects > 0) { @@ -65,22 +52,16 @@ const ScannedDataBox: React.FC = ({ data, dataType, clearSc } }; - // Handle sharing the data - const handleShare = async () => { + const shareQRCodeData = async () => { try { - await Share.share({ - message: data, - }); - console.log('Data shared:', data); + await Sharing.shareAsync(data); } catch (error) { - console.error('Error sharing the data:', error); + console.error('Error sharing QR code data:', error); } }; - // Handle opening the data in a sandboxed WebView - const handleOpen = () => { + const openWebView = () => { setIsWebViewVisible(true); - console.log('Opening data in WebView:', data); }; return ( @@ -111,17 +92,17 @@ const ScannedDataBox: React.FC = ({ data, dataType, clearSc {/* Display scan checks */} Checks - Secure Connection: {scanResult?.secureConnection ? '✔️' : '✘'} - Virus Total Check: {scanResult?.virusTotalCheck ? '✔️' : '✘'} - Redirects: {scanResult ? scanResult.redirects : 'N/A'} + Secure Connection: {scanResult.secureConnection ? '✔️' : '✘'} + Virus Total Check: {scanResult.virusTotalCheck ? '✔️' : '✘'} + Redirects: {scanResult.redirects} {/* Action buttons */} - + Share - + Open @@ -160,19 +141,26 @@ const ScannedDataBox: React.FC = ({ data, dataType, clearSc - - {/* Modal for SecureWebView */} setIsWebViewVisible(false)} > - - setIsWebViewVisible(false)}> - - - + + + true} + /> + setIsWebViewVisible(false)}> + Close + + @@ -316,18 +304,11 @@ const styles = StyleSheet.create({ color: '#fff', }, webViewContainer: { - flex: 1, - backgroundColor: 'rgba(0, 0, 0, 0.5)', - }, - webView: { - flex: 1, - marginTop: 40, - }, - closeWebViewButton: { - position: 'absolute', - top: 10, - right: 10, - zIndex: 1, + width: '100%', + height: '80%', + backgroundColor: 'white', + borderRadius: 7.5, + overflow: 'hidden' }, }); diff --git a/package-lock.json b/package-lock.json index 3650bac..88be649 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "expo-camera": "~15.0.10", "expo-image-manipulator": "^12.0.5", "expo-image-picker": "~15.0.5", + "expo-sharing": "~12.0.1", "expo-status-bar": "~1.12.1", "react": "18.2.0", "react-native": "0.74.2", @@ -7882,6 +7883,14 @@ "invariant": "^2.2.4" } }, + "node_modules/expo-sharing": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/expo-sharing/-/expo-sharing-12.0.1.tgz", + "integrity": "sha512-wBT+WeXwapj/9NWuLJO01vi9bdlchYu/Q/xD8slL/Ls4vVYku8CPqzkTtDFcjLrjtlJqyeHsdQXwKLvORmBIew==", + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-status-bar": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-1.12.1.tgz", diff --git a/package.json b/package.json index 5c27346..7915f16 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "react-native-safe-area-context": "^4.10.4", "react-native-webview": "^13.10.4", "react-redux": "^9.1.2", - "redux": "^5.0.1" + "redux": "^5.0.1", + "expo-sharing": "~12.0.1" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/screens/QRScannerScreen.tsx b/screens/QRScannerScreen.tsx index 7e2d5d4..f4d795b 100644 --- a/screens/QRScannerScreen.tsx +++ b/screens/QRScannerScreen.tsx @@ -1,23 +1,24 @@ -import React, { useState, useEffect } from 'react'; +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 { useDispatch } from 'react-redux'; +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'; -import { addQRCode } from '../actions/qrCodeActions'; - -interface QRScannerScreenProps { - clearScanData: () => void; -} +import { useDispatch } from 'react-redux'; +import { addQRCode } from '../actions/qrCodeActions'; // Assuming you have actions defined for Redux +import { detectQRCodeType, verifyURL, checkRedirects } from '../api/qrCodeAPI'; // Import utility functions // Main Function -const QRScannerScreen: React.FC = ({ clearScanData }) => { +const QRScannerScreen: React.FC<{ clearScanData: () => void }> = ({ clearScanData }) => { const navigation = useNavigation(); // call Navigation bar + const dispatch = useDispatch(); // Use dispatch for Redux actions + const [showSplash, setShowSplash] = useState(true); // call splash screen - const dispatch = useDispatch(); + const qrCodeContext = useContext(QRCodeContext); // From ./types.ts + const { qrCodes, setQrCodes } = qrCodeContext || { qrCodes: [], setQrCodes: () => {} }; const [hasPermission, setHasPermission] = useState(null); const [scanned, setScanned] = useState(false); @@ -25,6 +26,11 @@ const QRScannerScreen: React.FC = ({ clearScanData }) => { const [dataType, setDataType] = useState(''); // State for data type const [enableTorch, setEnableTorch] = useState(false); // State for torch + // Add state variables for scan results + const [secureConnection, setSecureConnection] = useState(null); + const [virusTotalCheck, setVirusTotalCheck] = useState(null); + const [redirects, setRedirects] = useState(null); + // Request Camera Permission and initialize the app useEffect(() => { const initializeApp = async () => { @@ -49,17 +55,24 @@ const QRScannerScreen: React.FC = ({ clearScanData }) => { const handlePayload = async (payload: string) => { setScanned(true); console.log("Scanning Completed. Payload is:", payload); - const type = await sendToAPIServer(payload); + + const type = await detectQRCodeType(payload); + const secureConnectionResult = await verifyURL(payload); + const redirectResult = await checkRedirects(payload); + + setSecureConnection(secureConnectionResult.isSecure); + setVirusTotalCheck(!secureConnectionResult.isMalicious); // Assuming you have virusTotalCheck logic integrated here + setRedirects(redirectResult.redirects); 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 + secureConnection: secureConnectionResult.isSecure, + virusTotalCheck: !secureConnectionResult.isMalicious, + redirects: redirectResult.redirects }, - bookmarked: false, // Add the bookmarked property + bookmarked: false // by default }; setScannedData(payload); @@ -185,7 +198,16 @@ const QRScannerScreen: React.FC = ({ clearScanData }) => { {scannedData !== '' && ( - + )}