@atomiqlab/react-native-mapbox-navigation 1.1.2 → 1.1.3
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/README.md +11 -0
- package/android/src/main/java/expo/modules/mapboxnavigation/MapboxNavigationActivity.kt +54 -9
- package/docs/imgs/support_me_on_kofi_badge_red.png +0 -0
- package/docs/screenshots/.gitkeep +0 -0
- package/docs/screenshots/android.png +0 -0
- package/docs/screenshots/ios.png +0 -0
- package/ios/MapboxNavigationModule.swift +112 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,17 @@ Native Mapbox turn-by-turn navigation for Expo apps on iOS and Android.
|
|
|
11
11
|
- Navigation customization: camera mode/pitch/zoom, theme, map style, and UI visibility toggles.
|
|
12
12
|
- Expo config plugin that applies required Android and iOS native setup.
|
|
13
13
|
|
|
14
|
+
## Screenshots
|
|
15
|
+
|
|
16
|
+

|
|
17
|
+

|
|
18
|
+
|
|
19
|
+
## Support Us
|
|
20
|
+
|
|
21
|
+
[](https://ko-fi.com/atomiqlab)
|
|
22
|
+
|
|
23
|
+
If this package helps your work, support development on Ko-fi.
|
|
24
|
+
|
|
14
25
|
## Requirements
|
|
15
26
|
|
|
16
27
|
- Expo SDK `>=50`
|
|
@@ -8,7 +8,10 @@ import android.os.Handler
|
|
|
8
8
|
import android.os.Looper
|
|
9
9
|
import android.util.Log
|
|
10
10
|
import android.view.Gravity
|
|
11
|
+
import android.view.ViewGroup
|
|
12
|
+
import android.widget.Button
|
|
11
13
|
import android.widget.FrameLayout
|
|
14
|
+
import android.widget.LinearLayout
|
|
12
15
|
import android.widget.TextView
|
|
13
16
|
import androidx.appcompat.app.AppCompatDelegate
|
|
14
17
|
import androidx.appcompat.app.AppCompatActivity
|
|
@@ -95,7 +98,7 @@ class MapboxNavigationActivity : AppCompatActivity() {
|
|
|
95
98
|
result[Manifest.permission.ACCESS_COARSE_LOCATION] == true
|
|
96
99
|
|
|
97
100
|
if (!granted) {
|
|
98
|
-
|
|
101
|
+
showErrorScreen("Location permission is required to start navigation.", null)
|
|
99
102
|
return@registerForActivityResult
|
|
100
103
|
}
|
|
101
104
|
|
|
@@ -120,7 +123,7 @@ class MapboxNavigationActivity : AppCompatActivity() {
|
|
|
120
123
|
"message" to message
|
|
121
124
|
)
|
|
122
125
|
)
|
|
123
|
-
|
|
126
|
+
showErrorScreen(message, null)
|
|
124
127
|
}
|
|
125
128
|
|
|
126
129
|
override fun onRouteFetchSuccessful(routes: List<NavigationRoute>) {
|
|
@@ -182,7 +185,7 @@ class MapboxNavigationActivity : AppCompatActivity() {
|
|
|
182
185
|
Log.w(TAG, "No valid destination extras provided, starting without preview")
|
|
183
186
|
}
|
|
184
187
|
} catch (throwable: Throwable) {
|
|
185
|
-
|
|
188
|
+
showErrorScreen("Navigation init failed: ${throwable.message}", throwable)
|
|
186
189
|
return
|
|
187
190
|
}
|
|
188
191
|
|
|
@@ -272,7 +275,7 @@ class MapboxNavigationActivity : AppCompatActivity() {
|
|
|
272
275
|
}
|
|
273
276
|
|
|
274
277
|
} catch (throwable: Throwable) {
|
|
275
|
-
|
|
278
|
+
showErrorScreen("Failed to create NavigationView: ${throwable.message}", throwable)
|
|
276
279
|
}
|
|
277
280
|
}
|
|
278
281
|
|
|
@@ -350,7 +353,7 @@ class MapboxNavigationActivity : AppCompatActivity() {
|
|
|
350
353
|
return fineGranted || coarseGranted
|
|
351
354
|
}
|
|
352
355
|
|
|
353
|
-
private fun
|
|
356
|
+
private fun showErrorScreen(message: String, throwable: Throwable?) {
|
|
354
357
|
if (throwable != null) {
|
|
355
358
|
Log.e(TAG, message, throwable)
|
|
356
359
|
} else {
|
|
@@ -365,16 +368,58 @@ class MapboxNavigationActivity : AppCompatActivity() {
|
|
|
365
368
|
)
|
|
366
369
|
)
|
|
367
370
|
|
|
368
|
-
val
|
|
371
|
+
val titleView = TextView(this).apply {
|
|
372
|
+
text = "Navigation Error"
|
|
373
|
+
textSize = 22f
|
|
374
|
+
setTextColor(0xFFFFFFFF.toInt())
|
|
375
|
+
gravity = Gravity.CENTER
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
val messageView = TextView(this).apply {
|
|
369
379
|
text = message
|
|
380
|
+
textSize = 15f
|
|
381
|
+
setTextColor(0xFFD6E4FF.toInt())
|
|
370
382
|
gravity = Gravity.CENTER
|
|
371
|
-
|
|
372
|
-
|
|
383
|
+
setPadding(0, 24, 0, 32)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
val closeButton = Button(this).apply {
|
|
387
|
+
text = "Back"
|
|
388
|
+
setOnClickListener { finish() }
|
|
373
389
|
}
|
|
390
|
+
|
|
391
|
+
val content = LinearLayout(this).apply {
|
|
392
|
+
orientation = LinearLayout.VERTICAL
|
|
393
|
+
gravity = Gravity.CENTER
|
|
394
|
+
setBackgroundColor(0xFF0B1020.toInt())
|
|
395
|
+
setPadding(48, 48, 48, 48)
|
|
396
|
+
addView(
|
|
397
|
+
titleView,
|
|
398
|
+
LinearLayout.LayoutParams(
|
|
399
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
400
|
+
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
401
|
+
)
|
|
402
|
+
)
|
|
403
|
+
addView(
|
|
404
|
+
messageView,
|
|
405
|
+
LinearLayout.LayoutParams(
|
|
406
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
407
|
+
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
408
|
+
)
|
|
409
|
+
)
|
|
410
|
+
addView(
|
|
411
|
+
closeButton,
|
|
412
|
+
LinearLayout.LayoutParams(
|
|
413
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
414
|
+
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
415
|
+
)
|
|
416
|
+
)
|
|
417
|
+
}
|
|
418
|
+
|
|
374
419
|
setContentView(
|
|
375
420
|
FrameLayout(this).apply {
|
|
376
421
|
addView(
|
|
377
|
-
|
|
422
|
+
content,
|
|
378
423
|
FrameLayout.LayoutParams(
|
|
379
424
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
380
425
|
FrameLayout.LayoutParams.MATCH_PARENT
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
@@ -157,9 +157,20 @@ public class MapboxNavigationModule: Module {
|
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
private func configuredMapboxPublicToken() -> String? {
|
|
161
|
+
guard let raw = Bundle.main.object(forInfoDictionaryKey: "MBXAccessToken") as? String else {
|
|
162
|
+
return nil
|
|
163
|
+
}
|
|
164
|
+
let token = raw.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
165
|
+
guard token.hasPrefix("pk."), token.count > 20 else {
|
|
166
|
+
return nil
|
|
167
|
+
}
|
|
168
|
+
return token
|
|
169
|
+
}
|
|
170
|
+
|
|
160
171
|
private func startNavigation(options: NavigationStartOptions, promise: Promise) {
|
|
161
172
|
guard let destination = options.destination.toCLLocationCoordinate2D() else {
|
|
162
|
-
self.
|
|
173
|
+
self.emitErrorAndShowScreen([
|
|
163
174
|
"code": "INVALID_COORDINATES",
|
|
164
175
|
"message": "Invalid coordinates provided"
|
|
165
176
|
])
|
|
@@ -188,7 +199,7 @@ public class MapboxNavigationModule: Module {
|
|
|
188
199
|
promise: promise
|
|
189
200
|
)
|
|
190
201
|
case .failure(let error):
|
|
191
|
-
self.
|
|
202
|
+
self.emitErrorAndShowScreen([
|
|
192
203
|
"code": "CURRENT_LOCATION_UNAVAILABLE",
|
|
193
204
|
"message": error.localizedDescription
|
|
194
205
|
])
|
|
@@ -203,7 +214,16 @@ public class MapboxNavigationModule: Module {
|
|
|
203
214
|
options: NavigationStartOptions,
|
|
204
215
|
promise: Promise
|
|
205
216
|
) {
|
|
206
|
-
|
|
217
|
+
guard configuredMapboxPublicToken() != nil else {
|
|
218
|
+
let message = "Missing or invalid MBXAccessToken. Add the package plugin to app.json and set EXPO_PUBLIC_MAPBOX_ACCESS_TOKEN before prebuild."
|
|
219
|
+
self.emitErrorAndShowScreen([
|
|
220
|
+
"code": "MISSING_ACCESS_TOKEN",
|
|
221
|
+
"message": message
|
|
222
|
+
])
|
|
223
|
+
promise.reject("MISSING_ACCESS_TOKEN", message)
|
|
224
|
+
return
|
|
225
|
+
}
|
|
226
|
+
|
|
207
227
|
var waypoints = [Waypoint(coordinate: origin)]
|
|
208
228
|
|
|
209
229
|
// Add intermediate waypoints if provided
|
|
@@ -233,7 +253,7 @@ public class MapboxNavigationModule: Module {
|
|
|
233
253
|
switch result {
|
|
234
254
|
case .success(let response):
|
|
235
255
|
guard response.routes?.first != nil else {
|
|
236
|
-
self.
|
|
256
|
+
self.emitErrorAndShowScreen([
|
|
237
257
|
"code": "NO_ROUTE",
|
|
238
258
|
"message": "No route found"
|
|
239
259
|
])
|
|
@@ -284,12 +304,16 @@ public class MapboxNavigationModule: Module {
|
|
|
284
304
|
promise.resolve(nil)
|
|
285
305
|
}
|
|
286
306
|
} else {
|
|
307
|
+
self.emitErrorAndShowScreen([
|
|
308
|
+
"code": "NO_ROOT_VC",
|
|
309
|
+
"message": "Could not find root view controller"
|
|
310
|
+
])
|
|
287
311
|
promise.reject("NO_ROOT_VC", "Could not find root view controller")
|
|
288
312
|
}
|
|
289
313
|
|
|
290
314
|
case .failure(let error):
|
|
291
315
|
let (code, message) = self.mapDirectionsError(error)
|
|
292
|
-
self.
|
|
316
|
+
self.emitErrorAndShowScreen([
|
|
293
317
|
"code": code,
|
|
294
318
|
"message": message
|
|
295
319
|
])
|
|
@@ -309,6 +333,25 @@ public class MapboxNavigationModule: Module {
|
|
|
309
333
|
}
|
|
310
334
|
}
|
|
311
335
|
|
|
336
|
+
private func emitErrorAndShowScreen(_ payload: [String: Any]) {
|
|
337
|
+
sendEvent("onError", payload)
|
|
338
|
+
|
|
339
|
+
guard let navVC = navigationViewController else {
|
|
340
|
+
return
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if navVC.presentingViewController == nil {
|
|
344
|
+
navigationViewController = nil
|
|
345
|
+
isCurrentlyNavigating = false
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
navVC.dismiss(animated: true) {
|
|
350
|
+
self.navigationViewController = nil
|
|
351
|
+
self.isCurrentlyNavigating = false
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
312
355
|
private func mapDirectionsError(_ error: Error) -> (String, String) {
|
|
313
356
|
let message = error.localizedDescription
|
|
314
357
|
let lowered = message.lowercased()
|
|
@@ -645,3 +688,67 @@ private final class CurrentLocationResolver: NSObject, CLLocationManagerDelegate
|
|
|
645
688
|
completion(result)
|
|
646
689
|
}
|
|
647
690
|
}
|
|
691
|
+
|
|
692
|
+
private final class NavigationErrorViewController: UIViewController {
|
|
693
|
+
private let message: String
|
|
694
|
+
|
|
695
|
+
init(message: String) {
|
|
696
|
+
self.message = message
|
|
697
|
+
super.init(nibName: nil, bundle: nil)
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
required init?(coder: NSCoder) {
|
|
701
|
+
nil
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
override func viewDidLoad() {
|
|
705
|
+
super.viewDidLoad()
|
|
706
|
+
|
|
707
|
+
view.backgroundColor = UIColor(red: 11 / 255, green: 16 / 255, blue: 32 / 255, alpha: 1)
|
|
708
|
+
|
|
709
|
+
let titleLabel = UILabel()
|
|
710
|
+
titleLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
711
|
+
titleLabel.text = "Navigation Error"
|
|
712
|
+
titleLabel.textColor = .white
|
|
713
|
+
titleLabel.font = UIFont.systemFont(ofSize: 28, weight: .bold)
|
|
714
|
+
titleLabel.textAlignment = .center
|
|
715
|
+
titleLabel.numberOfLines = 0
|
|
716
|
+
|
|
717
|
+
let messageLabel = UILabel()
|
|
718
|
+
messageLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
719
|
+
messageLabel.text = message
|
|
720
|
+
messageLabel.textColor = UIColor(red: 214 / 255, green: 228 / 255, blue: 255 / 255, alpha: 1)
|
|
721
|
+
messageLabel.font = UIFont.systemFont(ofSize: 16, weight: .regular)
|
|
722
|
+
messageLabel.textAlignment = .center
|
|
723
|
+
messageLabel.numberOfLines = 0
|
|
724
|
+
|
|
725
|
+
let closeButton = UIButton(type: .system)
|
|
726
|
+
closeButton.translatesAutoresizingMaskIntoConstraints = false
|
|
727
|
+
closeButton.setTitle("Back", for: .normal)
|
|
728
|
+
closeButton.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .semibold)
|
|
729
|
+
closeButton.backgroundColor = UIColor.white.withAlphaComponent(0.15)
|
|
730
|
+
closeButton.setTitleColor(.white, for: .normal)
|
|
731
|
+
closeButton.layer.cornerRadius = 10
|
|
732
|
+
closeButton.contentEdgeInsets = UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)
|
|
733
|
+
closeButton.addTarget(self, action: #selector(closeTapped), for: .touchUpInside)
|
|
734
|
+
|
|
735
|
+
let stack = UIStackView(arrangedSubviews: [titleLabel, messageLabel, closeButton])
|
|
736
|
+
stack.translatesAutoresizingMaskIntoConstraints = false
|
|
737
|
+
stack.axis = .vertical
|
|
738
|
+
stack.alignment = .fill
|
|
739
|
+
stack.spacing = 20
|
|
740
|
+
|
|
741
|
+
view.addSubview(stack)
|
|
742
|
+
|
|
743
|
+
NSLayoutConstraint.activate([
|
|
744
|
+
stack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 24),
|
|
745
|
+
stack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -24),
|
|
746
|
+
stack.centerYAnchor.constraint(equalTo: view.centerYAnchor),
|
|
747
|
+
])
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
@objc
|
|
751
|
+
private func closeTapped() {
|
|
752
|
+
dismiss(animated: true)
|
|
753
|
+
}
|
|
754
|
+
}
|
package/package.json
CHANGED