@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,298 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import MapboxNavigation
|
|
3
|
+
import MapboxDirections
|
|
4
|
+
import MapboxCoreNavigation
|
|
5
|
+
import CoreLocation
|
|
6
|
+
import UIKit
|
|
7
|
+
|
|
8
|
+
class MapboxNavigationView: ExpoView {
|
|
9
|
+
var startOrigin: [String: Double]? {
|
|
10
|
+
didSet { startNavigationIfReady() }
|
|
11
|
+
}
|
|
12
|
+
var destination: [String: Any]? {
|
|
13
|
+
didSet { startNavigationIfReady() }
|
|
14
|
+
}
|
|
15
|
+
var waypoints: [[String: Any]]? {
|
|
16
|
+
didSet { startNavigationIfReady() }
|
|
17
|
+
}
|
|
18
|
+
var shouldSimulateRoute: Bool = false {
|
|
19
|
+
didSet { startNavigationIfReady() }
|
|
20
|
+
}
|
|
21
|
+
var showCancelButton: Bool = true
|
|
22
|
+
var mute: Bool = false
|
|
23
|
+
var voiceVolume: Double = 1
|
|
24
|
+
var cameraPitch: Double?
|
|
25
|
+
var cameraZoom: Double?
|
|
26
|
+
var cameraMode: String = "following"
|
|
27
|
+
var mapStyleUri: String?
|
|
28
|
+
var routeAlternatives: Bool = false
|
|
29
|
+
var showsSpeedLimits: Bool = true
|
|
30
|
+
var showsWayNameLabel: Bool = true
|
|
31
|
+
var distanceUnit: String = "metric"
|
|
32
|
+
var language: String = "en"
|
|
33
|
+
|
|
34
|
+
private var navigationViewController: NavigationViewController?
|
|
35
|
+
private var hostViewController: UIViewController?
|
|
36
|
+
private var isRouteCalculationInProgress = false
|
|
37
|
+
|
|
38
|
+
let onLocationChange = EventDispatcher()
|
|
39
|
+
let onRouteProgressChange = EventDispatcher()
|
|
40
|
+
let onBannerInstruction = EventDispatcher()
|
|
41
|
+
let onArrive = EventDispatcher()
|
|
42
|
+
let onCancelNavigation = EventDispatcher()
|
|
43
|
+
let onError = EventDispatcher()
|
|
44
|
+
|
|
45
|
+
required init(appContext: AppContext? = nil) {
|
|
46
|
+
super.init(appContext: appContext)
|
|
47
|
+
setupView()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private func setupView() {
|
|
51
|
+
backgroundColor = .black
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
override func didMoveToWindow() {
|
|
55
|
+
super.didMoveToWindow()
|
|
56
|
+
|
|
57
|
+
if window != nil {
|
|
58
|
+
startNavigationIfReady()
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private func startNavigationIfReady() {
|
|
63
|
+
guard navigationViewController == nil else {
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
guard !isRouteCalculationInProgress else {
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
guard let origin = startOrigin,
|
|
70
|
+
let dest = destination,
|
|
71
|
+
let originLat = origin["latitude"],
|
|
72
|
+
let originLng = origin["longitude"],
|
|
73
|
+
let destLat = dest["latitude"] as? Double,
|
|
74
|
+
let destLng = dest["longitude"] as? Double else {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let originCoord = CLLocationCoordinate2D(latitude: originLat, longitude: originLng)
|
|
79
|
+
let destCoord = CLLocationCoordinate2D(latitude: destLat, longitude: destLng)
|
|
80
|
+
|
|
81
|
+
var waypointsList = [Waypoint(coordinate: originCoord)]
|
|
82
|
+
|
|
83
|
+
// Add intermediate waypoints
|
|
84
|
+
if let intermediateWaypoints = waypoints {
|
|
85
|
+
for wp in intermediateWaypoints {
|
|
86
|
+
if let lat = wp["latitude"] as? Double,
|
|
87
|
+
let lng = wp["longitude"] as? Double {
|
|
88
|
+
let coord = CLLocationCoordinate2D(latitude: lat, longitude: lng)
|
|
89
|
+
let waypoint = Waypoint(coordinate: coord)
|
|
90
|
+
waypoint.name = wp["name"] as? String
|
|
91
|
+
waypointsList.append(waypoint)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Add final destination
|
|
97
|
+
let finalWaypoint = Waypoint(coordinate: destCoord)
|
|
98
|
+
finalWaypoint.name = (dest["name"] as? String) ?? (dest["title"] as? String) ?? "Destination"
|
|
99
|
+
waypointsList.append(finalWaypoint)
|
|
100
|
+
|
|
101
|
+
let routeOptions = NavigationRouteOptions(waypoints: waypointsList)
|
|
102
|
+
routeOptions.locale = Locale(identifier: language)
|
|
103
|
+
routeOptions.distanceMeasurementSystem = distanceUnit == "imperial" ? .imperial : .metric
|
|
104
|
+
routeOptions.includesAlternativeRoutes = routeAlternatives
|
|
105
|
+
|
|
106
|
+
isRouteCalculationInProgress = true
|
|
107
|
+
Directions.shared.calculate(routeOptions) { [weak self] (_, result) in
|
|
108
|
+
guard let self = self else { return }
|
|
109
|
+
self.isRouteCalculationInProgress = false
|
|
110
|
+
|
|
111
|
+
switch result {
|
|
112
|
+
case .success(let response):
|
|
113
|
+
guard response.routes?.first != nil else {
|
|
114
|
+
self.onError([
|
|
115
|
+
"code": "NO_ROUTE",
|
|
116
|
+
"message": "No route found"
|
|
117
|
+
])
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
DispatchQueue.main.async {
|
|
122
|
+
self.embedNavigation(response: response, routeOptions: routeOptions)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
case .failure(let error):
|
|
126
|
+
self.onError([
|
|
127
|
+
"code": "ROUTE_ERROR",
|
|
128
|
+
"message": error.localizedDescription
|
|
129
|
+
])
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private func embedNavigation(response: RouteResponse, routeOptions: NavigationRouteOptions) {
|
|
135
|
+
let indexedRouteResponse = IndexedRouteResponse(routeResponse: response, routeIndex: 0)
|
|
136
|
+
let navigationService = MapboxNavigationService(
|
|
137
|
+
indexedRouteResponse: indexedRouteResponse,
|
|
138
|
+
credentials: Directions.shared.credentials,
|
|
139
|
+
simulating: shouldSimulateRoute ? .always : nil
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
let navigationOptions = buildNavigationOptions(navigationService: navigationService)
|
|
143
|
+
|
|
144
|
+
let viewController = NavigationViewController(
|
|
145
|
+
for: indexedRouteResponse,
|
|
146
|
+
navigationOptions: navigationOptions
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
viewController.delegate = self
|
|
150
|
+
|
|
151
|
+
NavigationSettings.shared.distanceUnit = distanceUnit == "imperial" ? .mile : .kilometer
|
|
152
|
+
NavigationSettings.shared.voiceMuted = mute
|
|
153
|
+
NavigationSettings.shared.voiceVolume = Float(max(0, min(voiceVolume, 1)))
|
|
154
|
+
viewController.showsSpeedLimits = showsSpeedLimits
|
|
155
|
+
applyCameraConfiguration(to: viewController)
|
|
156
|
+
|
|
157
|
+
// Find the parent view controller
|
|
158
|
+
var parentVC: UIViewController? = self.window?.rootViewController
|
|
159
|
+
while let presented = parentVC?.presentedViewController {
|
|
160
|
+
parentVC = presented
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if let parent = parentVC {
|
|
164
|
+
// Add as child view controller
|
|
165
|
+
parent.addChild(viewController)
|
|
166
|
+
addSubview(viewController.view)
|
|
167
|
+
viewController.view.frame = bounds
|
|
168
|
+
viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
169
|
+
viewController.didMove(toParent: parent)
|
|
170
|
+
|
|
171
|
+
navigationViewController = viewController
|
|
172
|
+
hostViewController = parent
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
override func layoutSubviews() {
|
|
177
|
+
super.layoutSubviews()
|
|
178
|
+
navigationViewController?.view.frame = bounds
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
deinit {
|
|
182
|
+
cleanupNavigation()
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private func cleanupNavigation() {
|
|
186
|
+
navigationViewController?.willMove(toParent: nil)
|
|
187
|
+
navigationViewController?.view.removeFromSuperview()
|
|
188
|
+
navigationViewController?.removeFromParent()
|
|
189
|
+
navigationViewController = nil
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private func applyCameraConfiguration(to viewController: NavigationViewController) {
|
|
193
|
+
guard
|
|
194
|
+
let navigationMapView = viewController.navigationMapView,
|
|
195
|
+
let viewportDataSource = navigationMapView.navigationCamera
|
|
196
|
+
.viewportDataSource as? NavigationViewportDataSource else {
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let normalizedMode = cameraMode.lowercased()
|
|
201
|
+
|
|
202
|
+
if normalizedMode == "overview" {
|
|
203
|
+
viewportDataSource.options.followingCameraOptions.zoomUpdatesAllowed = false
|
|
204
|
+
viewportDataSource.followingMobileCamera.zoom = CGFloat(cameraZoom ?? 10)
|
|
205
|
+
viewportDataSource.options.followingCameraOptions.pitchUpdatesAllowed = false
|
|
206
|
+
viewportDataSource.followingMobileCamera.pitch = 0
|
|
207
|
+
} else {
|
|
208
|
+
// Keep dynamic camera updates in following mode so turn-by-turn camera behavior
|
|
209
|
+
// (zoom/pitch/bearing adaptation) remains managed by the SDK.
|
|
210
|
+
viewportDataSource.options.followingCameraOptions.pitchUpdatesAllowed = true
|
|
211
|
+
viewportDataSource.options.followingCameraOptions.zoomUpdatesAllowed = true
|
|
212
|
+
viewportDataSource.options.followingCameraOptions.bearingUpdatesAllowed = true
|
|
213
|
+
|
|
214
|
+
if let pitch = cameraPitch {
|
|
215
|
+
viewportDataSource.followingMobileCamera.pitch = CGFloat(max(0, min(pitch, 85)))
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if let zoom = cameraZoom {
|
|
219
|
+
viewportDataSource.followingMobileCamera.zoom = CGFloat(max(1, min(zoom, 22)))
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
navigationMapView.navigationCamera.follow()
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private func buildNavigationOptions(navigationService: NavigationService) -> NavigationOptions {
|
|
227
|
+
guard
|
|
228
|
+
let styleUri = mapStyleUri?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
229
|
+
!styleUri.isEmpty,
|
|
230
|
+
let styleURL = URL(string: styleUri)
|
|
231
|
+
else {
|
|
232
|
+
return NavigationOptions(navigationService: navigationService)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let dayStyle = DayStyle()
|
|
236
|
+
dayStyle.mapStyleURL = styleURL
|
|
237
|
+
|
|
238
|
+
let nightStyle = NightStyle()
|
|
239
|
+
nightStyle.mapStyleURL = styleURL
|
|
240
|
+
|
|
241
|
+
return NavigationOptions(styles: [dayStyle, nightStyle], navigationService: navigationService)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// MARK: - NavigationViewControllerDelegate
|
|
246
|
+
extension MapboxNavigationView: NavigationViewControllerDelegate {
|
|
247
|
+
func navigationViewController(
|
|
248
|
+
_ navigationViewController: NavigationViewController,
|
|
249
|
+
didUpdate progress: RouteProgress,
|
|
250
|
+
with location: CLLocation,
|
|
251
|
+
rawLocation: CLLocation
|
|
252
|
+
) {
|
|
253
|
+
if cameraMode.lowercased() == "following" {
|
|
254
|
+
navigationViewController.navigationMapView?.navigationCamera.follow()
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
onLocationChange([
|
|
258
|
+
"latitude": location.coordinate.latitude,
|
|
259
|
+
"longitude": location.coordinate.longitude,
|
|
260
|
+
"bearing": location.course,
|
|
261
|
+
"speed": location.speed,
|
|
262
|
+
"altitude": location.altitude,
|
|
263
|
+
"accuracy": location.horizontalAccuracy
|
|
264
|
+
])
|
|
265
|
+
|
|
266
|
+
onRouteProgressChange([
|
|
267
|
+
"distanceTraveled": progress.distanceTraveled,
|
|
268
|
+
"distanceRemaining": progress.distanceRemaining,
|
|
269
|
+
"durationRemaining": progress.durationRemaining,
|
|
270
|
+
"fractionTraveled": progress.fractionTraveled
|
|
271
|
+
])
|
|
272
|
+
|
|
273
|
+
onBannerInstruction([
|
|
274
|
+
"primaryText": progress.currentLegProgress.currentStep.instructions,
|
|
275
|
+
"stepDistanceRemaining": progress.currentLegProgress.currentStepProgress.distanceRemaining
|
|
276
|
+
])
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
func navigationViewController(
|
|
280
|
+
_ navigationViewController: NavigationViewController,
|
|
281
|
+
didArriveAt waypoint: Waypoint
|
|
282
|
+
) -> Bool {
|
|
283
|
+
onArrive([
|
|
284
|
+
"name": waypoint.name ?? ""
|
|
285
|
+
])
|
|
286
|
+
return true
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
func navigationViewControllerDidDismiss(
|
|
290
|
+
_ navigationViewController: NavigationViewController,
|
|
291
|
+
byCanceling canceled: Bool
|
|
292
|
+
) {
|
|
293
|
+
if canceled {
|
|
294
|
+
onCancelNavigation([:])
|
|
295
|
+
}
|
|
296
|
+
cleanupNavigation()
|
|
297
|
+
}
|
|
298
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@atomiqlab/react-native-mapbox-navigation",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Native Mapbox turn-by-turn navigation for Expo and React Native (iOS + Android)",
|
|
5
|
+
"main": "src/index.tsx",
|
|
6
|
+
"types": "src/index.tsx",
|
|
7
|
+
"react-native": "src/index.tsx",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./src/index.tsx",
|
|
11
|
+
"default": "./src/index.tsx"
|
|
12
|
+
},
|
|
13
|
+
"./app.plugin.js": "./app.plugin.js",
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"files": [
|
|
18
|
+
"android/src",
|
|
19
|
+
"android/build.gradle",
|
|
20
|
+
"android/src/main/AndroidManifest.xml",
|
|
21
|
+
"ios/ExpoMapboxNavigationNative.podspec",
|
|
22
|
+
"ios/MapboxNavigationModule.swift",
|
|
23
|
+
"ios/MapboxNavigationView.swift",
|
|
24
|
+
"src",
|
|
25
|
+
"app.plugin.js",
|
|
26
|
+
"expo-module.config.json",
|
|
27
|
+
"README.md",
|
|
28
|
+
"QUICKSTART.md",
|
|
29
|
+
"CHANGELOG.md",
|
|
30
|
+
"docs",
|
|
31
|
+
"scripts"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "expo-module build",
|
|
35
|
+
"clean": "expo-module clean",
|
|
36
|
+
"lint": "expo-module lint",
|
|
37
|
+
"test": "expo-module test",
|
|
38
|
+
"verify": "node ./scripts/verify-release.mjs"
|
|
39
|
+
},
|
|
40
|
+
"keywords": [
|
|
41
|
+
"react-native",
|
|
42
|
+
"expo",
|
|
43
|
+
"mapbox",
|
|
44
|
+
"navigation",
|
|
45
|
+
"turn-by-turn"
|
|
46
|
+
],
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "https://github.com/ATOMIQTECH/react-native-mapbox-navigation"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://github.com/ATOMIQTECH/react-native-mapbox-navigation#readme",
|
|
53
|
+
"bugs": {
|
|
54
|
+
"url": "https://github.com/ATOMIQTECH/react-native-mapbox-navigation/issues"
|
|
55
|
+
},
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public"
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=18"
|
|
61
|
+
},
|
|
62
|
+
"expo": {
|
|
63
|
+
"plugins": [
|
|
64
|
+
"./app.plugin.js"
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
"peerDependencies": {
|
|
68
|
+
"expo": ">=50",
|
|
69
|
+
"react": "*",
|
|
70
|
+
"react-native": "*"
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"expo-module-scripts": "^3.0.0"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const moduleDir = path.resolve(path.dirname(__filename), "..");
|
|
8
|
+
const repoRoot = path.resolve(moduleDir, "..", "..");
|
|
9
|
+
const androidDir = path.join(repoRoot, "android");
|
|
10
|
+
const envFilePath = path.join(repoRoot, ".env");
|
|
11
|
+
|
|
12
|
+
function loadDotEnv(filePath) {
|
|
13
|
+
if (!existsSync(filePath)) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const content = readFileSync(filePath, "utf8");
|
|
18
|
+
for (const line of content.split(/\r?\n/)) {
|
|
19
|
+
const trimmed = line.trim();
|
|
20
|
+
if (!trimmed || trimmed.startsWith("#") || !trimmed.includes("=")) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const separatorIndex = trimmed.indexOf("=");
|
|
25
|
+
const key = trimmed.slice(0, separatorIndex).trim();
|
|
26
|
+
const rawValue = trimmed.slice(separatorIndex + 1).trim();
|
|
27
|
+
if (!key || process.env[key]) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let value = rawValue;
|
|
32
|
+
if (
|
|
33
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
34
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
35
|
+
) {
|
|
36
|
+
value = value.slice(1, -1);
|
|
37
|
+
}
|
|
38
|
+
process.env[key] = value;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function run(command, cwd = moduleDir, options = {}) {
|
|
43
|
+
const { optionalOnOfflineGradle = false } = options;
|
|
44
|
+
console.log(`\n> ${command} (${cwd})`);
|
|
45
|
+
try {
|
|
46
|
+
const output = execSync(command, {
|
|
47
|
+
cwd,
|
|
48
|
+
stdio: "pipe",
|
|
49
|
+
env: {
|
|
50
|
+
...process.env,
|
|
51
|
+
GRADLE_USER_HOME:
|
|
52
|
+
process.env.GRADLE_USER_HOME || "/tmp/gradle-user-home-react-native-mapbox-navigation",
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
if (output?.length) {
|
|
56
|
+
process.stdout.write(output);
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
const stdout = String(error?.stdout || "");
|
|
60
|
+
const stderr = String(error?.stderr || "");
|
|
61
|
+
if (stdout) {
|
|
62
|
+
process.stdout.write(stdout);
|
|
63
|
+
}
|
|
64
|
+
if (stderr) {
|
|
65
|
+
process.stderr.write(stderr);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const combined = `${stdout}\n${stderr}\n${String(error?.message || "")}`;
|
|
69
|
+
const skippableGradleEnvironmentIssue =
|
|
70
|
+
combined.includes("UnknownHostException") ||
|
|
71
|
+
combined.includes("FileLockContentionHandler") ||
|
|
72
|
+
combined.includes("SocketException: Operation not permitted");
|
|
73
|
+
|
|
74
|
+
if (optionalOnOfflineGradle && skippableGradleEnvironmentIssue) {
|
|
75
|
+
console.warn(`\nSkipping optional Gradle check due to restricted/offline environment: ${command}`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function runWithFallback(commands, cwd, options = {}) {
|
|
83
|
+
let lastError;
|
|
84
|
+
for (const command of commands) {
|
|
85
|
+
try {
|
|
86
|
+
run(command, cwd, options);
|
|
87
|
+
return;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
lastError = error;
|
|
90
|
+
console.warn(`\nCommand failed, trying fallback: ${command}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
throw lastError;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
loadDotEnv(envFilePath);
|
|
97
|
+
|
|
98
|
+
run("npx tsc --noEmit", repoRoot);
|
|
99
|
+
|
|
100
|
+
if (existsSync(path.join(androidDir, "gradlew"))) {
|
|
101
|
+
runWithFallback(
|
|
102
|
+
[
|
|
103
|
+
"./gradlew :react-native-mapbox-navigation:compileDebugKotlin",
|
|
104
|
+
"./gradlew :mapbox-navigation-native:compileDebugKotlin",
|
|
105
|
+
],
|
|
106
|
+
androidDir,
|
|
107
|
+
{ optionalOnOfflineGradle: true }
|
|
108
|
+
);
|
|
109
|
+
} else {
|
|
110
|
+
console.log("\n> Skipping Android compile check (android/gradlew not found).");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
run("npm pack --dry-run --cache /tmp/npm-cache-react-native-mapbox-navigation", moduleDir);
|
|
114
|
+
|
|
115
|
+
console.log("\nRelease verification completed.");
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
export type Coordinate = {
|
|
2
|
+
latitude: number;
|
|
3
|
+
longitude: number;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type Waypoint = Coordinate & {
|
|
7
|
+
name?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type NavigationOptions = {
|
|
11
|
+
startOrigin?: Coordinate;
|
|
12
|
+
destination: Waypoint;
|
|
13
|
+
waypoints?: Waypoint[];
|
|
14
|
+
shouldSimulateRoute?: boolean;
|
|
15
|
+
uiTheme?: 'system' | 'light' | 'dark' | 'day' | 'night';
|
|
16
|
+
routeAlternatives?: boolean;
|
|
17
|
+
distanceUnit?: 'metric' | 'imperial';
|
|
18
|
+
language?: string;
|
|
19
|
+
mute?: boolean;
|
|
20
|
+
voiceVolume?: number;
|
|
21
|
+
cameraPitch?: number;
|
|
22
|
+
cameraZoom?: number;
|
|
23
|
+
cameraMode?: 'following' | 'overview';
|
|
24
|
+
mapStyleUri?: string;
|
|
25
|
+
mapStyleUriDay?: string;
|
|
26
|
+
mapStyleUriNight?: string;
|
|
27
|
+
showsSpeedLimits?: boolean;
|
|
28
|
+
showsWayNameLabel?: boolean;
|
|
29
|
+
showsTripProgress?: boolean;
|
|
30
|
+
showsManeuverView?: boolean;
|
|
31
|
+
showsActionButtons?: boolean;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type NavigationSettings = {
|
|
35
|
+
isNavigating: boolean;
|
|
36
|
+
mute: boolean;
|
|
37
|
+
voiceVolume: number;
|
|
38
|
+
distanceUnit: 'metric' | 'imperial';
|
|
39
|
+
language: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type LocationUpdate = {
|
|
43
|
+
latitude: number;
|
|
44
|
+
longitude: number;
|
|
45
|
+
bearing?: number;
|
|
46
|
+
speed?: number;
|
|
47
|
+
altitude?: number;
|
|
48
|
+
accuracy?: number;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type RouteProgress = {
|
|
52
|
+
distanceTraveled: number;
|
|
53
|
+
distanceRemaining: number;
|
|
54
|
+
durationRemaining: number;
|
|
55
|
+
fractionTraveled: number;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type ArrivalEvent = {
|
|
59
|
+
index?: number;
|
|
60
|
+
name?: string;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type NavigationError = {
|
|
64
|
+
code: string;
|
|
65
|
+
message: string;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export type BannerInstruction = {
|
|
69
|
+
primaryText: string;
|
|
70
|
+
secondaryText?: string;
|
|
71
|
+
stepDistanceRemaining?: number;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export type Subscription = {
|
|
75
|
+
remove: () => void;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export interface MapboxNavigationModule {
|
|
79
|
+
// Start navigation with options
|
|
80
|
+
startNavigation(options: NavigationOptions): Promise<void>;
|
|
81
|
+
|
|
82
|
+
// Stop/cancel navigation
|
|
83
|
+
stopNavigation(): Promise<void>;
|
|
84
|
+
|
|
85
|
+
// Mute/unmute voice guidance
|
|
86
|
+
setMuted(muted: boolean): Promise<void>;
|
|
87
|
+
|
|
88
|
+
// Set voice volume (0.0 - 1.0)
|
|
89
|
+
setVoiceVolume(volume: number): Promise<void>;
|
|
90
|
+
|
|
91
|
+
// Set distance unit used by spoken and visual instructions
|
|
92
|
+
setDistanceUnit(unit: 'metric' | 'imperial'): Promise<void>;
|
|
93
|
+
|
|
94
|
+
// Set route instruction language (BCP-47, e.g. 'en', 'fr')
|
|
95
|
+
setLanguage(language: string): Promise<void>;
|
|
96
|
+
|
|
97
|
+
// Check if navigation is active
|
|
98
|
+
isNavigating(): Promise<boolean>;
|
|
99
|
+
|
|
100
|
+
// Get current native navigation settings
|
|
101
|
+
getNavigationSettings(): Promise<NavigationSettings>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface MapboxNavigationViewProps {
|
|
105
|
+
style?: any;
|
|
106
|
+
startOrigin?: Coordinate;
|
|
107
|
+
destination: Waypoint;
|
|
108
|
+
waypoints?: Waypoint[];
|
|
109
|
+
shouldSimulateRoute?: boolean;
|
|
110
|
+
showCancelButton?: boolean;
|
|
111
|
+
uiTheme?: 'system' | 'light' | 'dark' | 'day' | 'night';
|
|
112
|
+
distanceUnit?: 'metric' | 'imperial';
|
|
113
|
+
language?: string;
|
|
114
|
+
mute?: boolean;
|
|
115
|
+
voiceVolume?: number;
|
|
116
|
+
cameraPitch?: number;
|
|
117
|
+
cameraZoom?: number;
|
|
118
|
+
cameraMode?: 'following' | 'overview';
|
|
119
|
+
mapStyleUri?: string;
|
|
120
|
+
mapStyleUriDay?: string;
|
|
121
|
+
mapStyleUriNight?: string;
|
|
122
|
+
routeAlternatives?: boolean;
|
|
123
|
+
showsSpeedLimits?: boolean;
|
|
124
|
+
showsWayNameLabel?: boolean;
|
|
125
|
+
showsTripProgress?: boolean;
|
|
126
|
+
showsManeuverView?: boolean;
|
|
127
|
+
showsActionButtons?: boolean;
|
|
128
|
+
|
|
129
|
+
// Event callbacks
|
|
130
|
+
onLocationChange?: (location: LocationUpdate) => void;
|
|
131
|
+
onRouteProgressChange?: (progress: RouteProgress) => void;
|
|
132
|
+
onArrive?: (point: ArrivalEvent) => void;
|
|
133
|
+
onCancelNavigation?: () => void;
|
|
134
|
+
onError?: (error: NavigationError) => void;
|
|
135
|
+
onBannerInstruction?: (instruction: BannerInstruction) => void;
|
|
136
|
+
}
|