expo-gaode-map-navigation 2.0.8 → 2.0.9

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 (126) hide show
  1. package/README.md +233 -3
  2. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapModule.kt +4 -2
  3. package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNaviView.kt +931 -391
  4. package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNaviViewModule.kt +86 -1
  5. package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNavigationModule.kt +4 -5
  6. package/android/src/main/java/expo/modules/gaodemap/navigation/listeners/IndependentRouteListener.kt +4 -3
  7. package/android/src/main/java/expo/modules/gaodemap/navigation/listeners/RouteCalculateListener.kt +2 -2
  8. package/android/src/main/java/expo/modules/gaodemap/navigation/managers/IndependentRouteManager.kt +96 -14
  9. package/android/src/main/java/expo/modules/gaodemap/navigation/routes/drive/DriveTruckRouteCalculator.kt +2 -0
  10. package/android/src/main/java/expo/modules/gaodemap/navigation/utils/Converters.kt +19 -10
  11. package/android/src/main/res/drawable/landback_0.png +0 -0
  12. package/android/src/main/res/drawable/landback_1.png +0 -0
  13. package/android/src/main/res/drawable/landback_2.png +0 -0
  14. package/android/src/main/res/drawable/landback_3.png +0 -0
  15. package/android/src/main/res/drawable/landback_4.png +0 -0
  16. package/android/src/main/res/drawable/landback_5.png +0 -0
  17. package/android/src/main/res/drawable/landback_6.png +0 -0
  18. package/android/src/main/res/drawable/landback_7.png +0 -0
  19. package/android/src/main/res/drawable/landback_8.png +0 -0
  20. package/android/src/main/res/drawable/landback_9.png +0 -0
  21. package/android/src/main/res/drawable/landback_a.png +0 -0
  22. package/android/src/main/res/drawable/landback_b.png +0 -0
  23. package/android/src/main/res/drawable/landback_c.png +0 -0
  24. package/android/src/main/res/drawable/landback_d.png +0 -0
  25. package/android/src/main/res/drawable/landback_e.png +0 -0
  26. package/android/src/main/res/drawable/landback_f.png +0 -0
  27. package/android/src/main/res/drawable/landback_g.png +0 -0
  28. package/android/src/main/res/drawable/landback_h.png +0 -0
  29. package/android/src/main/res/drawable/landback_i.png +0 -0
  30. package/android/src/main/res/drawable/landback_j.png +0 -0
  31. package/android/src/main/res/drawable/landback_k.png +0 -0
  32. package/android/src/main/res/drawable/landback_l.png +0 -0
  33. package/android/src/main/res/drawable/landfront_0.png +0 -0
  34. package/android/src/main/res/drawable/landfront_00.png +0 -0
  35. package/android/src/main/res/drawable/landfront_1.png +0 -0
  36. package/android/src/main/res/drawable/landfront_11.png +0 -0
  37. package/android/src/main/res/drawable/landfront_20.png +0 -0
  38. package/android/src/main/res/drawable/landfront_21.png +0 -0
  39. package/android/src/main/res/drawable/landfront_22.png +0 -0
  40. package/android/src/main/res/drawable/landfront_3.png +0 -0
  41. package/android/src/main/res/drawable/landfront_33.png +0 -0
  42. package/android/src/main/res/drawable/landfront_40.png +0 -0
  43. package/android/src/main/res/drawable/landfront_43.png +0 -0
  44. package/android/src/main/res/drawable/landfront_44.png +0 -0
  45. package/android/src/main/res/drawable/landfront_5.png +0 -0
  46. package/android/src/main/res/drawable/landfront_55.png +0 -0
  47. package/android/src/main/res/drawable/landfront_61.png +0 -0
  48. package/android/src/main/res/drawable/landfront_63.png +0 -0
  49. package/android/src/main/res/drawable/landfront_66.png +0 -0
  50. package/android/src/main/res/drawable/landfront_70.png +0 -0
  51. package/android/src/main/res/drawable/landfront_71.png +0 -0
  52. package/android/src/main/res/drawable/landfront_73.png +0 -0
  53. package/android/src/main/res/drawable/landfront_77.png +0 -0
  54. package/android/src/main/res/drawable/landfront_8.png +0 -0
  55. package/android/src/main/res/drawable/landfront_88.png +0 -0
  56. package/android/src/main/res/drawable/landfront_90.png +0 -0
  57. package/android/src/main/res/drawable/landfront_95.png +0 -0
  58. package/android/src/main/res/drawable/landfront_99.png +0 -0
  59. package/android/src/main/res/drawable/landfront_a0.png +0 -0
  60. package/android/src/main/res/drawable/landfront_a8.png +0 -0
  61. package/android/src/main/res/drawable/landfront_aa.png +0 -0
  62. package/android/src/main/res/drawable/landfront_b1.png +0 -0
  63. package/android/src/main/res/drawable/landfront_b5.png +0 -0
  64. package/android/src/main/res/drawable/landfront_bb.png +0 -0
  65. package/android/src/main/res/drawable/landfront_c3.png +0 -0
  66. package/android/src/main/res/drawable/landfront_c8.png +0 -0
  67. package/android/src/main/res/drawable/landfront_cc.png +0 -0
  68. package/android/src/main/res/drawable/landfront_d.png +0 -0
  69. package/android/src/main/res/drawable/landfront_dd.png +0 -0
  70. package/android/src/main/res/drawable/landfront_e1.png +0 -0
  71. package/android/src/main/res/drawable/landfront_e5.png +0 -0
  72. package/android/src/main/res/drawable/landfront_ee.png +0 -0
  73. package/android/src/main/res/drawable/landfront_f0.png +0 -0
  74. package/android/src/main/res/drawable/landfront_f1.png +0 -0
  75. package/android/src/main/res/drawable/landfront_f5.png +0 -0
  76. package/android/src/main/res/drawable/landfront_ff.png +0 -0
  77. package/android/src/main/res/drawable/landfront_g3.png +0 -0
  78. package/android/src/main/res/drawable/landfront_g5.png +0 -0
  79. package/android/src/main/res/drawable/landfront_gg.png +0 -0
  80. package/android/src/main/res/drawable/landfront_h1.png +0 -0
  81. package/android/src/main/res/drawable/landfront_h3.png +0 -0
  82. package/android/src/main/res/drawable/landfront_h5.png +0 -0
  83. package/android/src/main/res/drawable/landfront_hh.png +0 -0
  84. package/android/src/main/res/drawable/landfront_i0.png +0 -0
  85. package/android/src/main/res/drawable/landfront_i3.png +0 -0
  86. package/android/src/main/res/drawable/landfront_i5.png +0 -0
  87. package/android/src/main/res/drawable/landfront_ii.png +0 -0
  88. package/android/src/main/res/drawable/landfront_j1.png +0 -0
  89. package/android/src/main/res/drawable/landfront_j8.png +0 -0
  90. package/android/src/main/res/drawable/landfront_jj.png +0 -0
  91. package/android/src/main/res/drawable/landfront_kk.png +0 -0
  92. package/android/src/main/res/drawable/landfront_ll.png +0 -0
  93. package/android/src/main/res/drawable/navi_arrow_leftline.png +0 -0
  94. package/android/src/main/res/drawable/navi_lane_shape_bg_center.xml +5 -0
  95. package/android/src/main/res/drawable/navi_lane_shape_bg_left.xml +8 -0
  96. package/android/src/main/res/drawable/navi_lane_shape_bg_over.xml +6 -0
  97. package/android/src/main/res/drawable/navi_lane_shape_bg_right.xml +8 -0
  98. package/build/ExpoGaodeMapNaviView.d.ts +8 -0
  99. package/build/ExpoGaodeMapNaviView.d.ts.map +1 -1
  100. package/build/ExpoGaodeMapNaviView.js +38 -1
  101. package/build/ExpoGaodeMapNaviView.js.map +1 -1
  102. package/build/index.d.ts +8 -4
  103. package/build/index.d.ts.map +1 -1
  104. package/build/index.js +408 -4
  105. package/build/index.js.map +1 -1
  106. package/build/types/independent.types.d.ts +91 -0
  107. package/build/types/independent.types.d.ts.map +1 -1
  108. package/build/types/independent.types.js.map +1 -1
  109. package/build/types/naviview.types.d.ts +256 -12
  110. package/build/types/naviview.types.d.ts.map +1 -1
  111. package/build/types/naviview.types.js.map +1 -1
  112. package/build/types/route.types.d.ts +2 -0
  113. package/build/types/route.types.d.ts.map +1 -1
  114. package/build/types/route.types.js.map +1 -1
  115. package/ios/ExpoGaodeMapNaviView.swift +888 -66
  116. package/ios/ExpoGaodeMapNaviViewModule.swift +87 -1
  117. package/ios/ExpoGaodeMapNavigationModule.swift +1 -1
  118. package/ios/managers/IndependentRouteManager.swift +1 -0
  119. package/ios/map/ExpoGaodeMapModule.swift +9 -4
  120. package/ios/map/ExpoGaodeMapView.swift +13 -2
  121. package/ios/map/modules/LocationManager.swift +17 -0
  122. package/ios/map/utils/PermissionManager.swift +11 -6
  123. package/ios/routes/drive/DriveTruckRouteCalculator.swift +9 -0
  124. package/ios/routes/walkride/WalkRideRouteCalculator.swift +30 -0
  125. package/ios/services/IndependentRouteService.swift +25 -0
  126. package/package.json +5 -2
