diff --git a/App.tsx b/App.tsx index 1794a3b..f8a190c 100644 --- a/App.tsx +++ b/App.tsx @@ -1,14 +1,14 @@ import { - StatusBar, - useColorScheme, - View, - StyleSheet, - Text, - TouchableOpacity, - Alert, - NativeModules, - Image, - ScrollView, + StatusBar, + useColorScheme, + View, + StyleSheet, + Text, + TouchableOpacity, + Alert, + NativeModules, + Image, + ScrollView, } from 'react-native'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { useState, useEffect } from 'react'; @@ -18,241 +18,258 @@ import Register from './src/components/Register'; import { authAPI } from './src/services/authAPI'; import { networkService } from './src/services/networkService'; import { ScannerScreen } from './src/screens/ScannerScreen'; +import Map from './src/screens/Map'; -type Screen = 'login' | 'register' | 'home' | 'scanner'; +type Screen = 'login' | 'register' | 'home' | 'scanner' | 'map'; const { MyNativeModule } = NativeModules; function App() { - const isDarkMode = useColorScheme() === 'dark'; + const isDarkMode = useColorScheme() === 'dark'; - const [currentScreen, setCurrentScreen] = useState('login'); - const [isInitialized, setIsInitialized] = useState(false); - const [isOnline, setIsOnline] = useState(true); - const [currentUser, setCurrentUser] = useState(null); - const [qrCode, setQrCode] = useState(null); - const [isGeneratingQR, setIsGeneratingQR] = useState(false); - const [scannedCodes, setScannedCodes] = useState([]); + const [currentScreen, setCurrentScreen] = useState('login'); + const [isInitialized, setIsInitialized] = useState(false); + const [isOnline, setIsOnline] = useState(true); + const [currentUser, setCurrentUser] = useState(null); + const [qrCode, setQrCode] = useState(null); + const [isGeneratingQR, setIsGeneratingQR] = useState(false); + const [scannedCodes, setScannedCodes] = useState([]); - /* -------------------- INIT -------------------- */ - useEffect(() => { - initializeApp(); + /* -------------------- INIT -------------------- */ + useEffect(() => { + initializeApp(); - console.log('MyNativeModule:', MyNativeModule); - MyNativeModule?.greet?.('John').then((msg: any) => { - console.log(msg); - }); + console.log('MyNativeModule:', MyNativeModule); + MyNativeModule?.greet?.('John').then((msg: any) => { + console.log(msg); + }); - const unsubscribe = networkService.addListener(networkState => { - setIsOnline(networkState.isConnected); - }); + const unsubscribe = networkService.addListener(networkState => { + setIsOnline(networkState.isConnected); + }); - return unsubscribe; - }, []); + return unsubscribe; + }, []); - const initializeApp = async () => { - try { - await authAPI.initialize(); + const initializeApp = async () => { + try { + await authAPI.initialize(); - const isLoggedIn = await authAPI.isLoggedIn(); - if (isLoggedIn) { - const user = await authAPI.getCurrentUser(); - setCurrentUser(user); - setCurrentScreen('home'); - } - } catch (error) { - console.error('Initialization error:', error); - } finally { - setIsInitialized(true); - } - }; + const isLoggedIn = await authAPI.isLoggedIn(); + if (isLoggedIn) { + const user = await authAPI.getCurrentUser(); + setCurrentUser(user); + setCurrentScreen('home'); + } + } catch (error) { + console.error('Initialization error:', error); + } finally { + setIsInitialized(true); + } + }; - /* -------------------- NAVIGATION -------------------- */ - const navigateToLogin = () => { - setCurrentUser(null); - setQrCode(null); - setCurrentScreen('login'); - }; + /* -------------------- NAVIGATION -------------------- */ + const navigateToLogin = () => { + setCurrentUser(null); + setQrCode(null); + setCurrentScreen('login'); + }; - const navigateToRegister = () => { - setCurrentScreen('register'); - }; + const navigateToRegister = () => { + setCurrentScreen('register'); + }; - const navigateToHome = async () => { - const user = await authAPI.getCurrentUser(); - setCurrentUser(user); - setQrCode(null); - setCurrentScreen('home'); - }; + const navigateToHome = async () => { + const user = await authAPI.getCurrentUser(); + setCurrentUser(user); + setQrCode(null); + setCurrentScreen('home'); + }; - const openScanner = () => { - setCurrentScreen('scanner'); - }; + const openScanner = () => { + setCurrentScreen('scanner'); + }; - /* -------------------- ACTIONS -------------------- */ - const handleLogout = async () => { - try { - await authAPI.logout(); - setQrCode(null); - navigateToLogin(); - Alert.alert('Success', 'Logged out successfully'); - } catch { - Alert.alert('Error', 'Failed to logout'); - } - }; + const openMap = () => { + setCurrentScreen('map'); + }; - const showAppStatus = async () => { - try { - const status = await authAPI.getAppStatus(); - Alert.alert( - 'App Status', - `Network: ${status.network.isOnline ? 'Online' : 'Offline'} + /* -------------------- ACTIONS -------------------- */ + const handleLogout = async () => { + try { + await authAPI.logout(); + setQrCode(null); + navigateToLogin(); + Alert.alert('Success', 'Logged out successfully'); + } catch { + Alert.alert('Error', 'Failed to logout'); + } + }; + + const showAppStatus = async () => { + try { + const status = await authAPI.getAppStatus(); + Alert.alert( + 'App Status', + `Network: ${status.network.isOnline ? 'Online' : 'Offline'} Users: ${status.storage.totalUsers} Current User: ${status.authentication.currentUser || 'None'} Logged In: ${status.authentication.isLoggedIn ? 'Yes' : 'No'}` - ); - } catch { - Alert.alert('Error', 'Failed to get app status'); - } - }; + ); + } catch { + Alert.alert('Error', 'Failed to get app status'); + } + }; - const generateQRCode = async () => { - if (!currentUser?.email) { - Alert.alert('Error', 'No user email available'); - return; - } + const generateQRCode = async () => { + if (!currentUser?.email) { + Alert.alert('Error', 'No user email available'); + return; + } - setIsGeneratingQR(true); - try { - const qrData = `User: ${currentUser.fullName}\nEmail: ${currentUser.email}`; - const base64Image = await MyNativeModule.generateQRCode(qrData, 300, 300); - setQrCode(base64Image); - } catch (error) { - console.error(error); - Alert.alert('Error', 'Failed to generate QR code'); - } finally { - setIsGeneratingQR(false); - } - }; + setIsGeneratingQR(true); + try { + const qrData = `User: ${currentUser.fullName}\nEmail: ${currentUser.email}`; + const base64Image = await MyNativeModule.generateQRCode(qrData, 300, 300); + setQrCode(base64Image); + } catch (error) { + console.error(error); + Alert.alert('Error', 'Failed to generate QR code'); + } finally { + setIsGeneratingQR(false); + } + }; - const handleScanResult = (result: any) => { - setScannedCodes(prev => [ - { - code: result.code, - format: result.format, - timestamp: new Date().toLocaleTimeString(), - }, - ...prev, - ]); + const handleScanResult = (result: any) => { + setScannedCodes(prev => [ + { + code: result.code, + format: result.format, + timestamp: new Date().toLocaleTimeString(), + }, + ...prev, + ]); - Alert.alert( - 'Scanned Successfully', - `Code: ${result.code}\nFormat: ${result.format}`, - [{ text: 'OK', onPress: () => setCurrentScreen('home') }] - ); - }; + Alert.alert( + 'Scanned Successfully', + `Code: ${result.code}\nFormat: ${result.format}`, + [{ text: 'OK', onPress: () => setCurrentScreen('home') }] + ); + }; - /* -------------------- RENDER -------------------- */ - const renderScreen = () => { - switch (currentScreen) { - case 'login': - return ( - - ); + /* -------------------- RENDER -------------------- */ + const renderScreen = () => { + switch (currentScreen) { + case 'login': + return ( + + ); - case 'register': - return ( - - ); + case 'register': + return ( + + ); - case 'scanner': - return ( - - ); + case 'scanner': + return ( + + ); - case 'home': - return ( - - - Welcome, { currentUser?.fullName || 'User' -} - + case 'home': + return ( + + + Welcome, {currentUser?.fullName || 'User' + } + -{ - qrCode && ( - - ) -} + { + qrCode && ( + + ) + } - - Scan QR / Barcode - + + Scan QR / Barcode + - < TouchableOpacity -style = { styles.qrButton } -onPress = { generateQRCode } -disabled = { isGeneratingQR } - > - - { isGeneratingQR? 'Generating...': 'Generate QR Code' } - - + < TouchableOpacity + style={styles.qrButton} + onPress={generateQRCode} + disabled={isGeneratingQR} + > + + {isGeneratingQR ? 'Generating...' : 'Generate QR Code'} + + - < TouchableOpacity style = { styles.statusButton } onPress = { showAppStatus } > - App Status - + < TouchableOpacity style={styles.statusButton} onPress={showAppStatus} > + App Status + - < TouchableOpacity style = { styles.logoutButton } onPress = { handleLogout } > - Logout - - - ); + < TouchableOpacity style={styles.mapButton} onPress={openMap} > + Open Map + - default: -return null; - } -}; + < TouchableOpacity style={styles.logoutButton} onPress={handleLogout} > + Logout + + + ); -/* -------------------- LOADING -------------------- */ -if (!isInitialized) { - return ( - - - Initializing... - - - ); -} + case 'map': + return ( + + setCurrentScreen('home')} /> + + ); -return ( - - - { renderScreen() } - -); + default: + return null; + } + }; + + /* -------------------- LOADING -------------------- */ + if (!isInitialized) { + return ( + + + Initializing... + + + ); + } + + return ( + + + {renderScreen()} + + ); } const styles = StyleSheet.create({ - loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center' }, - loadingText: { fontSize: 18, fontWeight: '600' }, - homeContainer: { flex: 1 }, - scrollContent: { alignItems: 'center', padding: 20 }, - welcomeText: { fontSize: 24, fontWeight: '700', marginBottom: 20 }, - qrImage: { width: 300, height: 300, marginBottom: 20 }, - scannerButton: { backgroundColor: '#9333ea', padding: 14, borderRadius: 20, marginBottom: 10 }, - qrButton: { backgroundColor: '#10b981', padding: 14, borderRadius: 20, marginBottom: 10 }, - statusButton: { backgroundColor: '#3bb6d8', padding: 14, borderRadius: 20, marginBottom: 10 }, - logoutButton: { backgroundColor: '#ff6b6b', padding: 14, borderRadius: 20 }, - buttonText: { color: '#fff', fontWeight: '600' }, + loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center' }, + loadingText: { fontSize: 18, fontWeight: '600' }, + homeContainer: { flex: 1 }, + scrollContent: { alignItems: 'center', padding: 20 }, + welcomeText: { fontSize: 24, fontWeight: '700', marginBottom: 20 }, + qrImage: { width: 300, height: 300, marginBottom: 20 }, + scannerButton: { backgroundColor: '#9333ea', padding: 14, borderRadius: 20, marginBottom: 10 }, + qrButton: { backgroundColor: '#10b981', padding: 14, borderRadius: 20, marginBottom: 10 }, + statusButton: { backgroundColor: '#3bb6d8', padding: 14, borderRadius: 20, marginBottom: 10 }, + mapButton: { backgroundColor: '#2563eb', padding: 14, borderRadius: 20, marginBottom: 10 }, + logoutButton: { backgroundColor: '#ff6b6b', padding: 14, borderRadius: 20 }, + buttonText: { color: '#fff', fontWeight: '600' }, }); export default App; diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index fb78f39..1a78b88 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,8 @@ + + NSLocationWhenInUseUsageDescription - + Used to show your current location on the map. RCTNewArchEnabled UIAppFonts diff --git a/src/screens/Map.tsx b/src/screens/Map.tsx index d680a10..e94bbaf 100644 --- a/src/screens/Map.tsx +++ b/src/screens/Map.tsx @@ -1,72 +1,147 @@ -import React, { useEffect } from 'react'; -import { View, Text, Platform, Alert, Linking } from 'react-native'; +import React, { useEffect, useState } from 'react'; +import { View, Text, Platform, Alert, Linking, StyleSheet, TouchableOpacity, ActivityIndicator } from 'react-native'; import { check, PERMISSIONS, request, RESULTS } from 'react-native-permissions'; +import MapView, { Marker, PROVIDER_GOOGLE, Region } from 'react-native-maps'; +import Geolocation from '@react-native-community/geolocation'; -export default function Map() { +type Props = { onClose?: () => void }; - useEffect(() => { - const init = async () => { - const hasPermission = await requestLocationPermission(); - if (hasPermission) { - console.log('Permission granted'); - } else { - console.log('Permission denied'); - } - }; - init(); - }, []); +export default function Map({ onClose }: Props) { + const [region, setRegion] = useState(null); + const [loading, setLoading] = useState(true); + const [permissionStatus, setPermissionStatus] = useState('loading'); - async function requestLocationPermission(): Promise { - try { - const permission = Platform.select({ - - android: PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION, - ios: PERMISSIONS.IOS.LOCATION_WHEN_IN_USE, - default: PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION, - }); - - const status = await check(permission); - if (status === RESULTS.GRANTED) { - console.log('Location permission already granted'); - return true; - } - - if (status === RESULTS.UNAVAILABLE) { - console.log('Location permission not available on this device'); - return false; - } - - const result = await request(permission); - if (result === RESULTS.GRANTED) { - console.log('Location permission granted'); - return true; - } else if (result === RESULTS.DENIED) { - console.log('Location permission denied'); - return false; - } else if (result === RESULTS.BLOCKED) { - console.log('Location permission permanently denied'); - Alert.alert( - 'Location Permission Blocked', - 'Location permission is required to use this feature.', - [ - { text: 'Cancel', style: 'cancel' }, - { text: 'Open Settings', onPress: () => Linking.openSettings() }, - ], - { cancelable: true }, - ); - return false; - } - - return false; - } catch (error) { - console.error('Location permission error:', error); - return false; - } + useEffect(() => { + const init = async () => { + setLoading(true); + const status = await requestLocationPermission(); + if (status === 'granted') { + getCurrentLocation(); + } else { + setLoading(false); + } + }; + init(); + }, []); + + const getCurrentLocation = () => { + setLoading(true); + Geolocation.getCurrentPosition( + (pos) => { + const { latitude, longitude } = pos.coords; + setRegion({ latitude, longitude, latitudeDelta: 0.01, longitudeDelta: 0.01 }); + setLoading(false); + }, + (err) => { + console.error(err); + Alert.alert('Error', 'Failed to get current location'); + setLoading(false); + }, + { enableHighAccuracy: true, timeout: 15000, maximumAge: 10000 } + ); + }; + + async function requestLocationPermission(): Promise<'granted' | 'denied' | 'blocked' | 'unavailable'> { + try { + const permission = Platform.select({ + android: PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION, + ios: PERMISSIONS.IOS.LOCATION_WHEN_IN_USE, + default: PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION, + }); + + const status = await check(permission); + if (status === RESULTS.GRANTED) { + setPermissionStatus('granted'); + return 'granted'; } - return ( - - Map + if (status === RESULTS.UNAVAILABLE) { + setPermissionStatus('unavailable'); + return 'unavailable'; + } + + const result = await request(permission); + if (result === RESULTS.GRANTED) { + setPermissionStatus('granted'); + return 'granted'; + } else if (result === RESULTS.DENIED) { + setPermissionStatus('denied'); + return 'denied'; + } else if (result === RESULTS.BLOCKED) { + setPermissionStatus('blocked'); + Alert.alert( + 'Location Permission Blocked', + 'Location permission is required to use this feature.', + [ + { text: 'Cancel', style: 'cancel' }, + { text: 'Open Settings', onPress: () => Linking.openSettings() }, + ], + { cancelable: true }, + ); + return 'blocked'; + } + + setPermissionStatus('unavailable'); + return 'unavailable'; + } catch (error) { + console.error('Location permission error:', error); + setPermissionStatus('unavailable'); + return 'unavailable'; + } + } + + const openSettings = () => Linking.openSettings(); + const retryPermission = async () => { + setLoading(true); + const res = await requestLocationPermission(); + if (res === 'granted') getCurrentLocation(); + else setLoading(false); + }; + + const retryGetLocation = () => { + if (permissionStatus === 'granted') getCurrentLocation(); + else retryPermission(); + }; + + return ( + + + Back + + + {loading ? ( + + ) : permissionStatus === 'blocked' ? ( + + Location permission is blocked. Open settings to enable it. + Open Settings + Retry - ); -} \ No newline at end of file + ) : permissionStatus === 'denied' || permissionStatus === 'unavailable' ? ( + + Location permission is not available. Please allow location access. + Request Permission + + ) : region ? ( + + + + ) : ( + + No location available + Retry Location + + )} + + ); +} + +const styles = StyleSheet.create({ + container: { flex: 1 }, + map: { flex: 1 }, + closeButton: { position: 'absolute', top: 16, left: 16, zIndex: 10, padding: 8, backgroundColor: 'rgba(0,0,0,0.6)', borderRadius: 8 }, + closeText: { color: '#fff' }, + center: { flex: 1, justifyContent: 'center', alignItems: 'center' }, + actionButton: { backgroundColor: '#2563eb', padding: 12, borderRadius: 8, marginTop: 8 }, + actionText: { color: '#fff', fontWeight: '600' }, +}); \ No newline at end of file