Add Map screen with location permissions handling and integrate current location feature

This commit is contained in:
2025-12-24 23:49:57 +05:30
parent 48c266c252
commit 741ab60fce
4 changed files with 365 additions and 271 deletions

19
App.tsx
View File

@@ -18,8 +18,9 @@ import Register from './src/components/Register';
import { authAPI } from './src/services/authAPI'; import { authAPI } from './src/services/authAPI';
import { networkService } from './src/services/networkService'; import { networkService } from './src/services/networkService';
import { ScannerScreen } from './src/screens/ScannerScreen'; 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; const { MyNativeModule } = NativeModules;
@@ -89,6 +90,10 @@ function App() {
setCurrentScreen('scanner'); setCurrentScreen('scanner');
}; };
const openMap = () => {
setCurrentScreen('map');
};
/* -------------------- ACTIONS -------------------- */ /* -------------------- ACTIONS -------------------- */
const handleLogout = async () => { const handleLogout = async () => {
try { try {
@@ -211,12 +216,23 @@ disabled = { isGeneratingQR }
<Text style={styles.buttonText}> App Status </Text> <Text style={styles.buttonText}> App Status </Text>
</TouchableOpacity> </TouchableOpacity>
< TouchableOpacity style={styles.mapButton} onPress={openMap} >
<Text style={styles.buttonText}> Open Map </Text>
</TouchableOpacity>
< TouchableOpacity style={styles.logoutButton} onPress={handleLogout} > < TouchableOpacity style={styles.logoutButton} onPress={handleLogout} >
<Text style={styles.buttonText}> Logout </Text> <Text style={styles.buttonText}> Logout </Text>
</TouchableOpacity> </TouchableOpacity>
</ScrollView> </ScrollView>
); );
case 'map':
return (
<View style={{ flex: 1 }}>
<Map onClose={() => setCurrentScreen('home')} />
</View>
);
default: default:
return null; return null;
} }
@@ -251,6 +267,7 @@ const styles = StyleSheet.create({
scannerButton: { backgroundColor: '#9333ea', padding: 14, borderRadius: 20, marginBottom: 10 }, scannerButton: { backgroundColor: '#9333ea', padding: 14, borderRadius: 20, marginBottom: 10 },
qrButton: { backgroundColor: '#10b981', padding: 14, borderRadius: 20, marginBottom: 10 }, qrButton: { backgroundColor: '#10b981', padding: 14, borderRadius: 20, marginBottom: 10 },
statusButton: { backgroundColor: '#3bb6d8', 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 }, logoutButton: { backgroundColor: '#ff6b6b', padding: 14, borderRadius: 20 },
buttonText: { color: '#fff', fontWeight: '600' }, buttonText: { color: '#fff', fontWeight: '600' },
}); });

View File

@@ -1,6 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application <application
android:name=".MainApplication" android:name=".MainApplication"

View File

@@ -34,7 +34,7 @@
<true/> <true/>
</dict> </dict>
<key>NSLocationWhenInUseUsageDescription</key> <key>NSLocationWhenInUseUsageDescription</key>
<string></string> <string>Used to show your current location on the map.</string>
<key>RCTNewArchEnabled</key> <key>RCTNewArchEnabled</key>
<true/> <true/>
<key>UIAppFonts</key> <key>UIAppFonts</key>

View File

