expo-gaode-map-navigation 2.0.10 → 2.0.11

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.
Files changed (53) hide show
  1. package/README.md +54 -2
  2. package/android/build.gradle +4 -0
  3. package/android/src/main/AndroidManifest.xml +2 -1
  4. package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNaviView.kt +501 -27
  5. package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNaviViewModule.kt +35 -0
  6. package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNavigationModule.kt +10 -23
  7. package/android/src/main/java/expo/modules/gaodemap/navigation/listeners/IndependentRouteListener.kt +24 -0
  8. package/android/src/main/java/expo/modules/gaodemap/navigation/managers/IndependentRouteManager.kt +24 -7
  9. package/android/src/main/java/expo/modules/gaodemap/navigation/routes/drive/DriveTruckRouteCalculator.kt +22 -35
  10. package/android/src/main/java/expo/modules/gaodemap/navigation/services/IndependentRouteService.kt +45 -35
  11. package/android/src/main/java/expo/modules/gaodemap/navigation/services/NavigationForegroundService.kt +661 -0
  12. package/android/src/main/java/expo/modules/gaodemap/navigation/utils/Converters.kt +2 -2
  13. package/android/src/main/res/drawable/ic_nav_notification_small.xml +10 -0
  14. package/android/src/main/res/drawable/nav_notification_brand_icon.xml +16 -0
  15. package/android/src/main/res/drawable/navi_lane_shape_bg_center.xml +4 -4
  16. package/android/src/main/res/drawable/navi_lane_shape_bg_left.xml +7 -7
  17. package/android/src/main/res/drawable/navi_lane_shape_bg_over.xml +5 -5
  18. package/android/src/main/res/drawable/navi_lane_shape_bg_right.xml +7 -7
  19. package/android/src/main/res/drawable-nodpi/nav_tracker_car.png +0 -0
  20. package/build/ExpoGaodeMapNaviView.d.ts +9 -1
  21. package/build/ExpoGaodeMapNaviView.d.ts.map +1 -1
  22. package/build/ExpoGaodeMapNaviView.js +39 -3
  23. package/build/ExpoGaodeMapNaviView.js.map +1 -1
  24. package/build/index.d.ts +32 -6
  25. package/build/index.d.ts.map +1 -1
  26. package/build/index.js +32 -6
  27. package/build/index.js.map +1 -1
  28. package/build/types/independent.types.d.ts +18 -3
  29. package/build/types/independent.types.d.ts.map +1 -1
  30. package/build/types/independent.types.js.map +1 -1
  31. package/build/types/naviview.types.d.ts +49 -3
  32. package/build/types/naviview.types.d.ts.map +1 -1
  33. package/build/types/naviview.types.js.map +1 -1
  34. package/build/types/route.types.d.ts +10 -2
  35. package/build/types/route.types.d.ts.map +1 -1
  36. package/build/types/route.types.js.map +1 -1
  37. package/ios/ExpoGaodeMapNaviView.swift +1526 -246
  38. package/ios/ExpoGaodeMapNaviViewModule.swift +22 -0
  39. package/ios/ExpoGaodeMapNavigationModule.swift +6 -4
  40. package/ios/managers/IndependentRouteManager.swift +89 -26
  41. package/ios/map/ExpoGaodeMapModule.swift +25 -11
  42. package/ios/map/modules/LocationManager.swift +10 -1
  43. package/ios/map/utils/PermissionManager.swift +104 -0
  44. package/ios/routes/drive/DriveTruckRouteCalculator.swift +157 -78
  45. package/ios/routes/walkride/WalkRideRouteCalculator.swift +97 -1
  46. package/ios/services/IndependentRouteService.swift +165 -32
  47. package/ios/services/NavigationLiveActivityAttributes.swift +48 -0
  48. package/ios/services/NavigationLiveActivityManager.swift +359 -0
  49. package/package.json +2 -1
  50. package/plugin/build/withGaodeMap.d.ts +8 -0
  51. package/plugin/build/withGaodeMap.js +48 -4
  52. package/widget-template/README.md +46 -0
  53. package/widget-template/ios/NavigationLiveActivityWidget.swift +367 -0
@@ -8,6 +8,8 @@
8
8
  import Foundation
9
9
  import ExpoModulesCore
10
10
  import AMapNaviKit
11
+ import AVFAudio
12
+ import CoreLocation
11
13
 
12
14
  final class ExpoGaodeMapCustomDriveView: AMapNaviDriveView {
13
15
  var suppressLaneInfoUI: Bool = false
@@ -17,6 +19,12 @@ final class ExpoGaodeMapCustomDriveView: AMapNaviDriveView {
17
19
  setNeedsLayout()
18
20
  }
19
21
  }
