Compare commits
8 Commits
first-pres
...
refactorin
| Author | SHA1 | Date | |
|---|---|---|---|
| bca444d56d | |||
| 304d5932f7 | |||
| 19a1230781 | |||
| cdac16a723 | |||
| 47d1867cab | |||
| 947bd474d1 | |||
| 4d4b55ab71 | |||
| 0f9453ef45 |
13
App.tsx
13
App.tsx
@@ -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}>
|
||||||
|
<QRCodeContext.Provider value={{ scannedData, setScannedData }}>
|
||||||
<NavigationContainer>
|
<NavigationContainer>
|
||||||
<Tab.Navigator
|
<Tab.Navigator
|
||||||
initialRouteName="QRScanner"
|
initialRouteName="QRScanner"
|
||||||
tabBar={props => <CustomTabBar {...props} clearScanData={clearScanData} />}
|
tabBar={(props) => <CustomTabBar {...props} clearScanData={clearScanData} />}
|
||||||
>
|
>
|
||||||
<Tab.Screen name="History" component={HistoryScreen} />
|
<Tab.Screen name="History" component={HistoryScreen} />
|
||||||
<Tab.Screen name="QRScanner">
|
<Tab.Screen name="QRScanner">
|
||||||
{(props) => <QRScannerScreen clearScanData={function (): void {
|
{(props) => <QRScannerScreen {...props} clearScanData={clearScanData} />}
|
||||||
throw new Error('Function not implemented.');
|
|
||||||
} } {...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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
133
README.md
133
README.md
@@ -1,21 +1,35 @@
|
|||||||
|
|
||||||
# SafeQR Code Scanner
|
# SafeQR Code Scanner
|
||||||
|
|
||||||
SafeQR is a React Native application that allows users to scan QR codes using their mobile device's camera. The app provides a user-friendly interface with a bottom navigation bar for easy access to different sections such as QR Scanner, History, Settings, and Profile.
|
SafeQR Code Scanner is a React Native application that allows users to scan QR codes securely. The app includes features such as scanning QR codes using the camera or image gallery, checking the security of URLs via VirusTotal, bookmarking scanned QR codes, and viewing detailed scan results.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Features](#features)
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Usage](#usage)
|
||||||
|
- [File Structure](#file-structure)
|
||||||
|
- [Components](#components)
|
||||||
|
- [Navigation](#navigation)
|
||||||
|
- [Screens](#screens)
|
||||||
|
- [Dependencies](#dependencies)
|
||||||
|
- [Contributing](#contributing)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **QR Code Scanning**: Scan QR codes and display the scanned data.
|
- Scan QR codes using the device camera
|
||||||
- **History**: (Placeholder) View the history of scanned QR codes.
|
- Scan QR codes from the image gallery
|
||||||
- **Settings**: (Placeholder) Adjust application settings.
|
- Check the security of scanned URLs via VirusTotal
|
||||||
- **Profile**: (Placeholder) View and edit user profile.
|
- Bookmark and manage scanned QR codes
|
||||||
|
- View detailed scan results and security headers
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. **Clone the Repository**:
|
1. **Clone the Repository**:
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/safeqr/SafeQR_Front_End_Mobile.git
|
git clone https://github.com/yourusername/safeqr.git
|
||||||
cd SafeQR_Front_End_Mobile
|
cd safeqr
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Install Dependencies**:
|
2. **Install Dependencies**:
|
||||||
@@ -24,75 +38,94 @@ SafeQR is a React Native application that allows users to scan QR codes using th
|
|||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
3. **Install Expo CLI** (if not already installed):
|
||||||
3. **Start the Application**:
|
|
||||||
```sh
|
|
||||||
npx expo start
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
3. **Install Expo CLI** (if step 3 causes expo error):
|
|
||||||
```sh
|
```sh
|
||||||
npm install -g expo-cli
|
npm install -g expo-cli
|
||||||
```
|
```
|
||||||
|
|
||||||
|
4. **Start the Application**:
|
||||||
|
cd to project directory **SafeQR**
|
||||||
|
```shell
|
||||||
|
npx expo start
|
||||||
|
```
|
||||||
|
|
||||||
5. **Run on Device**:
|
5. **Run on Device**:
|
||||||
- For iOS, use the Expo Go app.
|
- For iOS, use the Expo Go app.
|
||||||
- For Android, use the Expo Go app or an emulator.
|
- For Android, use the Expo Go app or an emulator.
|
||||||
|
- For Windows/MacOS Install Android studio and stat the [[Android Emulator]] **first**. Once Expo and Emulater has started, press a on the terminal to connect expo to emulator
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
1. Open the app on your device.
|
1. Open the app on your device.
|
||||||
2. Navigate to the **QR Scanner** tab.
|
2. Use the camera to scan a QR code or select an image from your gallery.
|
||||||
3. Point your camera at a QR code.
|
3. View the scan results and security details.
|
||||||
4. The app will scan and display the QR code data below the camera view.
|
4. Bookmark important QR codes for later reference.
|
||||||
5. Use the bottom navigation to explore other sections (History,QR Scanner, Settings).
|
|
||||||
|
|
||||||
## Project Structure
|
## File Structure
|
||||||
|
|
||||||
|
The project structure is organized as follows:
|
||||||
|
|
||||||
```
|
```
|
||||||
.//OTHER Node_modules
|
safeqr-code-scanner/
|
||||||
├── App.tsx
|
├── assets/
|
||||||
├── package.json
|
│ └── ScanIcon3.png
|
||||||
├── tsconfig.json
|
├── components/
|
||||||
├── assets
|
│ ├── ScannedDataBox.tsx
|
||||||
│ └── ...
|
|
||||||
├── components
|
|
||||||
│ ├── CameraView.tsx
|
|
||||||
│ └── CustomTabBar.tsx
|
│ └── CustomTabBar.tsx
|
||||||
├── screens
|
├── navigation/
|
||||||
│ ├── QRScannerScreen.tsx
|
│ ├── AppNavigator.tsx
|
||||||
|
│ └── BottomTabNavigator.tsx
|
||||||
|
├── screens/
|
||||||
│ ├── HistoryScreen.tsx
|
│ ├── HistoryScreen.tsx
|
||||||
|
│ ├── QRScannerScreen.tsx
|
||||||
│ └── SettingsScreen.tsx
|
│ └── SettingsScreen.tsx
|
||||||
├── navigation
|
├── types/
|
||||||
│ └── AppNavigator.tsx
|
│ └── index.ts
|
||||||
└── types.ts
|
├── App.tsx
|
||||||
|
├── README.md
|
||||||
|
└── package.json
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Components
|
||||||
|
|
||||||
|
Reusable components used throughout the application.
|
||||||
|
|
||||||
|
- `ScannedDataBox.tsx`: Displays detailed information about the scanned QR code.
|
||||||
|
- `CustomTabBar.tsx`: Custom tab bar for navigation.
|
||||||
|
|
||||||
|
### Navigation
|
||||||
|
|
||||||
|
Handles the navigation structure of the application.
|
||||||
|
|
||||||
|
- `AppNavigator.tsx`: Main navigator that includes the bottom tab navigator.
|
||||||
|
- `BottomTabNavigator.tsx`: Defines the bottom tab navigation.
|
||||||
|
|
||||||
|
### Screens
|
||||||
|
|
||||||
|
Individual screens used in the application.
|
||||||
|
|
||||||
|
- `HistoryScreen.tsx`: Displays the history of scanned QR codes and bookmarks.
|
||||||
|
- `QRScannerScreen.tsx`: Main screen for scanning QR codes using the camera.
|
||||||
|
- `SettingsScreen.tsx`: Displays app settings and additional information.
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
The following dependencies are required to run this project:
|
The project relies on the following major dependencies:
|
||||||
|
|
||||||
"@expo/vector-icons": "^14.0.2",
|
- `react-native`: For building native apps using React.
|
||||||
"@react-navigation/bottom-tabs": "^6.5.20",
|
- `expo-camera`: For camera access and barcode scanning.
|
||||||
"@react-navigation/native": "^6.1.17",
|
- `react-native-qrcode-svg`: For generating QR codes.
|
||||||
"axios": "^1.7.2",
|
- `axios`: For making HTTP requests to the VirusTotal API.
|
||||||
"expo": "~51.0.11",
|
- `@react-navigation/native`: For navigation within the app.
|
||||||
"expo-camera": "~15.0.10",
|
- `@expo/vector-icons`: For using vector icons in the app.
|
||||||
"expo-image-picker": "~15.0.5",
|
- `expo-image-picker`: For selecting images from the device gallery.
|
||||||
"expo-status-bar": "~1.12.1",
|
- `react-native-reanimated`: For animations and gesture handling.
|
||||||
"react": "18.2.0",
|
|
||||||
"react-native": "0.74.2",
|
|
||||||
"react-native-safe-area-context": "^4.10.4",
|
|
||||||
"react-native-screens": "^3.31.1"
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
|
### Installation of dependencies
|
||||||
|
|
||||||
To install the dependencies, run the following command(it will auto read package.json):
|
To install the dependencies, run the following command(it will auto read package.json):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
6
actions/qrCodeActions.ts
Normal file
6
actions/qrCodeActions.ts
Normal 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');
|
||||||
31
api/qrCodeAPI.tsx
Normal file
31
api/qrCodeAPI.tsx
Normal file
@@ -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;
|
||||||
|
};
|
||||||
@@ -2,51 +2,33 @@ import React, { useEffect, useState } from 'react';
|
|||||||
import { View, Text, StyleSheet, Image, TouchableOpacity, Modal } from 'react-native';
|
import { View, Text, StyleSheet, Image, TouchableOpacity, Modal } from 'react-native';
|
||||||
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';
|
||||||
|
import * as Sharing from 'expo-sharing';
|
||||||
|
import { WebView } from 'react-native-webview';
|
||||||
|
|
||||||
|
// Define Props for ScannedDataBox component
|
||||||
interface ScannedDataBoxProps {
|
interface ScannedDataBoxProps {
|
||||||
data: string;
|
data: string;
|
||||||
dataType: string;
|
dataType: string;
|
||||||
clearScanData: () => void;
|
clearScanData: () => void;
|
||||||
}
|
scanResult: {
|
||||||
|
|
||||||
interface ScanResult {
|
|
||||||
secureConnection: boolean;
|
secureConnection: boolean;
|
||||||
virusTotalCheck: boolean;
|
virusTotalCheck: boolean;
|
||||||
redirects: number;
|
redirects: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const ScannedDataBox: React.FC<ScannedDataBoxProps> = ({ data, dataType, clearScanData }) => {
|
const ScannedDataBox: React.FC<ScannedDataBoxProps> = ({ data, dataType, clearScanData, scanResult }) => {
|
||||||
const [scanResult, setScanResult] = useState<ScanResult | null>(null);
|
|
||||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||||
|
const [isWebViewVisible, setIsWebViewVisible] = useState(false);
|
||||||
|
console.log("ScannedDataBox -> Data", data);
|
||||||
|
console.log("DataType", dataType);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data.includes('https://Safe_website.com')) {
|
console.log("Scan result set:", scanResult);
|
||||||
setScanResult({
|
|
||||||
secureConnection: true,
|
|
||||||
virusTotalCheck: true,
|
|
||||||
redirects: 0,
|
|
||||||
});
|
|
||||||
} else if (data.includes('https://unknown_website.com')) {
|
|
||||||
setScanResult({
|
|
||||||
secureConnection: true,
|
|
||||||
virusTotalCheck: true,
|
|
||||||
redirects: 2,
|
|
||||||
});
|
|
||||||
} else if (data.includes('http://danger_website.com')) {
|
|
||||||
setScanResult({
|
|
||||||
secureConnection: false,
|
|
||||||
virusTotalCheck: false,
|
|
||||||
redirects: 3,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setScanResult(null);
|
|
||||||
}
|
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
|
// Determine the result text based on scan result
|
||||||
const getResultText = () => {
|
const getResultText = () => {
|
||||||
if (!scanResult) {
|
|
||||||
return 'UNKNOWN';
|
|
||||||
}
|
|
||||||
if (!scanResult.secureConnection && !scanResult.virusTotalCheck) {
|
if (!scanResult.secureConnection && !scanResult.virusTotalCheck) {
|
||||||
return 'DANGEROUS';
|
return 'DANGEROUS';
|
||||||
} else if (scanResult.redirects > 0) {
|
} else if (scanResult.redirects > 0) {
|
||||||
@@ -56,6 +38,7 @@ 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') {
|
||||||
@@ -69,49 +52,72 @@ const ScannedDataBox: React.FC<ScannedDataBoxProps> = ({ data, dataType, clearSc
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const extractedData = data.split('\n')[1]?.split('Data: ')[1] || '';
|
const shareQRCodeData = async () => {
|
||||||
|
try {
|
||||||
|
await Sharing.shareAsync(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sharing QR code data:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openWebView = () => {
|
||||||
|
setIsWebViewVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
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}>{extractedData}</Text>
|
<Text style={styles.payload}>{data}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.divider} />
|
<View style={styles.divider} />
|
||||||
<Text style={styles.timestampText}>{new Date().toLocaleString()}</Text>
|
<Text style={styles.timestampText}>{new Date().toLocaleString()}</Text>
|
||||||
<View style={styles.qrContainer}>
|
<View style={styles.qrContainer}>
|
||||||
<QRCode value={extractedData} size={75} backgroundColor="transparent" />
|
<QRCode value={data || 'No Data'} size={75} backgroundColor="transparent" />
|
||||||
<Text style={[styles.resultText, { color: getResultColor() }]}>
|
<Text style={[styles.resultText, { color: getResultColor() }]}>
|
||||||
Result: {getResultText()}
|
Result: {getResultText()}
|
||||||
</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.redirects}</Text>
|
||||||
|
|
||||||
|
{/* Action buttons */}
|
||||||
<View style={styles.iconContainer}>
|
<View style={styles.iconContainer}>
|
||||||
<TouchableOpacity style={styles.iconButton}>
|
<TouchableOpacity style={styles.iconButton} onPress={shareQRCodeData}>
|
||||||
<Ionicons name="share-social" size={18} color="#2196F3" />
|
<Ionicons name="share-social" size={18} color="#2196F3" />
|
||||||
<Text style={styles.iconText}>Share</Text>
|
<Text style={styles.iconText}>Share</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<TouchableOpacity style={styles.iconButton}>
|
<TouchableOpacity style={styles.iconButton} onPress={openWebView}>
|
||||||
<Ionicons name="open" size={18} color="#2196F3" />
|
<Ionicons name="open" size={18} color="#2196F3" />
|
||||||
<Text style={styles.iconText}>Open</Text>
|
<Text style={styles.iconText}>Open</Text>
|
||||||
</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}
|
||||||
@@ -135,6 +141,28 @@ const ScannedDataBox: React.FC<ScannedDataBoxProps> = ({ data, dataType, clearSc
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
<Modal
|
||||||
|
visible={isWebViewVisible}
|
||||||
|
transparent={true}
|
||||||
|
animationType="slide"
|
||||||
|
onRequestClose={() => setIsWebViewVisible(false)}
|
||||||
|
>
|
||||||
|
<View style={styles.modalContainer}>
|
||||||
|
<View style={styles.webViewContainer}>
|
||||||
|
<WebView
|
||||||
|
source={{ uri: data }}
|
||||||
|
javaScriptEnabled={false}
|
||||||
|
domStorageEnabled={false}
|
||||||
|
allowFileAccess={false}
|
||||||
|
originWhitelist={['*']}
|
||||||
|
onShouldStartLoadWithRequest={(request) => true}
|
||||||
|
/>
|
||||||
|
<TouchableOpacity style={styles.closeModalButton} onPress={() => setIsWebViewVisible(false)}>
|
||||||
|
<Text style={styles.closeModalButtonText}>Close</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -164,7 +192,6 @@ const styles = StyleSheet.create({
|
|||||||
shadowRadius: 3.75,
|
shadowRadius: 3.75,
|
||||||
elevation: 2.25,
|
elevation: 2.25,
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
//width: '95%', // Ensure the box uses the full width of its container
|
|
||||||
},
|
},
|
||||||
qrContainer: {
|
qrContainer: {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
@@ -276,6 +303,13 @@ const styles = StyleSheet.create({
|
|||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
},
|
},
|
||||||
|
webViewContainer: {
|
||||||
|
width: '100%',
|
||||||
|
height: '80%',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 7.5,
|
||||||
|
overflow: 'hidden'
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default ScannedDataBox;
|
export default ScannedDataBox;
|
||||||
|
|||||||
21
components/SecureWebView.tsx
Normal file
21
components/SecureWebView.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { WebView } from 'react-native-webview';
|
||||||
|
|
||||||
|
// Define the SecureWebView component
|
||||||
|
const SecureWebView = ({ url }) => {
|
||||||
|
return (
|
||||||
|
<WebView
|
||||||
|
source={{ uri: url }} // Load the URL passed as a prop
|
||||||
|
javaScriptEnabled={false} // Disable JavaScript for security
|
||||||
|
domStorageEnabled={false} // Disable DOM storage for security
|
||||||
|
allowFileAccess={false} // Disable file access within the WebView for security
|
||||||
|
originWhitelist={['*']} // Allow all origins to be loaded in the WebView
|
||||||
|
onShouldStartLoadWithRequest={(request) => {
|
||||||
|
// Implement additional URL filtering logic here if needed
|
||||||
|
return true; // Return true to allow the URL to be loaded
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SecureWebView;
|
||||||
121
package-lock.json
generated
121
package-lock.json
generated
@@ -11,16 +11,21 @@
|
|||||||
"@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",
|
||||||
"expo-image-manipulator": "^12.0.5",
|
"expo-image-manipulator": "^12.0.5",
|
||||||
"expo-image-picker": "~15.0.5",
|
"expo-image-picker": "~15.0.5",
|
||||||
|
"expo-sharing": "~12.0.1",
|
||||||
"expo-status-bar": "~1.12.1",
|
"expo-status-bar": "~1.12.1",
|
||||||
"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-native-webview": "^13.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 +5535,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 +5727,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",
|
||||||
@@ -7850,6 +7883,14 @@
|
|||||||
"invariant": "^2.2.4"
|
"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": {
|
"node_modules/expo-status-bar": {
|
||||||
"version": "1.12.1",
|
"version": "1.12.1",
|
||||||
"resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-1.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-1.12.1.tgz",
|
||||||
@@ -8601,6 +8642,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",
|
||||||
@@ -12230,6 +12280,27 @@
|
|||||||
"react-native": "*"
|
"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": {
|
"node_modules/react-native/node_modules/ansi-styles": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
@@ -12315,6 +12386,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 +12469,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 +12611,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 +14052,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",
|
||||||
|
|||||||
@@ -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,11 @@
|
|||||||
"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-native-webview": "^13.10.4",
|
||||||
|
"react-redux": "^9.1.2",
|
||||||
|
"redux": "^5.0.1",
|
||||||
|
"expo-sharing": "~12.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.20.0",
|
"@babel/core": "^7.20.0",
|
||||||
|
|||||||
30
reducers/qrCodesReducer.ts
Normal file
30
reducers/qrCodesReducer.ts
Normal 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;
|
||||||
@@ -1,78 +1,84 @@
|
|||||||
import React, { useContext, useState } from 'react';
|
import React, { useContext, useState, useEffect } from 'react';
|
||||||
import { View, Text, StyleSheet, FlatList, TouchableOpacity, Image, 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);
|
||||||
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);
|
||||||
|
|
||||||
const toggleBookmark = (index: number) => {
|
useEffect(() => {
|
||||||
setQrCodes((prev: QRCode[]) => {
|
const backAction = () => {
|
||||||
const originalIndex = prev.length - 1 - index; // Compute the original index
|
if (selectedData) {
|
||||||
const newQrCodes = [...prev];
|
setSelectedData(null);
|
||||||
newQrCodes[originalIndex].bookmarked = !newQrCodes[originalIndex].bookmarked;
|
setSelectedScanResult(null);
|
||||||
return newQrCodes;
|
setSelectedType(null);
|
||||||
});
|
return true;
|
||||||
};
|
|
||||||
|
|
||||||
const deleteQRCode = () => {
|
|
||||||
if (indexToDelete !== null) {
|
|
||||||
setQrCodes((prev: QRCode[]) => {
|
|
||||||
const originalIndex = prev.length - 1 - indexToDelete; // Compute the original index
|
|
||||||
return prev.filter((_, i) => i !== originalIndex);
|
|
||||||
});
|
|
||||||
setIndexToDelete(null);
|
|
||||||
setIsModalVisible(false);
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredQrCodes = (showBookmarks ? qrCodes.filter(qr => qr.bookmarked) : qrCodes.slice().reverse());
|
const backHandler = BackHandler.addEventListener('hardwareBackPress', backAction);
|
||||||
|
|
||||||
const handleItemPress = (item: any) => {
|
return () => backHandler.remove();
|
||||||
|
}, [selectedData]);
|
||||||
|
|
||||||
|
const filteredQrCodes = showBookmarks ? qrCodes.filter(qr => qr.bookmarked) : qrCodes.slice().reverse();
|
||||||
|
|
||||||
|
const handleItemPress = (item: QRCode) => {
|
||||||
setSelectedData(item.data);
|
setSelectedData(item.data);
|
||||||
setSelectedScanResult(item.scanResult);
|
setSelectedScanResult(item.scanResult);
|
||||||
|
setSelectedType(item.type);
|
||||||
|
console.log('Selected QR code data:', item.data);
|
||||||
|
console.log('Selected QR code type:', item.type);
|
||||||
};
|
};
|
||||||
|
|
||||||
const confirmDelete = (index: number) => {
|
const confirmDelete = (index: number) => {
|
||||||
setIndexToDelete(index);
|
setIndexToDelete(index);
|
||||||
setIsModalVisible(true);
|
setIsModalVisible(true);
|
||||||
|
console.log('Confirm delete for QR code at index:', index);
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearSelectedData = () => {
|
const clearSelectedData = () => {
|
||||||
setSelectedData(null);
|
setSelectedData(null);
|
||||||
setSelectedScanResult(null);
|
setSelectedScanResult(null);
|
||||||
|
setSelectedType(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
|
{/* Header for toggling between History and Bookmarks */}
|
||||||
<View style={styles.headerContainer}>
|
<View style={styles.headerContainer}>
|
||||||
<TouchableOpacity onPress={() => setShowBookmarks(false)}>
|
<TouchableOpacity onPress={() => { 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={() => setShowBookmarks(true)}>
|
<TouchableOpacity onPress={() => { 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>
|
||||||
|
{/* Display scanned data details */}
|
||||||
{selectedData && (
|
{selectedData && (
|
||||||
<View style={styles.scannedDataBoxContainer}>
|
<View style={styles.scannedDataBoxContainer}>
|
||||||
<ScannedDataBox data={selectedData} scanResult={selectedScanResult} dataType="URL" clearScanData={clearSelectedData} />
|
<ScannedDataBox data={selectedData} scanResult={selectedScanResult} dataType={selectedType} clearScanData={clearSelectedData} />
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
{/* List of QR codes */}
|
||||||
<FlatList
|
<FlatList
|
||||||
data={filteredQrCodes}
|
data={filteredQrCodes}
|
||||||
renderItem={({ item, index }) => {
|
renderItem={({ item, index }) => {
|
||||||
console.log('item:', item); // Log the item data for debugging
|
console.log('Rendering QR code item:', item);
|
||||||
const itemData = item.data ? item.data.split('\n')[1]?.split('Data: ')[1] : 'Invalid data';
|
const itemData = item.data;
|
||||||
return (
|
return (
|
||||||
<View style={styles.itemContainer}>
|
<View style={styles.itemContainer}>
|
||||||
<View style={styles.itemLeft}>
|
<View style={styles.itemLeft}>
|
||||||
@@ -85,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)}>
|
||||||
@@ -98,6 +104,7 @@ const HistoryScreen: React.FC = () => {
|
|||||||
keyExtractor={(item, index) => index.toString()}
|
keyExtractor={(item, index) => index.toString()}
|
||||||
contentContainerStyle={styles.flatListContent}
|
contentContainerStyle={styles.flatListContent}
|
||||||
/>
|
/>
|
||||||
|
{/* Modal for delete confirmation */}
|
||||||
<Modal
|
<Modal
|
||||||
transparent={true}
|
transparent={true}
|
||||||
visible={isModalVisible}
|
visible={isModalVisible}
|
||||||
@@ -109,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)}>
|
||||||
|
|||||||
@@ -1,203 +1,142 @@
|
|||||||
import React, { useState, useEffect, useContext } from 'react';
|
import React, { useState, useEffect, useContext } from 'react';
|
||||||
import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert, Image, BackHandler } from 'react-native';
|
import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert, Image } from 'react-native';
|
||||||
import { Camera, CameraView, scanFromURLAsync } from 'expo-camera';
|
import { Camera, CameraView, scanFromURLAsync } from 'expo-camera';
|
||||||
import { QRCodeContext } from '../types';
|
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 QRCode from 'react-native-qrcode-svg';
|
|
||||||
import ScannedDataBox from '../components/ScannedDataBox';
|
import ScannedDataBox from '../components/ScannedDataBox';
|
||||||
|
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
|
||||||
//-----------------FUNCTIONS DECLARED HERE------------------//
|
const QRScannerScreen: React.FC<{ clearScanData: () => void }> = ({ clearScanData }) => {
|
||||||
// Function to determine the type of data
|
|
||||||
const determineDataType = (data: string): string => {
|
|
||||||
if (/^(http|https):\/\//.test(data)) {
|
|
||||||
return 'URL';
|
|
||||||
} else if (/^[0-9]+$/.test(data)) {
|
|
||||||
return 'Number';
|
|
||||||
} else if (/^mailto:/.test(data)) {
|
|
||||||
return 'Email';
|
|
||||||
} else if (/^tel:/.test(data)) {
|
|
||||||
return 'Phone Number';
|
|
||||||
} else if (/^smsto:/.test(data)) {
|
|
||||||
return 'SMS';
|
|
||||||
} else {
|
|
||||||
return 'Text';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function to handle VirusTotal scanning
|
|
||||||
const processWithVirusTotal = async (data: string) => {
|
|
||||||
const apiKey = '3566a17933bb36dd97cb35e84d0446e5ab8ad623e6de968d34b655c79485251e';
|
|
||||||
const url = 'https://www.virustotal.com/vtapi/v2/url/scan';
|
|
||||||
const params = {
|
|
||||||
apikey: apiKey,
|
|
||||||
url: data,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await axios.post(url, null, { params });
|
|
||||||
return response.data.scan_id;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error scanning with VirusTotal:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function to get VirusTotal scan results
|
|
||||||
const getVirusTotalResults = async (scanId: string) => {
|
|
||||||
const apiKey = '3566a17933bb36dd97cb35e84d0446e5ab8ad623e6de968d34b655c79485251e';
|
|
||||||
const url = 'https://www.virustotal.com/vtapi/v2/url/report';
|
|
||||||
const params = {
|
|
||||||
apikey: apiKey,
|
|
||||||
resource: scanId,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await axios.get(url, { params });
|
|
||||||
return response.data.positives;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting scan result:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Define the props for QRScannerScreen
|
|
||||||
interface QRScannerScreenProps {
|
|
||||||
clearScanData: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//-----------------Main------------------//
|
|
||||||
const QRScannerScreen: React.FC<QRScannerScreenProps> = ({ clearScanData }) => {
|
|
||||||
|
|
||||||
const navigation = useNavigation(); // call Navigation bar
|
const navigation = useNavigation(); // call Navigation bar
|
||||||
|
const dispatch = useDispatch(); // Use dispatch for Redux actions
|
||||||
|
|
||||||
const [showSplash, setShowSplash] = useState<boolean>(true); // call splash screen
|
const [showSplash, setShowSplash] = useState<boolean>(true); // call splash screen
|
||||||
|
const qrCodeContext = useContext(QRCodeContext); // From ./types.ts
|
||||||
// These are for .....
|
|
||||||
const qrCodeContext = useContext(QRCodeContext); // From ./tpes.ts
|
|
||||||
|
|
||||||
// this function stores the scanned data for the history page
|
|
||||||
const { qrCodes, setQrCodes } = qrCodeContext || { qrCodes: [], setQrCodes: () => {} };
|
const { qrCodes, setQrCodes } = qrCodeContext || { qrCodes: [], setQrCodes: () => {} };
|
||||||
|
|
||||||
// Camera permission and scan state
|
|
||||||
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);
|
||||||
|
|
||||||
|
|
||||||
const [scannedData, setScannedData] = useState<string>(''); // State for QR scanned Data
|
const [scannedData, setScannedData] = useState<string>(''); // State for QR scanned Data
|
||||||
const extractedData = scannedData.split('\n')[1]?.split('Data: ')[1] || ''; // Split
|
|
||||||
|
|
||||||
|
|
||||||
const [scanResult, setScanResult] = useState<any>(null); // State for VirusTotal scan result
|
|
||||||
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
|
||||||
|
|
||||||
|
// Add state variables for scan results
|
||||||
|
const [secureConnection, setSecureConnection] = useState<boolean | null>(null);
|
||||||
|
const [virusTotalCheck, setVirusTotalCheck] = useState<boolean | null>(null);
|
||||||
|
const [redirects, setRedirects] = useState<number | null>(null);
|
||||||
|
|
||||||
|
// 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();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// If loading this screen , reset the scanning data
|
// Clear Scan Data
|
||||||
const clearScanDataInternal = () => {
|
const clearScanDataInternal = () => {
|
||||||
setScannedData('');
|
setScannedData('');
|
||||||
setScanResult(null);
|
|
||||||
setScanned(false);
|
setScanned(false);
|
||||||
setDataType('');
|
setDataType('');
|
||||||
|
console.log("Scan data cleared");
|
||||||
};
|
};
|
||||||
|
|
||||||
// The function takes data from Cameraview.onBarcodeScanned
|
// Handle QR Code Payload
|
||||||
const handleQRCodeScanned = async ({ data }: { type: string; data: string }) => {
|
const handlePayload = async (payload: string) => {
|
||||||
setScanned(true); //Flag is QR code already scanned
|
setScanned(true);
|
||||||
|
console.log("Scanning Completed. Payload is:", payload);
|
||||||
|
|
||||||
const dataType = determineDataType(data);
|
const type = await detectQRCodeType(payload);
|
||||||
setDataType(dataType);
|
const secureConnectionResult = await verifyURL(payload);
|
||||||
|
const redirectResult = await checkRedirects(payload);
|
||||||
|
|
||||||
let newScannedData = `Type: ${dataType}\nData: ${data}`;
|
setSecureConnection(secureConnectionResult.isSecure);
|
||||||
|
setVirusTotalCheck(!secureConnectionResult.isMalicious); // Assuming you have virusTotalCheck logic integrated here
|
||||||
let scanResult = {
|
setRedirects(redirectResult.redirects);
|
||||||
secureConnection: false,
|
|
||||||
virusTotalCheck: false,
|
|
||||||
redirects: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const scanId = await processWithVirusTotal(data);
|
|
||||||
const positive = await getVirusTotalResults(scanId);
|
|
||||||
newScannedData += `\nScore: ${positive}`;
|
|
||||||
scanResult = {
|
|
||||||
secureConnection: true, // Assume secure connection if we get here
|
|
||||||
virusTotalCheck: positive === 0, // Safe if no positive results
|
|
||||||
redirects: 2 // Arbitrary value, replace with real data if available
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error handling barcode scan:', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const qrCode = {
|
const qrCode = {
|
||||||
data: newScannedData,
|
data: payload,
|
||||||
bookmarked: false,
|
type,
|
||||||
scanResult
|
scanResult: {
|
||||||
|
secureConnection: secureConnectionResult.isSecure,
|
||||||
|
virusTotalCheck: !secureConnectionResult.isMalicious,
|
||||||
|
redirects: redirectResult.redirects
|
||||||
|
},
|
||||||
|
bookmarked: false // by default
|
||||||
};
|
};
|
||||||
|
|
||||||
setScannedData(newScannedData);
|
setScannedData(payload);
|
||||||
setQrCodes([...qrCodes, qrCode]);
|
console.log("Payload received:", payload);
|
||||||
|
console.log("Type received from server:", type);
|
||||||
|
setDataType(type);
|
||||||
|
dispatch(addQRCode(qrCode)); // Dispatch action to save QR code data
|
||||||
|
console.log("QR code data added to history");
|
||||||
};
|
};
|
||||||
|
|
||||||
// If the focus is lost focus on this screen , when come reset the scan data
|
// Send QR Code Data to Backend Server
|
||||||
useEffect(() => {
|
const sendToAPIServer = async (payload: string): Promise<string> => {
|
||||||
const unsubscribe = navigation.addListener('focus', () => {
|
console.log('Sending QR code data to backend:', payload);
|
||||||
clearScanDataInternal();
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post('http://192.168.10.247:8080/v1/api/qrcodetypes/detect', {
|
||||||
|
data: payload,
|
||||||
|
}, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
return unsubscribe;
|
console.log('Response from backend:', response.data);
|
||||||
}, [navigation]);
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error detecting QR code type:', error);
|
||||||
|
return 'UNKNOWN';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// This function is for toggling torch
|
// Toggle Torch (Flashlight)
|
||||||
const toggleTorch = () => {
|
const toggleTorch = () => {
|
||||||
setEnableTorch((prev) => !prev);
|
setEnableTorch((prev) => !prev);
|
||||||
|
console.log("Torch toggled:", enableTorch ? "off" : "on");
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is hardcoded function for testing
|
// Handle Test Scan
|
||||||
const handleTestScan = () => {
|
const handleTestScan = () => {
|
||||||
handleQRCodeScanned({ type: 'TEST', data: 'TEST123' });
|
handlePayload('TEST123');
|
||||||
|
console.log("Test scan executed");
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://medium.com/@funti009/create-a-mobile-qr-scanner-that-scans-via-camera-and-image-in-the-gallery-react-native-expo-ee7098a265d7
|
// Read QR Code from Image
|
||||||
// Refactored to use Camera.scanFromURLAsync instead
|
const readQRFromImage = async () => {
|
||||||
// Function to handle QR code scanning from the image picker
|
|
||||||
const handleImagePicker = async () => {
|
|
||||||
clearScanDataInternal();
|
clearScanDataInternal();
|
||||||
|
console.log("Reading QR code from image");
|
||||||
|
|
||||||
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
|
||||||
quality: 1,
|
quality: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
// if (result && result.assets[0].uri) { // KIV this....
|
if (result && result.assets && result.assets.length > 0 && result.assets[0].uri) { // Ensure the uri is not empty
|
||||||
if (result && result.assets && result.assets.length > 0 && result.assets[0].uri) { // this is to unsure the uri is not empty
|
|
||||||
try {
|
try {
|
||||||
const scannedResult = await scanFromURLAsync(result.assets[0].uri);
|
const scannedResult = await scanFromURLAsync(result.assets[0].uri);
|
||||||
if (!scannedResult.data) { // This will check if no QR was scanned
|
if (scannedResult && scannedResult[0] && scannedResult[0].data) {
|
||||||
// Not sure why by passing the scannedResults.data is not working , only works when I use scannedResults[0].data..... KIV >.<
|
handlePayload(scannedResult[0].data);
|
||||||
const dataNeeded = scannedResult[0].data;
|
// Not sure why scannedResult.data is undefined but access as array work, KIV
|
||||||
handleQRCodeScanned({ type: 'QR_CODE', data: dataNeeded });
|
console.log('QR code data from image:', 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);
|
||||||
@@ -206,18 +145,15 @@ const QRScannerScreen: React.FC<QRScannerScreenProps> = ({ clearScanData }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add back button handler to clear scanned data
|
// Clear scan data when screen is focused
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const backHandler = BackHandler.addEventListener('hardwareBackPress', () => {
|
const unsubscribe = navigation.addListener('focus', () => {
|
||||||
clearScanDataInternal();
|
clearScanDataInternal();
|
||||||
return true;
|
console.log("Screen focused, scan data cleared");
|
||||||
});
|
});
|
||||||
|
return unsubscribe;
|
||||||
|
}, [navigation]);
|
||||||
|
|
||||||
return () => backHandler.remove();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// For Splash, for some reason need to be near the end of the function...
|
|
||||||
// or else permission for camera is not asked
|
|
||||||
if (showSplash) {
|
if (showSplash) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.splashContainer}>
|
<View style={styles.splashContainer}>
|
||||||
@@ -226,55 +162,52 @@ const QRScannerScreen: React.FC<QRScannerScreenProps> = ({ clearScanData }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// While asking for permission the page behind will render this only
|
|
||||||
if (hasPermission === null) {
|
if (hasPermission === null) {
|
||||||
return <Text>Requesting for camera permission</Text>;
|
return <Text>Requesting for camera permission</Text>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this will thrown on the screen and nothing else will load if no permission
|
|
||||||
if (hasPermission === false) {
|
if (hasPermission === false) {
|
||||||
return <Text>No access to camera</Text>;
|
return <Text>No access to camera</Text>;
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------The UI part-----------------//
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
{/* Banner section */}
|
|
||||||
<View style={styles.banner}>
|
<View style={styles.banner}>
|
||||||
<Text style={styles.headerText}>SafeQR v0.89</Text>
|
<Text style={styles.headerText}>SafeQR v0.89</Text>
|
||||||
</View>
|
</View>
|
||||||
{/* Welcome Text */}
|
|
||||||
<Text style={styles.welcomeText}>Welcome to SafeQR code Scanner</Text>
|
<Text style={styles.welcomeText}>Welcome to SafeQR code Scanner</Text>
|
||||||
|
|
||||||
{/* The cutout for the camera */}
|
|
||||||
<View style={styles.cameraContainer}>
|
<View style={styles.cameraContainer}>
|
||||||
<CameraView
|
<CameraView
|
||||||
onBarcodeScanned={scanned ? undefined : handleQRCodeScanned}
|
onBarcodeScanned={scanned ? undefined : ({ data }) => handlePayload(data)}
|
||||||
barcodeScannerSettings={{ barcodeTypes: ['qr', 'pdf417'] }}
|
barcodeScannerSettings={{ barcodeTypes: ['qr', 'pdf417'] }}
|
||||||
style={styles.camera}
|
style={styles.camera}
|
||||||
enableTorch={enableTorch}
|
enableTorch={enableTorch}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* the torch icon floating above the CameraView */}
|
|
||||||
<TouchableOpacity onPress={toggleTorch} style={styles.flashButton}>
|
<TouchableOpacity onPress={toggleTorch} style={styles.flashButton}>
|
||||||
<Ionicons name="flashlight" size={24} color="#fff" />
|
<Ionicons name="flashlight" size={24} color="#fff" />
|
||||||
{enableTorch}
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<TouchableOpacity onPress={handleTestScan} style={styles.testButton}>
|
<TouchableOpacity onPress={handleTestScan} style={styles.testButton}>
|
||||||
<Ionicons name="bug" size={24} color="#fff" />
|
<Ionicons name="bug" size={24} color="#fff" />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity onPress={readQRFromImage} style={styles.galleryButton}>
|
||||||
{/* the image icon for opening album/gallery */}
|
|
||||||
<TouchableOpacity onPress={handleImagePicker} style={styles.galleryButton}>
|
|
||||||
<Ionicons name="image" size={24} color="#fff" />
|
<Ionicons name="image" size={24} color="#fff" />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* The CONTENT , the popup for the scanned data */}
|
|
||||||
{/* This is called from ../components/ScannedDataBox*/}
|
|
||||||
{scannedData !== '' && (
|
{scannedData !== '' && (
|
||||||
<View style={styles.scannedDataBox}>
|
<View style={styles.scannedDataBox}>
|
||||||
<ScannedDataBox data={scannedData} scanResult={scanResult} dataType={dataType} clearScanData={clearScanDataInternal} />
|
<ScannedDataBox
|
||||||
|
data={scannedData}
|
||||||
|
dataType={dataType}
|
||||||
|
clearScanData={clearScanDataInternal}
|
||||||
|
scanResult={{
|
||||||
|
secureConnection,
|
||||||
|
virusTotalCheck,
|
||||||
|
redirects
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
@@ -346,10 +279,8 @@ const styles = StyleSheet.create({
|
|||||||
scannedDataBox: {
|
scannedDataBox: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: '10%',
|
top: '10%',
|
||||||
height: '100%',
|
|
||||||
left: '5%',
|
left: '5%',
|
||||||
right: '5%',
|
right: '5%',
|
||||||
|
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
},
|
},
|
||||||
welcomeText: {
|
welcomeText: {
|
||||||
|
|||||||
11
store.ts
Normal file
11
store.ts
Normal 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;
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
{
|
{
|
||||||
"extends": "expo/tsconfig.base",
|
"compilerOptions": {},
|
||||||
"compilerOptions": {
|
"extends": "expo/tsconfig.base"
|
||||||
"strict": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
14
types.ts
14
types.ts
@@ -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);
|
|
||||||
Reference in New Issue
Block a user