@jacques_gordon/expo-mapbox-navigation 2.2.0 → 2.2.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.
File without changes
@@ -0,0 +1,2 @@
1
+ #Tue Jun 30 22:43:09 CEST 2026
2
+ gradle.version=8.9
File without changes
@@ -58,6 +58,25 @@ class ExpoMapboxNavigationModule : Module() {
58
58
  Prop("customRasterAboveLayerId") { view: ExpoMapboxNavigationView, layerId: String? ->
59
59
  view.setCustomRasterAboveLayerId(layerId)
60
60
  }
61
+ // ── Color customization props ────────────────────────────────────────
62
+ Prop("maneuverBackgroundColorDay") { view: ExpoMapboxNavigationView, color: String? ->
63
+ view.setManeuverBackgroundColorDay(color)
64
+ }
65
+ Prop("maneuverTurnIconColor") { view: ExpoMapboxNavigationView, color: String? ->
66
+ view.setManeuverTurnIconColor(color)
67
+ }
68
+ Prop("etaBarBackgroundColor") { view: ExpoMapboxNavigationView, color: String? ->
69
+ view.setEtaBarBackgroundColor(color)
70
+ }
71
+ Prop("etaTextColor") { view: ExpoMapboxNavigationView, color: String? ->
72
+ view.setEtaTextColor(color)
73
+ }
74
+ Prop("iconButtonColor") { view: ExpoMapboxNavigationView, color: String? ->
75
+ view.setIconButtonColor(color)
76
+ }
77
+ Prop("iconButtonMutedColor") { view: ExpoMapboxNavigationView, color: String? ->
78
+ view.setIconButtonMutedColor(color)
79
+ }
61
80
  }
62
81
  }
63
82
  }
@@ -54,6 +54,7 @@ import com.mapbox.navigation.tripdata.progress.model.EstimatedTimeToArrivalForma
54
54
  import com.mapbox.navigation.tripdata.progress.model.TimeRemainingFormatter
55
55
  import com.mapbox.navigation.tripdata.progress.model.TripProgressUpdateFormatter
56
56
  import com.mapbox.navigation.tripdata.speedlimit.api.MapboxSpeedInfoApi
57
+ import com.mapbox.navigation.ui.components.maneuver.model.ManeuverViewOptions
57
58
  import com.mapbox.navigation.ui.components.maneuver.view.MapboxManeuverView
58
59
  import com.mapbox.navigation.ui.components.speedlimit.view.MapboxSpeedInfoView
59
60
  import com.mapbox.navigation.ui.components.tripprogress.view.MapboxTripProgressView
@@ -139,13 +140,21 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
139
140
  // ── Pixel density ─────────────────────────────────────────────────────────
140
141
  private val dp = context.resources.displayMetrics.density
141
142
 
142
- // ── Viewport padding ──────────────────────────────────────────────────────
143
143
  // ── Viewport padding — EXACT official Mapbox values from the camera guide ──
144
144
  // Previous bottom=300dp pushed the focal point too far up, causing
145
145
  // the puck to visibly jump/drift between location updates.
146
146
  // Official guide value: top=180, left=40, bottom=150, right=40
147
+ // ── Viewport padding — Waze-style vehicle position ──────────────────────────
148
+ // bottom=300dp pushes the focal point (and thus the vehicle puck) up to
149
+ // roughly 30% from the bottom of the screen, matching Waze/Google Maps.
150
+ // NOTE: this was briefly regressed to bottom=150dp (the "exact" value from
151
+ // the Mapbox camera guide example) while investigating puck jitter — but
152
+ // the jitter was actually caused by passing keyPoints to changePosition()
153
+ // (see the LocationObserver fix below), NOT by this padding value. The
154
+ // jitter fix and the Waze-style positioning are independent and both
155
+ // needed; restoring bottom=300dp here does not reintroduce the jitter.
147
156
  private val followingPadding by lazy {
148
- EdgeInsets(180.0 * dp, 40.0 * dp, 150.0 * dp, 40.0 * dp)
157
+ EdgeInsets(180.0 * dp, 40.0 * dp, 300.0 * dp, 40.0 * dp)
149
158
  }
