updated 280925-01

This commit is contained in:
wengki81 2025-09-28 19:43:26 +08:00
parent 448824a187
commit f5be8180f2
22 changed files with 820 additions and 78 deletions

View File

@ -94,6 +94,52 @@ configureEdgeToEdge(options: {
Mengatur status bar dan navigasi bar agar transparan, dengan warna dan icon style sesuai UI. Mengatur status bar dan navigasi bar agar transparan, dengan warna dan icon style sesuai UI.
### setOptions()
```typescript
setOptions(options: {
distanceThresholdMeters?: number;
speedChangeThreshold?: number;
directionChangeThreshold?: number;
emitDebounceMs?: number;
drivingEmitIntervalMs?: number;
wifiScanIntervalMs?: number;
enableWifiRtt?: boolean;
enableLogging?: boolean;
enableForwardPrediction?: boolean;
maxPredictionSeconds?: number;
emitGnssStatus?: boolean;
suppressMockedUpdates?: boolean;
keepScreenOn?: boolean;
}): Promise<void>
```
Mengubah parameter runtime tanpa rebuild. Semua default menjaga perilaku saat ini.
### getGnssStatus()
```typescript
getGnssStatus(): Promise<SatelliteStatus | null>
```
Mengambil status GNSS terakhir untuk debugging.
### getLocationServicesStatus()
```typescript
getLocationServicesStatus(): Promise<{ gpsEnabled: boolean; networkEnabled: boolean }>
```
Memeriksa apakah provider lokasi aktif.
### addListener('onGnssStatus', …)
```typescript
addListener('onGnssStatus', (data: SatelliteStatus) => void): PluginListenerHandle
```
Menerima update status GNSS jika `emitGnssStatus: true` di setOptions.
--- ---
## Interfaces ## Interfaces
@ -102,7 +148,7 @@ PositioningData
```typescript ```typescript
interface PositioningData { interface PositioningData {
source: 'GNSS' | 'WIFI' | 'FUSED' | 'MOCK' | 'PREDICTED'; source: 'GNSS' | 'WIFI' | 'FUSED' | 'MOCK';
timestamp: number; timestamp: number;
latitude: number; latitude: number;
longitude: number; longitude: number;
@ -189,4 +235,4 @@ Contoh implementasi tersedia di folder /example-app dengan tombol:
--- ---
- Lisensi: MIT - Lisensi: MIT
- Dibuat oleh: Tim Dumon 🇮🇩 - Dibuat oleh: Tim Dumon 🇮🇩

View File

@ -25,6 +25,7 @@ import com.dumon.plugin.geolocation.wifi.WifiPositioningManager
import com.dumon.plugin.geolocation.wifi.WifiScanResult import com.dumon.plugin.geolocation.wifi.WifiScanResult
//import com.dumon.plugin.geolocation.fusion.SensorFusionManager //import com.dumon.plugin.geolocation.fusion.SensorFusionManager
import com.dumon.plugin.geolocation.utils.PermissionUtils import com.dumon.plugin.geolocation.utils.PermissionUtils
import com.dumon.plugin.geolocation.utils.LogUtils
import com.getcapacitor.annotation.PermissionCallback import com.getcapacitor.annotation.PermissionCallback
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
@ -67,9 +68,9 @@ class DumonGeolocation : Plugin() {
private var prevSpeed = 0f private var prevSpeed = 0f
private var prevDirection = 0f private var prevDirection = 0f
// private val significantChangeThreshold = 0.00007 // ~7 meters // private val significantChangeThreshold = 0.00007 // ~7 meters
private val significantChangeThreshold = 7 // ~7 meters private var significantChangeThreshold = 7.0 // ~7 meters
private val speedChangeThreshold = 0.5f // m/s private var speedChangeThreshold = 0.5f // m/s
private val directionChangeThreshold = 0.17f // ~10 deg private var directionChangeThreshold = 0.17f // ~10 deg
// private val emitIntervalMs: Long = 500L // private val emitIntervalMs: Long = 500L
private var emitIntervalMs: Long = 1000L // hard debounce private var emitIntervalMs: Long = 1000L // hard debounce
@ -80,17 +81,32 @@ class DumonGeolocation : Plugin() {
private var bufferedDrivingLocation: Location? = null private var bufferedDrivingLocation: Location? = null
private var drivingEmitHandler: Handler? = null private var drivingEmitHandler: Handler? = null
private var drivingEmitRunnable: Runnable? = null private var drivingEmitRunnable: Runnable? = null
private val drivingEmitIntervalMs = 1600L private var drivingEmitIntervalMs = 1600L
private var wifiScanIntervalMs = 3000L
private var enableWifiRtt = true
private var enableForwardPrediction = false
private var maxPredictionSeconds = 1.0
private var emitGnssStatus = false
private var suppressMockedUpdates = false
private var keepScreenOn = false
private var currentTrackingMode = GpsTrackingMode.NORMAL private var currentTrackingMode = GpsTrackingMode.NORMAL
override fun load() { override fun load() {
gpsManager = GpsStatusManager( gpsManager = GpsStatusManager(
context, context,
onSatelliteStatusUpdate = { satelliteStatus = it }, onSatelliteStatusUpdate = { status ->
satelliteStatus = status
if (emitGnssStatus) {
Handler(Looper.getMainLooper()).post {
notifyListeners("onGnssStatus", buildGnssStatusData(status))
}
}
},
onLocationUpdate = onLocationUpdate@{ location, isMocked -> onLocationUpdate = onLocationUpdate@{ location, isMocked ->
if (location.latitude == 0.0 && location.longitude == 0.0) { if (location.latitude == 0.0 && location.longitude == 0.0) {
Log.w("GPS_LOCATION", "Ignored location update: (0.0, 0.0)") LogUtils.w("GPS_LOCATION", "Ignored location update: (0.0, 0.0)")
return@onLocationUpdate return@onLocationUpdate
} }
@ -148,7 +164,9 @@ class DumonGeolocation : Plugin() {
} }
private fun stopDrivingEmitLoop() { private fun stopDrivingEmitLoop() {
drivingEmitHandler?.removeCallbacks(drivingEmitRunnable!!) drivingEmitRunnable?.let { runnable ->
drivingEmitHandler?.removeCallbacks(runnable)
}
drivingEmitHandler = null drivingEmitHandler = null
drivingEmitRunnable = null drivingEmitRunnable = null
bufferedDrivingLocation = null bufferedDrivingLocation = null
@ -163,7 +181,9 @@ class DumonGeolocation : Plugin() {
gpsManager?.start() gpsManager?.start()
imuManager?.start() imuManager?.start()
wifiManager?.startPeriodicScan(3000L) wifiManager?.setEnableRtt(enableWifiRtt)
wifiManager?.startPeriodicScan(wifiScanIntervalMs)
applyKeepScreenOn(keepScreenOn)
call.resolve() call.resolve()
} }
@ -173,6 +193,7 @@ class DumonGeolocation : Plugin() {
imuManager?.stop() imuManager?.stop()
wifiManager?.stopPeriodicScan() wifiManager?.stopPeriodicScan()
stopDrivingEmitLoop() stopDrivingEmitLoop()
applyKeepScreenOn(false)
call.resolve() call.resolve()
} }
@ -184,7 +205,18 @@ class DumonGeolocation : Plugin() {
@PluginMethod @PluginMethod
fun checkAndRequestPermissions(call: PluginCall) { fun checkAndRequestPermissions(call: PluginCall) {
// requestAllPermissions(call, "checkAndRequestPermissions") // requestAllPermissions(call, "checkAndRequestPermissions")
requestAllPermissions(call, "onPermissionResult") val isLocationGranted =
ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
val isWifiGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ActivityCompat.checkSelfPermission(context, Manifest.permission.NEARBY_WIFI_DEVICES) == PackageManager.PERMISSION_GRANTED
} else {
true
}
if (!isLocationGranted || !isWifiGranted) {
requestAllPermissions(call, "onPermissionResult")
return
}
val locationStatus = PermissionUtils.getPermissionStatus( val locationStatus = PermissionUtils.getPermissionStatus(
ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
@ -272,12 +304,12 @@ class DumonGeolocation : Plugin() {
gpsManager?.startContinuousMode() gpsManager?.startContinuousMode()
currentTrackingMode = GpsTrackingMode.DRIVING currentTrackingMode = GpsTrackingMode.DRIVING
startDrivingEmitLoop() startDrivingEmitLoop()
Log.d("DUMON_GEOLOCATION", "Switched to driving mode (continuous GPS)") LogUtils.d("DUMON_GEOLOCATION", "Switched to driving mode (continuous GPS)")
} else { } else {
gpsManager?.startPollingMode() gpsManager?.startPollingMode()
currentTrackingMode = GpsTrackingMode.NORMAL currentTrackingMode = GpsTrackingMode.NORMAL
stopDrivingEmitLoop() stopDrivingEmitLoop()
Log.d("DUMON_GEOLOCATION", "Switched to normal mode (polling GPS)") LogUtils.d("DUMON_GEOLOCATION", "Switched to normal mode (polling GPS)")
} }
call.resolve() call.resolve()
} }
@ -302,9 +334,89 @@ class DumonGeolocation : Plugin() {
call.resolve(result) call.resolve(result)
} }
@PluginMethod
fun setOptions(call: PluginCall) {
call.getDouble("distanceThresholdMeters")?.let { significantChangeThreshold = it }
call.getDouble("speedChangeThreshold")?.let { speedChangeThreshold = it.toFloat() }
call.getDouble("directionChangeThreshold")?.let { directionChangeThreshold = it.toFloat() }
call.getInt("emitDebounceMs")?.let {
emitIntervalMs = it.toLong().coerceAtLeast(0L)
gpsManager?.setPollingInterval(emitIntervalMs)
}
call.getInt("drivingEmitIntervalMs")?.let {
drivingEmitIntervalMs = it.toLong().coerceAtLeast(200L)
if (currentTrackingMode == GpsTrackingMode.DRIVING) {
stopDrivingEmitLoop()
startDrivingEmitLoop()
}
}
call.getInt("wifiScanIntervalMs")?.let {
wifiScanIntervalMs = it.toLong().coerceAtLeast(1000L)
}
call.getBoolean("enableWifiRtt")?.let {
enableWifiRtt = it
wifiManager?.setEnableRtt(it)
}
call.getBoolean("enableLogging")?.let { LogUtils.enabled = it }
call.getBoolean("enableForwardPrediction")?.let { enableForwardPrediction = it }
call.getDouble("maxPredictionSeconds")?.let { maxPredictionSeconds = it.coerceIn(0.0, 5.0) }
call.getBoolean("emitGnssStatus")?.let { emitGnssStatus = it }
call.getBoolean("suppressMockedUpdates")?.let { suppressMockedUpdates = it }
call.getBoolean("keepScreenOn")?.let {
keepScreenOn = it
applyKeepScreenOn(keepScreenOn)
}
call.resolve()
}
private fun applyKeepScreenOn(enabled: Boolean) {
val webView = bridge?.webView
val activity = bridge?.activity
Handler(Looper.getMainLooper()).post {
webView?.keepScreenOn = enabled
val window = activity?.window
if (window != null) {
if (enabled) {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
}
}
}
@PluginMethod
fun getGnssStatus(call: PluginCall) {
val status = satelliteStatus
if (status == null) {
call.resolve(JSObject())
return
}
val obj = JSObject()
obj.put("satellitesInView", status.satellitesInView)
obj.put("usedInFix", status.usedInFix)
val counts = JSObject()
status.constellationCounts.forEach { (k, v) -> counts.put(k, v) }
obj.put("constellationCounts", counts)
call.resolve(obj)
}
@PluginMethod
fun getLocationServicesStatus(call: PluginCall) {
val lm = context.getSystemService(android.content.Context.LOCATION_SERVICE) as android.location.LocationManager
val gpsEnabled = try { lm.isProviderEnabled(android.location.LocationManager.GPS_PROVIDER) } catch (_: Exception) { false }
val netEnabled = try { lm.isProviderEnabled(android.location.LocationManager.NETWORK_PROVIDER) } catch (_: Exception) { false }
val obj = JSObject().apply {
put("gpsEnabled", gpsEnabled)
put("networkEnabled", netEnabled)
}
call.resolve(obj)
}
private fun emitPositionUpdate(forceEmit: Boolean = false) { private fun emitPositionUpdate(forceEmit: Boolean = false) {
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
if (!forceEmit && now - lastEmitTimestamp < emitIntervalMs) return if (!forceEmit && now - lastEmitTimestamp < emitIntervalMs) return
if (suppressMockedUpdates && isMockedLocation) return
val distance = calculateDistance(latestLatitude, latestLongitude, prevLatitude, prevLongitude) val distance = calculateDistance(latestLatitude, latestLongitude, prevLatitude, prevLongitude)
val speedNow = latestImu?.speed ?: 0f val speedNow = latestImu?.speed ?: 0f
@ -330,7 +442,10 @@ class DumonGeolocation : Plugin() {
prevDirection = directionNow prevDirection = directionNow
lastEmitTimestamp = now lastEmitTimestamp = now
notifyListeners("onPositionUpdate", buildPositionData()) // Ensure listener notifications run on the main thread for consistency
Handler(Looper.getMainLooper()).post {
notifyListeners("onPositionUpdate", buildPositionData())
}
} }
} }
@ -345,7 +460,7 @@ class DumonGeolocation : Plugin() {
if (emitIntervalMs != targetInterval) { if (emitIntervalMs != targetInterval) {
emitIntervalMs = targetInterval emitIntervalMs = targetInterval
gpsManager?.setPollingInterval(targetInterval) gpsManager?.setPollingInterval(targetInterval)
Log.d("DUMON_GEOLOCATION", "Auto-set emitIntervalMs = $emitIntervalMs ms") LogUtils.d("DUMON_GEOLOCATION", "Auto-set emitIntervalMs = $emitIntervalMs ms")
} }
imuManager?.setSensorDelayBySpeed(speed) imuManager?.setSensorDelayBySpeed(speed)
@ -369,23 +484,67 @@ class DumonGeolocation : Plugin() {
return R * c return R * c
} }
private fun buildGnssStatusData(status: SatelliteStatus): JSObject {
val obj = JSObject()
obj.put("satellitesInView", status.satellitesInView)
obj.put("usedInFix", status.usedInFix)
val counts = JSObject()
status.constellationCounts.forEach { (k, v) -> counts.put(k, v) }
obj.put("constellationCounts", counts)
return obj
}
private fun buildPositionData(): JSObject { private fun buildPositionData(): JSObject {
val obj = JSObject() val obj = JSObject()
val now = System.currentTimeMillis()
var outLat = latestLatitude
var outLon = latestLongitude
var predicted = false
val imu = latestImu
val dtSec = ((now - (if (latestTimestamp > 0) latestTimestamp else now)).toDouble() / 1000.0)
if (enableForwardPrediction && imu != null && !isMockedLocation && dtSec > 0) {
val clampedDt = dtSec.coerceAtMost(maxPredictionSeconds)
val speed = imu.speed
val dir = imu.directionRad
val dNorth = speed * clampedDt * kotlin.math.cos(dir)
val dEast = speed * clampedDt * kotlin.math.sin(dir)
val R = 6371000.0
val latRad = degToRad(latestLatitude)
val dLat = (dNorth / R) * (180.0 / PI)
val dLon = (dEast / (R * kotlin.math.cos(latRad))) * (180.0 / PI)
outLat = latestLatitude + dLat
outLon = latestLongitude + dLon
predicted = true
}
obj.put("source", latestSource) obj.put("source", latestSource)
obj.put("timestamp", if (latestTimestamp > 0) latestTimestamp else System.currentTimeMillis()) obj.put("timestamp", if (latestTimestamp > 0) latestTimestamp else now)
obj.put("latitude", latestLatitude) obj.put("latitude", outLat)
obj.put("longitude", latestLongitude) obj.put("longitude", outLon)
obj.put("accuracy", latestAccuracy) obj.put("accuracy", latestAccuracy)
obj.put("isMocked", isMockedLocation) obj.put("isMocked", isMockedLocation)
latestImu?.let { // Always provide IMU-related fields to match TS definitions
obj.put("speed", it.speed) val speedVal = latestImu?.speed ?: 0f
obj.put("acceleration", it.acceleration) val accelVal = latestImu?.acceleration ?: 0f
obj.put("directionRad", it.directionRad) val dirVal = latestImu?.directionRad ?: 0f
} obj.put("speed", speedVal)
obj.put("acceleration", accelVal)
obj.put("directionRad", dirVal)
obj.put("predicted", latestSource == "PREDICTED") obj.put("predicted", predicted)
return obj return obj
} }
}
override fun handleOnDestroy() {
gpsManager?.stop()
imuManager?.stop()
wifiManager?.stopPeriodicScan()
stopDrivingEmitLoop()
applyKeepScreenOn(false)
super.handleOnDestroy()
}
}

View File

@ -13,7 +13,9 @@ import android.os.Handler
import android.os.Looper import android.os.Looper
import android.text.format.DateFormat import android.text.format.DateFormat
import android.util.Log import android.util.Log
import com.dumon.plugin.geolocation.utils.LogUtils
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
enum class GpsTrackingMode { enum class GpsTrackingMode {
NORMAL, NORMAL,
@ -73,7 +75,7 @@ class GpsStatusManager(
} }
val info = "$providerTag Lat: ${location.latitude}, Lon: ${location.longitude}, Acc: ${location.accuracy} m @ $timestamp | Mock=$isMocked" val info = "$providerTag Lat: ${location.latitude}, Lon: ${location.longitude}, Acc: ${location.accuracy} m @ $timestamp | Mock=$isMocked"
Log.d("GPS_LOCATION", info) LogUtils.d("GPS_LOCATION", info)
onLocationUpdate(location, isMocked) onLocationUpdate(location, isMocked)
locationManager.removeUpdates(this) locationManager.removeUpdates(this)
@ -92,7 +94,7 @@ class GpsStatusManager(
oneShotListener oneShotListener
) )
} else { } else {
Log.e("GPS_STATUS", "Missing location permission") LogUtils.e("GPS_STATUS", "Missing location permission")
} }
} }
@ -106,6 +108,10 @@ class GpsStatusManager(
fun setPollingInterval(intervalMs: Long) { fun setPollingInterval(intervalMs: Long) {
this.pollingIntervalMs = intervalMs this.pollingIntervalMs = intervalMs
if (isPolling) {
handler.removeCallbacks(pollingRunnable)
handler.postDelayed(pollingRunnable, pollingIntervalMs)
}
} }
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
@ -114,11 +120,18 @@ class GpsStatusManager(
currentMode = mode currentMode = mode
if (ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Log.e("GPS_STATUS", "Missing location permissions") LogUtils.e("GPS_STATUS", "Missing location permissions")
return return
} }
locationManager.registerGnssStatusCallback(gnssStatusCallback, null) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
locationManager.registerGnssStatusCallback(
ContextCompat.getMainExecutor(context),
gnssStatusCallback
)
} else {
locationManager.registerGnssStatusCallback(gnssStatusCallback, handler)
}
if (mode == GpsTrackingMode.DRIVING) { if (mode == GpsTrackingMode.DRIVING) {
startContinuousUpdates() startContinuousUpdates()
@ -139,12 +152,12 @@ class GpsStatusManager(
} }
} }
Log.d("GPS_STATUS", "GPS started with mode: $mode") LogUtils.d("GPS_STATUS", "GPS started with mode: $mode")
} }
private fun startContinuousUpdates() { private fun startContinuousUpdates() {
if (ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Log.e("GPS_STATUS", "Missing ACCESS_FINE_LOCATION permission") LogUtils.e("GPS_STATUS", "Missing ACCESS_FINE_LOCATION permission")
return return
} }
@ -166,7 +179,7 @@ class GpsStatusManager(
continuousListener!! continuousListener!!
) )
Log.d("GPS_STATUS", "Started continuous location updates") LogUtils.d("GPS_STATUS", "Started continuous location updates")
} }
fun startContinuousMode() { fun startContinuousMode() {
@ -188,7 +201,7 @@ class GpsStatusManager(
locationManager.removeUpdates(it) locationManager.removeUpdates(it)
continuousListener = null continuousListener = null
} }
Log.d("GPS_STATUS", "GPS stopped for mode: $currentMode") LogUtils.d("GPS_STATUS", "GPS stopped for mode: $currentMode")
} }
private fun getConstellationName(type: Int): String { private fun getConstellationName(type: Int): String {
@ -209,4 +222,4 @@ data class SatelliteStatus(
val satellitesInView: Int, val satellitesInView: Int,
val usedInFix: Int, val usedInFix: Int,
val constellationCounts: Map<String, Int> val constellationCounts: Map<String, Int>
) )

View File

@ -3,6 +3,7 @@ package com.dumon.plugin.geolocation.imu
import android.content.Context import android.content.Context
import android.hardware.* import android.hardware.*
import android.util.Log import android.util.Log
import com.dumon.plugin.geolocation.utils.LogUtils
import kotlin.math.* import kotlin.math.*
class ImuSensorManager( class ImuSensorManager(
@ -35,12 +36,12 @@ class ImuSensorManager(
accelerometer?.let { sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_GAME) } accelerometer?.let { sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_GAME) }
gyroscope?.let { sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_GAME) } gyroscope?.let { sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_GAME) }
rotationVector?.let { sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_GAME) } rotationVector?.let { sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_GAME) }
Log.d("IMU_SENSOR", "IMU sensor tracking started") LogUtils.d("IMU_SENSOR", "IMU sensor tracking started")
} }
fun stop() { fun stop() {
sensorManager.unregisterListener(this) sensorManager.unregisterListener(this)
Log.d("IMU_SENSOR", "IMU sensor tracking stopped") LogUtils.d("IMU_SENSOR", "IMU sensor tracking stopped")
} }
fun setSensorDelayBySpeed(speed: Float) { fun setSensorDelayBySpeed(speed: Float) {
@ -54,7 +55,7 @@ class ImuSensorManager(
if (desiredDelay != currentDelay) { if (desiredDelay != currentDelay) {
currentDelay = desiredDelay currentDelay = desiredDelay
restartSensorsWithDelay(currentDelay) restartSensorsWithDelay(currentDelay)
Log.d("IMU_SENSOR", "Sensor delay changed to $currentDelay") LogUtils.d("IMU_SENSOR", "Sensor delay changed to $currentDelay")
} }
} }
@ -110,7 +111,7 @@ class ImuSensorManager(
emitCombinedImuData(speed, acceleration, latestDirectionRad) emitCombinedImuData(speed, acceleration, latestDirectionRad)
Log.d("IMU_SENSOR", "Accel x: %.3f y: %.3f z: %.3f | Speed: %.3f | AccelMag: %.3f | Dir: %.2f rad".format( LogUtils.d("IMU_SENSOR", "Accel x: %.3f y: %.3f z: %.3f | Speed: %.3f | AccelMag: %.3f | Dir: %.2f rad".format(
lastAccel[0], lastAccel[1], lastAccel[2], speed, acceleration, latestDirectionRad lastAccel[0], lastAccel[1], lastAccel[2], speed, acceleration, latestDirectionRad
)) ))
} }
@ -165,4 +166,4 @@ data class ImuData(
val speed: Float, val speed: Float,
val acceleration: Float, val acceleration: Float,
val directionRad: Float val directionRad: Float
) )

