Fix create maps

iOS Project
- HIPMIBadungConnect.xcodeproj/project.pbxproj

Maps & Location Screens
- screens/Maps/MapsView2.tsx
- screens/Portofolio/BusinessLocationSection.tsx

New Map Components
- components/Map/MapsV2Custom.tsx
- components/Map/SelectLocationMap.tsx

### No Issue
This commit is contained in:
2026-02-27 16:57:01 +08:00
parent 67070bb2f1
commit f5d09a2906
7 changed files with 542 additions and 144 deletions

View File

@@ -0,0 +1,112 @@
import { Platform } from "react-native";
import MapSelected from "./MapSelected";
import { MapSelectedV2 } from "./MapSelectedV2";
import { Region } from "./MapSelectedV2";
import { LatLng } from "react-native-maps";
/**
* Props untuk komponen MapSelectedPlatform
* Mendukung kedua format koordinat (LatLng untuk iOS, [number, number] untuk Android)
*/
export interface MapSelectedPlatformProps {
/** Region awal kamera */
initialRegion?: {
latitude?: number;
longitude?: number;
latitudeDelta?: number;
longitudeDelta?: number;
};
/** Lokasi yang dipilih (support kedua format) */
selectedLocation: LatLng | [number, number] | null;
/** Callback ketika lokasi dipilih */
onLocationSelect: (location: LatLng | [number, number]) => void;
/** Tinggi peta dalam pixels (default: 400) */
height?: number;
/** Tampilkan lokasi user (default: true) */
showUserLocation?: boolean;
/** Tampilkan tombol my location (default: true) */
showsMyLocationButton?: boolean;
}
/**
* Komponen Map yang otomatis memilih implementasi berdasarkan platform
*
* Platform Strategy:
* - **iOS**: Menggunakan react-native-maps (MapSelected)
* - **Android**: Menggunakan @maplibre/maplibre-react-native (MapSelectedV2)
*
* @example
* ```tsx
* <MapSelectedPlatform
* selectedLocation={selectedLocation}
* onLocationSelect={setSelectedLocation}
* height={300}
* />
* ```
*/
export function MapSelectedPlatform({
initialRegion,
selectedLocation,
onLocationSelect,
height = 400,
showUserLocation = true,
showsMyLocationButton = true,
}: MapSelectedPlatformProps) {
// iOS: Gunakan react-native-maps
if (Platform.OS === "ios") {
return (
<MapSelected
initialRegion={initialRegion}
selectedLocation={(selectedLocation as LatLng) || { latitude: 0, longitude: 0 }}
setSelectedLocation={(location: LatLng) => {
onLocationSelect(location);
}}
height={height}
/>
);
}
// Android: Gunakan MapLibre
// Konversi dari LatLng ke [longitude, latitude] jika perlu
const androidLocation: [number, number] | undefined = selectedLocation
? isLatLng(selectedLocation)
? [selectedLocation.longitude, selectedLocation.latitude]
: selectedLocation
: undefined;
return (
<MapSelectedV2
initialRegion={initialRegion as Region}
selectedLocation={androidLocation}
onLocationSelect={(location: [number, number]) => {
// Konversi dari [longitude, latitude] ke LatLng untuk konsistensi
const latLng: LatLng = {
latitude: location[1],
longitude: location[0],
};
onLocationSelect(latLng);
}}
height={height}
showUserLocation={showUserLocation}
showsMyLocationButton={showsMyLocationButton}
/>
);
}
/**
* Type guard untuk mengecek apakah object adalah LatLng
*/
function isLatLng(location: any): location is LatLng {
return (
location &&
typeof location.latitude === "number" &&
typeof location.longitude === "number"
);
}
export default MapSelectedPlatform;

View File