@@ -9,7 +9,183 @@ import Foundation
9
9
  import ExpoModulesCore
10
10
  import AMapNaviKit
11
11
 
12
+ final class ExpoGaodeMapCustomDriveView: AMapNaviDriveView {
13
+ var suppressLaneInfoUI: Bool = false
14
+ var suppressTopInfoUI: Bool = false {
15
+ didSet {
16
+ scheduleTopInfoSuppressionPasses()
17
+ setNeedsLayout()
18
+ }
19
+ }
20
+ private let topInfoCoverView: UIView = {
21
+ let view = UIView()
22
+ view.isHidden = true
23
+ view.isUserInteractionEnabled = false
24
+ view.backgroundColor = UIColor(red: 14.0 / 255.0, green: 18.0 / 255.0, blue: 26.0 / 255.0, alpha: 0.96)
25
+ return view
26
+ }()
27
+ private var scheduledSuppressionPasses = 0
28
+
29
+ override init(frame: CGRect) {
30
+ super.init(frame: frame)
31
+ installTopInfoCoverViewIfNeeded()
32
+ }
33
+
34
+ required init?(coder: NSCoder) {
35
+ super.init(coder: coder)
36
+ installTopInfoCoverViewIfNeeded()
37
+ }
38
+
39
+ override func layoutSubviews() {
40
+ super.layoutSubviews()
41
+ applySuppressedChromeVisibility()
42
+ }
43
+
44
+ override func didMoveToWindow() {
45
+ super.didMoveToWindow()
46
+ installTopInfoCoverViewIfNeeded()
47
+ if suppressTopInfoUI {
48
+ scheduleTopInfoSuppressionPasses()
49
+ }
50
+ }
51
+
52
+ override func driveManager(_ driveManager: AMapNaviDriveManager, showLaneBackInfo laneBackInfo: String, laneSelectInfo: String) {
53
+ guard !suppressLaneInfoUI else {
54
+ return
55
+ }
56
+ super.driveManager(driveManager, showLaneBackInfo: laneBackInfo, laneSelectInfo: laneSelectInfo)
57
+ }
58
+
59
+ override func driveManagerHideLaneInfo(_ driveManager: AMapNaviDriveManager) {
60
+ guard !suppressLaneInfoUI else {
61
+ return
62
+ }
63
+ super.driveManagerHideLaneInfo(driveManager)
64
+ }
65
+
66
+ private func applySuppressedChromeVisibility() {
67
+ installTopInfoCoverViewIfNeeded()
68
+ let topCandidates = collectTopInfoCandidates()
69
+ for candidate in topCandidates {
70
+ candidate.isHidden = suppressTopInfoUI
71
+ candidate.alpha = suppressTopInfoUI ? 0.0 : 1.0
72
+ }
73
+
74
+ guard suppressTopInfoUI, !topCandidates.isEmpty else {
75
+ topInfoCoverView.isHidden = true
76
+ return
77
+ }
78
+
79
+ let unionFrame = topCandidates.reduce(CGRect.null) { partial, view in
80
+ let frame = view.convert(view.bounds, to: self)
81
+ return partial.union(frame)
82
+ }
83
+ let fallbackFrame = CGRect(x: 0, y: 0, width: bounds.width, height: min(max(bounds.height * 0.17, 96), 150))
84
+ let targetFrame = (unionFrame.isNull ? fallbackFrame : unionFrame.insetBy(dx: -8, dy: -6)).intersection(bounds)
85
+ topInfoCoverView.frame = targetFrame
86
+ topInfoCoverView.isHidden = targetFrame.isEmpty
87
+ if !topInfoCoverView.isHidden {
88
+ bringSubviewToFront(topInfoCoverView)
89
+ }
90
+ }
91
+
92
+ private func collectTopInfoCandidates() -> [UIView] {
93
+ guard suppressTopInfoUI || !subviews.isEmpty else {
94
+ return []
95
+ }
96
+
97
+ let protectedClassNameFragments = [
98
+ "MAMap",
99
+ "Lane",
100
+ "Cross",
101
+ "Eagle",
102
+ "TrafficBar",
103
+ "Compass",
104
+ "Zoom",
105
+ "Scale",
106
+ "Logo",
107
+ ]
108
+
109
+ return allDescendantSubviews(of: self).filter { view in
110
+ guard view !== self, view !== topInfoCoverView else {
111
+ return false
112
+ }
113
+
114
+ let frame = view.convert(view.bounds, to: self)
115
+ guard !frame.isEmpty else {
116
+ return false
117
+ }
118
+
119
+ let className = NSStringFromClass(type(of: view))
120
+ if protectedClassNameFragments.contains(where: { className.localizedCaseInsensitiveContains($0) }) {
121
+ return false
122
+ }
123
+
124
+ let topBandMaxY = min(max(bounds.height * 0.28, 150), 220)
125
+ guard frame.minY <= topBandMaxY && frame.maxY <= topBandMaxY + 70 else {
126
+ return false
127
+ }
128
+
129
+ guard frame.height >= 12 && frame.height <= 140 && frame.width >= 20 else {
130
+ return false
131
+ }
132
+
133
+ // Keep small corner controls (for example compass / map tool buttons) out of the suppression set.
134
+ let isCornerControl =
135
+ frame.width <= 60 &&
136
+ frame.height <= 60 &&
137
+ (frame.minX <= 24 || frame.maxX >= bounds.width - 24)
138
+ if isCornerControl {
139
+ return false
140
+ }
141
+
142
+ return true
143
+ }
144
+ }
145
+
146
+ private func allDescendantSubviews(of root: UIView) -> [UIView] {
147
+ root.subviews.flatMap { subview in
148
+ [subview] + allDescendantSubviews(of: subview)
149
+ }
150
+ }
151
+
152
+ private func installTopInfoCoverViewIfNeeded() {
153
+ guard topInfoCoverView.superview !== self else {
154
+ return
155
+ }
156
+ addSubview(topInfoCoverView)
157
+ }
158
+
159
+ private func scheduleTopInfoSuppressionPasses() {
160
+ scheduledSuppressionPasses = suppressTopInfoUI ? 18 : 0
161
+ guard suppressTopInfoUI else {
162
+ topInfoCoverView.isHidden = true
163
+ return
164
+ }
165
+ runScheduledTopInfoSuppressionPass()
166
+ }
167
+
168
+ private func runScheduledTopInfoSuppressionPass() {
169
+ applySuppressedChromeVisibility()
170
+ guard scheduledSuppressionPasses > 0 else {
171
+ return
172
+ }
173
+ scheduledSuppressionPasses -= 1
174
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.12) { [weak self] in
175
+ self?.runScheduledTopInfoSuppressionPass()
176
+ }
177
+ }
178
+
179
+ func refreshSuppressedTopInfoUIIfNeeded() {
180
+ guard suppressTopInfoUI else {
181
+ return
182
+ }
183
+ applySuppressedChromeVisibility()
184
+ }
185
+ }
186
+
12
187
  public class ExpoGaodeMapNaviView: ExpoView {
188
+ private let independentRouteManager = IndependentRouteManager.shared
13
189
 
14
190
  // MARK: - 高德 SDK 初始化检查
15
191
 
@@ -121,15 +297,30 @@ public class ExpoGaodeMapNaviView: ExpoView {
121
297
  let onGpsStatusChanged = EventDispatcher()
122
298
  let onNavigationInfoUpdate = EventDispatcher()
123
299
  let onGpsSignalWeak = EventDispatcher()
300
+ let onNavigationVisualStateUpdate = EventDispatcher()
301
+ let onLaneInfoUpdate = EventDispatcher()
302
+ let onTrafficStatusesUpdate = EventDispatcher()
124
303
 
125
304
  // MARK: - Properties
126
305
  private var driveView: AMapNaviDriveView?
127
- private var driveViewLoaded: Bool = false
128
306
  private var pendingShowUIElements: Bool?
129
- private var pendingShowUIElementsWorkItem: DispatchWorkItem?
130
307
  private var hasStartedNavi: Bool = false
131
308
  private var hasReceivedFirstNaviData: Bool = false
132
309
  private var driveManager: AMapNaviDriveManager?
310
+ private var lastKnownSpeed: Int = 0
311
+ private var currentRouteTotalLength: Int?
312
+ private var lastNavigationInfoPayload: [String: Any]?
313
+ private var lastTurnIconType: Int?
314
+ private var lastNextTurnIconType: Int?
315
+ private var lastTurnIconImageUri: String?
316
+ private var lastNextTurnIconImageUri: String?
317
+ private var isCrossVisible: Bool = false
318
+ private var isLaneInfoVisible: Bool = false
319
+
320
+ private enum LaneStringKind {
321
+ case background
322
+ case selected
323
+ }
133
324
 
134
325
  // Props - 通用属性
135
326
  var naviType: Int = 0 // 0: GPS, 1: Emulator
@@ -139,6 +330,24 @@ public class ExpoGaodeMapNaviView: ExpoView {
139
330
  var showCamera: Bool = true {
140
331
  didSet { applyShowCamera(showCamera) }
141
332
  }
333
+ var carImageSource: String? {
334
+ didSet { applyCarImageSource() }
335
+ }
336
+ var carCompassImageSource: String? {
337
+ didSet { applyCarCompassImageSource() }
338
+ }
339
+ var startPointImageSource: String? {
340
+ didSet { applyStartPointImageSource() }
341
+ }
342
+ var wayPointImageSource: String? {
343
+ didSet { applyWayPointImageSource() }
344
+ }
345
+ var endPointImageSource: String? {
346
+ didSet { applyEndPointImageSource() }
347
+ }
348
+ var cameraImageSource: String? {
349
+ didSet { applyCameraImageSource() }
350
+ }
142
351
  var autoLockCar: Bool = true {
143
352
  didSet { applyAutoLockCar(autoLockCar) }
144
353
  }
@@ -171,6 +380,12 @@ public class ExpoGaodeMapNaviView: ExpoView {
171
380
  var showTrafficBar: Bool = true {
172
381
  didSet { driveView?.showTrafficBar = showTrafficBar }
173
382
  }
383
+ var trafficBarFrame: CGRect = .zero {
384
+ didSet { applyTrafficBarFrame(trafficBarFrame) }
385
+ }
386
+ var trafficBarColors: [String: Any]? {
387
+ didSet { applyTrafficBarColors(trafficBarColors) }
388
+ }
174
389
  var showBrowseRouteButton: Bool = true {
175
390
  didSet { driveView?.showBrowseRouteButton = showBrowseRouteButton }
176
391
  }
@@ -180,6 +395,12 @@ public class ExpoGaodeMapNaviView: ExpoView {
180
395
  var showTrafficButton: Bool = true {
181
396
  didSet { driveView?.showTrafficButton = showTrafficButton }
182
397
  }
398
+ var showBackupRoute: Bool = true {
399
+ didSet { driveView?.showBackupRoute = showBackupRoute }
400
+ }
401
+ var showEagleMap: Bool = false {
402
+ didSet { driveView?.showEagleMap = showEagleMap }
403
+ }
183
404
  var showUIElements: Bool = true {
184
405
  didSet { applyShowUIElementsToDriveViewIfReady() }
185
406
  }
@@ -192,12 +413,43 @@ public class ExpoGaodeMapNaviView: ExpoView {
192
413
  var showTrafficLights: Bool = true {
193
414
  didSet { driveView?.showTrafficLights = showTrafficLights }
194
415
  }
416
+ var showCompassEnabled: Bool? {
417
+ didSet {
418
+ guard let showCompassEnabled else { return }
419
+ driveView?.showCompass = showCompassEnabled
420
+ }
421
+ }
422
+ var showDriveCongestion: Bool = true {
423
+ didSet { driveView?.showDriveCongestion = showDriveCongestion }
424
+ }
425
+ var showTrafficLightView: Bool = true {
426
+ didSet { driveView?.showTrafficLightView = showTrafficLightView }
427
+ }
195
428
  var mapViewModeType: Int = 0 {
196
429
  didSet { applyMapViewModeType(mapViewModeType) }
197
430
  }
198
431
  var lineWidth: CGFloat = 0 {
199
432
  didSet { driveView?.lineWidth = lineWidth }
200
433
  }
434
+ var driveViewEdgePadding: UIEdgeInsets = .zero {
435
+ didSet {
436
+ driveView?.setNeedsLayout()
437
+ driveView?.layoutIfNeeded()
438
+ scheduleOverviewRouteVisibleRegionRefresh()
439
+ }
440
+ }
441
+ var screenAnchor: CGPoint = .zero {
442
+ didSet {
443
+ driveView?.screenAnchor = screenAnchor
444
+ scheduleOverviewRouteVisibleRegionRefresh()
445
+ }
446
+ }
447
+ var hideNativeTopInfoLayout: Bool = false {
448
+ didSet { applyHideNativeTopInfoLayout(hideNativeTopInfoLayout) }
449
+ }
450
+ var hideNativeLaneInfoLayout: Bool = false {
451
+ didSet { applyHideNativeLaneInfoLayout(hideNativeLaneInfoLayout) }
452
+ }
201
453
 
202
454
  func applyShowUIElements(_ visible: Bool) {
203
455
  showUIElements = visible
@@ -238,13 +490,16 @@ public class ExpoGaodeMapNaviView: ExpoView {
238
490
 
239
491
  // 初始化驾车导航管理器
240
492
  driveManager = AMapNaviDriveManager.sharedInstance()
241
- driveManager?.delegate = self
493
+ rebindDriveManagerToView()
242
494
 
243
495
  // 使用内置语音
244
496
  driveManager?.isUseInternalTTS = true
245
497
 
246
498
  // 初始化导航视图
247
- driveView = AMapNaviDriveView(frame: bounds)
499
+ let customDriveView = ExpoGaodeMapCustomDriveView(frame: bounds)
500
+ customDriveView.suppressTopInfoUI = hideNativeTopInfoLayout
501
+ customDriveView.suppressLaneInfoUI = hideNativeLaneInfoLayout
502
+ driveView = customDriveView
248
503
  driveView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
249
504
  driveView?.delegate = self
250
505
 
@@ -256,13 +511,233 @@ public class ExpoGaodeMapNaviView: ExpoView {
256
511
  // 应用初始配置
257
512
  applyViewOptions()
258
513
  }
514
+
515
+ private func rebindDriveManagerToView() {
516
+ driveManager = AMapNaviDriveManager.sharedInstance()
517
+ driveManager?.delegate = self
518
+ driveManager?.removeDataRepresentative(self)
519
+ if let view = driveView {
520
+ driveManager?.removeDataRepresentative(view)
521
+ driveManager?.addDataRepresentative(view)
522
+ }
523
+ driveManager?.addDataRepresentative(self)
524
+ }
525
+
526
+ private func resetTransientNavigationState() {
527
+ lastNavigationInfoPayload = nil
528
+ lastTurnIconType = nil
529
+ lastNextTurnIconType = nil
530
+ clearCachedTurnIconUris()
531
+ isCrossVisible = false
532
+ isLaneInfoVisible = false
533
+ emitVisualStateUpdate()
534
+ }
535
+
536
+ private func emitVisualStateUpdate() {
537
+ onNavigationVisualStateUpdate([
538
+ "isCrossVisible": isCrossVisible,
539
+ // iOS 官方导航 SDK 公开的是 showCrossImage / hideCrossImage,
540
+ // 当前没有 Android showModeCross / hideModeCross 对等的 3D 路口模型接口。
541
+ "isModeCrossVisible": false,
542
+ "isLaneInfoVisible": isLaneInfoVisible
543
+ ])
544
+ }
545
+
546
+ private func emitNavigationInfoUpdate(_ payload: [String: Any]) {
547
+ (driveView as? ExpoGaodeMapCustomDriveView)?.refreshSuppressedTopInfoUIIfNeeded()
548
+ var nextPayload = payload
549
+ if let lastTurnIconType {
550
+ nextPayload["iconType"] = lastTurnIconType
551
+ nextPayload["iconDirection"] = lastTurnIconType
552
+ }
553
+ if let lastNextTurnIconType {
554
+ nextPayload["nextIconType"] = lastNextTurnIconType
555
+ }
556
+ if let lastTurnIconImageUri {
557
+ nextPayload["turnIconImage"] = lastTurnIconImageUri
558
+ } else {
559
+ nextPayload.removeValue(forKey: "turnIconImage")
560
+ }
561
+ if let lastNextTurnIconImageUri {
562
+ nextPayload["nextTurnIconImage"] = lastNextTurnIconImageUri
563
+ } else {
564
+ nextPayload.removeValue(forKey: "nextTurnIconImage")
565
+ }
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
+ lastNavigationInfoPayload = nextPayload
574
+ onNavigationInfoUpdate(nextPayload)
575
+ }
576
+
577
+ private func reemitLastNavigationInfoIfNeeded() {
578
+ guard var nextPayload = lastNavigationInfoPayload else {
579
+ return
580
+ }
581
+ if let lastTurnIconType {
582
+ nextPayload["iconType"] = lastTurnIconType
583
+ nextPayload["iconDirection"] = lastTurnIconType
584
+ }
585
+ if let lastNextTurnIconType {
586
+ nextPayload["nextIconType"] = lastNextTurnIconType
587
+ } else {
588
+ nextPayload.removeValue(forKey: "nextIconType")
589
+ }
590
+ if let lastTurnIconImageUri {
591
+ nextPayload["turnIconImage"] = lastTurnIconImageUri
592
+ } else {
593
+ nextPayload.removeValue(forKey: "turnIconImage")
594
+ }
595
+ if let lastNextTurnIconImageUri {
596
+ nextPayload["nextTurnIconImage"] = lastNextTurnIconImageUri
597
+ } else {
598
+ nextPayload.removeValue(forKey: "nextTurnIconImage")
599
+ }
600
+
601
+ lastNavigationInfoPayload = nextPayload
602
+ onNavigationInfoUpdate(nextPayload)
603
+ }
604
+
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
611
+ }
612
+
613
+ let filename = "\(prefix)_\(UUID().uuidString).png"
614
+ let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent(filename)
615
+
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
624
+ }
625
+ }
626
+
627
+ private func deleteCachedTurnIcon(at uriString: String) {
628
+ guard let fileURL = URL(string: uriString), fileURL.isFileURL else {
629
+ return
630
+ }
631
+ try? FileManager.default.removeItem(at: fileURL)
632
+ }
633
+
634
+ private func clearCachedTurnIconUris() {
635
+ if let lastTurnIconImageUri {
636
+ deleteCachedTurnIcon(at: lastTurnIconImageUri)
637
+ }
638
+ if let lastNextTurnIconImageUri {
639
+ deleteCachedTurnIcon(at: lastNextTurnIconImageUri)
640
+ }
641
+ lastTurnIconImageUri = nil
642
+ lastNextTurnIconImageUri = nil
643
+ }
644
+
645
+ private func splitLaneInfoString(_ value: String) -> [String] {
646
+ value
647
+ .split(separator: "|", omittingEmptySubsequences: false)
648
+ .map { $0.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() }
649
+ }
650
+
651
+ private func parseLaneToken(_ token: String, kind: LaneStringKind) -> Int? {
652
+ guard !token.isEmpty else {
653
+ return nil
654
+ }
655
+
656
+ if token == "255" || token == "ff" {
657
+ return 255
658
+ }
659
+
660
+ // Older iOS callbacks may use `f` as filler / unavailable lane marker.
661
+ if token == "f" {
662
+ return kind == .background ? 255 : 255
663
+ }
664
+
665
+ if let decimalValue = Int(token) {
666
+ return decimalValue
667
+ }
668
+
669
+ switch token {
670
+ case "a":
671
+ return 10
672
+ case "b":
673
+ return 11
674
+ case "c":
675
+ return 12
676
+ case "d":
677
+ return 13
678
+ case "e":
679
+ return 14
680
+ case "g":
681
+ return 16
682
+ case "h":
683
+ return 17
684
+ case "i":
685
+ return 18
686
+ case "j":
687
+ return 19
688
+ case "k":
689
+ return 20
690
+ case "kk":
691
+ return 21
692
+ case "l":
693
+ return 23
694
+ default:
695
+ return Int(token, radix: 16)
696
+ }
697
+ }
698
+
699
+ private func serializeLaneInfo(laneBackInfo: String, laneSelectInfo: String) -> [String: Any]? {
700
+ let backgroundLane = splitLaneInfoString(laneBackInfo).map {
701
+ parseLaneToken($0, kind: .background) ?? 255
702
+ }
703
+ let frontLane = splitLaneInfoString(laneSelectInfo).map {
704
+ parseLaneToken($0, kind: .selected) ?? 255
705
+ }
706
+
707
+ let sentinelIndex = backgroundLane.firstIndex(of: 255)
708
+ let resolvedCount = [
709
+ sentinelIndex,
710
+ backgroundLane.isEmpty ? nil : backgroundLane.count,
711
+ frontLane.isEmpty ? nil : frontLane.count
712
+ ]
713
+ .compactMap { $0 }
714
+ .min() ?? 0
715
+
716
+ guard resolvedCount > 0 else {
717
+ return nil
718
+ }
719
+
720
+ let normalizedBackground = (0..<resolvedCount).map { index in
721
+ backgroundLane.indices.contains(index) ? backgroundLane[index] : 255
722
+ }
723
+ let normalizedFront = (0..<resolvedCount).map { index in
724
+ frontLane.indices.contains(index) ? frontLane[index] : 255
725
+ }
726
+
727
+ return [
728
+ "laneCount": resolvedCount,
729
+ "backgroundLane": normalizedBackground,
730
+ "frontLane": normalizedFront
731
+ ]
732
+ }
259
733
 
260
734
  private func applyViewOptions() {
261
735
  // 通用属性
736
+ driveView?.showUIElements = showUIElements
262
737
  driveView?.showCamera = showCamera
263
738
  driveView?.autoSwitchShowModeToCarPositionLocked = autoLockCar
264
739
  driveView?.autoZoomMapLevel = autoChangeZoom
265
- driveView?.showTrafficLayer = trafficLayerEnabled
740
+ driveView?.mapShowTraffic = trafficLayerEnabled
266
741
  driveView?.showCrossImage = realCrossDisplay
267
742
  driveView?.trackingMode = naviMode == 0 ? .carNorth : .mapNorth
268
743
 
@@ -270,69 +745,275 @@ public class ExpoGaodeMapNaviView: ExpoView {
270
745
  driveView?.showRoute = showRoute
271
746
  driveView?.showTurnArrow = showTurnArrow
272
747
  driveView?.showTrafficBar = showTrafficBar
748
+ applyTrafficBarFrame(trafficBarFrame)
749
+ applyTrafficBarColors(trafficBarColors)
273
750
  driveView?.showBrowseRouteButton = showBrowseRouteButton
274
751
  driveView?.showMoreButton = showMoreButton
275
752
  driveView?.showTrafficButton = showTrafficButton
753
+ driveView?.showBackupRoute = showBackupRoute
754
+ driveView?.showEagleMap = showEagleMap
276
755
  driveView?.showGreyAfterPass = showGreyAfterPass
277
756
  driveView?.showVectorline = showVectorline
278
757
  driveView?.showTrafficLights = showTrafficLights
758
+ if let showCompassEnabled {
759
+ driveView?.showCompass = showCompassEnabled
760
+ }
761
+ driveView?.showDriveCongestion = showDriveCongestion
762
+ driveView?.showTrafficLightView = showTrafficLightView
279
763
  if lineWidth > 0 {
280
764
  driveView?.lineWidth = lineWidth
281
765
  }
766
+ applyCustomAnnotationImages()
767
+ applyCustomUILayoutOptionsIfNeeded()
768
+ }
769
+
770
+ private func applyCustomAnnotationImages() {
771
+ applyCarImageSource()
772
+ applyCarCompassImageSource()
773
+ applyStartPointImageSource()
774
+ applyWayPointImageSource()
775
+ applyEndPointImageSource()
776
+ applyCameraImageSource()
777
+ }
778
+
779
+ private func resolveLocalImage(_ source: String) -> UIImage? {
780
+ if source.hasPrefix("file://") {
781
+ let path = String(source.dropFirst(7))
782
+ return UIImage(contentsOfFile: path)
783
+ }
282
784
 
283
- applyShowUIElementsToDriveViewIfReady()
785
+ return UIImage(named: source) ?? UIImage(contentsOfFile: source)
284
786
  }
285
787
 
286
- private func applyShowUIElementsToDriveViewIfReady() {
287
- guard driveView != nil else {
288
- pendingShowUIElements = showUIElements
788
+ private func applyAnnotationImage(
789
+ source: String?,
790
+ currentSource: @escaping () -> String?,
791
+ apply: @escaping (UIImage?) -> Void
792
+ ) {
793
+ guard let source, !source.isEmpty else {
794
+ apply(nil)
289
795
  return
290
796
  }
291
797
 
292
- guard driveViewLoaded else {
293
- pendingShowUIElements = showUIElements
798
+ if source.hasPrefix("http://") || source.hasPrefix("https://") {
799
+ DispatchQueue.global().async {
800
+ let image: UIImage? = {
801
+ guard let url = URL(string: source),
802
+ let data = try? Data(contentsOf: url) else {
803
+ return nil
804
+ }
805
+ return UIImage(data: data)
806
+ }()
807
+
808
+ DispatchQueue.main.async {
809
+ guard currentSource() == source else {
810
+ return
811
+ }
812
+ apply(image)
813
+ }
814
+ }
815
+ return
816
+ }
817
+
818
+ apply(resolveLocalImage(source))
819
+ }
820
+
821
+ private func applyCarImageSource() {
822
+ guard let driveView else {
823
+ return
824
+ }
825
+ applyAnnotationImage(source: carImageSource, currentSource: { [weak self] in
826
+ self?.carImageSource
827
+ }) { [weak driveView] image in
828
+ driveView?.setCarImage(image)
829
+ }
830
+ }
831
+
832
+ private func applyCarCompassImageSource() {
833
+ guard let driveView else {
834
+ return
835
+ }
836
+ applyAnnotationImage(source: carCompassImageSource, currentSource: { [weak self] in
837
+ self?.carCompassImageSource
838
+ }) { [weak driveView] image in
839
+ driveView?.setCarCompassImage(image)
840
+ }
841
+ }
842
+
843
+ private func applyStartPointImageSource() {
844
+ guard let driveView else {
845
+ return
846
+ }
847
+ applyAnnotationImage(source: startPointImageSource, currentSource: { [weak self] in
848
+ self?.startPointImageSource
849
+ }) { [weak driveView] image in
850
+ driveView?.setStartPointImage(image)
851
+ }
852
+ }
853
+
854
+ private func applyWayPointImageSource() {
855
+ guard let driveView else {
856
+ return
857
+ }
858
+ applyAnnotationImage(source: wayPointImageSource, currentSource: { [weak self] in
859
+ self?.wayPointImageSource
860
+ }) { [weak driveView] image in
861
+ driveView?.setWayPointImage(image)
862
+ }
863
+ }
864
+
865
+ private func applyEndPointImageSource() {
866
+ guard let driveView else {
867
+ return
868
+ }
869
+ applyAnnotationImage(source: endPointImageSource, currentSource: { [weak self] in
870
+ self?.endPointImageSource
871
+ }) { [weak driveView] image in
872
+ driveView?.setEndPointImage(image)
873
+ }
874
+ }
875
+
876
+ private func applyCameraImageSource() {
877
+ guard let driveView else {
294
878
  return
295
879
  }
880
+ applyAnnotationImage(source: cameraImageSource, currentSource: { [weak self] in
881
+ self?.cameraImageSource
882
+ }) { [weak driveView] image in
883
+ driveView?.setCameraImage(image)
884
+ }
885
+ }
296
886
 
887
+ private func applyShowUIElementsToDriveViewIfReady() {
888
+ guard let driveView else {
889
+ pendingShowUIElements = showUIElements
890
+ return
891
+ }
297
892
  let value = pendingShowUIElements ?? showUIElements
298
893
  pendingShowUIElements = nil
894
+ driveView.showUIElements = value
895
+ applyCustomUILayoutOptionsIfNeeded()
896
+ }
299
897
 
300
- if value == false && (!hasStartedNavi || !hasReceivedFirstNaviData) {
301
- pendingShowUIElements = false
302
- pendingShowUIElementsWorkItem?.cancel()
303
- let workItem = DispatchWorkItem { [weak self] in
304
- guard let self else { return }
305
- self.applyDriveViewShowUIElements(true, remainingAttempts: 20)
306
- }
307
- pendingShowUIElementsWorkItem = workItem
308
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.08, execute: workItem)
898
+ private func applyCustomUILayoutOptionsIfNeeded() {
899
+ guard let driveView else {
309
900
  return
310
901
  }
311
902
 
312
- pendingShowUIElementsWorkItem?.cancel()
313
- let workItem = DispatchWorkItem { [weak self] in
314
- guard let self else { return }
315
- self.applyDriveViewShowUIElements(value, remainingAttempts: 20)
903
+ guard showUIElements == false else {
904
+ return
316
905
  }
317
- pendingShowUIElementsWorkItem = workItem
318
906
 
319
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.08, execute: workItem)
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
+ scheduleOverviewRouteVisibleRegionRefresh()
320
917
  }
321
918
 
322
- private func applyDriveViewShowUIElements(_ value: Bool, remainingAttempts: Int) {
323
- guard let driveView else { return }
919
+ private func refreshOverviewRouteVisibleRegionIfNeeded() {
920
+ guard showUIElements == false, let driveView else {
921
+ return
922
+ }
324
923
 
325
- if driveView.bounds.isEmpty {
326
- if remainingAttempts <= 0 {
327
- return
328
- }
329
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self] in
330
- self?.applyDriveViewShowUIElements(value, remainingAttempts: remainingAttempts - 1)
924
+ guard driveView.showMode == .overview else {
925
+ return
926
+ }
927
+
928
+ driveView.updateRoutePolylineInTheVisualRangeWhenTheShowModeIsOverview()
929
+ }
930
+
931
+ private func scheduleOverviewRouteVisibleRegionRefresh() {
932
+ DispatchQueue.main.async { [weak self] in
933
+ self?.driveView?.setNeedsLayout()
934
+ self?.driveView?.layoutIfNeeded()
935
+ self?.refreshOverviewRouteVisibleRegionIfNeeded()
936
+ }
937
+ }
938
+
939
+ private func applyHideNativeLaneInfoLayout(_ hidden: Bool) {
940
+ (driveView as? ExpoGaodeMapCustomDriveView)?.suppressLaneInfoUI = hidden
941
+ }
942
+
943
+ private func applyHideNativeTopInfoLayout(_ hidden: Bool) {
944
+ (driveView as? ExpoGaodeMapCustomDriveView)?.suppressTopInfoUI = hidden
945
+ driveView?.setNeedsLayout()
946
+ driveView?.layoutIfNeeded()
947
+ }
948
+
949
+ private func applyTrafficBarFrame(_ frame: CGRect) {
950
+ guard let driveView else {
951
+ return
952
+ }
953
+
954
+ guard frame.width > 0, frame.height > 0 else {
955
+ return
956
+ }
957
+
958
+ driveView.tmcRouteFrame = frame
959
+ }
960
+
961
+ private func applyTrafficBarColors(_ colors: [String: Any]?) {
962
+ guard let driveView, let colors else {
963
+ return
964
+ }
965
+
966
+ let mappings: [(String, AMapNaviRouteStatus)] = [
967
+ ("unknown", .unknow),
968
+ ("smooth", .smooth),
969
+ ("fineOpen", .fineOpen),
970
+ ("slow", .slow),
971
+ ("jam", .jam),
972
+ ("seriousJam", .seriousJam),
973
+ ("defaultRoad", .default),
974
+ ]
975
+
976
+ let resolvedColors = mappings.compactMap { key, status -> AMapNaviTMCStatusColor? in
977
+ guard let value = colors[key], let color = ColorParser.parseColor(value) else {
978
+ return nil
331
979
  }
980
+ let item = AMapNaviTMCStatusColor()
981
+ item.status = status
982
+ item.color = color
983
+ return item
984
+ }
985
+
986
+ guard !resolvedColors.isEmpty else {
332
987
  return
333
988
  }
334
989
 
335
- driveView.showUIElements = value
990
+ driveView.tmcRouteColor = resolvedColors
991
+ }
992
+
993
+ private func emitTrafficStatusesUpdate(_ trafficStatuses: [AMapNaviTrafficStatus]?) {
994
+ // iOS 会提供 fineStatus;统一事件结构时保留它,方便 RN 自绘层按需细化颜色策略。
995
+ let items = (trafficStatuses ?? []).map { status in
996
+ var payload: [String: Any] = [
997
+ "status": Int(status.status.rawValue),
998
+ "length": status.length
999
+ ]
1000
+ payload["fineStatus"] = status.trafficFineStatus
1001
+ return payload
1002
+ }
1003
+
1004
+ var payload: [String: Any] = [
1005
+ "items": items
1006
+ ]
1007
+
1008
+ if let totalLength = currentRouteTotalLength, totalLength > 0 {
1009
+ payload["totalLength"] = totalLength
1010
+ }
1011
+
1012
+ if let retainDistance = lastNavigationInfoPayload?["pathRetainDistance"] as? Int {
1013
+ payload["retainDistance"] = retainDistance
1014
+ }
1015
+
1016
+ onTrafficStatusesUpdate(payload)
336
1017
  }
337
1018
 
338
1019
  // MARK: - Prop Setters
@@ -354,7 +1035,7 @@ public class ExpoGaodeMapNaviView: ExpoView {
354
1035
  }
355
1036
 
356
1037
  private func applyTrafficLayerEnabled(_ enabled: Bool) {
357
- driveView?.showTrafficLayer = enabled
1038
+ driveView?.mapShowTraffic = enabled
358
1039
  }
359
1040
 
360
1041
  private func applyRealCrossDisplay(_ enabled: Bool) {
@@ -377,6 +1058,7 @@ public class ExpoGaodeMapNaviView: ExpoView {
377
1058
  default:
378
1059
  break
379
1060
  }
1061
+ scheduleOverviewRouteVisibleRegionRefresh()
380
1062
  }
381
1063
 
382
1064
  private func applyNightMode(_ enabled: Bool) {
@@ -420,6 +1102,9 @@ public class ExpoGaodeMapNaviView: ExpoView {
420
1102
  return
421
1103
  }
422
1104
 
1105
+ rebindDriveManagerToView()
1106
+ resetTransientNavigationState()
1107
+
423
1108
  startCoordinate = AMapNaviPoint.location(withLatitude: CGFloat(startLat), longitude: CGFloat(startLng))
424
1109
  endCoordinate = AMapNaviPoint.location(withLatitude: CGFloat(endLat), longitude: CGFloat(endLng))
425
1110
 
@@ -446,9 +1131,61 @@ public class ExpoGaodeMapNaviView: ExpoView {
446
1131
  promise.reject("CALCULATE_FAILED", "启动路线规划失败")
447
1132
  }
448
1133
  }
1134
+
1135
+ func startNavigationWithIndependentPath(
1136
+ token: Int,
1137
+ routeId: Int?,
1138
+ routeIndex: Int?,
1139
+ naviType: Int?,
1140
+ promise: Promise
1141
+ ) {
1142
+ do {
1143
+ try checkPrivacyReady()
1144
+ try checkAMapInitialization()
1145
+ try ensureBackgroundLocationModeForNavigation()
1146
+ } catch {
1147
+ let nsError = error as NSError
1148
+ let code: String
1149
+ if nsError.domain == "ExpoGaodeMapPrivacy" {
1150
+ code = "PRIVACY_NOT_AGREED"
1151
+ } else if nsError.code == -1003 {
1152
+ code = "BACKGROUND_LOCATION_NOT_ENABLED"
1153
+ } else {
1154
+ code = "AMAP_NOT_INITIALIZED"
1155
+ }
1156
+ promise.reject(code, formatError(error))
1157
+ return
1158
+ }
1159
+
1160
+ rebindDriveManagerToView()
1161
+ resetTransientNavigationState()
1162
+
1163
+ if let naviType {
1164
+ self.naviType = naviType
1165
+ }
1166
+
1167
+ let ok = independentRouteManager.start(
1168
+ token: token,
1169
+ naviType: self.naviType,
1170
+ routeId: routeId,
1171
+ routeIndex: routeIndex
1172
+ )
1173
+
1174
+ if ok {
1175
+ promise.resolve([
1176
+ "success": true,
1177
+ "message": "独立路径导航启动中...",
1178
+ "token": token,
1179
+ "naviType": self.naviType
1180
+ ])
1181
+ } else {
1182
+ promise.reject("START_INDEPENDENT_NAVI_FAILED", "独立路径导航启动失败")
1183
+ }
1184
+ }
449
1185
 
450
1186
  func stopNavigation(promise: Promise) {
451
1187
  driveManager?.stopNavi()
1188
+ resetTransientNavigationState()
452
1189
  promise.resolve([
453
1190
  "success": true,
454
1191
  "message": "导航已停止"
@@ -466,6 +1203,7 @@ public class ExpoGaodeMapNaviView: ExpoView {
466
1203
  public override func layoutSubviews() {
467
1204
  super.layoutSubviews()
468
1205
  driveView?.frame = bounds
1206
+ scheduleOverviewRouteVisibleRegionRefresh()
469
1207
  }
470
1208
 
471
1209
  deinit {
@@ -473,7 +1211,9 @@ public class ExpoGaodeMapNaviView: ExpoView {
473
1211
  if let view = driveView {
474
1212
  driveManager?.removeDataRepresentative(view)
475
1213
  }
1214
+ driveManager?.removeDataRepresentative(self)
476
1215
  driveManager?.delegate = nil
1216
+ clearCachedTurnIconUris()
477
1217
  }
478
1218
  }
479
1219
 
@@ -495,6 +1235,9 @@ extension ExpoGaodeMapNaviView: AMapNaviDriveManagerDelegate {
495
1235
  }
496
1236
 
497
1237
  applyShowUIElementsToDriveViewIfReady()
1238
+ DispatchQueue.main.async { [weak self] in
1239
+ self?.applyCustomAnnotationImages()
1240
+ }
498
1241
  }
499
1242
 
500
1243
  public func driveManager(_ driveManager: AMapNaviDriveManager, onCalculateRouteFailure error: Error) {
@@ -521,37 +1264,10 @@ extension ExpoGaodeMapNaviView: AMapNaviDriveManagerDelegate {
521
1264
  }
522
1265
 
523
1266
  public func driveManagerDidEndEmulatorNavi(_ driveManager: AMapNaviDriveManager) {
1267
+ resetTransientNavigationState()
524
1268
  onNavigationEnded([:])
525
1269
  }
526
1270
 
527
- public func driveManager(_ driveManager: AMapNaviDriveManager, didUpdate naviLocation: AMapNaviLocation) {
528
- if !hasReceivedFirstNaviData {
529
- hasReceivedFirstNaviData = true
530
- applyShowUIElementsToDriveViewIfReady()
531
- }
532
- onLocationUpdate([
533
- "latitude": naviLocation.coordinate.latitude,
534
- "longitude": naviLocation.coordinate.longitude,
535
- "speed": naviLocation.speed,
536
- "bearing": naviLocation.heading
537
- ])
538
- }
539
-
540
- public func driveManager(_ driveManager: AMapNaviDriveManager, didUpdate naviInfo: AMapNaviInfo) {
541
- if !hasReceivedFirstNaviData {
542
- hasReceivedFirstNaviData = true
543
- applyShowUIElementsToDriveViewIfReady()
544
- }
545
- onNavigationInfoUpdate([
546
- "currentRoadName": naviInfo.currentRoadName ?? "",
547
- "nextRoadName": naviInfo.nextRoadName ?? "",
548
- "pathRetainDistance": naviInfo.routeRemainDistance,
549
- "pathRetainTime": naviInfo.routeRemainTime,
550
- "curStepRetainDistance": naviInfo.segmentRemainDistance,
551
- "curStepRetainTime": naviInfo.segmentRemainTime
552
- ])
553
- }
554
-
555
1271
  public func driveManager(_ driveManager: AMapNaviDriveManager, playNaviSound soundString: String, soundStringType: AMapNaviSoundType) {
556
1272
  onNavigationText([
557
1273
  "type": soundStringType.rawValue,
@@ -587,13 +1303,119 @@ extension ExpoGaodeMapNaviView: AMapNaviDriveManagerDelegate {
587
1303
  extension ExpoGaodeMapNaviView: AMapNaviDriveViewDelegate {
588
1304
 
589
1305
  public func driveViewDidLoad(_ driveView: AMapNaviDriveView) {
590
- driveViewLoaded = true
591
1306
  applyViewOptions()
592
1307
  applyShowUIElementsToDriveViewIfReady()
1308
+ scheduleOverviewRouteVisibleRegionRefresh()
593
1309
  onNavigationReady([:])
594
1310
  }
595
1311
 
596
1312
  public func driveViewEdgePadding(_ driveView: AMapNaviDriveView) -> UIEdgeInsets {
597
- return .zero
1313
+ return driveViewEdgePadding
1314
+ }
1315
+
1316
+ public func driveView(_ driveView: AMapNaviDriveView, didChange showMode: AMapNaviDriveViewShowMode) {
1317
+ if showMode == .overview {
1318
+ scheduleOverviewRouteVisibleRegionRefresh()
1319
+ }
1320
+ }
1321
+ }
1322
+
1323
+ extension ExpoGaodeMapNaviView: AMapNaviDriveDataRepresentable {
1324
+ public func driveManager(_ driveManager: AMapNaviDriveManager, update naviRoute: AMapNaviRoute?) {
1325
+ if let routeLength = naviRoute?.routeLength, routeLength > 0 {
1326
+ currentRouteTotalLength = routeLength
1327
+ }
1328
+ }
1329
+
1330
+ public func driveManager(_ driveManager: AMapNaviDriveManager, update naviLocation: AMapNaviLocation?) {
1331
+ guard let naviLocation else {
1332
+ return
1333
+ }
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
+ ])
1345
+ }
1346
+
1347
+ public func driveManager(_ driveManager: AMapNaviDriveManager, update naviInfo: AMapNaviInfo?) {
1348
+ guard let naviInfo else {
1349
+ return
1350
+ }
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
+ ])
1373
+ }
1374
+
1375
+ public func driveManager(_ driveManager: AMapNaviDriveManager, showCross crossImage: UIImage?) {
1376
+ isCrossVisible = true
1377
+ emitVisualStateUpdate()
1378
+ }
1379
+
1380
+ public func driveManagerHideCrossImage(_ driveManager: AMapNaviDriveManager) {
1381
+ isCrossVisible = false
1382
+ emitVisualStateUpdate()
1383
+ }
1384
+
1385
+ public func driveManager(_ driveManager: AMapNaviDriveManager, showLaneBackInfo laneBackInfo: String, laneSelectInfo: String) {
1386
+ isLaneInfoVisible = true
1387
+ emitVisualStateUpdate()
1388
+ if let payload = serializeLaneInfo(laneBackInfo: laneBackInfo, laneSelectInfo: laneSelectInfo) {
1389
+ onLaneInfoUpdate(payload)
1390
+ }
1391
+ }
1392
+
1393
+ public func driveManagerHideLaneInfo(_ driveManager: AMapNaviDriveManager) {
1394
+ isLaneInfoVisible = false
1395
+ emitVisualStateUpdate()
1396
+ }
1397
+
1398
+ public func driveManager(_ driveManager: AMapNaviDriveManager, updateTrafficStatus trafficStatus: [AMapNaviTrafficStatus]?) {
1399
+ emitTrafficStatusesUpdate(trafficStatus)
1400
+ }
1401
+
1402
+ public func driveManager(_ driveManager: AMapNaviDriveManager, updateTurnIconImage turnIconImage: UIImage?, turn turnIconType: AMapNaviIconType) {
1403
+ lastTurnIconType = Int(turnIconType.rawValue)
1404
+ lastTurnIconImageUri = cacheTurnIconImage(
1405
+ turnIconImage,
1406
+ prefix: "turn_icon",
1407
+ previousUri: lastTurnIconImageUri
1408
+ )
1409
+ reemitLastNavigationInfoIfNeeded()
1410
+ }
1411
+
1412
+ public func driveManager(_ driveManager: AMapNaviDriveManager, updateNextTurnIconImage turnIconImage: UIImage?, nextTurn turnIconType: AMapNaviIconType) {
1413
+ lastNextTurnIconType = Int(turnIconType.rawValue)
1414
+ lastNextTurnIconImageUri = cacheTurnIconImage(
1415
+ turnIconImage,
1416
+ prefix: "next_turn_icon",
1417
+ previousUri: lastNextTurnIconImageUri
1418
+ )
1419
+ reemitLastNavigationInfoIfNeeded()
598
1420
  }
599
1421
  }