package com.dumon.plugin.geolocation import android.Manifest import android.graphics.Color import android.os.Build import android.view.View import android.view.WindowInsetsController import android.view.WindowManager import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import com.getcapacitor.* import com.getcapacitor.annotation.CapacitorPlugin import com.getcapacitor.annotation.Permission import com.dumon.plugin.geolocation.gps.GpsStatusManager import com.dumon.plugin.geolocation.gps.SatelliteStatus import com.dumon.plugin.geolocation.imu.ImuData import com.dumon.plugin.geolocation.imu.ImuSensorManager import com.dumon.plugin.geolocation.wifi.WifiPositioningManager import com.dumon.plugin.geolocation.wifi.WifiScanResult import com.dumon.plugin.geolocation.fusion.SensorFusionManager import com.dumon.plugin.geolocation.utils.PermissionUtils import org.json.JSONArray import org.json.JSONObject @CapacitorPlugin( name = "DumonGeolocation", permissions = [ Permission(strings = [ Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_WIFI_STATE, Manifest.permission.CHANGE_WIFI_STATE, Manifest.permission.NEARBY_WIFI_DEVICES ]) ] ) class DumonGeolocation : Plugin() { private var gpsManager: GpsStatusManager? = null private var imuManager: ImuSensorManager? = null private var wifiManager: WifiPositioningManager? = null private var fusionManager: SensorFusionManager? = null private var latestLatitude = 0.0 private var latestLongitude = 0.0 private var latestAccuracy = 999.0 private var latestSource = "GNSS" private var latestTimestamp: Long = 0L private var latestImu: ImuData? = null private var satelliteStatus: SatelliteStatus? = null private var wifiScanResult: WifiScanResult? = null private var isMockedLocation = false private var lastEmitTimestamp: Long = 0L private val emitIntervalMs: Long = 500L override fun load() { gpsManager = GpsStatusManager( context, onSatelliteStatusUpdate = { satelliteStatus = it }, onLocationUpdate = { location, isMocked -> latestLatitude = location.latitude latestLongitude = location.longitude latestAccuracy = location.accuracy.toDouble() latestSource = if (isMocked) "MOCK" else "GNSS" isMockedLocation = isMocked latestTimestamp = location.time fusionManager?.updateGpsPosition(latestLatitude, latestLongitude) emitPositionUpdate() } ) imuManager = ImuSensorManager( context, onImuUpdate = { latestImu = it emitPositionUpdate() } ) wifiManager = WifiPositioningManager( context, onWifiPositioningUpdate = { wifiScanResult = it emitPositionUpdate() } ) fusionManager = SensorFusionManager { lat, lon -> latestLatitude = lat latestLongitude = lon latestAccuracy = 3.0 latestSource = "FUSED" latestTimestamp = System.currentTimeMillis() emitPositionUpdate() } } @PluginMethod fun startPositioning(call: PluginCall) { if (!PermissionUtils.hasLocationAndWifiPermissions(context)) { call.reject("Required permissions not granted") return } gpsManager?.start() imuManager?.start() wifiManager?.startPeriodicScan(3000L) call.resolve() } @PluginMethod fun stopPositioning(call: PluginCall) { gpsManager?.stop() imuManager?.stop() wifiManager?.stopPeriodicScan() call.resolve() } @PluginMethod fun getLatestPosition(call: PluginCall) { call.resolve(buildPositionData()) } @PluginMethod fun checkAndRequestPermissions(call: PluginCall) { if (PermissionUtils.hasLocationAndWifiPermissions(context)) { val result = JSObject().apply { put("location", "granted") put("wifi", "granted") } call.resolve(result) } else { requestAllPermissions(call, "checkAndRequestPermissions") } } @PluginMethod fun configureEdgeToEdge(call: PluginCall) { val bgColorHex = call.getString("bgColor") ?: "#FFFFFF" val style = call.getString("style") ?: "DARK" val overlay = call.getBoolean("overlay") ?: false val activity = bridge?.activity val window = activity?.window val view = bridge?.webView if (window == null || view == null) { call.reject("No active window or webView") return } val parsedColor = Color.parseColor(bgColorHex) // Atur overlay (decor fit) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { window.setDecorFitsSystemWindows(!overlay) } else { if (!overlay) { @Suppress("DEPRECATION") window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS) } } // Atur warna status bar & nav bar if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { window.statusBarColor = parsedColor window.navigationBarColor = parsedColor } // Style icon (dark/light) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val controller = window.insetsController val flags = WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS or WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS if (style.uppercase() == "DARK") { controller?.setSystemBarsAppearance(flags, flags) } else { controller?.setSystemBarsAppearance(0, flags) } } else { @Suppress("DEPRECATION") view.systemUiVisibility = when (style.uppercase()) { "DARK" -> View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR else -> 0 } } // Handling insets agar konten tidak tertutup nav/status bar ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets -> val sysInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) v.setPadding(sysInsets.left, sysInsets.top, sysInsets.right, sysInsets.bottom) WindowInsetsCompat.CONSUMED } call.resolve() } private fun emitPositionUpdate() { val now = System.currentTimeMillis() if (now - lastEmitTimestamp < emitIntervalMs) return lastEmitTimestamp = now notifyListeners("onPositionUpdate", buildPositionData()) } private fun buildPositionData(): JSObject { val obj = JSObject() obj.put("source", latestSource) obj.put("timestamp", if (latestTimestamp > 0) latestTimestamp else System.currentTimeMillis()) obj.put("latitude", latestLatitude) obj.put("longitude", latestLongitude) obj.put("accuracy", latestAccuracy) obj.put("isMocked", isMockedLocation) latestImu?.let { obj.put("speed", it.speed) obj.put("acceleration", it.acceleration) obj.put("directionRad", it.directionRad) } // === Full Detail (commented out for future use) === /* satelliteStatus?.let { val gnss = JSObject() gnss.put("satellitesInView", it.satellitesInView) gnss.put("usedInFix", it.usedInFix) val constellations = JSObject() it.constellationCounts.forEach { (k, v) -> constellations.put(k, v) } gnss.put("constellationCounts", constellations) obj.put("gnssData", gnss) } latestImu?.let { val imu = JSObject() imu.put("accelX", it.accelX) imu.put("accelY", it.accelY) imu.put("accelZ", it.accelZ) imu.put("gyroX", it.gyroX) imu.put("gyroY", it.gyroY) imu.put("gyroZ", it.gyroZ) imu.put("speed", it.speed) imu.put("acceleration", it.acceleration) imu.put("directionRad", it.directionRad) obj.put("imuData", imu) } wifiScanResult?.let { val wifi = JSArray() it.aps.forEach { ap -> val a = JSObject() a.put("ssid", ap.ssid) a.put("bssid", ap.bssid) a.put("rssi", ap.rssi) ap.distance?.let { d -> a.put("distance", d) } wifi.put(a) } obj.put("wifiData", wifi) } */ return obj } }