@atomiqlab/react-native-mapbox-navigation 1.1.0
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/CHANGELOG.md +18 -0
- package/LICENSE +21 -0
- package/QUICKSTART.md +46 -0
- package/README.md +131 -0
- package/android/build.gradle +64 -0
- package/android/src/main/AndroidManifest.xml +8 -0
- package/android/src/main/java/expo/modules/mapboxnavigation/MapboxNavigationActivity.kt +421 -0
- package/android/src/main/java/expo/modules/mapboxnavigation/MapboxNavigationEventBridge.kt +18 -0
- package/android/src/main/java/expo/modules/mapboxnavigation/MapboxNavigationModule.kt +296 -0
- package/android/src/main/java/expo/modules/mapboxnavigation/MapboxNavigationViewManager.kt +143 -0
- package/app.plugin.js +154 -0
- package/docs/PUBLISHING.md +97 -0
- package/docs/TROUBLESHOOTING.md +35 -0
- package/docs/USAGE.md +100 -0
- package/expo-module.config.json +9 -0
- package/ios/ExpoMapboxNavigationNative.podspec +31 -0
- package/ios/MapboxNavigationModule.swift +613 -0
- package/ios/MapboxNavigationView.swift +298 -0
- package/package.json +75 -0
- package/scripts/verify-release.mjs +115 -0
- package/src/MapboxNavigation.types.ts +136 -0
- package/src/index.tsx +204 -0
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
package expo.modules.mapboxnavigation
|
|
2
|
+
|
|
3
|
+
import android.Manifest
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import android.content.pm.PackageManager
|
|
6
|
+
import android.os.Bundle
|
|
7
|
+
import android.os.Handler
|
|
8
|
+
import android.os.Looper
|
|
9
|
+
import android.util.Log
|
|
10
|
+
import android.view.Gravity
|
|
11
|
+
import android.widget.FrameLayout
|
|
12
|
+
import android.widget.TextView
|
|
13
|
+
import androidx.appcompat.app.AppCompatDelegate
|
|
14
|
+
import androidx.appcompat.app.AppCompatActivity
|
|
15
|
+
import androidx.activity.result.contract.ActivityResultContracts
|
|
16
|
+
import androidx.core.content.ContextCompat
|
|
17
|
+
import com.mapbox.api.directions.v5.models.BannerInstructions
|
|
18
|
+
import com.mapbox.api.directions.v5.models.RouteOptions
|
|
19
|
+
import com.mapbox.geojson.Point
|
|
20
|
+
import com.mapbox.navigation.base.trip.model.RouteProgress
|
|
21
|
+
import com.mapbox.navigation.core.MapboxNavigation
|
|
22
|
+
import com.mapbox.navigation.core.MapboxNavigationProvider
|
|
23
|
+
import com.mapbox.navigation.core.trip.session.BannerInstructionsObserver
|
|
24
|
+
import com.mapbox.navigation.core.trip.session.LocationMatcherResult
|
|
25
|
+
import com.mapbox.navigation.core.trip.session.LocationObserver
|
|
26
|
+
import com.mapbox.navigation.core.trip.session.RouteProgressObserver
|
|
27
|
+
import com.mapbox.navigation.dropin.NavigationView
|
|
28
|
+
import com.mapbox.navigation.dropin.RouteOptionsInterceptor
|
|
29
|
+
import com.mapbox.navigation.dropin.navigationview.NavigationViewListener
|
|
30
|
+
import com.mapbox.navigation.base.route.NavigationRoute
|
|
31
|
+
import com.mapbox.navigation.base.route.RouterFailure
|
|
32
|
+
import com.mapbox.navigation.base.route.RouterOrigin
|
|
33
|
+
|
|
34
|
+
class MapboxNavigationActivity : AppCompatActivity() {
|
|
35
|
+
companion object {
|
|
36
|
+
private const val TAG = "MapboxNavigationActivity"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private var navigationView: NavigationView? = null
|
|
40
|
+
private lateinit var accessToken: String
|
|
41
|
+
private var startPoint: Point? = null
|
|
42
|
+
private var destinationPoint: Point? = null
|
|
43
|
+
private var waypointPoints: List<Point> = emptyList()
|
|
44
|
+
private var shouldSimulateRoute: Boolean = false
|
|
45
|
+
private var routeAlternatives: Boolean = false
|
|
46
|
+
private var showsSpeedLimits: Boolean = true
|
|
47
|
+
private var showsWayNameLabel: Boolean = true
|
|
48
|
+
private var showsTripProgress: Boolean = true
|
|
49
|
+
private var showsManeuverView: Boolean = true
|
|
50
|
+
private var showsActionButtons: Boolean = true
|
|
51
|
+
private var mapStyleUriDay: String? = null
|
|
52
|
+
private var mapStyleUriNight: String? = null
|
|
53
|
+
private var uiTheme: String = "system"
|
|
54
|
+
private var hasStartedGuidance: Boolean = false
|
|
55
|
+
private var mapboxNavigation: MapboxNavigation? = null
|
|
56
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
57
|
+
private val locationObserver = object : LocationObserver {
|
|
58
|
+
override fun onNewRawLocation(rawLocation: android.location.Location) = Unit
|
|
59
|
+
|
|
60
|
+
override fun onNewLocationMatcherResult(locationMatcherResult: LocationMatcherResult) {
|
|
61
|
+
val location = locationMatcherResult.enhancedLocation
|
|
62
|
+
MapboxNavigationEventBridge.emit(
|
|
63
|
+
"onLocationChange",
|
|
64
|
+
mapOf(
|
|
65
|
+
"latitude" to location.latitude,
|
|
66
|
+
"longitude" to location.longitude,
|
|
67
|
+
"bearing" to location.bearing.toDouble(),
|
|
68
|
+
"speed" to location.speed.toDouble(),
|
|
69
|
+
"altitude" to location.altitude,
|
|
70
|
+
"accuracy" to location.accuracy.toDouble()
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
private val routeProgressObserver = RouteProgressObserver { routeProgress: RouteProgress ->
|
|
76
|
+
MapboxNavigationEventBridge.emit(
|
|
77
|
+
"onRouteProgressChange",
|
|
78
|
+
mapOf(
|
|
79
|
+
"distanceTraveled" to routeProgress.distanceTraveled.toDouble(),
|
|
80
|
+
"distanceRemaining" to routeProgress.distanceRemaining.toDouble(),
|
|
81
|
+
"durationRemaining" to routeProgress.durationRemaining,
|
|
82
|
+
"fractionTraveled" to routeProgress.fractionTraveled.toDouble()
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
emitBannerInstruction(routeProgress.bannerInstructions)
|
|
87
|
+
}
|
|
88
|
+
private val bannerInstructionsObserver = BannerInstructionsObserver { bannerInstructions ->
|
|
89
|
+
emitBannerInstruction(bannerInstructions)
|
|
90
|
+
}
|
|
91
|
+
private val locationPermissionLauncher = registerForActivityResult(
|
|
92
|
+
ActivityResultContracts.RequestMultiplePermissions()
|
|
93
|
+
) { result ->
|
|
94
|
+
val granted = result[Manifest.permission.ACCESS_FINE_LOCATION] == true ||
|
|
95
|
+
result[Manifest.permission.ACCESS_COARSE_LOCATION] == true
|
|
96
|
+
|
|
97
|
+
if (!granted) {
|
|
98
|
+
showErrorAndStay("Location permission is required to start navigation.", null)
|
|
99
|
+
return@registerForActivityResult
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
createNavigationViewIfNeeded()
|
|
103
|
+
}
|
|
104
|
+
private val navigationViewListener = object : NavigationViewListener() {
|
|
105
|
+
override fun onDestinationChanged(destination: Point?) {
|
|
106
|
+
Log.d(TAG, "Destination changed: $destination")
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
override fun onDestinationPreview() {
|
|
110
|
+
Log.d(TAG, "NavigationView entered destination preview")
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
override fun onRouteFetchFailed(reasons: List<RouterFailure>, routeOptions: RouteOptions) {
|
|
114
|
+
Log.e(TAG, "Route fetch failed. reasons=${reasons.size}, options=$routeOptions")
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
override fun onRouteFetchSuccessful(routes: List<NavigationRoute>) {
|
|
118
|
+
Log.d(TAG, "Route fetch succeeded. routeCount=${routes.size}")
|
|
119
|
+
|
|
120
|
+
if (!shouldSimulateRoute || routes.isEmpty() || hasStartedGuidance) {
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
hasStartedGuidance = true
|
|
125
|
+
navigationView?.api?.startActiveGuidance(routes)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
override fun onRouteFetchCanceled(routeOptions: RouteOptions, routerOrigin: RouterOrigin) {
|
|
129
|
+
Log.w(TAG, "Route fetch canceled. origin=$routerOrigin, options=$routeOptions")
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
134
|
+
super.onCreate(savedInstanceState)
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
accessToken = resolveAccessToken()
|
|
138
|
+
val originLat = intent.getDoubleExtraOrNull("originLat")
|
|
139
|
+
val originLng = intent.getDoubleExtraOrNull("originLng")
|
|
140
|
+
val destinationLat = intent.getDoubleExtraOrNull("destLat")
|
|
141
|
+
val destinationLng = intent.getDoubleExtraOrNull("destLng")
|
|
142
|
+
shouldSimulateRoute = intent.getBooleanExtra("shouldSimulate", false)
|
|
143
|
+
routeAlternatives = intent.getBooleanExtra("routeAlternatives", false)
|
|
144
|
+
showsSpeedLimits = intent.getBooleanExtra("showsSpeedLimits", true)
|
|
145
|
+
showsWayNameLabel = intent.getBooleanExtra("showsWayNameLabel", true)
|
|
146
|
+
showsTripProgress = intent.getBooleanExtra("showsTripProgress", true)
|
|
147
|
+
showsManeuverView = intent.getBooleanExtra("showsManeuverView", true)
|
|
148
|
+
showsActionButtons = intent.getBooleanExtra("showsActionButtons", true)
|
|
149
|
+
mapStyleUriDay = intent.getStringExtra("mapStyleUriDay")?.trim()?.takeIf { it.isNotEmpty() }
|
|
150
|
+
mapStyleUriNight = intent.getStringExtra("mapStyleUriNight")?.trim()?.takeIf { it.isNotEmpty() }
|
|
151
|
+
uiTheme = intent.getStringExtra("uiTheme")?.trim()?.lowercase() ?: "system"
|
|
152
|
+
|
|
153
|
+
val waypointLats = intent.getDoubleArrayExtra("waypointLats")
|
|
154
|
+
val waypointLngs = intent.getDoubleArrayExtra("waypointLngs")
|
|
155
|
+
waypointPoints = parseWaypoints(waypointLats, waypointLngs)
|
|
156
|
+
|
|
157
|
+
if (originLat != null && originLng != null) {
|
|
158
|
+
validateLatLng(originLat, originLng, label = "origin")
|
|
159
|
+
startPoint = Point.fromLngLat(originLng, originLat)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (destinationLat != null && destinationLng != null) {
|
|
163
|
+
validateLatLng(destinationLat, destinationLng, label = "destination")
|
|
164
|
+
destinationPoint = Point.fromLngLat(destinationLng, destinationLat)
|
|
165
|
+
} else {
|
|
166
|
+
Log.w(TAG, "No valid destination extras provided, starting without preview")
|
|
167
|
+
}
|
|
168
|
+
} catch (throwable: Throwable) {
|
|
169
|
+
showErrorAndStay("Navigation init failed: ${throwable.message}", throwable)
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (hasLocationPermission()) {
|
|
174
|
+
createNavigationViewIfNeeded()
|
|
175
|
+
} else {
|
|
176
|
+
locationPermissionLauncher.launch(
|
|
177
|
+
arrayOf(
|
|
178
|
+
Manifest.permission.ACCESS_FINE_LOCATION,
|
|
179
|
+
Manifest.permission.ACCESS_COARSE_LOCATION
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private fun getMapboxAccessToken(): String {
|
|
186
|
+
val resourceId = resources.getIdentifier(
|
|
187
|
+
"mapbox_access_token",
|
|
188
|
+
"string",
|
|
189
|
+
packageName
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
if (resourceId == 0) {
|
|
193
|
+
throw IllegalStateException("Missing string resource: mapbox_access_token")
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
val token = getString(resourceId).trim()
|
|
197
|
+
if (token.isEmpty()) {
|
|
198
|
+
throw IllegalStateException("mapbox_access_token is empty")
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return token
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private fun createNavigationViewIfNeeded() {
|
|
205
|
+
if (navigationView != null) {
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
delegate.localNightMode = when (uiTheme) {
|
|
211
|
+
"light", "day" -> AppCompatDelegate.MODE_NIGHT_NO
|
|
212
|
+
"dark", "night" -> AppCompatDelegate.MODE_NIGHT_YES
|
|
213
|
+
else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
navigationView = NavigationView(this, null, accessToken).also { view ->
|
|
217
|
+
view.customizeViewOptions {
|
|
218
|
+
resolveDayStyleUri()?.let { mapStyleUriDay = it }
|
|
219
|
+
resolveNightStyleUri()?.let { mapStyleUriNight = it }
|
|
220
|
+
showSpeedLimit = showsSpeedLimits
|
|
221
|
+
showRoadName = showsWayNameLabel
|
|
222
|
+
showTripProgress = showsTripProgress
|
|
223
|
+
showManeuver = showsManeuverView
|
|
224
|
+
showActionButtons = showsActionButtons
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (startPoint != null && destinationPoint != null) {
|
|
228
|
+
view.setRouteOptionsInterceptor(
|
|
229
|
+
RouteOptionsInterceptor { builder ->
|
|
230
|
+
val coordinates = mutableListOf<Point>()
|
|
231
|
+
coordinates.add(startPoint!!)
|
|
232
|
+
coordinates.addAll(waypointPoints)
|
|
233
|
+
coordinates.add(destinationPoint!!)
|
|
234
|
+
builder.coordinatesList(coordinates)
|
|
235
|
+
builder.alternatives(routeAlternatives)
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
view.addListener(navigationViewListener)
|
|
240
|
+
}
|
|
241
|
+
setContentView(navigationView ?: FrameLayout(this))
|
|
242
|
+
attachNavigationObserversWithRetry()
|
|
243
|
+
|
|
244
|
+
navigationView?.api?.routeReplayEnabled(shouldSimulateRoute)
|
|
245
|
+
|
|
246
|
+
// Start destination flow only after the window is attached and active.
|
|
247
|
+
destinationPoint?.let { point ->
|
|
248
|
+
mainHandler.postDelayed({
|
|
249
|
+
if (isFinishing || isDestroyed) return@postDelayed
|
|
250
|
+
if (!hasWindowFocus()) {
|
|
251
|
+
Log.w(TAG, "Skipping destination preview because activity has no window focus")
|
|
252
|
+
return@postDelayed
|
|
253
|
+
}
|
|
254
|
+
navigationView?.api?.startDestinationPreview(point)
|
|
255
|
+
}, 350L)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
} catch (throwable: Throwable) {
|
|
259
|
+
showErrorAndStay("Failed to create NavigationView: ${throwable.message}", throwable)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
override fun onDestroy() {
|
|
264
|
+
mainHandler.removeCallbacksAndMessages(null)
|
|
265
|
+
navigationView?.removeListener(navigationViewListener)
|
|
266
|
+
detachNavigationObservers()
|
|
267
|
+
super.onDestroy()
|
|
268
|
+
navigationView = null
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private fun resolveAccessToken(): String {
|
|
272
|
+
val fromIntent = intent.getStringExtra("accessToken")?.trim().orEmpty()
|
|
273
|
+
if (fromIntent.startsWith("pk.") && fromIntent.length > 20) {
|
|
274
|
+
return fromIntent
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
val fromResources = getMapboxAccessToken()
|
|
278
|
+
if (!fromResources.startsWith("pk.") || fromResources.length <= 20) {
|
|
279
|
+
throw IllegalStateException("Invalid Mapbox public token in mapbox_access_token")
|
|
280
|
+
}
|
|
281
|
+
return fromResources
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private fun Intent.getDoubleExtraOrNull(key: String): Double? {
|
|
285
|
+
if (!hasExtra(key)) {
|
|
286
|
+
return null
|
|
287
|
+
}
|
|
288
|
+
val value = getDoubleExtra(key, Double.NaN)
|
|
289
|
+
if (!value.isFinite()) {
|
|
290
|
+
return null
|
|
291
|
+
}
|
|
292
|
+
return value
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
private fun validateLatLng(latitude: Double, longitude: Double, label: String) {
|
|
296
|
+
if (latitude < -90.0 || latitude > 90.0) {
|
|
297
|
+
throw IllegalStateException("Invalid $label latitude: $latitude")
|
|
298
|
+
}
|
|
299
|
+
if (longitude < -180.0 || longitude > 180.0) {
|
|
300
|
+
throw IllegalStateException("Invalid $label longitude: $longitude")
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private fun hasLocationPermission(): Boolean {
|
|
305
|
+
val fineGranted = ContextCompat.checkSelfPermission(
|
|
306
|
+
this,
|
|
307
|
+
Manifest.permission.ACCESS_FINE_LOCATION
|
|
308
|
+
) == PackageManager.PERMISSION_GRANTED
|
|
309
|
+
|
|
310
|
+
val coarseGranted = ContextCompat.checkSelfPermission(
|
|
311
|
+
this,
|
|
312
|
+
Manifest.permission.ACCESS_COARSE_LOCATION
|
|
313
|
+
) == PackageManager.PERMISSION_GRANTED
|
|
314
|
+
|
|
315
|
+
return fineGranted || coarseGranted
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private fun showErrorAndStay(message: String, throwable: Throwable?) {
|
|
319
|
+
if (throwable != null) {
|
|
320
|
+
Log.e(TAG, message, throwable)
|
|
321
|
+
} else {
|
|
322
|
+
Log.e(TAG, message)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
val errorView = TextView(this).apply {
|
|
326
|
+
text = message
|
|
327
|
+
gravity = Gravity.CENTER
|
|
328
|
+
textSize = 16f
|
|
329
|
+
setPadding(32, 32, 32, 32)
|
|
330
|
+
}
|
|
331
|
+
setContentView(
|
|
332
|
+
FrameLayout(this).apply {
|
|
333
|
+
addView(
|
|
334
|
+
errorView,
|
|
335
|
+
FrameLayout.LayoutParams(
|
|
336
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
337
|
+
FrameLayout.LayoutParams.MATCH_PARENT
|
|
338
|
+
)
|
|
339
|
+
)
|
|
340
|
+
}
|
|
341
|
+
)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private fun attachNavigationObserversWithRetry(attempt: Int = 0) {
|
|
345
|
+
if (mapboxNavigation != null) {
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
if (!MapboxNavigationProvider.isCreated()) {
|
|
349
|
+
if (attempt < 10) {
|
|
350
|
+
mainHandler.postDelayed({ attachNavigationObserversWithRetry(attempt + 1) }, 150L)
|
|
351
|
+
}
|
|
352
|
+
return
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
val navigation = runCatching { MapboxNavigationProvider.retrieve() }
|
|
356
|
+
.getOrElse { throwable ->
|
|
357
|
+
Log.e(TAG, "Unable to retrieve MapboxNavigation", throwable)
|
|
358
|
+
return
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
mapboxNavigation = navigation
|
|
362
|
+
navigation.registerLocationObserver(locationObserver)
|
|
363
|
+
navigation.registerRouteProgressObserver(routeProgressObserver)
|
|
364
|
+
navigation.registerBannerInstructionsObserver(bannerInstructionsObserver)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private fun detachNavigationObservers() {
|
|
368
|
+
mapboxNavigation?.let { navigation ->
|
|
369
|
+
runCatching { navigation.unregisterLocationObserver(locationObserver) }
|
|
370
|
+
runCatching { navigation.unregisterRouteProgressObserver(routeProgressObserver) }
|
|
371
|
+
runCatching { navigation.unregisterBannerInstructionsObserver(bannerInstructionsObserver) }
|
|
372
|
+
}
|
|
373
|
+
mapboxNavigation = null
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
private fun emitBannerInstruction(instruction: BannerInstructions?) {
|
|
377
|
+
val primary = instruction?.primary()?.text()?.trim().orEmpty()
|
|
378
|
+
if (primary.isEmpty()) {
|
|
379
|
+
return
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
val payload = mutableMapOf<String, Any?>("primaryText" to primary)
|
|
383
|
+
val secondary = instruction?.secondary()?.text()?.trim().orEmpty()
|
|
384
|
+
if (secondary.isNotEmpty()) {
|
|
385
|
+
payload["secondaryText"] = secondary
|
|
386
|
+
}
|
|
387
|
+
payload["stepDistanceRemaining"] = instruction?.distanceAlongGeometry() ?: 0.0
|
|
388
|
+
|
|
389
|
+
MapboxNavigationEventBridge.emit("onBannerInstruction", payload)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private fun parseWaypoints(
|
|
393
|
+
waypointLats: DoubleArray?,
|
|
394
|
+
waypointLngs: DoubleArray?
|
|
395
|
+
): List<Point> {
|
|
396
|
+
if (waypointLats == null || waypointLngs == null || waypointLats.size != waypointLngs.size) {
|
|
397
|
+
return emptyList()
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return waypointLats.indices.mapNotNull { index ->
|
|
401
|
+
val latitude = waypointLats[index]
|
|
402
|
+
val longitude = waypointLngs[index]
|
|
403
|
+
if (latitude !in -90.0..90.0 || longitude !in -180.0..180.0) {
|
|
404
|
+
return@mapNotNull null
|
|
405
|
+
}
|
|
406
|
+
Point.fromLngLat(longitude, latitude)
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private fun resolveDayStyleUri(): String? {
|
|
411
|
+
mapStyleUriDay?.let { return it }
|
|
412
|
+
val legacy = intent.getStringExtra("mapStyleUri")?.trim().orEmpty()
|
|
413
|
+
return legacy.ifEmpty { null }
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
private fun resolveNightStyleUri(): String? {
|
|
417
|
+
mapStyleUriNight?.let { return it }
|
|
418
|
+
val dayFallback = resolveDayStyleUri()
|
|
419
|
+
return dayFallback
|
|
420
|
+
}
|
|
421
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
package expo.modules.mapboxnavigation
|
|
2
|
+
|
|
3
|
+
object MapboxNavigationEventBridge {
|
|
4
|
+
@Volatile
|
|
5
|
+
private var emitter: ((String, Map<String, Any?>) -> Unit)? = null
|
|
6
|
+
|
|
7
|
+
fun setEmitter(nextEmitter: (String, Map<String, Any?>) -> Unit) {
|
|
8
|
+
emitter = nextEmitter
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
fun clearEmitter() {
|
|
12
|
+
emitter = null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
fun emit(eventName: String, payload: Map<String, Any?> = emptyMap()) {
|
|
16
|
+
emitter?.invoke(eventName, payload)
|
|
17
|
+
}
|
|
18
|
+
}
|