@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 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
+ ![Android](docs/screenshots/android.png)
17
+ ![iOS](docs/screenshots/ios.png)
18
+
19
+ ## Support Us
20
+
21
+ [![Support on Ko-fi](docs/imgs/support_me_on_kofi_badge_red.png)](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
- showErrorAndStay("Location permission is required to start navigation.", null)
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
- showErrorAndStay(message, null)
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
- showErrorAndStay("Navigation init failed: ${throwable.message}", throwable)
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
- showErrorAndStay("Failed to create NavigationView: ${throwable.message}", throwable)
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 showErrorAndStay(message: String, throwable: Throwable?) {
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 errorView = TextView(this).apply {
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
- textSize = 16f
372
- setPadding(32, 32, 32, 32)
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
- errorView,
422
+ content,
378
423
  FrameLayout.LayoutParams(
379
424
  FrameLayout.LayoutParams.MATCH_PARENT,
380
425
  FrameLayout.LayoutParams.MATCH_PARENT
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.sendEvent("onError", [
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.sendEvent("onError", [
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.sendEvent("onError", [
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.sendEvent("onError", [
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atomiqlab/react-native-mapbox-navigation",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "Native Mapbox turn-by-turn navigation for Expo and React Native (iOS + Android)",
5
5
  "main": "src/index.tsx",
6
6
  "types": "src/index.tsx",