expo-gaode-map-navigation 2.0.9-next.0 → 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 (127) hide show
  1. package/README.md +258 -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 +7 -1
  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 +920 -67
  116. package/ios/ExpoGaodeMapNaviViewModule.swift +87 -1
  117. package/ios/ExpoGaodeMapNavigationModule.swift +90 -9
  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/GaodeMapPrivacyManager.swift +23 -3
  122. package/ios/map/modules/LocationManager.swift +17 -0
  123. package/ios/map/utils/PermissionManager.swift +11 -6
  124. package/ios/routes/drive/DriveTruckRouteCalculator.swift +9 -0
  125. package/ios/routes/walkride/WalkRideRouteCalculator.swift +30 -0
  126. package/ios/services/IndependentRouteService.swift +211 -44
  127. package/package.json +16 -4
@@ -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
 
@@ -68,6 +244,28 @@ public class ExpoGaodeMapNaviView: ExpoView {
68
244
  )
69
245
  }
70
246
  }
247
+
248
+ /// 检查 iOS 是否配置了后台定位模式(避免导航启动时触发系统异常)
249
+ private func ensureBackgroundLocationModeForNavigation() throws {
250
+ let backgroundModes = Bundle.main.object(forInfoDictionaryKey: "UIBackgroundModes") as? [String]
251
+ guard backgroundModes?.contains("location") == true else {
252
+ throw NSError(
253
+ domain: "ExpoGaodeMapNaviView",
254
+ code: -1003,
255
+ userInfo: [
256
+ NSLocalizedDescriptionKey: "iOS 后台定位模式未开启,无法安全启动导航",
257
+ NSLocalizedFailureReasonErrorKey: "Info.plist 缺少 UIBackgroundModes: location",
258
+ NSLocalizedRecoverySuggestionErrorKey: """
259
+ 请在 app.json 插件配置中开启后台定位后重新 prebuild:
260
+ ["expo-gaode-map-navigation", {
261
+ "enableBackgroundLocation": true
262
+ }]
263
+ 然后执行:npx expo prebuild --clean
264
+ """
265
+ ]
266
+ )
267
+ }
268
+ }
71
269
 
72
270
  /// 格式化错误信息