22
+ var suppressBottomRightUI: Bool = false {
23
+ didSet {
24
+ scheduleTopInfoSuppressionPasses()
25
+ setNeedsLayout()
26
+ }
27
+ }
20
28
  private let topInfoCoverView: UIView = {
21
29
  let view = UIView()
22
30
  view.isHidden = true
@@ -71,6 +79,12 @@ final class ExpoGaodeMapCustomDriveView: AMapNaviDriveView {
71
79
  candidate.alpha = suppressTopInfoUI ? 0.0 : 1.0
72
80
  }
73
81
 
82
+ let bottomRightCandidates = collectBottomRightCandidates()
83
+ for candidate in bottomRightCandidates {
84
+ candidate.isHidden = suppressBottomRightUI
85
+ candidate.alpha = suppressBottomRightUI ? 0.0 : 1.0
86
+ }
87
+
74
88
  guard suppressTopInfoUI, !topCandidates.isEmpty else {
75
89
  topInfoCoverView.isHidden = true
76
90
  return
@@ -143,6 +157,58 @@ final class ExpoGaodeMapCustomDriveView: AMapNaviDriveView {
143
157
  }
144
158
  }
145
159
 
160
+ private func collectBottomRightCandidates() -> [UIView] {
161
+ guard suppressBottomRightUI || !subviews.isEmpty else {
162
+ return []
163
+ }
164
+
165
+ let protectedClassNameFragments = [
166
+ "MAMap",
167
+ "Lane",
168
+ "Cross",
169
+ "Eagle",
170
+ "TrafficBar",
171
+ "Compass",
172
+ "Zoom",
173
+ "Scale",
174
+ "Logo",
175
+ ]
176
+
177
+ return allDescendantSubviews(of: self).filter { view in
178
+ guard view !== self, view !== topInfoCoverView else {
179
+ return false
180
+ }
181
+
182
+ let frame = view.convert(view.bounds, to: self)
183
+ guard !frame.isEmpty else {
184
+ return false
185
+ }
186
+
187
+ let className = NSStringFromClass(type(of: view))
188
+ if protectedClassNameFragments.contains(where: { className.localizedCaseInsensitiveContains($0) }) {
189
+ return false
190
+ }
191
+
192
+ guard frame.minX >= bounds.width * 0.76 || frame.maxX >= bounds.width - 10 else {
193
+ return false
194
+ }
195
+
196
+ guard frame.minY >= bounds.height * 0.46 else {
197
+ return false
198
+ }
199
+
200
+ guard frame.width >= 18 && frame.width <= 110 else {
201
+ return false
202
+ }
203
+
204
+ guard frame.height >= 18 && frame.height <= 240 else {
205
+ return false
206
+ }
207
+
208
+ return true
209
+ }
210
+ }
211
+
146
212
  private func allDescendantSubviews(of root: UIView) -> [UIView] {
147
213
  root.subviews.flatMap { subview in
148
214
  [subview] + allDescendantSubviews(of: subview)
@@ -157,8 +223,8 @@ final class ExpoGaodeMapCustomDriveView: AMapNaviDriveView {
157
223
  }
158
224
 
159
225
  private func scheduleTopInfoSuppressionPasses() {
160
- scheduledSuppressionPasses = suppressTopInfoUI ? 18 : 0
161
- guard suppressTopInfoUI else {
226
+ scheduledSuppressionPasses = (suppressTopInfoUI || suppressBottomRightUI) ? 18 : 0
227
+ guard suppressTopInfoUI || suppressBottomRightUI else {
162
228
  topInfoCoverView.isHidden = true
163
229
  return
164
230
  }
@@ -177,13 +243,55 @@ final class ExpoGaodeMapCustomDriveView: AMapNaviDriveView {
177
243
  }
178
244
 
179
245
  func refreshSuppressedTopInfoUIIfNeeded() {
180
- guard suppressTopInfoUI else {
246
+ guard suppressTopInfoUI || suppressBottomRightUI else {
181
247
  return
182
248
  }
183
249
  applySuppressedChromeVisibility()
184
250
  }
185
251
  }
186
252
 
253
+ private struct NaviCustomWaypointMarkerModel {
254
+ let latitude: Double
255
+ let longitude: Double
256
+ let title: String
257
+ var arrived: Bool = false
258
+ }
259
+
260
+ private final class NaviCustomWaypointBubbleView: UIView {
261
+ init(title: String) {
262
+ let font = UIFont.systemFont(ofSize: 17, weight: .semibold)
263
+ let textWidth = ceil((title as NSString).size(withAttributes: [.font: font]).width)
264
+ let bodyWidth = min(max(textWidth + 28, 62), 110)
265
+ let size = CGSize(width: bodyWidth, height: 34)
266
+ super.init(frame: CGRect(origin: .zero, size: size))
267
+
268
+ backgroundColor = .clear
269
+ isOpaque = false
270
+
271
+ let bodyView = UIView(frame: bounds)
272
+ bodyView.backgroundColor = UIColor(red: 47.0 / 255.0, green: 103.0 / 255.0, blue: 255.0 / 255.0, alpha: 1)
273
+ bodyView.layer.cornerRadius = 17
274
+ bodyView.layer.borderWidth = 2
275
+ bodyView.layer.borderColor = UIColor.white.cgColor
276
+ bodyView.layer.shadowColor = UIColor(red: 21.0 / 255.0, green: 53.0 / 255.0, blue: 127.0 / 255.0, alpha: 1).cgColor
277
+ bodyView.layer.shadowOpacity = 0.18
278
+ bodyView.layer.shadowRadius = 6
279
+ bodyView.layer.shadowOffset = CGSize(width: 0, height: 3)
280
+ addSubview(bodyView)
281
+
282
+ let label = UILabel(frame: bodyView.bounds.insetBy(dx: 10, dy: 4))
283
+ label.text = title
284
+ label.textAlignment = .center
285
+ label.textColor = .white
286
+ label.font = font
287
+ bodyView.addSubview(label)
288
+ }
289
+
290
+ required init?(coder: NSCoder) {
291
+ fatalError("init(coder:) has not been implemented")
292
+ }
293
+ }
294
+
187
295
  public class ExpoGaodeMapNaviView: ExpoView {
188
296
  private let independentRouteManager = IndependentRouteManager.shared
189
297
 
@@ -302,11 +410,22 @@ public class ExpoGaodeMapNaviView: ExpoView {
302
410
  let onTrafficStatusesUpdate = EventDispatcher()
303
411
 
304
412
  // MARK: - Properties
413
+ private enum NaviScene: String {
414
+ case drive
415
+ case walk
416
+ case ride
417
+ }
418
+
419
+ private var activeScene: NaviScene = .drive
305
420
  private var driveView: AMapNaviDriveView?
421
+ private var walkView: AMapNaviWalkView?
422
+ private var rideView: AMapNaviRideView?
306
423
  private var pendingShowUIElements: Bool?
307
424
  private var hasStartedNavi: Bool = false
308
425
  private var hasReceivedFirstNaviData: Bool = false
309
426
  private var driveManager: AMapNaviDriveManager?
427
+ private var walkManager: AMapNaviWalkManager?
428
+ private var rideManager: AMapNaviRideManager?
310
429
  private var lastKnownSpeed: Int = 0
311
430
  private var currentRouteTotalLength: Int?
312
431
  private var lastNavigationInfoPayload: [String: Any]?
@@ -314,8 +433,14 @@ public class ExpoGaodeMapNaviView: ExpoView {
314
433
  private var lastNextTurnIconType: Int?
315
434
  private var lastTurnIconImageUri: String?
316
435
  private var lastNextTurnIconImageUri: String?
436
+ private var lastTurnIconBase64: String?
437
+ private var trafficBarTotalLength: Int?
317
438
  private var isCrossVisible: Bool = false
318
439
  private var isLaneInfoVisible: Bool = false
440
+ private var isNavigationAudioSessionActive: Bool = false
441
+ private var hasLoggedMissingBackgroundAudioMode: Bool = false
442
+ private var renderedCustomWaypointAnnotations: [AMapNaviCompositeCustomAnnotation] = []
443
+ private var customWaypointMarkers: [NaviCustomWaypointMarkerModel] = []
319
444
 
320
445
  private enum LaneStringKind {
321
446
  case background
@@ -333,6 +458,9 @@ public class ExpoGaodeMapNaviView: ExpoView {
333
458
  var carImageSource: String? {
334
459
  didSet { applyCarImageSource() }
335
460
  }
461
+ var carImageSize: CGSize? {
462
+ didSet { applyCarImageSource() }
463
+ }
336
464
  var carCompassImageSource: String? {
337
465
  didSet { applyCarCompassImageSource() }
338
466
  }
@@ -342,6 +470,9 @@ public class ExpoGaodeMapNaviView: ExpoView {
342
470
  var wayPointImageSource: String? {
343
471
  didSet { applyWayPointImageSource() }
344
472
  }
473
+ var customWaypointMarkerPayloads: [[String: Any]]? {
474
+ didSet { applyCustomWaypointMarkerPayloads(customWaypointMarkerPayloads) }
475
+ }
345
476
  var endPointImageSource: String? {
346
477
  didSet { applyEndPointImageSource() }
347
478
  }
@@ -375,7 +506,7 @@ public class ExpoGaodeMapNaviView: ExpoView {
375
506
  didSet { driveView?.showRoute = showRoute }
376
507
  }
377
508
  var showTurnArrow: Bool = true {
378
- didSet { driveView?.showTurnArrow = showTurnArrow }
509
+ didSet { applyShowTurnArrow(showTurnArrow) }
379
510
  }
380
511
  var showTrafficBar: Bool = true {
381
512
  didSet { driveView?.showTrafficBar = showTrafficBar }
@@ -387,10 +518,10 @@ public class ExpoGaodeMapNaviView: ExpoView {
387
518
  didSet { applyTrafficBarColors(trafficBarColors) }
388
519
  }
389
520
  var showBrowseRouteButton: Bool = true {
390
- didSet { driveView?.showBrowseRouteButton = showBrowseRouteButton }
521
+ didSet { applyShowBrowseRouteButton(showBrowseRouteButton) }
391
522
  }
392
523
  var showMoreButton: Bool = true {
393
- didSet { driveView?.showMoreButton = showMoreButton }
524
+ didSet { applyShowMoreButton(showMoreButton) }
394
525
  }
395
526
  var showTrafficButton: Bool = true {
396
527
  didSet { driveView?.showTrafficButton = showTrafficButton }
@@ -402,10 +533,10 @@ public class ExpoGaodeMapNaviView: ExpoView {
402
533
  didSet { driveView?.showEagleMap = showEagleMap }
403
534
  }
404
535
  var showUIElements: Bool = true {
405
- didSet { applyShowUIElementsToDriveViewIfReady() }
536
+ didSet { applyShowUIElementsToActiveViewIfReady() }
406
537
  }
407
- var showGreyAfterPass: Bool = false {
408
- didSet { driveView?.showGreyAfterPass = showGreyAfterPass }
538
+ var showGreyAfterPass: Bool = true {
539
+ didSet { applyShowGreyAfterPass(showGreyAfterPass) }
409
540
  }
410
541
  var showVectorline: Bool = true {
411
542
  didSet { driveView?.showVectorline = showVectorline }
@@ -416,7 +547,7 @@ public class ExpoGaodeMapNaviView: ExpoView {
416
547
  var showCompassEnabled: Bool? {
417
548
  didSet {
418
549
  guard let showCompassEnabled else { return }
419
- driveView?.showCompass = showCompassEnabled
550
+ applyShowCompassEnabled(showCompassEnabled)
420
551
  }
421
552
  }
422
553
  var showDriveCongestion: Bool = true {
@@ -429,18 +560,18 @@ public class ExpoGaodeMapNaviView: ExpoView {
429
560
  didSet { applyMapViewModeType(mapViewModeType) }
430
561
  }
431
562
  var lineWidth: CGFloat = 0 {
432
- didSet { driveView?.lineWidth = lineWidth }
563
+ didSet { applyLineWidth(lineWidth) }
433
564
  }
434
565
  var driveViewEdgePadding: UIEdgeInsets = .zero {
435
566
  didSet {
436
- driveView?.setNeedsLayout()
437
- driveView?.layoutIfNeeded()
567
+ currentNaviView()?.setNeedsLayout()
568
+ currentNaviView()?.layoutIfNeeded()
438
569
  scheduleOverviewRouteVisibleRegionRefresh()
439
570
  }
440
571
  }
441
572
  var screenAnchor: CGPoint = .zero {
442
573
  didSet {
443
- driveView?.screenAnchor = screenAnchor
574
+ applyScreenAnchor(screenAnchor)
444
575
  scheduleOverviewRouteVisibleRegionRefresh()
445
576
  }
446
577
  }
@@ -450,6 +581,15 @@ public class ExpoGaodeMapNaviView: ExpoView {
450
581
  var hideNativeLaneInfoLayout: Bool = false {
451
582
  didSet { applyHideNativeLaneInfoLayout(hideNativeLaneInfoLayout) }
452
583
  }
584
+ var iosLiveActivityEnabled: Bool = false {
585
+ didSet {
586
+ if iosLiveActivityEnabled {
587
+ syncNavigationLiveActivityWithLastPayload()
588
+ } else {
589
+ NavigationLiveActivityManager.shared.stop()
590
+ }
591
+ }
592
+ }
453
593
 
454
594
  func applyShowUIElements(_ visible: Bool) {
455
595
  showUIElements = visible
@@ -487,34 +627,164 @@ public class ExpoGaodeMapNaviView: ExpoView {
487
627
  }
488
628
  return
489
629
  }
490
-
491
- // 初始化驾车导航管理器
492
- driveManager = AMapNaviDriveManager.sharedInstance()
493
- rebindDriveManagerToView()
494
-
495
- // 使用内置语音
496
- driveManager?.isUseInternalTTS = true
497
-
498
- // 初始化导航视图
630
+
631
+ switchToScene(.drive)
632
+ }
633
+
634
+ private func currentNaviView() -> UIView? {
635
+ switch activeScene {
636
+ case .drive:
637
+ return driveView
638
+ case .walk:
639
+ return walkView
640
+ case .ride:
641
+ return rideView
642
+ }
643
+ }
644
+
645
+ private func currentNaviManager() -> AMapNaviBaseManager? {
646
+ switch activeScene {
647
+ case .drive:
648
+ return driveManager
649
+ case .walk:
650
+ return walkManager
651
+ case .ride:
652
+ return rideManager
653
+ }
654
+ }
655
+
656
+ private func setupDriveViewIfNeeded() {
657
+ guard driveView == nil else {
658
+ return
659
+ }
499
660
  let customDriveView = ExpoGaodeMapCustomDriveView(frame: bounds)
661
+ customDriveView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
662
+ customDriveView.delegate = self
500
663
  customDriveView.suppressTopInfoUI = hideNativeTopInfoLayout
501
664
  customDriveView.suppressLaneInfoUI = hideNativeLaneInfoLayout
502
665
  driveView = customDriveView
503
- driveView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
504
- driveView?.delegate = self
505
-
506
- if let view = driveView {
507
- addSubview(view)
508
- driveManager?.addDataRepresentative(view)
666
+ }
667
+
668
+ private func setupWalkViewIfNeeded() {
669
+ guard walkView == nil else {
670
+ return
509
671
  }
510
-
511
- // 应用初始配置
672
+ let view = AMapNaviWalkView(frame: bounds)
673
+ view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
674
+ view.delegate = self
675
+ walkView = view
676
+ }
677
+
678
+ private func setupRideViewIfNeeded() {
679
+ guard rideView == nil else {
680
+ return
681
+ }
682
+ let view = AMapNaviRideView(frame: bounds)
683
+ view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
684
+ view.delegate = self
685
+ rideView = view
686
+ }
687
+
688
+ private func attachActiveView(_ targetView: UIView?) {
689
+ let knownViews = [driveView, walkView, rideView].compactMap { $0 }
690
+ for view in knownViews where view !== targetView {
691
+ if view.superview === self {
692
+ view.removeFromSuperview()
693
+ }
694
+ }
695
+
696
+ guard let targetView else {
697
+ return
698
+ }
699
+ targetView.frame = bounds
700
+ if targetView.superview !== self {
701
+ addSubview(targetView)
702
+ }
703
+ sendSubviewToBack(targetView)
704
+ }
705
+
706
+ private func switchToScene(_ scene: NaviScene) {
707
+ teardownManagers(except: scene)
708
+ activeScene = scene
709
+
710
+ switch scene {
711
+ case .drive:
712
+ setupDriveViewIfNeeded()
713
+ rebindDriveManagerToView()
714
+ attachActiveView(driveView)
715
+ case .walk:
716
+ setupWalkViewIfNeeded()
717
+ rebindWalkManagerToView()
718
+ attachActiveView(walkView)
719
+ case .ride:
720
+ setupRideViewIfNeeded()
721
+ rebindRideManagerToView()
722
+ attachActiveView(rideView)
723
+ }
724
+
512
725
  applyViewOptions()
726
+ applyShowUIElementsToActiveViewIfReady()
727
+ DispatchQueue.main.async { [weak self] in
728
+ self?.onNavigationReady([:])
729
+ }
730
+ }
731
+
732
+ private func teardownManagers(except scene: NaviScene) {
733
+ if scene != .drive {
734
+ destroyDriveManagerIfNeeded()
735
+ }
736
+ if scene != .walk {
737
+ destroyWalkManagerIfNeeded()
738
+ }
739
+ if scene != .ride {
740
+ destroyRideManagerIfNeeded()
741
+ }
742
+ }
743
+
744
+ private func destroyDriveManagerIfNeeded() {
745
+ guard let driveManager else {
746
+ return
747
+ }
748
+ if let view = driveView {
749
+ driveManager.removeDataRepresentative(view)
750
+ }
751
+ driveManager.removeDataRepresentative(self)
752
+ driveManager.delegate = nil
753
+ self.driveManager = nil
754
+ _ = AMapNaviDriveManager.destroyInstance()
755
+ }
756
+
757
+ private func destroyWalkManagerIfNeeded() {
758
+ guard let walkManager else {
759
+ return
760
+ }
761
+ if let view = walkView {
762
+ walkManager.removeDataRepresentative(view)
763
+ }
764
+ walkManager.removeDataRepresentative(self)
765
+ walkManager.delegate = nil
766
+ self.walkManager = nil
767
+ _ = AMapNaviWalkManager.destroyInstance()
768
+ }
769
+
770
+ private func destroyRideManagerIfNeeded() {
771
+ guard let rideManager else {
772
+ return
773
+ }
774
+ if let view = rideView {
775
+ rideManager.removeDataRepresentative(view)
776
+ }
777
+ rideManager.removeDataRepresentative(self)
778
+ rideManager.delegate = nil
779
+ self.rideManager = nil
780
+ _ = AMapNaviRideManager.destroyInstance()
513
781
  }
514
782
 
515
783
  private func rebindDriveManagerToView() {
516
784
  driveManager = AMapNaviDriveManager.sharedInstance()
517
785
  driveManager?.delegate = self
786
+ applyDriveManagerBackgroundLocationOptionsIfNeeded()
787
+ driveManager?.isUseInternalTTS = enableVoice
518
788
  driveManager?.removeDataRepresentative(self)
519
789
  if let view = driveView {
520
790
  driveManager?.removeDataRepresentative(view)
@@ -523,14 +793,137 @@ public class ExpoGaodeMapNaviView: ExpoView {
523
793
  driveManager?.addDataRepresentative(self)
524
794
  }
525
795
 
796
+ private func rebindWalkManagerToView() {
797
+ walkManager = AMapNaviWalkManager.sharedInstance()
798
+ walkManager?.delegate = self
799
+ applyWalkManagerBackgroundLocationOptionsIfNeeded()
800
+ walkManager?.isUseInternalTTS = enableVoice
801
+ walkManager?.removeDataRepresentative(self)
802
+ if let view = walkView {
803
+ walkManager?.removeDataRepresentative(view)
804
+ walkManager?.addDataRepresentative(view)
805
+ }
806
+ walkManager?.addDataRepresentative(self)
807
+ }
808
+
809
+ private func rebindRideManagerToView() {
810
+ rideManager = AMapNaviRideManager.sharedInstance()
811
+ rideManager?.delegate = self
812
+ applyRideManagerBackgroundLocationOptionsIfNeeded()
813
+ rideManager?.isUseInternalTTS = enableVoice
814
+ rideManager?.removeDataRepresentative(self)
815
+ if let view = rideView {
816
+ rideManager?.removeDataRepresentative(view)
817
+ rideManager?.addDataRepresentative(view)
818
+ }
819
+ rideManager?.addDataRepresentative(self)
820
+ }
821
+
822
+ private func applyDriveManagerBackgroundLocationOptionsIfNeeded() {
823
+ guard let driveManager else {
824
+ return
825
+ }
826
+ driveManager.pausesLocationUpdatesAutomatically = false
827
+ let backgroundModes = Bundle.main.object(forInfoDictionaryKey: "UIBackgroundModes") as? [String]
828
+ if backgroundModes?.contains("location") == true {
829
+ driveManager.allowsBackgroundLocationUpdates = true
830
+ }
831
+ }
832
+
833
+ private func applyWalkManagerBackgroundLocationOptionsIfNeeded() {
834
+ guard let walkManager else {
835
+ return
836
+ }
837
+ walkManager.pausesLocationUpdatesAutomatically = false
838
+ let backgroundModes = Bundle.main.object(forInfoDictionaryKey: "UIBackgroundModes") as? [String]
839
+ if backgroundModes?.contains("location") == true {
840
+ walkManager.allowsBackgroundLocationUpdates = true
841
+ }
842
+ }
843
+
844
+ private func applyRideManagerBackgroundLocationOptionsIfNeeded() {
845
+ guard let rideManager else {
846
+ return
847
+ }
848
+ rideManager.pausesLocationUpdatesAutomatically = false
849
+ let backgroundModes = Bundle.main.object(forInfoDictionaryKey: "UIBackgroundModes") as? [String]
850
+ if backgroundModes?.contains("location") == true {
851
+ rideManager.allowsBackgroundLocationUpdates = true
852
+ }
853
+ }
854
+
855
+ private func hasBackgroundAudioModeEnabled() -> Bool {
856
+ let backgroundModes = Bundle.main.object(forInfoDictionaryKey: "UIBackgroundModes") as? [String]
857
+ return backgroundModes?.contains("audio") == true
858
+ }
859
+
860
+ private func activateNavigationAudioSessionIfNeeded(reason: String) {
861
+ guard enableVoice else {
862
+ return
863
+ }
864
+
865
+ if !hasBackgroundAudioModeEnabled(), !hasLoggedMissingBackgroundAudioMode {
866
+ hasLoggedMissingBackgroundAudioMode = true
867
+ NSLog(
868
+ "[ExpoGaodeMapNaviView][Audio] UIBackgroundModes 缺少 audio,切后台后语音可能中断。reason=%@",
869
+ reason
870
+ )
871
+ }
872
+
873
+ let audioSession = AVAudioSession.sharedInstance()
874
+ do {
875
+ try audioSession.setCategory(
876
+ .playback,
877
+ mode: .voicePrompt,
878
+ options: [.duckOthers, .allowBluetooth, .allowBluetoothA2DP]
879
+ )
880
+ try audioSession.setActive(true)
881
+ isNavigationAudioSessionActive = true
882
+ NSLog("[ExpoGaodeMapNaviView][Audio] activated. reason=%@", reason)
883
+ } catch {
884
+ NSLog(
885
+ "[ExpoGaodeMapNaviView][Audio] activate failed. reason=%@ error=%@",
886
+ reason,
887
+ String(describing: error)
888
+ )
889
+ }
890
+ }
891
+
892
+ private func deactivateNavigationAudioSessionIfNeeded(reason: String) {
893
+ guard isNavigationAudioSessionActive else {
894
+ return
895
+ }
896
+ let audioSession = AVAudioSession.sharedInstance()
897
+ do {
898
+ try audioSession.setActive(false, options: [.notifyOthersOnDeactivation])
899
+ isNavigationAudioSessionActive = false
900
+ NSLog("[ExpoGaodeMapNaviView][Audio] deactivated. reason=%@", reason)
901
+ } catch {
902
+ NSLog(
903
+ "[ExpoGaodeMapNaviView][Audio] deactivate failed. reason=%@ error=%@",
904
+ reason,
905
+ String(describing: error)
906
+ )
907
+ }
908
+ }
909
+
526
910
  private func resetTransientNavigationState() {
911
+ hasStartedNavi = false
912
+ hasReceivedFirstNaviData = false
913
+ lastKnownSpeed = 0
914
+ currentRouteTotalLength = nil
915
+ trafficBarTotalLength = nil
527
916
  lastNavigationInfoPayload = nil
528
917
  lastTurnIconType = nil
529
918
  lastNextTurnIconType = nil
919
+ lastTurnIconBase64 = nil
530
920
  clearCachedTurnIconUris()
531
921
  isCrossVisible = false
532
922
  isLaneInfoVisible = false
923
+ resetCustomWaypointArrivalState()
533
924
  emitVisualStateUpdate()
925
+ NavigationLiveActivityManager.shared.stop()
926
+ deactivateNavigationAudioSessionIfNeeded(reason: "reset_transient_navigation_state")
534
927
  }
535
928
 
536
929
  private func emitVisualStateUpdate() {
@@ -546,11 +939,13 @@ public class ExpoGaodeMapNaviView: ExpoView {
546
939
  private func emitNavigationInfoUpdate(_ payload: [String: Any]) {
547
940
  (driveView as? ExpoGaodeMapCustomDriveView)?.refreshSuppressedTopInfoUIIfNeeded()
548
941
  var nextPayload = payload
549
- if let lastTurnIconType {
942
+ let payloadIconType = payloadIntValue(nextPayload["iconType"]) ?? 0
943
+ if payloadIconType <= 0, let lastTurnIconType {
550
944
  nextPayload["iconType"] = lastTurnIconType
551
945
  nextPayload["iconDirection"] = lastTurnIconType
552
946
  }
553
- if let lastNextTurnIconType {
947
+ let payloadNextIconType = payloadIntValue(nextPayload["nextIconType"]) ?? 0
948
+ if payloadNextIconType <= 0, let lastNextTurnIconType {
554
949
  nextPayload["nextIconType"] = lastNextTurnIconType
555
950
  }
556
951
  if let lastTurnIconImageUri {
@@ -563,15 +958,9 @@ public class ExpoGaodeMapNaviView: ExpoView {
563
958
  } else {
564
959
  nextPayload.removeValue(forKey: "nextTurnIconImage")
565
960
  }
566
- if
567
- let retainDistance = nextPayload["pathRetainDistance"] as? Int,
568
- let driveDistance = nextPayload["driveDistance"] as? Int,
569
- driveDistance > 0
570
- {
571
- currentRouteTotalLength = retainDistance + driveDistance
572
- }
573
961
  lastNavigationInfoPayload = nextPayload
574
962
  onNavigationInfoUpdate(nextPayload)
963
+ syncNavigationLiveActivity(payload: nextPayload)
575
964
  }
576
965
 
577
966
  private func reemitLastNavigationInfoIfNeeded() {
@@ -600,77 +989,338 @@ public class ExpoGaodeMapNaviView: ExpoView {
600
989
 
601
990
  lastNavigationInfoPayload = nextPayload
602
991
  onNavigationInfoUpdate(nextPayload)
992
+ syncNavigationLiveActivity(payload: nextPayload)
603
993
  }
604
994
 
605
- private func cacheTurnIconImage(_ image: UIImage?, prefix: String, previousUri: String?) -> String? {
606
- guard let image, let data = image.pngData() else {
607
- if let previousUri {
608
- deleteCachedTurnIcon(at: previousUri)
609
- }
610
- return nil
995
+ private func resolveAppDisplayName() -> String {
996
+ let info = Bundle.main.infoDictionary
997
+ if let displayName = info?["CFBundleDisplayName"] as? String, !displayName.isEmpty {
998
+ return displayName
999
+ }
1000
+ if let appName = info?["CFBundleName"] as? String, !appName.isEmpty {
1001
+ return appName
611
1002
  }
1003
+ return "导航"
1004
+ }
612
1005
 
613
- let filename = "\(prefix)_\(UUID().uuidString).png"
614
- let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent(filename)
1006
+ private func payloadIntValue(_ value: Any?) -> Int? {
1007
+ if let intValue = value as? Int {
1008
+ return intValue
1009
+ }
1010
+ if let numberValue = value as? NSNumber {
1011
+ return numberValue.intValue
1012
+ }
1013
+ if let doubleValue = value as? Double {
1014
+ return Int(doubleValue)
1015
+ }
1016
+ if let floatValue = value as? Float {
1017
+ return Int(floatValue)
1018
+ }
1019
+ return nil
1020
+ }
615
1021
 
616
- do {
617
- try data.write(to: fileURL, options: .atomic)
618
- if let previousUri, previousUri != fileURL.absoluteString {
619
- deleteCachedTurnIcon(at: previousUri)
620
- }
621
- return fileURL.absoluteString
622
- } catch {
623
- return previousUri
1022
+ private func makeLiveActivitySnapshot(payload: [String: Any]?) -> NavigationLiveActivitySnapshot? {
1023
+ guard let payload else {
1024
+ return nil
624
1025
  }
1026
+
1027
+ let remainDistance = payloadIntValue(payload["pathRetainDistance"]) ?? 0
1028
+ let routeTotalDistance = max(
1029
+ trafficBarTotalLength ?? 0,
1030
+ currentRouteTotalLength ?? 0,
1031
+ remainDistance
1032
+ )
1033
+ let remainTime = payloadIntValue(payload["pathRetainTime"]) ?? 0
1034
+ let stepRemainDistance = payloadIntValue(payload["curStepRetainDistance"]) ?? 0
1035
+ let iconType = payloadIntValue(payload["iconType"]) ?? 0
1036
+ let currentRoadName = (payload["currentRoadName"] as? String) ?? ""
1037
+ let nextRoadName = (payload["nextRoadName"] as? String) ?? ""
1038
+
1039
+ return NavigationLiveActivitySnapshot(
1040
+ appName: resolveAppDisplayName(),
1041
+ currentRoadName: currentRoadName,
1042
+ nextRoadName: nextRoadName,
1043
+ pathRetainDistance: remainDistance,
1044
+ routeTotalDistance: routeTotalDistance,
1045
+ pathRetainTime: remainTime,
1046
+ curStepRetainDistance: stepRemainDistance,
1047
+ iconType: iconType,
1048
+ turnIconBase64: lastTurnIconBase64
1049
+ )
625
1050
  }
626
1051
 
627
- private func deleteCachedTurnIcon(at uriString: String) {
628
- guard let fileURL = URL(string: uriString), fileURL.isFileURL else {
1052
+ private func syncNavigationLiveActivity(payload: [String: Any]?) {
1053
+ guard iosLiveActivityEnabled else {
629
1054
  return
630
1055
  }
631
- try? FileManager.default.removeItem(at: fileURL)
1056
+ guard hasStartedNavi else {
1057
+ return
1058
+ }
1059
+
1060
+ guard let snapshot = makeLiveActivitySnapshot(payload: payload) else {
1061
+ return
1062
+ }
1063
+ NavigationLiveActivityManager.shared.startOrUpdate(snapshot: snapshot)
632
1064
  }
633
1065
 
634
- private func clearCachedTurnIconUris() {
635
- if let lastTurnIconImageUri {
636
- deleteCachedTurnIcon(at: lastTurnIconImageUri)
1066
+ private func syncNavigationLiveActivityWithLastPayload() {
1067
+ syncNavigationLiveActivity(payload: lastNavigationInfoPayload)
1068
+ }
1069
+
1070
+ private func normalizedTurnIconImage(_ image: UIImage) -> UIImage {
1071
+ guard image.imageOrientation != .up else {
1072
+ return image
637
1073
  }
638
- if let lastNextTurnIconImageUri {
639
- deleteCachedTurnIcon(at: lastNextTurnIconImageUri)
1074
+ let renderer = UIGraphicsImageRenderer(size: image.size)
1075
+ return renderer.image { _ in
1076
+ image.draw(in: CGRect(origin: .zero, size: image.size))
640
1077
  }
641
- lastTurnIconImageUri = nil
642
- lastNextTurnIconImageUri = nil
643
1078
  }
644
1079
 
645
- private func splitLaneInfoString(_ value: String) -> [String] {
646
- value
647
- .split(separator: "|", omittingEmptySubsequences: false)
648
- .map { $0.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() }
649
- }
1080
+ private func trimmedTransparentBoundsImage(_ image: UIImage) -> UIImage {
1081
+ guard let cgImage = image.cgImage else {
1082
+ return image
1083
+ }
1084
+ guard let dataProvider = cgImage.dataProvider, let data = dataProvider.data else {
1085
+ return image
1086
+ }
650
1087
 
651
- private func parseLaneToken(_ token: String, kind: LaneStringKind) -> Int? {
652
- guard !token.isEmpty else {
653
- return nil
1088
+ let bytes = CFDataGetBytePtr(data)
1089
+ let bytesPerPixel = max(cgImage.bitsPerPixel / 8, 0)
1090
+ let bytesPerRow = cgImage.bytesPerRow
1091
+ let width = cgImage.width
1092
+ let height = cgImage.height
1093
+ guard let bytes, bytesPerPixel >= 4, width > 0, height > 0 else {
1094
+ return image
654
1095
  }
655
1096
 
656
- if token == "255" || token == "ff" {
657
- return 255
1097
+ let alphaOffset: Int
1098
+ switch cgImage.alphaInfo {
1099
+ case .premultipliedLast, .last, .noneSkipLast:
1100
+ alphaOffset = 3
1101
+ case .premultipliedFirst, .first, .noneSkipFirst:
1102
+ alphaOffset = 0
1103
+ default:
1104
+ return image
1105
+ }
1106
+
1107
+ var minX = width
1108
+ var minY = height
1109
+ var maxX = -1
1110
+ var maxY = -1
1111
+ let alphaThreshold: UInt8 = 8
1112
+
1113
+ for y in 0..<height {
1114
+ let rowStart = y * bytesPerRow
1115
+ for x in 0..<width {
1116
+ let pixelStart = rowStart + x * bytesPerPixel
1117
+ let alpha = bytes[pixelStart + alphaOffset]
1118
+ if alpha > alphaThreshold {
1119
+ minX = min(minX, x)
1120
+ minY = min(minY, y)
1121
+ maxX = max(maxX, x)
1122
+ maxY = max(maxY, y)
1123
+ }
1124
+ }
658
1125
  }
659
1126
 
660
- // Older iOS callbacks may use `f` as filler / unavailable lane marker.
661
- if token == "f" {
662
- return kind == .background ? 255 : 255
1127
+ guard maxX >= minX, maxY >= minY else {
1128
+ return image
663
1129
  }
664
1130
 
665
- if let decimalValue = Int(token) {
666
- return decimalValue
1131
+ let cropRect = CGRect(
1132
+ x: minX,
1133
+ y: minY,
1134
+ width: (maxX - minX + 1),
1135
+ height: (maxY - minY + 1)
1136
+ )
1137
+ guard let croppedCGImage = cgImage.cropping(to: cropRect) else {
1138
+ return image
667
1139
  }
1140
+ return UIImage(cgImage: croppedCGImage, scale: image.scale, orientation: .up)
1141
+ }
668
1142
 
669
- switch token {
670
- case "a":
671
- return 10
672
- case "b":
673
- return 11
1143
+ private func aspectFitRect(sourceSize: CGSize, targetSize: CGSize) -> CGRect {
1144
+ guard sourceSize.width > 0, sourceSize.height > 0, targetSize.width > 0, targetSize.height > 0 else {
1145
+ return CGRect(origin: .zero, size: targetSize)
1146
+ }
1147
+ let scale = min(targetSize.width / sourceSize.width, targetSize.height / sourceSize.height)
1148
+ let drawSize = CGSize(width: sourceSize.width * scale, height: sourceSize.height * scale)
1149
+ return CGRect(
1150
+ x: (targetSize.width - drawSize.width) / 2.0,
1151
+ y: (targetSize.height - drawSize.height) / 2.0,
1152
+ width: drawSize.width,
1153
+ height: drawSize.height
1154
+ )
1155
+ }
1156
+
1157
+ private func encodeTurnIconForLiveActivity(_ image: UIImage?) -> String? {
1158
+ guard let image else {
1159
+ return nil
1160
+ }
1161
+
1162
+ let maxEncodedLength = 2600
1163
+ let targetSizes: [CGSize] = [
1164
+ CGSize(width: 34, height: 34),
1165
+ CGSize(width: 30, height: 30),
1166
+ CGSize(width: 26, height: 26),
1167
+ CGSize(width: 28, height: 28),
1168
+ CGSize(width: 24, height: 24),
1169
+ CGSize(width: 20, height: 20)
1170
+ ]
1171
+ let preparedImage = trimmedTransparentBoundsImage(normalizedTurnIconImage(image))
1172
+
1173
+ for size in targetSizes {
1174
+ let renderer = UIGraphicsImageRenderer(size: size)
1175
+ let rendered = renderer.image { _ in
1176
+ let rect = aspectFitRect(sourceSize: preparedImage.size, targetSize: size)
1177
+ preparedImage.draw(in: rect)
1178
+ }
1179
+
1180
+ if let pngData = rendered.pngData() {
1181
+ let pngBase64 = pngData.base64EncodedString()
1182
+ if pngBase64.count <= maxEncodedLength {
1183
+ return pngBase64
1184
+ }
1185
+ }
1186
+ }
1187
+
1188
+ NSLog("[ExpoGaodeMapNaviView][LiveActivity] turn icon dropped because encoded payload is too large")
1189
+ return nil
1190
+ }
1191
+
1192
+ private func fallbackTurnSymbolName(for iconType: Int) -> String {
1193
+ switch iconType {
1194
+ case 2:
1195
+ return "arrow.turn.up.left"
1196
+ case 3:
1197
+ return "arrow.turn.up.right"
1198
+ case 4:
1199
+ return "arrow.up.left"
1200
+ case 5:
1201
+ return "arrow.up.right"
1202
+ case 6:
1203
+ return "arrow.down.left"
1204
+ case 7:
1205
+ return "arrow.down.right"
1206
+ case 8:
1207
+ return "arrow.uturn.left"
1208
+ case 9, 20:
1209
+ return "arrow.up"
1210
+ case 11, 12:
1211
+ return "arrow.clockwise.circle"
1212
+ case 15:
1213
+ return "flag.checkered"
1214
+ case 19:
1215
+ return "arrow.uturn.right"
1216
+ case 65:
1217
+ return "arrow.left.circle"
1218
+ case 66:
1219
+ return "arrow.right.circle"
1220
+ default:
1221
+ return "arrow.up"
1222
+ }
1223
+ }
1224
+
1225
+ private func fallbackTurnIconImage(iconType: Int) -> UIImage? {
1226
+ let config = UIImage.SymbolConfiguration(pointSize: 36, weight: .bold)
1227
+ return UIImage(systemName: fallbackTurnSymbolName(for: iconType), withConfiguration: config)?
1228
+ .withTintColor(.white, renderingMode: .alwaysOriginal)
1229
+ }
1230
+
1231
+ private func resolveTravelTurnIconImage(iconType: Int) -> UIImage? {
1232
+ let iconEnum = AMapNaviIconType(rawValue: iconType) ?? .none
1233
+ if activeScene == .ride {
1234
+ return AMapNaviRideView.rideViewTurnIconImage(with: iconEnum)
1235
+ }
1236
+ return fallbackTurnIconImage(iconType: iconType)
1237
+ }
1238
+
1239
+ private func updateCachedTurnIconForTravelSceneIfNeeded(iconType: Int) {
1240
+ guard activeScene != .drive else {
1241
+ return
1242
+ }
1243
+ let image = resolveTravelTurnIconImage(iconType: iconType)
1244
+ if let encoded = encodeTurnIconForLiveActivity(image) {
1245
+ lastTurnIconBase64 = encoded
1246
+ }
1247
+ lastTurnIconImageUri = cacheTurnIconImage(
1248
+ image,
1249
+ prefix: "travel_turn_icon",
1250
+ previousUri: lastTurnIconImageUri
1251
+ )
1252
+ }
1253
+
1254
+ private func cacheTurnIconImage(_ image: UIImage?, prefix: String, previousUri: String?) -> String? {
1255
+ guard let image, let data = image.pngData() else {
1256
+ if let previousUri {
1257
+ deleteCachedTurnIcon(at: previousUri)
1258
+ }
1259
+ return nil
1260
+ }
1261
+
1262
+ let filename = "\(prefix)_\(UUID().uuidString).png"
1263
+ let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent(filename)
1264
+
1265
+ do {
1266
+ try data.write(to: fileURL, options: .atomic)
1267
+ if let previousUri, previousUri != fileURL.absoluteString {
1268
+ deleteCachedTurnIcon(at: previousUri)
1269
+ }
1270
+ return fileURL.absoluteString
1271
+ } catch {
1272
+ return previousUri
1273
+ }
1274
+ }
1275
+
1276
+ private func deleteCachedTurnIcon(at uriString: String) {
1277
+ guard let fileURL = URL(string: uriString), fileURL.isFileURL else {
1278
+ return
1279
+ }
1280
+ try? FileManager.default.removeItem(at: fileURL)
1281
+ }
1282
+
1283
+ private func clearCachedTurnIconUris() {
1284
+ if let lastTurnIconImageUri {
1285
+ deleteCachedTurnIcon(at: lastTurnIconImageUri)
1286
+ }
1287
+ if let lastNextTurnIconImageUri {
1288
+ deleteCachedTurnIcon(at: lastNextTurnIconImageUri)
1289
+ }
1290
+ lastTurnIconImageUri = nil
1291
+ lastNextTurnIconImageUri = nil
1292
+ lastTurnIconBase64 = nil
1293
+ }
1294
+
1295
+ private func splitLaneInfoString(_ value: String) -> [String] {
1296
+ value
1297
+ .split(separator: "|", omittingEmptySubsequences: false)
1298
+ .map { $0.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() }
1299
+ }
1300
+
1301
+ private func parseLaneToken(_ token: String, kind: LaneStringKind) -> Int? {
1302
+ guard !token.isEmpty else {
1303
+ return nil
1304
+ }
1305
+
1306
+ if token == "255" || token == "ff" {
1307
+ return 255
1308
+ }
1309
+
1310
+ // Older iOS callbacks may use `f` as filler / unavailable lane marker.
1311
+ if token == "f" {
1312
+ return kind == .background ? 255 : 255
1313
+ }
1314
+
1315
+ if let decimalValue = Int(token) {
1316
+ return decimalValue
1317
+ }
1318
+
1319
+ switch token {
1320
+ case "a":
1321
+ return 10
1322
+ case "b":
1323
+ return 11
674
1324
  case "c":
675
1325
  return 12
676
1326
  case "d":
@@ -732,39 +1382,90 @@ public class ExpoGaodeMapNaviView: ExpoView {
732
1382
  }
733
1383
 
734
1384
  private func applyViewOptions() {
735
- // 通用属性
736
- driveView?.showUIElements = showUIElements
737
- driveView?.showCamera = showCamera
738
- driveView?.autoSwitchShowModeToCarPositionLocked = autoLockCar
739
- driveView?.autoZoomMapLevel = autoChangeZoom
740
- driveView?.mapShowTraffic = trafficLayerEnabled
741
- driveView?.showCrossImage = realCrossDisplay
742
- driveView?.trackingMode = naviMode == 0 ? .carNorth : .mapNorth
743
-
744
- // iOS 特有属性
745
- driveView?.showRoute = showRoute
746
- driveView?.showTurnArrow = showTurnArrow
747
- driveView?.showTrafficBar = showTrafficBar
1385
+ switch activeScene {
1386
+ case .drive:
1387
+ applyDriveViewOptions()
1388
+ case .walk:
1389
+ applyWalkViewOptions()
1390
+ case .ride:
1391
+ applyRideViewOptions()
1392
+ }
1393
+ applyCustomAnnotationImages()
1394
+ applyCustomUILayoutOptionsIfNeeded()
1395
+ }
1396
+
1397
+ private func applyDriveViewOptions() {
1398
+ guard let driveView else {
1399
+ return
1400
+ }
1401
+ (driveView as? ExpoGaodeMapCustomDriveView)?.suppressBottomRightUI = showUIElements == false
1402
+ driveView.showUIElements = pendingShowUIElements ?? showUIElements
1403
+ driveView.showCamera = showCamera
1404
+ driveView.autoSwitchShowModeToCarPositionLocked = autoLockCar
1405
+ driveView.autoZoomMapLevel = autoChangeZoom
1406
+ driveView.mapShowTraffic = trafficLayerEnabled
1407
+ driveView.showCrossImage = realCrossDisplay
1408
+ driveView.showRoute = showRoute
1409
+ driveView.showTrafficBar = showTrafficBar
1410
+ driveView.showTrafficButton = showTrafficButton
1411
+ driveView.showBackupRoute = showBackupRoute
1412
+ driveView.showEagleMap = showEagleMap
1413
+ driveView.showVectorline = showVectorline
1414
+ driveView.showTrafficLights = showTrafficLights
1415
+ driveView.showDriveCongestion = showDriveCongestion
1416
+ driveView.showTrafficLightView = showTrafficLightView
1417
+ applyNaviMode(naviMode)
1418
+ applyShowMode(showMode)
1419
+ applyShowTurnArrow(showTurnArrow)
1420
+ applyShowBrowseRouteButton(showBrowseRouteButton)
1421
+ applyShowMoreButton(showMoreButton)
1422
+ applyShowGreyAfterPass(showGreyAfterPass)
1423
+ if let showCompassEnabled {
1424
+ applyShowCompassEnabled(showCompassEnabled)
1425
+ }
1426
+ applyLineWidth(lineWidth)
1427
+ applyScreenAnchor(screenAnchor)
1428
+ applyMapViewModeType(mapViewModeType)
748
1429
  applyTrafficBarFrame(trafficBarFrame)
749
1430
  applyTrafficBarColors(trafficBarColors)
750
- driveView?.showBrowseRouteButton = showBrowseRouteButton
751
- driveView?.showMoreButton = showMoreButton
752
- driveView?.showTrafficButton = showTrafficButton
753
- driveView?.showBackupRoute = showBackupRoute
754
- driveView?.showEagleMap = showEagleMap
755
- driveView?.showGreyAfterPass = showGreyAfterPass
756
- driveView?.showVectorline = showVectorline
757
- driveView?.showTrafficLights = showTrafficLights
1431
+ }
1432
+
1433
+ private func applyWalkViewOptions() {
1434
+ guard let walkView else {
1435
+ return
1436
+ }
1437
+ walkView.showUIElements = pendingShowUIElements ?? showUIElements
1438
+ applyNaviMode(naviMode)
1439
+ applyShowMode(showMode)
1440
+ applyShowTurnArrow(showTurnArrow)
1441
+ applyShowBrowseRouteButton(showBrowseRouteButton)
1442
+ applyShowMoreButton(showMoreButton)
1443
+ applyShowGreyAfterPass(showGreyAfterPass)
758
1444
  if let showCompassEnabled {
759
- driveView?.showCompass = showCompassEnabled
1445
+ applyShowCompassEnabled(showCompassEnabled)
1446
+ }
1447
+ applyLineWidth(lineWidth)
1448
+ applyScreenAnchor(screenAnchor)
1449
+ applyMapViewModeType(mapViewModeType)
1450
+ }
1451
+
1452
+ private func applyRideViewOptions() {
1453
+ guard let rideView else {
1454
+ return
760
1455
  }
761
- driveView?.showDriveCongestion = showDriveCongestion
762
- driveView?.showTrafficLightView = showTrafficLightView
763
- if lineWidth > 0 {
764
- driveView?.lineWidth = lineWidth
1456
+ rideView.showUIElements = pendingShowUIElements ?? showUIElements
1457
+ applyNaviMode(naviMode)
1458
+ applyShowMode(showMode)
1459
+ applyShowTurnArrow(showTurnArrow)
1460
+ applyShowBrowseRouteButton(showBrowseRouteButton)
1461
+ applyShowMoreButton(showMoreButton)
1462
+ applyShowGreyAfterPass(showGreyAfterPass)
1463
+ if let showCompassEnabled {
1464
+ applyShowCompassEnabled(showCompassEnabled)
765
1465
  }
766
- applyCustomAnnotationImages()
767
- applyCustomUILayoutOptionsIfNeeded()
1466
+ applyLineWidth(lineWidth)
1467
+ applyScreenAnchor(screenAnchor)
1468
+ applyMapViewModeType(mapViewModeType)
768
1469
  }
769
1470
 
770
1471
  private func applyCustomAnnotationImages() {
@@ -776,6 +1477,100 @@ public class ExpoGaodeMapNaviView: ExpoView {
776
1477
  applyCameraImageSource()
777
1478
  }
778
1479
 
1480
+ private func applyCustomWaypointMarkerPayloads(_ payloads: [[String: Any]]?) {
1481
+ customWaypointMarkers = (payloads ?? []).compactMap { item in
1482
+ guard
1483
+ let latitude = item["latitude"] as? Double,
1484
+ let longitude = item["longitude"] as? Double
1485
+ else {
1486
+ return nil
1487
+ }
1488
+
1489
+ let title = (item["title"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
1490
+ return NaviCustomWaypointMarkerModel(
1491
+ latitude: latitude,
1492
+ longitude: longitude,
1493
+ title: (title?.isEmpty == false ? title : nil) ?? "途经"
1494
+ )
1495
+ }
1496
+
1497
+ refreshCustomWaypointAnnotations()
1498
+ }
1499
+
1500
+ private func clearCustomWaypointAnnotations() {
1501
+ guard let driveView else {
1502
+ renderedCustomWaypointAnnotations.removeAll()
1503
+ return
1504
+ }
1505
+
1506
+ for annotation in renderedCustomWaypointAnnotations {
1507
+ driveView.remove(annotation)
1508
+ }
1509
+ renderedCustomWaypointAnnotations.removeAll()
1510
+ }
1511
+
1512
+ private func refreshCustomWaypointAnnotations() {
1513
+ clearCustomWaypointAnnotations()
1514
+
1515
+ guard activeScene == .drive, let driveView else {
1516
+ return
1517
+ }
1518
+
1519
+ renderedCustomWaypointAnnotations = customWaypointMarkers.compactMap { marker in
1520
+ guard !marker.arrived else {
1521
+ return nil
1522
+ }
1523
+
1524
+ let coordinate = CLLocationCoordinate2D(
1525
+ latitude: marker.latitude,
1526
+ longitude: marker.longitude
1527
+ )
1528
+ let bubbleView = NaviCustomWaypointBubbleView(title: marker.title)
1529
+ guard let annotation = AMapNaviCompositeCustomAnnotation(
1530
+ coordinate: coordinate,
1531
+ view: bubbleView
1532
+ ) else {
1533
+ return nil
1534
+ }
1535
+
1536
+ driveView.add(annotation)
1537
+ return annotation
1538
+ }
1539
+ }
1540
+
1541
+ private func resetCustomWaypointArrivalState() {
1542
+ customWaypointMarkers = customWaypointMarkers.map { marker in
1543
+ var nextMarker = marker
1544
+ nextMarker.arrived = false
1545
+ return nextMarker
1546
+ }
1547
+ refreshCustomWaypointAnnotations()
1548
+ }
1549
+
1550
+ private func markNearestCustomWaypointArrived(_ point: AMapNaviPoint) {
1551
+ guard !customWaypointMarkers.isEmpty else {
1552
+ return
1553
+ }
1554
+
1555
+ let targetLatitude = Double(point.latitude)
1556
+ let targetLongitude = Double(point.longitude)
1557
+ let nextIndex = customWaypointMarkers.enumerated()
1558
+ .filter { !$0.element.arrived }
1559
+ .min { lhs, rhs in
1560
+ let lhsDistance = abs(lhs.element.latitude - targetLatitude) + abs(lhs.element.longitude - targetLongitude)
1561
+ let rhsDistance = abs(rhs.element.latitude - targetLatitude) + abs(rhs.element.longitude - targetLongitude)
1562
+ return lhsDistance < rhsDistance
1563
+ }?
1564
+ .offset
1565
+
1566
+ guard let nextIndex else {
1567
+ return
1568
+ }
1569
+
1570
+ customWaypointMarkers[nextIndex].arrived = true
1571
+ refreshCustomWaypointAnnotations()
1572
+ }
1573
+
779
1574
  private func resolveLocalImage(_ source: String) -> UIImage? {
780
1575
  if source.hasPrefix("file://") {
781
1576
  let path = String(source.dropFirst(7))
@@ -818,58 +1613,76 @@ public class ExpoGaodeMapNaviView: ExpoView {
818
1613
  apply(resolveLocalImage(source))
819
1614
  }
820
1615
 
821
- private func applyCarImageSource() {
822
- guard let driveView else {
823
- return
1616
+ private func resizeImageIfNeeded(_ image: UIImage?, targetSize: CGSize?) -> UIImage? {
1617
+ guard let image else {
1618
+ return nil
1619
+ }
1620
+
1621
+ guard let targetSize, targetSize.width > 0, targetSize.height > 0 else {
1622
+ return image
1623
+ }
1624
+
1625
+ let renderer = UIGraphicsImageRenderer(size: targetSize)
1626
+ return renderer.image { _ in
1627
+ image.draw(in: CGRect(origin: .zero, size: targetSize))
824
1628
  }
1629
+ }
1630
+
1631
+ private func applyCarImageSource() {
825
1632
  applyAnnotationImage(source: carImageSource, currentSource: { [weak self] in
826
1633
  self?.carImageSource
827
- }) { [weak driveView] image in
828
- driveView?.setCarImage(image)
1634
+ }) { [weak self, weak driveView, weak walkView, weak rideView] image in
1635
+ let resizedImage = self?.resizeImageIfNeeded(image, targetSize: self?.carImageSize) ?? image
1636
+ driveView?.setCarImage(resizedImage)
1637
+ walkView?.setCarImage(resizedImage)
1638
+ if let resizedImage {
1639
+ rideView?.setCarImageWithSize(resizedImage)
1640
+ } else {
1641
+ rideView?.setCarImage(nil)
1642
+ }
829
1643
  }
830
1644
  }
831
1645
 
832
1646
  private func applyCarCompassImageSource() {
833
- guard let driveView else {
834
- return
835
- }
836
1647
  applyAnnotationImage(source: carCompassImageSource, currentSource: { [weak self] in
837
1648
  self?.carCompassImageSource
838
- }) { [weak driveView] image in
1649
+ }) { [weak driveView, weak walkView, weak rideView] image in
839
1650
  driveView?.setCarCompassImage(image)
1651
+ walkView?.setCarCompassImage(image)
1652
+ rideView?.setCarCompassImage(image)
840
1653
  }
841
1654
  }
842
1655
 
843
1656
  private func applyStartPointImageSource() {
844
- guard let driveView else {
845
- return
846
- }
847
1657
  applyAnnotationImage(source: startPointImageSource, currentSource: { [weak self] in
848
1658
  self?.startPointImageSource
849
- }) { [weak driveView] image in
1659
+ }) { [weak driveView, weak walkView, weak rideView] image in
850
1660
  driveView?.setStartPointImage(image)
1661
+ walkView?.setStartPointImage(image)
1662
+ rideView?.setStartPointImage(image)
851
1663
  }
852
1664
  }
853
1665
 
854
1666
  private func applyWayPointImageSource() {
855
- guard let driveView else {
1667
+ guard let source = wayPointImageSource?.trimmingCharacters(in: .whitespacesAndNewlines), !source.isEmpty else {
856
1668
  return
857
1669
  }
858
1670
  applyAnnotationImage(source: wayPointImageSource, currentSource: { [weak self] in
859
1671
  self?.wayPointImageSource
860
- }) { [weak driveView] image in
1672
+ }) { [weak driveView, weak walkView, weak rideView] image in
861
1673
  driveView?.setWayPointImage(image)
1674
+ walkView?.setWayPointImage(image)
1675
+ rideView?.setWayPointImage(image)
862
1676
  }
863
1677
  }
864
1678
 
865
1679
  private func applyEndPointImageSource() {
866
- guard let driveView else {
867
- return
868
- }
869
1680
  applyAnnotationImage(source: endPointImageSource, currentSource: { [weak self] in
870
1681
  self?.endPointImageSource
871
- }) { [weak driveView] image in
1682
+ }) { [weak driveView, weak walkView, weak rideView] image in
872
1683
  driveView?.setEndPointImage(image)
1684
+ walkView?.setEndPointImage(image)
1685
+ rideView?.setEndPointImage(image)
873
1686
  }
874
1687
  }
875
1688
 
@@ -884,54 +1697,93 @@ public class ExpoGaodeMapNaviView: ExpoView {
884
1697
  }
885
1698
  }
886
1699
 
887
- private func applyShowUIElementsToDriveViewIfReady() {
888
- guard let driveView else {
1700
+ private func applyShowUIElementsToActiveViewIfReady() {
1701
+ guard currentNaviView() != nil else {
889
1702
  pendingShowUIElements = showUIElements
890
1703
  return
891
1704
  }
892
1705
  let value = pendingShowUIElements ?? showUIElements
893
1706
  pendingShowUIElements = nil
894
- driveView.showUIElements = value
1707
+
1708
+ switch activeScene {
1709
+ case .drive:
1710
+ (driveView as? ExpoGaodeMapCustomDriveView)?.suppressBottomRightUI = value == false
1711
+ driveView?.showUIElements = value
1712
+ case .walk:
1713
+ walkView?.showUIElements = value
1714
+ case .ride:
1715
+ rideView?.showUIElements = value
1716
+ }
1717
+
895
1718
  applyCustomUILayoutOptionsIfNeeded()
896
1719
  }
897
1720
 
898
1721
  private func applyCustomUILayoutOptionsIfNeeded() {
899
- guard let driveView else {
1722
+ guard showUIElements == false else {
1723
+ (driveView as? ExpoGaodeMapCustomDriveView)?.suppressBottomRightUI = false
900
1724
  return
901
1725
  }
902
1726
 
903
- guard showUIElements == false else {
904
- return
1727
+ switch activeScene {
1728
+ case .drive:
1729
+ guard let driveView else {
1730
+ return
1731
+ }
1732
+ (driveView as? ExpoGaodeMapCustomDriveView)?.suppressBottomRightUI = true
1733
+ driveView.showCrossImage = realCrossDisplay
1734
+ driveView.showTrafficBar = showTrafficBar
1735
+ applyTrafficBarFrame(trafficBarFrame)
1736
+ applyTrafficBarColors(trafficBarColors)
1737
+ driveView.screenAnchor = screenAnchor
1738
+ driveView.setNeedsLayout()
1739
+ driveView.layoutIfNeeded()
1740
+ case .walk:
1741
+ guard let walkView else {
1742
+ return
1743
+ }
1744
+ walkView.screenAnchor = screenAnchor
1745
+ walkView.setNeedsLayout()
1746
+ walkView.layoutIfNeeded()
1747
+ case .ride:
1748
+ guard let rideView else {
1749
+ return
1750
+ }
1751
+ rideView.screenAnchor = screenAnchor
1752
+ rideView.setNeedsLayout()
1753
+ rideView.layoutIfNeeded()
905
1754
  }
906
1755
 
907
- // In custom UI mode, these properties are controlled independently from
908
- // the built-in widget layer and should be re-applied after toggling it off.
909
- driveView.showCrossImage = realCrossDisplay
910
- driveView.showTrafficBar = showTrafficBar
911
- applyTrafficBarFrame(trafficBarFrame)
912
- applyTrafficBarColors(trafficBarColors)
913
- driveView.screenAnchor = screenAnchor
914
- driveView.setNeedsLayout()
915
- driveView.layoutIfNeeded()
916
1756
  scheduleOverviewRouteVisibleRegionRefresh()
917
1757
  }
918
1758
 
919
1759
  private func refreshOverviewRouteVisibleRegionIfNeeded() {
920
- guard showUIElements == false, let driveView else {
1760
+ guard showUIElements == false else {
921
1761
  return
922
1762
  }
923
1763
 
924
- guard driveView.showMode == .overview else {
925
- return
1764
+ switch activeScene {
1765
+ case .drive:
1766
+ guard let driveView, driveView.showMode == .overview else {
1767
+ return
1768
+ }
1769
+ driveView.updateRoutePolylineInTheVisualRangeWhenTheShowModeIsOverview()
1770
+ case .walk:
1771
+ guard let walkView, walkView.showMode == .overview else {
1772
+ return
1773
+ }
1774
+ walkView.updateRoutePolylineInTheVisualRangeWhenTheShowModeIsOverview()
1775
+ case .ride:
1776
+ guard let rideView, rideView.showMode == .overview else {
1777
+ return
1778
+ }
1779
+ rideView.updateRoutePolylineInTheVisualRangeWhenTheShowModeIsOverview()
926
1780
  }
927
-
928
- driveView.updateRoutePolylineInTheVisualRangeWhenTheShowModeIsOverview()
929
1781
  }
930
1782
 
931
1783
  private func scheduleOverviewRouteVisibleRegionRefresh() {
932
1784
  DispatchQueue.main.async { [weak self] in
933
- self?.driveView?.setNeedsLayout()
934
- self?.driveView?.layoutIfNeeded()
1785
+ self?.currentNaviView()?.setNeedsLayout()
1786
+ self?.currentNaviView()?.layoutIfNeeded()
935
1787
  self?.refreshOverviewRouteVisibleRegionIfNeeded()
936
1788
  }
937
1789
  }
@@ -951,7 +1803,8 @@ public class ExpoGaodeMapNaviView: ExpoView {
951
1803
  return
952
1804
  }
953
1805
 
954
- guard frame.width > 0, frame.height > 0 else {
1806
+ guard showTrafficBar, frame.width > 0, frame.height > 0 else {
1807
+ driveView.tmcRouteFrame = .zero
955
1808
  return
956
1809
  }
957
1810
 
@@ -991,6 +1844,20 @@ public class ExpoGaodeMapNaviView: ExpoView {
991
1844
  }
992
1845
 
993
1846
  private func emitTrafficStatusesUpdate(_ trafficStatuses: [AMapNaviTrafficStatus]?) {
1847
+ let totalLengthFromTraffic = (trafficStatuses ?? []).reduce(0) { partial, status in
1848
+ partial + max(status.length, 0)
1849
+ }
1850
+
1851
+ if totalLengthFromTraffic > 0 {
1852
+ if let retainDistance = lastNavigationInfoPayload?["pathRetainDistance"] as? Int {
1853
+ if totalLengthFromTraffic >= retainDistance {
1854
+ trafficBarTotalLength = totalLengthFromTraffic
1855
+ }
1856
+ } else {
1857
+ trafficBarTotalLength = totalLengthFromTraffic
1858
+ }
1859
+ }
1860
+
994
1861
  // iOS 会提供 fineStatus;统一事件结构时保留它,方便 RN 自绘层按需细化颜色策略。
995
1862
  let items = (trafficStatuses ?? []).map { status in
996
1863
  var payload: [String: Any] = [
@@ -1005,7 +1872,12 @@ public class ExpoGaodeMapNaviView: ExpoView {
1005
1872
  "items": items
1006
1873
  ]
1007
1874
 
1008
- if let totalLength = currentRouteTotalLength, totalLength > 0 {
1875
+ let resolvedTotalLength = max(trafficBarTotalLength ?? 0, currentRouteTotalLength ?? 0)
1876
+ if resolvedTotalLength > 0 {
1877
+ payload["totalLength"] = resolvedTotalLength
1878
+ } else if totalLengthFromTraffic > 0 {
1879
+ payload["totalLength"] = totalLengthFromTraffic
1880
+ } else if let totalLength = currentRouteTotalLength, totalLength > 0 {
1009
1881
  payload["totalLength"] = totalLength
1010
1882
  }
1011
1883
 
@@ -1023,6 +1895,13 @@ public class ExpoGaodeMapNaviView: ExpoView {
1023
1895
 
1024
1896
  private func applyEnableVoice(_ enabled: Bool) {
1025
1897
  driveManager?.isUseInternalTTS = enabled
1898
+ walkManager?.isUseInternalTTS = enabled
1899
+ rideManager?.isUseInternalTTS = enabled
1900
+ if enabled {
1901
+ activateNavigationAudioSessionIfNeeded(reason: "enable_voice_true")
1902
+ } else {
1903
+ deactivateNavigationAudioSessionIfNeeded(reason: "enable_voice_false")
1904
+ }
1026
1905
  // 内置语音会自动播报,不需要手动调用 startSpeak/stopSpeak
1027
1906
  }
1028
1907
 
@@ -1043,43 +1922,260 @@ public class ExpoGaodeMapNaviView: ExpoView {
1043
1922
  }
1044
1923
 
1045
1924
  private func applyNaviMode(_ mode: Int) {
1046
- driveView?.trackingMode = mode == 0 ? .carNorth : .mapNorth
1925
+ let trackingMode: AMapNaviViewTrackingMode = mode == 0 ? .carNorth : .mapNorth
1926
+ switch activeScene {
1927
+ case .drive:
1928
+ driveView?.trackingMode = trackingMode
1929
+ case .walk:
1930
+ walkView?.trackingMode = trackingMode
1931
+ case .ride:
1932
+ rideView?.trackingMode = trackingMode
1933
+ }
1047
1934
  }
1048
1935
 
1049
1936
  private func applyShowMode(_ mode: Int) {
1050
- // 1: 锁车态, 2: 全览态, 3: 普通态
1051
- switch mode {
1052
- case 1:
1053
- driveView?.showMode = .carPositionLocked
1054
- case 2:
1055
- driveView?.showMode = .overview
1056
- case 3:
1057
- driveView?.showMode = .normal
1058
- default:
1059
- break
1937
+ switch activeScene {
1938
+ case .drive:
1939
+ switch mode {
1940
+ case 1:
1941
+ driveView?.showMode = .carPositionLocked
1942
+ case 2:
1943
+ driveView?.showMode = .overview
1944
+ case 3:
1945
+ driveView?.showMode = .normal
1946
+ default:
1947
+ break
1948
+ }
1949
+ case .walk:
1950
+ switch mode {
1951
+ case 1:
1952
+ walkView?.showMode = .carPositionLocked
1953
+ case 2:
1954
+ walkView?.showMode = .overview
1955
+ case 3:
1956
+ walkView?.showMode = .normal
1957
+ default:
1958
+ break
1959
+ }
1960
+ case .ride:
1961
+ switch mode {
1962
+ case 1:
1963
+ rideView?.showMode = .carPositionLocked
1964
+ case 2:
1965
+ rideView?.showMode = .overview
1966
+ case 3:
1967
+ rideView?.showMode = .normal
1968
+ default:
1969
+ break
1970
+ }
1060
1971
  }
1061
1972
  scheduleOverviewRouteVisibleRegionRefresh()
1062
1973
  }
1063
1974
 
1064
1975
  private func applyNightMode(_ enabled: Bool) {
1065
- driveView?.mapViewModeType = enabled ? .night : .day
1976
+ applyMapViewModeType(enabled ? 1 : 0)
1066
1977
  }
1067
1978
 
1068
1979
  private func applyMapViewModeType(_ type: Int) {
1069
- // 0: 白天, 1: 黑夜, 2: 自动切换, 3: 自定义
1980
+ let resolvedMode: AMapNaviViewMapModeType
1070
1981
  switch type {
1071
1982
  case 0:
1072
- driveView?.mapViewModeType = .day
1983
+ resolvedMode = .day
1073
1984
  case 1:
1074
- driveView?.mapViewModeType = .night
1985
+ resolvedMode = .night
1075
1986
  case 2:
1076
- driveView?.mapViewModeType = .dayNightAuto
1987
+ resolvedMode = .dayNightAuto
1077
1988
  case 3:
1078
- driveView?.mapViewModeType = .custom
1989
+ resolvedMode = .custom
1079
1990
  default:
1080
- break
1991
+ return
1992
+ }
1993
+
1994
+ switch activeScene {
1995
+ case .drive:
1996
+ driveView?.mapViewModeType = resolvedMode
1997
+ case .walk:
1998
+ walkView?.mapViewModeType = resolvedMode
1999
+ case .ride:
2000
+ rideView?.mapViewModeType = resolvedMode
2001
+ }
2002
+ }
2003
+
2004
+ private func applyShowTurnArrow(_ visible: Bool) {
2005
+ switch activeScene {
2006
+ case .drive:
2007
+ driveView?.showTurnArrow = visible
2008
+ case .walk:
2009
+ walkView?.showTurnArrow = visible
2010
+ case .ride:
2011
+ rideView?.showTurnArrow = visible
2012
+ }
2013
+ }
2014
+
2015
+ private func applyShowBrowseRouteButton(_ visible: Bool) {
2016
+ switch activeScene {
2017
+ case .drive:
2018
+ driveView?.showBrowseRouteButton = visible
2019
+ case .walk:
2020
+ walkView?.showBrowseRouteButton = visible
2021
+ case .ride:
2022
+ rideView?.showBrowseRouteButton = visible
2023
+ }
2024
+ }
2025
+
2026
+ private func applyShowMoreButton(_ visible: Bool) {
2027
+ switch activeScene {
2028
+ case .drive:
2029
+ driveView?.showMoreButton = visible
2030
+ case .walk:
2031
+ walkView?.showMoreButton = visible
2032
+ case .ride:
2033
+ rideView?.showMoreButton = visible
1081
2034
  }
1082
2035
  }
2036
+
2037
+ private func applyShowGreyAfterPass(_ enabled: Bool) {
2038
+ switch activeScene {
2039
+ case .drive:
2040
+ driveView?.showGreyAfterPass = enabled
2041
+ case .walk:
2042
+ walkView?.showGreyAfterPass = enabled
2043
+ case .ride:
2044
+ rideView?.showGreyAfterPass = enabled
2045
+ }
2046
+ }
2047
+
2048
+ private func applyShowCompassEnabled(_ enabled: Bool) {
2049
+ switch activeScene {
2050
+ case .drive:
2051
+ driveView?.showCompass = enabled
2052
+ case .walk:
2053
+ walkView?.showCompass = enabled
2054
+ case .ride:
2055
+ rideView?.showCompass = enabled
2056
+ }
2057
+ }
2058
+
2059
+ private func applyLineWidth(_ width: CGFloat) {
2060
+ switch activeScene {
2061
+ case .drive:
2062
+ driveView?.lineWidth = width
2063
+ case .walk:
2064
+ walkView?.lineWidth = width
2065
+ case .ride:
2066
+ rideView?.lineWidth = width
2067
+ }
2068
+ }
2069
+
2070
+ private func applyScreenAnchor(_ anchor: CGPoint) {
2071
+ switch activeScene {
2072
+ case .drive:
2073
+ driveView?.screenAnchor = anchor
2074
+ case .walk:
2075
+ walkView?.screenAnchor = anchor
2076
+ case .ride:
2077
+ rideView?.screenAnchor = anchor
2078
+ }
2079
+ }
2080
+
2081
+ private func applyBackgroundLocationOptionsForActiveScene() {
2082
+ switch activeScene {
2083
+ case .drive:
2084
+ applyDriveManagerBackgroundLocationOptionsIfNeeded()
2085
+ case .walk:
2086
+ applyWalkManagerBackgroundLocationOptionsIfNeeded()
2087
+ case .ride:
2088
+ applyRideManagerBackgroundLocationOptionsIfNeeded()
2089
+ }
2090
+ }
2091
+
2092
+ private func handleNavigationStarted(_ naviMode: AMapNaviMode) {
2093
+ hasStartedNavi = true
2094
+ applyBackgroundLocationOptionsForActiveScene()
2095
+ activateNavigationAudioSessionIfNeeded(reason: "did_start_navi")
2096
+ onNavigationStarted([
2097
+ "type": naviMode == .emulator ? 1 : 0,
2098
+ "isEmulator": naviMode == .emulator
2099
+ ])
2100
+ applyShowUIElementsToActiveViewIfReady()
2101
+ refreshCustomWaypointAnnotations()
2102
+ syncNavigationLiveActivityWithLastPayload()
2103
+ }
2104
+
2105
+ private func handleNavigationLocationUpdate(_ naviLocation: AMapNaviLocation) {
2106
+ if !hasReceivedFirstNaviData {
2107
+ hasReceivedFirstNaviData = true
2108
+ applyShowUIElementsToActiveViewIfReady()
2109
+ }
2110
+ lastKnownSpeed = Int(naviLocation.speed)
2111
+ onLocationUpdate([
2112
+ "latitude": naviLocation.coordinate.latitude,
2113
+ "longitude": naviLocation.coordinate.longitude,
2114
+ "speed": naviLocation.speed,
2115
+ "bearing": naviLocation.heading
2116
+ ])
2117
+ }
2118
+
2119
+ private func navigationInfoPayload(from naviInfo: AMapNaviInfo) -> [String: Any] {
2120
+ [
2121
+ "naviMode": naviInfo.naviMode.rawValue,
2122
+ "currentRoadName": naviInfo.currentRoadName ?? "",
2123
+ "nextRoadName": naviInfo.nextRoadName ?? "",
2124
+ "pathRetainDistance": naviInfo.routeRemainDistance,
2125
+ "pathRetainTime": naviInfo.routeRemainTime,
2126
+ "curStepRetainDistance": naviInfo.segmentRemainDistance,
2127
+ "curStepRetainTime": naviInfo.segmentRemainTime,
2128
+ "currentSpeed": lastKnownSpeed,
2129
+ "iconType": naviInfo.iconType.rawValue,
2130
+ "iconDirection": naviInfo.iconType.rawValue,
2131
+ "currentSegmentIndex": naviInfo.currentSegmentIndex,
2132
+ "currentLinkIndex": naviInfo.currentLinkIndex,
2133
+ "currentPointIndex": naviInfo.currentPointIndex,
2134
+ "routeRemainTrafficLightCount": naviInfo.routeRemainTrafficLightCount,
2135
+ "driveDistance": naviInfo.routeDriveDistance,
2136
+ "driveTime": naviInfo.routeDriveTime
2137
+ ]
2138
+ }
2139
+
2140
+ private func handleNavigationInfoUpdate(_ naviInfo: AMapNaviInfo) {
2141
+ if !hasReceivedFirstNaviData {
2142
+ hasReceivedFirstNaviData = true
2143
+ applyShowUIElementsToActiveViewIfReady()
2144
+ }
2145
+ updateCachedTurnIconForTravelSceneIfNeeded(iconType: Int(naviInfo.iconType.rawValue))
2146
+ emitNavigationInfoUpdate(navigationInfoPayload(from: naviInfo))
2147
+ }
2148
+
2149
+ private func handleNavigationSound(_ soundString: String, soundStringType: AMapNaviSoundType) {
2150
+ activateNavigationAudioSessionIfNeeded(reason: "play_navi_sound")
2151
+ onNavigationText([
2152
+ "type": soundStringType.rawValue,
2153
+ "text": soundString
2154
+ ])
2155
+ }
2156
+
2157
+ private func handleGpsSignalUpdate(_ gpsSignalStrength: AMapNaviGPSSignalStrength) {
2158
+ let rawValue = gpsSignalStrength.rawValue
2159
+ let isWeak = rawValue == AMapNaviGPSSignalStrength.weak.rawValue || rawValue == 0
2160
+ onGpsSignalWeak([
2161
+ "isWeak": isWeak
2162
+ ])
2163
+ }
2164
+
2165
+ private func handleWayPointArrived(index: Int, point: AMapNaviPoint? = nil) {
2166
+ if let point {
2167
+ markNearestCustomWaypointArrived(point)
2168
+ }
2169
+ onWayPointArrived([
2170
+ "index": index
2171
+ ])
2172
+ }
2173
+
2174
+ private func handleDidEndEmulatorNavi(reason: String) {
2175
+ resetTransientNavigationState()
2176
+ deactivateNavigationAudioSessionIfNeeded(reason: reason)
2177
+ onNavigationEnded([:])
2178
+ }
1083
2179
 
1084
2180
  // MARK: - Public Methods
1085
2181
  func startNavigation(startLat: Double, startLng: Double, endLat: Double, endLng: Double, promise: Promise) {
@@ -1101,8 +2197,8 @@ public class ExpoGaodeMapNaviView: ExpoView {
1101
2197
  promise.reject(code, formatError(error))
1102
2198
  return
1103
2199
  }
1104
-
1105
- rebindDriveManagerToView()
2200
+
2201
+ switchToScene(.drive)
1106
2202
  resetTransientNavigationState()
1107
2203
 
1108
2204
  startCoordinate = AMapNaviPoint.location(withLatitude: CGFloat(startLat), longitude: CGFloat(startLng))
@@ -1157,7 +2253,8 @@ public class ExpoGaodeMapNaviView: ExpoView {
1157
2253
  return
1158
2254
  }
1159
2255
 
1160
- rebindDriveManagerToView()
2256
+ let scene = NaviScene(rawValue: independentRouteManager.scene(for: token) ?? "") ?? .drive
2257
+ switchToScene(scene)
1161
2258
  resetTransientNavigationState()
1162
2259
 
1163
2260
  if let naviType {
@@ -1184,7 +2281,14 @@ public class ExpoGaodeMapNaviView: ExpoView {
1184
2281
  }
1185
2282
 
1186
2283
  func stopNavigation(promise: Promise) {
1187
- driveManager?.stopNavi()
2284
+ switch activeScene {
2285
+ case .drive:
2286
+ driveManager?.stopNavi()
2287
+ case .walk:
2288
+ walkManager?.stopNavi()
2289
+ case .ride:
2290
+ rideManager?.stopNavi()
2291
+ }
1188
2292
  resetTransientNavigationState()
1189
2293
  promise.resolve([
1190
2294
  "success": true,
@@ -1193,35 +2297,81 @@ public class ExpoGaodeMapNaviView: ExpoView {
1193
2297
  }
1194
2298
 
1195
2299
  func playCustomTTS(text: String, forcePlay: Bool, promise: Promise) {
1196
- // iOS 使用内置 TTS,会自动播报导航语音
1197
- promise.resolve([
1198
- "success": true
1199
- ])
2300
+ guard let manager = currentNaviManager() else {
2301
+ promise.reject("NAVI_MANAGER_UNAVAILABLE", "导航管理器尚未初始化")
2302
+ return
2303
+ }
2304
+ let success = manager.playTTS(text, forcePlay: forcePlay)
2305
+ if success {
2306
+ promise.resolve([
2307
+ "success": true
2308
+ ])
2309
+ } else {
2310
+ promise.reject("PLAY_TTS_FAILED", "当前场景暂不支持或正在播报其他导航语音")
2311
+ }
1200
2312
  }
1201
2313
 
1202
2314
  // MARK: - Lifecycle
1203
2315
  public override func layoutSubviews() {
1204
2316
  super.layoutSubviews()
1205
2317
  driveView?.frame = bounds
2318
+ walkView?.frame = bounds
2319
+ rideView?.frame = bounds
1206
2320
  scheduleOverviewRouteVisibleRegionRefresh()
1207
2321
  }
1208
2322
 
1209
2323
  deinit {
2324
+ NavigationLiveActivityManager.shared.stop()
2325
+ deactivateNavigationAudioSessionIfNeeded(reason: "deinit")
1210
2326
  driveManager?.stopNavi()
1211
- if let view = driveView {
1212
- driveManager?.removeDataRepresentative(view)
1213
- }
1214
- driveManager?.removeDataRepresentative(self)
1215
- driveManager?.delegate = nil
2327
+ walkManager?.stopNavi()
2328
+ rideManager?.stopNavi()
2329
+ clearCustomWaypointAnnotations()
2330
+ destroyDriveManagerIfNeeded()
2331
+ destroyWalkManagerIfNeeded()
2332
+ destroyRideManagerIfNeeded()
1216
2333
  clearCachedTurnIconUris()
1217
2334
  }
1218
2335
  }
1219
2336
 
1220
2337
  // MARK: - AMapNaviDriveManagerDelegate
1221
2338
  extension ExpoGaodeMapNaviView: AMapNaviDriveManagerDelegate {
2339
+ private func makeArrivedLiveActivitySnapshot() -> NavigationLiveActivitySnapshot {
2340
+ let remainDistance = payloadIntValue(lastNavigationInfoPayload?["pathRetainDistance"]) ?? 0
2341
+ let routeTotalDistance = max(
2342
+ trafficBarTotalLength ?? 0,
2343
+ currentRouteTotalLength ?? 0,
2344
+ remainDistance
2345
+ )
2346
+ return NavigationLiveActivitySnapshot(
2347
+ appName: resolveAppDisplayName(),
2348
+ currentRoadName: "",
2349
+ nextRoadName: "",
2350
+ pathRetainDistance: 0,
2351
+ routeTotalDistance: routeTotalDistance,
2352
+ pathRetainTime: 0,
2353
+ curStepRetainDistance: 0,
2354
+ iconType: 15,
2355
+ turnIconBase64: nil
2356
+ )
2357
+ }
2358
+
2359
+ private func handleArrivedDestination(source: String) {
2360
+ NSLog("[ExpoGaodeMapNaviView][LiveActivity] arrived destination callback received: %@", source)
2361
+ hasStartedNavi = false
2362
+ if iosLiveActivityEnabled {
2363
+ let arrivedSnapshot = makeArrivedLiveActivitySnapshot()
2364
+ NavigationLiveActivityManager.shared.showArrivedAndStop(snapshot: arrivedSnapshot, dismissAfter: 6)
2365
+ } else {
2366
+ NavigationLiveActivityManager.shared.stop()
2367
+ }
2368
+ onArriveDestination([:])
2369
+ }
1222
2370
 
1223
2371
  public func driveManager(onCalculateRouteSuccess driveManager: AMapNaviDriveManager) {
1224
2372
  hasStartedNavi = true
2373
+ applyDriveManagerBackgroundLocationOptionsIfNeeded()
2374
+ activateNavigationAudioSessionIfNeeded(reason: "calculate_route_success")
1225
2375
  onRouteCalculated([
1226
2376
  "success": true,
1227
2377
  "naviType": naviType
@@ -1234,9 +2384,10 @@ extension ExpoGaodeMapNaviView: AMapNaviDriveManagerDelegate {
1234
2384
  driveManager.startGPSNavi()
1235
2385
  }
1236
2386
 
1237
- applyShowUIElementsToDriveViewIfReady()
2387
+ applyShowUIElementsToActiveViewIfReady()
1238
2388
  DispatchQueue.main.async { [weak self] in
1239
2389
  self?.applyCustomAnnotationImages()
2390
+ self?.refreshCustomWaypointAnnotations()
1240
2391
  }
1241
2392
  }
1242
2393
 
@@ -1248,54 +2399,47 @@ extension ExpoGaodeMapNaviView: AMapNaviDriveManagerDelegate {
1248
2399
  }
1249
2400
 
1250
2401
  public func driveManager(_ driveManager: AMapNaviDriveManager, didStartNavi naviMode: AMapNaviMode) {
1251
- hasStartedNavi = true
1252
- onNavigationStarted([
1253
- "type": naviMode == .emulator ? 1 : 0,
1254
- "isEmulator": naviMode == .emulator
1255
- ])
1256
-
1257
- applyShowUIElementsToDriveViewIfReady()
2402
+ handleNavigationStarted(naviMode)
1258
2403
  }
1259
2404
 
1260
2405
  public func driveManagerNavi(_ driveManager: AMapNaviDriveManager, didArrive wayPoint: AMapNaviPoint) {
1261
- onWayPointArrived([
1262
- "index": 0
1263
- ])
2406
+ handleWayPointArrived(index: 0, point: wayPoint)
1264
2407
  }
1265
2408
 
1266
2409
  public func driveManagerDidEndEmulatorNavi(_ driveManager: AMapNaviDriveManager) {
1267
- resetTransientNavigationState()
1268
- onNavigationEnded([:])
2410
+ handleDidEndEmulatorNavi(reason: "did_end_emulator_navi")
1269
2411
  }
1270
2412
 
1271
2413
  public func driveManager(_ driveManager: AMapNaviDriveManager, playNaviSound soundString: String, soundStringType: AMapNaviSoundType) {
1272
- onNavigationText([
1273
- "type": soundStringType.rawValue,
1274
- "text": soundString
1275
- ])
2414
+ handleNavigationSound(soundString, soundStringType: soundStringType)
1276
2415
  }
1277
2416
 
2417
+ /// 兼容一部分 SDK/Swift 导入下的到达终点回调签名
1278
2418
  public func driveManager(onArrivedDestination driveManager: AMapNaviDriveManager) {
1279
- onArriveDestination([:])
2419
+ handleArrivedDestination(source: "driveManager(onArrivedDestination:)")
2420
+ deactivateNavigationAudioSessionIfNeeded(reason: "arrived_destination_named")
2421
+ }
2422
+
2423
+ /// AMapNaviDriveManagerDelegate 官方到达终点回调
2424
+ public func driveManagerOnArrivedDestination(_ driveManager: AMapNaviDriveManager) {
2425
+ handleArrivedDestination(source: "driveManagerOnArrivedDestination")
2426
+ deactivateNavigationAudioSessionIfNeeded(reason: "arrived_destination_official")
1280
2427
  }
1281
2428
 
1282
- public func driveManagerOnReCalculateRoute(forYaw driveManager: AMapNaviDriveManager) {
2429
+ public func driveManagerNeedRecalculateRoute(forYaw driveManager: AMapNaviDriveManager) {
1283
2430
  onRouteRecalculate([
1284
2431
  "reason": "yaw"
1285
2432
  ])
1286
2433
  }
1287
2434
 
1288
- public func driveManagerOnReCalculateRoute(forTrafficJam driveManager: AMapNaviDriveManager) {
2435
+ public func driveManagerNeedRecalculateRoute(forTrafficJam driveManager: AMapNaviDriveManager) {
1289
2436
  onRouteRecalculate([
1290
2437
  "reason": "traffic"
1291
2438
  ])
1292
2439
  }
1293
2440
 
1294
2441
  public func driveManager(_ driveManager: AMapNaviDriveManager, update gpsSignalStrength: AMapNaviGPSSignalStrength) {
1295
- let isWeak = gpsSignalStrength == .weak || gpsSignalStrength == .none
1296
- onGpsSignalWeak([
1297
- "isWeak": isWeak
1298
- ])
2442
+ handleGpsSignalUpdate(gpsSignalStrength)
1299
2443
  }
1300
2444
  }
1301
2445
 
@@ -1304,7 +2448,8 @@ extension ExpoGaodeMapNaviView: AMapNaviDriveViewDelegate {
1304
2448
 
1305
2449
  public func driveViewDidLoad(_ driveView: AMapNaviDriveView) {
1306
2450
  applyViewOptions()
1307
- applyShowUIElementsToDriveViewIfReady()
2451
+ applyShowUIElementsToActiveViewIfReady()
2452
+ refreshCustomWaypointAnnotations()
1308
2453
  scheduleOverviewRouteVisibleRegionRefresh()
1309
2454
  onNavigationReady([:])
1310
2455
  }
@@ -1331,45 +2476,14 @@ extension ExpoGaodeMapNaviView: AMapNaviDriveDataRepresentable {
1331
2476
  guard let naviLocation else {
1332
2477
  return
1333
2478
  }
1334
- if !hasReceivedFirstNaviData {
1335
- hasReceivedFirstNaviData = true
1336
- applyShowUIElementsToDriveViewIfReady()
1337
- }
1338
- lastKnownSpeed = Int(naviLocation.speed)
1339
- onLocationUpdate([
1340
- "latitude": naviLocation.coordinate.latitude,
1341
- "longitude": naviLocation.coordinate.longitude,
1342
- "speed": naviLocation.speed,
1343
- "bearing": naviLocation.heading
1344
- ])
2479
+ handleNavigationLocationUpdate(naviLocation)
1345
2480
  }
1346
2481
 
1347
2482
  public func driveManager(_ driveManager: AMapNaviDriveManager, update naviInfo: AMapNaviInfo?) {
1348
2483
  guard let naviInfo else {
1349
2484
  return
1350
2485
  }
1351
- if !hasReceivedFirstNaviData {
1352
- hasReceivedFirstNaviData = true
1353
- applyShowUIElementsToDriveViewIfReady()
1354
- }
1355
- emitNavigationInfoUpdate([
1356
- "naviMode": naviInfo.naviMode.rawValue,
1357
- "currentRoadName": naviInfo.currentRoadName ?? "",
1358
- "nextRoadName": naviInfo.nextRoadName ?? "",
1359
- "pathRetainDistance": naviInfo.routeRemainDistance,
1360
- "pathRetainTime": naviInfo.routeRemainTime,
1361
- "curStepRetainDistance": naviInfo.segmentRemainDistance,
1362
- "curStepRetainTime": naviInfo.segmentRemainTime,
1363
- "currentSpeed": lastKnownSpeed,
1364
- "iconType": lastTurnIconType ?? naviInfo.iconType.rawValue,
1365
- "iconDirection": lastTurnIconType ?? naviInfo.iconType.rawValue,
1366
- "currentSegmentIndex": naviInfo.currentSegmentIndex,
1367
- "currentLinkIndex": naviInfo.currentLinkIndex,
1368
- "currentPointIndex": naviInfo.currentPointIndex,
1369
- "routeRemainTrafficLightCount": naviInfo.routeRemainTrafficLightCount,
1370
- "driveDistance": naviInfo.routeDriveDistance,
1371
- "driveTime": naviInfo.routeDriveTime
1372
- ])
2486
+ handleNavigationInfoUpdate(naviInfo)
1373
2487
  }
1374
2488
 
1375
2489
  public func driveManager(_ driveManager: AMapNaviDriveManager, showCross crossImage: UIImage?) {
@@ -1401,6 +2515,20 @@ extension ExpoGaodeMapNaviView: AMapNaviDriveDataRepresentable {
1401
2515
 
1402
2516
  public func driveManager(_ driveManager: AMapNaviDriveManager, updateTurnIconImage turnIconImage: UIImage?, turn turnIconType: AMapNaviIconType) {
1403
2517
  lastTurnIconType = Int(turnIconType.rawValue)
2518
+ let encodedTurnIcon = encodeTurnIconForLiveActivity(turnIconImage)
2519
+ if let encodedTurnIcon {
2520
+ lastTurnIconBase64 = encodedTurnIcon
2521
+ } else if turnIconImage == nil {
2522
+ NSLog(
2523
+ "[ExpoGaodeMapNaviView][LiveActivity] turnIconImage is nil, keep previous turn icon snapshot"
2524
+ )
2525
+ }
2526
+ NSLog(
2527
+ "[ExpoGaodeMapNaviView][LiveActivity] turnIconType=%d, incomingBase64Length=%d, effectiveBase64Length=%d",
2528
+ Int(turnIconType.rawValue),
2529
+ encodedTurnIcon?.count ?? 0,
2530
+ lastTurnIconBase64?.count ?? 0
2531
+ )
1404
2532
  lastTurnIconImageUri = cacheTurnIconImage(
1405
2533
  turnIconImage,
1406
2534
  prefix: "turn_icon",
@@ -1419,3 +2547,155 @@ extension ExpoGaodeMapNaviView: AMapNaviDriveDataRepresentable {
1419
2547
  reemitLastNavigationInfoIfNeeded()
1420
2548
  }
1421
2549
  }
2550
+
2551
+ // MARK: - AMapNaviWalkManagerDelegate
2552
+ extension ExpoGaodeMapNaviView: AMapNaviWalkManagerDelegate {
2553
+ public func walkManager(_ walkManager: AMapNaviWalkManager, didStartNavi naviMode: AMapNaviMode) {
2554
+ handleNavigationStarted(naviMode)
2555
+ }
2556
+
2557
+ public func walkManagerDidEndEmulatorNavi(_ walkManager: AMapNaviWalkManager) {
2558
+ handleDidEndEmulatorNavi(reason: "walk_did_end_emulator_navi")
2559
+ }
2560
+
2561
+ public func walkManager(_ walkManager: AMapNaviWalkManager, playNaviSound soundString: String, soundStringType: AMapNaviSoundType) {
2562
+ handleNavigationSound(soundString, soundStringType: soundStringType)
2563
+ }
2564
+
2565
+ public func walkManager(onArrivedDestination walkManager: AMapNaviWalkManager) {
2566
+ handleArrivedDestination(source: "walkManagerOnArrivedDestination")
2567
+ deactivateNavigationAudioSessionIfNeeded(reason: "walk_arrived_destination")
2568
+ }
2569
+
2570
+ public func walkManagerNeedRecalculateRoute(forYaw walkManager: AMapNaviWalkManager) {
2571
+ onRouteRecalculate([
2572
+ "reason": "yaw"
2573
+ ])
2574
+ }
2575
+
2576
+ public func walkManager(_ walkManager: AMapNaviWalkManager, update gpsSignalStrength: AMapNaviGPSSignalStrength) {
2577
+ handleGpsSignalUpdate(gpsSignalStrength)
2578
+ }
2579
+
2580
+ public func walkManager(_ walkManager: AMapNaviWalkManager, onArrivedWayPoint wayPointIndex: Int32) {
2581
+ handleWayPointArrived(index: Int(wayPointIndex))
2582
+ }
2583
+
2584
+ public func walkManager(_ walkManager: AMapNaviWalkManager, onCalculateRouteFailure error: Error) {
2585
+ onRouteCalculated([
2586
+ "success": false,
2587
+ "errorInfo": error.localizedDescription
2588
+ ])
2589
+ }
2590
+ }
2591
+
2592
+ // MARK: - AMapNaviWalkViewDelegate
2593
+ extension ExpoGaodeMapNaviView: AMapNaviWalkViewDelegate {
2594
+ public func walkViewEdgePadding(_ walkView: AMapNaviWalkView) -> UIEdgeInsets {
2595
+ driveViewEdgePadding
2596
+ }
2597
+
2598
+ public func walkView(_ walkView: AMapNaviWalkView, didChange showMode: AMapNaviWalkViewShowMode) {
2599
+ if showMode == .overview {
2600
+ scheduleOverviewRouteVisibleRegionRefresh()
2601
+ }
2602
+ }
2603
+ }
2604
+
2605
+ extension ExpoGaodeMapNaviView: AMapNaviWalkDataRepresentable {
2606
+ public func walkManager(_ walkManager: AMapNaviWalkManager, update naviRoute: AMapNaviRoute?) {
2607
+ if let routeLength = naviRoute?.routeLength, routeLength > 0 {
2608
+ currentRouteTotalLength = routeLength
2609
+ }
2610
+ }
2611
+
2612
+ public func walkManager(_ walkManager: AMapNaviWalkManager, update naviLocation: AMapNaviLocation?) {
2613
+ guard let naviLocation else {
2614
+ return
2615
+ }
2616
+ handleNavigationLocationUpdate(naviLocation)
2617
+ }
2618
+
2619
+ public func walkManager(_ walkManager: AMapNaviWalkManager, update naviInfo: AMapNaviInfo?) {
2620
+ guard let naviInfo else {
2621
+ return
2622
+ }
2623
+ handleNavigationInfoUpdate(naviInfo)
2624
+ }
2625
+ }
2626
+
2627
+ // MARK: - AMapNaviRideManagerDelegate
2628
+ extension ExpoGaodeMapNaviView: AMapNaviRideManagerDelegate {
2629
+ public func rideManager(_ rideManager: AMapNaviRideManager, didStartNavi naviMode: AMapNaviMode) {
2630
+ handleNavigationStarted(naviMode)
2631
+ }
2632
+
2633
+ public func rideManagerDidEndEmulatorNavi(_ rideManager: AMapNaviRideManager) {
2634
+ handleDidEndEmulatorNavi(reason: "ride_did_end_emulator_navi")
2635
+ }
2636
+
2637
+ public func rideManager(_ rideManager: AMapNaviRideManager, playNaviSound soundString: String, soundStringType: AMapNaviSoundType) {
2638
+ handleNavigationSound(soundString, soundStringType: soundStringType)
2639
+ }
2640
+
2641
+ public func rideManager(onArrivedDestination rideManager: AMapNaviRideManager) {
2642
+ handleArrivedDestination(source: "rideManagerOnArrivedDestination")
2643
+ deactivateNavigationAudioSessionIfNeeded(reason: "ride_arrived_destination")
2644
+ }
2645
+
2646
+ public func rideManagerNeedRecalculateRoute(forYaw rideManager: AMapNaviRideManager) {
2647
+ onRouteRecalculate([
2648
+ "reason": "yaw"
2649
+ ])
2650
+ }
2651
+
2652
+ public func rideManager(_ rideManager: AMapNaviRideManager, update gpsSignalStrength: AMapNaviGPSSignalStrength) {
2653
+ handleGpsSignalUpdate(gpsSignalStrength)
2654
+ }
2655
+
2656
+ public func rideManager(_ rideManager: AMapNaviRideManager, onArrivedWayPoint wayPointIndex: Int32) {
2657
+ handleWayPointArrived(index: Int(wayPointIndex))
2658
+ }
2659
+
2660
+ public func rideManager(_ rideManager: AMapNaviRideManager, onCalculateRouteFailure error: Error) {
2661
+ onRouteCalculated([
2662
+ "success": false,
2663
+ "errorInfo": error.localizedDescription
2664
+ ])
2665
+ }
2666
+ }
2667
+
2668
+ // MARK: - AMapNaviRideViewDelegate
2669
+ extension ExpoGaodeMapNaviView: AMapNaviRideViewDelegate {
2670
+ public func rideViewEdgePadding(_ rideView: AMapNaviRideView) -> UIEdgeInsets {
2671
+ driveViewEdgePadding
2672
+ }
2673
+
2674
+ public func rideView(_ rideView: AMapNaviRideView, didChange showMode: AMapNaviRideViewShowMode) {
2675
+ if showMode == .overview {
2676
+ scheduleOverviewRouteVisibleRegionRefresh()
2677
+ }
2678
+ }
2679
+ }
2680
+
2681
+ extension ExpoGaodeMapNaviView: AMapNaviRideDataRepresentable {
2682
+ public func rideManager(_ rideManager: AMapNaviRideManager, update naviRoute: AMapNaviRoute?) {
2683
+ if let routeLength = naviRoute?.routeLength, routeLength > 0 {
2684
+ currentRouteTotalLength = routeLength
2685
+ }
2686
+ }
2687
+
2688
+ public func rideManager(_ rideManager: AMapNaviRideManager, update naviLocation: AMapNaviLocation?) {
2689
+ guard let naviLocation else {
2690
+ return
2691
+ }
2692
+ handleNavigationLocationUpdate(naviLocation)
2693
+ }
2694
+
2695
+ public func rideManager(_ rideManager: AMapNaviRideManager, update naviInfo: AMapNaviInfo?) {
2696
+ guard let naviInfo else {
2697
+ return
2698
+ }
2699
+ handleNavigationInfoUpdate(naviInfo)
2700
+ }
2701
+ }