150
159
  private val overviewPadding by lazy {
151
160
  EdgeInsets(140.0 * dp, 40.0 * dp, 120.0 * dp, 40.0 * dp)
@@ -166,6 +175,18 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
166
175
  private var customRasterTileUrl: String? = null
167
176
  private var customRasterAboveLayerId: String? = null
168
177
 
178
+ // ── Color customization props ────────────────────────────────────────────
179
+ // Maneuver banner colors — verified against ManeuverViewOptions public API
180
+ // (maneuverBackgroundColor, turnIconManeuver are real documented properties)
181
+ private var maneuverBackgroundColorDay: String? = null
182
+ private var maneuverTurnIconColor: String? = null
183
+ // ETA bottom bar colors — fully custom view, safe to color freely
184
+ private var etaBarBackgroundColor: String? = null
185
+ private var etaTextColor: String? = null
186
+ // Custom icon button colors (mute, overview, recenter) — our own bitmaps
187
+ private var iconButtonColor: String? = null
188
+ private var iconButtonMutedColor: String? = null
189
+
169
190
  init {
170
191
  buildUI()
171
192
  initAPIs()
@@ -183,7 +204,10 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
183
204
  val bmp = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
184
205
  val c = Canvas(bmp)
185
206
  val s = size.toFloat()
186
- val color = if (muted) Color.parseColor("#5F6368") else Color.parseColor("#1A73E8")
207
+ val defaultColor = if (muted) "#5F6368" else "#1A73E8"
208
+ val color = try {
209
+ Color.parseColor(if (muted) (iconButtonMutedColor ?: defaultColor) else (iconButtonColor ?: defaultColor))
210
+ } catch (e: Exception) { Color.parseColor(defaultColor) }
187
211
  val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
188
212
  this.color = color
189
213
  style = Paint.Style.FILL
@@ -227,9 +251,8 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
227
251
  val c = Canvas(bmp)
228
252
  val s = size.toFloat()
229
253
  val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
230
- color = Color.parseColor("#1A73E8")
254
+ color = try { Color.parseColor(iconButtonColor ?: "#1A73E8") } catch (e: Exception) { Color.parseColor("#1A73E8") }
231
255
  }
232
- // Map/overview icon: stacked layers (like Google Maps "map view" icon)
233
256
  paint.style = Paint.Style.STROKE
234
257
  paint.strokeWidth = s * 0.07f
235
258
  paint.strokeJoin = Paint.Join.ROUND
@@ -257,10 +280,9 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
257
280
  val c = Canvas(bmp)
258
281
  val s = size.toFloat()
259
282
  val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
260
- color = Color.parseColor("#1A73E8")
283
+ color = try { Color.parseColor(iconButtonColor ?: "#1A73E8") } catch (e: Exception) { Color.parseColor("#1A73E8") }
261
284
  style = Paint.Style.FILL
262
285
  }
263
- // Navigation/recenter compass arrow (Google Maps "my location, follow" style)
264
286
  val arrow = Path().apply {
265
287
  moveTo(s * 0.50f, s * 0.12f) // tip
266
288
  lineTo(s * 0.80f, s * 0.82f) // bottom-right
@@ -303,20 +325,19 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
303
325
  ))
304
326
 
305
327
  // ── ManeuverView — top ─────────────────────────────────────────────────
