import React, { createContext, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState, } from 'react'; import { View, StyleSheet } from 'react-native'; import './types'; // Get API key from environment variable const getGoogleMapsApiKey = (): string => { if ( typeof process !== 'undefined' && process.env?.EXPO_PUBLIC_GOOGLE_MAPS_API_KEY ) { return process.env.EXPO_PUBLIC_GOOGLE_MAPS_API_KEY; } console.warn( 'Google Maps API key not found in process.env.EXPO_PUBLIC_GOOGLE_MAPS_API_KEY', ); return 'YOUR_GOOGLE_MAPS_API_KEY'; }; const GOOGLE_MAPS_API_KEY = getGoogleMapsApiKey(); // Load Google Maps API script dynamically const loadGoogleMapsScript = (): Promise => { return new Promise((resolve, reject) => { if ( typeof window !== 'undefined' && window.google && window.google.maps ) { resolve(); return; } const script = document.createElement('script'); script.src = `https://maps.googleapis.com/maps/api/js?key=${GOOGLE_MAPS_API_KEY}&libraries=geometry`; script.async = true; script.defer = true; script.onload = () => resolve(); script.onerror = () => reject(new Error('Failed to load Google Maps')); document.head.appendChild(script); }); }; // Create a Context to share the map instance interface MapContextValue { map: google.maps.Map | null; } export const MapContext = createContext({ map: null }); export interface LatLng { latitude: number; longitude: number; } export interface Region extends LatLng { latitudeDelta: number; longitudeDelta: number; } export interface MapPressEvent { nativeEvent: { coordinate: LatLng; }; } interface MapViewProps { style?: object; initialRegion?: Region; onPress?: (event: MapPressEvent) => void; children?: React.ReactNode; } const MapView = forwardRef((props: MapViewProps, ref) => { const mapContainerRef = useRef(null); const mapRef = useRef(null); const [isLoaded, setIsLoaded] = useState(false); useImperativeHandle(ref, () => ({ fitToCoordinates: ( coordinates: LatLng[], options?: { edgePadding?: google.maps.Padding; animated?: boolean; }, ) => { if (!mapRef.current || coordinates.length === 0) { return; } const bounds = new window.google.maps.LatLngBounds(); coordinates.forEach((coord) => { bounds.extend({ lat: coord.latitude, lng: coord.longitude, }); }); mapRef.current.fitBounds(bounds, options?.edgePadding); }, })); useEffect(() => { loadGoogleMapsScript() .then(() => { if (mapContainerRef.current) { const map = new window.google.maps.Map( mapContainerRef.current, { center: props.initialRegion ? { lat: props.initialRegion.latitude, lng: props.initialRegion.longitude, } : { lat: 0, lng: 0 }, zoom: 10, }, ); map.addListener('click', (e: google.maps.MapMouseEvent) => { if (props.onPress && e.latLng) { props.onPress({ nativeEvent: { coordinate: { latitude: e.latLng.lat(), longitude: e.latLng.lng(), }, }, }); } }); mapRef.current = map; setIsLoaded(true); } }) .catch((error) => { console.error('Error loading Google Maps:', error); }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const contextValue = useMemo( () => ({ map: mapRef.current }), // eslint-disable-next-line react-hooks/exhaustive-deps [isLoaded], ); return (
{isLoaded && props.children} ); }); const styles = StyleSheet.create({ container: { flex: 1, }, mapContainer: { width: '100%', height: '100%', }, }); MapView.displayName = 'MapView'; export default MapView;