@jacques_gordon/expo-mapbox-navigation 2.1.2 → 2.1.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.
|
@@ -4,19 +4,25 @@ import android.annotation.SuppressLint
|
|
|
4
4
|
import android.content.Context
|
|
5
5
|
import android.content.res.Resources
|
|
6
6
|
import android.graphics.Color
|
|
7
|
+
import android.graphics.drawable.GradientDrawable
|
|
7
8
|
import android.util.Log
|
|
8
9
|
import android.view.Gravity
|
|
9
10
|
import android.view.View
|
|
10
11
|
import android.widget.FrameLayout
|
|
12
|
+
import android.widget.ImageView
|
|
11
13
|
import android.widget.LinearLayout
|
|
12
14
|
import android.widget.TextView
|
|
13
15
|
import com.mapbox.api.directions.v5.models.RouteOptions
|
|
14
16
|
import com.mapbox.common.location.Location
|
|
15
17
|
import com.mapbox.geojson.Point
|
|
16
18
|
import com.mapbox.maps.EdgeInsets
|
|
19
|
+
import com.mapbox.maps.ImageHolder
|
|
17
20
|
import com.mapbox.maps.MapView
|
|
21
|
+
import com.mapbox.maps.plugin.LocationPuck2D
|
|
18
22
|
import com.mapbox.maps.plugin.animation.camera
|
|
19
23
|
import com.mapbox.maps.plugin.locationcomponent.location
|
|
24
|
+
import com.mapbox.maps.plugin.locationcomponent.OnIndicatorBearingChangedListener
|
|
25
|
+
import com.mapbox.maps.plugin.locationcomponent.OnIndicatorPositionChangedListener
|
|
20
26
|
import com.mapbox.navigation.base.TimeFormat
|
|
21
27
|
import com.mapbox.navigation.base.extensions.applyDefaultNavigationOptions
|
|
22
28
|
import com.mapbox.navigation.base.formatter.DistanceFormatterOptions
|
|
@@ -84,6 +90,12 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
84
90
|
private var tvDistance: TextView? = null
|
|
85
91
|
private var etaBar: LinearLayout? = null
|
|
86
92
|
|
|
93
|
+
// ── Side action buttons (mute, overview, recenter) ────────────────────────
|
|
94
|
+
private var btnMute: TextView? = null
|
|
95
|
+
private var btnOverview: TextView? = null
|
|
96
|
+
private var btnRecenter: TextView? = null
|
|
97
|
+
private var sideButtons: LinearLayout? = null
|
|
98
|
+
|
|
87
99
|
// ── Navigation APIs ───────────────────────────────────────────────────────
|
|
88
100
|
private lateinit var navigationCamera: NavigationCamera
|
|
89
101
|
private lateinit var viewportDataSource: MapboxNavigationViewportDataSource
|
|
@@ -96,28 +108,34 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
96
108
|
private lateinit var routeArrowView: MapboxRouteArrowView
|
|
97
109
|
private val navigationLocationProvider = NavigationLocationProvider()
|
|
98
110
|
private var mapboxNavigation: MapboxNavigation? = null
|
|
99
|
-
private var isNightMode = false
|
|
100
111
|
|
|
101
|
-
// ──
|
|
102
|
-
|
|
112
|
+
// ── State ─────────────────────────────────────────────────────────────────
|
|
113
|
+
private var isNightMode = false
|
|
114
|
+
private var isMuted = false
|
|
115
|
+
private var isOverviewMode = false
|
|
103
116
|
private var firstLocationReceived = false
|
|
104
117
|
|
|
105
|
-
// ──
|
|
106
|
-
private val
|
|
118
|
+
// ── Pixel density ─────────────────────────────────────────────────────────
|
|
119
|
+
private val dp = context.resources.displayMetrics.density
|
|
120
|
+
|
|
121
|
+
// ── Viewport padding (Waze-style: vehicle at ~30% from bottom) ────────────
|
|
122
|
+
// top=180dp → room for maneuver banner
|
|
123
|
+
// bottom=300dp → pushes vehicle UP toward 30% from bottom (Waze style)
|
|
124
|
+
// Without large bottom padding, vehicle sits at screen center
|
|
107
125
|
private val followingPadding by lazy {
|
|
108
126
|
EdgeInsets(
|
|
109
|
-
180.0 *
|
|
110
|
-
40.0 *
|
|
111
|
-
|
|
112
|
-
40.0 *
|
|
127
|
+
180.0 * dp, // top (maneuver banner height)
|
|
128
|
+
40.0 * dp, // left
|
|
129
|
+
300.0 * dp, // bottom — large value pushes vehicle up like Waze
|
|
130
|
+
40.0 * dp // right
|
|
113
131
|
)
|
|
114
132
|
}
|
|
115
133
|
private val overviewPadding by lazy {
|
|
116
134
|
EdgeInsets(
|
|
117
|
-
|
|
118
|
-
40.0 *
|
|
119
|
-
120.0 *
|
|
120
|
-
40.0 *
|
|
135
|
+
120.0 * dp,
|
|
136
|
+
40.0 * dp,
|
|
137
|
+
120.0 * dp,
|
|
138
|
+
40.0 * dp
|
|
121
139
|
)
|
|
122
140
|
}
|
|
123
141
|
|
|
@@ -143,22 +161,20 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
143
161
|
}
|
|
144
162
|
|
|
145
163
|
// ─────────────────────────────────────────────────────────────────────────
|
|
146
|
-
// Build UI
|
|
164
|
+
// Build Waze-style UI
|
|
147
165
|
// ─────────────────────────────────────────────────────────────────────────
|
|
148
166
|
private fun buildUI() {
|
|
149
|
-
val dp = context.resources.displayMetrics.density
|
|
150
167
|
val root = FrameLayout(context).apply {
|
|
151
168
|
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
|
152
169
|
}
|
|
153
170
|
|
|
154
|
-
// Full
|
|
171
|
+
// Full-screen map
|
|
155
172
|
root.addView(mapView, FrameLayout.LayoutParams(
|
|
156
173
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
157
174
|
FrameLayout.LayoutParams.MATCH_PARENT
|
|
158
175
|
))
|
|
159
176
|
|
|
160
177
|
// ── ManeuverView — top banner ──────────────────────────────────────────
|
|
161
|
-
// extends ConstraintLayout → must cast to View for addView()
|
|
162
178
|
val mv = MapboxManeuverView(context)
|
|
163
179
|
root.addView(mv as View, FrameLayout.LayoutParams(
|
|
164
180
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
@@ -167,24 +183,60 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
167
183
|
mv.visibility = View.INVISIBLE
|
|
168
184
|
maneuverView = mv
|
|
169
185
|
|
|
170
|
-
// ── SpeedInfoView — bottom-left
|
|
171
|
-
// Shows posted speed limit + current vehicle speed
|
|
186
|
+
// ── SpeedInfoView — bottom-left, above ETA bar ────────────────────────
|
|
172
187
|
val siv = MapboxSpeedInfoView(context)
|
|
173
188
|
root.addView(siv as View, FrameLayout.LayoutParams(
|
|
174
|
-
|
|
175
|
-
|
|
189
|
+
(72 * dp).toInt(),
|
|
190
|
+
(72 * dp).toInt()
|
|
176
191
|
).also {
|
|
177
192
|
it.gravity = Gravity.BOTTOM or Gravity.START
|
|
178
|
-
|
|
179
|
-
it.setMargins((16 * dp).toInt(), 0, 0, (96 * dp).toInt())
|
|
193
|
+
it.setMargins((12 * dp).toInt(), 0, 0, (88 * dp).toInt())
|
|
180
194
|
})
|
|
181
195
|
siv.visibility = View.GONE
|
|
182
196
|
speedInfoView = siv
|
|
183
197
|
|
|
198
|
+
// ── Side action buttons (right side, Waze-style) ──────────────────────
|
|
199
|
+
val sideCol = LinearLayout(context).apply {
|
|
200
|
+
orientation = LinearLayout.VERTICAL
|
|
201
|
+
gravity = Gravity.CENTER_HORIZONTAL
|
|
202
|
+
visibility = View.INVISIBLE
|
|
203
|
+
}
|
|
204
|
+
root.addView(sideCol, FrameLayout.LayoutParams(
|
|
205
|
+
FrameLayout.LayoutParams.WRAP_CONTENT,
|
|
206
|
+
FrameLayout.LayoutParams.WRAP_CONTENT
|
|
207
|
+
).also {
|
|
208
|
+
it.gravity = Gravity.END or Gravity.BOTTOM
|
|
209
|
+
it.setMargins(0, 0, (12 * dp).toInt(), (96 * dp).toInt())
|
|
210
|
+
})
|
|
211
|
+
sideButtons = sideCol
|
|
212
|
+
|
|
213
|
+
// Mute button
|
|
214
|
+
val muteBtn = makeCircleButton("🔊")
|
|
215
|
+
muteBtn.setOnClickListener { toggleMute() }
|
|
216
|
+
sideCol.addView(muteBtn, circleButtonParams())
|
|
217
|
+
btnMute = muteBtn
|
|
218
|
+
|
|
219
|
+
sideCol.addView(View(context), LinearLayout.LayoutParams(1, (10 * dp).toInt()))
|
|
220
|
+
|
|
221
|
+
// Overview button
|
|
222
|
+
val overviewBtn = makeCircleButton("⊕")
|
|
223
|
+
overviewBtn.setOnClickListener { toggleOverview() }
|
|
224
|
+
sideCol.addView(overviewBtn, circleButtonParams())
|
|
225
|
+
btnOverview = overviewBtn
|
|
226
|
+
|
|
227
|
+
sideCol.addView(View(context), LinearLayout.LayoutParams(1, (10 * dp).toInt()))
|
|
228
|
+
|
|
229
|
+
// Recenter button (shown only in overview mode)
|
|
230
|
+
val recenterBtn = makeCircleButton("◎")
|
|
231
|
+
recenterBtn.setOnClickListener { recenterCamera() }
|
|
232
|
+
recenterBtn.visibility = View.GONE
|
|
233
|
+
sideCol.addView(recenterBtn, circleButtonParams())
|
|
234
|
+
btnRecenter = recenterBtn
|
|
235
|
+
|
|
184
236
|
// ── ETA bottom bar ─────────────────────────────────────────────────────
|
|
185
237
|
val bar = LinearLayout(context).apply {
|
|
186
238
|
orientation = LinearLayout.HORIZONTAL
|
|
187
|
-
setBackgroundColor(Color.parseColor("#1E2433"))
|
|
239
|
+
setBackgroundColor(Color.parseColor("#1E2433"))
|
|
188
240
|
elevation = 8 * dp
|
|
189
241
|
visibility = View.INVISIBLE
|
|
190
242
|
gravity = Gravity.CENTER_VERTICAL
|
|
@@ -204,9 +256,7 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
204
256
|
setTextColor(Color.WHITE)
|
|
205
257
|
typeface = android.graphics.Typeface.DEFAULT_BOLD
|
|
206
258
|
}
|
|
207
|
-
bar.addView(etaTime, LinearLayout.LayoutParams(
|
|
208
|
-
0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f
|
|
209
|
-
))
|
|
259
|
+
bar.addView(etaTime, LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f))
|
|
210
260
|
tvEtaTime = etaTime
|
|
211
261
|
|
|
212
262
|
// Duration + distance (center)
|
|
@@ -227,9 +277,7 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
227
277
|
}
|
|
228
278
|
center.addView(dur)
|
|
229
279
|
center.addView(dist)
|
|
230
|
-
bar.addView(center, LinearLayout.LayoutParams(
|
|
231
|
-
0, LinearLayout.LayoutParams.WRAP_CONTENT, 2f
|
|
232
|
-
))
|
|
280
|
+
bar.addView(center, LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 2f))
|
|
233
281
|
tvDuration = dur
|
|
234
282
|
tvDistance = dist
|
|
235
283
|
|
|
@@ -241,21 +289,39 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
241
289
|
gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
|
242
290
|
setOnClickListener { cancelNavigation() }
|
|
243
291
|
}
|
|
244
|
-
bar.addView(cancelBtn, LinearLayout.LayoutParams(
|
|
245
|
-
0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f
|
|
246
|
-
))
|
|
292
|
+
bar.addView(cancelBtn, LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f))
|
|
247
293
|
etaBar = bar
|
|
248
294
|
|
|
249
|
-
//
|
|
250
|
-
// FIX: was created but never added to root — now added but invisible
|
|
295
|
+
// TripProgressView — 1×1 hidden (required for render() API)
|
|
251
296
|
val tpv = MapboxTripProgressView(context)
|
|
252
|
-
root.addView(tpv as View, FrameLayout.LayoutParams(1, 1))
|
|
297
|
+
root.addView(tpv as View, FrameLayout.LayoutParams(1, 1))
|
|
253
298
|
tpv.visibility = View.GONE
|
|
254
299
|
tripProgressView = tpv
|
|
255
300
|
|
|
256
301
|
addView(root)
|
|
257
302
|
}
|
|
258
303
|
|
|
304
|
+
// Helper: creates a white circle button (Waze style)
|
|
305
|
+
private fun makeCircleButton(text: String): TextView {
|
|
306
|
+
return TextView(context).apply {
|
|
307
|
+
this.text = text
|
|
308
|
+
textSize = 20f
|
|
309
|
+
gravity = Gravity.CENTER
|
|
310
|
+
setTextColor(Color.BLACK)
|
|
311
|
+
background = GradientDrawable().apply {
|
|
312
|
+
shape = GradientDrawable.OVAL
|
|
313
|
+
setColor(Color.WHITE)
|
|
314
|
+
// Drop shadow via elevation
|
|
315
|
+
}
|
|
316
|
+
elevation = 6 * dp
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private fun circleButtonParams(): LinearLayout.LayoutParams {
|
|
321
|
+
val size = (52 * dp).toInt()
|
|
322
|
+
return LinearLayout.LayoutParams(size, size)
|
|
323
|
+
}
|
|
324
|
+
|
|
259
325
|
// ─────────────────────────────────────────────────────────────────────────
|
|
260
326
|
// Init APIs
|
|
261
327
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -297,11 +363,8 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
297
363
|
mapView.mapboxMap.loadStyle(mapStyle ?: getAutoStyle()) { style ->
|
|
298
364
|
routeLineView.initializeLayers(style)
|
|
299
365
|
|
|
300
|
-
// Camera viewport
|
|
366
|
+
// Camera viewport
|
|
301
367
|
viewportDataSource = MapboxNavigationViewportDataSource(mapView.mapboxMap)
|
|
302
|
-
|
|
303
|
-
// FIX: Set padding BEFORE creating NavigationCamera
|
|
304
|
-
// Without this, the camera doesn't account for the maneuver banner and ETA bar
|
|
305
368
|
viewportDataSource.followingPadding = followingPadding
|
|
306
369
|
viewportDataSource.overviewPadding = overviewPadding
|
|
307
370
|
|
|
@@ -311,10 +374,24 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
311
374
|
viewportDataSource
|
|
312
375
|
)
|
|
313
376
|
|
|
314
|
-
// Location puck
|
|
377
|
+
// ── Location puck — Waze-style arrow ─────────────────────────────
|
|
378
|
+
// FIX: use mapbox_navigation_puck_icon (built-in arrow)
|
|
379
|
+
// + PuckBearing.COURSE to orient it in direction of travel
|
|
315
380
|
mapView.location.apply {
|
|
316
381
|
setLocationProvider(navigationLocationProvider)
|
|
317
|
-
|
|
382
|
+
updateSettings {
|
|
383
|
+
// Built-in navigation arrow (chevron) from Nav SDK
|
|
384
|
+
locationPuck = LocationPuck2D(
|
|
385
|
+
bearingImage = ImageHolder.from(
|
|
386
|
+
com.mapbox.navigation.R.drawable.mapbox_navigation_puck_icon
|
|
387
|
+
)
|
|
388
|
+
)
|
|
389
|
+
// COURSE = direction of movement (not compass heading)
|
|
390
|
+
puckBearingEnabled = true
|
|
391
|
+
enabled = true
|
|
392
|
+
}
|
|
393
|
+
// Must set puckBearing separately — confirmed from migration guide
|
|
394
|
+
puckBearing = com.mapbox.maps.plugin.PuckBearing.COURSE
|
|
318
395
|
}
|
|
319
396
|
|
|
320
397
|
registerObservers()
|
|
@@ -327,7 +404,7 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
327
404
|
private fun registerObservers() {
|
|
328
405
|
val nav = mapboxNavigation ?: return
|
|
329
406
|
|
|
330
|
-
//
|
|
407
|
+
// Routes observer
|
|
331
408
|
nav.registerRoutesObserver(object : RoutesObserver {
|
|
332
409
|
override fun onRoutesChanged(result: RoutesUpdatedResult) {
|
|
333
410
|
if (result.navigationRoutes.isNotEmpty()) {
|
|
@@ -338,12 +415,9 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
338
415
|
}
|
|
339
416
|
viewportDataSource.onRouteChanged(result.navigationRoutes.first())
|
|
340
417
|
viewportDataSource.evaluate()
|
|
341
|
-
|
|
342
|
-
// FIX: transition to following mode when route is set
|
|
343
|
-
// This triggers the camera to zoom/pitch/center correctly
|
|
418
|
+
isOverviewMode = false
|
|
344
419
|
safeCameraFollowing()
|
|
345
420
|
showUI()
|
|
346
|
-
|
|
347
421
|
onRoutesReady(mapOf(
|
|
348
422
|
"routeCount" to result.navigationRoutes.size,
|
|
349
423
|
"distanceMeters" to (result.navigationRoutes.first().directionsRoute.distance() ?: 0.0),
|
|
@@ -363,13 +437,12 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
363
437
|
}
|
|
364
438
|
})
|
|
365
439
|
|
|
366
|
-
//
|
|
440
|
+
// Route progress observer
|
|
367
441
|
nav.registerRouteProgressObserver(RouteProgressObserver { routeProgress ->
|
|
368
|
-
// Update viewport with progress (camera follows route progress)
|
|
369
442
|
viewportDataSource.onRouteProgressChanged(routeProgress)
|
|
370
443
|
viewportDataSource.evaluate()
|
|
371
444
|
|
|
372
|
-
//
|
|
445
|
+
// Maneuver arrow on map
|
|
373
446
|
mapView.mapboxMap.style?.let { style ->
|
|
374
447
|
val arrowResult = routeArrowApi.addUpcomingManeuverArrow(routeProgress)
|
|
375
448
|
routeArrowView.renderManeuverUpdate(style, arrowResult)
|
|
@@ -378,10 +451,7 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
378
451
|
// Maneuver banner
|
|
379
452
|
val maneuvers = maneuverApi.getManeuvers(routeProgress)
|
|
380
453
|
maneuvers.fold(
|
|
381
|
-
{ error ->
|
|
382
|
-
Log.w(TAG, "Maneuver error: ${error.errorMessage}")
|
|
383
|
-
Unit
|
|
384
|
-
},
|
|
454
|
+
{ error -> Log.w(TAG, "Maneuver error: ${error.errorMessage}"); Unit },
|
|
385
455
|
{ _ ->
|
|
386
456
|
maneuverView?.visibility = View.VISIBLE
|
|
387
457
|
maneuverView?.renderManeuvers(maneuvers)
|
|
@@ -409,32 +479,27 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
409
479
|
))
|
|
410
480
|
})
|
|
411
481
|
|
|
412
|
-
//
|
|
482
|
+
// Location observer
|
|
413
483
|
nav.registerLocationObserver(object : LocationObserver {
|
|
414
484
|
override fun onNewRawLocation(rawLocation: Location) {}
|
|
415
|
-
|
|
416
485
|
override fun onNewLocationMatcherResult(result: LocationMatcherResult) {
|
|
417
486
|
val loc = result.enhancedLocation
|
|
418
487
|
|
|
419
|
-
// Update location puck position
|
|
420
488
|
navigationLocationProvider.changePosition(
|
|
421
489
|
location = loc,
|
|
422
490
|
keyPoints = result.keyPoints,
|
|
423
491
|
)
|
|
424
492
|
|
|
425
|
-
// FIX: Update viewport data source with location AND bearing
|
|
426
|
-
// This is what drives the camera to follow the vehicle's direction
|
|
427
493
|
viewportDataSource.onLocationChanged(loc)
|
|
428
494
|
viewportDataSource.evaluate()
|
|
429
495
|
|
|
430
|
-
//
|
|
431
|
-
// This ensures the camera centers on the user as soon as GPS is available
|
|
496
|
+
// First GPS fix → enter following mode
|
|
432
497
|
if (!firstLocationReceived) {
|
|
433
498
|
firstLocationReceived = true
|
|
434
499
|
safeCameraFollowing()
|
|
435
500
|
}
|
|
436
501
|
|
|
437
|
-
//
|
|
502
|
+
// Speed limit display
|
|
438
503
|
val fmtOptions = DistanceFormatterOptions.Builder(context).build()
|
|
439
504
|
val speedInfo = speedInfoApi.updatePostedAndCurrentSpeed(result, fmtOptions)
|
|
440
505
|
if (speedInfo != null) {
|
|
@@ -450,11 +515,45 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
450
515
|
}
|
|
451
516
|
|
|
452
517
|
// ─────────────────────────────────────────────────────────────────────────
|
|
453
|
-
//
|
|
454
|
-
//
|
|
455
|
-
|
|
456
|
-
//
|
|
457
|
-
|
|
518
|
+
// Button actions
|
|
519
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
520
|
+
|
|
521
|
+
// Toggle mute voice guidance
|
|
522
|
+
private fun toggleMute() {
|
|
523
|
+
isMuted = !isMuted
|
|
524
|
+
btnMute?.text = if (isMuted) "🔇" else "🔊"
|
|
525
|
+
btnMute?.setTextColor(if (isMuted) Color.parseColor("#FF4444") else Color.BLACK)
|
|
526
|
+
// Actual audio muting handled by the voice instruction observer in the app
|
|
527
|
+
// We expose the state via event so the RN layer can mute TTS
|
|
528
|
+
onNavigationCancelled(mapOf("type" to "mute", "muted" to isMuted))
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Toggle overview / following mode
|
|
532
|
+
private fun toggleOverview() {
|
|
533
|
+
if (isOverviewMode) {
|
|
534
|
+
recenterCamera()
|
|
535
|
+
} else {
|
|
536
|
+
isOverviewMode = true
|
|
537
|
+
btnOverview?.text = "◉"
|
|
538
|
+
btnRecenter?.visibility = View.VISIBLE
|
|
539
|
+
try {
|
|
540
|
+
navigationCamera.requestNavigationCameraToOverview()
|
|
541
|
+
} catch (e: Exception) {
|
|
542
|
+
Log.e(TAG, "Camera overview error: ${e.message}")
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Recenter camera to vehicle (following mode)
|
|
548
|
+
private fun recenterCamera() {
|
|
549
|
+
isOverviewMode = false
|
|
550
|
+
btnOverview?.text = "⊕"
|
|
551
|
+
btnRecenter?.visibility = View.GONE
|
|
552
|
+
safeCameraFollowing()
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
556
|
+
// ETA bar
|
|
458
557
|
// ─────────────────────────────────────────────────────────────────────────
|
|
459
558
|
private fun updateEtaBar(
|
|
460
559
|
estimatedTimeToArrivalMs: Long,
|
|
@@ -483,7 +582,7 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
483
582
|
}
|
|
484
583
|
|
|
485
584
|
// ─────────────────────────────────────────────────────────────────────────
|
|
486
|
-
// Day / Night
|
|
585
|
+
// Day / Night
|
|
487
586
|
// ─────────────────────────────────────────────────────────────────────────
|
|
488
587
|
private fun getAutoStyle(): String {
|
|
489
588
|
val hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY)
|
|
@@ -509,8 +608,7 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
509
608
|
}
|
|
510
609
|
|
|
511
610
|
// ─────────────────────────────────────────────────────────────────────────
|
|
512
|
-
// Camera following —
|
|
513
|
-
// FIX: maxDuration(0) = instant transition, no animation delay
|
|
611
|
+
// Camera following — issue #43 safe wrapper
|
|
514
612
|
// ─────────────────────────────────────────────────────────────────────────
|
|
515
613
|
private fun safeCameraFollowing() {
|
|
516
614
|
try {
|
|
@@ -525,12 +623,13 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
525
623
|
}
|
|
526
624
|
|
|
527
625
|
// ─────────────────────────────────────────────────────────────────────────
|
|
528
|
-
// Cancel navigation
|
|
626
|
+
// Cancel navigation
|
|
529
627
|
// ─────────────────────────────────────────────────────────────────────────
|
|
530
628
|
private fun cancelNavigation() {
|
|
531
629
|
mapboxNavigation?.setNavigationRoutes(listOf())
|
|
532
630
|
mapboxNavigation?.stopTripSession()
|
|
533
631
|
firstLocationReceived = false
|
|
632
|
+
isOverviewMode = false
|
|
534
633
|
hideUI()
|
|
535
634
|
onNavigationCancelled(mapOf<String, Any>())
|
|
536
635
|
}
|
|
@@ -538,12 +637,14 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
538
637
|
private fun showUI() {
|
|
539
638
|
maneuverView?.visibility = View.VISIBLE
|
|
540
639
|
etaBar?.visibility = View.VISIBLE
|
|
640
|
+
sideButtons?.visibility = View.VISIBLE
|
|
541
641
|
}
|
|
542
642
|
|
|
543
643
|
private fun hideUI() {
|
|
544
644
|
maneuverView?.visibility = View.INVISIBLE
|
|
545
645
|
speedInfoView?.visibility = View.GONE
|
|
546
646
|
etaBar?.visibility = View.INVISIBLE
|
|
647
|
+
sideButtons?.visibility = View.INVISIBLE
|
|
547
648
|
}
|
|
548
649
|
|
|
549
650
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -578,8 +679,7 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
578
679
|
.language(locale.toLanguageTag())
|
|
579
680
|
.voiceUnits(resolveVoiceUnits())
|
|
580
681
|
.coordinatesList(points)
|
|
581
|
-
|
|
582
|
-
.annotations("maxspeed,congestion,duration")
|
|
682
|
+
.annotations("maxspeed,congestion,duration,speed")
|
|
583
683
|
|
|
584
684
|
waypointIndices?.let { builder.waypointIndicesList(it) }
|
|
585
685
|
navigationProfile?.let {
|
|
@@ -602,8 +702,6 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
602
702
|
}
|
|
603
703
|
nav.setNavigationRoutes(routes)
|
|
604
704
|
nav.startTripSession()
|
|
605
|
-
// Camera will follow automatically via onNewLocationMatcherResult
|
|
606
|
-
// + onRoutesChanged observer
|
|
607
705
|
}
|
|
608
706
|
|
|
609
707
|
override fun onFailure(reasons: List<RouterFailure>, routeOptions: RouteOptions) {
|
|
@@ -632,7 +730,7 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
632
730
|
fun setNavigationProfile(p: String?) { navigationProfile = p }
|
|
633
731
|
fun setExcludeTypes(t: List<String>?) { excludeTypes = t }
|
|
634
732
|
fun setMapStyle(s: String?) { mapStyle = s }
|
|
635
|
-
fun setMute(m: Boolean) { mute = m }
|
|
733
|
+
fun setMute(m: Boolean) { mute = m; if (m != isMuted) toggleMute() }
|
|
636
734
|
fun setMaxHeight(h: Double?) { maxHeight = h }
|
|
637
735
|
fun setMaxWidth(w: Double?) { maxWidth = w }
|
|
638
736
|
fun setUseMapMatching(u: Boolean) { useMapMatching = u }
|
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.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",
|