306
- // NOTE: a previous attempt to customize the primary maneuver text
307
- // appearance (auto-sizing for long street names) used a fictional
308
- // "primaryManeuverTextAppearance" attribute that doesn't exist in the
309
- // SDK, which broke AAPT resource linking in host apps. A follow-up
310
- // attempt to call updatePrimaryManeuverTextAppearance() was also
311
- // dropped: in Navigation SDK v3 the UI components were restructured
312
- // around a ComponentInstaller/ManeuverConfig system and that method's
313
- // availability/signature could not be confirmed for v3 with
314
- // certainty. To avoid risking another build-breaking regression on
315
- // an unverified API, MapboxManeuverView is used with its default
316
- // text appearance for now. Long street name truncation can be
317
- // revisited via the officially supported ComponentInstaller config
318
- // API in a future, carefully tested release.
319
- val mv = MapboxManeuverView(context)
328
+ // Colors are customizable via maneuverBackgroundColorDay,
329
+ // maneuverTurnIconColor, and laneGuidanceTurnIconColor props using the
330
+ // SDK's official ManeuverViewOptions.Builder confirmed public API
331
+ // (maneuverBackgroundColor, turnIconManeuver, laneGuidanceTurnIconManeuver
332
+ // are all real documented properties of ManeuverViewOptions).
333
+ val maneuverOptionsBuilder = ManeuverViewOptions.Builder()
334
+ maneuverBackgroundColorDay?.let {
335
+ try { maneuverOptionsBuilder.maneuverBackgroundColor(Color.parseColor(it)) } catch (e: Exception) {}
336
+ }
337
+ maneuverTurnIconColor?.let {
338
+ try { maneuverOptionsBuilder.turnIconManeuver(Color.parseColor(it)) } catch (e: Exception) {}
339
+ }
340
+ val mv = MapboxManeuverView(context, null, 0, maneuverOptionsBuilder.build())
320
341
  root.addView(mv as View, FrameLayout.LayoutParams(
321
342
  FrameLayout.LayoutParams.MATCH_PARENT,
322
343
  FrameLayout.LayoutParams.WRAP_CONTENT
@@ -385,9 +406,11 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
385
406
  speedInfoView = siv
386
407
 
387
408
  // ── ETA bottom bar ─────────────────────────────────────────────────────
409
+ val resolvedEtaBg = try { Color.parseColor(etaBarBackgroundColor ?: "#1E2433") } catch (e: Exception) { Color.parseColor("#1E2433") }
410
+ val resolvedEtaText = try { Color.parseColor(etaTextColor ?: "#FFFFFF") } catch (e: Exception) { Color.WHITE }
388
411
  val bar = LinearLayout(context).apply {
389
412
  orientation = LinearLayout.HORIZONTAL
390
- setBackgroundColor(Color.parseColor("#1E2433"))
413
+ setBackgroundColor(resolvedEtaBg)
391
414
  elevation = 8 * dp
392
415
  visibility = View.INVISIBLE
393
416
  gravity = Gravity.CENTER_VERTICAL
@@ -402,7 +425,7 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
402
425
  ).also { it.gravity = Gravity.BOTTOM })
403
426
 
404
427
  val etaTime = TextView(context).apply {
405
- textSize = 24f; setTextColor(Color.WHITE)
428
+ textSize = 24f; setTextColor(resolvedEtaText)
406
429
  typeface = android.graphics.Typeface.DEFAULT_BOLD
407
430
  }
408
431
  bar.addView(etaTime, LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f))
@@ -412,7 +435,7 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
412
435
  orientation = LinearLayout.VERTICAL; gravity = Gravity.CENTER
413
436
  }
414
437
  val dur = TextView(context).apply {
415
- textSize = 18f; setTextColor(Color.WHITE)
438
+ textSize = 18f; setTextColor(resolvedEtaText)
416
439
  typeface = android.graphics.Typeface.DEFAULT_BOLD; gravity = Gravity.CENTER
417
440
  }
418
441
  val dist = TextView(context).apply {
@@ -422,6 +445,7 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
422
445
  bar.addView(center, LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 2f))
423
446
  tvDuration = dur; tvDistance = dist
424
447
 
448
+
425
449
  val cancelBtn = TextView(context).apply {
426
450
  text = "✕"; textSize = 22f; setTextColor(Color.parseColor("#AAAAAA"))
427
451
  gravity = Gravity.END or Gravity.CENTER_VERTICAL
@@ -811,7 +835,7 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
811
835
  onManeuverBannerPressed(mapOf("steps" to stepsPayload))
812
836
  }
813
837
 