@@ -1,25 +1,49 @@
import React, { useEffect } from 'react'; import React, { useEffect, useState } from 'react';
import { View, Text, Platform, Alert, Linking } from 'react-native'; import { View, Text, Platform, Alert, Linking, StyleSheet, TouchableOpacity, ActivityIndicator } from 'react-native';
import { check, PERMISSIONS, request, RESULTS } from 'react-native-permissions'; 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 };
export default function Map({ onClose }: Props) {
const [region, setRegion] = useState<Region | null>(null);
const [loading, setLoading] = useState(true);
const [permissionStatus, setPermissionStatus] = useState<string>('loading');
useEffect(() => { useEffect(() => {
const init = async () => { const init = async () => {
const hasPermission = await requestLocationPermission(); setLoading(true);
if (hasPermission) { const status = await requestLocationPermission();
console.log('Permission granted'); if (status === 'granted') {
getCurrentLocation();
} else { } else {
console.log('Permission denied'); setLoading(false);
} }
}; };
init(); init();
}, []); }, []);
async function requestLocationPermission(): Promise<boolean> { 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 { try {
const permission = Platform.select({ const permission = Platform.select({
android: PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION, android: PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION,
ios: PERMISSIONS.IOS.LOCATION_WHEN_IN_USE, ios: PERMISSIONS.IOS.LOCATION_WHEN_IN_USE,
default: PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION, default: PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION,
@@ -27,24 +51,24 @@ export default function Map() {
const status = await check(permission); const status = await check(permission);
if (status === RESULTS.GRANTED) { if (status === RESULTS.GRANTED) {
console.log('Location permission already granted'); setPermissionStatus('granted');
return true; return 'granted';
} }
if (status === RESULTS.UNAVAILABLE) { if (status === RESULTS.UNAVAILABLE) {
console.log('Location permission not available on this device'); setPermissionStatus('unavailable');
return false; return 'unavailable';
} }
const result = await request(permission); const result = await request(permission);
if (result === RESULTS.GRANTED) { if (result === RESULTS.GRANTED) {
console.log('Location permission granted'); setPermissionStatus('granted');
return true; return 'granted';
} else if (result === RESULTS.DENIED) { } else if (result === RESULTS.DENIED) {
console.log('Location permission denied'); setPermissionStatus('denied');
return false; return 'denied';
} else if (result === RESULTS.BLOCKED) { } else if (result === RESULTS.BLOCKED) {
console.log('Location permission permanently denied'); setPermissionStatus('blocked');
Alert.alert( Alert.alert(
'Location Permission Blocked', 'Location Permission Blocked',
'Location permission is required to use this feature.', 'Location permission is required to use this feature.',
@@ -54,19 +78,70 @@ export default function Map() {
], ],
{ cancelable: true }, { cancelable: true },
); );
return false; return 'blocked';
} }
return false; setPermissionStatus('unavailable');
return 'unavailable';
} catch (error) { } catch (error) {
console.error('Location permission error:', error); console.error('Location permission error:', error);
return false; 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 ( return (
<View> <View style={styles.container}>
<Text>Map</Text> <TouchableOpacity style={styles.closeButton} onPress={onClose}>
<Text style={styles.closeText}>Back</Text>
</TouchableOpacity>
{loading ? (
<ActivityIndicator size="large" />
) : permissionStatus === 'blocked' ? (
<View style={styles.center}>
<Text style={{ marginBottom: 12 }}>Location permission is blocked. Open settings to enable it.</Text>
<TouchableOpacity style={styles.actionButton} onPress={openSettings}><Text style={styles.actionText}>Open Settings</Text></TouchableOpacity>
<TouchableOpacity style={styles.actionButton} onPress={retryPermission}><Text style={styles.actionText}>Retry</Text></TouchableOpacity>
</View>
) : permissionStatus === 'denied' || permissionStatus === 'unavailable' ? (
<View style={styles.center}>
<Text style={{ marginBottom: 12 }}>Location permission is not available. Please allow location access.</Text>
<TouchableOpacity style={styles.actionButton} onPress={retryPermission}><Text style={styles.actionText}>Request Permission</Text></TouchableOpacity>
</View>
) : region ? (
<MapView style={styles.map} provider={PROVIDER_GOOGLE} region={region} showsUserLocation={true} showsMyLocationButton={true}>
<Marker coordinate={{ latitude: region.latitude, longitude: region.longitude }} title="You are here" />
</MapView>
) : (
<View style={styles.center}>
<Text style={{ marginBottom: 12 }}>No location available</Text>
<TouchableOpacity style={styles.actionButton} onPress={retryGetLocation}><Text style={styles.actionText}>Retry Location</Text></TouchableOpacity>
</View>
)}
</View> </View>
); );
} }
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' },
});