@jacques_gordon/expo-mapbox-navigation 2.1.0 → 2.1.1
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.
|
@@ -58,6 +58,7 @@ import expo.modules.kotlin.AppContext
|
|
|
58
58
|
import expo.modules.kotlin.viewevent.EventDispatcher
|
|
59
59
|
import expo.modules.kotlin.views.ExpoView
|
|
60
60
|
import java.util.Calendar
|
|
61
|
+
import java.util.Date
|
|
61
62
|
import java.util.Locale
|
|
62
63
|
|
|
63
64
|
private const val TAG = "ExpoMapboxNavigation"
|
|
@@ -65,7 +66,7 @@ private const val TAG = "ExpoMapboxNavigation"
|
|
|
65
66
|
class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
66
67
|
ExpoView(context, appContext) {
|
|
67
68
|
|
|
68
|
-
// ── EventDispatchers
|
|
69
|
+
// ── EventDispatchers ──────────────────────────────────────────────────────
|
|
69
70
|
private val onRouteProgressChanged by EventDispatcher()
|
|
70
71
|
private val onRoutesReady by EventDispatcher()
|
|
71
72
|
private val onNavigationFinished by EventDispatcher()
|
|
@@ -83,7 +84,7 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
83
84
|
private var tvDistance: TextView? = null
|
|
84
85
|
private var etaBar: LinearLayout? = null
|
|
85
86
|
|
|
86
|
-
// ── Navigation APIs
|
|
87
|
+
// ── Navigation APIs ───────────────────────────────────────────────────────
|
|
87
88
|
private lateinit var navigationCamera: NavigationCamera
|
|
88
89
|
private lateinit var viewportDataSource: MapboxNavigationViewportDataSource
|
|
89
90
|
private lateinit var routeLineApi: MapboxRouteLineApi
|
|
@@ -95,8 +96,6 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
95
96
|
private lateinit var routeArrowView: MapboxRouteArrowView
|
|
96
97
|
private val navigationLocationProvider = NavigationLocationProvider()
|
|
97
98
|
private var mapboxNavigation: MapboxNavigation? = null
|
|
98
|
-
|
|
99
|
-
// ── Night mode ────────────────────────────────────────────────────────────
|
|
100
99
|
private var isNightMode = false
|
|
101
100
|
|
|
102
101
|
// ── Props ─────────────────────────────────────────────────────────────────
|
|
@@ -121,7 +120,7 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
121
120
|
}
|
|
122
121
|
|
|
123
122
|
// ─────────────────────────────────────────────────────────────────────────
|
|
124
|
-
//
|
|
123
|
+
// Build UI
|
|
125
124
|
// ─────────────────────────────────────────────────────────────────────────
|
|
126
125
|
private fun buildUI() {
|
|
127
126
|
val dp = context.resources.displayMetrics.density
|
|
@@ -129,41 +128,37 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
129
128
|
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
|
130
129
|
}
|
|
131
130
|
|
|
132
|
-
// Full-screen MapView
|
|
133
131
|
root.addView(mapView, FrameLayout.LayoutParams(
|
|
134
132
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
135
133
|
FrameLayout.LayoutParams.MATCH_PARENT
|
|
136
134
|
))
|
|
137
135
|
|
|
138
|
-
// ManeuverView — top
|
|
139
|
-
// NOTE: MapboxManeuverView extends ConstraintLayout, cast to View for addView()
|
|
136
|
+
// ManeuverView — top (cast to View: extends ConstraintLayout)
|
|
140
137
|
val mv = MapboxManeuverView(context)
|
|
141
|
-
|
|
138
|
+
root.addView(mv as View, FrameLayout.LayoutParams(
|
|
142
139
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
143
140
|
FrameLayout.LayoutParams.WRAP_CONTENT
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
mv.visibility = View.INVISIBLE // matches official example (INVISIBLE not GONE)
|
|
147
|
-
root.addView(mv as View, mvParams)
|
|
141
|
+
).also { it.gravity = Gravity.TOP })
|
|
142
|
+
mv.visibility = View.INVISIBLE
|
|
148
143
|
maneuverView = mv
|
|
149
144
|
|
|
150
|
-
// SpeedInfoView — bottom-left
|
|
145
|
+
// SpeedInfoView — bottom-left (cast to View)
|
|
151
146
|
val siv = MapboxSpeedInfoView(context)
|
|
152
|
-
|
|
147
|
+
root.addView(siv as View, FrameLayout.LayoutParams(
|
|
153
148
|
FrameLayout.LayoutParams.WRAP_CONTENT,
|
|
154
149
|
FrameLayout.LayoutParams.WRAP_CONTENT
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
|
|
150
|
+
).also {
|
|
151
|
+
it.gravity = Gravity.BOTTOM or Gravity.START
|
|
152
|
+
it.setMargins((16 * dp).toInt(), 0, 0, (100 * dp).toInt())
|
|
153
|
+
})
|
|
158
154
|
siv.visibility = View.GONE
|
|
159
|
-
root.addView(siv as View, sivParams)
|
|
160
155
|
speedInfoView = siv
|
|
161
156
|
|
|
162
|
-
// TripProgressView
|
|
157
|
+
// TripProgressView (used by the official Mapbox render method)
|
|
163
158
|
val tpv = MapboxTripProgressView(context)
|
|
164
159
|
tripProgressView = tpv
|
|
165
160
|
|
|
166
|
-
// ETA
|
|
161
|
+
// Bottom ETA bar
|
|
167
162
|
val bar = LinearLayout(context).apply {
|
|
168
163
|
orientation = LinearLayout.HORIZONTAL
|
|
169
164
|
setBackgroundColor(Color.WHITE)
|
|
@@ -175,11 +170,9 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
175
170
|
(16 * dp).toInt(), (8 * dp).toInt()
|
|
176
171
|
)
|
|
177
172
|
}
|
|
178
|
-
|
|
179
|
-
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
180
|
-
|
|
181
|
-
)
|
|
182
|
-
barParams.gravity = Gravity.BOTTOM
|
|
173
|
+
root.addView(bar, FrameLayout.LayoutParams(
|
|
174
|
+
FrameLayout.LayoutParams.MATCH_PARENT, (80 * dp).toInt()
|
|
175
|
+
).also { it.gravity = Gravity.BOTTOM })
|
|
183
176
|
|
|
184
177
|
val etaTime = TextView(context).apply {
|
|
185
178
|
textSize = 22f
|
|
@@ -193,8 +186,12 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
193
186
|
orientation = LinearLayout.VERTICAL
|
|
194
187
|
gravity = Gravity.CENTER
|
|
195
188
|
}
|
|
196
|
-
val dur = TextView(context).apply {
|
|
197
|
-
|
|
189
|
+
val dur = TextView(context).apply {
|
|
190
|
+
textSize = 16f; setTextColor(Color.DKGRAY); gravity = Gravity.CENTER
|
|
191
|
+
}
|
|
192
|
+
val dist = TextView(context).apply {
|
|
193
|
+
textSize = 13f; setTextColor(Color.GRAY); gravity = Gravity.CENTER
|
|
194
|
+
}
|
|
198
195
|
center.addView(dur)
|
|
199
196
|
center.addView(dist)
|
|
200
197
|
bar.addView(center, LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 2f))
|
|
@@ -202,35 +199,30 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
202
199
|
tvDistance = dist
|
|
203
200
|
|
|
204
201
|
val cancelBtn = TextView(context).apply {
|
|
205
|
-
text = "✕"
|
|
206
|
-
textSize = 22f
|
|
207
|
-
setTextColor(Color.DKGRAY)
|
|
202
|
+
text = "✕"; textSize = 22f; setTextColor(Color.DKGRAY)
|
|
208
203
|
gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
|
209
204
|
setOnClickListener { cancelNavigation() }
|
|
210
205
|
}
|
|
211
206
|
bar.addView(cancelBtn, LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f))
|
|
212
207
|
etaBar = bar
|
|
213
|
-
root.addView(bar, barParams)
|
|
214
208
|
|
|
215
209
|
addView(root)
|
|
216
210
|
}
|
|
217
211
|
|
|
218
212
|
// ─────────────────────────────────────────────────────────────────────────
|
|
219
|
-
//
|
|
213
|
+
// Init APIs — confirmed from official Mapbox examples
|
|
220
214
|
// ─────────────────────────────────────────────────────────────────────────
|
|
221
215
|
private fun initAPIs() {
|
|
222
216
|
val distanceFormatterOptions = DistanceFormatterOptions.Builder(context).build()
|
|
223
217
|
|
|
224
|
-
|
|
225
|
-
maneuverApi = MapboxManeuverApi(
|
|
226
|
-
MapboxDistanceFormatter(distanceFormatterOptions)
|
|
227
|
-
)
|
|
218
|
+
maneuverApi = MapboxManeuverApi(MapboxDistanceFormatter(distanceFormatterOptions))
|
|
228
219
|
|
|
220
|
+
// TripProgressApi — from ShowTripProgressActivity official example
|
|
221
|
+
// PercentDistanceTraveledFormatter is optional — omit if causing issues
|
|
229
222
|
tripProgressApi = MapboxTripProgressApi(
|
|
230
223
|
TripProgressUpdateFormatter.Builder(context)
|
|
231
224
|
.distanceRemainingFormatter(DistanceRemainingFormatter(distanceFormatterOptions))
|
|
232
225
|
.timeRemainingFormatter(TimeRemainingFormatter(context))
|
|
233
|
-
.percentRouteTraveledFormatter(PercentDistanceTraveledFormatter())
|
|
234
226
|
.estimatedTimeToArrivalFormatter(
|
|
235
227
|
EstimatedTimeToArrivalFormatter(context, TimeFormat.NONE_SPECIFIED)
|
|
236
228
|
)
|
|
@@ -239,31 +231,27 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
239
231
|
|
|
240
232
|
speedInfoApi = MapboxSpeedInfoApi()
|
|
241
233
|
|
|
242
|
-
val routeLineViewOptions = MapboxRouteLineViewOptions.Builder(context)
|
|
243
|
-
.routeLineBelowLayerId("road-label-navigation")
|
|
244
|
-
.build()
|
|
245
234
|
routeLineApi = MapboxRouteLineApi(MapboxRouteLineApiOptions.Builder().build())
|
|
246
|
-
routeLineView = MapboxRouteLineView(
|
|
235
|
+
routeLineView = MapboxRouteLineView(
|
|
236
|
+
MapboxRouteLineViewOptions.Builder(context)
|
|
237
|
+
.routeLineBelowLayerId("road-label-navigation")
|
|
238
|
+
.build()
|
|
239
|
+
)
|
|
247
240
|
|
|
248
|
-
|
|
249
|
-
routeArrowView = MapboxRouteArrowView(routeArrowOptions)
|
|
241
|
+
routeArrowView = MapboxRouteArrowView(RouteArrowOptions.Builder(context).build())
|
|
250
242
|
}
|
|
251
243
|
|
|
252
244
|
// ─────────────────────────────────────────────────────────────────────────
|
|
253
|
-
//
|
|
245
|
+
// Setup Navigation
|
|
254
246
|
// ─────────────────────────────────────────────────────────────────────────
|
|
255
247
|
private fun setupNavigation() {
|
|
256
248
|
mapboxNavigation = MapboxNavigationProvider.create(
|
|
257
249
|
NavigationOptions.Builder(context).build()
|
|
258
250
|
)
|
|
259
251
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
// Route line layers must be initialized first
|
|
264
|
-
routeLineView.initializeLayers(mapStyle)
|
|
252
|
+
mapView.mapboxMap.loadStyle(mapStyle ?: getAutoStyle()) { style ->
|
|
253
|
+
routeLineView.initializeLayers(style)
|
|
265
254
|
|
|
266
|
-
// Camera setup — EXACT same as official example
|
|
267
255
|
viewportDataSource = MapboxNavigationViewportDataSource(mapView.mapboxMap)
|
|
268
256
|
navigationCamera = NavigationCamera(
|
|
269
257
|
mapView.mapboxMap,
|
|
@@ -271,7 +259,6 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
271
259
|
viewportDataSource
|
|
272
260
|
)
|
|
273
261
|
|
|
274
|
-
// Location puck
|
|
275
262
|
mapView.location.apply {
|
|
276
263
|
setLocationProvider(navigationLocationProvider)
|
|
277
264
|
enabled = true
|
|
@@ -282,38 +269,30 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
282
269
|
}
|
|
283
270
|
|
|
284
271
|
// ─────────────────────────────────────────────────────────────────────────
|
|
285
|
-
//
|
|
272
|
+
// Observers
|
|
286
273
|
// ─────────────────────────────────────────────────────────────────────────
|
|
287
274
|
private fun registerObservers() {
|
|
288
275
|
val nav = mapboxNavigation ?: return
|
|
289
276
|
|
|
290
|
-
//
|
|
277
|
+
// Routes observer
|
|
291
278
|
nav.registerRoutesObserver(object : RoutesObserver {
|
|
292
279
|
override fun onRoutesChanged(result: RoutesUpdatedResult) {
|
|
293
280
|
if (result.navigationRoutes.isNotEmpty()) {
|
|
294
|
-
// Draw route line
|
|
295
281
|
routeLineApi.setNavigationRoutes(result.navigationRoutes) { value ->
|
|
296
282
|
mapView.mapboxMap.style?.apply {
|
|
297
283
|
routeLineView.renderRouteDrawData(this, value)
|
|
298
284
|
}
|
|
299
285
|
}
|
|
300
|
-
// Update camera viewport
|
|
301
286
|
viewportDataSource.onRouteChanged(result.navigationRoutes.first())
|
|
302
287
|
viewportDataSource.evaluate()
|
|
303
|
-
|
|
304
|
-
// Show navigation UI
|
|
305
|
-
maneuverView?.visibility = View.VISIBLE
|
|
306
|
-
etaBar?.visibility = View.VISIBLE
|
|
307
|
-
|
|
288
|
+
showUI()
|
|
308
289
|
onRoutesReady(mapOf(
|
|
309
290
|
"routeCount" to result.navigationRoutes.size,
|
|
310
291
|
"distanceMeters" to (result.navigationRoutes.first().directionsRoute.distance() ?: 0.0),
|
|
311
292
|
"durationSeconds" to (result.navigationRoutes.first().directionsRoute.duration() ?: 0.0)
|
|
312
293
|
))
|
|
313
294
|
} else {
|
|
314
|
-
|
|
315
|
-
val style = mapView.mapboxMap.style
|
|
316
|
-
if (style != null) {
|
|
295
|
+
mapView.mapboxMap.style?.let { style ->
|
|
317
296
|
routeLineApi.clearRouteLine { value ->
|
|
318
297
|
routeLineView.renderClearRouteLineValue(style, value)
|
|
319
298
|
}
|
|
@@ -326,39 +305,48 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
326
305
|
}
|
|
327
306
|
})
|
|
328
307
|
|
|
329
|
-
//
|
|
330
|
-
// EXACT same pattern as TurnByTurnExperienceActivity.routeProgressObserver
|
|
308
|
+
// Route progress observer
|
|
331
309
|
nav.registerRouteProgressObserver(RouteProgressObserver { routeProgress ->
|
|
332
|
-
// Update camera
|
|
333
310
|
viewportDataSource.onRouteProgressChanged(routeProgress)
|
|
334
311
|
viewportDataSource.evaluate()
|
|
335
312
|
|
|
336
|
-
//
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
routeArrowView.renderManeuverUpdate(style, maneuverArrowResult)
|
|
313
|
+
// Route arrow
|
|
314
|
+
mapView.mapboxMap.style?.let { style ->
|
|
315
|
+
val arrowResult = routeArrowApi.addUpcomingManeuverArrow(routeProgress)
|
|
316
|
+
routeArrowView.renderManeuverUpdate(style, arrowResult)
|
|
341
317
|
}
|
|
342
318
|
|
|
343
|
-
// Maneuver
|
|
319
|
+
// ── Maneuver — fold() with explicit Unit returns on both branches ──
|
|
320
|
+
// Fix for error: "Argument type mismatch: Unit? but R & Any expected"
|
|
321
|
+
// Solution: use explicit return type annotation on the success lambda
|
|
344
322
|
val maneuvers = maneuverApi.getManeuvers(routeProgress)
|
|
345
323
|
maneuvers.fold(
|
|
346
|
-
{ error ->
|
|
347
|
-
|
|
324
|
+
{ error ->
|
|
325
|
+
Log.w(TAG, "Maneuver error: ${error.errorMessage}")
|
|
326
|
+
Unit
|
|
327
|
+
},
|
|
328
|
+
{ _ ->
|
|
348
329
|
maneuverView?.visibility = View.VISIBLE
|
|
349
330
|
maneuverView?.renderManeuvers(maneuvers)
|
|
331
|
+
Unit
|
|
350
332
|
}
|
|
351
333
|
)
|
|
352
334
|
|
|
353
|
-
// Trip progress —
|
|
354
|
-
//
|
|
335
|
+
// ── Trip progress — use getTripProgress exactly as official example ──
|
|
336
|
+
// render() to the MapboxTripProgressView (official pattern)
|
|
337
|
+
// For our custom ETA bar, use the raw fields from TripProgressUpdateValue:
|
|
338
|
+
// .estimatedTimeToArrival — Long (Unix timestamp ms of arrival)
|
|
339
|
+
// .currentLegTimeRemaining — Double (seconds remaining for current leg)
|
|
340
|
+
// .totalTimeRemaining — Double (seconds remaining total)
|
|
341
|
+
// .distanceRemaining — Double (metres remaining)
|
|
355
342
|
val tripProgress = tripProgressApi.getTripProgress(routeProgress)
|
|
356
343
|
tripProgressView?.render(tripProgress)
|
|
344
|
+
updateEtaBar(
|
|
345
|
+
tripProgress.estimatedTimeToArrival,
|
|
346
|
+
tripProgress.totalTimeRemaining,
|
|
347
|
+
tripProgress.distanceRemaining
|
|
348
|
+
)
|
|
357
349
|
|
|
358
|
-
// Update our custom ETA bar from tripProgress fields
|
|
359
|
-
updateEtaBar(tripProgress)
|
|
360
|
-
|
|
361
|
-
// Emit event
|
|
362
350
|
onRouteProgressChanged(mapOf(
|
|
363
351
|
"distanceRemaining" to routeProgress.distanceRemaining,
|
|
364
352
|
"durationRemaining" to routeProgress.durationRemaining,
|
|
@@ -370,30 +358,21 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
370
358
|
))
|
|
371
359
|
})
|
|
372
360
|
|
|
373
|
-
//
|
|
374
|
-
// EXACT same as official example
|
|
361
|
+
// Location observer
|
|
375
362
|
nav.registerLocationObserver(object : LocationObserver {
|
|
376
363
|
override fun onNewRawLocation(rawLocation: Location) {}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
val enhancedLocation = locationMatcherResult.enhancedLocation
|
|
380
|
-
|
|
381
|
-
// Update location puck
|
|
364
|
+
override fun onNewLocationMatcherResult(result: LocationMatcherResult) {
|
|
365
|
+
val loc = result.enhancedLocation
|
|
382
366
|
navigationLocationProvider.changePosition(
|
|
383
|
-
location =
|
|
384
|
-
keyPoints =
|
|
367
|
+
location = loc,
|
|
368
|
+
keyPoints = result.keyPoints,
|
|
385
369
|
)
|
|
386
|
-
|
|
387
|
-
// Update camera
|
|
388
|
-
viewportDataSource.onLocationChanged(enhancedLocation)
|
|
370
|
+
viewportDataSource.onLocationChanged(loc)
|
|
389
371
|
viewportDataSource.evaluate()
|
|
390
372
|
|
|
391
|
-
// Speed limit
|
|
392
|
-
val
|
|
393
|
-
val speedInfo = speedInfoApi.updatePostedAndCurrentSpeed(
|
|
394
|
-
locationMatcherResult,
|
|
395
|
-
formatterOptions
|
|
396
|
-
)
|
|
373
|
+
// Speed limit
|
|
374
|
+
val fmtOptions = DistanceFormatterOptions.Builder(context).build()
|
|
375
|
+
val speedInfo = speedInfoApi.updatePostedAndCurrentSpeed(result, fmtOptions)
|
|
397
376
|
if (speedInfo != null) {
|
|
398
377
|
speedInfoView?.visibility = View.VISIBLE
|
|
399
378
|
speedInfoView?.render(speedInfo)
|
|
@@ -401,45 +380,48 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
401
380
|
speedInfoView?.visibility = View.GONE
|
|
402
381
|
}
|
|
403
382
|
|
|
404
|
-
// Auto day/night switch
|
|
405
383
|
checkAndSwitchDayNight()
|
|
406
384
|
}
|
|
407
385
|
})
|
|
408
386
|
}
|
|
409
387
|
|
|
410
388
|
// ─────────────────────────────────────────────────────────────────────────
|
|
411
|
-
//
|
|
412
|
-
//
|
|
389
|
+
// ETA bar — using confirmed TripProgressUpdateValue fields
|
|
390
|
+
// estimatedTimeToArrival: Long — Unix timestamp (ms) of expected arrival
|
|
391
|
+
// totalTimeRemaining: Double — seconds remaining
|
|
392
|
+
// distanceRemaining: Double — metres remaining
|
|
413
393
|
// ─────────────────────────────────────────────────────────────────────────
|
|
414
|
-
private fun updateEtaBar(
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
val
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
394
|
+
private fun updateEtaBar(
|
|
395
|
+
estimatedTimeToArrivalMs: Long,
|
|
396
|
+
totalTimeRemainingSec: Double,
|
|
397
|
+
distanceRemainingMetres: Double
|
|
398
|
+
) {
|
|
399
|
+
// Arrival clock time from Unix timestamp
|
|
400
|
+
val arrivalCal = Calendar.getInstance().apply {
|
|
401
|
+
timeInMillis = estimatedTimeToArrivalMs
|
|
402
|
+
}
|
|
403
|
+
tvEtaTime?.text = String.format(
|
|
404
|
+
"%02d:%02d",
|
|
405
|
+
arrivalCal.get(Calendar.HOUR_OF_DAY),
|
|
406
|
+
arrivalCal.get(Calendar.MINUTE)
|
|
424
407
|
)
|
|
425
408
|
|
|
426
409
|
// Duration remaining
|
|
427
|
-
val totalMin = (
|
|
410
|
+
val totalMin = (totalTimeRemainingSec / 60).toInt()
|
|
428
411
|
tvDuration?.text = if (totalMin >= 60)
|
|
429
412
|
"${totalMin / 60}h ${totalMin % 60}min"
|
|
430
413
|
else
|
|
431
414
|
"${totalMin} min"
|
|
432
415
|
|
|
433
416
|
// Distance remaining
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
String.format("%.1f km", distM / 1000.0)
|
|
417
|
+
tvDistance?.text = if (distanceRemainingMetres >= 1000)
|
|
418
|
+
String.format("%.1f km", distanceRemainingMetres / 1000.0)
|
|
437
419
|
else
|
|
438
|
-
"${
|
|
420
|
+
"${distanceRemainingMetres.toInt()} m"
|
|
439
421
|
}
|
|
440
422
|
|
|
441
423
|
// ─────────────────────────────────────────────────────────────────────────
|
|
442
|
-
//
|
|
424
|
+
// Day / Night
|
|
443
425
|
// ─────────────────────────────────────────────────────────────────────────
|
|
444
426
|
private fun getAutoStyle(): String {
|
|
445
427
|
val hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY)
|
|
@@ -452,10 +434,8 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
452
434
|
val shouldBeNight = hour !in 6..20
|
|
453
435
|
if (shouldBeNight == isNightMode) return
|
|
454
436
|
isNightMode = shouldBeNight
|
|
455
|
-
|
|
456
437
|
val newStyle = if (shouldBeNight) NavigationStyles.NAVIGATION_NIGHT_STYLE
|
|
457
438
|
else NavigationStyles.NAVIGATION_DAY_STYLE
|
|
458
|
-
|
|
459
439
|
mapView.mapboxMap.loadStyle(newStyle) { style ->
|
|
460
440
|
routeLineView.initializeLayers(style)
|
|
461
441
|
mapboxNavigation?.getNavigationRoutes()?.takeIf { it.isNotEmpty() }?.let { routes ->
|
|
@@ -464,10 +444,7 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
464
444
|
}
|
|
465
445
|
}
|
|
466
446
|
}
|
|
467
|
-
|
|
468
|
-
etaBar?.setBackgroundColor(
|
|
469
|
-
if (shouldBeNight) Color.parseColor("#1E2433") else Color.WHITE
|
|
470
|
-
)
|
|
447
|
+
etaBar?.setBackgroundColor(if (shouldBeNight) Color.parseColor("#1E2433") else Color.WHITE)
|
|
471
448
|
val tc = if (shouldBeNight) Color.WHITE else Color.BLACK
|
|
472
449
|
tvEtaTime?.setTextColor(tc)
|
|
473
450
|
tvDuration?.setTextColor(if (shouldBeNight) Color.LTGRAY else Color.DKGRAY)
|
|
@@ -475,7 +452,7 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
475
452
|
}
|
|
476
453
|
|
|
477
454
|
// ─────────────────────────────────────────────────────────────────────────
|
|
478
|
-
//
|
|
455
|
+
// Cancel navigation
|
|
479
456
|
// ─────────────────────────────────────────────────────────────────────────
|
|
480
457
|
private fun cancelNavigation() {
|
|
481
458
|
mapboxNavigation?.setNavigationRoutes(listOf())
|
|
@@ -484,30 +461,30 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
484
461
|
onNavigationCancelled(mapOf<String, Any>())
|
|
485
462
|
}
|
|
486
463
|
|
|
464
|
+
private fun showUI() {
|
|
465
|
+
maneuverView?.visibility = View.VISIBLE
|
|
466
|
+
etaBar?.visibility = View.VISIBLE
|
|
467
|
+
}
|
|
468
|
+
|
|
487
469
|
private fun hideUI() {
|
|
488
470
|
maneuverView?.visibility = View.INVISIBLE
|
|
489
471
|
speedInfoView?.visibility = View.GONE
|
|
490
472
|
etaBar?.visibility = View.INVISIBLE
|
|
491
473
|
}
|
|
492
474
|
|
|
493
|
-
//
|
|
494
|
-
// 8. Issue #43 — safe camera following
|
|
495
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
475
|
+
// Issue #43
|
|
496
476
|
private fun safeCameraFollowing() {
|
|
497
477
|
try {
|
|
498
478
|
navigationCamera.requestNavigationCameraToFollowing(
|
|
499
479
|
stateTransitionOptions = NavigationCameraTransitionOptions.Builder()
|
|
500
|
-
.maxDuration(0)
|
|
501
|
-
.build()
|
|
480
|
+
.maxDuration(0).build()
|
|
502
481
|
)
|
|
503
482
|
} catch (e: Exception) {
|
|
504
|
-
Log.e(TAG, "Camera
|
|
483
|
+
Log.e(TAG, "Camera error: ${e.message}")
|
|
505
484
|
}
|
|
506
485
|
}
|
|
507
486
|
|
|
508
|
-
//
|
|
509
|
-
// 9. Issue #31 — Voice units
|
|
510
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
487
|
+
// Issue #31
|
|
511
488
|
private fun resolveVoiceUnits(): String {
|
|
512
489
|
return when (voiceUnits?.lowercase()) {
|
|
513
490
|
"metric" -> "metric"
|
|
@@ -519,71 +496,44 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
519
496
|
}
|
|
520
497
|
}
|
|
521
498
|
|
|
522
|
-
//
|
|
523
|
-
// 10. Route request
|
|
524
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
499
|
+
// Route request
|
|
525
500
|
@SuppressLint("MissingPermission")
|
|
526
501
|
private fun fetchRoutes() {
|
|
527
502
|
val nav = mapboxNavigation ?: return
|
|
528
503
|
if (coordinates.size < 2) return
|
|
529
|
-
|
|
530
|
-
val points = coordinates.map { coord ->
|
|
531
|
-
Point.fromLngLat(coord["longitude"] ?: 0.0, coord["latitude"] ?: 0.0)
|
|
532
|
-
}
|
|
504
|
+
val points = coordinates.map { Point.fromLngLat(it["longitude"] ?: 0.0, it["latitude"] ?: 0.0) }
|
|
533
505
|
val locale = language?.let { Locale.forLanguageTag(it) } ?: Locale.getDefault()
|
|
534
|
-
|
|
535
506
|
val builder = RouteOptions.builder()
|
|
536
507
|
.applyDefaultNavigationOptions()
|
|
537
508
|
.language(locale.toLanguageTag())
|
|
538
509
|
.voiceUnits(resolveVoiceUnits())
|
|
539
510
|
.coordinatesList(points)
|
|
540
511
|
.annotations("maxspeed,congestion,duration")
|
|
541
|
-
|
|
542
512
|
waypointIndices?.let { builder.waypointIndicesList(it) }
|
|
543
|
-
navigationProfile?.let {
|
|
544
|
-
builder.profile(if (it.startsWith("mapbox/")) it else "mapbox/$it")
|
|
545
|
-
}
|
|
513
|
+
navigationProfile?.let { builder.profile(if (it.startsWith("mapbox/")) it else "mapbox/$it") }
|
|
546
514
|
excludeTypes?.takeIf { it.isNotEmpty() }?.let { builder.exclude(it.joinToString(",")) }
|
|
547
515
|
maxHeight?.let { builder.maxHeight(it) }
|
|
548
516
|
maxWidth?.let { builder.maxWidth(it) }
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
@RouterOrigin routerOrigin: String
|
|
556
|
-
) {
|
|
557
|
-
if (routes.isEmpty()) {
|
|
558
|
-
onRoutesFailed(mapOf("message" to "No routes returned"))
|
|
559
|
-
return
|
|
560
|
-
}
|
|
561
|
-
nav.setNavigationRoutes(routes)
|
|
562
|
-
nav.startTripSession()
|
|
563
|
-
safeCameraFollowing()
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
override fun onFailure(reasons: List<RouterFailure>, routeOptions: RouteOptions) {
|
|
567
|
-
val msg = reasons.firstOrNull()?.message ?: "Unknown error"
|
|
568
|
-
Log.e(TAG, "Route request failed: $msg")
|
|
569
|
-
onRoutesFailed(mapOf("message" to msg))
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
override fun onCanceled(
|
|
573
|
-
routeOptions: RouteOptions,
|
|
574
|
-
@RouterOrigin routerOrigin: String
|
|
575
|
-
) {
|
|
576
|
-
Log.d(TAG, "Route request cancelled")
|
|
577
|
-
}
|
|
517
|
+
nav.requestRoutes(builder.build(), object : NavigationRouterCallback {
|
|
518
|
+
override fun onRoutesReady(routes: List<NavigationRoute>, @RouterOrigin routerOrigin: String) {
|
|
519
|
+
if (routes.isEmpty()) { onRoutesFailed(mapOf("message" to "No routes returned")); return }
|
|
520
|
+
nav.setNavigationRoutes(routes)
|
|
521
|
+
nav.startTripSession()
|
|
522
|
+
safeCameraFollowing()
|
|
578
523
|
}
|
|
579
|
-
|
|
524
|
+
override fun onFailure(reasons: List<RouterFailure>, routeOptions: RouteOptions) {
|
|
525
|
+
val msg = reasons.firstOrNull()?.message ?: "Unknown error"
|
|
526
|
+
Log.e(TAG, "Route failed: $msg")
|
|
527
|
+
onRoutesFailed(mapOf("message" to msg))
|
|
528
|
+
}
|
|
529
|
+
override fun onCanceled(routeOptions: RouteOptions, @RouterOrigin routerOrigin: String) {
|
|
530
|
+
Log.d(TAG, "Route cancelled")
|
|
531
|
+
}
|
|
532
|
+
})
|
|
580
533
|
}
|
|
581
534
|
|
|
582
|
-
//
|
|
583
|
-
fun setCoordinates(coords: List<Map<String, Double>>) {
|
|
584
|
-
coordinates = coords
|
|
585
|
-
if (coords.size >= 2) fetchRoutes()
|
|
586
|
-
}
|
|
535
|
+
// Prop setters
|
|
536
|
+
fun setCoordinates(coords: List<Map<String, Double>>) { coordinates = coords; if (coords.size >= 2) fetchRoutes() }
|
|
587
537
|
fun setWaypointIndices(indices: List<Int>?) { waypointIndices = indices }
|
|
588
538
|
fun setLanguage(lang: String?) { language = lang }
|
|
589
539
|
fun setVoiceUnits(units: String?) { voiceUnits = units }
|
|
@@ -597,7 +547,6 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
597
547
|
fun setCustomRasterTileUrl(url: String?) { customRasterTileUrl = url }
|
|
598
548
|
fun setCustomRasterAboveLayerId(layerId: String?) { customRasterAboveLayerId = layerId }
|
|
599
549
|
|
|
600
|
-
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
|
601
550
|
override fun onDetachedFromWindow() {
|
|
602
551
|
super.onDetachedFromWindow()
|
|
603
552
|
maneuverApi.cancel()
|
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.1",
|
|
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",
|