react-native-background-live-tracking 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/reactnativebackgroundlivetracking/LocationForegroundService.kt +11 -3
- package/android/src/main/java/com/reactnativebackgroundlivetracking/StaticMapFetcher.kt +32 -11
- package/android/src/main/java/com/reactnativebackgroundlivetracking/TrackingModule.kt +10 -0
- package/android/src/main/java/com/reactnativebackgroundlivetracking/TrackingPreferences.kt +18 -0
- package/package.json +3 -3
- package/src/index.ts +11 -1
- package/src/types.ts +23 -3
package/android/src/main/java/com/reactnativebackgroundlivetracking/LocationForegroundService.kt
CHANGED
|
@@ -194,6 +194,13 @@ class LocationForegroundService : Service() {
|
|
|
194
194
|
locationCallback!!,
|
|
195
195
|
Looper.getMainLooper(),
|
|
196
196
|
)
|
|
197
|
+
fused
|
|
198
|
+
.getLastLocation()
|
|
199
|
+
.addOnSuccessListener { loc ->
|
|
200
|
+
if (loc != null) {
|
|
201
|
+
maybeRefreshMapNotification(loc, prefs)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
197
204
|
} catch (_: SecurityException) {
|
|
198
205
|
stopSelf()
|
|
199
206
|
}
|
|
@@ -234,7 +241,8 @@ class LocationForegroundService : Service() {
|
|
|
234
241
|
if (!prefs.notificationMapPreview || prefs.staticMapsApiKey.isBlank()) return
|
|
235
242
|
staticMapExecutor.execute {
|
|
236
243
|
val now = System.currentTimeMillis()
|
|
237
|
-
|
|
244
|
+
val minGap = prefs.notificationMapRefreshIntervalMs.coerceIn(3_000L, 600_000L)
|
|
245
|
+
if (now - lastStaticMapFetchMs < minGap) return@execute
|
|
238
246
|
val plat = if (prefs.hasPickup) prefs.pickupLatitude else null
|
|
239
247
|
val plng = if (prefs.hasPickup) prefs.pickupLongitude else null
|
|
240
248
|
val bmp =
|
|
@@ -244,6 +252,8 @@ class LocationForegroundService : Service() {
|
|
|
244
252
|
plat,
|
|
245
253
|
plng,
|
|
246
254
|
prefs.staticMapsApiKey,
|
|
255
|
+
prefs.driverMarkerIconUrl,
|
|
256
|
+
prefs.pickupMarkerIconUrl,
|
|
247
257
|
)
|
|
248
258
|
?: return@execute
|
|
249
259
|
lastStaticMapFetchMs = System.currentTimeMillis()
|
|
@@ -267,8 +277,6 @@ class LocationForegroundService : Service() {
|
|
|
267
277
|
companion object {
|
|
268
278
|
private const val NOTIFICATION_ID = 90421
|
|
269
279
|
private const val QUEUE_FLUSH_INTERVAL_MS = 30_000L
|
|
270
|
-
/** Limits Static Maps quota / network; expanded notification still updates on this cadence. */
|
|
271
|
-
private const val STATIC_MAP_MIN_INTERVAL_MS = 45_000L
|
|
272
280
|
const val ACTION_STOP = "com.reactnativebackgroundlivetracking.action.STOP"
|
|
273
281
|
|
|
274
282
|
fun start(context: Context) {
|
|
@@ -2,6 +2,7 @@ package com.reactnativebackgroundlivetracking
|
|
|
2
2
|
|
|
3
3
|
import android.graphics.Bitmap
|
|
4
4
|
import android.graphics.BitmapFactory
|
|
5
|
+
import android.util.Log
|
|
5
6
|
import java.util.concurrent.TimeUnit
|
|
6
7
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
|
7
8
|
import okhttp3.OkHttpClient
|
|
@@ -9,6 +10,8 @@ import okhttp3.Request
|
|
|
9
10
|
|
|
10
11
|
/** Fetches a map bitmap via Google Static Maps API (not an interactive MapView). */
|
|
11
12
|
internal object StaticMapFetcher {
|
|
13
|
+
private const val TAG = "RNBackgroundLiveTracking"
|
|
14
|
+
|
|
12
15
|
private val client =
|
|
13
16
|
OkHttpClient.Builder()
|
|
14
17
|
.connectTimeout(18, TimeUnit.SECONDS)
|
|
@@ -21,6 +24,8 @@ internal object StaticMapFetcher {
|
|
|
21
24
|
pickupLat: Double?,
|
|
22
25
|
pickupLng: Double?,
|
|
23
26
|
apiKey: String,
|
|
27
|
+
driverIconUrl: String,
|
|
28
|
+
pickupIconUrl: String,
|
|
24
29
|
): Bitmap? {
|
|
25
30
|
if (apiKey.isBlank()) return null
|
|
26
31
|
val urlBuilder =
|
|
@@ -30,26 +35,42 @@ internal object StaticMapFetcher {
|
|
|
30
35
|
.addQueryParameter("size", "480x240")
|
|
31
36
|
.addQueryParameter("scale", "2")
|
|
32
37
|
.addQueryParameter("maptype", "roadmap")
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
|
|
39
|
+
val driverMarker =
|
|
40
|
+
if (driverIconUrl.isNotBlank()) {
|
|
41
|
+
"icon:$driverIconUrl|$driverLat,$driverLng"
|
|
42
|
+
} else {
|
|
43
|
+
"color:0x4285F4|size:mid|$driverLat,$driverLng"
|
|
44
|
+
}
|
|
45
|
+
urlBuilder.addQueryParameter("markers", driverMarker)
|
|
38
46
|
|
|
39
47
|
if (pickupLat != null && pickupLng != null) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
val pickupMarker =
|
|
49
|
+
if (pickupIconUrl.isNotBlank()) {
|
|
50
|
+
"icon:$pickupIconUrl|$pickupLat,$pickupLng"
|
|
51
|
+
} else {
|
|
52
|
+
"color:0x0F9D58|label:P|size:mid|$pickupLat,$pickupLng"
|
|
53
|
+
}
|
|
54
|
+
urlBuilder.addQueryParameter("markers", pickupMarker)
|
|
44
55
|
}
|
|
45
56
|
|
|
57
|
+
urlBuilder.addQueryParameter("key", apiKey)
|
|
58
|
+
|
|
46
59
|
val request = Request.Builder().url(urlBuilder.build()).get().build()
|
|
47
60
|
return try {
|
|
48
61
|
client.newCall(request).execute().use { response ->
|
|
49
|
-
if (!response.isSuccessful)
|
|
62
|
+
if (!response.isSuccessful) {
|
|
63
|
+
val snippet = response.body?.string()?.take(280)?.replace("\n", " ")
|
|
64
|
+
Log.w(
|
|
65
|
+
TAG,
|
|
66
|
+
"Static Maps request failed: HTTP ${response.code}${if (snippet != null) " — $snippet" else ""}",
|
|
67
|
+
)
|
|
68
|
+
return null
|
|
69
|
+
}
|
|
50
70
|
response.body?.byteStream()?.use { BitmapFactory.decodeStream(it) }
|
|
51
71
|
}
|
|
52
|
-
} catch (
|
|
72
|
+
} catch (e: Exception) {
|
|
73
|
+
Log.w(TAG, "Static Maps request error", e)
|
|
53
74
|
null
|
|
54
75
|
}
|
|
55
76
|
}
|
|
@@ -96,6 +96,16 @@ class RNBackgroundLiveTrackingModule(
|
|
|
96
96
|
prefs.restHeadersJson = restHeaders
|
|
97
97
|
prefs.notificationMapPreview = notificationMapPreview
|
|
98
98
|
prefs.staticMapsApiKey = staticMapsApiKey
|
|
99
|
+
val mapRefresh =
|
|
100
|
+
when {
|
|
101
|
+
!json.has("notificationMapRefreshIntervalMs") || json.isNull("notificationMapRefreshIntervalMs") ->
|
|
102
|
+
45_000L
|
|
103
|
+
else ->
|
|
104
|
+
json.optLong("notificationMapRefreshIntervalMs", 45_000L).coerceIn(3_000L, 600_000L)
|
|
105
|
+
}
|
|
106
|
+
prefs.notificationMapRefreshIntervalMs = mapRefresh
|
|
107
|
+
prefs.driverMarkerIconUrl = json.optString("driverMarkerIconUrl", "")
|
|
108
|
+
prefs.pickupMarkerIconUrl = json.optString("pickupMarkerIconUrl", "")
|
|
99
109
|
if (json.has("pickup") && !json.isNull("pickup")) {
|
|
100
110
|
val pu = json.getJSONObject("pickup")
|
|
101
111
|
prefs.pickupLatitude = pu.getDouble("latitude")
|
|
@@ -55,6 +55,11 @@ internal class TrackingPreferences(context: Context) {
|
|
|
55
55
|
get() = p.getString(KEY_STATIC_MAP_KEY, "") ?: ""
|
|
56
56
|
set(value) = p.edit().putString(KEY_STATIC_MAP_KEY, value).apply()
|
|
57
57
|
|
|
58
|
+
/** Min time between Static Maps fetches for the notification bitmap. */
|
|
59
|
+
var notificationMapRefreshIntervalMs: Long
|
|
60
|
+
get() = p.getLong(KEY_MAP_REFRESH_MS, 45_000L)
|
|
61
|
+
set(value) = p.edit().putLong(KEY_MAP_REFRESH_MS, value).apply()
|
|
62
|
+
|
|
58
63
|
var hasPickup: Boolean
|
|
59
64
|
get() = p.getBoolean(KEY_HAS_PICKUP, false)
|
|
60
65
|
set(value) = p.edit().putBoolean(KEY_HAS_PICKUP, value).apply()
|
|
@@ -75,6 +80,16 @@ internal class TrackingPreferences(context: Context) {
|
|
|
75
80
|
set(value) =
|
|
76
81
|
p.edit().putLong(KEY_PICKUP_LNG_BITS, java.lang.Double.doubleToRawLongBits(value)).apply()
|
|
77
82
|
|
|
83
|
+
/** HTTPS URL for Static Maps custom driver marker; empty = default blue pin. */
|
|
84
|
+
var driverMarkerIconUrl: String
|
|
85
|
+
get() = p.getString(KEY_DRIVER_MARKER_ICON, "") ?: ""
|
|
86
|
+
set(value) = p.edit().putString(KEY_DRIVER_MARKER_ICON, value).apply()
|
|
87
|
+
|
|
88
|
+
/** HTTPS URL for pickup/destination marker; empty = default green “P” pin. */
|
|
89
|
+
var pickupMarkerIconUrl: String
|
|
90
|
+
get() = p.getString(KEY_PICKUP_MARKER_ICON, "") ?: ""
|
|
91
|
+
set(value) = p.edit().putString(KEY_PICKUP_MARKER_ICON, value).apply()
|
|
92
|
+
|
|
78
93
|
fun clearTrackingFlag() {
|
|
79
94
|
p.edit().putBoolean(KEY_ACTIVE, false).apply()
|
|
80
95
|
}
|
|
@@ -93,8 +108,11 @@ internal class TrackingPreferences(context: Context) {
|
|
|
93
108
|
private const val KEY_REST_HEADERS = "rest_headers_json"
|
|
94
109
|
private const val KEY_MAP_PREVIEW = "map_preview"
|
|
95
110
|
private const val KEY_STATIC_MAP_KEY = "static_maps_api_key"
|
|
111
|
+
private const val KEY_MAP_REFRESH_MS = "map_refresh_interval_ms"
|
|
96
112
|
private const val KEY_HAS_PICKUP = "has_pickup"
|
|
97
113
|
private const val KEY_PICKUP_LAT_BITS = "pickup_lat_bits"
|
|
98
114
|
private const val KEY_PICKUP_LNG_BITS = "pickup_lng_bits"
|
|
115
|
+
private const val KEY_DRIVER_MARKER_ICON = "driver_marker_icon_url"
|
|
116
|
+
private const val KEY_PICKUP_MARKER_ICON = "pickup_marker_icon_url"
|
|
99
117
|
}
|
|
100
118
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-background-live-tracking",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Production-ready Android background GPS tracking with REST/WebSocket, offline queue, and foreground service.",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"module": "src/index.ts",
|
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
"homepage": "https://www.npmjs.com/package/react-native-background-live-tracking",
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
|
15
|
-
"url": "git+https://github.com/
|
|
15
|
+
"url": "git+https://github.com/Rahul6694/react-native-background-live-tracking.git"
|
|
16
16
|
},
|
|
17
17
|
"bugs": {
|
|
18
|
-
"url": "https://github.com/
|
|
18
|
+
"url": "https://github.com/Rahul6694/react-native-background-live-tracking/issues"
|
|
19
19
|
},
|
|
20
20
|
"keywords": [
|
|
21
21
|
"react-native",
|
package/src/index.ts
CHANGED
|
@@ -29,7 +29,11 @@ export const Tracking = {
|
|
|
29
29
|
restHeaders,
|
|
30
30
|
notificationMapPreview,
|
|
31
31
|
googleStaticMapsApiKey,
|
|
32
|
+
notificationMapRefreshIntervalMs,
|
|
32
33
|
pickup,
|
|
34
|
+
destination,
|
|
35
|
+
driverMarkerIconUrl,
|
|
36
|
+
pickupMarkerIconUrl,
|
|
33
37
|
} = options;
|
|
34
38
|
if (!driverId || !serverUrl || !interval || interval < 1000) {
|
|
35
39
|
throw new Error(
|
|
@@ -49,7 +53,13 @@ export const Tracking = {
|
|
|
49
53
|
restHeaders: restHeaders ?? {},
|
|
50
54
|
notificationMapPreview: notificationMapPreview === true,
|
|
51
55
|
googleStaticMapsApiKey: googleStaticMapsApiKey ?? '',
|
|
52
|
-
|
|
56
|
+
notificationMapRefreshIntervalMs:
|
|
57
|
+
notificationMapRefreshIntervalMs != null
|
|
58
|
+
? Math.floor(notificationMapRefreshIntervalMs)
|
|
59
|
+
: null,
|
|
60
|
+
pickup: pickup ?? destination ?? null,
|
|
61
|
+
driverMarkerIconUrl: driverMarkerIconUrl ?? '',
|
|
62
|
+
pickupMarkerIconUrl: pickupMarkerIconUrl ?? '',
|
|
53
63
|
};
|
|
54
64
|
await Native.startTracking(JSON.stringify(payload));
|
|
55
65
|
},
|
package/src/types.ts
CHANGED
|
@@ -16,14 +16,34 @@ export type TrackingStartOptions = {
|
|
|
16
16
|
/** Extra headers for REST POST (JSON string map). */
|
|
17
17
|
restHeaders?: Record<string, string>;
|
|
18
18
|
/**
|
|
19
|
-
* Show a map image in the expanded notification (Android only).
|
|
20
|
-
*
|
|
19
|
+
* Show a map image in the expanded notification (Android only). Android cannot embed a real
|
|
20
|
+
* interactive MapView in the shade—only a **bitmap** that we refresh. Use
|
|
21
|
+
* `notificationMapRefreshIntervalMs` (e.g. same as `interval`) so the map tracks your location
|
|
22
|
+
* as often as GPS updates; higher frequency uses more Static Maps quota.
|
|
21
23
|
*/
|
|
22
24
|
notificationMapPreview?: boolean;
|
|
23
25
|
/** Google Cloud API key with Static Maps API enabled (billing). */
|
|
24
26
|
googleStaticMapsApiKey?: string;
|
|
25
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* How often to fetch a new Static Maps image for the notification (milliseconds). Default 45000.
|
|
29
|
+
* Set close to `interval` (e.g. 5000) for “near live” snapshots; minimum enforced on native side.
|
|
30
|
+
*/
|
|
31
|
+
notificationMapRefreshIntervalMs?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Optional second marker (e.g. destination). Same as `destination` if you prefer that name.
|
|
34
|
+
*/
|
|
26
35
|
pickup?: { latitude: number; longitude: number };
|
|
36
|
+
/** Alias for `pickup` (destination / drop-off coordinates). */
|
|
37
|
+
destination?: { latitude: number; longitude: number };
|
|
38
|
+
/**
|
|
39
|
+
* Public HTTPS URL of a small PNG (or GIF/JPEG) for the driver marker. Passed to Static Maps as a custom icon.
|
|
40
|
+
* @see https://developers.google.com/maps/documentation/maps-static/start#Markers
|
|
41
|
+
*/
|
|
42
|
+
driverMarkerIconUrl?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Public HTTPS URL for the pickup/destination marker when `pickup` or `destination` is set.
|
|
45
|
+
*/
|
|
46
|
+
pickupMarkerIconUrl?: string;
|
|
27
47
|
};
|
|
28
48
|
|
|
29
49
|
export type TrackingStatus = {
|