View File

@ -0,0 +1,22 @@
package com.dumon.plugin.geolocation.utils
import android.util.Log
object LogUtils {
@Volatile
var enabled: Boolean = true
fun d(tag: String, msg: String) {
if (enabled) Log.d(tag, msg)
}
fun w(tag: String, msg: String) {
if (enabled) Log.w(tag, msg)
}
fun e(tag: String, msg: String, tr: Throwable? = null) {
if (!enabled) return
if (tr != null) Log.e(tag, msg, tr) else Log.e(tag, msg)
}
}

View File

@ -2,6 +2,9 @@ package com.dumon.plugin.geolocation.wifi
import android.Manifest import android.Manifest
import android.content.Context import android.content.Context
import android.content.BroadcastReceiver
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.wifi.ScanResult import android.net.wifi.ScanResult
import android.net.wifi.WifiManager import android.net.wifi.WifiManager
@ -10,8 +13,9 @@ import android.net.wifi.rtt.RangingResultCallback
import android.net.wifi.rtt.WifiRttManager import android.net.wifi.rtt.WifiRttManager
import android.os.* import android.os.*
import android.util.Log import android.util.Log
import com.dumon.plugin.geolocation.utils.LogUtils
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import java.util.concurrent.Executors import androidx.core.content.ContextCompat
class WifiPositioningManager( class WifiPositioningManager(
private val context: Context, private val context: Context,
@ -30,6 +34,9 @@ class WifiPositioningManager(
private var isScanning = false private var isScanning = false
private var lastWifiScanAps: MutableList<WifiAp> = mutableListOf() private var lastWifiScanAps: MutableList<WifiAp> = mutableListOf()
private var scanResultsReceiver: BroadcastReceiver? = null
private var scanReceiverRegistered: Boolean = false
private var enableRtt: Boolean = true
fun isRttSupported(): Boolean { fun isRttSupported(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && wifiRttManager != null && wifiManager.isWifiEnabled return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && wifiRttManager != null && wifiManager.isWifiEnabled
@ -37,6 +44,7 @@ class WifiPositioningManager(
fun startPeriodicScan(intervalMs: Long = 3000L) { fun startPeriodicScan(intervalMs: Long = 3000L) {
isScanning = true isScanning = true
registerScanResultsReceiverIfNeeded()
handler.post(object : Runnable { handler.post(object : Runnable {
override fun run() { override fun run() {
if (isScanning) { if (isScanning) {
@ -50,6 +58,7 @@ class WifiPositioningManager(
fun stopPeriodicScan() { fun stopPeriodicScan() {
isScanning = false isScanning = false
handler.removeCallbacksAndMessages(null) handler.removeCallbacksAndMessages(null)
unregisterScanResultsReceiverIfNeeded()
} }
fun startWifiScan() { fun startWifiScan() {
@ -59,22 +68,50 @@ class WifiPositioningManager(
) == PackageManager.PERMISSION_GRANTED ) == PackageManager.PERMISSION_GRANTED
) { ) {
val success = wifiManager.startScan() val success = wifiManager.startScan()
if (success) { if (!success) {
val results = wifiManager.scanResults LogUtils.e("WIFI_POSITION", "Wi-Fi scan failed")
processScanResults(results)
if (isRttSupported()) {
startRttRanging(results)
}
} else {
Log.e("WIFI_POSITION", "Wi-Fi scan failed")
onWifiPositioningUpdate(WifiScanResult(0, emptyList())) onWifiPositioningUpdate(WifiScanResult(0, emptyList()))
} }
} else { } else {
Log.e("WIFI_POSITION", "Missing ACCESS_FINE_LOCATION permission") LogUtils.e("WIFI_POSITION", "Missing ACCESS_FINE_LOCATION permission")
onWifiPositioningUpdate(WifiScanResult(0, emptyList())) onWifiPositioningUpdate(WifiScanResult(0, emptyList()))
} }
} }
private fun registerScanResultsReceiverIfNeeded() {
if (scanReceiverRegistered) return
scanResultsReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action == WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) {
val results = wifiManager.scanResults
processScanResults(results)
if (enableRtt && isRttSupported()) {
startRttRanging(results)
}
}
}
}
val filter = IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)
try {
context.registerReceiver(scanResultsReceiver, filter)
scanReceiverRegistered = true
} catch (e: Exception) {
LogUtils.e("WIFI_POSITION", "Failed to register scan receiver", e)
}
}
private fun unregisterScanResultsReceiverIfNeeded() {
if (!scanReceiverRegistered) return
try {
context.unregisterReceiver(scanResultsReceiver)
} catch (e: IllegalArgumentException) {
// Receiver already unregistered
} finally {
scanReceiverRegistered = false
scanResultsReceiver = null
}
}
private fun processScanResults(results: List<ScanResult>) { private fun processScanResults(results: List<ScanResult>) {
lastWifiScanAps = results.map { result -> lastWifiScanAps = results.map { result ->
WifiAp( WifiAp(
@ -84,9 +121,9 @@ class WifiPositioningManager(
) )
}.toMutableList() }.toMutableList()
Log.d("WIFI_POSITION", "Wi-Fi scan → AP count: ${lastWifiScanAps.size}") LogUtils.d("WIFI_POSITION", "Wi-Fi scan → AP count: ${lastWifiScanAps.size}")
lastWifiScanAps.forEach { lastWifiScanAps.forEach {
Log.d("WIFI_POSITION", "SSID: ${it.ssid}, BSSID: ${it.bssid}, RSSI: ${it.rssi} dBm") LogUtils.d("WIFI_POSITION", "SSID: ${it.ssid}, BSSID: ${it.bssid}, RSSI: ${it.rssi} dBm")
} }
onWifiPositioningUpdate(WifiScanResult(lastWifiScanAps.size, lastWifiScanAps)) onWifiPositioningUpdate(WifiScanResult(lastWifiScanAps.size, lastWifiScanAps))
@ -94,13 +131,13 @@ class WifiPositioningManager(
private fun startRttRanging(results: List<ScanResult>) { private fun startRttRanging(results: List<ScanResult>) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P || wifiRttManager == null) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P || wifiRttManager == null) {
Log.d("WIFI_POSITION", "RTT not supported or WifiRttManager is null.") LogUtils.d("WIFI_POSITION", "RTT not supported or WifiRttManager is null.")
return return
} }
val rttCapableAps = results.filter { it.is80211mcResponder } val rttCapableAps = results.filter { it.is80211mcResponder }
if (rttCapableAps.isEmpty()) { if (rttCapableAps.isEmpty()) {
Log.d("WIFI_POSITION", "No RTT-capable AP found.") LogUtils.d("WIFI_POSITION", "No RTT-capable AP found.")
return return
} }
@ -111,10 +148,10 @@ class WifiPositioningManager(
try { try {
wifiRttManager.startRanging( wifiRttManager.startRanging(
rangingRequest, rangingRequest,
Executors.newSingleThreadExecutor(), ContextCompat.getMainExecutor(context),
object : RangingResultCallback() { object : RangingResultCallback() {
override fun onRangingFailure(code: Int) { override fun onRangingFailure(code: Int) {
Log.e("WIFI_POSITION", "RTT Ranging failed: $code") LogUtils.e("WIFI_POSITION", "RTT Ranging failed: $code")
} }
override fun onRangingResults(results: List<android.net.wifi.rtt.RangingResult>) { override fun onRangingResults(results: List<android.net.wifi.rtt.RangingResult>) {
@ -123,7 +160,7 @@ class WifiPositioningManager(
val distance = if (result.status == android.net.wifi.rtt.RangingResult.STATUS_SUCCESS) { val distance = if (result.status == android.net.wifi.rtt.RangingResult.STATUS_SUCCESS) {
result.distanceMm / 1000.0 result.distanceMm / 1000.0
} else { } else {
Log.w("WIFI_POSITION", "RTT distance unavailable for ${result.macAddress}") LogUtils.w("WIFI_POSITION", "RTT distance unavailable for ${result.macAddress}")
null null
} }
@ -131,7 +168,7 @@ class WifiPositioningManager(
lastWifiScanAps[idx] = lastWifiScanAps[idx].copy(distance = distance) lastWifiScanAps[idx] = lastWifiScanAps[idx].copy(distance = distance)
} }
Log.d( LogUtils.d(
"WIFI_POSITION", "WIFI_POSITION",
if (distance != null) if (distance != null)
"RTT → ${mac}, Distance: ${distance} m" "RTT → ${mac}, Distance: ${distance} m"
@ -145,9 +182,13 @@ class WifiPositioningManager(
} }
) )
} catch (e: SecurityException) { } catch (e: SecurityException) {
Log.e("WIFI_POSITION", "SecurityException: Missing NEARBY_WIFI_DEVICES permission", e) LogUtils.e("WIFI_POSITION", "SecurityException: Missing NEARBY_WIFI_DEVICES permission", e)
} }
} }
fun setEnableRtt(enable: Boolean) {
enableRtt = enable
}
} }
data class WifiAp( data class WifiAp(
@ -160,4 +201,4 @@ data class WifiAp(
data class WifiScanResult( data class WifiScanResult(
val apCount: Int, val apCount: Int,
val aps: List<WifiAp> val aps: List<WifiAp>
) )

194
dist/docs.json vendored
View File

@ -49,6 +49,46 @@
], ],
"slug": "checkandrequestpermissions" "slug": "checkandrequestpermissions"
}, },
{
"name": "setOptions",
"signature": "(options: DumonGeoOptions) => Promise<void>",
"parameters": [
{
"name": "options",
"docs": "",
"type": "DumonGeoOptions"
}
],
"returns": "Promise<void>",
"tags": [],
"docs": "",
"complexTypes": [
"DumonGeoOptions"
],
"slug": "setoptions"
},
{
"name": "getGnssStatus",
"signature": "() => Promise<SatelliteStatus | null>",
"parameters": [],
"returns": "Promise<SatelliteStatus | null>",
"tags": [],
"docs": "",
"complexTypes": [
"SatelliteStatus"
],
"slug": "getgnssstatus"
},
{
"name": "getLocationServicesStatus",
"signature": "() => Promise<{ gpsEnabled: boolean; networkEnabled: boolean; }>",
"parameters": [],
"returns": "Promise<{ gpsEnabled: boolean; networkEnabled: boolean; }>",
"tags": [],
"docs": "",
"complexTypes": [],
"slug": "getlocationservicesstatus"
},
{ {
"name": "configureEdgeToEdge", "name": "configureEdgeToEdge",
"signature": "(options: { bgColor: string; style: 'DARK' | 'LIGHT'; overlay?: boolean; }) => Promise<void>", "signature": "(options: { bgColor: string; style: 'DARK' | 'LIGHT'; overlay?: boolean; }) => Promise<void>",
@ -104,6 +144,30 @@
"PositioningData" "PositioningData"
], ],
"slug": "addlisteneronpositionupdate-" "slug": "addlisteneronpositionupdate-"
},
{
"name": "addListener",
"signature": "(eventName: 'onGnssStatus', listenerFunc: (data: SatelliteStatus) => void) => PluginListenerHandle",
"parameters": [
{
"name": "eventName",
"docs": "",
"type": "'onGnssStatus'"
},
{
"name": "listenerFunc",
"docs": "",
"type": "(data: SatelliteStatus) => void"
}
],
"returns": "PluginListenerHandle",
"tags": [],
"docs": "",
"complexTypes": [
"PluginListenerHandle",
"SatelliteStatus"
],
"slug": "addlistenerongnssstatus-"
} }
], ],
"properties": [] "properties": []
@ -211,6 +275,136 @@
} }
] ]
}, },
{
"name": "DumonGeoOptions",
"slug": "dumongeooptions",
"docs": "",
"tags": [],
"methods": [],
"properties": [
{
"name": "distanceThresholdMeters",
"tags": [],
"docs": "",
"complexTypes": [],
"type": "number | undefined"
},
{
"name": "speedChangeThreshold",
"tags": [],
"docs": "",
"complexTypes": [],
"type": "number | undefined"
},
{
"name": "directionChangeThreshold",
"tags": [],
"docs": "",
"complexTypes": [],
"type": "number | undefined"
},
{
"name": "emitDebounceMs",
"tags": [],
"docs": "",
"complexTypes": [],
"type": "number | undefined"
},
{
"name": "drivingEmitIntervalMs",
"tags": [],
"docs": "",
"complexTypes": [],
"type": "number | undefined"
},
{
"name": "wifiScanIntervalMs",
"tags": [],
"docs": "",
"complexTypes": [],
"type": "number | undefined"
},
{
"name": "enableWifiRtt",
"tags": [],
"docs": "",
"complexTypes": [],
"type": "boolean | undefined"
},
{
"name": "enableLogging",
"tags": [],
"docs": "",
"complexTypes": [],
"type": "boolean | undefined"
},
{
"name": "enableForwardPrediction",
"tags": [],
"docs": "",
"complexTypes": [],
"type": "boolean | undefined"
},
{
"name": "maxPredictionSeconds",
"tags": [],
"docs": "",
"complexTypes": [],
"type": "number | undefined"
},
{
"name": "emitGnssStatus",
"tags": [],
"docs": "",
"complexTypes": [],
"type": "boolean | undefined"
},
{
"name": "suppressMockedUpdates",
"tags": [],
"docs": "",
"complexTypes": [],
"type": "boolean | undefined"
},
{
"name": "keepScreenOn",
"tags": [],
"docs": "",
"complexTypes": [],
"type": "boolean | undefined"
}
]
},
{
"name": "SatelliteStatus",
"slug": "satellitestatus",
"docs": "",
"tags": [],
"methods": [],
"properties": [
{
"name": "satellitesInView",
"tags": [],
"docs": "",
"complexTypes": [],
"type": "number"
},
{
"name": "usedInFix",
"tags": [],
"docs": "",
"complexTypes": [],
"type": "number"
},
{
"name": "constellationCounts",
"tags": [],
"docs": "",
"complexTypes": [],
"type": "{ [key: string]: number; }"
}
]
},
{ {
"name": "PluginListenerHandle", "name": "PluginListenerHandle",
"slug": "pluginlistenerhandle", "slug": "pluginlistenerhandle",

View File

@ -11,6 +11,28 @@ export interface PositioningData {
isMocked: boolean; isMocked: boolean;
predicted?: boolean; predicted?: boolean;
} }
export interface SatelliteStatus {
satellitesInView: number;
usedInFix: number;
constellationCounts: {
[key: string]: number;
};
}
export interface DumonGeoOptions {
distanceThresholdMeters?: number;
speedChangeThreshold?: number;
directionChangeThreshold?: number;
emitDebounceMs?: number;
drivingEmitIntervalMs?: number;
wifiScanIntervalMs?: number;
enableWifiRtt?: boolean;
enableLogging?: boolean;
enableForwardPrediction?: boolean;
maxPredictionSeconds?: number;
emitGnssStatus?: boolean;
suppressMockedUpdates?: boolean;
keepScreenOn?: boolean;
}
export interface PermissionStatus { export interface PermissionStatus {
location: 'granted' | 'denied'; location: 'granted' | 'denied';
wifi: 'granted' | 'denied'; wifi: 'granted' | 'denied';
@ -20,6 +42,12 @@ export interface DumonGeolocationPlugin {
stopPositioning(): Promise<void>; stopPositioning(): Promise<void>;
getLatestPosition(): Promise<PositioningData>; getLatestPosition(): Promise<PositioningData>;
checkAndRequestPermissions(): Promise<PermissionStatus>; checkAndRequestPermissions(): Promise<PermissionStatus>;
setOptions(options: DumonGeoOptions): Promise<void>;
getGnssStatus(): Promise<SatelliteStatus | null>;
getLocationServicesStatus(): Promise<{
gpsEnabled: boolean;
networkEnabled: boolean;
}>;
configureEdgeToEdge(options: { configureEdgeToEdge(options: {
bgColor: string; bgColor: string;
style: 'DARK' | 'LIGHT'; style: 'DARK' | 'LIGHT';
@ -29,4 +57,5 @@ export interface DumonGeolocationPlugin {
mode: 'normal' | 'driving'; mode: 'normal' | 'driving';
}): Promise<void>; }): Promise<void>;
addListener(eventName: 'onPositionUpdate', listenerFunc: (data: PositioningData) => void): PluginListenerHandle; addListener(eventName: 'onPositionUpdate', listenerFunc: (data: PositioningData) => void): PluginListenerHandle;
addListener(eventName: 'onGnssStatus', listenerFunc: (data: SatelliteStatus) => void): PluginListenerHandle;
} }

View File

@ -1 +1 @@
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\n// export interface SatelliteStatus {\n// satellitesInView: number;\n// usedInFix: number;\n// constellationCounts: { [key: string]: number };\n// }\n\n// export interface WifiAp {\n// ssid: string;\n// bssid: string;\n// rssi: number;\n// distance?: number;\n// }\n\n// export interface WifiScanResult {\n// apCount: number;\n// aps: WifiAp[];\n// }\n\n// export interface ImuData {\n// accelX: number;\n// accelY: number;\n// accelZ: number;\n// gyroX: number;\n// gyroY: number;\n// gyroZ: number;\n// speed?: number;\n// acceleration?: number;\n// directionRad?: number;\n// }\n\n// export interface GpsData {\n// latitude: number;\n// longitude: number;\n// accuracy: number;\n// satellitesInView?: number;\n// usedInFix?: number;\n// constellationCounts?: { [key: string]: number };\n// }\n\n// export interface PositioningData {\n// source: 'GNSS' | 'WIFI' | 'FUSED' | 'MOCK';\n// timestamp: number;\n// latitude: number;\n// longitude: number;\n// accuracy: number;\n\n// gnssData?: SatelliteStatus;\n// wifiData?: WifiAp[];\n// imuData?: ImuData;\n// }\n\nexport interface PositioningData {\n source: 'GNSS' | 'WIFI' | 'FUSED' | 'MOCK';\n timestamp: number;\n latitude: number;\n longitude: number;\n accuracy: number;\n speed: number;\n acceleration: number;\n directionRad: number;\n isMocked: boolean;\n predicted?: boolean;\n}\n\nexport interface PermissionStatus {\n location: 'granted' | 'denied';\n wifi: 'granted' | 'denied';\n}\n\nexport interface DumonGeolocationPlugin {\n startPositioning(): Promise<void>;\n stopPositioning(): Promise<void>;\n getLatestPosition(): Promise<PositioningData>;\n checkAndRequestPermissions(): Promise<PermissionStatus>;\n\n configureEdgeToEdge(options: {\n bgColor: string;\n style: 'DARK' | 'LIGHT';\n overlay?: boolean;\n }): Promise<void>;\n\n setGpsMode(options: { mode: 'normal' | 'driving' }): Promise<void>;\n\n addListener(\n eventName: 'onPositionUpdate',\n listenerFunc: (data: PositioningData) => void\n ): PluginListenerHandle;\n}"]} {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\n// export interface SatelliteStatus {\n// satellitesInView: number;\n// usedInFix: number;\n// constellationCounts: { [key: string]: number };\n// }\n\n// export interface WifiAp {\n// ssid: string;\n// bssid: string;\n// rssi: number;\n// distance?: number;\n// }\n\n// export interface WifiScanResult {\n// apCount: number;\n// aps: WifiAp[];\n// }\n\n// export interface ImuData {\n// accelX: number;\n// accelY: number;\n// accelZ: number;\n// gyroX: number;\n// gyroY: number;\n// gyroZ: number;\n// speed?: number;\n// acceleration?: number;\n// directionRad?: number;\n// }\n\n// export interface GpsData {\n// latitude: number;\n// longitude: number;\n// accuracy: number;\n// satellitesInView?: number;\n// usedInFix?: number;\n// constellationCounts?: { [key: string]: number };\n// }\n\n// export interface PositioningData {\n// source: 'GNSS' | 'WIFI' | 'FUSED' | 'MOCK';\n// timestamp: number;\n// latitude: number;\n// longitude: number;\n// accuracy: number;\n\n// gnssData?: SatelliteStatus;\n// wifiData?: WifiAp[];\n// imuData?: ImuData;\n// }\n\nexport interface PositioningData {\n source: 'GNSS' | 'WIFI' | 'FUSED' | 'MOCK';\n timestamp: number;\n latitude: number;\n longitude: number;\n accuracy: number;\n speed: number;\n acceleration: number;\n directionRad: number;\n isMocked: boolean;\n predicted?: boolean;\n}\n\nexport interface SatelliteStatus {\n satellitesInView: number;\n usedInFix: number;\n constellationCounts: { [key: string]: number };\n}\n\nexport interface DumonGeoOptions {\n distanceThresholdMeters?: number;\n speedChangeThreshold?: number;\n directionChangeThreshold?: number;\n emitDebounceMs?: number;\n drivingEmitIntervalMs?: number;\n wifiScanIntervalMs?: number;\n enableWifiRtt?: boolean;\n enableLogging?: boolean;\n enableForwardPrediction?: boolean;\n maxPredictionSeconds?: number;\n emitGnssStatus?: boolean;\n suppressMockedUpdates?: boolean;\n keepScreenOn?: boolean;\n}\n\nexport interface PermissionStatus {\n location: 'granted' | 'denied';\n wifi: 'granted' | 'denied';\n}\n\nexport interface DumonGeolocationPlugin {\n startPositioning(): Promise<void>;\n stopPositioning(): Promise<void>;\n getLatestPosition(): Promise<PositioningData>;\n checkAndRequestPermissions(): Promise<PermissionStatus>;\n setOptions(options: DumonGeoOptions): Promise<void>;\n getGnssStatus(): Promise<SatelliteStatus | null>;\n getLocationServicesStatus(): Promise<{ gpsEnabled: boolean; networkEnabled: boolean }>;\n\n configureEdgeToEdge(options: {\n bgColor: string;\n style: 'DARK' | 'LIGHT';\n overlay?: boolean;\n }): Promise<void>;\n\n setGpsMode(options: { mode: 'normal' | 'driving' }): Promise<void>;\n\n addListener(\n eventName: 'onPositionUpdate',\n listenerFunc: (data: PositioningData) => void\n ): PluginListenerHandle;\n\n addListener(\n eventName: 'onGnssStatus',\n listenerFunc: (data: SatelliteStatus) => void\n ): PluginListenerHandle;\n}\n"]}

8
dist/esm/web.d.ts vendored
View File

@ -1,5 +1,5 @@
import { WebPlugin } from '@capacitor/core'; import { WebPlugin } from '@capacitor/core';
import type { PositioningData } from './definitions'; import type { PositioningData, DumonGeoOptions, SatelliteStatus } from './definitions';
export declare class DumonGeolocationWeb extends WebPlugin { export declare class DumonGeolocationWeb extends WebPlugin {
startPositioning(): Promise<void>; startPositioning(): Promise<void>;
stopPositioning(): Promise<void>; stopPositioning(): Promise<void>;
@ -13,4 +13,10 @@ export declare class DumonGeolocationWeb extends WebPlugin {
style: 'DARK' | 'LIGHT'; style: 'DARK' | 'LIGHT';
overlay?: boolean; overlay?: boolean;
}): Promise<void>; }): Promise<void>;
setOptions(_options: DumonGeoOptions): Promise<void>;
getGnssStatus(): Promise<SatelliteStatus | null>;
getLocationServicesStatus(): Promise<{
gpsEnabled: boolean;
networkEnabled: boolean;
}>;
} }

10
dist/esm/web.js vendored
View File

@ -31,5 +31,15 @@ export class DumonGeolocationWeb extends WebPlugin {
console.info('[dumon-geolocation] configureEdgeToEdge called on web with:', options); console.info('[dumon-geolocation] configureEdgeToEdge called on web with:', options);
// No-op // No-op
} }
async setOptions(_options) {
// No-op on web
}
async getGnssStatus() {
return null;
}
async getLocationServicesStatus() {
// Web stub; assume enabled
return { gpsEnabled: true, networkEnabled: true };
}
} }
//# sourceMappingURL=web.js.map //# sourceMappingURL=web.js.map

