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.
@@ -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
- if (now - lastStaticMapFetchMs < STATIC_MAP_MIN_INTERVAL_MS) return@execute
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
- .addQueryParameter(
34
- "markers",
35
- "color:0x4285F4|size:mid|$driverLat,$driverLng",
36
- )
37
- .addQueryParameter("key", apiKey)
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
- urlBuilder.addQueryParameter(
41
- "markers",
42
- "color:0x0F9D58|label:P|size:mid|$pickupLat,$pickupLng",
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) return null
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 (_: Exception) {
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.1",
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/your-username/react-native-background-live-tracking.git"
15
+ "url": "git+https://github.com/Rahul6694/react-native-background-live-tracking.git"
16
16
  },
17
17
  "bugs": {
18
- "url": "https://github.com/your-username/react-native-background-live-tracking/issues"
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
- pickup: pickup ?? null,
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). This is **not** a live MapView:
20
- * it uses the Google Static Maps API, so you need a key and it refreshes on an interval (see README).
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
- /** Optional green “pickup” marker on the static map preview. */
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 = {