@@ -0,0 +1,163 @@
import React, { useCallback, useMemo, useRef } from "react";
import {
StyleSheet,
View,
ViewStyle,
StyleProp,
} from "react-native";
import { MainColor } from "@/constants/color-palet";
import {
MapView,
Camera,
PointAnnotation,
} from "@maplibre/maplibre-react-native";
const DEFAULT_MAP_STYLE = "https://tiles.openfreemap.org/styles/liberty";
export interface Region {
latitude: number;
longitude: number;
latitudeDelta: number;
longitudeDelta: number;
}
export interface MapSelectedV2Props {
initialRegion?: Region;
selectedLocation?: [number, number];
onLocationSelect?: (location: [number, number]) => void;
height?: number;
style?: StyleProp<ViewStyle>;
mapViewStyle?: StyleProp<ViewStyle>;
showUserLocation?: boolean;
showsMyLocationButton?: boolean;
mapStyle?: string;
zoomLevel?: number;
}
// ✅ Marker simple tanpa Animated — hapus pulse animation
function SelectedLocationMarker({
color = MainColor.darkblue,
}: {
size?: number;
color?: string;
}) {
return (
<View style={styles.markerContainer}>
<View style={[styles.markerRing, { borderColor: color }]} />
<View style={[styles.markerDot, { backgroundColor: color }]} />
</View>
);
}
export function MapSelectedV2({
initialRegion,
selectedLocation,
onLocationSelect,
height = 400,
style = styles.container,
mapViewStyle = styles.map,
mapStyle,
zoomLevel = 12,
}: MapSelectedV2Props) {
const defaultRegion = useMemo(
() => ({
latitude: -8.737109,
longitude: 115.1756897,
latitudeDelta: 0.1,
longitudeDelta: 0.1,
}),
[],
);
const region = initialRegion || defaultRegion;
// ✅ Simpan initial center — TIDAK berubah saat user tap
const initialCenter = useRef<[number, number]>([
region.longitude,
region.latitude,
]);
const handleMapPress = useCallback(
(event: any) => {
const coordinate = event?.geometry?.coordinates || event?.coordinates;
if (coordinate && Array.isArray(coordinate) && coordinate.length === 2) {
onLocationSelect?.([coordinate[0], coordinate[1]]);
}
},
[onLocationSelect],
);
return (
<View style={[style, { height }]} collapsable={false}>
<MapView
style={mapViewStyle}
mapStyle={mapStyle || DEFAULT_MAP_STYLE}
onPress={handleMapPress}
logoEnabled={false}
compassEnabled={true}
compassViewPosition={2}
compassViewMargins={{ x: 10, y: 10 }}
scrollEnabled={true}
zoomEnabled={true}
rotateEnabled={true}
pitchEnabled={false}
>
{/* ✅ Camera hanya set sekali di awal, tidak reactive ke selectedLocation */}
<Camera
defaultSettings={{
centerCoordinate: initialCenter.current,
zoomLevel: zoomLevel,
}}
/>
{/* ✅ Hanya render PointAnnotation jika ada selectedLocation */}
{/* ✅ Key statis — tidak pernah unmount/remount */}
{selectedLocation && (
<PointAnnotation
id="selected-location"
key="selected-location"
coordinate={selectedLocation}
>
<SelectedLocationMarker />
</PointAnnotation>
)}
</MapView>
</View>
);
}
const styles = StyleSheet.create({
container: {
width: "100%",
backgroundColor: "#f5f5f5",
overflow: "hidden",
borderRadius: 8,
},
map: {
flex: 1,
},
markerContainer: {
width: 40,
height: 40,
alignItems: "center",
justifyContent: "center",
},
// ✅ Ring statis pengganti pulse animation
markerRing: {
position: "absolute",
width: 36,
height: 36,
borderRadius: 18,
borderWidth: 2,
opacity: 0.4,
},
markerDot: {
width: 16,
height: 16,
borderRadius: 8,
borderWidth: 2,
borderColor: "#FFFFFF",
},
});
export default MapSelectedV2;