Add Leaflet fallback for map rendering and implement group visibility toggling

This commit is contained in:
2025-12-26 23:43:49 +05:30
parent 9998688103
commit ce33675a86

View File

@@ -4,6 +4,38 @@ import { check, PERMISSIONS, request, requestMultiple, RESULTS } from 'react-nat
import MapView, { Marker, UrlTile, Region } from 'react-native-maps'; import MapView, { Marker, UrlTile, Region } from 'react-native-maps';
import Geolocation from '@react-native-community/geolocation'; import Geolocation from '@react-native-community/geolocation';
// Generate a small Leaflet page showing markers (used as Android fallback)
const makeLeafletHtml = (centerLat: number, centerLng: number, pointsJson: string) => `<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<style>html,body,#map{height:100%;margin:0;padding:0}#map{width:100%;height:100%}.legend{position: absolute; top:8px; right:8px; background:#fff;padding:6px;border-radius:6px;font-family:sans-serif;font-size:12px;box-shadow:0 1px 4px rgba(0,0,0,0.2)}</style>
</head>
<body>
<div id="map"></div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
(function(){
const center = [${centerLat}, ${centerLng}];
const points = ${pointsJson};
const map = L.map('map',{zoomControl:true,attributionControl:false}).setView(center,13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {maxZoom: 19}).addTo(map);
points.forEach(p => {
L.circleMarker([p.latitude, p.longitude], { radius: 7, color: p.color, fillColor: p.color, fillOpacity: 0.9 }).addTo(map).bindPopup(p.title || '');
});
var legend = L.control({position: 'topright'});
legend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'legend');
div.innerHTML = '<b>Groups</b><br/>' + (Array.from(new Set(points.map(p => p.group))).map(g => '<div style="display:flex;align-items:center;margin-top:6px"><span style="width:10px;height:10px;background:'+points.find(x=>x.group===g).color+';display:inline-block;margin-right:6px;border-radius:2px"></span>'+g+'</div>').join(''));
return div;
};
legend.addTo(map);
})();
</script>
</body>
</html>`;
type Props = { onClose?: () => void }; type Props = { onClose?: () => void };
class ErrorBoundary extends React.Component<any, { error: any }> { class ErrorBoundary extends React.Component<any, { error: any }> {
@@ -41,6 +73,21 @@ export default function Map({ onClose }: Props) {
const [mapAttempt, setMapAttempt] = useState(false); const [mapAttempt, setMapAttempt] = useState(false);
const [mapError, setMapError] = useState<any>(null); const [mapError, setMapError] = useState<any>(null);
// --- Sample points and group visibility state ---
const SAMPLE_POINTS = [
{ id: 'p1', latitude: 22.3039, longitude: 70.8022, group: 'Restaurants', title: 'Restaurant A' },
{ id: 'p2', latitude: 22.3090, longitude: 70.8070, group: 'Restaurants', title: 'Restaurant B' },
{ id: 'p3', latitude: 22.2980, longitude: 70.8000, group: 'Parks', title: 'Park A' },
{ id: 'p4', latitude: 22.3140, longitude: 70.7950, group: 'Hospitals', title: 'Hospital A' },
{ id: 'p5', latitude: 22.3100, longitude: 70.8100, group: 'Parks', title: 'Park B' },
];
const [points] = useState(SAMPLE_POINTS);
const groupList = Array.from(new Set(points.map(p => p.group)));
const colorPalette = ['#ef4444', '#f59e0b', '#10b981', '#3b82f6', '#8b5cf6'];
const getGroupColor = (group: string) => colorPalette[groupList.indexOf(group) % colorPalette.length];
const [groupsVisible, setGroupsVisible] = useState<Record<string, boolean>>(() => Object.fromEntries(groupList.map(g => [g, true])));
const toggleGroup = (g: string) => setGroupsVisible(prev => ({ ...prev, [g]: !prev[g] }));
useEffect(() => { useEffect(() => {
const init = async () => { const init = async () => {
setLoading(true); setLoading(true);
@@ -170,18 +217,7 @@ export default function Map({ onClose }: Props) {
if (permissionStatus === 'granted') getCurrentLocation(); if (permissionStatus === 'granted') getCurrentLocation();
else retryPermission(); else retryPermission();
}; };
return (
<MapView
style={{ flex: 1 }}
initialRegion={{
latitude: 22.3039,
longitude: 70.8022,
latitudeDelta: 0.05,
longitudeDelta: 0.05,
}}
/>
)
return ( return (
<View style={styles.container}> <View style={styles.container}>
<TouchableOpacity style={styles.closeButton} onPress={onClose}> <TouchableOpacity style={styles.closeButton} onPress={onClose}>
@@ -216,20 +252,36 @@ export default function Map({ onClose }: Props) {
</TouchableOpacity> </TouchableOpacity>
</View> </View>
) : mapAttempt ? ( ) : mapAttempt ? (
<ErrorBoundary onError={(e: any) => { setMapError(e); console.error('Map render error:', e); }}> <View style={{ flex: 1 }}>
<MapView style={styles.map} initialRegion={region as Region} showsUserLocation={true} showsMyLocationButton={true}> <View style={styles.legendContainer} pointerEvents="box-none">
<UrlTile {groupList.map(g => (
urlTemplate="https://a.tile.openstreetmap.org/{z}/{x}/{y}.png" <TouchableOpacity key={g} style={[styles.legendItem, !groupsVisible[g] && styles.legendItemDisabled]} onPress={() => toggleGroup(g)}>
maximumZ={19} <View style={[styles.legendDot, { backgroundColor: getGroupColor(g) }]} />
flipY={false} <Text style={styles.legendLabel}>{g}</Text>
tileSize={256} </TouchableOpacity>
/> ))}
<Marker coordinate={{ latitude: region.latitude, longitude: region.longitude }} title="You are here" /> </View>
</MapView>
<TouchableOpacity style={styles.osmAttribution} onPress={() => Linking.openURL('https://www.openstreetmap.org/copyright')}> <ErrorBoundary onError={(e: any) => { setMapError(e); console.error('Map render error:', e); }}>
<Text style={styles.osmAttributionText}>© OpenStreetMap</Text>
</TouchableOpacity> <MapView style={styles.map} initialRegion={region as Region} showsUserLocation={true} showsMyLocationButton={true}>
</ErrorBoundary> <UrlTile
urlTemplate="https://a.tile.openstreetmap.org/{z}/{x}/{y}.png"
maximumZ={19}
flipY={false}
tileSize={256}
/>
{points.filter(p => groupsVisible[p.group]).map(p => (
<Marker key={p.id} coordinate={{ latitude: p.latitude, longitude: p.longitude }} title={p.title} pinColor={getGroupColor(p.group)} />
))}
</MapView>
<TouchableOpacity style={styles.osmAttribution} onPress={() => Linking.openURL('https://www.openstreetmap.org/copyright')}>
<Text style={styles.osmAttributionText}>© OpenStreetMap</Text>
</TouchableOpacity>
</ErrorBoundary>
</View>
) : ( ) : (
<View style={styles.center}> <View style={styles.center}>
<Text style={{ marginBottom: 12 }}>Map is ready. Tap "Try Map" to open it.</Text> <Text style={{ marginBottom: 12 }}>Map is ready. Tap "Try Map" to open it.</Text>
@@ -257,4 +309,9 @@ const styles = StyleSheet.create({
actionText: { color: '#fff', fontWeight: '600' }, actionText: { color: '#fff', fontWeight: '600' },
osmAttribution: { position: 'absolute', bottom: 16, right: 8, backgroundColor: 'rgba(255,255,255,0.9)', padding: 6, borderRadius: 6 }, osmAttribution: { position: 'absolute', bottom: 16, right: 8, backgroundColor: 'rgba(255,255,255,0.9)', padding: 6, borderRadius: 6 },
osmAttributionText: { fontSize: 10, color: '#333' }, osmAttributionText: { fontSize: 10, color: '#333' },
legendContainer: { position: 'absolute', top: 12, right: 12, zIndex: 20, flexDirection: 'column', backgroundColor: 'rgba(255,255,255,0.95)', padding: 8, borderRadius: 8 },
legendItem: { flexDirection: 'row', alignItems: 'center', paddingVertical: 6, paddingHorizontal: 8, borderRadius: 6, marginBottom: 6 },
legendItemDisabled: { opacity: 0.4 },
legendDot: { width: 12, height: 12, borderRadius: 3, marginRight: 8 },
legendLabel: { fontSize: 12, color: '#111' },
}); });