Add Map screen with location permissions handling and integrate current location feature
This commit is contained in:
19
App.tsx
19
App.tsx
@@ -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' },
|
||||
});
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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' },
|
||||
});
|
||||
Reference in New Issue
Block a user