73
271
  private func formatError(_ error: Error) -> String {
@@ -99,15 +297,30 @@ public class ExpoGaodeMapNaviView: ExpoView {
99
297
  let onGpsStatusChanged = EventDispatcher()
100
298
  let onNavigationInfoUpdate = EventDispatcher()
101
299
  let onGpsSignalWeak = EventDispatcher()
300
+ let onNavigationVisualStateUpdate = EventDispatcher()
301
+ let onLaneInfoUpdate = EventDispatcher()
302
+ let onTrafficStatusesUpdate = EventDispatcher()
102
303
 
103
304
  // MARK: - Properties
104
305
  private var driveView: AMapNaviDriveView?
105
- private var driveViewLoaded: Bool = false
106
306
  private var pendingShowUIElements: Bool?
107
- private var pendingShowUIElementsWorkItem: DispatchWorkItem?
108
307
  private var hasStartedNavi: Bool = false
109
308
  private var hasReceivedFirstNaviData: Bool = false
110
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
+ }
111
324
 
112
325
  // Props - 通用属性
113
326
  var naviType: Int = 0 // 0: GPS, 1: Emulator
@@ -117,6 +330,24 @@ public class ExpoGaodeMapNaviView: ExpoView {
117
330
  var showCamera: Bool = true {
118
331
  didSet { applyShowCamera(showCamera) }
119
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
+ }
120
351
  var autoLockCar: Bool = true {
121
352
  didSet { applyAutoLockCar(autoLockCar) }
122
353
  }
@@ -149,6 +380,12 @@ public class ExpoGaodeMapNaviView: ExpoView {
149
380
  var showTrafficBar: Bool = true {
150
381
  didSet { driveView?.showTrafficBar = showTrafficBar }
151
382
  }
383
+ var trafficBarFrame: CGRect = .zero {
384
+ didSet { applyTrafficBarFrame(trafficBarFrame) }
385
+ }
386
+ var trafficBarColors: [String: Any]? {
387
+ didSet { applyTrafficBarColors(trafficBarColors) }
388
+ }
152
389
  var showBrowseRouteButton: Bool = true {
153
390
  didSet { driveView?.showBrowseRouteButton = showBrowseRouteButton }
154
391
  }
@@ -158,6 +395,12 @@ public class ExpoGaodeMapNaviView: ExpoView {
158
395
  var showTrafficButton: Bool = true {
159
396
  didSet { driveView?.showTrafficButton = showTrafficButton }
160
397
  }
398
+ var showBackupRoute: Bool = true {
399
+ didSet { driveView?.showBackupRoute = showBackupRoute }
400
+ }
401
+ var showEagleMap: Bool = false {
402
+ didSet { driveView?.showEagleMap = showEagleMap }
403
+ }
161
404
  var showUIElements: Bool = true {
162
405
  didSet { applyShowUIElementsToDriveViewIfReady() }
163
406
  }
@@ -170,12 +413,43 @@ public class ExpoGaodeMapNaviView: ExpoView {
170
413
  var showTrafficLights: Bool = true {
171
414
  didSet { driveView?.showTrafficLights = showTrafficLights }
172
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
+ }
173
428
  var mapViewModeType: Int = 0 {
174
429
  didSet { applyMapViewModeType(mapViewModeType) }
175
430
  }
176
431
  var lineWidth: CGFloat = 0 {
177
432
  didSet { driveView?.lineWidth = lineWidth }
178
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
+ }
179
453
 
180
454
  func applyShowUIElements(_ visible: Bool) {
181
455
  showUIElements = visible
@@ -216,13 +490,16 @@ public class ExpoGaodeMapNaviView: ExpoView {
216
490
 
217
491
  // 初始化驾车导航管理器
218
492
  driveManager = AMapNaviDriveManager.sharedInstance()
219
- driveManager?.delegate = self
493
+ rebindDriveManagerToView()
220
494
 
221
495
  // 使用内置语音
222
496
  driveManager?.isUseInternalTTS = true
223
497
 
224
498
  // 初始化导航视图
225
- driveView = AMapNaviDriveView(frame: bounds)
499
+ let customDriveView = ExpoGaodeMapCustomDriveView(frame: bounds)
500
+ customDriveView.suppressTopInfoUI = hideNativeTopInfoLayout
501
+ customDriveView.suppressLaneInfoUI = hideNativeLaneInfoLayout
502
+ driveView = customDriveView
226
503
  driveView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
227
504
  driveView?.delegate = self
228
505
 
@@ -234,13 +511,233 @@ public class ExpoGaodeMapNaviView: ExpoView {
234
511
  // 应用初始配置
235
512
  applyViewOptions()
236
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
+ }
237
733
 
238
734
  private func applyViewOptions() {
239
735
  // 通用属性
736
+ driveView?.showUIElements = showUIElements
240
737
  driveView?.showCamera = showCamera
241
738
  driveView?.autoSwitchShowModeToCarPositionLocked = autoLockCar
242
739
  driveView?.autoZoomMapLevel = autoChangeZoom
243
- driveView?.showTrafficLayer = trafficLayerEnabled
740
+ driveView?.mapShowTraffic = trafficLayerEnabled
244
741
  driveView?.showCrossImage = realCrossDisplay
245
742
  driveView?.trackingMode = naviMode == 0 ? .carNorth : .mapNorth
246
743
 
@@ -248,69 +745,275 @@ public class ExpoGaodeMapNaviView: ExpoView {
248
745
  driveView?.showRoute = showRoute
249
746
  driveView?.showTurnArrow = showTurnArrow
250
747
  driveView?.showTrafficBar = showTrafficBar
748
+ applyTrafficBarFrame(trafficBarFrame)
749
+ applyTrafficBarColors(trafficBarColors)
251
750
  driveView?.showBrowseRouteButton = showBrowseRouteButton
252
751
  driveView?.showMoreButton = showMoreButton
253
752
  driveView?.showTrafficButton = showTrafficButton
753
+ driveView?.showBackupRoute = showBackupRoute
754
+ driveView?.showEagleMap = showEagleMap
254
755
  driveView?.showGreyAfterPass = showGreyAfterPass
255
756
  driveView?.showVectorline = showVectorline
256
757
  driveView?.showTrafficLights = showTrafficLights
758
+ if let showCompassEnabled {
759
+ driveView?.showCompass = showCompassEnabled
760
+ }
761
+ driveView?.showDriveCongestion = showDriveCongestion
762
+ driveView?.showTrafficLightView = showTrafficLightView
257
763
  if lineWidth > 0 {
258
764
  driveView?.lineWidth = lineWidth
259
765
  }
766
+ applyCustomAnnotationImages()
767
+ applyCustomUILayoutOptionsIfNeeded()
768
+ }
260
769
 
261
- applyShowUIElementsToDriveViewIfReady()
770
+ private func applyCustomAnnotationImages() {
771
+ applyCarImageSource()
772
+ applyCarCompassImageSource()
773
+ applyStartPointImageSource()
774
+ applyWayPointImageSource()
775
+ applyEndPointImageSource()
776
+ applyCameraImageSource()
262
777
  }
263
778
 
264
- private func applyShowUIElementsToDriveViewIfReady() {
265
- guard driveView != nil else {
266
- pendingShowUIElements = showUIElements
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
+ }
784
+
785
+ return UIImage(named: source) ?? UIImage(contentsOfFile: source)
786
+ }
787
+
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)
267
795
  return
268
796
  }
269
797
 
270
- guard driveViewLoaded else {
271
- 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 {
272
834
  return
273
835
  }
836
+ applyAnnotationImage(source: carCompassImageSource, currentSource: { [weak self] in
837
+ self?.carCompassImageSource
838
+ }) { [weak driveView] image in
839
+ driveView?.setCarCompassImage(image)
840
+ }
841
+ }
274
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 {
878
+ return
879
+ }
880
+ applyAnnotationImage(source: cameraImageSource, currentSource: { [weak self] in
881
+ self?.cameraImageSource
882
+ }) { [weak driveView] image in
883
+ driveView?.setCameraImage(image)
884
+ }
885
+ }
886
+
887
+ private func applyShowUIElementsToDriveViewIfReady() {
888
+ guard let driveView else {
889
+ pendingShowUIElements = showUIElements
890
+ return
891
+ }
275
892
  let value = pendingShowUIElements ?? showUIElements
276
893
  pendingShowUIElements = nil
894
+ driveView.showUIElements = value
895
+ applyCustomUILayoutOptionsIfNeeded()
896
+ }
277
897
 
278
- if value == false && (!hasStartedNavi || !hasReceivedFirstNaviData) {
279
- pendingShowUIElements = false
280
- pendingShowUIElementsWorkItem?.cancel()
281
- let workItem = DispatchWorkItem { [weak self] in
282
- guard let self else { return }
283
- self.applyDriveViewShowUIElements(true, remainingAttempts: 20)
284
- }
285
- pendingShowUIElementsWorkItem = workItem
286
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.08, execute: workItem)
898
+ private func applyCustomUILayoutOptionsIfNeeded() {
899
+ guard let driveView else {
287
900
  return
288
901
  }
289
902
 
290
- pendingShowUIElementsWorkItem?.cancel()
291
- let workItem = DispatchWorkItem { [weak self] in
292
- guard let self else { return }
293
- self.applyDriveViewShowUIElements(value, remainingAttempts: 20)
903
+ guard showUIElements == false else {
904
+ return
294
905
  }
295
- pendingShowUIElementsWorkItem = workItem
296
906
 
297
- 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()
298
917
  }
299
918
 
300
- private func applyDriveViewShowUIElements(_ value: Bool, remainingAttempts: Int) {
301
- guard let driveView else { return }
919
+ private func refreshOverviewRouteVisibleRegionIfNeeded() {
920
+ guard showUIElements == false, let driveView else {
921
+ return
922
+ }
302
923
 
303
- if driveView.bounds.isEmpty {
304
- if remainingAttempts <= 0 {
305
- return
306
- }
307
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self] in
308
- 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
309
979
  }
980
+ let item = AMapNaviTMCStatusColor()
981
+ item.status = status
982
+ item.color = color
983
+ return item
984
+ }
985
+
986
+ guard !resolvedColors.isEmpty else {
310
987
  return
311
988
  }
312
989
 
313
- 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)
314
1017
  }
315
1018
 
316
1019
  // MARK: - Prop Setters
@@ -332,7 +1035,7 @@ public class ExpoGaodeMapNaviView: ExpoView {
332
1035
  }
333
1036
 
334
1037
  private func applyTrafficLayerEnabled(_ enabled: Bool) {
335
- driveView?.showTrafficLayer = enabled
1038
+ driveView?.mapShowTraffic = enabled
336
1039
  }
337
1040
 
338
1041
  private func applyRealCrossDisplay(_ enabled: Bool) {
@@ -355,6 +1058,7 @@ public class ExpoGaodeMapNaviView: ExpoView {
355
1058
  default:
356
1059
  break
357
1060
  }
1061
+ scheduleOverviewRouteVisibleRegionRefresh()
358
1062
  }
359
1063
 
360
1064
  private func applyNightMode(_ enabled: Bool) {
@@ -383,12 +1087,24 @@ public class ExpoGaodeMapNaviView: ExpoView {
383
1087
  do {
384
1088
  try checkPrivacyReady()
385
1089
  try checkAMapInitialization()
1090
+ try ensureBackgroundLocationModeForNavigation()
386
1091
  } catch {
387
- let code = (error as NSError).domain == "ExpoGaodeMapPrivacy" ? "PRIVACY_NOT_AGREED" : "AMAP_NOT_INITIALIZED"
1092
+ let nsError = error as NSError
1093
+ let code: String
1094
+ if nsError.domain == "ExpoGaodeMapPrivacy" {
1095
+ code = "PRIVACY_NOT_AGREED"
1096
+ } else if nsError.code == -1003 {
1097
+ code = "BACKGROUND_LOCATION_NOT_ENABLED"
1098
+ } else {
1099
+ code = "AMAP_NOT_INITIALIZED"
1100
+ }
388
1101
  promise.reject(code, formatError(error))
389
1102
  return
390
1103
  }
391
1104
 
1105
+ rebindDriveManagerToView()
1106
+ resetTransientNavigationState()
1107
+
392
1108
  startCoordinate = AMapNaviPoint.location(withLatitude: CGFloat(startLat), longitude: CGFloat(startLng))
393
1109
  endCoordinate = AMapNaviPoint.location(withLatitude: CGFloat(endLat), longitude: CGFloat(endLng))
394
1110
 
@@ -415,9 +1131,61 @@ public class ExpoGaodeMapNaviView: ExpoView {
415
1131
  promise.reject("CALCULATE_FAILED", "启动路线规划失败")
416
1132
  }
417
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
+ }
418
1185
 
419
1186
  func stopNavigation(promise: Promise) {
420
1187
  driveManager?.stopNavi()
1188
+ resetTransientNavigationState()
421
1189
  promise.resolve([
422
1190
  "success": true,
423
1191
  "message": "导航已停止"
@@ -435,6 +1203,7 @@ public class ExpoGaodeMapNaviView: ExpoView {
435
1203
  public override func layoutSubviews() {
436
1204
  super.layoutSubviews()
437
1205
  driveView?.frame = bounds
1206
+ scheduleOverviewRouteVisibleRegionRefresh()
438
1207
  }
439
1208
 
440
1209
  deinit {
@@ -442,7 +1211,9 @@ public class ExpoGaodeMapNaviView: ExpoView {
442
1211
  if let view = driveView {
443
1212
  driveManager?.removeDataRepresentative(view)
444
1213
  }
1214
+ driveManager?.removeDataRepresentative(self)
445
1215
  driveManager?.delegate = nil
1216
+ clearCachedTurnIconUris()
446
1217
  }
447
1218
  }
448
1219
 
@@ -464,6 +1235,9 @@ extension ExpoGaodeMapNaviView: AMapNaviDriveManagerDelegate {
464
1235
  }
465
1236
 
466
1237
  applyShowUIElementsToDriveViewIfReady()
1238
+ DispatchQueue.main.async { [weak self] in
1239
+ self?.applyCustomAnnotationImages()
1240
+ }
467
1241
  }
468
1242
 
469
1243
  public func driveManager(_ driveManager: AMapNaviDriveManager, onCalculateRouteFailure error: Error) {
@@ -490,37 +1264,10 @@ extension ExpoGaodeMapNaviView: AMapNaviDriveManagerDelegate {
490
1264
  }
491
1265
 
492
1266
  public func driveManagerDidEndEmulatorNavi(_ driveManager: AMapNaviDriveManager) {
1267
+ resetTransientNavigationState()
493
1268
  onNavigationEnded([:])
494
1269
  }
495
1270
 
496
- public func driveManager(_ driveManager: AMapNaviDriveManager, didUpdate naviLocation: AMapNaviLocation) {
497
- if !hasReceivedFirstNaviData {
498
- hasReceivedFirstNaviData = true
499
- applyShowUIElementsToDriveViewIfReady()
500
- }
501
- onLocationUpdate([
502
- "latitude": naviLocation.coordinate.latitude,
503
- "longitude": naviLocation.coordinate.longitude,
504
- "speed": naviLocation.speed,
505
- "bearing": naviLocation.heading
506
- ])
507
- }
508
-
509
- public func driveManager(_ driveManager: AMapNaviDriveManager, didUpdate naviInfo: AMapNaviInfo) {
510
- if !hasReceivedFirstNaviData {
511
- hasReceivedFirstNaviData = true
512
- applyShowUIElementsToDriveViewIfReady()
513
- }
514
- onNavigationInfoUpdate([
515
- "currentRoadName": naviInfo.currentRoadName ?? "",
516
- "nextRoadName": naviInfo.nextRoadName ?? "",
517
- "pathRetainDistance": naviInfo.routeRemainDistance,
518
- "pathRetainTime": naviInfo.routeRemainTime,
519
- "curStepRetainDistance": naviInfo.segmentRemainDistance,
520
- "curStepRetainTime": naviInfo.segmentRemainTime
521
- ])
522
- }
523
-
524
1271
  public func driveManager(_ driveManager: AMapNaviDriveManager, playNaviSound soundString: String, soundStringType: AMapNaviSoundType) {
525
1272
  onNavigationText([
526
1273
  "type": soundStringType.rawValue,
@@ -556,13 +1303,119 @@ extension ExpoGaodeMapNaviView: AMapNaviDriveManagerDelegate {
556
1303
  extension ExpoGaodeMapNaviView: AMapNaviDriveViewDelegate {
557
1304
 
558
1305
  public func driveViewDidLoad(_ driveView: AMapNaviDriveView) {
559
- driveViewLoaded = true
560
1306
  applyViewOptions()
561
1307
  applyShowUIElementsToDriveViewIfReady()
1308
+ scheduleOverviewRouteVisibleRegionRefresh()
562
1309
  onNavigationReady([:])
563
1310
  }
564
1311
 
565
1312
  public func driveViewEdgePadding(_ driveView: AMapNaviDriveView) -> UIEdgeInsets {
566
- 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()
567
1420
  }
568
1421
  }