@jacques_gordon/expo-mapbox-navigation 1.0.7 → 2.0.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.
- package/ExpoMapboxNavigation.podspec +39 -0
- package/LICENSE +21 -0
- package/README.md +103 -209
- package/android/build.gradle +101 -39
- package/android/src/main/java/expo/modules/mapboxnavigation/ExpoMapboxNavigationModule.kt +81 -77
- package/android/src/main/java/expo/modules/mapboxnavigation/ExpoMapboxNavigationView.kt +260 -142
- package/app.plugin.js +216 -1
- package/build/index.d.ts +121 -2
- package/build/index.js +34 -3
- package/build/src/index.d.ts +121 -0
- package/build/src/index.js +39 -0
- package/package.json +37 -37
- package/plugin/src/index.js +216 -0
- package/src/index.tsx +162 -0
- package/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.9/gc.properties +0 -0
- package/android/.gradle/9.2.0/checksums/checksums.lock +0 -0
- package/android/.gradle/9.2.0/fileChanges/last-build.bin +0 -0
- package/android/.gradle/9.2.0/fileHashes/fileHashes.bin +0 -0
- package/android/.gradle/9.2.0/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/9.2.0/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/build/MapboxNavigation.types.d.ts +0 -132
- package/build/MapboxNavigation.types.js +0 -2
- package/build/MapboxNavigationView.d.ts +0 -30
- package/build/MapboxNavigationView.js +0 -41
- package/expo-module.config.json +0 -9
- package/ios/ExpoMapboxNavigation.podspec +0 -25
- package/ios/ExpoMapboxNavigationModule.swift +0 -81
- package/ios/ExpoMapboxNavigationView.swift +0 -236
- package/plugin/index.js +0 -167
|
@@ -4,82 +4,86 @@ import expo.modules.kotlin.modules.Module
|
|
|
4
4
|
import expo.modules.kotlin.modules.ModuleDefinition
|
|
5
5
|
|
|
6
6
|
class ExpoMapboxNavigationModule : Module() {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
7
|
+
override fun definition() = ModuleDefinition {
|
|
8
|
+
Name("ExpoMapboxNavigation")
|
|
9
|
+
|
|
10
|
+
View(ExpoMapboxNavigationView::class) {
|
|
11
|
+
Events(
|
|
12
|
+
"onRouteProgressChanged",
|
|
13
|
+
"onRoutesReady",
|
|
14
|
+
"onNavigationFinished",
|
|
15
|
+
"onNavigationCancelled",
|
|
16
|
+
"onRoutesFailed",
|
|
17
|
+
"onArrival"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
// Coordinates: Array of {latitude, longitude} objects
|
|
21
|
+
Prop("coordinates") { view: ExpoMapboxNavigationView, coordinates: List<Map<String, Double>> ->
|
|
22
|
+
view.setCoordinates(coordinates)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Waypoint indices
|
|
26
|
+
Prop("waypointIndices") { view: ExpoMapboxNavigationView, indices: List<Int>? ->
|
|
27
|
+
view.setWaypointIndices(indices)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Language/locale
|
|
31
|
+
Prop("language") { view: ExpoMapboxNavigationView, language: String? ->
|
|
32
|
+
view.setLanguage(language)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ─────────────────────────────────────────────────────────────
|
|
36
|
+
// voiceUnits prop — fixes Issue #31 (metric/imperial selection)
|
|
37
|
+
// Accepts: "metric" | "imperial" | null (auto from locale)
|
|
38
|
+
// ─────────────────────────────────────────────────────────────
|
|
39
|
+
Prop("voiceUnits") { view: ExpoMapboxNavigationView, units: String? ->
|
|
40
|
+
view.setVoiceUnits(units)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Navigation profile
|
|
44
|
+
Prop("navigationProfile") { view: ExpoMapboxNavigationView, profile: String? ->
|
|
45
|
+
view.setNavigationProfile(profile)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Excluded road types
|
|
49
|
+
Prop("excludeTypes") { view: ExpoMapboxNavigationView, types: List<String>? ->
|
|
50
|
+
view.setExcludeTypes(types)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Map style URL
|
|
54
|
+
Prop("mapStyle") { view: ExpoMapboxNavigationView, style: String? ->
|
|
55
|
+
view.setMapStyle(style)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Mute navigation audio
|
|
59
|
+
Prop("mute") { view: ExpoMapboxNavigationView, mute: Boolean ->
|
|
60
|
+
view.setMute(mute)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Vehicle height restriction (meters)
|
|
64
|
+
Prop("maxHeight") { view: ExpoMapboxNavigationView, height: Double? ->
|
|
65
|
+
view.setMaxHeight(height)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Vehicle width restriction (meters)
|
|
69
|
+
Prop("maxWidth") { view: ExpoMapboxNavigationView, width: Double? ->
|
|
70
|
+
view.setMaxWidth(width)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Map matching mode
|
|
74
|
+
Prop("useMapMatching") { view: ExpoMapboxNavigationView, use: Boolean ->
|
|
75
|
+
view.setUseMapMatching(use)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Custom raster tile URL
|
|
79
|
+
Prop("customRasterTileUrl") { view: ExpoMapboxNavigationView, url: String? ->
|
|
80
|
+
view.setCustomRasterTileUrl(url)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Layer ID above which custom raster goes
|
|
84
|
+
Prop("customRasterAboveLayerId") { view: ExpoMapboxNavigationView, layerId: String? ->
|
|
85
|
+
view.setCustomRasterAboveLayerId(layerId)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
83
88
|
}
|
|
84
|
-
}
|
|
85
89
|
}
|
|
@@ -1,184 +1,302 @@
|
|
|
1
1
|
package expo.modules.mapboxnavigation
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
|
-
import android.
|
|
5
|
-
import
|
|
4
|
+
import android.util.Log
|
|
5
|
+
import androidx.lifecycle.DefaultLifecycleObserver
|
|
6
|
+
import androidx.lifecycle.LifecycleOwner
|
|
6
7
|
import com.mapbox.api.directions.v5.models.RouteOptions
|
|
8
|
+
import com.mapbox.common.location.AccuracyLevel
|
|
9
|
+
import com.mapbox.common.location.DeviceLocationProviderFactory
|
|
10
|
+
import com.mapbox.common.location.IntervalSettings
|
|
11
|
+
import com.mapbox.common.location.LocationProviderRequest
|
|
7
12
|
import com.mapbox.geojson.Point
|
|
13
|
+
import com.mapbox.maps.MapView
|
|
14
|
+
import com.mapbox.maps.Style
|
|
15
|
+
import com.mapbox.navigation.base.TimeFormat
|
|
8
16
|
import com.mapbox.navigation.base.extensions.applyDefaultNavigationOptions
|
|
9
17
|
import com.mapbox.navigation.base.extensions.applyLanguageAndVoiceUnitOptions
|
|
18
|
+
import com.mapbox.navigation.base.formatter.DistanceFormatterOptions
|
|
19
|
+
import com.mapbox.navigation.base.formatter.UnitType
|
|
20
|
+
import com.mapbox.navigation.base.options.NavigationOptions
|
|
10
21
|
import com.mapbox.navigation.base.route.NavigationRoute
|
|
11
|
-
import com.mapbox.navigation.base.route.NavigationRouterCallback
|
|
12
|
-
import com.mapbox.navigation.base.route.RouterFailure
|
|
13
22
|
import com.mapbox.navigation.base.route.RouterOrigin
|
|
14
|
-
import com.mapbox.navigation.base.trip.model.RouteProgress
|
|
15
|
-
import com.mapbox.navigation.base.trip.model.RouteProgressState
|
|
16
23
|
import com.mapbox.navigation.core.MapboxNavigation
|
|
17
24
|
import com.mapbox.navigation.core.MapboxNavigationProvider
|
|
18
25
|
import com.mapbox.navigation.core.directions.session.RoutesObserver
|
|
19
26
|
import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp
|
|
27
|
+
import com.mapbox.navigation.core.replay.MapboxReplayer
|
|
20
28
|
import com.mapbox.navigation.core.trip.session.RouteProgressObserver
|
|
29
|
+
import com.mapbox.navigation.core.trip.session.VoiceInstructionsObserver
|
|
21
30
|
import com.mapbox.navigation.dropin.NavigationView
|
|
22
|
-
import com.mapbox.navigation.dropin.
|
|
31
|
+
import com.mapbox.navigation.dropin.NavigationViewListener
|
|
32
|
+
import com.mapbox.navigation.ui.base.util.MapboxNavigationConsumer
|
|
23
33
|
import expo.modules.kotlin.AppContext
|
|
24
34
|
import expo.modules.kotlin.views.ExpoView
|
|
25
35
|
import java.util.Locale
|
|
26
36
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
37
|
+
private const val TAG = "ExpoMapboxNavigation"
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* ExpoMapboxNavigationView — Full-featured navigation view wrapping the Mapbox Drop-In UI.
|
|
41
|
+
*
|
|
42
|
+
* Fixes:
|
|
43
|
+
* - Issue #43: CameraAnimationsUtils.calculateCameraAnimationHint NoSuchMethodError
|
|
44
|
+
* Root cause: version mismatch between Navigation SDK and Maps SDK.
|
|
45
|
+
* Fix: build.gradle now pins mapbox-maps-android to ≥ 11.11.0 and navigation to 3.7.0,
|
|
46
|
+
* which ensures the method signature exists at runtime. The camera call is also wrapped
|
|
47
|
+
* in a try/catch to degrade gracefully if ever encountered.
|
|
48
|
+
*
|
|
49
|
+
* - Issue #31: Voice units always imperial regardless of device locale
|
|
50
|
+
* Fix: new `voiceUnits` prop ("metric" | "imperial" | null) exposed via module.
|
|
51
|
+
* The value is forwarded to RouteOptions via applyLanguageAndVoiceUnitOptions
|
|
52
|
+
* with an explicit UnitType override.
|
|
53
|
+
*/
|
|
54
|
+
class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
|
|
55
|
+
ExpoView(context, appContext) {
|
|
56
|
+
|
|
57
|
+
private var navigationView: NavigationView? = null
|
|
58
|
+
private var mapboxNavigation: MapboxNavigation? = null
|
|
59
|
+
|
|
60
|
+
// Props
|
|
61
|
+
private var coordinates: List<Map<String, Double>> = emptyList()
|
|
62
|
+
private var waypointIndices: List<Int>? = null
|
|
63
|
+
private var language: String? = null
|
|
64
|
+
private var voiceUnits: String? = null // "metric" | "imperial" | null
|
|
65
|
+
private var navigationProfile: String? = null
|
|
66
|
+
private var excludeTypes: List<String>? = null
|
|
67
|
+
private var mapStyle: String? = null
|
|
68
|
+
private var mute: Boolean = false
|
|
69
|
+
private var maxHeight: Double? = null
|
|
70
|
+
private var maxWidth: Double? = null
|
|
71
|
+
private var useMapMatching: Boolean = false
|
|
72
|
+
private var customRasterTileUrl: String? = null
|
|
73
|
+
private var customRasterAboveLayerId: String? = null
|
|
74
|
+
|
|
75
|
+
init {
|
|
76
|
+
setupNavigationView()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private fun setupNavigationView() {
|
|
80
|
+
val accessToken = getAccessToken() ?: run {
|
|
81
|
+
Log.e(TAG, "Mapbox access token not found. Set it via the config plugin.")
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
navigationView = NavigationView(context, accessToken).also { navView ->
|
|
86
|
+
navView.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
|
87
|
+
addView(navView)
|
|
88
|
+
|
|
89
|
+
navView.addNavigationViewListener(object : NavigationViewListener() {
|
|
90
|
+
override fun onDestinationPreviewOptionsVisible(visible: Boolean) {}
|
|
91
|
+
|
|
92
|
+
override fun onFreeDrive() {
|
|
93
|
+
sendEvent("onNavigationCancelled", mapOf<String, Any>())
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
override fun onArrival() {
|
|
97
|
+
sendEvent("onArrival", mapOf<String, Any>())
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
override fun onActiveNavigation() {}
|
|
101
|
+
override fun onPassiveNavigation() {}
|
|
102
|
+
override fun onPreview() {}
|
|
103
|
+
override fun onRoutePreview() {}
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
109
|
+
// ISSUE #43 FIX: Safe camera request
|
|
110
|
+
// Wraps requestNavigationCameraToFollowing in try/catch to handle any
|
|
111
|
+
// residual version mismatch at runtime without crashing the app.
|
|
112
|
+
// The real fix is the version pinning in build.gradle.
|
|
113
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
114
|
+
private fun safeCameraFollowing(mapboxNavigation: MapboxNavigation) {
|
|
115
|
+
try {
|
|
116
|
+
mapboxNavigation.navigationCamera?.requestNavigationCameraToFollowing()
|
|
117
|
+
} catch (e: NoSuchMethodError) {
|
|
118
|
+
Log.e(TAG, """
|
|
119
|
+
[Issue #43] CameraAnimationsUtils.calculateCameraAnimationHint not found.
|
|
120
|
+
This indicates a Mapbox Maps SDK version mismatch.
|
|
121
|
+
Ensure mapbox-maps-android >= 11.11.0 is resolved in your gradle dependencies.
|
|
122
|
+
Error: ${e.message}
|
|
123
|
+
""".trimIndent())
|
|
124
|
+
} catch (e: Exception) {
|
|
125
|
+
Log.e(TAG, "Unexpected error requesting navigation camera to following: ${e.message}")
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
130
|
+
// ISSUE #31 FIX: voiceUnits / distance units
|
|
131
|
+
// Maps the string prop value to the Mapbox UnitType enum and applies it
|
|
132
|
+
// to RouteOptions via applyLanguageAndVoiceUnitOptions.
|
|
133
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
134
|
+
private fun resolveUnitType(): UnitType {
|
|
135
|
+
return when (voiceUnits?.lowercase()) {
|
|
136
|
+
"metric" -> UnitType.METRIC
|
|
137
|
+
"imperial" -> UnitType.IMPERIAL
|
|
138
|
+
else -> {
|
|
139
|
+
// Auto-detect from device locale (default behaviour)
|
|
140
|
+
val locale = language?.let { Locale.forLanguageTag(it) } ?: Locale.getDefault()
|
|
141
|
+
// Countries that primarily use imperial: US, UK, Liberia, Myanmar
|
|
142
|
+
val imperialCountries = setOf("US", "GB", "LR", "MM")
|
|
143
|
+
if (locale.country in imperialCountries) UnitType.IMPERIAL else UnitType.METRIC
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private fun buildRouteOptions(): RouteOptions? {
|
|
149
|
+
if (coordinates.size < 2) return null
|
|
150
|
+
|
|
151
|
+
val points = coordinates.map { coord ->
|
|
152
|
+
Point.fromLngLat(coord["longitude"] ?: 0.0, coord["latitude"] ?: 0.0)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
val locale = language?.let { Locale.forLanguageTag(it) } ?: Locale.getDefault()
|
|
156
|
+
val unitType = resolveUnitType()
|
|
157
|
+
|
|
158
|
+
val builder = RouteOptions.builder()
|
|
159
|
+
.applyDefaultNavigationOptions()
|
|
160
|
+
.applyLanguageAndVoiceUnitOptions(context)
|
|
161
|
+
.language(locale.toLanguageTag())
|
|
162
|
+
.voiceUnits(unitType.value) // Issue #31 fix
|
|
163
|
+
.coordinatesList(points)
|
|
164
|
+
|
|
165
|
+
// Waypoints
|
|
166
|
+
waypointIndices?.let { indices ->
|
|
167
|
+
builder.waypointIndicesList(indices)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Navigation profile
|
|
171
|
+
navigationProfile?.let {
|
|
172
|
+
val profile = if (it.startsWith("mapbox/")) it else "mapbox/$it"
|
|
173
|
+
builder.profile(profile)
|
|
102
174
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
navView.registerRoutesObserver(object : RoutesObserver {
|
|
108
|
-
override fun onRoutesChanged(result: RoutesObserver.RoutesUpdatedResult) {
|
|
109
|
-
if (result.navigationRoutes.isNotEmpty()) {
|
|
110
|
-
onRouteChanged.emit(emptyMap<String, Any>())
|
|
175
|
+
|
|
176
|
+
// Exclude road types
|
|
177
|
+
excludeTypes?.takeIf { it.isNotEmpty() }?.let {
|
|
178
|
+
builder.exclude(it.joinToString(","))
|
|
111
179
|
}
|
|
112
|
-
}
|
|
113
|
-
})
|
|
114
180
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
181
|
+
// Vehicle restrictions
|
|
182
|
+
maxHeight?.let { builder.maxHeight(it) }
|
|
183
|
+
maxWidth?.let { builder.maxWidth(it) }
|
|
118
184
|
|
|
119
|
-
|
|
120
|
-
val points = coordinates.mapNotNull { coord ->
|
|
121
|
-
val lat = coord["latitude"] ?: return@mapNotNull null
|
|
122
|
-
val lng = coord["longitude"] ?: return@mapNotNull null
|
|
123
|
-
Point.fromLngLat(lng, lat)
|
|
185
|
+
return builder.build()
|
|
124
186
|
}
|
|
125
|
-
if (points.size < 2) return
|
|
126
187
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
188
|
+
private fun fetchAndStartRoutes() {
|
|
189
|
+
val nav = mapboxNavigation ?: return
|
|
190
|
+
val options = buildRouteOptions() ?: return
|
|
191
|
+
|
|
192
|
+
nav.requestRoutes(options, object : MapboxNavigationConsumer<List<NavigationRoute>> {
|
|
193
|
+
override fun accept(value: List<NavigationRoute>) {
|
|
194
|
+
if (value.isEmpty()) {
|
|
195
|
+
sendEvent("onRoutesFailed", mapOf("message" to "No routes returned"))
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
sendEvent("onRoutesReady", mapOf(
|
|
200
|
+
"routeCount" to value.size,
|
|
201
|
+
"distanceMeters" to (value.firstOrNull()?.directionsRoute?.distance() ?: 0.0),
|
|
202
|
+
"durationSeconds" to (value.firstOrNull()?.directionsRoute?.duration() ?: 0.0)
|
|
203
|
+
))
|
|
204
|
+
|
|
205
|
+
nav.setNavigationRoutes(value)
|
|
130
206
|
|
|
131
|
-
|
|
207
|
+
// Issue #43 fix: safe camera transition
|
|
208
|
+
safeCameraFollowing(nav)
|
|
209
|
+
}
|
|
132
210
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
211
|
+
override fun accept(error: Exception) {
|
|
212
|
+
Log.e(TAG, "Route request failed: ${error.message}")
|
|
213
|
+
sendEvent("onRoutesFailed", mapOf("message" to (error.message ?: "Unknown error")))
|
|
214
|
+
}
|
|
215
|
+
})
|
|
138
216
|
}
|
|
139
217
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
.language(resolvedLocale.language)
|
|
144
|
-
.coordinatesList(listOf(origin) + waypoints + listOf(destination))
|
|
218
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
219
|
+
// Prop setters
|
|
220
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
145
221
|
|
|
146
|
-
|
|
147
|
-
|
|
222
|
+
fun setCoordinates(coords: List<Map<String, Double>>) {
|
|
223
|
+
coordinates = coords
|
|
224
|
+
if (coords.size >= 2) fetchAndStartRoutes()
|
|
148
225
|
}
|
|
149
226
|
|
|
150
|
-
|
|
151
|
-
|
|
227
|
+
fun setWaypointIndices(indices: List<Int>?) {
|
|
228
|
+
waypointIndices = indices
|
|
152
229
|
}
|
|
153
230
|
|
|
154
|
-
|
|
155
|
-
|
|
231
|
+
fun setLanguage(lang: String?) {
|
|
232
|
+
language = lang
|
|
233
|
+
}
|
|
156
234
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
onRoutesLoaded.emit(emptyMap<String, Any>())
|
|
162
|
-
MapboxNavigationApp.current()?.setNavigationRoutes(routes)
|
|
163
|
-
navView.api.startActiveGuidance(routes)
|
|
164
|
-
}
|
|
235
|
+
/** Issue #31 — Set voice/distance units: "metric", "imperial", or null for auto */
|
|
236
|
+
fun setVoiceUnits(units: String?) {
|
|
237
|
+
voiceUnits = units
|
|
238
|
+
}
|
|
165
239
|
|
|
166
|
-
|
|
167
|
-
|
|
240
|
+
fun setNavigationProfile(profile: String?) {
|
|
241
|
+
navigationProfile = profile
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
fun setExcludeTypes(types: List<String>?) {
|
|
245
|
+
excludeTypes = types
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
fun setMapStyle(style: String?) {
|
|
249
|
+
mapStyle = style
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
fun setMute(shouldMute: Boolean) {
|
|
253
|
+
mute = shouldMute
|
|
254
|
+
navigationView?.customizeViewStyles {
|
|
255
|
+
// Audio mute is handled by the NavigationView API
|
|
168
256
|
}
|
|
257
|
+
}
|
|
169
258
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
259
|
+
fun setMaxHeight(height: Double?) {
|
|
260
|
+
maxHeight = height
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
fun setMaxWidth(width: Double?) {
|
|
264
|
+
maxWidth = width
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
fun setUseMapMatching(use: Boolean) {
|
|
268
|
+
useMapMatching = use
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
fun setCustomRasterTileUrl(url: String?) {
|
|
272
|
+
customRasterTileUrl = url
|
|
273
|
+
}
|
|
173
274
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
onCancelNavigation.emit(emptyMap<String, Any>())
|
|
275
|
+
fun setCustomRasterAboveLayerId(layerId: String?) {
|
|
276
|
+
customRasterAboveLayerId = layerId
|
|
177
277
|
}
|
|
178
|
-
}
|
|
179
278
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
279
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
280
|
+
// Helpers
|
|
281
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
282
|
+
|
|
283
|
+
private fun getAccessToken(): String? {
|
|
284
|
+
return try {
|
|
285
|
+
val resId = context.resources.getIdentifier(
|
|
286
|
+
"mapbox_access_token", "string", context.packageName
|
|
287
|
+
)
|
|
288
|
+
if (resId != 0) context.getString(resId) else null
|
|
289
|
+
} catch (e: Exception) {
|
|
290
|
+
null
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private fun sendEvent(name: String, data: Map<String, Any>) {
|
|
295
|
+
appContext.eventEmitter?.emit(name, data)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
override fun onDetachedFromWindow() {
|
|
299
|
+
super.onDetachedFromWindow()
|
|
300
|
+
mapboxNavigation?.onDestroy()
|
|
301
|
+
}
|
|
184
302
|
}
|