2
dist/esm/web.js.map vendored
View File

@ -1 +1 @@
{"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5C,MAAM,OAAO,mBAAoB,SAAQ,SAAS;IAChD,KAAK,CAAC,gBAAgB;QACpB,OAAO,CAAC,GAAG,CAAC,wDAAwD,CAAC,CAAC;IACxE,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,OAAO,CAAC,GAAG,CAAC,uDAAuD,CAAC,CAAC;IACvE,CAAC;IAED,KAAK,CAAC,iBAAiB;QACrB,OAAO,CAAC,GAAG,CAAC,wEAAwE,CAAC,CAAC;QACtF,OAAO;YACL,MAAM,EAAE,MAAM;YACd,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;YACrB,QAAQ,EAAE,CAAC;YACX,SAAS,EAAE,CAAC;YACZ,QAAQ,EAAE,GAAG;YACb,KAAK,EAAE,CAAC;YACR,YAAY,EAAE,CAAC;YACf,YAAY,EAAE,CAAC;YACf,QAAQ,EAAE,KAAK;SAChB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,0BAA0B;QAI9B,OAAO,CAAC,IAAI,CAAC,gEAAgE,CAAC,CAAC;QAC/E,OAAO;YACL,QAAQ,EAAE,SAAS;YACnB,IAAI,EAAE,SAAS;SAChB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,mBAAmB,CAAC,OAIzB;QACC,OAAO,CAAC,IAAI,CAAC,6DAA6D,EAAE,OAAO,CAAC,CAAC;QACrF,QAAQ;IACV,CAAC;CACF","sourcesContent":["import { WebPlugin } from '@capacitor/core';\nimport type { PositioningData } from './definitions';\n\nexport class DumonGeolocationWeb extends WebPlugin {\n async startPositioning(): Promise<void> {\n console.log('DumonGeolocationWeb: startPositioning() called (no-op)');\n }\n\n async stopPositioning(): Promise<void> {\n console.log('DumonGeolocationWeb: stopPositioning() called (no-op)');\n }\n\n async getLatestPosition(): Promise<PositioningData> {\n console.log('DumonGeolocationWeb: getLatestPosition() called (returning dummy data)');\n return {\n source: 'GNSS',\n timestamp: Date.now(),\n latitude: 0,\n longitude: 0,\n accuracy: 999,\n speed: 0,\n acceleration: 0,\n directionRad: 0,\n isMocked: false,\n };\n }\n\n async checkAndRequestPermissions(): Promise<{\n location: 'granted' | 'denied';\n wifi: 'granted' | 'denied';\n }> {\n console.info('[dumon-geolocation] checkAndRequestPermissions mocked for web.');\n return {\n location: 'granted',\n wifi: 'granted',\n };\n }\n\n async configureEdgeToEdge(options: {\n bgColor: string;\n style: 'DARK' | 'LIGHT';\n overlay?: boolean;\n }): Promise<void> {\n console.info('[dumon-geolocation] configureEdgeToEdge called on web with:', options);\n // No-op\n }\n}"]} {"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5C,MAAM,OAAO,mBAAoB,SAAQ,SAAS;IAChD,KAAK,CAAC,gBAAgB;QACpB,OAAO,CAAC,GAAG,CAAC,wDAAwD,CAAC,CAAC;IACxE,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,OAAO,CAAC,GAAG,CAAC,uDAAuD,CAAC,CAAC;IACvE,CAAC;IAED,KAAK,CAAC,iBAAiB;QACrB,OAAO,CAAC,GAAG,CAAC,wEAAwE,CAAC,CAAC;QACtF,OAAO;YACL,MAAM,EAAE,MAAM;YACd,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;YACrB,QAAQ,EAAE,CAAC;YACX,SAAS,EAAE,CAAC;YACZ,QAAQ,EAAE,GAAG;YACb,KAAK,EAAE,CAAC;YACR,YAAY,EAAE,CAAC;YACf,YAAY,EAAE,CAAC;YACf,QAAQ,EAAE,KAAK;SAChB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,0BAA0B;QAI9B,OAAO,CAAC,IAAI,CAAC,gEAAgE,CAAC,CAAC;QAC/E,OAAO;YACL,QAAQ,EAAE,SAAS;YACnB,IAAI,EAAE,SAAS;SAChB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,mBAAmB,CAAC,OAIzB;QACC,OAAO,CAAC,IAAI,CAAC,6DAA6D,EAAE,OAAO,CAAC,CAAC;QACrF,QAAQ;IACV,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,QAAyB;QACxC,eAAe;IACjB,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,yBAAyB;QAC7B,2BAA2B;QAC3B,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC;IACpD,CAAC;CACF","sourcesContent":["import { WebPlugin } from '@capacitor/core';\nimport type { PositioningData, DumonGeoOptions, SatelliteStatus } from './definitions';\n\nexport class DumonGeolocationWeb extends WebPlugin {\n async startPositioning(): Promise<void> {\n console.log('DumonGeolocationWeb: startPositioning() called (no-op)');\n }\n\n async stopPositioning(): Promise<void> {\n console.log('DumonGeolocationWeb: stopPositioning() called (no-op)');\n }\n\n async getLatestPosition(): Promise<PositioningData> {\n console.log('DumonGeolocationWeb: getLatestPosition() called (returning dummy data)');\n return {\n source: 'GNSS',\n timestamp: Date.now(),\n latitude: 0,\n longitude: 0,\n accuracy: 999,\n speed: 0,\n acceleration: 0,\n directionRad: 0,\n isMocked: false,\n };\n }\n\n async checkAndRequestPermissions(): Promise<{\n location: 'granted' | 'denied';\n wifi: 'granted' | 'denied';\n }> {\n console.info('[dumon-geolocation] checkAndRequestPermissions mocked for web.');\n return {\n location: 'granted',\n wifi: 'granted',\n };\n }\n\n async configureEdgeToEdge(options: {\n bgColor: string;\n style: 'DARK' | 'LIGHT';\n overlay?: boolean;\n }): Promise<void> {\n console.info('[dumon-geolocation] configureEdgeToEdge called on web with:', options);\n // No-op\n }\n\n async setOptions(_options: DumonGeoOptions): Promise<void> {\n // No-op on web\n }\n\n async getGnssStatus(): Promise<SatelliteStatus | null> {\n return null;\n }\n\n async getLocationServicesStatus(): Promise<{ gpsEnabled: boolean; networkEnabled: boolean }> {\n // Web stub; assume enabled\n return { gpsEnabled: true, networkEnabled: true };\n }\n}\n"]}

