@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,296 @@
|
|
|
1
|
+
package expo.modules.mapboxnavigation
|
|
2
|
+
|
|
3
|
+
import android.content.Intent
|
|
4
|
+
import expo.modules.kotlin.Promise
|
|
5
|
+
import expo.modules.kotlin.modules.Module
|
|
6
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
7
|
+
|
|
8
|
+
class MapboxNavigationModule : Module() {
|
|
9
|
+
private var isNavigating = false
|
|
10
|
+
|
|
11
|
+
override fun definition() = ModuleDefinition {
|
|
12
|
+
Name("MapboxNavigationModule")
|
|
13
|
+
|
|
14
|
+
Events(
|
|
15
|
+
"onLocationChange",
|
|
16
|
+
"onRouteProgressChange",
|
|
17
|
+
"onBannerInstruction",
|
|
18
|
+
"onArrive",
|
|
19
|
+
"onCancelNavigation",
|
|
20
|
+
"onError"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
OnCreate {
|
|
24
|
+
MapboxNavigationEventBridge.setEmitter { eventName, payload ->
|
|
25
|
+
sendEvent(eventName, payload)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
OnDestroy {
|
|
30
|
+
MapboxNavigationEventBridge.clearEmitter()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
AsyncFunction("startNavigation") { options: Map<String, Any?>, promise: Promise ->
|
|
34
|
+
startNavigation(options, promise)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
AsyncFunction("stopNavigation") { promise: Promise ->
|
|
38
|
+
stopNavigation(promise)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
AsyncFunction("setMuted") { _: Boolean, promise: Promise ->
|
|
42
|
+
// Voice control is handled by native navigation UI state.
|
|
43
|
+
promise.resolve(null)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
AsyncFunction("setVoiceVolume") { _: Double, promise: Promise ->
|
|
47
|
+
promise.resolve(null)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
AsyncFunction("setDistanceUnit") { _: String, promise: Promise ->
|
|
51
|
+
promise.resolve(null)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
AsyncFunction("setLanguage") { _: String, promise: Promise ->
|
|
55
|
+
promise.resolve(null)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
AsyncFunction("isNavigating") { promise: Promise ->
|
|
59
|
+
promise.resolve(isNavigating)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
AsyncFunction("getNavigationSettings") { promise: Promise ->
|
|
63
|
+
promise.resolve(
|
|
64
|
+
mapOf(
|
|
65
|
+
"isNavigating" to isNavigating,
|
|
66
|
+
"mute" to false,
|
|
67
|
+
"voiceVolume" to 1.0,
|
|
68
|
+
"distanceUnit" to "metric",
|
|
69
|
+
"language" to "en"
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
View(MapboxNavigationView::class) {
|
|
75
|
+
Events(
|
|
76
|
+
"onLocationChange",
|
|
77
|
+
"onRouteProgressChange",
|
|
78
|
+
"onBannerInstruction",
|
|
79
|
+
"onArrive",
|
|
80
|
+
"onCancelNavigation",
|
|
81
|
+
"onError"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
Prop("startOrigin") { view: MapboxNavigationView, origin: Map<String, Double> ->
|
|
85
|
+
view.setStartOrigin(origin)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
Prop("destination") { view: MapboxNavigationView, destination: Map<String, Any> ->
|
|
89
|
+
view.setDestination(destination)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
Prop("waypoints") { view: MapboxNavigationView, waypoints: List<Map<String, Any>>? ->
|
|
93
|
+
view.setWaypoints(waypoints)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
Prop("shouldSimulateRoute") { view: MapboxNavigationView, simulate: Boolean ->
|
|
97
|
+
view.setShouldSimulateRoute(simulate)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
Prop("showCancelButton") { view: MapboxNavigationView, show: Boolean ->
|
|
101
|
+
view.setShowCancelButton(show)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
Prop("mute") { view: MapboxNavigationView, mute: Boolean ->
|
|
105
|
+
view.setMute(mute)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
Prop("voiceVolume") { view: MapboxNavigationView, volume: Double ->
|
|
109
|
+
view.setVoiceVolume(volume)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
Prop("cameraPitch") { view: MapboxNavigationView, pitch: Double ->
|
|
113
|
+
view.setCameraPitch(pitch)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
Prop("cameraZoom") { view: MapboxNavigationView, zoom: Double ->
|
|
117
|
+
view.setCameraZoom(zoom)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
Prop("cameraMode") { view: MapboxNavigationView, mode: String ->
|
|
121
|
+
view.setCameraMode(mode)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
Prop("mapStyleUri") { view: MapboxNavigationView, styleUri: String ->
|
|
125
|
+
view.setMapStyleUri(styleUri)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
Prop("mapStyleUriDay") { view: MapboxNavigationView, styleUri: String ->
|
|
129
|
+
view.setMapStyleUriDay(styleUri)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
Prop("mapStyleUriNight") { view: MapboxNavigationView, styleUri: String ->
|
|
133
|
+
view.setMapStyleUriNight(styleUri)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
Prop("uiTheme") { view: MapboxNavigationView, theme: String ->
|
|
137
|
+
view.setUiTheme(theme)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
Prop("routeAlternatives") { view: MapboxNavigationView, routeAlternatives: Boolean ->
|
|
141
|
+
view.setRouteAlternatives(routeAlternatives)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
Prop("showsSpeedLimits") { view: MapboxNavigationView, showsSpeedLimits: Boolean ->
|
|
145
|
+
view.setShowsSpeedLimits(showsSpeedLimits)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
Prop("showsWayNameLabel") { view: MapboxNavigationView, showsWayNameLabel: Boolean ->
|
|
149
|
+
view.setShowsWayNameLabel(showsWayNameLabel)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
Prop("showsTripProgress") { view: MapboxNavigationView, showsTripProgress: Boolean ->
|
|
153
|
+
view.setShowsTripProgress(showsTripProgress)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
Prop("showsManeuverView") { view: MapboxNavigationView, showsManeuverView: Boolean ->
|
|
157
|
+
view.setShowsManeuverView(showsManeuverView)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
Prop("showsActionButtons") { view: MapboxNavigationView, showsActionButtons: Boolean ->
|
|
161
|
+
view.setShowsActionButtons(showsActionButtons)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
Prop("distanceUnit") { view: MapboxNavigationView, unit: String ->
|
|
165
|
+
view.setDistanceUnit(unit)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
Prop("language") { view: MapboxNavigationView, language: String ->
|
|
169
|
+
view.setLanguage(language)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private fun startNavigation(options: Map<String, Any?>, promise: Promise) {
|
|
175
|
+
val activity = appContext.currentActivity
|
|
176
|
+
if (activity == null) {
|
|
177
|
+
promise.reject("NO_ACTIVITY", "No current activity", null)
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
val origin = options["startOrigin"] as? Map<*, *>
|
|
182
|
+
val destination = options["destination"] as? Map<*, *>
|
|
183
|
+
|
|
184
|
+
val originLat = (origin?.get("latitude") as? Number)?.toDouble()
|
|
185
|
+
val originLng = (origin?.get("longitude") as? Number)?.toDouble()
|
|
186
|
+
val destLat = (destination?.get("latitude") as? Number)?.toDouble()
|
|
187
|
+
val destLng = (destination?.get("longitude") as? Number)?.toDouble()
|
|
188
|
+
|
|
189
|
+
if (destLat == null || destLng == null) {
|
|
190
|
+
promise.reject("INVALID_COORDINATES", "Missing or invalid coordinates", null)
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
val shouldSimulate = (options["shouldSimulateRoute"] as? Boolean) ?: false
|
|
195
|
+
val mute = (options["mute"] as? Boolean) ?: false
|
|
196
|
+
val cameraPitch = (options["cameraPitch"] as? Number)?.toDouble()
|
|
197
|
+
val cameraZoom = (options["cameraZoom"] as? Number)?.toDouble()
|
|
198
|
+
val cameraMode = (options["cameraMode"] as? String) ?: "following"
|
|
199
|
+
val mapStyleUri = (options["mapStyleUri"] as? String) ?: ""
|
|
200
|
+
val mapStyleUriDay = (options["mapStyleUriDay"] as? String) ?: ""
|
|
201
|
+
val mapStyleUriNight = (options["mapStyleUriNight"] as? String) ?: ""
|
|
202
|
+
val uiTheme = (options["uiTheme"] as? String) ?: "system"
|
|
203
|
+
val routeAlternatives = (options["routeAlternatives"] as? Boolean) ?: false
|
|
204
|
+
val showsSpeedLimits = (options["showsSpeedLimits"] as? Boolean) ?: true
|
|
205
|
+
val showsWayNameLabel = (options["showsWayNameLabel"] as? Boolean) ?: true
|
|
206
|
+
val showsTripProgress = (options["showsTripProgress"] as? Boolean) ?: true
|
|
207
|
+
val showsManeuverView = (options["showsManeuverView"] as? Boolean) ?: true
|
|
208
|
+
val showsActionButtons = (options["showsActionButtons"] as? Boolean) ?: true
|
|
209
|
+
val waypoints = parseCoordinatesList(options["waypoints"] as? List<*>)
|
|
210
|
+
|
|
211
|
+
activity.runOnUiThread {
|
|
212
|
+
val accessToken = try {
|
|
213
|
+
getMapboxAccessToken(activity.packageName)
|
|
214
|
+
} catch (e: IllegalStateException) {
|
|
215
|
+
promise.reject("MISSING_ACCESS_TOKEN", e.message ?: "Missing mapbox_access_token", e)
|
|
216
|
+
return@runOnUiThread
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
val intent = Intent(activity, MapboxNavigationActivity::class.java).apply {
|
|
220
|
+
putExtra("accessToken", accessToken)
|
|
221
|
+
if (originLat != null && originLng != null) {
|
|
222
|
+
putExtra("originLat", originLat)
|
|
223
|
+
putExtra("originLng", originLng)
|
|
224
|
+
}
|
|
225
|
+
putExtra("destLat", destLat)
|
|
226
|
+
putExtra("destLng", destLng)
|
|
227
|
+
putExtra("shouldSimulate", shouldSimulate)
|
|
228
|
+
putExtra("mute", mute)
|
|
229
|
+
putExtra("cameraPitch", cameraPitch)
|
|
230
|
+
putExtra("cameraZoom", cameraZoom)
|
|
231
|
+
putExtra("cameraMode", cameraMode)
|
|
232
|
+
putExtra("mapStyleUri", mapStyleUri)
|
|
233
|
+
putExtra("mapStyleUriDay", mapStyleUriDay)
|
|
234
|
+
putExtra("mapStyleUriNight", mapStyleUriNight)
|
|
235
|
+
putExtra("uiTheme", uiTheme)
|
|
236
|
+
putExtra("routeAlternatives", routeAlternatives)
|
|
237
|
+
putExtra("showsSpeedLimits", showsSpeedLimits)
|
|
238
|
+
putExtra("showsWayNameLabel", showsWayNameLabel)
|
|
239
|
+
putExtra("showsTripProgress", showsTripProgress)
|
|
240
|
+
putExtra("showsManeuverView", showsManeuverView)
|
|
241
|
+
putExtra("showsActionButtons", showsActionButtons)
|
|
242
|
+
if (waypoints.isNotEmpty()) {
|
|
243
|
+
putExtra("waypointLats", waypoints.map { it.first }.toDoubleArray())
|
|
244
|
+
putExtra("waypointLngs", waypoints.map { it.second }.toDoubleArray())
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
activity.startActivity(intent)
|
|
249
|
+
isNavigating = true
|
|
250
|
+
promise.resolve(null)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private fun getMapboxAccessToken(packageName: String): String {
|
|
255
|
+
val context = appContext.reactContext ?: throw IllegalStateException("Missing React context")
|
|
256
|
+
val resourceId = context.resources.getIdentifier(
|
|
257
|
+
"mapbox_access_token",
|
|
258
|
+
"string",
|
|
259
|
+
packageName
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
if (resourceId == 0) {
|
|
263
|
+
throw IllegalStateException("Missing string resource: mapbox_access_token")
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
val token = context.getString(resourceId).trim()
|
|
267
|
+
if (token.isEmpty()) {
|
|
268
|
+
throw IllegalStateException("mapbox_access_token is empty")
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return token
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private fun stopNavigation(promise: Promise) {
|
|
275
|
+
val activity = appContext.currentActivity
|
|
276
|
+
activity?.finish()
|
|
277
|
+
isNavigating = false
|
|
278
|
+
promise.resolve(null)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private fun parseCoordinatesList(value: List<*>?): List<Pair<Double, Double>> {
|
|
282
|
+
if (value.isNullOrEmpty()) {
|
|
283
|
+
return emptyList()
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return value.mapNotNull { item ->
|
|
287
|
+
val map = item as? Map<*, *> ?: return@mapNotNull null
|
|
288
|
+
val latitude = (map["latitude"] as? Number)?.toDouble() ?: return@mapNotNull null
|
|
289
|
+
val longitude = (map["longitude"] as? Number)?.toDouble() ?: return@mapNotNull null
|
|
290
|
+
if (latitude < -90.0 || latitude > 90.0 || longitude < -180.0 || longitude > 180.0) {
|
|
291
|
+
return@mapNotNull null
|
|
292
|
+
}
|
|
293
|
+
latitude to longitude
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
package expo.modules.mapboxnavigation
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.view.Gravity
|
|
5
|
+
import android.widget.FrameLayout
|
|
6
|
+
import android.widget.TextView
|
|
7
|
+
import expo.modules.kotlin.AppContext
|
|
8
|
+
import expo.modules.kotlin.viewevent.EventDispatcher
|
|
9
|
+
import expo.modules.kotlin.views.ExpoView
|
|
10
|
+
|
|
11
|
+
class MapboxNavigationView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
|
|
12
|
+
private var startOrigin: Map<String, Double>? = null
|
|
13
|
+
private var destination: Map<String, Any>? = null
|
|
14
|
+
private var waypoints: List<Map<String, Any>>? = null
|
|
15
|
+
private var shouldSimulateRoute = false
|
|
16
|
+
private var showCancelButton = true
|
|
17
|
+
private var mute = false
|
|
18
|
+
private var voiceVolume = 1.0
|
|
19
|
+
private var cameraPitch = 0.0
|
|
20
|
+
private var cameraZoom = 14.0
|
|
21
|
+
private var cameraMode = "following"
|
|
22
|
+
private var mapStyleUri = ""
|
|
23
|
+
private var mapStyleUriDay = ""
|
|
24
|
+
private var mapStyleUriNight = ""
|
|
25
|
+
private var uiTheme = "system"
|
|
26
|
+
private var routeAlternatives = false
|
|
27
|
+
private var showsSpeedLimits = true
|
|
28
|
+
private var showsWayNameLabel = true
|
|
29
|
+
private var showsTripProgress = true
|
|
30
|
+
private var showsManeuverView = true
|
|
31
|
+
private var showsActionButtons = true
|
|
32
|
+
private var distanceUnit = "metric"
|
|
33
|
+
private var language = "en"
|
|
34
|
+
|
|
35
|
+
val onLocationChange by EventDispatcher()
|
|
36
|
+
val onRouteProgressChange by EventDispatcher()
|
|
37
|
+
val onBannerInstruction by EventDispatcher()
|
|
38
|
+
val onArrive by EventDispatcher()
|
|
39
|
+
val onCancelNavigation by EventDispatcher()
|
|
40
|
+
val onError by EventDispatcher()
|
|
41
|
+
|
|
42
|
+
init {
|
|
43
|
+
val label = TextView(context).apply {
|
|
44
|
+
text = "Mapbox native navigation view is initializing..."
|
|
45
|
+
gravity = Gravity.CENTER
|
|
46
|
+
}
|
|
47
|
+
addView(
|
|
48
|
+
label,
|
|
49
|
+
FrameLayout.LayoutParams(
|
|
50
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
51
|
+
FrameLayout.LayoutParams.MATCH_PARENT
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fun setStartOrigin(origin: Map<String, Double>) {
|
|
57
|
+
startOrigin = origin
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
fun setDestination(dest: Map<String, Any>) {
|
|
61
|
+
destination = dest
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fun setWaypoints(wps: List<Map<String, Any>>?) {
|
|
65
|
+
waypoints = wps
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
fun setShouldSimulateRoute(simulate: Boolean) {
|
|
69
|
+
shouldSimulateRoute = simulate
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fun setShowCancelButton(show: Boolean) {
|
|
73
|
+
showCancelButton = show
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fun setMute(muted: Boolean) {
|
|
77
|
+
mute = muted
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fun setVoiceVolume(volume: Double) {
|
|
81
|
+
voiceVolume = volume
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
fun setCameraPitch(pitch: Double) {
|
|
85
|
+
cameraPitch = pitch
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
fun setCameraZoom(zoom: Double) {
|
|
89
|
+
cameraZoom = zoom
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
fun setCameraMode(mode: String) {
|
|
93
|
+
cameraMode = mode
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fun setMapStyleUri(styleUri: String) {
|
|
97
|
+
mapStyleUri = styleUri
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
fun setMapStyleUriDay(styleUri: String) {
|
|
101
|
+
mapStyleUriDay = styleUri
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
fun setMapStyleUriNight(styleUri: String) {
|
|
105
|
+
mapStyleUriNight = styleUri
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
fun setUiTheme(theme: String) {
|
|
109
|
+
uiTheme = theme
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fun setRouteAlternatives(enabled: Boolean) {
|
|
113
|
+
routeAlternatives = enabled
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
fun setShowsSpeedLimits(enabled: Boolean) {
|
|
117
|
+
showsSpeedLimits = enabled
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
fun setShowsWayNameLabel(enabled: Boolean) {
|
|
121
|
+
showsWayNameLabel = enabled
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
fun setShowsTripProgress(enabled: Boolean) {
|
|
125
|
+
showsTripProgress = enabled
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
fun setShowsManeuverView(enabled: Boolean) {
|
|
129
|
+
showsManeuverView = enabled
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
fun setShowsActionButtons(enabled: Boolean) {
|
|
133
|
+
showsActionButtons = enabled
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
fun setDistanceUnit(unit: String) {
|
|
137
|
+
distanceUnit = unit
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
fun setLanguage(lang: String) {
|
|
141
|
+
language = lang
|
|
142
|
+
}
|
|
143
|
+
}
|
package/app.plugin.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
const {
|
|
2
|
+
withProjectBuildGradle,
|
|
3
|
+
withAppBuildGradle,
|
|
4
|
+
withAndroidManifest,
|
|
5
|
+
withInfoPlist,
|
|
6
|
+
createRunOncePlugin,
|
|
7
|
+
} = require("@expo/config-plugins");
|
|
8
|
+
|
|
9
|
+
const MAPBOX_REPO_BLOCK = ` maven {
|
|
10
|
+
url 'https://api.mapbox.com/downloads/v2/releases/maven'
|
|
11
|
+
authentication {
|
|
12
|
+
basic(BasicAuthentication)
|
|
13
|
+
}
|
|
14
|
+
credentials {
|
|
15
|
+
username = "mapbox"
|
|
16
|
+
password = mapboxDownloadsToken
|
|
17
|
+
}
|
|
18
|
+
}`;
|
|
19
|
+
|
|
20
|
+
const MAPBOX_TOKEN_LINES = ` def mapboxPublicToken = project.findProperty("MAPBOX_PUBLIC_TOKEN") ?: System.getenv("EXPO_PUBLIC_MAPBOX_ACCESS_TOKEN") ?: ""
|
|
21
|
+
resValue "string", "mapbox_access_token", mapboxPublicToken`;
|
|
22
|
+
|
|
23
|
+
const REQUIRED_ANDROID_PERMISSIONS = [
|
|
24
|
+
"android.permission.ACCESS_COARSE_LOCATION",
|
|
25
|
+
"android.permission.ACCESS_FINE_LOCATION",
|
|
26
|
+
"android.permission.ACCESS_BACKGROUND_LOCATION",
|
|
27
|
+
"android.permission.FOREGROUND_SERVICE",
|
|
28
|
+
"android.permission.FOREGROUND_SERVICE_LOCATION",
|
|
29
|
+
"android.permission.POST_NOTIFICATIONS",
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const DEFAULT_IOS_LOCATION_USAGE =
|
|
33
|
+
"Allow $(PRODUCT_NAME) to access your location for turn-by-turn navigation.";
|
|
34
|
+
|
|
35
|
+
function ensureAndroidPermissions(androidManifest) {
|
|
36
|
+
const manifest = androidManifest.manifest;
|
|
37
|
+
if (!manifest["uses-permission"]) {
|
|
38
|
+
manifest["uses-permission"] = [];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const existingPermissions = new Set(
|
|
42
|
+
manifest["uses-permission"]
|
|
43
|
+
.map((entry) => entry?.$?.["android:name"])
|
|
44
|
+
.filter(Boolean)
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
REQUIRED_ANDROID_PERMISSIONS.forEach((permission) => {
|
|
48
|
+
if (!existingPermissions.has(permission)) {
|
|
49
|
+
manifest["uses-permission"].push({
|
|
50
|
+
$: {
|
|
51
|
+
"android:name": permission,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return androidManifest;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function ensureProjectBuildGradle(src) {
|
|
61
|
+
let out = src;
|
|
62
|
+
|
|
63
|
+
if (!out.includes("def mapboxDownloadsToken =")) {
|
|
64
|
+
out =
|
|
65
|
+
`def mapboxDownloadsToken = (findProperty("MAPBOX_DOWNLOADS_TOKEN") ?: System.getenv("MAPBOX_DOWNLOADS_TOKEN") ?: "")\n` +
|
|
66
|
+
` .toString()\n` +
|
|
67
|
+
` .replace('"', '')\n` +
|
|
68
|
+
` .trim()\n\n` +
|
|
69
|
+
out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!out.includes("https://api.mapbox.com/downloads/v2/releases/maven")) {
|
|
73
|
+
out = out.replace(
|
|
74
|
+
/allprojects\s*\{\s*repositories\s*\{\s*google\(\)\s*mavenCentral\(\)/m,
|
|
75
|
+
(match) => `${match}\n${MAPBOX_REPO_BLOCK}`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function ensureAppBuildGradle(src) {
|
|
83
|
+
if (src.includes('resValue "string", "mapbox_access_token"')) {
|
|
84
|
+
return src;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return src.replace(
|
|
88
|
+
/(versionName\s+"[^"]+"\s*\n)/m,
|
|
89
|
+
`$1${MAPBOX_TOKEN_LINES}\n`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function withMapboxNavigationAndroid(config) {
|
|
94
|
+
config = withProjectBuildGradle(config, (config) => {
|
|
95
|
+
config.modResults.contents = ensureProjectBuildGradle(
|
|
96
|
+
config.modResults.contents
|
|
97
|
+
);
|
|
98
|
+
return config;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
config = withAppBuildGradle(config, (config) => {
|
|
102
|
+
config.modResults.contents = ensureAppBuildGradle(config.modResults.contents);
|
|
103
|
+
return config;
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
config = withAndroidManifest(config, (config) => {
|
|
107
|
+
config.modResults = ensureAndroidPermissions(config.modResults);
|
|
108
|
+
return config;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return config;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function withMapboxNavigationIos(config) {
|
|
115
|
+
return withInfoPlist(config, (config) => {
|
|
116
|
+
const infoPlist = config.modResults;
|
|
117
|
+
const mapboxPublicToken =
|
|
118
|
+
process.env.EXPO_PUBLIC_MAPBOX_ACCESS_TOKEN ||
|
|
119
|
+
process.env.MAPBOX_PUBLIC_TOKEN ||
|
|
120
|
+
"";
|
|
121
|
+
|
|
122
|
+
if (!infoPlist.MBXAccessToken && mapboxPublicToken) {
|
|
123
|
+
infoPlist.MBXAccessToken = mapboxPublicToken;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!infoPlist.NSLocationWhenInUseUsageDescription) {
|
|
127
|
+
infoPlist.NSLocationWhenInUseUsageDescription = DEFAULT_IOS_LOCATION_USAGE;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!infoPlist.NSLocationAlwaysAndWhenInUseUsageDescription) {
|
|
131
|
+
infoPlist.NSLocationAlwaysAndWhenInUseUsageDescription = DEFAULT_IOS_LOCATION_USAGE;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const existingModes = Array.isArray(infoPlist.UIBackgroundModes)
|
|
135
|
+
? infoPlist.UIBackgroundModes
|
|
136
|
+
: [];
|
|
137
|
+
const mergedModes = new Set([...existingModes, "location", "audio"]);
|
|
138
|
+
infoPlist.UIBackgroundModes = Array.from(mergedModes);
|
|
139
|
+
|
|
140
|
+
return config;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const withMapboxNavigation = (config) => {
|
|
145
|
+
config = withMapboxNavigationAndroid(config);
|
|
146
|
+
config = withMapboxNavigationIos(config);
|
|
147
|
+
return config;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
module.exports = createRunOncePlugin(
|
|
151
|
+
withMapboxNavigation,
|
|
152
|
+
"react-native-mapbox-navigation-plugin",
|
|
153
|
+
"1.1.0"
|
|
154
|
+
);
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Publishing & CI/CD Guide
|
|
2
|
+
|
|
3
|
+
## 1. Create the package repository
|
|
4
|
+
|
|
5
|
+
Recommended repo name: `react-native-mapbox-navigation`.
|
|
6
|
+
|
|
7
|
+
Use this module as the repository root (do not keep it under `modules/` after copying).
|
|
8
|
+
|
|
9
|
+
Required top-level items:
|
|
10
|
+
- `src/`, `android/`, `ios/`
|
|
11
|
+
- `app.plugin.js`, `expo-module.config.json`, `package.json`
|
|
12
|
+
- `README.md`, `QUICKSTART.md`, `CHANGELOG.md`, `docs/`
|
|
13
|
+
- `.github/workflows/`
|
|
14
|
+
- `website/` (GitHub Pages)
|
|
15
|
+
|
|
16
|
+
## 2. Prepare GitHub repo
|
|
17
|
+
|
|
18
|
+
1. Create GitHub repo `react-native-mapbox-navigation`.
|
|
19
|
+
2. Copy this folder contents into that repo root.
|
|
20
|
+
3. Push initial commit:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
git init
|
|
24
|
+
git add .
|
|
25
|
+
git commit -m "chore: initial release-ready package"
|
|
26
|
+
git branch -M main
|
|
27
|
+
git remote add origin git@github.com:<your-org-or-user>/react-native-mapbox-navigation.git
|
|
28
|
+
git push -u origin main
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 3. Enable CI
|
|
32
|
+
|
|
33
|
+
This package includes workflow templates you can use directly:
|
|
34
|
+
- `.github/workflows/ci.yml`
|
|
35
|
+
- `.github/workflows/deploy-pages.yml`
|
|
36
|
+
|
|
37
|
+
`ci.yml` validates:
|
|
38
|
+
- TypeScript check (`npx tsc --noEmit`)
|
|
39
|
+
- Android module compile (`./gradlew ...compileDebugKotlin`)
|
|
40
|
+
- npm tarball dry-run (`npm pack --dry-run`)
|
|
41
|
+
|
|
42
|
+
## 4. Enable GitHub Pages
|
|
43
|
+
|
|
44
|
+
1. In GitHub repo: `Settings -> Pages`.
|
|
45
|
+
2. Source: `GitHub Actions`.
|
|
46
|
+
3. Keep `website/` as docs source for deployed site.
|
|
47
|
+
4. Push to `main`; workflow will publish automatically.
|
|
48
|
+
|
|
49
|
+
## 5. Local release verification
|
|
50
|
+
|
|
51
|
+
From package root:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm run verify
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This runs:
|
|
58
|
+
1. TypeScript check
|
|
59
|
+
2. Android compile check
|
|
60
|
+
3. `npm pack --dry-run`
|
|
61
|
+
|
|
62
|
+
## 6. Test before npm publish
|
|
63
|
+
|
|
64
|
+
### Pack locally
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npm pack --cache /tmp/npm-cache-react-native-mapbox-navigation
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Install in a clean test app
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npm install /absolute/path/to/react-native-mapbox-navigation-<version>.tgz
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Then validate both platforms:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npx expo prebuild --clean
|
|
80
|
+
npx expo run:android
|
|
81
|
+
npx expo run:ios
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 7. Publish steps (after testing)
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npm version patch # or minor / major
|
|
88
|
+
git push --follow-tags
|
|
89
|
+
npm publish --access public
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## 8. Recommended production setup
|
|
93
|
+
|
|
94
|
+
- Protect `main` and require PR checks.
|
|
95
|
+
- Enable npm trusted publishing from GitHub Actions.
|
|
96
|
+
- Create GitHub Releases for each version tag.
|
|
97
|
+
- Keep `CHANGELOG.md` updated per release.
|