@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
- // ── Props ────────────────────────────────────────────────────────────────
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 accessToken = getAccessToken() ?: run {
76
- Log.e(TAG, "Mapbox access token not found.")
77
- return
78
- }
68
+ val navigationOptions = NavigationOptions.Builder(context).build()
69
+ mapboxNavigation = MapboxNavigationProvider.create(navigationOptions)
79
70
 
80
- // Create MapboxNavigation instance (Nav SDK v3 API)
81
- val navigationOptions = NavigationOptions.Builder(context)
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
- mapboxNavigation = MapboxNavigationProvider.create(navigationOptions)
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 — updates route line on map
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.getMapboxMap().getStyle()?.let { style ->
100
+ mapView.mapboxMap.style?.let { style ->
121
101
  routeLineView?.renderRouteDrawData(style, value)
122
102
  }
123
103
  }
124
- // Issue #43 fix: safe camera transition to following
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
- sendEvent("onRoutesReady", mapOf(
111
+ emitEvent("onRoutesReady", mapOf(
128
112
  "routeCount" to routes.size,
129
- "distanceMeters" to (routes.firstOrNull()?.directionsRoute?.distance() ?: 0.0),
130
- "durationSeconds" to (routes.firstOrNull()?.directionsRoute?.duration() ?: 0.0)
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 { progress ->
138
- sendEvent("onRouteProgressChanged", mapOf(
139
- "distanceRemaining" to progress.distanceRemaining,
140
- "durationRemaining" to progress.durationRemaining,
141
- "distanceTraveled" to progress.distanceTraveled,
142
- "fractionTraveled" to progress.fractionTraveled,
143
- "currentStepDistanceRemaining" to (progress.currentLegProgress?.currentStepProgress?.distanceRemaining ?: 0f)
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
- // Issue #31 fix: voice units
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
- // Route request
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(unitType) // Issue #31 fix
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
- val profile = if (it.startsWith("mapbox/")) it else "mapbox/$it"
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: com.mapbox.navigation.base.route.RouterOrigin
205
+ @RouterOrigin routerOrigin: String
218
206
  ) {
219
207
  if (routes.isEmpty()) {
220
- sendEvent("onRoutesFailed", mapOf("message" to "No routes returned"))
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
- sendEvent("onRoutesFailed", mapOf("message" to msg))
221
+ emitEvent("onRoutesFailed", mapOf("message" to msg))
234
222
  }
235
223
 
236
224
  override fun onCanceled(
237
225
  routeOptions: RouteOptions,
238
- routerOrigin: com.mapbox.navigation.base.route.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
- // Prop setters
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.5",
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",