diff --git a/android/app/build.gradle b/android/app/build.gradle index b761b19..836f4c2 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -100,8 +100,8 @@ packagingOptions { applicationId 'com.bip.hipmimobileapp' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 4 - versionName "1.0.1" + versionCode 1 + versionName "1.0.2" buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\"" } diff --git a/app.config.js b/app.config.js index 3a7b560..2403739 100644 --- a/app.config.js +++ b/app.config.js @@ -4,7 +4,7 @@ require("dotenv").config(); export default { name: "HIPMI Badung Connect", slug: "hipmi-mobile", - version: "1.0.1", + version: "1.0.2", orientation: "portrait", icon: "./assets/images/icon.png", scheme: "hipmimobile", @@ -21,7 +21,7 @@ export default { "Aplikasi membutuhkan akses lokasi untuk menampilkan peta.", }, associatedDomains: ["applinks:cld-dkr-staging-hipmi.wibudev.com"], - buildNumber: "21", + buildNumber: "1", }, android: { @@ -32,7 +32,7 @@ export default { }, edgeToEdgeEnabled: true, package: "com.bip.hipmimobileapp", - versionCode: 4, + versionCode: 1, // softwareKeyboardLayoutMode: 'resize', // option: untuk mengatur keyboard pada room chst collaboration intentFilters: [ { diff --git a/components/Map/MapSelectedPlatform.tsx b/components/Map/MapSelectedPlatform.tsx index a6d316f..1eae1df 100644 --- a/components/Map/MapSelectedPlatform.tsx +++ b/components/Map/MapSelectedPlatform.tsx @@ -1,7 +1,6 @@ import { Platform } from "react-native"; import MapSelected from "./MapSelected"; -import { MapSelectedV2 } from "./MapSelectedV2"; -import { Region } from "./MapSelectedV2"; +import MapSelectedV2 from "./MapSelectedV2"; import { LatLng } from "react-native-maps"; /** @@ -58,18 +57,18 @@ export function MapSelectedPlatform({ showsMyLocationButton = true, }: MapSelectedPlatformProps) { // iOS: Gunakan react-native-maps - if (Platform.OS === "ios") { - return ( - { - onLocationSelect(location); - }} - height={height} - /> - ); - } + // if (Platform.OS === "ios") { + // return ( + // { + // onLocationSelect(location); + // }} + // height={height} + // /> + // ); + // } // Android: Gunakan MapLibre // Konversi dari LatLng ke [longitude, latitude] jika perlu @@ -81,7 +80,6 @@ export function MapSelectedPlatform({ return ( { // Konversi dari [longitude, latitude] ke LatLng untuk konsistensi @@ -92,8 +90,8 @@ export function MapSelectedPlatform({ onLocationSelect(latLng); }} height={height} - showUserLocation={showUserLocation} - showsMyLocationButton={showsMyLocationButton} + // showUserLocation={showUserLocation} + // showsMyLocationButton={showsMyLocationButton} /> ); } diff --git a/components/Map/MapSelectedV2.tsx b/components/Map/MapSelectedV2.tsx index 286ce4a..432ec1e 100644 --- a/components/Map/MapSelectedV2.tsx +++ b/components/Map/MapSelectedV2.tsx @@ -1,125 +1,119 @@ -import React, { useCallback, useMemo, useRef } from "react"; -import { - StyleSheet, - View, - ViewStyle, - StyleProp, -} from "react-native"; -import { MainColor } from "@/constants/color-palet"; +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 DEFAULT_MAP_STYLE = "https://tiles.openfreemap.org/styles/liberty"; +const MAP_STYLE = "https://tiles.openfreemap.org/styles/liberty"; +const DEBOUNCE_MS = 800; -export interface Region { - latitude: number; - longitude: number; - latitudeDelta: number; - longitudeDelta: number; -} - -export interface MapSelectedV2Props { - initialRegion?: Region; +interface Props { selectedLocation?: [number, number]; onLocationSelect?: (location: [number, number]) => void; height?: number; - style?: StyleProp; - mapViewStyle?: StyleProp; - 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 ( - - - - - ); } 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, - }), - [], +}: Props) { + const lastTapRef = useRef(0); + const cameraRef = useRef(null); + + const [userLocation, setUserLocation] = useState<[number, number] | null>( + null, ); + const [isLoadingLocation, setIsLoadingLocation] = useState(true); - const region = initialRegion || defaultRegion; + // ✅ 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; + } - // ✅ Simpan initial center — TIDAK berubah saat user tap - const initialCenter = useRef<[number, number]>([ - region.longitude, - region.latitude, - ]); + 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 coordinate = event?.geometry?.coordinates || event?.coordinates; - if (coordinate && Array.isArray(coordinate) && coordinate.length === 2) { - console.log("[MapSelectedV2] coordinate", coordinate); - onLocationSelect?.([coordinate[0], coordinate[1]]); - } + 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 ( - + + {/* Loading indicator saat fetch lokasi */} + {isLoadingLocation && ( + + + + )} + - {/* ✅ Camera hanya set sekali di awal, tidak reactive ke selectedLocation */} - {/* ✅ Hanya render PointAnnotation jika ada selectedLocation */} - {/* ✅ Key statis — tidak pernah unmount/remount */} {selectedLocation && ( - + )} @@ -128,37 +122,29 @@ export function MapSelectedV2({ } const styles = StyleSheet.create({ - container: { - width: "100%", - backgroundColor: "#f5f5f5", - overflow: "hidden", - borderRadius: 8, + dot: { + width: 20, + height: 20, + borderRadius: 10, + backgroundColor: "#0a1f44", + borderWidth: 2, + borderColor: "#fff", }, - map: { - flex: 1, - }, - markerContainer: { - width: 40, - height: 40, - alignItems: "center", - justifyContent: "center", - }, - // ✅ Ring statis pengganti pulse animation - markerRing: { + loadingOverlay: { position: "absolute", - width: 36, - height: 36, - borderRadius: 18, - borderWidth: 2, - opacity: 0.4, - }, - markerDot: { - width: 16, - height: 16, - borderRadius: 8, - borderWidth: 2, - borderColor: "#FFFFFF", + 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; \ No newline at end of file +export default MapSelectedV2; diff --git a/ios/HIPMIBadungConnect.xcodeproj/project.pbxproj b/ios/HIPMIBadungConnect.xcodeproj/project.pbxproj index b64e9e3..18e7185 100644 --- a/ios/HIPMIBadungConnect.xcodeproj/project.pbxproj +++ b/ios/HIPMIBadungConnect.xcodeproj/project.pbxproj @@ -165,6 +165,12 @@ CCCF75FD0B87410193A6B7DB /* Remove signature files (Xcode workaround) */, 51F61F14096F4B7FBD9344A7 /* Remove signature files (Xcode workaround) */, 60F3AE3AC4B24C2FA7FC8F10 /* Remove signature files (Xcode workaround) */, + E188CB171C1B4A4DA64BC5C4 /* Remove signature files (Xcode workaround) */, + 90714A8C562E4676B84E6E07 /* Remove signature files (Xcode workaround) */, + DB93AB500BC2459E8BAE3F74 /* Remove signature files (Xcode workaround) */, + EEC6AC8AF9C04E91AA81C190 /* Remove signature files (Xcode workaround) */, + D2BED766D85C4781B154BD69 /* Remove signature files (Xcode workaround) */, + E01278D305D540D5B29ED50A /* Remove signature files (Xcode workaround) */, ); buildRules = ( ); @@ -671,6 +677,108 @@ rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\"; "; }; + E188CB171C1B4A4DA64BC5C4 /* Remove signature files (Xcode workaround) */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + name = "Remove signature files (Xcode workaround)"; + inputPaths = ( + ); + outputPaths = ( + ); + shellPath = /bin/sh; + shellScript = " + echo \"Remove signature files (Xcode workaround)\"; + rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\"; + "; + }; + 90714A8C562E4676B84E6E07 /* Remove signature files (Xcode workaround) */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + name = "Remove signature files (Xcode workaround)"; + inputPaths = ( + ); + outputPaths = ( + ); + shellPath = /bin/sh; + shellScript = " + echo \"Remove signature files (Xcode workaround)\"; + rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\"; + "; + }; + DB93AB500BC2459E8BAE3F74 /* Remove signature files (Xcode workaround) */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + name = "Remove signature files (Xcode workaround)"; + inputPaths = ( + ); + outputPaths = ( + ); + shellPath = /bin/sh; + shellScript = " + echo \"Remove signature files (Xcode workaround)\"; + rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\"; + "; + }; + EEC6AC8AF9C04E91AA81C190 /* Remove signature files (Xcode workaround) */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + name = "Remove signature files (Xcode workaround)"; + inputPaths = ( + ); + outputPaths = ( + ); + shellPath = /bin/sh; + shellScript = " + echo \"Remove signature files (Xcode workaround)\"; + rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\"; + "; + }; + D2BED766D85C4781B154BD69 /* Remove signature files (Xcode workaround) */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + name = "Remove signature files (Xcode workaround)"; + inputPaths = ( + ); + outputPaths = ( + ); + shellPath = /bin/sh; + shellScript = " + echo \"Remove signature files (Xcode workaround)\"; + rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\"; + "; + }; + E01278D305D540D5B29ED50A /* Remove signature files (Xcode workaround) */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + name = "Remove signature files (Xcode workaround)"; + inputPaths = ( + ); + outputPaths = ( + ); + shellPath = /bin/sh; + shellScript = " + echo \"Remove signature files (Xcode workaround)\"; + rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\"; + "; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/ios/HIPMIBadungConnect/Info.plist b/ios/HIPMIBadungConnect/Info.plist index 9f0f327..a743c17 100644 --- a/ios/HIPMIBadungConnect/Info.plist +++ b/ios/HIPMIBadungConnect/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0.1 + 1.0.2 CFBundleSignature ???? CFBundleURLTypes @@ -39,7 +39,7 @@ CFBundleVersion - 21 + 1 ITSAppUsesNonExemptEncryption LSMinimumSystemVersion diff --git a/screens/Home/bottomFeatureSection.tsx b/screens/Home/bottomFeatureSection.tsx index 9e56f64..23bde4b 100644 --- a/screens/Home/bottomFeatureSection.tsx +++ b/screens/Home/bottomFeatureSection.tsx @@ -9,7 +9,7 @@ import { apiJobGetAll } from "@/service/api-client/api-job"; import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom"; export default function Home_BottomFeatureSection() { - const [listData, setListData] = useState([]); + const [listData, setListData] = useState(null); const onLoadData = async () => { try { @@ -17,7 +17,7 @@ export default function Home_BottomFeatureSection() { category: "beranda", }); - // console.log("[DATA JOB]", JSON.stringify(response.data, null, 2)); + console.log("[DATA JOB]", JSON.stringify(response.data, null, 2)); const result = response.data .sort( (a: any, b: any) => @@ -36,7 +36,7 @@ export default function Home_BottomFeatureSection() { }, []) ); - if (!listData || listData.length === 0) { + if (listData === null) { return } @@ -54,7 +54,7 @@ export default function Home_BottomFeatureSection() { {/* Vacancy Item 1 */} - {listData.map((item: any, index: number) => ( + {listData?.map((item: any, index: number) => ( diff --git a/screens/Maps/ScreenMapsCreate.tsx b/screens/Maps/ScreenMapsCreate.tsx index f6b345d..33f5dfb 100644 --- a/screens/Maps/ScreenMapsCreate.tsx +++ b/screens/Maps/ScreenMapsCreate.tsx @@ -20,7 +20,6 @@ import { router, useLocalSearchParams } from "expo-router"; import { useState } from "react"; import { LatLng } from "react-native-maps"; import Toast from "react-native-toast-message"; -import MapSelected from "@/components/Map/MapSelected"; /** * Screen untuk create maps @@ -150,14 +149,9 @@ export function Maps_ScreenMapsCreate() { - {/* */} { - // Set location (auto handle LatLng format) setSelectedLocation(location as LatLng); }} height={300} diff --git a/screens/Maps/ScreenMapsEdit.tsx b/screens/Maps/ScreenMapsEdit.tsx index c451815..deee80f 100644 --- a/screens/Maps/ScreenMapsEdit.tsx +++ b/screens/Maps/ScreenMapsEdit.tsx @@ -9,6 +9,9 @@ import { TextInputCustom, ViewWrapper, } from "@/components"; +import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom"; +import { MapSelectedPlatform } from "@/components/Map/MapSelectedPlatform"; +import MapSelectedV2 from "@/components/Map/MapSelectedV2"; import API_IMAGE from "@/constants/api-storage"; import DIRECTORY_ID from "@/constants/directory-id"; import { apiMapsGetOne, apiMapsUpdate } from "@/service/api-client/api-maps"; @@ -16,8 +19,7 @@ import { uploadFileService } from "@/service/upload-service"; import pickFile, { IFileData } from "@/utils/pickFile"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router"; import { useCallback, useState } from "react"; -import { StyleSheet, View } from "react-native"; -import MapView, { LatLng, Marker } from "react-native-maps"; +import { LatLng } from "react-native-maps"; import Toast from "react-native-toast-message"; const defaultRegion = { @@ -43,7 +45,7 @@ export function Maps_ScreenMapsEdit() { useFocusEffect( useCallback(() => { onLoadData(); - }, [id]) + }, [id]), ); const onLoadData = async () => { @@ -64,10 +66,14 @@ export function Maps_ScreenMapsEdit() { } }; - const handleMapPress = (event: any) => { - const { latitude, longitude } = event.nativeEvent.coordinate; - const location = { latitude, longitude }; - setSelectedLocation(location); + const handleLocationSelect = (location: LatLng | [number, number]) => { + if (Array.isArray(location)) { + // Android format: [longitude, latitude] + setSelectedLocation({ latitude: location[1], longitude: location[0] }); + } else { + // iOS format: LatLng + setSelectedLocation(location); + } }; const handleSubmit = async () => { @@ -149,51 +155,41 @@ export function Maps_ScreenMapsEdit() { ); + const initialRegion = + data?.latitude && data?.longitude + ? { + latitude: Number(data?.latitude), + longitude: Number(data?.longitude), + latitudeDelta: 0.1, + longitudeDelta: 0.1, + } + : defaultRegion; + return ( - - - {selectedLocation ? ( - - ) : ( - - )} - - + {/* */} + + {!data || !data.latitude || !data.longitude ? ( + + ) : ( + { + setData({ + ...data, + longitude: location[0], + latitude: location[1], + }); + }} + /> + )} ); } - -const styles = StyleSheet.create({ - container: { - width: "100%", - backgroundColor: "#f5f5f5", - overflow: "hidden", - borderRadius: 8, - marginBottom: 20, - }, - map: { - flex: 1, - }, -});