814
-
838
+ private fun cancelNavigation() {
815
839
  speechApi.cancel()
816
840
  voiceInstructionsPlayer.clear()
817
841
  mapboxNavigation?.setNavigationRoutes(listOf())
@@ -904,6 +928,12 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
904
928
  fun setUseMapMatching(u: Boolean) { useMapMatching = u }
905
929
  fun setCustomRasterTileUrl(u: String?) { customRasterTileUrl = u }
906
930
  fun setCustomRasterAboveLayerId(l: String?) { customRasterAboveLayerId = l }
931
+ fun setManeuverBackgroundColorDay(c: String?) { maneuverBackgroundColorDay = c }
932
+ fun setManeuverTurnIconColor(c: String?) { maneuverTurnIconColor = c }
933
+ fun setEtaBarBackgroundColor(c: String?) { etaBarBackgroundColor = c }
934
+ fun setEtaTextColor(c: String?) { etaTextColor = c }
935
+ fun setIconButtonColor(c: String?) { iconButtonColor = c }
936
+ fun setIconButtonMutedColor(c: String?) { iconButtonMutedColor = c }
907
937
 
908
938
  override fun onDetachedFromWindow() {
909
939
  super.onDetachedFromWindow()
package/build/index.d.ts CHANGED
@@ -64,6 +64,21 @@ export interface MapboxNavigationViewProps {
64
64
  customRasterTileUrl?: string;
65
65
  /** Map layer ID above which the custom raster layer is placed. */
66
66
  customRasterAboveLayerId?: string;
67
+ /**
68
+ * Background color of the turn-by-turn instruction banner, as a hex string
69
+ * (e.g. "#1A73E8"). Uses the SDK's official ManeuverViewOptions API.
70
+ */
71
+ maneuverBackgroundColorDay?: string;
72
+ /** Color of the turn arrow icon inside the instruction banner. */
73
+ maneuverTurnIconColor?: string;
74
+ /** Background color of the bottom ETA/duration/distance bar. */
75
+ etaBarBackgroundColor?: string;
76
+ /** Text color used for the ETA time and duration in the bottom bar. */
77
+ etaTextColor?: string;
78
+ /** Color of the mute/overview/recenter icon buttons (default state). */
79
+ iconButtonColor?: string;
80
+ /** Color of the mute icon when voice guidance is muted. */
81
+ iconButtonMutedColor?: string;
67
82
  onRouteProgressChanged?: (event: {
68
83
  nativeEvent: RouteProgressEvent;
69
84
  }) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jacques_gordon/expo-mapbox-navigation",
3
- "version": "2.2.0",
3
+ "version": "2.2.3",
4
4
  "description": "Expo module for Mapbox Navigation SDK with 16KB page size support, NDK27, and Mapbox Maps v11.11.0+",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -9,7 +9,7 @@
9
9
  "clean": "rm -rf build",
10
10
  "lint": "tsc --noEmit",
11
11
  "test": "echo \"Tests require a full Expo project with EAS Build. See README.\"",
12
- "prepublishOnly": "npm run build"
12
+ "prepublishOnly": ""
13
13
  },
14
14
  "keywords": [
15
15
  "expo",
package/src/index.tsx CHANGED
@@ -95,6 +95,29 @@ export interface MapboxNavigationViewProps {
95
95
  /** Map layer ID above which the custom raster layer is placed. */
96
96
  customRasterAboveLayerId?: string;
97
97
 
98
+ // ── Color customization ──────────────────────────────────────────────────
99
+
100
+ /**
101
+ * Background color of the turn-by-turn instruction banner, as a hex string
102
+ * (e.g. "#1A73E8"). Uses the SDK's official ManeuverViewOptions API.
103
+ */
104
+ maneuverBackgroundColorDay?: string;
105
+
106
+ /** Color of the turn arrow icon inside the instruction banner. */
107
+ maneuverTurnIconColor?: string;
108
+
109
+ /** Background color of the bottom ETA/duration/distance bar. */
110
+ etaBarBackgroundColor?: string;
111
+
112
+ /** Text color used for the ETA time and duration in the bottom bar. */
113
+ etaTextColor?: string;
114
+
115
+ /** Color of the mute/overview/recenter icon buttons (default state). */
116
+ iconButtonColor?: string;
117
+
118
+ /** Color of the mute icon when voice guidance is muted. */
119
+ iconButtonMutedColor?: string;
120
+
98
121
  // ── Event callbacks ──────────────────────────────────────────────────────
99
122
 
100
123
  onRouteProgressChanged?: (event: { nativeEvent: RouteProgressEvent }) => void;