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 { 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;
@@ -89,6 +90,10 @@ function App() {
setCurrentScreen('scanner');
};
const openMap = () => {
setCurrentScreen('map');
};
/* -------------------- ACTIONS -------------------- */
const handleLogout = async () => {
try {
@@ -211,12 +216,23 @@ disabled = { isGeneratingQR }
<Text style={styles.buttonText}> App Status </Text>
</TouchableOpacity>
< TouchableOpacity style={styles.mapButton} onPress={openMap} >
<Text style={styles.buttonText}> Open Map </Text>
</TouchableOpacity>
< TouchableOpacity style={styles.logoutButton} onPress={handleLogout} >
<Text style={styles.buttonText}> Logout </Text>
</TouchableOpacity>
</ScrollView>
);
case 'map':
return (
<View style={{ flex: 1 }}>
<Map onClose={() => setCurrentScreen('home')} />
</View>
);
default:
return null;
}
@@ -251,6 +267,7 @@ const styles = StyleSheet.create({
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' },
});

View File

@@ -1,6 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<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
android:name=".MainApplication"

View File

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

View File

@@ -1,25 +1,49 @@
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 };
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(() => {
const init = async () => {
const hasPermission = await requestLocationPermission();
if (hasPermission) {
console.log('Permission granted');
setLoading(true);
const status = await requestLocationPermission();
if (status === 'granted') {
getCurrentLocation();
} else {
console.log('Permission denied');
setLoading(false);
}
};
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 {
const permission = Platform.select({
android: PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION,
ios: PERMISSIONS.IOS.LOCATION_WHEN_IN_USE,
default: PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION,
@@ -27,24 +51,24 @@ export default function Map() {
const status = await check(permission);
if (status === RESULTS.GRANTED) {
console.log('Location permission already granted');
return true;
setPermissionStatus('granted');
return 'granted';
}
if (status === RESULTS.UNAVAILABLE) {
console.log('Location permission not available on this device');
return false;
setPermissionStatus('unavailable');
return 'unavailable';
}
const result = await request(permission);
if (result === RESULTS.GRANTED) {
console.log('Location permission granted');
return true;
setPermissionStatus('granted');
return 'granted';
} else if (result === RESULTS.DENIED) {
console.log('Location permission denied');
return false;
setPermissionStatus('denied');
return 'denied';
} else if (result === RESULTS.BLOCKED) {
console.log('Location permission permanently denied');
setPermissionStatus('blocked');
Alert.alert(
'Location Permission Blocked',
'Location permission is required to use this feature.',
@@ -54,19 +78,70 @@ export default function Map() {
],
{ cancelable: true },
);
return false;
return 'blocked';
}
return false;
setPermissionStatus('unavailable');
return 'unavailable';
} catch (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 (
<View>
<Text>Map</Text>
<View style={styles.container}>
<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>
);
}
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' },
});