@jacques_gordon/expo-mapbox-navigation 2.0.5 → 2.0.7
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.
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
package expo.modules.mapboxnavigation
|
|
2
2
|
|
|
3
|
+
import android.annotation.SuppressLint
|
|
3
4
|
import android.content.Context
|
|
4
5
|
import android.util.Log
|
|
5
6
|
import com.mapbox.api.directions.v5.models.RouteOptions
|
|
6
7
|
import com.mapbox.geojson.Point
|
|
7
8
|
import com.mapbox.maps.MapView
|
|
8
|
-
import com.mapbox.maps.MapboxMap
|
|
9
9
|
import com.mapbox.maps.Style
|
|
10
|
-
import com.mapbox.maps.plugin.animation.camera
|
|
10
|
+
import com.mapbox.maps.plugin.animation.camera // ← correct import for .camera extension
|
|
11
|
+
import com.mapbox.maps.plugin.locationcomponent.location
|
|
11
12
|
import com.mapbox.navigation.base.extensions.applyDefaultNavigationOptions
|
|
12
|
-
import com.mapbox.navigation.base.formatter.UnitType
|
|
13
13
|
import com.mapbox.navigation.base.options.NavigationOptions
|
|
14
14
|
import com.mapbox.navigation.base.route.NavigationRoute
|
|
15
15
|
import com.mapbox.navigation.base.route.NavigationRouterCallback
|
|
16
16
|
import com.mapbox.navigation.base.route.RouterFailure
|
|
17
|
+
import com.mapbox.navigation.base.route.RouterOrigin
|
|
17
18
|
import com.mapbox.navigation.core.MapboxNavigation
|
|
18
19
|
import com.mapbox.navigation.core.MapboxNavigationProvider
|
|
19
20
|
import com.mapbox.navigation.core.directions.session.RoutesObserver
|
|
20
21
|
import com.mapbox.navigation.core.directions.session.RoutesUpdatedResult
|
|
21
|
-
import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp
|
|
22
22
|
import com.mapbox.navigation.core.trip.session.RouteProgressObserver
|
|
23
23
|
import com.mapbox.navigation.ui.maps.camera.NavigationCamera
|
|
24
24
|
import com.mapbox.navigation.ui.maps.camera.data.MapboxNavigationViewportDataSource
|
|
@@ -36,17 +36,14 @@ private const val TAG = "ExpoMapboxNavigation"
|
|
|
36
36
|
class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
37
37
|
ExpoView(context, appContext) {
|
|
38
38
|
|
|
39
|
-
// ── UI ───────────────────────────────────────────────────────────────────
|
|
40
39
|
private val mapView: MapView = MapView(context)
|
|
41
40
|
private var navigationCamera: NavigationCamera? = null
|
|
42
41
|
private var viewportDataSource: MapboxNavigationViewportDataSource? = null
|
|
43
42
|
private var routeLineApi: MapboxRouteLineApi? = null
|
|
44
43
|
private var routeLineView: MapboxRouteLineView? = null
|
|
45
|
-
|
|
46
|
-
// ── Navigation ───────────────────────────────────────────────────────────
|
|
47
44
|
private var mapboxNavigation: MapboxNavigation? = null
|
|
48
45
|
|
|
49
|
-
//
|
|
46
|
+
// Props
|
|
50
47
|
private var coordinates: List<Map<String, Double>> = emptyList()
|
|
51
48
|
private var waypointIndices: List<Int>? = null
|
|
52
49
|
private var language: String? = null
|
|
@@ -67,87 +64,86 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
67
64
|
setupNavigation()
|
|
68
65
|
}
|
|
69
66
|
|
|
70
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
71
|
-
// Setup
|
|
72
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
73
|
-
|
|
74
67
|
private fun setupNavigation() {
|
|
75
|
-
val
|
|
76
|
-
|
|
77
|
-
return
|
|
78
|
-
}
|
|
68
|
+
val navigationOptions = NavigationOptions.Builder(context).build()
|
|
69
|
+
mapboxNavigation = MapboxNavigationProvider.create(navigationOptions)
|
|
79
70
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
.build()
|
|
71
|
+
mapView.mapboxMap.loadStyle(mapStyle ?: Style.MAPBOX_STREETS) { style ->
|
|
72
|
+
// Route line components
|
|
73
|
+
routeLineApi = MapboxRouteLineApi(MapboxRouteLineApiOptions.Builder().build())
|
|
74
|
+
routeLineView = MapboxRouteLineView(
|
|
75
|
+
MapboxRouteLineViewOptions.Builder(context).build()
|
|
76
|
+
)
|
|
83
77
|
|
|
84
|
-
|
|
78
|
+
// NavigationCamera — exactly as in official Mapbox example:
|
|
79
|
+
// NavigationCamera(mapboxMap, mapView.camera, viewportDataSource)
|
|
80
|
+
viewportDataSource = MapboxNavigationViewportDataSource(mapView.mapboxMap)
|
|
81
|
+
navigationCamera = NavigationCamera(
|
|
82
|
+
mapView.mapboxMap,
|
|
83
|
+
mapView.camera, // ← mapView.camera (Maps plugin extension)
|
|
84
|
+
viewportDataSource!!
|
|
85
|
+
)
|
|
85
86
|
|
|
86
|
-
// Setup map style + route line + camera once map is loaded
|
|
87
|
-
mapView.getMapboxMap().loadStyle(
|
|
88
|
-
mapStyle ?: Style.MAPBOX_STREETS
|
|
89
|
-
) { style ->
|
|
90
|
-
setupRouteLineComponents(style)
|
|
91
|
-
setupCamera()
|
|
92
87
|
registerObservers()
|
|
93
88
|
}
|
|
94
89
|
}
|
|
95
90
|
|
|
96
|
-
private fun setupRouteLineComponents(style: Style) {
|
|
97
|
-
routeLineApi = MapboxRouteLineApi(MapboxRouteLineApiOptions.Builder().build())
|
|
98
|
-
routeLineView = MapboxRouteLineView(MapboxRouteLineViewOptions.Builder(context).build())
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
private fun setupCamera() {
|
|
102
|
-
val mapboxMap = mapView.getMapboxMap()
|
|
103
|
-
viewportDataSource = MapboxNavigationViewportDataSource(mapboxMap)
|
|
104
|
-
navigationCamera = NavigationCamera(
|
|
105
|
-
mapboxMap,
|
|
106
|
-
mapView.camera,
|
|
107
|
-
viewportDataSource!!
|
|
108
|
-
)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
91
|
private fun registerObservers() {
|
|
112
92
|
val nav = mapboxNavigation ?: return
|
|
113
93
|
|
|
114
|
-
// Routes observer
|
|
94
|
+
// Routes observer
|
|
115
95
|
nav.registerRoutesObserver(object : RoutesObserver {
|
|
116
96
|
override fun onRoutesChanged(result: RoutesUpdatedResult) {
|
|
117
97
|
val routes = result.navigationRoutes
|
|
118
98
|
if (routes.isNotEmpty()) {
|
|
119
99
|
routeLineApi?.setNavigationRoutes(routes) { value ->
|
|
120
|
-
mapView.
|
|
100
|
+
mapView.mapboxMap.style?.let { style ->
|
|
121
101
|
routeLineView?.renderRouteDrawData(style, value)
|
|
122
102
|
}
|
|
123
103
|
}
|
|
124
|
-
//
|
|
104
|
+
// Update viewport data source with the route
|
|
105
|
+
viewportDataSource?.onRouteChanged(routes.first())
|
|
106
|
+
viewportDataSource?.evaluate()
|
|
107
|
+
|
|
108
|
+
// Issue #43 fix: safe camera following transition
|
|
125
109
|
safeCameraFollowing()
|
|
126
110
|
|
|
127
|
-
|
|
111
|
+
emitEvent("onRoutesReady", mapOf(
|
|
128
112
|
"routeCount" to routes.size,
|
|
129
|
-
"distanceMeters" to (routes.
|
|
130
|
-
"durationSeconds" to (routes.
|
|
113
|
+
"distanceMeters" to (routes.first().directionsRoute.distance() ?: 0.0),
|
|
114
|
+
"durationSeconds" to (routes.first().directionsRoute.duration() ?: 0.0)
|
|
131
115
|
))
|
|
116
|
+
} else {
|
|
117
|
+
routeLineApi?.clearRouteLine { value ->
|
|
118
|
+
mapView.mapboxMap.style?.let { style ->
|
|
119
|
+
routeLineView?.renderClearRouteLineValue(style, value)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
viewportDataSource?.clearRouteData()
|
|
123
|
+
viewportDataSource?.evaluate()
|
|
132
124
|
}
|
|
133
125
|
}
|
|
134
126
|
})
|
|
135
127
|
|
|
136
128
|
// Route progress observer
|
|
137
|
-
nav.registerRouteProgressObserver(RouteProgressObserver {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
"
|
|
129
|
+
nav.registerRouteProgressObserver(RouteProgressObserver { routeProgress ->
|
|
130
|
+
// Update viewport data source with progress
|
|
131
|
+
viewportDataSource?.onRouteProgressChanged(routeProgress)
|
|
132
|
+
viewportDataSource?.evaluate()
|
|
133
|
+
|
|
134
|
+
emitEvent("onRouteProgressChanged", mapOf(
|
|
135
|
+
"distanceRemaining" to routeProgress.distanceRemaining,
|
|
136
|
+
"durationRemaining" to routeProgress.durationRemaining,
|
|
137
|
+
"distanceTraveled" to routeProgress.distanceTraveled,
|
|
138
|
+
"fractionTraveled" to routeProgress.fractionTraveled,
|
|
139
|
+
"currentStepDistanceRemaining" to
|
|
140
|
+
(routeProgress.currentLegProgress
|
|
141
|
+
?.currentStepProgress?.distanceRemaining ?: 0f)
|
|
144
142
|
))
|
|
145
143
|
})
|
|
146
144
|
}
|
|
147
145
|
|
|
148
|
-
//
|
|
149
|
-
// Issue #43 fix: safe camera transition
|
|
150
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
146
|
+
// ── Issue #43: safe camera transition ────────────────────────────────────
|
|
151
147
|
private fun safeCameraFollowing() {
|
|
152
148
|
try {
|
|
153
149
|
navigationCamera?.requestNavigationCameraToFollowing(
|
|
@@ -160,10 +156,8 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
160
156
|
}
|
|
161
157
|
}
|
|
162
158
|
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
166
|
-
private fun resolveUnitType(): String {
|
|
159
|
+
// ── Issue #31: voice units ────────────────────────────────────────────────
|
|
160
|
+
private fun resolveVoiceUnits(): String {
|
|
167
161
|
return when (voiceUnits?.lowercase()) {
|
|
168
162
|
"metric" -> "metric"
|
|
169
163
|
"imperial" -> "imperial"
|
|
@@ -175,9 +169,8 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
175
169
|
}
|
|
176
170
|
}
|
|
177
171
|
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
172
|
+
// ── Route request ─────────────────────────────────────────────────────────
|
|
173
|
+
@SuppressLint("MissingPermission")
|
|
181
174
|
private fun fetchRoutes() {
|
|
182
175
|
val nav = mapboxNavigation ?: return
|
|
183
176
|
if (coordinates.size < 2) return
|
|
@@ -185,39 +178,34 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
185
178
|
val points = coordinates.map { coord ->
|
|
186
179
|
Point.fromLngLat(coord["longitude"] ?: 0.0, coord["latitude"] ?: 0.0)
|
|
187
180
|
}
|
|
188
|
-
|
|
189
181
|
val locale = language?.let { Locale.forLanguageTag(it) } ?: Locale.getDefault()
|
|
190
|
-
val unitType = resolveUnitType()
|
|
191
182
|
|
|
192
183
|
val builder = RouteOptions.builder()
|
|
193
184
|
.applyDefaultNavigationOptions()
|
|
194
185
|
.language(locale.toLanguageTag())
|
|
195
|
-
.voiceUnits(
|
|
186
|
+
.voiceUnits(resolveVoiceUnits()) // Issue #31 fix
|
|
196
187
|
.coordinatesList(points)
|
|
197
188
|
|
|
198
189
|
waypointIndices?.let { builder.waypointIndicesList(it) }
|
|
199
|
-
|
|
200
190
|
navigationProfile?.let {
|
|
201
|
-
|
|
202
|
-
builder.profile(profile)
|
|
191
|
+
builder.profile(if (it.startsWith("mapbox/")) it else "mapbox/$it")
|
|
203
192
|
}
|
|
204
|
-
|
|
205
193
|
excludeTypes?.takeIf { it.isNotEmpty() }?.let {
|
|
206
194
|
builder.exclude(it.joinToString(","))
|
|
207
195
|
}
|
|
208
|
-
|
|
209
196
|
maxHeight?.let { builder.maxHeight(it) }
|
|
210
197
|
maxWidth?.let { builder.maxWidth(it) }
|
|
211
198
|
|
|
199
|
+
// Exact Nav SDK v3 callback signature from official Mapbox docs
|
|
212
200
|
nav.requestRoutes(
|
|
213
201
|
builder.build(),
|
|
214
202
|
object : NavigationRouterCallback {
|
|
215
203
|
override fun onRoutesReady(
|
|
216
204
|
routes: List<NavigationRoute>,
|
|
217
|
-
routerOrigin:
|
|
205
|
+
@RouterOrigin routerOrigin: String
|
|
218
206
|
) {
|
|
219
207
|
if (routes.isEmpty()) {
|
|
220
|
-
|
|
208
|
+
emitEvent("onRoutesFailed", mapOf("message" to "No routes returned"))
|
|
221
209
|
return
|
|
222
210
|
}
|
|
223
211
|
nav.setNavigationRoutes(routes)
|
|
@@ -230,12 +218,12 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
230
218
|
) {
|
|
231
219
|
val msg = reasons.firstOrNull()?.message ?: "Unknown error"
|
|
232
220
|
Log.e(TAG, "Route request failed: $msg")
|
|
233
|
-
|
|
221
|
+
emitEvent("onRoutesFailed", mapOf("message" to msg))
|
|
234
222
|
}
|
|
235
223
|
|
|
236
224
|
override fun onCanceled(
|
|
237
225
|
routeOptions: RouteOptions,
|
|
238
|
-
routerOrigin:
|
|
226
|
+
@RouterOrigin routerOrigin: String
|
|
239
227
|
) {
|
|
240
228
|
Log.d(TAG, "Route request cancelled")
|
|
241
229
|
}
|
|
@@ -243,15 +231,22 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
243
231
|
)
|
|
244
232
|
}
|
|
245
233
|
|
|
246
|
-
//
|
|
247
|
-
|
|
248
|
-
|
|
234
|
+
// ── Expo event emitter (SDK 53 compatible) ────────────────────────────────
|
|
235
|
+
private fun emitEvent(name: String, data: Map<String, Any>) {
|
|
236
|
+
try {
|
|
237
|
+
// Expo modules core SDK 53: use appContext.eventEmitter
|
|
238
|
+
val emitter = appContext.eventEmitter
|
|
239
|
+
emitter?.emit(name, data)
|
|
240
|
+
} catch (e: Exception) {
|
|
241
|
+
Log.e(TAG, "Failed to emit event $name: ${e.message}")
|
|
242
|
+
}
|
|
243
|
+
}
|
|
249
244
|
|
|
245
|
+
// ── Prop setters ──────────────────────────────────────────────────────────
|
|
250
246
|
fun setCoordinates(coords: List<Map<String, Double>>) {
|
|
251
247
|
coordinates = coords
|
|
252
248
|
if (coords.size >= 2) fetchRoutes()
|
|
253
249
|
}
|
|
254
|
-
|
|
255
250
|
fun setWaypointIndices(indices: List<Int>?) { waypointIndices = indices }
|
|
256
251
|
fun setLanguage(lang: String?) { language = lang }
|
|
257
252
|
fun setVoiceUnits(units: String?) { voiceUnits = units }
|
|
@@ -265,28 +260,13 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
|
265
260
|
fun setCustomRasterTileUrl(url: String?) { customRasterTileUrl = url }
|
|
266
261
|
fun setCustomRasterAboveLayerId(layerId: String?) { customRasterAboveLayerId = layerId }
|
|
267
262
|
|
|
268
|
-
//
|
|
269
|
-
// Helpers
|
|
270
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
271
|
-
|
|
272
|
-
private fun getAccessToken(): String? {
|
|
273
|
-
return try {
|
|
274
|
-
val resId = context.resources.getIdentifier(
|
|
275
|
-
"mapbox_access_token", "string", context.packageName
|
|
276
|
-
)
|
|
277
|
-
if (resId != 0) context.getString(resId) else null
|
|
278
|
-
} catch (e: Exception) { null }
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
private fun sendEvent(name: String, data: Map<String, Any>) {
|
|
282
|
-
appContext.eventEmitter?.emit(name, data)
|
|
283
|
-
}
|
|
284
|
-
|
|
263
|
+
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
|
285
264
|
override fun onDetachedFromWindow() {
|
|
286
265
|
super.onDetachedFromWindow()
|
|
266
|
+
routeLineApi?.cancel()
|
|
267
|
+
routeLineView?.cancel()
|
|
287
268
|
mapboxNavigation?.stopTripSession()
|
|
288
269
|
MapboxNavigationProvider.destroy()
|
|
289
|
-
routeLineApi?.cancel()
|
|
290
270
|
mapView.onStop()
|
|
291
271
|
mapView.onDestroy()
|
|
292
272
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jacques_gordon/expo-mapbox-navigation",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.7",
|
|
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",
|