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