import { Platform, PermissionsAndroid, Alert, Linking } from 'react-native'; import Geolocation from 'react-native-geolocation-service'; import type { GeoPosition, GeoError } from 'react-native-geolocation-service'; // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- export interface LatLng { latitude: number; longitude: number; } export interface LocationResult { coords: LatLng; address: string; } // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- const GOOGLE_MAPS_API_KEY = 'AIzaSyDKaKVyQCmSpHMXcMkNCccSXpILpuvwsVM'; /** Default region (Bengaluru) used as a fallback before the user's location loads. */ export const DEFAULT_LOCATION: LatLng = { latitude: 12.9352, longitude: 77.6245, }; // --------------------------------------------------------------------------- // Permission helpers // --------------------------------------------------------------------------- /** * Request location permission from the user. * Returns `true` if permission was granted, `false` otherwise. */ export const requestLocationPermission = async (): Promise => { if (Platform.OS === 'ios') { try { const result = await Geolocation.requestAuthorization('whenInUse'); return result === 'granted'; } catch { return false; } } // Android — check first to avoid showing the dialog when already granted try { const alreadyGranted = await PermissionsAndroid.check( PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, ); if (alreadyGranted) { return true; } const granted = await PermissionsAndroid.request( PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, { title: 'Location Permission', message: 'This app needs access to your location to set it on the map.', buttonNeutral: 'Ask Me Later', buttonNegative: 'Cancel', buttonPositive: 'OK', }, ); console.log('granted', granted); return granted === PermissionsAndroid.RESULTS.GRANTED; } catch { return false; } }; /** * Show an alert when the user denies location permission, with an option * to open app settings. */ export const showPermissionDeniedAlert = (): void => { Alert.alert( 'Location Permission Required', 'Please enable location permission in your device settings to use this feature.', [ { text: 'Cancel', style: 'cancel' }, { text: 'Open Settings', onPress: () => Linking.openSettings() }, ], ); }; // --------------------------------------------------------------------------- // Position helpers // --------------------------------------------------------------------------- /** * Get the device's current GPS position. * Wraps the callback-based Geolocation API in a Promise. */ /** * Wraps Geolocation.getCurrentPosition in a Promise. * Tries high-accuracy first; falls back to low-accuracy on failure. */ const getPositionOnce = (highAccuracy: boolean): Promise => new Promise((resolve, reject) => { Geolocation.getCurrentPosition( (position: GeoPosition) => { resolve({ latitude: position.coords.latitude, longitude: position.coords.longitude, }); }, (error: GeoError) => { console.log('Geolocation error (highAccuracy=' + highAccuracy + '):', error); reject(error); }, { enableHighAccuracy: highAccuracy, timeout: highAccuracy ? 5000 : 10000, maximumAge: 10000, forceRequestLocation: false, // avoid native dialog that can crash showLocationDialog: true, }, ); }); export const getCurrentPosition = async (): Promise => { console.log('getCurrentPosition called'); try { // First attempt: high accuracy (GPS) return await getPositionOnce(true); } catch (highAccuracyError) { console.warn('High-accuracy location failed, retrying with low accuracy…', highAccuracyError); try { // Fallback: network / cell-tower accuracy return await getPositionOnce(false); } catch (lowAccuracyError) { console.error('Failed to get current position:', lowAccuracyError); throw lowAccuracyError; } } }; // --------------------------------------------------------------------------- // Geocoding // --------------------------------------------------------------------------- /** * Reverse-geocode a lat/lng pair into a human-readable address string * using the Google Maps Geocoding API. */ export const reverseGeocode = async (coords: LatLng): Promise => { try { const url = `https://maps.googleapis.com/maps/api/geocode/json` + `?latlng=${coords.latitude},${coords.longitude}` + `&key=${GOOGLE_MAPS_API_KEY}`; const response = await fetch(url); const data = await response.json(); if (data.status === 'OK' && data.results.length > 0) { // Return the most specific formatted address. return data.results[0].formatted_address; } console.warn('Google Maps Geocoding failed:', data.status, data.error_message || ''); // Fallback to OpenStreetMap Nominatim const osmUrl = `https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${coords.latitude}&lon=${coords.longitude}`; const osmResponse = await fetch(osmUrl, { headers: { 'User-Agent': 'SGDeliveryCustomerApp/1.0', }, }); const osmData = await osmResponse.json(); if (osmData && osmData.display_name) { return osmData.display_name; } return 'Address not found'; } catch (error) { console.log('reverseGeocode error', error); try { // Fallback to OpenStreetMap Nominatim on catch const osmUrl = `https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${coords.latitude}&lon=${coords.longitude}`; const osmResponse = await fetch(osmUrl, { headers: { 'User-Agent': 'SGDeliveryCustomerApp/1.0', }, }); const osmData = await osmResponse.json(); if (osmData && osmData.display_name) { return osmData.display_name; } } catch (osmError) { console.log('OSM fallback error', osmError); } return 'Unable to fetch address'; } }; // --------------------------------------------------------------------------- // Combined helper // --------------------------------------------------------------------------- /** * All-in-one helper: request permissions → get position → reverse geocode. * Returns both the coordinates and the human-readable address. * * Throws if permission is denied or position cannot be obtained. */ export const getCurrentLocationWithAddress = async (): Promise => { const hasPermission = await requestLocationPermission(); console.log('hasPermission', hasPermission); if (!hasPermission) { // Alert shown by the caller so it doesn't fire inside a Promise chain // on mount (which can cause a crash on some Android versions). throw new Error('Location permission denied'); } const coords = await getCurrentPosition(); const address = await reverseGeocode(coords); console.log('coords', coords); console.log('address', address); return { coords, address }; };