10
dist/plugin.cjs.js vendored
View File

@ -38,6 +38,16 @@ class DumonGeolocationWeb extends core.WebPlugin {
console.info('[dumon-geolocation] configureEdgeToEdge called on web with:', options); console.info('[dumon-geolocation] configureEdgeToEdge called on web with:', options);
// No-op // No-op
} }
async setOptions(_options) {
// No-op on web
}
async getGnssStatus() {
return null;
}
async getLocationServicesStatus() {
// Web stub; assume enabled
return { gpsEnabled: true, networkEnabled: true };
}
} }
var web = /*#__PURE__*/Object.freeze({ var web = /*#__PURE__*/Object.freeze({

View File

@ -1 +1 @@
{"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst DumonGeolocation = registerPlugin('DumonGeolocation', {\n web: () => import('./web').then((m) => new m.DumonGeolocationWeb()),\n});\nexport * from './definitions';\nexport { DumonGeolocation };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class DumonGeolocationWeb extends WebPlugin {\n async startPositioning() {\n console.log('DumonGeolocationWeb: startPositioning() called (no-op)');\n }\n async stopPositioning() {\n console.log('DumonGeolocationWeb: stopPositioning() called (no-op)');\n }\n async getLatestPosition() {\n console.log('DumonGeolocationWeb: getLatestPosition() called (returning dummy data)');\n return {\n source: 'GNSS',\n timestamp: Date.now(),\n latitude: 0,\n longitude: 0,\n accuracy: 999,\n speed: 0,\n acceleration: 0,\n directionRad: 0,\n isMocked: false,\n };\n }\n async checkAndRequestPermissions() {\n console.info('[dumon-geolocation] checkAndRequestPermissions mocked for web.');\n return {\n location: 'granted',\n wifi: 'granted',\n };\n }\n async configureEdgeToEdge(options) {\n console.info('[dumon-geolocation] configureEdgeToEdge called on web with:', options);\n // No-op\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;;AACK,MAAC,gBAAgB,GAAGA,mBAAc,CAAC,kBAAkB,EAAE;AAC5D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,mBAAmB,EAAE,CAAC;AACvE,CAAC;;ACFM,MAAM,mBAAmB,SAASC,cAAS,CAAC;AACnD,IAAI,MAAM,gBAAgB,GAAG;AAC7B,QAAQ,OAAO,CAAC,GAAG,CAAC,wDAAwD,CAAC;AAC7E;AACA,IAAI,MAAM,eAAe,GAAG;AAC5B,QAAQ,OAAO,CAAC,GAAG,CAAC,uDAAuD,CAAC;AAC5E;AACA,IAAI,MAAM,iBAAiB,GAAG;AAC9B,QAAQ,OAAO,CAAC,GAAG,CAAC,wEAAwE,CAAC;AAC7F,QAAQ,OAAO;AACf,YAAY,MAAM,EAAE,MAAM;AAC1B,YAAY,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;AACjC,YAAY,QAAQ,EAAE,CAAC;AACvB,YAAY,SAAS,EAAE,CAAC;AACxB,YAAY,QAAQ,EAAE,GAAG;AACzB,YAAY,KAAK,EAAE,CAAC;AACpB,YAAY,YAAY,EAAE,CAAC;AAC3B,YAAY,YAAY,EAAE,CAAC;AAC3B,YAAY,QAAQ,EAAE,KAAK;AAC3B,SAAS;AACT;AACA,IAAI,MAAM,0BAA0B,GAAG;AACvC,QAAQ,OAAO,CAAC,IAAI,CAAC,gEAAgE,CAAC;AACtF,QAAQ,OAAO;AACf,YAAY,QAAQ,EAAE,SAAS;AAC/B,YAAY,IAAI,EAAE,SAAS;AAC3B,SAAS;AACT;AACA,IAAI,MAAM,mBAAmB,CAAC,OAAO,EAAE;AACvC,QAAQ,OAAO,CAAC,IAAI,CAAC,6DAA6D,EAAE,OAAO,CAAC;AAC5F;AACA;AACA;;;;;;;;;"} {"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst DumonGeolocation = registerPlugin('DumonGeolocation', {\n web: () => import('./web').then((m) => new m.DumonGeolocationWeb()),\n});\nexport * from './definitions';\nexport { DumonGeolocation };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class DumonGeolocationWeb extends WebPlugin {\n async startPositioning() {\n console.log('DumonGeolocationWeb: startPositioning() called (no-op)');\n }\n async stopPositioning() {\n console.log('DumonGeolocationWeb: stopPositioning() called (no-op)');\n }\n async getLatestPosition() {\n console.log('DumonGeolocationWeb: getLatestPosition() called (returning dummy data)');\n return {\n source: 'GNSS',\n timestamp: Date.now(),\n latitude: 0,\n longitude: 0,\n accuracy: 999,\n speed: 0,\n acceleration: 0,\n directionRad: 0,\n isMocked: false,\n };\n }\n async checkAndRequestPermissions() {\n console.info('[dumon-geolocation] checkAndRequestPermissions mocked for web.');\n return {\n location: 'granted',\n wifi: 'granted',\n };\n }\n async configureEdgeToEdge(options) {\n console.info('[dumon-geolocation] configureEdgeToEdge called on web with:', options);\n // No-op\n }\n async setOptions(_options) {\n // No-op on web\n }\n async getGnssStatus() {\n return null;\n }\n async getLocationServicesStatus() {\n // Web stub; assume enabled\n return { gpsEnabled: true, networkEnabled: true };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;;AACK,MAAC,gBAAgB,GAAGA,mBAAc,CAAC,kBAAkB,EAAE;AAC5D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,mBAAmB,EAAE,CAAC;AACvE,CAAC;;ACFM,MAAM,mBAAmB,SAASC,cAAS,CAAC;AACnD,IAAI,MAAM,gBAAgB,GAAG;AAC7B,QAAQ,OAAO,CAAC,GAAG,CAAC,wDAAwD,CAAC;AAC7E;AACA,IAAI,MAAM,eAAe,GAAG;AAC5B,QAAQ,OAAO,CAAC,GAAG,CAAC,uDAAuD,CAAC;AAC5E;AACA,IAAI,MAAM,iBAAiB,GAAG;AAC9B,QAAQ,OAAO,CAAC,GAAG,CAAC,wEAAwE,CAAC;AAC7F,QAAQ,OAAO;AACf,YAAY,MAAM,EAAE,MAAM;AAC1B,YAAY,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;AACjC,YAAY,QAAQ,EAAE,CAAC;AACvB,YAAY,SAAS,EAAE,CAAC;AACxB,YAAY,QAAQ,EAAE,GAAG;AACzB,YAAY,KAAK,EAAE,CAAC;AACpB,YAAY,YAAY,EAAE,CAAC;AAC3B,YAAY,YAAY,EAAE,CAAC;AAC3B,YAAY,QAAQ,EAAE,KAAK;AAC3B,SAAS;AACT;AACA,IAAI,MAAM,0BAA0B,GAAG;AACvC,QAAQ,OAAO,CAAC,IAAI,CAAC,gEAAgE,CAAC;AACtF,QAAQ,OAAO;AACf,YAAY,QAAQ,EAAE,SAAS;AAC/B,YAAY,IAAI,EAAE,SAAS;AAC3B,SAAS;AACT;AACA,IAAI,MAAM,mBAAmB,CAAC,OAAO,EAAE;AACvC,QAAQ,OAAO,CAAC,IAAI,CAAC,6DAA6D,EAAE,OAAO,CAAC;AAC5F;AACA;AACA,IAAI,MAAM,UAAU,CAAC,QAAQ,EAAE;AAC/B;AACA;AACA,IAAI,MAAM,aAAa,GAAG;AAC1B,QAAQ,OAAO,IAAI;AACnB;AACA,IAAI,MAAM,yBAAyB,GAAG;AACtC;AACA,QAAQ,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE;AACzD;AACA;;;;;;;;;"}

10
dist/plugin.js vendored
View File

@ -37,6 +37,16 @@ var capacitorDumonGeolocation = (function (exports, core) {
console.info('[dumon-geolocation] configureEdgeToEdge called on web with:', options); console.info('[dumon-geolocation] configureEdgeToEdge called on web with:', options);
// No-op // No-op
} }
async setOptions(_options) {
// No-op on web
}
async getGnssStatus() {
return null;
}
async getLocationServicesStatus() {
// Web stub; assume enabled
return { gpsEnabled: true, networkEnabled: true };
}
} }
var web = /*#__PURE__*/Object.freeze({ var web = /*#__PURE__*/Object.freeze({

2
dist/plugin.js.map vendored
View File

@ -1 +1 @@
{"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst DumonGeolocation = registerPlugin('DumonGeolocation', {\n web: () => import('./web').then((m) => new m.DumonGeolocationWeb()),\n});\nexport * from './definitions';\nexport { DumonGeolocation };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class DumonGeolocationWeb extends WebPlugin {\n async startPositioning() {\n console.log('DumonGeolocationWeb: startPositioning() called (no-op)');\n }\n async stopPositioning() {\n console.log('DumonGeolocationWeb: stopPositioning() called (no-op)');\n }\n async getLatestPosition() {\n console.log('DumonGeolocationWeb: getLatestPosition() called (returning dummy data)');\n return {\n source: 'GNSS',\n timestamp: Date.now(),\n latitude: 0,\n longitude: 0,\n accuracy: 999,\n speed: 0,\n acceleration: 0,\n directionRad: 0,\n isMocked: false,\n };\n }\n async checkAndRequestPermissions() {\n console.info('[dumon-geolocation] checkAndRequestPermissions mocked for web.');\n return {\n location: 'granted',\n wifi: 'granted',\n };\n }\n async configureEdgeToEdge(options) {\n console.info('[dumon-geolocation] configureEdgeToEdge called on web with:', options);\n // No-op\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;AACK,UAAC,gBAAgB,GAAGA,mBAAc,CAAC,kBAAkB,EAAE;IAC5D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,mBAAmB,EAAE,CAAC;IACvE,CAAC;;ICFM,MAAM,mBAAmB,SAASC,cAAS,CAAC;IACnD,IAAI,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,OAAO,CAAC,GAAG,CAAC,wDAAwD,CAAC;IAC7E;IACA,IAAI,MAAM,eAAe,GAAG;IAC5B,QAAQ,OAAO,CAAC,GAAG,CAAC,uDAAuD,CAAC;IAC5E;IACA,IAAI,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,OAAO,CAAC,GAAG,CAAC,wEAAwE,CAAC;IAC7F,QAAQ,OAAO;IACf,YAAY,MAAM,EAAE,MAAM;IAC1B,YAAY,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;IACjC,YAAY,QAAQ,EAAE,CAAC;IACvB,YAAY,SAAS,EAAE,CAAC;IACxB,YAAY,QAAQ,EAAE,GAAG;IACzB,YAAY,KAAK,EAAE,CAAC;IACpB,YAAY,YAAY,EAAE,CAAC;IAC3B,YAAY,YAAY,EAAE,CAAC;IAC3B,YAAY,QAAQ,EAAE,KAAK;IAC3B,SAAS;IACT;IACA,IAAI,MAAM,0BAA0B,GAAG;IACvC,QAAQ,OAAO,CAAC,IAAI,CAAC,gEAAgE,CAAC;IACtF,QAAQ,OAAO;IACf,YAAY,QAAQ,EAAE,SAAS;IAC/B,YAAY,IAAI,EAAE,SAAS;IAC3B,SAAS;IACT;IACA,IAAI,MAAM,mBAAmB,CAAC,OAAO,EAAE;IACvC,QAAQ,OAAO,CAAC,IAAI,CAAC,6DAA6D,EAAE,OAAO,CAAC;IAC5F;IACA;IACA;;;;;;;;;;;;;;;"} {"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst DumonGeolocation = registerPlugin('DumonGeolocation', {\n web: () => import('./web').then((m) => new m.DumonGeolocationWeb()),\n});\nexport * from './definitions';\nexport { DumonGeolocation };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class DumonGeolocationWeb extends WebPlugin {\n async startPositioning() {\n console.log('DumonGeolocationWeb: startPositioning() called (no-op)');\n }\n async stopPositioning() {\n console.log('DumonGeolocationWeb: stopPositioning() called (no-op)');\n }\n async getLatestPosition() {\n console.log('DumonGeolocationWeb: getLatestPosition() called (returning dummy data)');\n return {\n source: 'GNSS',\n timestamp: Date.now(),\n latitude: 0,\n longitude: 0,\n accuracy: 999,\n speed: 0,\n acceleration: 0,\n directionRad: 0,\n isMocked: false,\n };\n }\n async checkAndRequestPermissions() {\n console.info('[dumon-geolocation] checkAndRequestPermissions mocked for web.');\n return {\n location: 'granted',\n wifi: 'granted',\n };\n }\n async configureEdgeToEdge(options) {\n console.info('[dumon-geolocation] configureEdgeToEdge called on web with:', options);\n // No-op\n }\n async setOptions(_options) {\n // No-op on web\n }\n async getGnssStatus() {\n return null;\n }\n async getLocationServicesStatus() {\n // Web stub; assume enabled\n return { gpsEnabled: true, networkEnabled: true };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;AACK,UAAC,gBAAgB,GAAGA,mBAAc,CAAC,kBAAkB,EAAE;IAC5D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,mBAAmB,EAAE,CAAC;IACvE,CAAC;;ICFM,MAAM,mBAAmB,SAASC,cAAS,CAAC;IACnD,IAAI,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,OAAO,CAAC,GAAG,CAAC,wDAAwD,CAAC;IAC7E;IACA,IAAI,MAAM,eAAe,GAAG;IAC5B,QAAQ,OAAO,CAAC,GAAG,CAAC,uDAAuD,CAAC;IAC5E;IACA,IAAI,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,OAAO,CAAC,GAAG,CAAC,wEAAwE,CAAC;IAC7F,QAAQ,OAAO;IACf,YAAY,MAAM,EAAE,MAAM;IAC1B,YAAY,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;IACjC,YAAY,QAAQ,EAAE,CAAC;IACvB,YAAY,SAAS,EAAE,CAAC;IACxB,YAAY,QAAQ,EAAE,GAAG;IACzB,YAAY,KAAK,EAAE,CAAC;IACpB,YAAY,YAAY,EAAE,CAAC;IAC3B,YAAY,YAAY,EAAE,CAAC;IAC3B,YAAY,QAAQ,EAAE,KAAK;IAC3B,SAAS;IACT;IACA,IAAI,MAAM,0BAA0B,GAAG;IACvC,QAAQ,OAAO,CAAC,IAAI,CAAC,gEAAgE,CAAC;IACtF,QAAQ,OAAO;IACf,YAAY,QAAQ,EAAE,SAAS;IAC/B,YAAY,IAAI,EAAE,SAAS;IAC3B,SAAS;IACT;IACA,IAAI,MAAM,mBAAmB,CAAC,OAAO,EAAE;IACvC,QAAQ,OAAO,CAAC,IAAI,CAAC,6DAA6D,EAAE,OAAO,CAAC;IAC5F;IACA;IACA,IAAI,MAAM,UAAU,CAAC,QAAQ,EAAE;IAC/B;IACA;IACA,IAAI,MAAM,aAAa,GAAG;IAC1B,QAAQ,OAAO,IAAI;IACnB;IACA,IAAI,MAAM,yBAAyB,GAAG;IACtC;IACA,QAAQ,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE;IACzD;IACA;;;;;;;;;;;;;;;"}

View File

@ -20,7 +20,7 @@
} }
}, },
"..": { "..": {
"version": "0.0.1", "version": "1.0.2",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@capacitor/android": "^7.0.0", "@capacitor/android": "^7.0.0",

View File

@ -50,9 +50,43 @@
<button id="startButton">Start Positioning</button> <button id="startButton">Start Positioning</button>
<button id="stopButton">Stop Positioning</button> <button id="stopButton">Stop Positioning</button>
<button id="getLatestButton">Get Latest Position</button> <button id="getLatestButton">Get Latest Position</button>
<button id="permButton">Request Permissions</button>
<button id="clearLogButton" style="background-color:#999">Clear Log</button>
<h1>Modes</h1>
<button id="drivingBtn">Driving Mode</button>
<button id="normalBtn">Normal Mode</button>
<h1>Options</h1>
<div style="display:flex;gap:1rem;flex-wrap:wrap">
<div>
<label><input type="checkbox" id="optEnableForwardPrediction" /> enableForwardPrediction</label><br />
<label><input type="checkbox" id="optEmitGnssStatus" /> emitGnssStatus</label><br />
<label><input type="checkbox" id="optEnableWifiRtt" checked /> enableWifiRtt</label><br />
<label><input type="checkbox" id="optEnableLogging" checked /> enableLogging</label><br />
<label><input type="checkbox" id="optSuppressMocked" /> suppressMockedUpdates</label><br />
<label><input type="checkbox" id="optKeepScreenOn" /> keepScreenOn</label><br />
</div>
<div>
<label>emitDebounceMs <input type="number" id="optEmitDebounceMs" placeholder="1000" /></label><br />
<label>drivingEmitIntervalMs <input type="number" id="optDrivingEmitIntervalMs" placeholder="1600" /></label><br />
<label>wifiScanIntervalMs <input type="number" id="optWifiScanIntervalMs" placeholder="3000" /></label><br />
</div>
<div>
<label>distanceThresholdMeters <input type="number" step="0.1" id="optDistanceThreshold" placeholder="7.0" /></label><br />
<label>speedChangeThreshold <input type="number" step="0.1" id="optSpeedThreshold" placeholder="0.5" /></label><br />
<label>directionChangeThreshold <input type="number" step="0.01" id="optDirectionThreshold" placeholder="0.17" /></label><br />
<label>maxPredictionSeconds <input type="number" step="0.1" id="optMaxPrediction" placeholder="1.0" /></label><br />
</div>
</div>
<button id="applyOptionsButton" style="background-color:#2e7d32">Apply Options</button>
<h1>Diagnostics</h1>
<button id="getGnssStatusButton">Get GNSS Status</button>
<button id="getLocationServicesStatusButton">Get Location Services Status</button>
<pre id="logArea"></pre> <pre id="logArea"></pre>
<script type="module" src="./js/example.js"></script> <script type="module" src="./js/example.js"></script>
</body> </body>
</html> </html>

View File

@ -1,18 +1,99 @@
import { DumonGeolocation } from 'dumon-geolocation'; import { DumonGeolocation } from 'dumon-geolocation';
const logArea = document.getElementById('logArea'); const logArea = document.getElementById('logArea');
let posListenerHandle = null;
let gnssListenerHandle = null;
function appendLog(title, data) { function appendLog(title, data) {
const timestamp = new Date().toLocaleTimeString(); const timestamp = new Date().toLocaleTimeString();
const formatted = `[${timestamp}] ${title}\n${JSON.stringify(data, null, 2)}\n\n`; const formatted = `[${timestamp}] ${title}\n${JSON.stringify(data, null, 2)}\n\n`;
logArea.textContent = formatted; // + logArea.textContent; logArea.textContent = formatted + logArea.textContent;
}
function clearLog() {
logArea.textContent = '';
}
function readNumber(id) {
const el = document.getElementById(id);
if (!el) return undefined;
const v = el.value.trim();
if (v === '') return undefined;
const n = Number(v);
return Number.isFinite(n) ? n : undefined;
}
function readBool(id) {
const el = document.getElementById(id);
return !!el?.checked;
}
async function applyOptionsFromForm() {
const options = {};
// Booleans
if (document.getElementById('optEnableForwardPrediction'))
options.enableForwardPrediction = readBool('optEnableForwardPrediction');
if (document.getElementById('optEmitGnssStatus'))
options.emitGnssStatus = readBool('optEmitGnssStatus');
if (document.getElementById('optEnableWifiRtt'))
options.enableWifiRtt = readBool('optEnableWifiRtt');
if (document.getElementById('optEnableLogging'))
options.enableLogging = readBool('optEnableLogging');
if (document.getElementById('optSuppressMocked'))
options.suppressMockedUpdates = readBool('optSuppressMocked');
if (document.getElementById('optKeepScreenOn'))
options.keepScreenOn = readBool('optKeepScreenOn');
// Numbers
const emitDebounceMs = readNumber('optEmitDebounceMs');
const drivingEmitIntervalMs = readNumber('optDrivingEmitIntervalMs');
const wifiScanIntervalMs = readNumber('optWifiScanIntervalMs');
const distanceThresholdMeters = readNumber('optDistanceThreshold');
const speedChangeThreshold = readNumber('optSpeedThreshold');
const directionChangeThreshold = readNumber('optDirectionThreshold');
const maxPredictionSeconds = readNumber('optMaxPrediction');
if (emitDebounceMs !== undefined) options.emitDebounceMs = emitDebounceMs;
if (drivingEmitIntervalMs !== undefined) options.drivingEmitIntervalMs = drivingEmitIntervalMs;
if (wifiScanIntervalMs !== undefined) options.wifiScanIntervalMs = wifiScanIntervalMs;
if (distanceThresholdMeters !== undefined)
options.distanceThresholdMeters = distanceThresholdMeters;
if (speedChangeThreshold !== undefined) options.speedChangeThreshold = speedChangeThreshold;
if (directionChangeThreshold !== undefined)
options.directionChangeThreshold = directionChangeThreshold;
if (maxPredictionSeconds !== undefined) options.maxPredictionSeconds = maxPredictionSeconds;
await DumonGeolocation.setOptions(options);
appendLog('setOptions', options);
// Manage GNSS status listener dynamically
const wantGnss = !!options.emitGnssStatus;
if (wantGnss && !gnssListenerHandle) {
gnssListenerHandle = DumonGeolocation.addListener('onGnssStatus', (data) => {
appendLog('onGnssStatus', data);
});
} else if (!wantGnss && gnssListenerHandle) {
await gnssListenerHandle.remove();
gnssListenerHandle = null;
}
}
async function requestPermissions() {
try {
const perm = await DumonGeolocation.checkAndRequestPermissions();
appendLog('permissions', perm);
} catch (err) {
appendLog('permissions', { error: err.message });
}
} }
async function startGeolocation() { async function startGeolocation() {
DumonGeolocation.addListener('onPositionUpdate', (data) => { if (!posListenerHandle) {
appendLog('onPositionUpdate', data); posListenerHandle = DumonGeolocation.addListener('onPositionUpdate', (data) => {
}); appendLog('onPositionUpdate', data);
});
}
try { try {
await DumonGeolocation.startPositioning(); await DumonGeolocation.startPositioning();
appendLog('startPositioning', { success: true }); appendLog('startPositioning', { success: true });
@ -39,15 +120,58 @@ async function getLatestPosition() {
} }
} }
async function setDrivingMode() {
try {
await DumonGeolocation.setGpsMode({ mode: 'driving' });
appendLog('setGpsMode', { mode: 'driving' });
} catch (err) {
appendLog('setGpsMode', { error: err.message });
}
}
async function setNormalMode() {
try {
await DumonGeolocation.setGpsMode({ mode: 'normal' });
appendLog('setGpsMode', { mode: 'normal' });
} catch (err) {
appendLog('setGpsMode', { error: err.message });
}
}
async function getGnssStatus() {
try {
const data = await DumonGeolocation.getGnssStatus();
appendLog('getGnssStatus', data);
} catch (err) {
appendLog('getGnssStatus', { error: err.message });
}
}
async function getLocationServicesStatus() {
try {
const data = await DumonGeolocation.getLocationServicesStatus();
appendLog('getLocationServicesStatus', data);
} catch (err) {
appendLog('getLocationServicesStatus', { error: err.message });
}
}
window.addEventListener('DOMContentLoaded', async () => { window.addEventListener('DOMContentLoaded', async () => {
document.getElementById('startButton').addEventListener('click', startGeolocation); document.getElementById('startButton').addEventListener('click', startGeolocation);
document.getElementById('stopButton').addEventListener('click', stopGeolocation); document.getElementById('stopButton').addEventListener('click', stopGeolocation);
document.getElementById('getLatestButton').addEventListener('click', getLatestPosition); document.getElementById('getLatestButton').addEventListener('click', getLatestPosition);
document.getElementById('permButton').addEventListener('click', requestPermissions);
document.getElementById('drivingBtn').addEventListener('click', setDrivingMode);
document.getElementById('normalBtn').addEventListener('click', setNormalMode);
document.getElementById('applyOptionsButton').addEventListener('click', applyOptionsFromForm);
document.getElementById('getGnssStatusButton').addEventListener('click', getGnssStatus);
document.getElementById('getLocationServicesStatusButton').addEventListener('click', getLocationServicesStatus);
document.getElementById('clearLogButton').addEventListener('click', clearLog);
// Apply a reasonable default UI config for E2E
await DumonGeolocation.configureEdgeToEdge({ await DumonGeolocation.configureEdgeToEdge({
bgColor: '#ffffff', bgColor: '#ffffff',
style: 'DARK', style: 'DARK',
overlay: false, // atau true jika kamu ingin konten masuk ke area statusbar/navbar overlay: false,
}); });
}); });

View File

@ -1,6 +1,6 @@
{ {
"name": "dumon-geolocation", "name": "dumon-geolocation",
"version": "0.0.1", "version": "1.0.2",
"description": "Implement manager GNSS, WiFi RTT, IMU, Kalman fusion, event emitter", "description": "Implement manager GNSS, WiFi RTT, IMU, Kalman fusion, event emitter",
"main": "dist/plugin.cjs.js", "main": "dist/plugin.cjs.js",
"module": "dist/esm/index.js", "module": "dist/esm/index.js",

View File

@ -64,6 +64,28 @@ export interface PositioningData {
predicted?: boolean; predicted?: boolean;
} }
export interface SatelliteStatus {
satellitesInView: number;
usedInFix: number;
constellationCounts: { [key: string]: number };
}
export interface DumonGeoOptions {
distanceThresholdMeters?: number;
speedChangeThreshold?: number;
directionChangeThreshold?: number;
emitDebounceMs?: number;
drivingEmitIntervalMs?: number;
wifiScanIntervalMs?: number;
enableWifiRtt?: boolean;
enableLogging?: boolean;
enableForwardPrediction?: boolean;
maxPredictionSeconds?: number;
emitGnssStatus?: boolean;
suppressMockedUpdates?: boolean;
keepScreenOn?: boolean;
}
export interface PermissionStatus { export interface PermissionStatus {
location: 'granted' | 'denied'; location: 'granted' | 'denied';
wifi: 'granted' | 'denied'; wifi: 'granted' | 'denied';
@ -74,6 +96,9 @@ export interface DumonGeolocationPlugin {
stopPositioning(): Promise<void>; stopPositioning(): Promise<void>;
getLatestPosition(): Promise<PositioningData>; getLatestPosition(): Promise<PositioningData>;
checkAndRequestPermissions(): Promise<PermissionStatus>; checkAndRequestPermissions(): Promise<PermissionStatus>;
setOptions(options: DumonGeoOptions): Promise<void>;
getGnssStatus(): Promise<SatelliteStatus | null>;
getLocationServicesStatus(): Promise<{ gpsEnabled: boolean; networkEnabled: boolean }>;
configureEdgeToEdge(options: { configureEdgeToEdge(options: {
bgColor: string; bgColor: string;
@ -87,4 +112,9 @@ export interface DumonGeolocationPlugin {
eventName: 'onPositionUpdate', eventName: 'onPositionUpdate',
listenerFunc: (data: PositioningData) => void listenerFunc: (data: PositioningData) => void
): PluginListenerHandle; ): PluginListenerHandle;
}
addListener(
eventName: 'onGnssStatus',
listenerFunc: (data: SatelliteStatus) => void
): PluginListenerHandle;
}

View File

@ -1,5 +1,5 @@
import { WebPlugin } from '@capacitor/core'; import { WebPlugin } from '@capacitor/core';
import type { PositioningData } from './definitions'; import type { PositioningData, DumonGeoOptions, SatelliteStatus } from './definitions';
export class DumonGeolocationWeb extends WebPlugin { export class DumonGeolocationWeb extends WebPlugin {
async startPositioning(): Promise<void> { async startPositioning(): Promise<void> {
@ -44,4 +44,17 @@ export class DumonGeolocationWeb extends WebPlugin {
console.info('[dumon-geolocation] configureEdgeToEdge called on web with:', options); console.info('[dumon-geolocation] configureEdgeToEdge called on web with:', options);
// No-op // No-op
} }
}
async setOptions(_options: DumonGeoOptions): Promise<void> {
// No-op on web
}
async getGnssStatus(): Promise<SatelliteStatus | null> {
return null;
}
async getLocationServicesStatus(): Promise<{ gpsEnabled: boolean; networkEnabled: boolean }> {
// Web stub; assume enabled
return { gpsEnabled: true, networkEnabled: true };
}
}