@jacques_gordon/expo-mapbox-navigation 2.1.5 → 2.1.6
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/expo/modules/mapboxnavigation/ExpoMapboxNavigationModule.kt +2 -1
- package/android/src/main/java/expo/modules/mapboxnavigation/ExpoMapboxNavigationView.kt +166 -62
- package/android/src/main/res/values/styles.xml +40 -0
- package/build/index.d.ts +35 -0
- package/package.json +1 -1
- package/src/index.tsx +37 -0
|
@@ -15,7 +15,8 @@ class ExpoMapboxNavigationModule : Module() {
|
|
|
15
15
|
"onNavigationFinished",
|
|
16
16
|
"onNavigationCancelled",
|
|
17
17
|
"onRoutesFailed",
|
|
18
|
-
"onArrival"
|
|
18
|
+
"onArrival",
|
|
19
|
+
"onManeuverBannerPressed"
|
|
19
20
|
)
|
|
20
21
|
|
|
21
22
|
Prop("coordinates") { view: ExpoMapboxNavigationView, coordinates: List<Map<String, Double>> ->
|
|
@@ -93,6 +93,11 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
93
93
|
private val onNavigationCancelled by EventDispatcher()
|
|
94
94
|
private val onRoutesFailed by EventDispatcher()
|
|
95
95
|
private val onArrival by EventDispatcher()
|
|
96
|
+
// Feature: emits the full list of turn-by-turn steps when the instruction
|
|
97
|
+
// banner is tapped, so the RN layer can render a steps list (bottom
|
|
98
|
+
// sheet/modal). Payload: { steps: [{ instruction, distanceMeters,
|
|
99
|
+
// maneuverType, maneuverModifier, laneInstructions }] }
|
|
100
|
+
private val onManeuverBannerPressed by EventDispatcher()
|
|
96
101
|
|
|
97
102
|
// ── Views ─────────────────────────────────────────────────────────────────
|
|
98
103
|
private val mapView: MapView = MapView(context)
|
|
@@ -135,11 +140,15 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
135
140
|
private val dp = context.resources.displayMetrics.density
|
|
136
141
|
|
|
137
142
|
// ── Viewport padding ──────────────────────────────────────────────────────
|
|
143
|
+
// ── Viewport padding — EXACT official Mapbox values from the camera guide ──
|
|
144
|
+
// Previous bottom=300dp pushed the focal point too far up, causing
|
|
145
|
+
// the puck to visibly jump/drift between location updates.
|
|
146
|
+
// Official guide value: top=180, left=40, bottom=150, right=40
|
|
138
147
|
private val followingPadding by lazy {
|
|
139
|
-
EdgeInsets(180.0 * dp, 40.0 * dp,
|
|
148
|
+
EdgeInsets(180.0 * dp, 40.0 * dp, 150.0 * dp, 40.0 * dp)
|
|
140
149
|
}
|
|
141
150
|
private val overviewPadding by lazy {
|
|
142
|
-
EdgeInsets(
|
|
151
|
+
EdgeInsets(140.0 * dp, 40.0 * dp, 120.0 * dp, 40.0 * dp)
|
|
143
152
|
}
|
|
144
153
|
|
|
145
154
|
// ── Props ─────────────────────────────────────────────────────────────────
|
|
@@ -167,43 +176,47 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
167
176
|
// Draw icons programmatically (matching screenshot: speaker, route, arrow)
|
|
168
177
|
// ─────────────────────────────────────────────────────────────────────────
|
|
169
178
|
|
|
179
|
+
// ── Modern Google Maps / Waze style icons ──────────────────────────────────
|
|
180
|
+
|
|
170
181
|
private fun drawSpeakerIcon(muted: Boolean): Bitmap {
|
|
171
182
|
val size = (44 * dp).toInt()
|
|
172
183
|
val bmp = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
|
|
173
184
|
val c = Canvas(bmp)
|
|
185
|
+
val s = size.toFloat()
|
|
186
|
+
val color = if (muted) Color.parseColor("#5F6368") else Color.parseColor("#1A73E8")
|
|
174
187
|
val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
175
|
-
color =
|
|
188
|
+
this.color = color
|
|
176
189
|
style = Paint.Style.FILL
|
|
177
190
|
}
|
|
178
|
-
|
|
179
|
-
// Speaker body (trapezoid)
|
|
191
|
+
// Speaker cone (rounded, modern Material style)
|
|
180
192
|
val body = Path().apply {
|
|
181
|
-
moveTo(s * 0.
|
|
182
|
-
lineTo(s * 0.
|
|
183
|
-
lineTo(s * 0.
|
|
184
|
-
|
|
185
|
-
lineTo(s * 0.
|
|
186
|
-
|
|
193
|
+
moveTo(s * 0.16f, s * 0.40f)
|
|
194
|
+
lineTo(s * 0.34f, s * 0.40f)
|
|
195
|
+
lineTo(s * 0.56f, s * 0.20f)
|
|
196
|
+
quadTo(s * 0.60f, s * 0.17f, s * 0.60f, s * 0.22f)
|
|
197
|
+
lineTo(s * 0.60f, s * 0.78f)
|
|
198
|
+
quadTo(s * 0.60f, s * 0.83f, s * 0.56f, s * 0.80f)
|
|
199
|
+
lineTo(s * 0.34f, s * 0.60f)
|
|
200
|
+
lineTo(s * 0.16f, s * 0.60f)
|
|
201
|
+
quadTo(s * 0.12f, s * 0.60f, s * 0.12f, s * 0.56f)
|
|
202
|
+
lineTo(s * 0.12f, s * 0.44f)
|
|
203
|
+
quadTo(s * 0.12f, s * 0.40f, s * 0.16f, s * 0.40f)
|
|
187
204
|
close()
|
|
188
205
|
}
|
|
189
206
|
c.drawPath(body, paint)
|
|
190
207
|
if (!muted) {
|
|
191
|
-
// Sound waves arcs
|
|
192
208
|
paint.style = Paint.Style.STROKE
|
|
193
|
-
paint.strokeWidth = s * 0.
|
|
209
|
+
paint.strokeWidth = s * 0.06f
|
|
194
210
|
paint.strokeCap = Paint.Cap.ROUND
|
|
195
|
-
|
|
196
|
-
c.drawArc(RectF(s*0.
|
|
197
|
-
// Outer arc
|
|
198
|
-
c.drawArc(RectF(s*0.64f, s*0.24f, s*0.92f, s*0.76f), -55f, 110f, false, paint)
|
|
211
|
+
c.drawArc(RectF(s*0.62f, s*0.34f, s*0.80f, s*0.66f), -45f, 90f, false, paint)
|
|
212
|
+
c.drawArc(RectF(s*0.66f, s*0.22f, s*0.92f, s*0.78f), -40f, 80f, false, paint)
|
|
199
213
|
} else {
|
|
200
|
-
// X mark for muted
|
|
201
214
|
paint.style = Paint.Style.STROKE
|
|
202
|
-
paint.strokeWidth = s * 0.
|
|
215
|
+
paint.strokeWidth = s * 0.065f
|
|
203
216
|
paint.strokeCap = Paint.Cap.ROUND
|
|
204
|
-
paint.color = Color.parseColor("#
|
|
205
|
-
c.drawLine(s*0.
|
|
206
|
-
c.drawLine(s*0.88f, s*0.
|
|
217
|
+
paint.color = Color.parseColor("#EA4335")
|
|
218
|
+
c.drawLine(s*0.66f, s*0.34f, s*0.88f, s*0.66f, paint)
|
|
219
|
+
c.drawLine(s*0.88f, s*0.34f, s*0.66f, s*0.66f, paint)
|
|
207
220
|
}
|
|
208
221
|
return bmp
|
|
209
222
|
}
|
|
@@ -212,34 +225,29 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
212
225
|
val size = (44 * dp).toInt()
|
|
213
226
|
val bmp = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
|
|
214
227
|
val c = Canvas(bmp)
|
|
228
|
+
val s = size.toFloat()
|
|
215
229
|
val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
216
|
-
color = Color.
|
|
217
|
-
style = Paint.Style.STROKE
|
|
218
|
-
strokeWidth = size * 0.08f
|
|
219
|
-
strokeCap = Paint.Cap.ROUND
|
|
220
|
-
strokeJoin = Paint.Join.ROUND
|
|
230
|
+
color = Color.parseColor("#1A73E8")
|
|
221
231
|
}
|
|
222
|
-
|
|
223
|
-
// Route line with S-curve (like the image: start dot → curve → end pin)
|
|
224
|
-
// Start dot
|
|
225
|
-
paint.style = Paint.Style.FILL
|
|
226
|
-
c.drawCircle(s * 0.28f, s * 0.75f, s * 0.07f, paint)
|
|
227
|
-
// Route path
|
|
232
|
+
// Map/overview icon: stacked layers (like Google Maps "map view" icon)
|
|
228
233
|
paint.style = Paint.Style.STROKE
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
)
|
|
234
|
+
paint.strokeWidth = s * 0.07f
|
|
235
|
+
paint.strokeJoin = Paint.Join.ROUND
|
|
236
|
+
paint.strokeCap = Paint.Cap.ROUND
|
|
237
|
+
// Outer diamond/map frame
|
|
238
|
+
val frame = Path().apply {
|
|
239
|
+
moveTo(s * 0.50f, s * 0.14f)
|
|
240
|
+
lineTo(s * 0.84f, s * 0.34f)
|
|
241
|
+
lineTo(s * 0.84f, s * 0.66f)
|
|
242
|
+
lineTo(s * 0.50f, s * 0.86f)
|
|
243
|
+
lineTo(s * 0.16f, s * 0.66f)
|
|
244
|
+
lineTo(s * 0.16f, s * 0.34f)
|
|
245
|
+
close()
|
|
236
246
|
}
|
|
237
|
-
c.drawPath(
|
|
238
|
-
//
|
|
239
|
-
|
|
240
|
-
c.
|
|
241
|
-
paint.style = Paint.Style.FILL
|
|
242
|
-
c.drawCircle(s * 0.72f, s * 0.22f, s * 0.04f, paint)
|
|
247
|
+
c.drawPath(frame, paint)
|
|
248
|
+
// Two horizontal fold lines (classic map icon)
|
|
249
|
+
c.drawLine(s*0.36f, s*0.22f, s*0.36f, s*0.78f, paint)
|
|
250
|
+
c.drawLine(s*0.64f, s*0.22f, s*0.64f, s*0.78f, paint)
|
|
243
251
|
return bmp
|
|
244
252
|
}
|
|
245
253
|
|
|
@@ -247,17 +255,17 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
247
255
|
val size = (44 * dp).toInt()
|
|
248
256
|
val bmp = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
|
|
249
257
|
val c = Canvas(bmp)
|
|
258
|
+
val s = size.toFloat()
|
|
250
259
|
val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
251
|
-
color = Color.
|
|
260
|
+
color = Color.parseColor("#1A73E8")
|
|
252
261
|
style = Paint.Style.FILL
|
|
253
262
|
}
|
|
254
|
-
|
|
255
|
-
// Navigation arrow pointing up (filled chevron/arrow like Waze)
|
|
263
|
+
// Navigation/recenter compass arrow (Google Maps "my location, follow" style)
|
|
256
264
|
val arrow = Path().apply {
|
|
257
|
-
moveTo(s * 0.50f, s * 0.
|
|
258
|
-
lineTo(s * 0.
|
|
259
|
-
lineTo(s * 0.50f, s * 0.
|
|
260
|
-
lineTo(s * 0.
|
|
265
|
+
moveTo(s * 0.50f, s * 0.12f) // tip
|
|
266
|
+
lineTo(s * 0.80f, s * 0.82f) // bottom-right
|
|
267
|
+
lineTo(s * 0.50f, s * 0.64f) // notch center
|
|
268
|
+
lineTo(s * 0.20f, s * 0.82f) // bottom-left
|
|
261
269
|
close()
|
|
262
270
|
}
|
|
263
271
|
c.drawPath(arrow, paint)
|
|
@@ -295,12 +303,21 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
295
303
|
))
|
|
296
304
|
|
|
297
305
|
// ── ManeuverView — top ─────────────────────────────────────────────────
|
|
298
|
-
|
|
306
|
+
// FIX: apply custom style with autoSizeTextType so long street names
|
|
307
|
+
// (e.g. "Wulmersumsesteenweg") shrink to fit instead of being clipped.
|
|
308
|
+
val mv = MapboxManeuverView(
|
|
309
|
+
android.view.ContextThemeWrapper(context, R.style.MapboxCustomManeuverViewStyle)
|
|
310
|
+
)
|
|
299
311
|
root.addView(mv as View, FrameLayout.LayoutParams(
|
|
300
312
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
301
313
|
FrameLayout.LayoutParams.WRAP_CONTENT
|
|
302
314
|
).also { it.gravity = Gravity.TOP })
|
|
303
315
|
mv.visibility = View.INVISIBLE
|
|
316
|
+
// Feature: tap the instruction banner to see the full list of upcoming
|
|
317
|
+
// turn-by-turn steps. We emit the data via event so the RN/JS layer can
|
|
318
|
+
// render a bottom sheet or modal using its own native UI components —
|
|
319
|
+
// consistent with how all other navigation events are surfaced.
|
|
320
|
+
mv.setOnClickListener { emitFullRouteSteps() }
|
|
304
321
|
maneuverView = mv
|
|
305
322
|
|
|
306
323
|
// ── Side buttons — RIGHT side, just below maneuver banner ─────────────
|
|
@@ -336,17 +353,21 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
336
353
|
})
|
|
337
354
|
btnOverviewView = overviewBtn
|
|
338
355
|
|
|
339
|
-
// Button 3: Recenter /
|
|
356
|
+
// Button 3: Recenter / return to follow mode — ALWAYS visible, below button 2
|
|
357
|
+
// Greyed out (lower opacity) when already following; full opacity in overview mode
|
|
340
358
|
val recenterBtn = makeIconButton(drawNavigationArrowIcon()) { recenterCamera() }
|
|
341
|
-
recenterBtn.
|
|
359
|
+
recenterBtn.alpha = 0.4f // dimmed by default (already following)
|
|
342
360
|
sideCol.addView(recenterBtn, LinearLayout.LayoutParams(btnSize, btnSize))
|
|
343
361
|
btnRecenterView = recenterBtn
|
|
344
362
|
|
|
345
363
|
// ── SpeedInfoView — bottom-left, above ETA bar ────────────────────────
|
|
364
|
+
// FIX: use WRAP_CONTENT instead of fixed 72x72dp — the native Mapbox
|
|
365
|
+
// component renders both the posted speed limit sign AND the current
|
|
366
|
+
// vehicle speed side-by-side, which needs flexible width
|
|
346
367
|
val siv = MapboxSpeedInfoView(context)
|
|
347
368
|
root.addView(siv as View, FrameLayout.LayoutParams(
|
|
348
|
-
|
|
349
|
-
|
|
369
|
+
FrameLayout.LayoutParams.WRAP_CONTENT,
|
|
370
|
+
FrameLayout.LayoutParams.WRAP_CONTENT
|
|
350
371
|
).also {
|
|
351
372
|
it.gravity = Gravity.BOTTOM or Gravity.START
|
|
352
373
|
it.setMargins((12 * dp).toInt(), 0, 0, (88 * dp).toInt())
|
|
@@ -492,6 +513,11 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
492
513
|
viewportDataSource.followingPadding = followingPadding
|
|
493
514
|
viewportDataSource.overviewPadding = overviewPadding
|
|
494
515
|
|
|
516
|
+
// Waze-style tilted 3D following view — also helps stabilize the
|
|
517
|
+
// visual framing since pitch reduces perceived jitter from GPS noise
|
|
518
|
+
viewportDataSource.options.followingFrameOptions.defaultPitch = 45.0
|
|
519
|
+
viewportDataSource.options.followingFrameOptions.maxZoom = 17.0
|
|
520
|
+
|
|
495
521
|
navigationCamera = NavigationCamera(
|
|
496
522
|
mapView.mapboxMap,
|
|
497
523
|
mapView.camera,
|
|
@@ -601,7 +627,15 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
601
627
|
override fun onNewRawLocation(rawLocation: Location) {}
|
|
602
628
|
override fun onNewLocationMatcherResult(result: LocationMatcherResult) {
|
|
603
629
|
val loc = result.enhancedLocation
|
|
604
|
-
|
|
630
|
+
// ── FIX: Puck jitter / drift ──────────────────────────────────────
|
|
631
|
+
// Root cause (confirmed via Mapbox GitHub issue #4140):
|
|
632
|
+
// When keyPoints are passed to changePosition(), the puck animator
|
|
633
|
+
// splits the 1-second transition EVENLY across all keypoints in time,
|
|
634
|
+
// but the keypoints are NOT evenly spaced in distance. This causes the
|
|
635
|
+
// puck to speed up/slow down and visibly drift left/right off the
|
|
636
|
+
// route line, even on a perfectly straight road.
|
|
637
|
+
// Official Mapbox workaround: pass emptyList() instead of keyPoints.
|
|
638
|
+
navigationLocationProvider.changePosition(location = loc, keyPoints = emptyList())
|
|
605
639
|
viewportDataSource.onLocationChanged(loc)
|
|
606
640
|
viewportDataSource.evaluate()
|
|
607
641
|
|
|
@@ -610,6 +644,11 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
610
644
|
safeCameraFollowing()
|
|
611
645
|
}
|
|
612
646
|
|
|
647
|
+
// ── Speed limit (issue #4: was not displaying) ──────────────────
|
|
648
|
+
// updatePostedAndCurrentSpeed returns null when:
|
|
649
|
+
// 1. The route response has no `maxspeed` annotation for this segment
|
|
650
|
+
// 2. GPS speed is unavailable
|
|
651
|
+
// We log this so issues can be diagnosed via `adb logcat`
|
|
613
652
|
val fmtOptions = DistanceFormatterOptions.Builder(context).build()
|
|
614
653
|
val speedInfo = speedInfoApi.updatePostedAndCurrentSpeed(result, fmtOptions)
|
|
615
654
|
if (speedInfo != null) {
|
|
@@ -617,6 +656,8 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
617
656
|
speedInfoView?.render(speedInfo)
|
|
618
657
|
} else {
|
|
619
658
|
speedInfoView?.visibility = View.GONE
|
|
659
|
+
Log.d(TAG, "Speed info unavailable for this location/route segment " +
|
|
660
|
+
"(no maxspeed annotation or GPS speed not ready yet)")
|
|
620
661
|
}
|
|
621
662
|
|
|
622
663
|
checkAndSwitchDayNight()
|
|
@@ -643,7 +684,8 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
643
684
|
recenterCamera()
|
|
644
685
|
} else {
|
|
645
686
|
isOverviewMode = true
|
|
646
|
-
|
|
687
|
+
// Button 3 lights up (full opacity) to invite the user to tap it
|
|
688
|
+
btnRecenterView?.alpha = 1f
|
|
647
689
|
try {
|
|
648
690
|
navigationCamera.requestNavigationCameraToOverview()
|
|
649
691
|
} catch (e: Exception) {
|
|
@@ -654,7 +696,8 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
654
696
|
|
|
655
697
|
private fun recenterCamera() {
|
|
656
698
|
isOverviewMode = false
|
|
657
|
-
|
|
699
|
+
// Button 3 dims again — already in following mode
|
|
700
|
+
btnRecenterView?.alpha = 0.4f
|
|
658
701
|
safeCameraFollowing()
|
|
659
702
|
}
|
|
660
703
|
|
|
@@ -713,7 +756,53 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
713
756
|
}
|
|
714
757
|
}
|
|
715
758
|
|
|
716
|
-
|
|
759
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
760
|
+
// Feature: full route steps list (triggered by tapping the instruction
|
|
761
|
+
// banner). Extracts every LegStep across all legs of the active route,
|
|
762
|
+
// including lane guidance data when present on that step's BannerInstructions.
|
|
763
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
764
|
+
private fun emitFullRouteSteps() {
|
|
765
|
+
val routes = mapboxNavigation?.getNavigationRoutes()
|
|
766
|
+
val activeRoute = routes?.firstOrNull() ?: run {
|
|
767
|
+
onManeuverBannerPressed(mapOf("steps" to emptyList<Map<String, Any>>()))
|
|
768
|
+
return
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
val stepsPayload = mutableListOf<Map<String, Any>>()
|
|
772
|
+
|
|
773
|
+
activeRoute.directionsRoute.legs()?.forEach { leg ->
|
|
774
|
+
leg.steps()?.forEach { step ->
|
|
775
|
+
val maneuver = step.maneuver()
|
|
776
|
+
val firstBanner = step.bannerInstructions()?.firstOrNull()
|
|
777
|
+
|
|
778
|
+
// Lane guidance: present on BannerInstructions.sub() when type == "lane"
|
|
779
|
+
val laneData = firstBanner?.sub()?.components()
|
|
780
|
+
?.filter { it.type() == "lane" }
|
|
781
|
+
?.map { component ->
|
|
782
|
+
mapOf(
|
|
783
|
+
"active" to (component.active() ?: false),
|
|
784
|
+
"directions" to (component.directions() ?: emptyList<String>())
|
|
785
|
+
)
|
|
786
|
+
} ?: emptyList()
|
|
787
|
+
|
|
788
|
+
stepsPayload.add(
|
|
789
|
+
mapOf(
|
|
790
|
+
"instruction" to (maneuver?.instruction() ?: ""),
|
|
791
|
+
"distanceMeters" to (step.distance() ?: 0.0),
|
|
792
|
+
"durationSeconds" to (step.duration() ?: 0.0),
|
|
793
|
+
"maneuverType" to (maneuver?.type() ?: ""),
|
|
794
|
+
"maneuverModifier" to (maneuver?.modifier() ?: ""),
|
|
795
|
+
"roadName" to (step.name() ?: ""),
|
|
796
|
+
"laneInstructions" to laneData
|
|
797
|
+
)
|
|
798
|
+
)
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
onManeuverBannerPressed(mapOf("steps" to stepsPayload))
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
|
|
717
806
|
speechApi.cancel()
|
|
718
807
|
voiceInstructionsPlayer.clear()
|
|
719
808
|
mapboxNavigation?.setNavigationRoutes(listOf())
|
|
@@ -760,6 +849,21 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
760
849
|
.voiceUnits(resolveVoiceUnits())
|
|
761
850
|
.coordinatesList(points)
|
|
762
851
|
.annotations("maxspeed,congestion,duration,speed")
|
|
852
|
+
// ── FIX: Lane guidance was not displaying ───────────────────────────
|
|
853
|
+
// Root cause: BannerInstructions.sub() (which carries lane data) is
|
|
854
|
+
// only returned by the Directions API when explicitly requested.
|
|
855
|
+
// applyDefaultNavigationOptions() does set bannerInstructions(true)
|
|
856
|
+
// internally, but several confirmed cases (Mapbox GitHub issue #7377,
|
|
857
|
+
// "Lane guidance not showing, using Navigation Drop-in UI") show this
|
|
858
|
+
// needs to be set explicitly to reliably trigger lane data in the
|
|
859
|
+
// response — official Mapbox examples do the same as a defensive fix.
|
|
860
|
+
.bannerInstructions(true)
|
|
861
|
+
// steps(true) is required for any BannerInstructions/VoiceInstructions
|
|
862
|
+
// to be present in the response at all.
|
|
863
|
+
.steps(true)
|
|
864
|
+
// roundaboutExits(true) ensures lane + exit guidance is generated for
|
|
865
|
+
// roundabout maneuvers specifically (separate flag from bannerInstructions).
|
|
866
|
+
.roundaboutExits(true)
|
|
763
867
|
waypointIndices?.let { builder.waypointIndicesList(it) }
|
|
764
868
|
navigationProfile?.let { builder.profile(if (it.startsWith("mapbox/")) it else "mapbox/$it") }
|
|
765
869
|
excludeTypes?.takeIf { it.isNotEmpty() }?.let { builder.exclude(it.joinToString(",")) }
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<resources>
|
|
3
|
+
|
|
4
|
+
<!--
|
|
5
|
+
FIX: Street/road name text was being cut off on the instruction banner
|
|
6
|
+
(e.g. "Wulmersumsesteenwe" instead of "Wulmersumsesteenweg") on
|
|
7
|
+
narrower or smaller-DPI screens.
|
|
8
|
+
|
|
9
|
+
Root cause: MapboxPrimaryManeuver (the road name text view inside
|
|
10
|
+
MapboxManeuverView) uses a fixed text size by default with no
|
|
11
|
+
auto-shrink behavior, so long street names get clipped by ellipsis
|
|
12
|
+
instead of adapting to the available banner width.
|
|
13
|
+
|
|
14
|
+
Fix: define a custom text appearance with Android's built-in
|
|
15
|
+
autoSizeTextType="uniform", which lets the system shrink the text
|
|
16
|
+
size step-by-step (within autoSizeMinTextSize..autoSizeMaxTextSize)
|
|
17
|
+
until the full string fits the view width — exactly like Waze/Google
|
|
18
|
+
Maps adapt their instruction banner text across different phone
|
|
19
|
+
screen sizes.
|
|
20
|
+
-->
|
|
21
|
+
<style name="MapboxCustomManeuverTextStyle" parent="MapboxStylePrimaryManeuver">
|
|
22
|
+
<item name="android:autoSizeTextType">uniform</item>
|
|
23
|
+
<item name="android:autoSizeMinTextSize">16sp</item>
|
|
24
|
+
<item name="android:autoSizeMaxTextSize">28sp</item>
|
|
25
|
+
<item name="android:autoSizeStepGranularity">1sp</item>
|
|
26
|
+
<item name="android:maxLines">2</item>
|
|
27
|
+
<item name="android:ellipsize">end</item>
|
|
28
|
+
</style>
|
|
29
|
+
|
|
30
|
+
<!--
|
|
31
|
+
Custom MapboxManeuverView style that:
|
|
32
|
+
1. Applies the auto-sizing primary maneuver text style above.
|
|
33
|
+
2. Keeps default backgrounds/colors (day mode handled by Nav SDK
|
|
34
|
+
style switching already implemented in ExpoMapboxNavigationView).
|
|
35
|
+
-->
|
|
36
|
+
<style name="MapboxCustomManeuverViewStyle" parent="MapboxStyleManeuverView">
|
|
37
|
+
<item name="primaryManeuverTextAppearance">@style/MapboxCustomManeuverTextStyle</item>
|
|
38
|
+
</style>
|
|
39
|
+
|
|
40
|
+
</resources>
|
package/build/index.d.ts
CHANGED
|
@@ -84,6 +84,14 @@ export interface MapboxNavigationViewProps {
|
|
|
84
84
|
onArrival?: (event: {
|
|
85
85
|
nativeEvent: {};
|
|
86
86
|
}) => void;
|
|
87
|
+
/**
|
|
88
|
+
* Fired when the user taps the turn-by-turn instruction banner.
|
|
89
|
+
* Use this to open a bottom sheet / modal listing every upcoming step
|
|
90
|
+
* of the route, including lane guidance data where available.
|
|
91
|
+
*/
|
|
92
|
+
onManeuverBannerPressed?: (event: {
|
|
93
|
+
nativeEvent: ManeuverBannerPressedEvent;
|
|
94
|
+
}) => void;
|
|
87
95
|
style?: ViewStyle;
|
|
88
96
|
}
|
|
89
97
|
export interface RouteProgressEvent {
|
|
@@ -98,6 +106,33 @@ export interface RoutesReadyEvent {
|
|
|
98
106
|
distanceMeters: number;
|
|
99
107
|
durationSeconds: number;
|
|
100
108
|
}
|
|
109
|
+
/** A single lane's directional options at an upcoming intersection. */
|
|
110
|
+
export interface LaneInstruction {
|
|
111
|
+
/** Whether this lane is the recommended lane for the upcoming maneuver. */
|
|
112
|
+
active: boolean;
|
|
113
|
+
/** Directions this lane allows, e.g. ["straight"], ["left"], ["straight", "right"]. */
|
|
114
|
+
directions: string[];
|
|
115
|
+
}
|
|
116
|
+
/** One step (maneuver) along the route, used for the full-route steps list. */
|
|
117
|
+
export interface RouteStep {
|
|
118
|
+
/** Human-readable instruction text, e.g. "Turn left onto Main St". */
|
|
119
|
+
instruction: string;
|
|
120
|
+
/** Distance in metres for this step. */
|
|
121
|
+
distanceMeters: number;
|
|
122
|
+
/** Duration in seconds for this step. */
|
|
123
|
+
durationSeconds: number;
|
|
124
|
+
/** Directions API maneuver type, e.g. "turn", "merge", "roundabout". */
|
|
125
|
+
maneuverType: string;
|
|
126
|
+
/** Directions API maneuver modifier, e.g. "left", "right", "straight". */
|
|
127
|
+
maneuverModifier: string;
|
|
128
|
+
/** Name of the road for this step, if available. */
|
|
129
|
+
roadName: string;
|
|
130
|
+
/** Lane guidance for this step's upcoming maneuver, if available. */
|
|
131
|
+
laneInstructions: LaneInstruction[];
|
|
132
|
+
}
|
|
133
|
+
export interface ManeuverBannerPressedEvent {
|
|
134
|
+
steps: RouteStep[];
|
|
135
|
+
}
|
|
101
136
|
/**
|
|
102
137
|
* MapboxNavigationView
|
|
103
138
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jacques_gordon/expo-mapbox-navigation",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.6",
|
|
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",
|
package/src/index.tsx
CHANGED
|
@@ -104,6 +104,13 @@ export interface MapboxNavigationViewProps {
|
|
|
104
104
|
onRoutesFailed?: (event: { nativeEvent: { message: string } }) => void;
|
|
105
105
|
onArrival?: (event: { nativeEvent: {} }) => void;
|
|
106
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Fired when the user taps the turn-by-turn instruction banner.
|
|
109
|
+
* Use this to open a bottom sheet / modal listing every upcoming step
|
|
110
|
+
* of the route, including lane guidance data where available.
|
|
111
|
+
*/
|
|
112
|
+
onManeuverBannerPressed?: (event: { nativeEvent: ManeuverBannerPressedEvent }) => void;
|
|
113
|
+
|
|
107
114
|
style?: ViewStyle;
|
|
108
115
|
}
|
|
109
116
|
|
|
@@ -121,6 +128,36 @@ export interface RoutesReadyEvent {
|
|
|
121
128
|
durationSeconds: number;
|
|
122
129
|
}
|
|
123
130
|
|
|
131
|
+
/** A single lane's directional options at an upcoming intersection. */
|
|
132
|
+
export interface LaneInstruction {
|
|
133
|
+
/** Whether this lane is the recommended lane for the upcoming maneuver. */
|
|
134
|
+
active: boolean;
|
|
135
|
+
/** Directions this lane allows, e.g. ["straight"], ["left"], ["straight", "right"]. */
|
|
136
|
+
directions: string[];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** One step (maneuver) along the route, used for the full-route steps list. */
|
|
140
|
+
export interface RouteStep {
|
|
141
|
+
/** Human-readable instruction text, e.g. "Turn left onto Main St". */
|
|
142
|
+
instruction: string;
|
|
143
|
+
/** Distance in metres for this step. */
|
|
144
|
+
distanceMeters: number;
|
|
145
|
+
/** Duration in seconds for this step. */
|
|
146
|
+
durationSeconds: number;
|
|
147
|
+
/** Directions API maneuver type, e.g. "turn", "merge", "roundabout". */
|
|
148
|
+
maneuverType: string;
|
|
149
|
+
/** Directions API maneuver modifier, e.g. "left", "right", "straight". */
|
|
150
|
+
maneuverModifier: string;
|
|
151
|
+
/** Name of the road for this step, if available. */
|
|
152
|
+
roadName: string;
|
|
153
|
+
/** Lane guidance for this step's upcoming maneuver, if available. */
|
|
154
|
+
laneInstructions: LaneInstruction[];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface ManeuverBannerPressedEvent {
|
|
158
|
+
steps: RouteStep[];
|
|
159
|
+
}
|
|
160
|
+
|
|
124
161
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
125
162
|
// Native view
|
|
126
163
|
// ─────────────────────────────────────────────────────────────────────────────
|