264 lines
8.8 KiB
Kotlin
264 lines
8.8 KiB
Kotlin
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
|
|
}
|
|
} |