Files
hipmi-mobile/components/Map/MapSelectedV2.tsx
bagasbanuna 9c94ec0454 Fix Bug
Maps Platform Update
- components/Map/MapSelectedPlatform.tsx
- components/Map/MapSelectedV2.tsx

Maps Screens
- screens/Maps/ScreenMapsCreate.tsx
- screens/Maps/ScreenMapsEdit.tsx

Home
- screens/Home/bottomFeatureSection.tsx

Config & Native
- app.config.js
- android/app/build.gradle
- ios/HIPMIBadungConnect.xcodeproj/project.pbxproj
- ios/HIPMIBadungConnect/Info.plist

### No Issue
2026-03-02 16:34:24 +08:00

151 lines
3.8 KiB
TypeScript

import React, { useCallback, useRef, useEffect, useState } from "react";
import { StyleSheet, View, ActivityIndicator } from "react-native";
import {
MapView,
Camera,
PointAnnotation,
} from "@maplibre/maplibre-react-native";
import * as Location from "expo-location";
const MAP_STYLE = "https://tiles.openfreemap.org/styles/liberty";
const DEBOUNCE_MS = 800;
interface Props {
selectedLocation?: [number, number];
onLocationSelect?: (location: [number, number]) => void;
height?: number;
}
export function MapSelectedV2({
selectedLocation,
onLocationSelect,
height = 400,
}: Props) {
const lastTapRef = useRef<number>(0);
const cameraRef = useRef<any>(null);
const [userLocation, setUserLocation] = useState<[number, number] | null>(
null,
);
const [isLoadingLocation, setIsLoadingLocation] = useState(true);
// ✅ Ambil lokasi user saat pertama mount
useEffect(() => {
(async () => {
try {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== "granted") {
console.log("Permission lokasi ditolak");
setIsLoadingLocation(false);
return;
}
const location = await Location.getCurrentPositionAsync({
accuracy: Location.Accuracy.Balanced,
});
const coords: [number, number] = [
location.coords.longitude,
location.coords.latitude,
];
setUserLocation(coords);
// ✅ Fly ke posisi user jika belum ada selectedLocation
if (!selectedLocation && cameraRef.current) {
cameraRef.current.flyTo(coords, 1000);
}
} catch (error) {
console.log("Gagal ambil lokasi:", error);
} finally {
setIsLoadingLocation(false);
}
})();
}, [isLoadingLocation]);
const handleMapPress = useCallback(
(event: any) => {
const now = Date.now();
if (now - lastTapRef.current < DEBOUNCE_MS) return;
lastTapRef.current = now;
const coords = event?.geometry?.coordinates;
if (!coords || coords.length < 2) return;
onLocationSelect?.([coords[0], coords[1]]);
},
[onLocationSelect],
);
// Center awal kamera:
// 1. Jika ada selectedLocation → pakai itu
// 2. Jika ada userLocation → pakai itu
// 3. Fallback → Bali
const initialCenter: [number, number] = selectedLocation ??
userLocation ?? [115.1756897, -8.737109];
return (
<View style={{ height, width: "100%" }}>
{/* Loading indicator saat fetch lokasi */}
{isLoadingLocation && (
<View style={styles.loadingOverlay}>
<ActivityIndicator size="small" color="#0a1f44" />
</View>
)}
<MapView
style={StyleSheet.absoluteFillObject}
mapStyle={MAP_STYLE}
onPress={handleMapPress}
logoEnabled={false}
>
<Camera
ref={cameraRef}
defaultSettings={{
centerCoordinate: initialCenter,
zoomLevel: 14,
}}
/>
{selectedLocation && (
<PointAnnotation
id="selected-location"
key="selected-location"
coordinate={selectedLocation}
>
<View style={styles.dot} />
</PointAnnotation>
)}
</MapView>
</View>
);
}
const styles = StyleSheet.create({
dot: {
width: 20,
height: 20,
borderRadius: 10,
backgroundColor: "#0a1f44",
borderWidth: 2,
borderColor: "#fff",
},
loadingOverlay: {
position: "absolute",
top: 10,
alignSelf: "center",
zIndex: 10,
backgroundColor: "#fff",
borderRadius: 20,
paddingHorizontal: 12,
paddingVertical: 6,
elevation: 4,
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.15,
shadowRadius: 4,
},
});
export default MapSelectedV2;