expo-gaode-map-navigation 2.0.12-next.0 → 2.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +296 -7
- package/android/build.gradle +12 -4
- package/android/src/main/AndroidManifest.xml +10 -1
- package/android/src/main/cpp/cluster_jni.cpp +56 -0
- package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapModule.kt +49 -8
- package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapOfflineModule.kt +83 -15
- package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapView.kt +13 -3
- package/android/src/main/java/expo/modules/gaodemap/map/managers/UIManager.kt +36 -39
- package/android/src/main/java/expo/modules/gaodemap/map/modules/SDKInitializer.kt +23 -17
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/ClusterView.kt +5 -2
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/HeatMapView.kt +122 -10
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/HeatMapViewModule.kt +2 -2
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerView.kt +37 -25
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerViewModule.kt +6 -6
- package/android/src/main/java/expo/modules/gaodemap/map/search/ExpoGaodeMapSearchModule.kt +751 -0
- package/android/src/main/java/expo/modules/gaodemap/map/utils/GeometryUtils.kt +103 -0
- package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNaviView.kt +1408 -394
- package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNaviViewModule.kt +121 -1
- package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNavigationModule.kt +14 -28
- package/android/src/main/java/expo/modules/gaodemap/navigation/listeners/IndependentRouteListener.kt +28 -3
- package/android/src/main/java/expo/modules/gaodemap/navigation/listeners/RouteCalculateListener.kt +2 -2
- package/android/src/main/java/expo/modules/gaodemap/navigation/managers/IndependentRouteManager.kt +114 -15
- package/android/src/main/java/expo/modules/gaodemap/navigation/routes/drive/DriveTruckRouteCalculator.kt +24 -35
- package/android/src/main/java/expo/modules/gaodemap/navigation/services/IndependentRouteService.kt +50 -36
- package/android/src/main/java/expo/modules/gaodemap/navigation/services/NavigationForegroundService.kt +661 -0
- package/android/src/main/java/expo/modules/gaodemap/navigation/utils/Converters.kt +21 -12
- package/android/src/main/res/drawable/ic_nav_notification_small.xml +10 -0
- package/android/src/main/res/drawable/landback_0.png +0 -0
- package/android/src/main/res/drawable/landback_1.png +0 -0
- package/android/src/main/res/drawable/landback_2.png +0 -0
- package/android/src/main/res/drawable/landback_3.png +0 -0
- package/android/src/main/res/drawable/landback_4.png +0 -0
- package/android/src/main/res/drawable/landback_5.png +0 -0
- package/android/src/main/res/drawable/landback_6.png +0 -0
- package/android/src/main/res/drawable/landback_7.png +0 -0
- package/android/src/main/res/drawable/landback_8.png +0 -0
- package/android/src/main/res/drawable/landback_9.png +0 -0
- package/android/src/main/res/drawable/landback_a.png +0 -0
- package/android/src/main/res/drawable/landback_b.png +0 -0
- package/android/src/main/res/drawable/landback_c.png +0 -0
- package/android/src/main/res/drawable/landback_d.png +0 -0
- package/android/src/main/res/drawable/landback_e.png +0 -0
- package/android/src/main/res/drawable/landback_f.png +0 -0
- package/android/src/main/res/drawable/landback_g.png +0 -0
- package/android/src/main/res/drawable/landback_h.png +0 -0
- package/android/src/main/res/drawable/landback_i.png +0 -0
- package/android/src/main/res/drawable/landback_j.png +0 -0
- package/android/src/main/res/drawable/landback_k.png +0 -0
- package/android/src/main/res/drawable/landback_l.png +0 -0
- package/android/src/main/res/drawable/landfront_0.png +0 -0
- package/android/src/main/res/drawable/landfront_00.png +0 -0
- package/android/src/main/res/drawable/landfront_1.png +0 -0
- package/android/src/main/res/drawable/landfront_11.png +0 -0
- package/android/src/main/res/drawable/landfront_20.png +0 -0
- package/android/src/main/res/drawable/landfront_21.png +0 -0
- package/android/src/main/res/drawable/landfront_22.png +0 -0
- package/android/src/main/res/drawable/landfront_3.png +0 -0
- package/android/src/main/res/drawable/landfront_33.png +0 -0
- package/android/src/main/res/drawable/landfront_40.png +0 -0
- package/android/src/main/res/drawable/landfront_43.png +0 -0
- package/android/src/main/res/drawable/landfront_44.png +0 -0
- package/android/src/main/res/drawable/landfront_5.png +0 -0
- package/android/src/main/res/drawable/landfront_55.png +0 -0
- package/android/src/main/res/drawable/landfront_61.png +0 -0
- package/android/src/main/res/drawable/landfront_63.png +0 -0
- package/android/src/main/res/drawable/landfront_66.png +0 -0
- package/android/src/main/res/drawable/landfront_70.png +0 -0
- package/android/src/main/res/drawable/landfront_71.png +0 -0
- package/android/src/main/res/drawable/landfront_73.png +0 -0
- package/android/src/main/res/drawable/landfront_77.png +0 -0
- package/android/src/main/res/drawable/landfront_8.png +0 -0
- package/android/src/main/res/drawable/landfront_88.png +0 -0
- package/android/src/main/res/drawable/landfront_90.png +0 -0
- package/android/src/main/res/drawable/landfront_95.png +0 -0
- package/android/src/main/res/drawable/landfront_99.png +0 -0
- package/android/src/main/res/drawable/landfront_a0.png +0 -0
- package/android/src/main/res/drawable/landfront_a8.png +0 -0
- package/android/src/main/res/drawable/landfront_aa.png +0 -0
- package/android/src/main/res/drawable/landfront_b1.png +0 -0
- package/android/src/main/res/drawable/landfront_b5.png +0 -0
- package/android/src/main/res/drawable/landfront_bb.png +0 -0
- package/android/src/main/res/drawable/landfront_c3.png +0 -0
- package/android/src/main/res/drawable/landfront_c8.png +0 -0
- package/android/src/main/res/drawable/landfront_cc.png +0 -0
- package/android/src/main/res/drawable/landfront_d.png +0 -0
- package/android/src/main/res/drawable/landfront_dd.png +0 -0
- package/android/src/main/res/drawable/landfront_e1.png +0 -0
- package/android/src/main/res/drawable/landfront_e5.png +0 -0
- package/android/src/main/res/drawable/landfront_ee.png +0 -0
- package/android/src/main/res/drawable/landfront_f0.png +0 -0
- package/android/src/main/res/drawable/landfront_f1.png +0 -0
- package/android/src/main/res/drawable/landfront_f5.png +0 -0
- package/android/src/main/res/drawable/landfront_ff.png +0 -0
- package/android/src/main/res/drawable/landfront_g3.png +0 -0
- package/android/src/main/res/drawable/landfront_g5.png +0 -0
- package/android/src/main/res/drawable/landfront_gg.png +0 -0
- package/android/src/main/res/drawable/landfront_h1.png +0 -0
- package/android/src/main/res/drawable/landfront_h3.png +0 -0
- package/android/src/main/res/drawable/landfront_h5.png +0 -0
- package/android/src/main/res/drawable/landfront_hh.png +0 -0
- package/android/src/main/res/drawable/landfront_i0.png +0 -0
- package/android/src/main/res/drawable/landfront_i3.png +0 -0
- package/android/src/main/res/drawable/landfront_i5.png +0 -0
- package/android/src/main/res/drawable/landfront_ii.png +0 -0
- package/android/src/main/res/drawable/landfront_j1.png +0 -0
- package/android/src/main/res/drawable/landfront_j8.png +0 -0
- package/android/src/main/res/drawable/landfront_jj.png +0 -0
- package/android/src/main/res/drawable/landfront_kk.png +0 -0
- package/android/src/main/res/drawable/landfront_ll.png +0 -0
- package/android/src/main/res/drawable/nav_notification_brand_icon.xml +16 -0
- package/android/src/main/res/drawable/navi_arrow_leftline.png +0 -0
- package/android/src/main/res/drawable/navi_lane_shape_bg_center.xml +5 -0
- package/android/src/main/res/drawable/navi_lane_shape_bg_left.xml +8 -0
- package/android/src/main/res/drawable/navi_lane_shape_bg_over.xml +6 -0
- package/android/src/main/res/drawable/navi_lane_shape_bg_right.xml +8 -0
- package/android/src/main/res/drawable-nodpi/nav_tracker_car.png +0 -0
- package/build/ExpoGaodeMapNaviView.d.ts +16 -0
- package/build/ExpoGaodeMapNaviView.d.ts.map +1 -1
- package/build/ExpoGaodeMapNaviView.js +74 -1
- package/build/ExpoGaodeMapNaviView.js.map +1 -1
- package/build/index.d.ts +56 -8
- package/build/index.d.ts.map +1 -1
- package/build/index.js +452 -10
- package/build/index.js.map +1 -1
- package/build/map/ExpoGaodeMapModule.d.ts +15 -13
- package/build/map/ExpoGaodeMapModule.d.ts.map +1 -1
- package/build/map/ExpoGaodeMapModule.js +31 -39
- package/build/map/ExpoGaodeMapModule.js.map +1 -1
- package/build/map/ExpoGaodeMapOfflineModule.d.ts +5 -0
- package/build/map/ExpoGaodeMapOfflineModule.d.ts.map +1 -1
- package/build/map/ExpoGaodeMapOfflineModule.js.map +1 -1
- package/build/map/ExpoGaodeMapView.d.ts +3 -4
- package/build/map/ExpoGaodeMapView.d.ts.map +1 -1
- package/build/map/ExpoGaodeMapView.js +28 -25
- package/build/map/ExpoGaodeMapView.js.map +1 -1
- package/build/map/components/overlays/Circle.d.ts.map +1 -1
- package/build/map/components/overlays/Circle.js +1 -30
- package/build/map/components/overlays/Circle.js.map +1 -1
- package/build/map/components/overlays/Cluster.d.ts.map +1 -1
- package/build/map/components/overlays/Cluster.js +1 -42
- package/build/map/components/overlays/Cluster.js.map +1 -1
- package/build/map/components/overlays/HeatMap.d.ts.map +1 -1
- package/build/map/components/overlays/HeatMap.js +21 -21
- package/build/map/components/overlays/HeatMap.js.map +1 -1
- package/build/map/components/overlays/Marker.d.ts.map +1 -1
- package/build/map/components/overlays/Marker.js +76 -80
- package/build/map/components/overlays/Marker.js.map +1 -1
- package/build/map/components/overlays/Polygon.d.ts.map +1 -1
- package/build/map/components/overlays/Polygon.js +1 -25
- package/build/map/components/overlays/Polygon.js.map +1 -1
- package/build/map/components/overlays/Polyline.d.ts.map +1 -1
- package/build/map/components/overlays/Polyline.js +1 -31
- package/build/map/components/overlays/Polyline.js.map +1 -1
- package/build/map/index.d.ts +9 -2
- package/build/map/index.d.ts.map +1 -1
- package/build/map/index.js +9 -2
- package/build/map/index.js.map +1 -1
- package/build/map/search/ExpoGaodeMapSearch.types.d.ts +340 -0
- package/build/map/search/ExpoGaodeMapSearch.types.d.ts.map +1 -0
- package/build/map/search/ExpoGaodeMapSearch.types.js +19 -0
- package/build/map/search/ExpoGaodeMapSearch.types.js.map +1 -0
- package/build/map/search/ExpoGaodeMapSearchModule.d.ts +74 -0
- package/build/map/search/ExpoGaodeMapSearchModule.d.ts.map +1 -0
- package/build/map/search/ExpoGaodeMapSearchModule.js +47 -0
- package/build/map/search/ExpoGaodeMapSearchModule.js.map +1 -0
- package/build/map/search/index.d.ts +156 -0
- package/build/map/search/index.d.ts.map +1 -0
- package/build/map/search/index.js +171 -0
- package/build/map/search/index.js.map +1 -0
- package/build/map/types/index.d.ts +2 -2
- package/build/map/types/index.d.ts.map +1 -1
- package/build/map/types/index.js.map +1 -1
- package/build/map/types/map-view.types.d.ts +4 -2
- package/build/map/types/map-view.types.d.ts.map +1 -1
- package/build/map/types/map-view.types.js.map +1 -1
- package/build/map/types/native-module.types.d.ts +11 -12
- package/build/map/types/native-module.types.d.ts.map +1 -1
- package/build/map/types/native-module.types.js.map +1 -1
- package/build/map/types/overlays.types.d.ts +9 -14
- package/build/map/types/overlays.types.d.ts.map +1 -1
- package/build/map/types/overlays.types.js.map +1 -1
- package/build/map/types/route-playback.types.d.ts +16 -0
- package/build/map/types/route-playback.types.d.ts.map +1 -1
- package/build/map/types/route-playback.types.js.map +1 -1
- package/build/map/utils/ErrorHandler.js +11 -11
- package/build/map/utils/ErrorHandler.js.map +1 -1
- package/build/map/utils/OfflineMapManager.d.ts +4 -0
- package/build/map/utils/OfflineMapManager.d.ts.map +1 -1
- package/build/map/utils/OfflineMapManager.js +6 -0
- package/build/map/utils/OfflineMapManager.js.map +1 -1
- package/build/types/coordinates.types.d.ts +3 -0
- package/build/types/coordinates.types.d.ts.map +1 -1
- package/build/types/coordinates.types.js.map +1 -1
- package/build/types/independent.types.d.ts +111 -12
- package/build/types/independent.types.d.ts.map +1 -1
- package/build/types/independent.types.js.map +1 -1
- package/build/types/native-module.types.d.ts +1 -1
- package/build/types/native-module.types.js.map +1 -1
- package/build/types/naviview.types.d.ts +304 -14
- package/build/types/naviview.types.d.ts.map +1 -1
- package/build/types/naviview.types.js.map +1 -1
- package/build/types/route.types.d.ts +12 -2
- package/build/types/route.types.d.ts.map +1 -1
- package/build/types/route.types.js.map +1 -1
- package/expo-module.config.json +4 -2
- package/ios/ExpoGaodeMapNaviView.swift +2331 -201
- package/ios/ExpoGaodeMapNaviViewModule.swift +109 -1
- package/ios/ExpoGaodeMapNavigation.podspec +2 -1
- package/ios/ExpoGaodeMapNavigationModule.swift +7 -5
- package/ios/managers/IndependentRouteManager.swift +90 -26
- package/ios/map/ExpoGaodeMapModule.swift +72 -21
- package/ios/map/ExpoGaodeMapOfflineModule.swift +61 -0
- package/ios/map/ExpoGaodeMapSearchModule.swift +773 -0
- package/ios/map/ExpoGaodeMapView.swift +23 -5
- package/ios/map/GaodeMapPrivacyManager.swift +26 -18
- package/ios/map/cpp/GeometryEngine.cpp +112 -0
- package/ios/map/cpp/GeometryEngine.hpp +21 -0
- package/ios/map/modules/LocationManager.swift +37 -5
- package/ios/map/overlays/MarkerView.swift +11 -11
- package/ios/map/overlays/MarkerViewModule.swift +4 -4
- package/ios/map/overlays/PolylineView.swift +6 -12
- package/ios/map/utils/ClusterNative.h +8 -0
- package/ios/map/utils/ClusterNative.mm +27 -0
- package/ios/map/utils/PermissionManager.swift +115 -6
- package/ios/routes/drive/DriveTruckRouteCalculator.swift +165 -77
- package/ios/routes/walkride/WalkRideRouteCalculator.swift +127 -1
- package/ios/services/IndependentRouteService.swift +198 -39
- package/ios/services/NavigationLiveActivityAttributes.swift +48 -0
- package/ios/services/NavigationLiveActivityManager.swift +359 -0
- package/package.json +22 -7
- package/plugin/build/withGaodeMap.d.ts +8 -0
- package/plugin/build/withGaodeMap.js +60 -4
- package/scripts/check-expo-modules.js +68 -0
- package/shared/cpp/GeometryEngine.cpp +112 -0
- package/shared/cpp/GeometryEngine.hpp +21 -0
- package/widget-template/README.md +46 -0
- package/widget-template/ios/NavigationLiveActivityWidget.swift +367 -0
- package/android/src/main/java/expo/modules/gaodemap/navigation/managers/RouteCalculator.kt +0 -173
|
@@ -8,8 +8,292 @@
|
|
|
8
8
|
import Foundation
|
|
9
9
|
import ExpoModulesCore
|
|
10
10
|
import AMapNaviKit
|
|
11
|
+
import AVFAudio
|
|
12
|
+
import CoreLocation
|
|
13
|
+
|
|
14
|
+
final class ExpoGaodeMapCustomDriveView: AMapNaviDriveView {
|
|
15
|
+
var suppressLaneInfoUI: Bool = false
|
|
16
|
+
var suppressTopInfoUI: Bool = false {
|
|
17
|
+
didSet {
|
|
18
|
+
scheduleTopInfoSuppressionPasses()
|
|
19
|
+
setNeedsLayout()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
var suppressBottomRightUI: Bool = false {
|
|
23
|
+
didSet {
|
|
24
|
+
scheduleTopInfoSuppressionPasses()
|
|
25
|
+
setNeedsLayout()
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
private let topInfoCoverView: UIView = {
|
|
29
|
+
let view = UIView()
|
|
30
|
+
view.isHidden = true
|
|
31
|
+
view.isUserInteractionEnabled = false
|
|
32
|
+
view.backgroundColor = UIColor(red: 14.0 / 255.0, green: 18.0 / 255.0, blue: 26.0 / 255.0, alpha: 0.96)
|
|
33
|
+
return view
|
|
34
|
+
}()
|
|
35
|
+
private var scheduledSuppressionPasses = 0
|
|
36
|
+
|
|
37
|
+
override init(frame: CGRect) {
|
|
38
|
+
super.init(frame: frame)
|
|
39
|
+
installTopInfoCoverViewIfNeeded()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
required init?(coder: NSCoder) {
|
|
43
|
+
super.init(coder: coder)
|
|
44
|
+
installTopInfoCoverViewIfNeeded()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
override func layoutSubviews() {
|
|
48
|
+
super.layoutSubviews()
|
|
49
|
+
applySuppressedChromeVisibility()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
override func didMoveToWindow() {
|
|
53
|
+
super.didMoveToWindow()
|
|
54
|
+
installTopInfoCoverViewIfNeeded()
|
|
55
|
+
if suppressTopInfoUI {
|
|
56
|
+
scheduleTopInfoSuppressionPasses()
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
override func driveManager(_ driveManager: AMapNaviDriveManager, showLaneBackInfo laneBackInfo: String, laneSelectInfo: String) {
|
|
61
|
+
guard !suppressLaneInfoUI else {
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
super.driveManager(driveManager, showLaneBackInfo: laneBackInfo, laneSelectInfo: laneSelectInfo)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
override func driveManagerHideLaneInfo(_ driveManager: AMapNaviDriveManager) {
|
|
68
|
+
guard !suppressLaneInfoUI else {
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
super.driveManagerHideLaneInfo(driveManager)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private func applySuppressedChromeVisibility() {
|
|
75
|
+
installTopInfoCoverViewIfNeeded()
|
|
76
|
+
let topCandidates = collectTopInfoCandidates()
|
|
77
|
+
for candidate in topCandidates {
|
|
78
|
+
candidate.isHidden = suppressTopInfoUI
|
|
79
|
+
candidate.alpha = suppressTopInfoUI ? 0.0 : 1.0
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let bottomRightCandidates = collectBottomRightCandidates()
|
|
83
|
+
for candidate in bottomRightCandidates {
|
|
84
|
+
candidate.isHidden = suppressBottomRightUI
|
|
85
|
+
candidate.alpha = suppressBottomRightUI ? 0.0 : 1.0
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
guard suppressTopInfoUI, !topCandidates.isEmpty else {
|
|
89
|
+
topInfoCoverView.isHidden = true
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let unionFrame = topCandidates.reduce(CGRect.null) { partial, view in
|
|
94
|
+
let frame = view.convert(view.bounds, to: self)
|
|
95
|
+
return partial.union(frame)
|
|
96
|
+
}
|
|
97
|
+
let fallbackFrame = CGRect(x: 0, y: 0, width: bounds.width, height: min(max(bounds.height * 0.17, 96), 150))
|
|
98
|
+
let targetFrame = (unionFrame.isNull ? fallbackFrame : unionFrame.insetBy(dx: -8, dy: -6)).intersection(bounds)
|
|
99
|
+
topInfoCoverView.frame = targetFrame
|
|
100
|
+
topInfoCoverView.isHidden = targetFrame.isEmpty
|
|
101
|
+
if !topInfoCoverView.isHidden {
|
|
102
|
+
bringSubviewToFront(topInfoCoverView)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private func collectTopInfoCandidates() -> [UIView] {
|
|
107
|
+
guard suppressTopInfoUI || !subviews.isEmpty else {
|
|
108
|
+
return []
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let protectedClassNameFragments = [
|
|
112
|
+
"MAMap",
|
|
113
|
+
"Lane",
|
|
114
|
+
"Cross",
|
|
115
|
+
"Eagle",
|
|
116
|
+
"TrafficBar",
|
|
117
|
+
"Compass",
|
|
118
|
+
"Zoom",
|
|
119
|
+
"Scale",
|
|
120
|
+
"Logo",
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
return allDescendantSubviews(of: self).filter { view in
|
|
124
|
+
guard view !== self, view !== topInfoCoverView else {
|
|
125
|
+
return false
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let frame = view.convert(view.bounds, to: self)
|
|
129
|
+
guard !frame.isEmpty else {
|
|
130
|
+
return false
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let className = NSStringFromClass(type(of: view))
|
|
134
|
+
if protectedClassNameFragments.contains(where: { className.localizedCaseInsensitiveContains($0) }) {
|
|
135
|
+
return false
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let topBandMaxY = min(max(bounds.height * 0.28, 150), 220)
|
|
139
|
+
guard frame.minY <= topBandMaxY && frame.maxY <= topBandMaxY + 70 else {
|
|
140
|
+
return false
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
guard frame.height >= 12 && frame.height <= 140 && frame.width >= 20 else {
|
|
144
|
+
return false
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Keep small corner controls (for example compass / map tool buttons) out of the suppression set.
|
|
148
|
+
let isCornerControl =
|
|
149
|
+
frame.width <= 60 &&
|
|
150
|
+
frame.height <= 60 &&
|
|
151
|
+
(frame.minX <= 24 || frame.maxX >= bounds.width - 24)
|
|
152
|
+
if isCornerControl {
|
|
153
|
+
return false
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return true
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private func collectBottomRightCandidates() -> [UIView] {
|
|
161
|
+
guard suppressBottomRightUI || !subviews.isEmpty else {
|
|
162
|
+
return []
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let protectedClassNameFragments = [
|
|
166
|
+
"MAMap",
|
|
167
|
+
"Lane",
|
|
168
|
+
"Cross",
|
|
169
|
+
"Eagle",
|
|
170
|
+
"TrafficBar",
|
|
171
|
+
"Compass",
|
|
172
|
+
"Zoom",
|
|
173
|
+
"Scale",
|
|
174
|
+
"Logo",
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
return allDescendantSubviews(of: self).filter { view in
|
|
178
|
+
guard view !== self, view !== topInfoCoverView else {
|
|
179
|
+
return false
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
let frame = view.convert(view.bounds, to: self)
|
|
183
|
+
guard !frame.isEmpty else {
|
|
184
|
+
return false
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let className = NSStringFromClass(type(of: view))
|
|
188
|
+
if protectedClassNameFragments.contains(where: { className.localizedCaseInsensitiveContains($0) }) {
|
|
189
|
+
return false
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
guard frame.minX >= bounds.width * 0.76 || frame.maxX >= bounds.width - 10 else {
|
|
193
|
+
return false
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
guard frame.minY >= bounds.height * 0.46 else {
|
|
197
|
+
return false
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
guard frame.width >= 18 && frame.width <= 110 else {
|
|
201
|
+
return false
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
guard frame.height >= 18 && frame.height <= 240 else {
|
|
205
|
+
return false
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return true
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private func allDescendantSubviews(of root: UIView) -> [UIView] {
|
|
213
|
+
root.subviews.flatMap { subview in
|
|
214
|
+
[subview] + allDescendantSubviews(of: subview)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private func installTopInfoCoverViewIfNeeded() {
|
|
219
|
+
guard topInfoCoverView.superview !== self else {
|
|
220
|
+
return
|
|
221
|
+
}
|
|
222
|
+
addSubview(topInfoCoverView)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private func scheduleTopInfoSuppressionPasses() {
|
|
226
|
+
scheduledSuppressionPasses = (suppressTopInfoUI || suppressBottomRightUI) ? 18 : 0
|
|
227
|
+
guard suppressTopInfoUI || suppressBottomRightUI else {
|
|
228
|
+
topInfoCoverView.isHidden = true
|
|
229
|
+
return
|
|
230
|
+
}
|
|
231
|
+
runScheduledTopInfoSuppressionPass()
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private func runScheduledTopInfoSuppressionPass() {
|
|
235
|
+
applySuppressedChromeVisibility()
|
|
236
|
+
guard scheduledSuppressionPasses > 0 else {
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
scheduledSuppressionPasses -= 1
|
|
240
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.12) { [weak self] in
|
|
241
|
+
self?.runScheduledTopInfoSuppressionPass()
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
func refreshSuppressedTopInfoUIIfNeeded() {
|
|
246
|
+
guard suppressTopInfoUI || suppressBottomRightUI else {
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
applySuppressedChromeVisibility()
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private struct NaviCustomWaypointMarkerModel {
|
|
254
|
+
let latitude: Double
|
|
255
|
+
let longitude: Double
|
|
256
|
+
let title: String
|
|
257
|
+
var arrived: Bool = false
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private final class NaviCustomWaypointBubbleView: UIView {
|
|
261
|
+
init(title: String) {
|
|
262
|
+
let font = UIFont.systemFont(ofSize: 17, weight: .semibold)
|
|
263
|
+
let textWidth = ceil((title as NSString).size(withAttributes: [.font: font]).width)
|
|
264
|
+
let bodyWidth = min(max(textWidth + 28, 62), 110)
|
|
265
|
+
let size = CGSize(width: bodyWidth, height: 34)
|
|
266
|
+
super.init(frame: CGRect(origin: .zero, size: size))
|
|
267
|
+
|
|
268
|
+
backgroundColor = .clear
|
|
269
|
+
isOpaque = false
|
|
270
|
+
|
|
271
|
+
let bodyView = UIView(frame: bounds)
|
|
272
|
+
bodyView.backgroundColor = UIColor(red: 47.0 / 255.0, green: 103.0 / 255.0, blue: 255.0 / 255.0, alpha: 1)
|
|
273
|
+
bodyView.layer.cornerRadius = 17
|
|
274
|
+
bodyView.layer.borderWidth = 2
|
|
275
|
+
bodyView.layer.borderColor = UIColor.white.cgColor
|
|
276
|
+
bodyView.layer.shadowColor = UIColor(red: 21.0 / 255.0, green: 53.0 / 255.0, blue: 127.0 / 255.0, alpha: 1).cgColor
|
|
277
|
+
bodyView.layer.shadowOpacity = 0.18
|
|
278
|
+
bodyView.layer.shadowRadius = 6
|
|
279
|
+
bodyView.layer.shadowOffset = CGSize(width: 0, height: 3)
|
|
280
|
+
addSubview(bodyView)
|
|
281
|
+
|
|
282
|
+
let label = UILabel(frame: bodyView.bounds.insetBy(dx: 10, dy: 4))
|
|
283
|
+
label.text = title
|
|
284
|
+
label.textAlignment = .center
|
|
285
|
+
label.textColor = .white
|
|
286
|
+
label.font = font
|
|
287
|
+
bodyView.addSubview(label)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
required init?(coder: NSCoder) {
|
|
291
|
+
fatalError("init(coder:) has not been implemented")
|
|
292
|
+
}
|
|
293
|
+
}
|
|
11
294
|
|
|
12
295
|
public class ExpoGaodeMapNaviView: ExpoView {
|
|
296
|
+
private let independentRouteManager = IndependentRouteManager.shared
|
|
13
297
|
|
|
14
298
|
// MARK: - 高德 SDK 初始化检查
|
|
15
299
|
|
|
@@ -22,7 +306,7 @@ public class ExpoGaodeMapNaviView: ExpoView {
|
|
|
22
306
|
code: -1002,
|
|
23
307
|
userInfo: [
|
|
24
308
|
NSLocalizedDescriptionKey: "隐私协议未完成确认",
|
|
25
|
-
NSLocalizedFailureReasonErrorKey: "请先调用 setPrivacyConfig
|
|
309
|
+
NSLocalizedFailureReasonErrorKey: "请先调用 setPrivacyConfig 并确保参数为 true",
|
|
26
310
|
NSLocalizedRecoverySuggestionErrorKey: "建议在首次启动弹窗同意后再进入导航页面。"
|
|
27
311
|
]
|
|
28
312
|
)
|
|
@@ -121,15 +405,49 @@ public class ExpoGaodeMapNaviView: ExpoView {
|
|
|
121
405
|
let onGpsStatusChanged = EventDispatcher()
|
|
122
406
|
let onNavigationInfoUpdate = EventDispatcher()
|
|
123
407
|
let onGpsSignalWeak = EventDispatcher()
|
|
408
|
+
let onNavigationVisualStateUpdate = EventDispatcher()
|
|
409
|
+
let onLaneInfoUpdate = EventDispatcher()
|
|
410
|
+
let onTrafficStatusesUpdate = EventDispatcher()
|
|
124
411
|
|
|
125
412
|
// MARK: - Properties
|
|
413
|
+
private enum NaviScene: String {
|
|
414
|
+
case drive
|
|
415
|
+
case walk
|
|
416
|
+
case ride
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
private var activeScene: NaviScene = .drive
|
|
126
420
|
private var driveView: AMapNaviDriveView?
|
|
127
|
-
private var
|
|
421
|
+
private var walkView: AMapNaviWalkView?
|
|
422
|
+
private var rideView: AMapNaviRideView?
|
|
128
423
|
private var pendingShowUIElements: Bool?
|
|
129
|
-
private var pendingShowUIElementsWorkItem: DispatchWorkItem?
|
|
130
424
|
private var hasStartedNavi: Bool = false
|
|
131
425
|
private var hasReceivedFirstNaviData: Bool = false
|
|
132
426
|
private var driveManager: AMapNaviDriveManager?
|
|
427
|
+
private var walkManager: AMapNaviWalkManager?
|
|
428
|
+
private var rideManager: AMapNaviRideManager?
|
|
429
|
+
private var lastKnownSpeed: Int = 0
|
|
430
|
+
private var currentNaviRoute: AMapNaviRoute?
|
|
431
|
+
private var currentRouteTotalLength: Int?
|
|
432
|
+
private var lastNavigationInfoPayload: [String: Any]?
|
|
433
|
+
private var lastTurnIconType: Int?
|
|
434
|
+
private var lastNextTurnIconType: Int?
|
|
435
|
+
private var lastTurnIconImageUri: String?
|
|
436
|
+
private var lastNextTurnIconImageUri: String?
|
|
437
|
+
private var lastTurnIconBase64: String?
|
|
438
|
+
private var trafficBarTotalLength: Int?
|
|
439
|
+
private var isCrossVisible: Bool = false
|
|
440
|
+
private var isLaneInfoVisible: Bool = false
|
|
441
|
+
private var isNavigationAudioSessionActive: Bool = false
|
|
442
|
+
private var hasLoggedMissingBackgroundAudioMode: Bool = false
|
|
443
|
+
private var renderedCustomWaypointAnnotations: [AMapNaviCompositeCustomAnnotation] = []
|
|
444
|
+
private var customWaypointMarkers: [NaviCustomWaypointMarkerModel] = []
|
|
445
|
+
private let customSpeechSynthesizer = AVSpeechSynthesizer()
|
|
446
|
+
|
|
447
|
+
private enum LaneStringKind {
|
|
448
|
+
case background
|
|
449
|
+
case selected
|
|
450
|
+
}
|
|
133
451
|
|
|
134
452
|
// Props - 通用属性
|
|
135
453
|
var naviType: Int = 0 // 0: GPS, 1: Emulator
|
|
@@ -139,6 +457,30 @@ public class ExpoGaodeMapNaviView: ExpoView {
|
|
|
139
457
|
var showCamera: Bool = true {
|
|
140
458
|
didSet { applyShowCamera(showCamera) }
|
|
141
459
|
}
|
|
460
|
+
var carImageSource: String? {
|
|
461
|
+
didSet { applyCarImageSource() }
|
|
462
|
+
}
|
|
463
|
+
var carImageSize: CGSize? {
|
|
464
|
+
didSet { applyCarImageSource() }
|
|
465
|
+
}
|
|
466
|
+
var carCompassImageSource: String? {
|
|
467
|
+
didSet { applyCarCompassImageSource() }
|
|
468
|
+
}
|
|
469
|
+
var startPointImageSource: String? {
|
|
470
|
+
didSet { applyStartPointImageSource() }
|
|
471
|
+
}
|
|
472
|
+
var wayPointImageSource: String? {
|
|
473
|
+
didSet { applyWayPointImageSource() }
|
|
474
|
+
}
|
|
475
|
+
var customWaypointMarkerPayloads: [[String: Any]]? {
|
|
476
|
+
didSet { applyCustomWaypointMarkerPayloads(customWaypointMarkerPayloads) }
|
|
477
|
+
}
|
|
478
|
+
var endPointImageSource: String? {
|
|
479
|
+
didSet { applyEndPointImageSource() }
|
|
480
|
+
}
|
|
481
|
+
var cameraImageSource: String? {
|
|
482
|
+
didSet { applyCameraImageSource() }
|
|
483
|
+
}
|
|
142
484
|
var autoLockCar: Bool = true {
|
|
143
485
|
didSet { applyAutoLockCar(autoLockCar) }
|
|
144
486
|
}
|
|
@@ -166,25 +508,37 @@ public class ExpoGaodeMapNaviView: ExpoView {
|
|
|
166
508
|
didSet { driveView?.showRoute = showRoute }
|
|
167
509
|
}
|
|
168
510
|
var showTurnArrow: Bool = true {
|
|
169
|
-
didSet {
|
|
511
|
+
didSet { applyShowTurnArrow(showTurnArrow) }
|
|
170
512
|
}
|
|
171
513
|
var showTrafficBar: Bool = true {
|
|
172
514
|
didSet { driveView?.showTrafficBar = showTrafficBar }
|
|
173
515
|
}
|
|
516
|
+
var trafficBarFrame: CGRect = .zero {
|
|
517
|
+
didSet { applyTrafficBarFrame(trafficBarFrame) }
|
|
518
|
+
}
|
|
519
|
+
var trafficBarColors: [String: Any]? {
|
|
520
|
+
didSet { applyTrafficBarColors(trafficBarColors) }
|
|
521
|
+
}
|
|
174
522
|
var showBrowseRouteButton: Bool = true {
|
|
175
|
-
didSet {
|
|
523
|
+
didSet { applyShowBrowseRouteButton(showBrowseRouteButton) }
|
|
176
524
|
}
|
|
177
525
|
var showMoreButton: Bool = true {
|
|
178
|
-
didSet {
|
|
526
|
+
didSet { applyShowMoreButton(showMoreButton) }
|
|
179
527
|
}
|
|
180
528
|
var showTrafficButton: Bool = true {
|
|
181
529
|
didSet { driveView?.showTrafficButton = showTrafficButton }
|
|
182
530
|
}
|
|
531
|
+
var showBackupRoute: Bool = true {
|
|
532
|
+
didSet { driveView?.showBackupRoute = showBackupRoute }
|
|
533
|
+
}
|
|
534
|
+
var showEagleMap: Bool = false {
|
|
535
|
+
didSet { driveView?.showEagleMap = showEagleMap }
|
|
536
|
+
}
|
|
183
537
|
var showUIElements: Bool = true {
|
|
184
|
-
didSet {
|
|
538
|
+
didSet { applyShowUIElementsToActiveViewIfReady() }
|
|
185
539
|
}
|
|
186
|
-
var showGreyAfterPass: Bool =
|
|
187
|
-
didSet {
|
|
540
|
+
var showGreyAfterPass: Bool = true {
|
|
541
|
+
didSet { applyShowGreyAfterPass(showGreyAfterPass) }
|
|
188
542
|
}
|
|
189
543
|
var showVectorline: Bool = true {
|
|
190
544
|
didSet { driveView?.showVectorline = showVectorline }
|
|
@@ -192,11 +546,51 @@ public class ExpoGaodeMapNaviView: ExpoView {
|
|
|
192
546
|
var showTrafficLights: Bool = true {
|
|
193
547
|
didSet { driveView?.showTrafficLights = showTrafficLights }
|
|
194
548
|
}
|
|
549
|
+
var showCompassEnabled: Bool? {
|
|
550
|
+
didSet {
|
|
551
|
+
guard let showCompassEnabled else { return }
|
|
552
|
+
applyShowCompassEnabled(showCompassEnabled)
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
var showDriveCongestion: Bool = true {
|
|
556
|
+
didSet { driveView?.showDriveCongestion = showDriveCongestion }
|
|
557
|
+
}
|
|
558
|
+
var showTrafficLightView: Bool = true {
|
|
559
|
+
didSet { driveView?.showTrafficLightView = showTrafficLightView }
|
|
560
|
+
}
|
|
195
561
|
var mapViewModeType: Int = 0 {
|
|
196
562
|
didSet { applyMapViewModeType(mapViewModeType) }
|
|
197
563
|
}
|
|
198
564
|
var lineWidth: CGFloat = 0 {
|
|
199
|
-
didSet {
|
|
565
|
+
didSet { applyLineWidth(lineWidth) }
|
|
566
|
+
}
|
|
567
|
+
var driveViewEdgePadding: UIEdgeInsets = .zero {
|
|
568
|
+
didSet {
|
|
569
|
+
currentNaviView()?.setNeedsLayout()
|
|
570
|
+
currentNaviView()?.layoutIfNeeded()
|
|
571
|
+
scheduleOverviewRouteVisibleRegionRefresh()
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
var screenAnchor: CGPoint = .zero {
|
|
575
|
+
didSet {
|
|
576
|
+
applyScreenAnchor(screenAnchor)
|
|
577
|
+
scheduleOverviewRouteVisibleRegionRefresh()
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
var hideNativeTopInfoLayout: Bool = false {
|
|
581
|
+
didSet { applyHideNativeTopInfoLayout(hideNativeTopInfoLayout) }
|
|
582
|
+
}
|
|
583
|
+
var hideNativeLaneInfoLayout: Bool = false {
|
|
584
|
+
didSet { applyHideNativeLaneInfoLayout(hideNativeLaneInfoLayout) }
|
|
585
|
+
}
|
|
586
|
+
var iosLiveActivityEnabled: Bool = false {
|
|
587
|
+
didSet {
|
|
588
|
+
if iosLiveActivityEnabled {
|
|
589
|
+
syncNavigationLiveActivityWithLastPayload()
|
|
590
|
+
} else {
|
|
591
|
+
NavigationLiveActivityManager.shared.stop()
|
|
592
|
+
}
|
|
593
|
+
}
|
|
200
594
|
}
|
|
201
595
|
|
|
202
596
|
func applyShowUIElements(_ visible: Bool) {
|
|
@@ -235,172 +629,1575 @@ public class ExpoGaodeMapNaviView: ExpoView {
|
|
|
235
629
|
}
|
|
236
630
|
return
|
|
237
631
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
driveManager = AMapNaviDriveManager.sharedInstance()
|
|
241
|
-
driveManager?.delegate = self
|
|
242
|
-
|
|
243
|
-
// 使用内置语音
|
|
244
|
-
driveManager?.isUseInternalTTS = true
|
|
245
|
-
|
|
246
|
-
// 初始化导航视图
|
|
247
|
-
driveView = AMapNaviDriveView(frame: bounds)
|
|
248
|
-
driveView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
249
|
-
driveView?.delegate = self
|
|
250
|
-
|
|
251
|
-
if let view = driveView {
|
|
252
|
-
addSubview(view)
|
|
253
|
-
driveManager?.addDataRepresentative(view)
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// 应用初始配置
|
|
257
|
-
applyViewOptions()
|
|
632
|
+
|
|
633
|
+
switchToScene(.drive)
|
|
258
634
|
}
|
|
259
|
-
|
|
260
|
-
private func
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
// iOS 特有属性
|
|
270
|
-
driveView?.showRoute = showRoute
|
|
271
|
-
driveView?.showTurnArrow = showTurnArrow
|
|
272
|
-
driveView?.showTrafficBar = showTrafficBar
|
|
273
|
-
driveView?.showBrowseRouteButton = showBrowseRouteButton
|
|
274
|
-
driveView?.showMoreButton = showMoreButton
|
|
275
|
-
driveView?.showTrafficButton = showTrafficButton
|
|
276
|
-
driveView?.showGreyAfterPass = showGreyAfterPass
|
|
277
|
-
driveView?.showVectorline = showVectorline
|
|
278
|
-
driveView?.showTrafficLights = showTrafficLights
|
|
279
|
-
if lineWidth > 0 {
|
|
280
|
-
driveView?.lineWidth = lineWidth
|
|
635
|
+
|
|
636
|
+
private func currentNaviView() -> UIView? {
|
|
637
|
+
switch activeScene {
|
|
638
|
+
case .drive:
|
|
639
|
+
return driveView
|
|
640
|
+
case .walk:
|
|
641
|
+
return walkView
|
|
642
|
+
case .ride:
|
|
643
|
+
return rideView
|
|
281
644
|
}
|
|
645
|
+
}
|
|
282
646
|
|
|
283
|
-
|
|
647
|
+
private func currentNaviManager() -> AMapNaviBaseManager? {
|
|
648
|
+
switch activeScene {
|
|
649
|
+
case .drive:
|
|
650
|
+
return driveManager
|
|
651
|
+
case .walk:
|
|
652
|
+
return walkManager
|
|
653
|
+
case .ride:
|
|
654
|
+
return rideManager
|
|
655
|
+
}
|
|
284
656
|
}
|
|
285
657
|
|
|
286
|
-
private func
|
|
287
|
-
guard driveView
|
|
288
|
-
pendingShowUIElements = showUIElements
|
|
658
|
+
private func setupDriveViewIfNeeded() {
|
|
659
|
+
guard driveView == nil else {
|
|
289
660
|
return
|
|
290
661
|
}
|
|
662
|
+
let customDriveView = ExpoGaodeMapCustomDriveView(frame: bounds)
|
|
663
|
+
customDriveView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
664
|
+
customDriveView.delegate = self
|
|
665
|
+
customDriveView.suppressTopInfoUI = hideNativeTopInfoLayout
|
|
666
|
+
customDriveView.suppressLaneInfoUI = hideNativeLaneInfoLayout
|
|
667
|
+
driveView = customDriveView
|
|
668
|
+
}
|
|
291
669
|
|
|
292
|
-
|
|
293
|
-
|
|
670
|
+
private func setupWalkViewIfNeeded() {
|
|
671
|
+
guard walkView == nil else {
|
|
294
672
|
return
|
|
295
673
|
}
|
|
674
|
+
let view = AMapNaviWalkView(frame: bounds)
|
|
675
|
+
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
676
|
+
view.delegate = self
|
|
677
|
+
walkView = view
|
|
678
|
+
}
|
|
296
679
|
|
|
297
|
-
|
|
298
|
-
|
|
680
|
+
private func setupRideViewIfNeeded() {
|
|
681
|
+
guard rideView == nil else {
|
|
682
|
+
return
|
|
683
|
+
}
|
|
684
|
+
let view = AMapNaviRideView(frame: bounds)
|
|
685
|
+
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
686
|
+
view.delegate = self
|
|
687
|
+
rideView = view
|
|
688
|
+
}
|
|
299
689
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
self.applyDriveViewShowUIElements(true, remainingAttempts: 20)
|
|
690
|
+
private func attachActiveView(_ targetView: UIView?) {
|
|
691
|
+
let knownViews = [driveView, walkView, rideView].compactMap { $0 }
|
|
692
|
+
for view in knownViews where view !== targetView {
|
|
693
|
+
if view.superview === self {
|
|
694
|
+
view.removeFromSuperview()
|
|
306
695
|
}
|
|
307
|
-
|
|
308
|
-
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
guard let targetView else {
|
|
309
699
|
return
|
|
310
700
|
}
|
|
701
|
+
targetView.frame = bounds
|
|
702
|
+
if targetView.superview !== self {
|
|
703
|
+
addSubview(targetView)
|
|
704
|
+
}
|
|
705
|
+
sendSubviewToBack(targetView)
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
private func switchToScene(_ scene: NaviScene) {
|
|
709
|
+
teardownManagers(except: scene)
|
|
710
|
+
activeScene = scene
|
|
311
711
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
712
|
+
switch scene {
|
|
713
|
+
case .drive:
|
|
714
|
+
setupDriveViewIfNeeded()
|
|
715
|
+
rebindDriveManagerToView()
|
|
716
|
+
attachActiveView(driveView)
|
|
717
|
+
case .walk:
|
|
718
|
+
setupWalkViewIfNeeded()
|
|
719
|
+
rebindWalkManagerToView()
|
|
720
|
+
attachActiveView(walkView)
|
|
721
|
+
case .ride:
|
|
722
|
+
setupRideViewIfNeeded()
|
|
723
|
+
rebindRideManagerToView()
|
|
724
|
+
attachActiveView(rideView)
|
|
316
725
|
}
|
|
317
|
-
pendingShowUIElementsWorkItem = workItem
|
|
318
726
|
|
|
319
|
-
|
|
727
|
+
applyViewOptions()
|
|
728
|
+
applyShowUIElementsToActiveViewIfReady()
|
|
729
|
+
DispatchQueue.main.async { [weak self] in
|
|
730
|
+
self?.onNavigationReady([:])
|
|
731
|
+
}
|
|
320
732
|
}
|
|
321
733
|
|
|
322
|
-
private func
|
|
323
|
-
|
|
734
|
+
private func teardownManagers(except scene: NaviScene) {
|
|
735
|
+
if scene != .drive {
|
|
736
|
+
destroyDriveManagerIfNeeded()
|
|
737
|
+
}
|
|
738
|
+
if scene != .walk {
|
|
739
|
+
destroyWalkManagerIfNeeded()
|
|
740
|
+
}
|
|
741
|
+
if scene != .ride {
|
|
742
|
+
destroyRideManagerIfNeeded()
|
|
743
|
+
}
|
|
744
|
+
}
|
|
324
745
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
return
|
|
328
|
-
}
|
|
329
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self] in
|
|
330
|
-
self?.applyDriveViewShowUIElements(value, remainingAttempts: remainingAttempts - 1)
|
|
331
|
-
}
|
|
746
|
+
private func destroyDriveManagerIfNeeded() {
|
|
747
|
+
guard let driveManager else {
|
|
332
748
|
return
|
|
333
749
|
}
|
|
334
|
-
|
|
335
|
-
|
|
750
|
+
if let view = driveView {
|
|
751
|
+
driveManager.removeDataRepresentative(view)
|
|
752
|
+
}
|
|
753
|
+
driveManager.removeDataRepresentative(self)
|
|
754
|
+
driveManager.delegate = nil
|
|
755
|
+
self.driveManager = nil
|
|
756
|
+
_ = AMapNaviDriveManager.destroyInstance()
|
|
336
757
|
}
|
|
337
758
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
759
|
+
private func destroyWalkManagerIfNeeded() {
|
|
760
|
+
guard let walkManager else {
|
|
761
|
+
return
|
|
762
|
+
}
|
|
763
|
+
if let view = walkView {
|
|
764
|
+
walkManager.removeDataRepresentative(view)
|
|
765
|
+
}
|
|
766
|
+
walkManager.removeDataRepresentative(self)
|
|
767
|
+
walkManager.delegate = nil
|
|
768
|
+
self.walkManager = nil
|
|
769
|
+
_ = AMapNaviWalkManager.destroyInstance()
|
|
341
770
|
}
|
|
342
|
-
|
|
343
|
-
private func
|
|
344
|
-
|
|
345
|
-
|
|
771
|
+
|
|
772
|
+
private func destroyRideManagerIfNeeded() {
|
|
773
|
+
guard let rideManager else {
|
|
774
|
+
return
|
|
775
|
+
}
|
|
776
|
+
if let view = rideView {
|
|
777
|
+
rideManager.removeDataRepresentative(view)
|
|
778
|
+
}
|
|
779
|
+
rideManager.removeDataRepresentative(self)
|
|
780
|
+
rideManager.delegate = nil
|
|
781
|
+
self.rideManager = nil
|
|
782
|
+
_ = AMapNaviRideManager.destroyInstance()
|
|
346
783
|
}
|
|
347
|
-
|
|
348
|
-
private func
|
|
349
|
-
|
|
784
|
+
|
|
785
|
+
private func rebindDriveManagerToView() {
|
|
786
|
+
driveManager = AMapNaviDriveManager.sharedInstance()
|
|
787
|
+
driveManager?.delegate = self
|
|
788
|
+
applyDriveManagerBackgroundLocationOptionsIfNeeded()
|
|
789
|
+
driveManager?.isUseInternalTTS = enableVoice
|
|
790
|
+
driveManager?.removeDataRepresentative(self)
|
|
791
|
+
if let view = driveView {
|
|
792
|
+
driveManager?.removeDataRepresentative(view)
|
|
793
|
+
driveManager?.addDataRepresentative(view)
|
|
794
|
+
}
|
|
795
|
+
driveManager?.addDataRepresentative(self)
|
|
350
796
|
}
|
|
351
|
-
|
|
352
|
-
private func
|
|
353
|
-
|
|
797
|
+
|
|
798
|
+
private func rebindWalkManagerToView() {
|
|
799
|
+
walkManager = AMapNaviWalkManager.sharedInstance()
|
|
800
|
+
walkManager?.delegate = self
|
|
801
|
+
applyWalkManagerBackgroundLocationOptionsIfNeeded()
|
|
802
|
+
walkManager?.isUseInternalTTS = enableVoice
|
|
803
|
+
walkManager?.removeDataRepresentative(self)
|
|
804
|
+
if let view = walkView {
|
|
805
|
+
walkManager?.removeDataRepresentative(view)
|
|
806
|
+
walkManager?.addDataRepresentative(view)
|
|
807
|
+
}
|
|
808
|
+
walkManager?.addDataRepresentative(self)
|
|
354
809
|
}
|
|
355
|
-
|
|
356
|
-
private func
|
|
357
|
-
|
|
810
|
+
|
|
811
|
+
private func rebindRideManagerToView() {
|
|
812
|
+
rideManager = AMapNaviRideManager.sharedInstance()
|
|
813
|
+
rideManager?.delegate = self
|
|
814
|
+
applyRideManagerBackgroundLocationOptionsIfNeeded()
|
|
815
|
+
rideManager?.isUseInternalTTS = enableVoice
|
|
816
|
+
rideManager?.removeDataRepresentative(self)
|
|
817
|
+
if let view = rideView {
|
|
818
|
+
rideManager?.removeDataRepresentative(view)
|
|
819
|
+
rideManager?.addDataRepresentative(view)
|
|
820
|
+
}
|
|
821
|
+
rideManager?.addDataRepresentative(self)
|
|
358
822
|
}
|
|
359
|
-
|
|
360
|
-
private func
|
|
361
|
-
|
|
823
|
+
|
|
824
|
+
private func applyDriveManagerBackgroundLocationOptionsIfNeeded() {
|
|
825
|
+
guard let driveManager else {
|
|
826
|
+
return
|
|
827
|
+
}
|
|
828
|
+
driveManager.pausesLocationUpdatesAutomatically = false
|
|
829
|
+
let backgroundModes = Bundle.main.object(forInfoDictionaryKey: "UIBackgroundModes") as? [String]
|
|
830
|
+
if backgroundModes?.contains("location") == true {
|
|
831
|
+
driveManager.allowsBackgroundLocationUpdates = true
|
|
832
|
+
}
|
|
362
833
|
}
|
|
363
|
-
|
|
364
|
-
private func
|
|
365
|
-
|
|
834
|
+
|
|
835
|
+
private func applyWalkManagerBackgroundLocationOptionsIfNeeded() {
|
|
836
|
+
guard let walkManager else {
|
|
837
|
+
return
|
|
838
|
+
}
|
|
839
|
+
walkManager.pausesLocationUpdatesAutomatically = false
|
|
840
|
+
let backgroundModes = Bundle.main.object(forInfoDictionaryKey: "UIBackgroundModes") as? [String]
|
|
841
|
+
if backgroundModes?.contains("location") == true {
|
|
842
|
+
walkManager.allowsBackgroundLocationUpdates = true
|
|
843
|
+
}
|
|
366
844
|
}
|
|
367
|
-
|
|
368
|
-
private func
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
driveView?.showMode = .normal
|
|
377
|
-
default:
|
|
378
|
-
break
|
|
845
|
+
|
|
846
|
+
private func applyRideManagerBackgroundLocationOptionsIfNeeded() {
|
|
847
|
+
guard let rideManager else {
|
|
848
|
+
return
|
|
849
|
+
}
|
|
850
|
+
rideManager.pausesLocationUpdatesAutomatically = false
|
|
851
|
+
let backgroundModes = Bundle.main.object(forInfoDictionaryKey: "UIBackgroundModes") as? [String]
|
|
852
|
+
if backgroundModes?.contains("location") == true {
|
|
853
|
+
rideManager.allowsBackgroundLocationUpdates = true
|
|
379
854
|
}
|
|
380
855
|
}
|
|
381
|
-
|
|
382
|
-
private func
|
|
383
|
-
|
|
856
|
+
|
|
857
|
+
private func hasBackgroundAudioModeEnabled() -> Bool {
|
|
858
|
+
let backgroundModes = Bundle.main.object(forInfoDictionaryKey: "UIBackgroundModes") as? [String]
|
|
859
|
+
return backgroundModes?.contains("audio") == true
|
|
384
860
|
}
|
|
385
|
-
|
|
386
|
-
private func
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
case 0:
|
|
390
|
-
driveView?.mapViewModeType = .day
|
|
391
|
-
case 1:
|
|
392
|
-
driveView?.mapViewModeType = .night
|
|
393
|
-
case 2:
|
|
394
|
-
driveView?.mapViewModeType = .dayNightAuto
|
|
395
|
-
case 3:
|
|
396
|
-
driveView?.mapViewModeType = .custom
|
|
397
|
-
default:
|
|
398
|
-
break
|
|
861
|
+
|
|
862
|
+
private func activateNavigationAudioSessionIfNeeded(reason: String) {
|
|
863
|
+
guard enableVoice else {
|
|
864
|
+
return
|
|
399
865
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
866
|
+
|
|
867
|
+
if !hasBackgroundAudioModeEnabled(), !hasLoggedMissingBackgroundAudioMode {
|
|
868
|
+
hasLoggedMissingBackgroundAudioMode = true
|
|
869
|
+
NSLog(
|
|
870
|
+
"[ExpoGaodeMapNaviView][Audio] UIBackgroundModes 缺少 audio,切后台后语音可能中断。reason=%@",
|
|
871
|
+
reason
|
|
872
|
+
)
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
let audioSession = AVAudioSession.sharedInstance()
|
|
876
|
+
do {
|
|
877
|
+
try audioSession.setCategory(
|
|
878
|
+
.playback,
|
|
879
|
+
mode: .voicePrompt,
|
|
880
|
+
options: [.duckOthers, .allowBluetooth, .allowBluetoothA2DP]
|
|
881
|
+
)
|
|
882
|
+
try audioSession.setActive(true)
|
|
883
|
+
isNavigationAudioSessionActive = true
|
|
884
|
+
NSLog("[ExpoGaodeMapNaviView][Audio] activated. reason=%@", reason)
|
|
885
|
+
} catch {
|
|
886
|
+
NSLog(
|
|
887
|
+
"[ExpoGaodeMapNaviView][Audio] activate failed. reason=%@ error=%@",
|
|
888
|
+
reason,
|
|
889
|
+
String(describing: error)
|
|
890
|
+
)
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
private func deactivateNavigationAudioSessionIfNeeded(reason: String) {
|
|
895
|
+
guard isNavigationAudioSessionActive else {
|
|
896
|
+
return
|
|
897
|
+
}
|
|
898
|
+
let audioSession = AVAudioSession.sharedInstance()
|
|
899
|
+
do {
|
|
900
|
+
try audioSession.setActive(false, options: [.notifyOthersOnDeactivation])
|
|
901
|
+
isNavigationAudioSessionActive = false
|
|
902
|
+
NSLog("[ExpoGaodeMapNaviView][Audio] deactivated. reason=%@", reason)
|
|
903
|
+
} catch {
|
|
904
|
+
NSLog(
|
|
905
|
+
"[ExpoGaodeMapNaviView][Audio] deactivate failed. reason=%@ error=%@",
|
|
906
|
+
reason,
|
|
907
|
+
String(describing: error)
|
|
908
|
+
)
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
private func resetTransientNavigationState() {
|
|
913
|
+
hasStartedNavi = false
|
|
914
|
+
hasReceivedFirstNaviData = false
|
|
915
|
+
lastKnownSpeed = 0
|
|
916
|
+
currentNaviRoute = nil
|
|
917
|
+
currentRouteTotalLength = nil
|
|
918
|
+
trafficBarTotalLength = nil
|
|
919
|
+
lastNavigationInfoPayload = nil
|
|
920
|
+
lastTurnIconType = nil
|
|
921
|
+
lastNextTurnIconType = nil
|
|
922
|
+
lastTurnIconBase64 = nil
|
|
923
|
+
clearCachedTurnIconUris()
|
|
924
|
+
isCrossVisible = false
|
|
925
|
+
isLaneInfoVisible = false
|
|
926
|
+
resetCustomWaypointArrivalState()
|
|
927
|
+
emitVisualStateUpdate()
|
|
928
|
+
NavigationLiveActivityManager.shared.stop()
|
|
929
|
+
deactivateNavigationAudioSessionIfNeeded(reason: "reset_transient_navigation_state")
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
private func emitVisualStateUpdate() {
|
|
933
|
+
onNavigationVisualStateUpdate([
|
|
934
|
+
"isCrossVisible": isCrossVisible,
|
|
935
|
+
// iOS 官方导航 SDK 公开的是 showCrossImage / hideCrossImage,
|
|
936
|
+
// 当前没有 Android showModeCross / hideModeCross 对等的 3D 路口模型接口。
|
|
937
|
+
"isModeCrossVisible": false,
|
|
938
|
+
"isLaneInfoVisible": isLaneInfoVisible
|
|
939
|
+
])
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
private func emitNavigationInfoUpdate(_ payload: [String: Any]) {
|
|
943
|
+
(driveView as? ExpoGaodeMapCustomDriveView)?.refreshSuppressedTopInfoUIIfNeeded()
|
|
944
|
+
var nextPayload = payload
|
|
945
|
+
let payloadIconType = payloadIntValue(nextPayload["iconType"]) ?? 0
|
|
946
|
+
if payloadIconType <= 0, let lastTurnIconType {
|
|
947
|
+
nextPayload["iconType"] = lastTurnIconType
|
|
948
|
+
nextPayload["iconDirection"] = lastTurnIconType
|
|
949
|
+
}
|
|
950
|
+
let payloadNextIconType = payloadIntValue(nextPayload["nextIconType"]) ?? 0
|
|
951
|
+
if payloadNextIconType <= 0, let lastNextTurnIconType {
|
|
952
|
+
nextPayload["nextIconType"] = lastNextTurnIconType
|
|
953
|
+
}
|
|
954
|
+
if let lastTurnIconImageUri {
|
|
955
|
+
nextPayload["turnIconImage"] = lastTurnIconImageUri
|
|
956
|
+
} else {
|
|
957
|
+
nextPayload.removeValue(forKey: "turnIconImage")
|
|
958
|
+
}
|
|
959
|
+
if let lastNextTurnIconImageUri {
|
|
960
|
+
nextPayload["nextTurnIconImage"] = lastNextTurnIconImageUri
|
|
961
|
+
} else {
|
|
962
|
+
nextPayload.removeValue(forKey: "nextTurnIconImage")
|
|
963
|
+
}
|
|
964
|
+
lastNavigationInfoPayload = nextPayload
|
|
965
|
+
onNavigationInfoUpdate(nextPayload)
|
|
966
|
+
syncNavigationLiveActivity(payload: nextPayload)
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
private func reemitLastNavigationInfoIfNeeded() {
|
|
970
|
+
guard var nextPayload = lastNavigationInfoPayload else {
|
|
971
|
+
return
|
|
972
|
+
}
|
|
973
|
+
if let lastTurnIconType {
|
|
974
|
+
nextPayload["iconType"] = lastTurnIconType
|
|
975
|
+
nextPayload["iconDirection"] = lastTurnIconType
|
|
976
|
+
}
|
|
977
|
+
if let lastNextTurnIconType {
|
|
978
|
+
nextPayload["nextIconType"] = lastNextTurnIconType
|
|
979
|
+
} else {
|
|
980
|
+
nextPayload.removeValue(forKey: "nextIconType")
|
|
981
|
+
}
|
|
982
|
+
if let lastTurnIconImageUri {
|
|
983
|
+
nextPayload["turnIconImage"] = lastTurnIconImageUri
|
|
984
|
+
} else {
|
|
985
|
+
nextPayload.removeValue(forKey: "turnIconImage")
|
|
986
|
+
}
|
|
987
|
+
if let lastNextTurnIconImageUri {
|
|
988
|
+
nextPayload["nextTurnIconImage"] = lastNextTurnIconImageUri
|
|
989
|
+
} else {
|
|
990
|
+
nextPayload.removeValue(forKey: "nextTurnIconImage")
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
lastNavigationInfoPayload = nextPayload
|
|
994
|
+
onNavigationInfoUpdate(nextPayload)
|
|
995
|
+
syncNavigationLiveActivity(payload: nextPayload)
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
private func resolveAppDisplayName() -> String {
|
|
999
|
+
let info = Bundle.main.infoDictionary
|
|
1000
|
+
if let displayName = info?["CFBundleDisplayName"] as? String, !displayName.isEmpty {
|
|
1001
|
+
return displayName
|
|
1002
|
+
}
|
|
1003
|
+
if let appName = info?["CFBundleName"] as? String, !appName.isEmpty {
|
|
1004
|
+
return appName
|
|
1005
|
+
}
|
|
1006
|
+
return "导航"
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
private func payloadIntValue(_ value: Any?) -> Int? {
|
|
1010
|
+
if let intValue = value as? Int {
|
|
1011
|
+
return intValue
|
|
1012
|
+
}
|
|
1013
|
+
if let numberValue = value as? NSNumber {
|
|
1014
|
+
return numberValue.intValue
|
|
1015
|
+
}
|
|
1016
|
+
if let doubleValue = value as? Double {
|
|
1017
|
+
return Int(doubleValue)
|
|
1018
|
+
}
|
|
1019
|
+
if let floatValue = value as? Float {
|
|
1020
|
+
return Int(floatValue)
|
|
1021
|
+
}
|
|
1022
|
+
return nil
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
private func makeLiveActivitySnapshot(payload: [String: Any]?) -> NavigationLiveActivitySnapshot? {
|
|
1026
|
+
guard let payload else {
|
|
1027
|
+
return nil
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
let remainDistance = payloadIntValue(payload["pathRetainDistance"]) ?? 0
|
|
1031
|
+
let routeTotalDistance = max(
|
|
1032
|
+
trafficBarTotalLength ?? 0,
|
|
1033
|
+
currentRouteTotalLength ?? 0,
|
|
1034
|
+
remainDistance
|
|
1035
|
+
)
|
|
1036
|
+
let remainTime = payloadIntValue(payload["pathRetainTime"]) ?? 0
|
|
1037
|
+
let stepRemainDistance = payloadIntValue(payload["curStepRetainDistance"]) ?? 0
|
|
1038
|
+
let iconType = payloadIntValue(payload["iconType"]) ?? 0
|
|
1039
|
+
let currentRoadName = (payload["currentRoadName"] as? String) ?? ""
|
|
1040
|
+
let nextRoadName = (payload["nextRoadName"] as? String) ?? ""
|
|
1041
|
+
|
|
1042
|
+
return NavigationLiveActivitySnapshot(
|
|
1043
|
+
appName: resolveAppDisplayName(),
|
|
1044
|
+
currentRoadName: currentRoadName,
|
|
1045
|
+
nextRoadName: nextRoadName,
|
|
1046
|
+
pathRetainDistance: remainDistance,
|
|
1047
|
+
routeTotalDistance: routeTotalDistance,
|
|
1048
|
+
pathRetainTime: remainTime,
|
|
1049
|
+
curStepRetainDistance: stepRemainDistance,
|
|
1050
|
+
iconType: iconType,
|
|
1051
|
+
turnIconBase64: lastTurnIconBase64
|
|
1052
|
+
)
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
private func syncNavigationLiveActivity(payload: [String: Any]?) {
|
|
1056
|
+
guard iosLiveActivityEnabled else {
|
|
1057
|
+
return
|
|
1058
|
+
}
|
|
1059
|
+
guard hasStartedNavi else {
|
|
1060
|
+
return
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
guard let snapshot = makeLiveActivitySnapshot(payload: payload) else {
|
|
1064
|
+
return
|
|
1065
|
+
}
|
|
1066
|
+
NavigationLiveActivityManager.shared.startOrUpdate(snapshot: snapshot)
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
private func syncNavigationLiveActivityWithLastPayload() {
|
|
1070
|
+
syncNavigationLiveActivity(payload: lastNavigationInfoPayload)
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
private func normalizedTurnIconImage(_ image: UIImage) -> UIImage {
|
|
1074
|
+
guard image.imageOrientation != .up else {
|
|
1075
|
+
return image
|
|
1076
|
+
}
|
|
1077
|
+
let renderer = UIGraphicsImageRenderer(size: image.size)
|
|
1078
|
+
return renderer.image { _ in
|
|
1079
|
+
image.draw(in: CGRect(origin: .zero, size: image.size))
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
private func trimmedTransparentBoundsImage(_ image: UIImage) -> UIImage {
|
|
1084
|
+
guard let cgImage = image.cgImage else {
|
|
1085
|
+
return image
|
|
1086
|
+
}
|
|
1087
|
+
guard let dataProvider = cgImage.dataProvider, let data = dataProvider.data else {
|
|
1088
|
+
return image
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
let bytes = CFDataGetBytePtr(data)
|
|
1092
|
+
let bytesPerPixel = max(cgImage.bitsPerPixel / 8, 0)
|
|
1093
|
+
let bytesPerRow = cgImage.bytesPerRow
|
|
1094
|
+
let width = cgImage.width
|
|
1095
|
+
let height = cgImage.height
|
|
1096
|
+
guard let bytes, bytesPerPixel >= 4, width > 0, height > 0 else {
|
|
1097
|
+
return image
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
let alphaOffset: Int
|
|
1101
|
+
switch cgImage.alphaInfo {
|
|
1102
|
+
case .premultipliedLast, .last, .noneSkipLast:
|
|
1103
|
+
alphaOffset = 3
|
|
1104
|
+
case .premultipliedFirst, .first, .noneSkipFirst:
|
|
1105
|
+
alphaOffset = 0
|
|
1106
|
+
default:
|
|
1107
|
+
return image
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
var minX = width
|
|
1111
|
+
var minY = height
|
|
1112
|
+
var maxX = -1
|
|
1113
|
+
var maxY = -1
|
|
1114
|
+
let alphaThreshold: UInt8 = 8
|
|
1115
|
+
|
|
1116
|
+
for y in 0..<height {
|
|
1117
|
+
let rowStart = y * bytesPerRow
|
|
1118
|
+
for x in 0..<width {
|
|
1119
|
+
let pixelStart = rowStart + x * bytesPerPixel
|
|
1120
|
+
let alpha = bytes[pixelStart + alphaOffset]
|
|
1121
|
+
if alpha > alphaThreshold {
|
|
1122
|
+
minX = min(minX, x)
|
|
1123
|
+
minY = min(minY, y)
|
|
1124
|
+
maxX = max(maxX, x)
|
|
1125
|
+
maxY = max(maxY, y)
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
guard maxX >= minX, maxY >= minY else {
|
|
1131
|
+
return image
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
let cropRect = CGRect(
|
|
1135
|
+
x: minX,
|
|
1136
|
+
y: minY,
|
|
1137
|
+
width: (maxX - minX + 1),
|
|
1138
|
+
height: (maxY - minY + 1)
|
|
1139
|
+
)
|
|
1140
|
+
guard let croppedCGImage = cgImage.cropping(to: cropRect) else {
|
|
1141
|
+
return image
|
|
1142
|
+
}
|
|
1143
|
+
return UIImage(cgImage: croppedCGImage, scale: image.scale, orientation: .up)
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
private func aspectFitRect(sourceSize: CGSize, targetSize: CGSize) -> CGRect {
|
|
1147
|
+
guard sourceSize.width > 0, sourceSize.height > 0, targetSize.width > 0, targetSize.height > 0 else {
|
|
1148
|
+
return CGRect(origin: .zero, size: targetSize)
|
|
1149
|
+
}
|
|
1150
|
+
let scale = min(targetSize.width / sourceSize.width, targetSize.height / sourceSize.height)
|
|
1151
|
+
let drawSize = CGSize(width: sourceSize.width * scale, height: sourceSize.height * scale)
|
|
1152
|
+
return CGRect(
|
|
1153
|
+
x: (targetSize.width - drawSize.width) / 2.0,
|
|
1154
|
+
y: (targetSize.height - drawSize.height) / 2.0,
|
|
1155
|
+
width: drawSize.width,
|
|
1156
|
+
height: drawSize.height
|
|
1157
|
+
)
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
private func encodeTurnIconForLiveActivity(_ image: UIImage?) -> String? {
|
|
1161
|
+
guard let image else {
|
|
1162
|
+
return nil
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
let maxEncodedLength = 2600
|
|
1166
|
+
let targetSizes: [CGSize] = [
|
|
1167
|
+
CGSize(width: 34, height: 34),
|
|
1168
|
+
CGSize(width: 30, height: 30),
|
|
1169
|
+
CGSize(width: 26, height: 26),
|
|
1170
|
+
CGSize(width: 28, height: 28),
|
|
1171
|
+
CGSize(width: 24, height: 24),
|
|
1172
|
+
CGSize(width: 20, height: 20)
|
|
1173
|
+
]
|
|
1174
|
+
let preparedImage = trimmedTransparentBoundsImage(normalizedTurnIconImage(image))
|
|
1175
|
+
|
|
1176
|
+
for size in targetSizes {
|
|
1177
|
+
let renderer = UIGraphicsImageRenderer(size: size)
|
|
1178
|
+
let rendered = renderer.image { _ in
|
|
1179
|
+
let rect = aspectFitRect(sourceSize: preparedImage.size, targetSize: size)
|
|
1180
|
+
preparedImage.draw(in: rect)
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
if let pngData = rendered.pngData() {
|
|
1184
|
+
let pngBase64 = pngData.base64EncodedString()
|
|
1185
|
+
if pngBase64.count <= maxEncodedLength {
|
|
1186
|
+
return pngBase64
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
NSLog("[ExpoGaodeMapNaviView][LiveActivity] turn icon dropped because encoded payload is too large")
|
|
1192
|
+
return nil
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
private func fallbackTurnSymbolName(for iconType: Int) -> String {
|
|
1196
|
+
switch iconType {
|
|
1197
|
+
case 2:
|
|
1198
|
+
return "arrow.turn.up.left"
|
|
1199
|
+
case 3:
|
|
1200
|
+
return "arrow.turn.up.right"
|
|
1201
|
+
case 4:
|
|
1202
|
+
return "arrow.up.left"
|
|
1203
|
+
case 5:
|
|
1204
|
+
return "arrow.up.right"
|
|
1205
|
+
case 6:
|
|
1206
|
+
return "arrow.down.left"
|
|
1207
|
+
case 7:
|
|
1208
|
+
return "arrow.down.right"
|
|
1209
|
+
case 8:
|
|
1210
|
+
return "arrow.uturn.left"
|
|
1211
|
+
case 9, 20:
|
|
1212
|
+
return "arrow.up"
|
|
1213
|
+
case 11, 12:
|
|
1214
|
+
return "arrow.clockwise.circle"
|
|
1215
|
+
case 15:
|
|
1216
|
+
return "flag.checkered"
|
|
1217
|
+
case 19:
|
|
1218
|
+
return "arrow.uturn.right"
|
|
1219
|
+
case 65:
|
|
1220
|
+
return "arrow.left.circle"
|
|
1221
|
+
case 66:
|
|
1222
|
+
return "arrow.right.circle"
|
|
1223
|
+
default:
|
|
1224
|
+
return "arrow.up"
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
private func fallbackTurnIconImage(iconType: Int) -> UIImage? {
|
|
1229
|
+
let config = UIImage.SymbolConfiguration(pointSize: 36, weight: .bold)
|
|
1230
|
+
return UIImage(systemName: fallbackTurnSymbolName(for: iconType), withConfiguration: config)?
|
|
1231
|
+
.withTintColor(.white, renderingMode: .alwaysOriginal)
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
private func resolveTravelTurnIconImage(iconType: Int) -> UIImage? {
|
|
1235
|
+
return fallbackTurnIconImage(iconType: iconType)
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
private func updateCachedTurnIconForTravelSceneIfNeeded(iconType: Int) {
|
|
1239
|
+
guard activeScene != .drive else {
|
|
1240
|
+
return
|
|
1241
|
+
}
|
|
1242
|
+
let image = resolveTravelTurnIconImage(iconType: iconType)
|
|
1243
|
+
if let encoded = encodeTurnIconForLiveActivity(image) {
|
|
1244
|
+
lastTurnIconBase64 = encoded
|
|
1245
|
+
}
|
|
1246
|
+
lastTurnIconImageUri = cacheTurnIconImage(
|
|
1247
|
+
image,
|
|
1248
|
+
prefix: "travel_turn_icon",
|
|
1249
|
+
previousUri: lastTurnIconImageUri
|
|
1250
|
+
)
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
private func cacheTurnIconImage(_ image: UIImage?, prefix: String, previousUri: String?) -> String? {
|
|
1254
|
+
guard let image, let data = image.pngData() else {
|
|
1255
|
+
if let previousUri {
|
|
1256
|
+
deleteCachedTurnIcon(at: previousUri)
|
|
1257
|
+
}
|
|
1258
|
+
return nil
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
let filename = "\(prefix)_\(UUID().uuidString).png"
|
|
1262
|
+
let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent(filename)
|
|
1263
|
+
|
|
1264
|
+
do {
|
|
1265
|
+
try data.write(to: fileURL, options: .atomic)
|
|
1266
|
+
if let previousUri, previousUri != fileURL.absoluteString {
|
|
1267
|
+
deleteCachedTurnIcon(at: previousUri)
|
|
1268
|
+
}
|
|
1269
|
+
return fileURL.absoluteString
|
|
1270
|
+
} catch {
|
|
1271
|
+
return previousUri
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
private func deleteCachedTurnIcon(at uriString: String) {
|
|
1276
|
+
guard let fileURL = URL(string: uriString), fileURL.isFileURL else {
|
|
1277
|
+
return
|
|
1278
|
+
}
|
|
1279
|
+
try? FileManager.default.removeItem(at: fileURL)
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
private func clearCachedTurnIconUris() {
|
|
1283
|
+
if let lastTurnIconImageUri {
|
|
1284
|
+
deleteCachedTurnIcon(at: lastTurnIconImageUri)
|
|
1285
|
+
}
|
|
1286
|
+
if let lastNextTurnIconImageUri {
|
|
1287
|
+
deleteCachedTurnIcon(at: lastNextTurnIconImageUri)
|
|
1288
|
+
}
|
|
1289
|
+
lastTurnIconImageUri = nil
|
|
1290
|
+
lastNextTurnIconImageUri = nil
|
|
1291
|
+
lastTurnIconBase64 = nil
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
private func splitLaneInfoString(_ value: String) -> [String] {
|
|
1295
|
+
value
|
|
1296
|
+
.split(separator: "|", omittingEmptySubsequences: false)
|
|
1297
|
+
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() }
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
private func parseLaneToken(_ token: String, kind: LaneStringKind) -> Int? {
|
|
1301
|
+
guard !token.isEmpty else {
|
|
1302
|
+
return nil
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
if token == "255" || token == "ff" {
|
|
1306
|
+
return 255
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
// Older iOS callbacks may use `f` as filler / unavailable lane marker.
|
|
1310
|
+
if token == "f" {
|
|
1311
|
+
return kind == .background ? 255 : 255
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
if let decimalValue = Int(token) {
|
|
1315
|
+
return decimalValue
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
switch token {
|
|
1319
|
+
case "a":
|
|
1320
|
+
return 10
|
|
1321
|
+
case "b":
|
|
1322
|
+
return 11
|
|
1323
|
+
case "c":
|
|
1324
|
+
return 12
|
|
1325
|
+
case "d":
|
|
1326
|
+
return 13
|
|
1327
|
+
case "e":
|
|
1328
|
+
return 14
|
|
1329
|
+
case "g":
|
|
1330
|
+
return 16
|
|
1331
|
+
case "h":
|
|
1332
|
+
return 17
|
|
1333
|
+
case "i":
|
|
1334
|
+
return 18
|
|
1335
|
+
case "j":
|
|
1336
|
+
return 19
|
|
1337
|
+
case "k":
|
|
1338
|
+
return 20
|
|
1339
|
+
case "kk":
|
|
1340
|
+
return 21
|
|
1341
|
+
case "l":
|
|
1342
|
+
return 23
|
|
1343
|
+
default:
|
|
1344
|
+
return Int(token, radix: 16)
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
private func serializeLaneInfo(laneBackInfo: String, laneSelectInfo: String) -> [String: Any]? {
|
|
1349
|
+
let backgroundLane = splitLaneInfoString(laneBackInfo).map {
|
|
1350
|
+
parseLaneToken($0, kind: .background) ?? 255
|
|
1351
|
+
}
|
|
1352
|
+
let frontLane = splitLaneInfoString(laneSelectInfo).map {
|
|
1353
|
+
parseLaneToken($0, kind: .selected) ?? 255
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
let sentinelIndex = backgroundLane.firstIndex(of: 255)
|
|
1357
|
+
let resolvedCount = [
|
|
1358
|
+
sentinelIndex,
|
|
1359
|
+
backgroundLane.isEmpty ? nil : backgroundLane.count,
|
|
1360
|
+
frontLane.isEmpty ? nil : frontLane.count
|
|
1361
|
+
]
|
|
1362
|
+
.compactMap { $0 }
|
|
1363
|
+
.min() ?? 0
|
|
1364
|
+
|
|
1365
|
+
guard resolvedCount > 0 else {
|
|
1366
|
+
return nil
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
let normalizedBackground = (0..<resolvedCount).map { index in
|
|
1370
|
+
backgroundLane.indices.contains(index) ? backgroundLane[index] : 255
|
|
1371
|
+
}
|
|
1372
|
+
let normalizedFront = (0..<resolvedCount).map { index in
|
|
1373
|
+
frontLane.indices.contains(index) ? frontLane[index] : 255
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
return [
|
|
1377
|
+
"laneCount": resolvedCount,
|
|
1378
|
+
"backgroundLane": normalizedBackground,
|
|
1379
|
+
"frontLane": normalizedFront
|
|
1380
|
+
]
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
private func applyViewOptions() {
|
|
1384
|
+
switch activeScene {
|
|
1385
|
+
case .drive:
|
|
1386
|
+
applyDriveViewOptions()
|
|
1387
|
+
case .walk:
|
|
1388
|
+
applyWalkViewOptions()
|
|
1389
|
+
case .ride:
|
|
1390
|
+
applyRideViewOptions()
|
|
1391
|
+
}
|
|
1392
|
+
applyCustomAnnotationImages()
|
|
1393
|
+
applyCustomUILayoutOptionsIfNeeded()
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
private func applyDriveViewOptions() {
|
|
1397
|
+
guard let driveView else {
|
|
1398
|
+
return
|
|
1399
|
+
}
|
|
1400
|
+
(driveView as? ExpoGaodeMapCustomDriveView)?.suppressBottomRightUI = showUIElements == false
|
|
1401
|
+
driveView.showUIElements = pendingShowUIElements ?? showUIElements
|
|
1402
|
+
driveView.showCamera = showCamera
|
|
1403
|
+
driveView.autoSwitchShowModeToCarPositionLocked = autoLockCar
|
|
1404
|
+
driveView.autoZoomMapLevel = autoChangeZoom
|
|
1405
|
+
driveView.mapShowTraffic = trafficLayerEnabled
|
|
1406
|
+
driveView.showCrossImage = realCrossDisplay
|
|
1407
|
+
driveView.showRoute = showRoute
|
|
1408
|
+
driveView.showTrafficBar = showTrafficBar
|
|
1409
|
+
driveView.showTrafficButton = showTrafficButton
|
|
1410
|
+
driveView.showBackupRoute = showBackupRoute
|
|
1411
|
+
driveView.showEagleMap = showEagleMap
|
|
1412
|
+
driveView.showVectorline = showVectorline
|
|
1413
|
+
driveView.showTrafficLights = showTrafficLights
|
|
1414
|
+
driveView.showDriveCongestion = showDriveCongestion
|
|
1415
|
+
driveView.showTrafficLightView = showTrafficLightView
|
|
1416
|
+
applyNaviMode(naviMode)
|
|
1417
|
+
applyShowMode(showMode)
|
|
1418
|
+
applyShowTurnArrow(showTurnArrow)
|
|
1419
|
+
applyShowBrowseRouteButton(showBrowseRouteButton)
|
|
1420
|
+
applyShowMoreButton(showMoreButton)
|
|
1421
|
+
applyShowGreyAfterPass(showGreyAfterPass)
|
|
1422
|
+
if let showCompassEnabled {
|
|
1423
|
+
applyShowCompassEnabled(showCompassEnabled)
|
|
1424
|
+
}
|
|
1425
|
+
applyLineWidth(lineWidth)
|
|
1426
|
+
applyScreenAnchor(screenAnchor)
|
|
1427
|
+
applyMapViewModeType(mapViewModeType)
|
|
1428
|
+
applyTrafficBarFrame(trafficBarFrame)
|
|
1429
|
+
applyTrafficBarColors(trafficBarColors)
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
private func applyWalkViewOptions() {
|
|
1433
|
+
guard let walkView else {
|
|
1434
|
+
return
|
|
1435
|
+
}
|
|
1436
|
+
walkView.showUIElements = pendingShowUIElements ?? showUIElements
|
|
1437
|
+
applyNaviMode(naviMode)
|
|
1438
|
+
applyShowMode(showMode)
|
|
1439
|
+
applyShowTurnArrow(showTurnArrow)
|
|
1440
|
+
applyShowBrowseRouteButton(showBrowseRouteButton)
|
|
1441
|
+
applyShowMoreButton(showMoreButton)
|
|
1442
|
+
applyShowGreyAfterPass(showGreyAfterPass)
|
|
1443
|
+
if let showCompassEnabled {
|
|
1444
|
+
applyShowCompassEnabled(showCompassEnabled)
|
|
1445
|
+
}
|
|
1446
|
+
applyLineWidth(lineWidth)
|
|
1447
|
+
applyScreenAnchor(screenAnchor)
|
|
1448
|
+
applyMapViewModeType(mapViewModeType)
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
private func applyRideViewOptions() {
|
|
1452
|
+
guard let rideView else {
|
|
1453
|
+
return
|
|
1454
|
+
}
|
|
1455
|
+
rideView.showUIElements = pendingShowUIElements ?? showUIElements
|
|
1456
|
+
applyNaviMode(naviMode)
|
|
1457
|
+
applyShowMode(showMode)
|
|
1458
|
+
applyShowTurnArrow(showTurnArrow)
|
|
1459
|
+
applyShowBrowseRouteButton(showBrowseRouteButton)
|
|
1460
|
+
applyShowMoreButton(showMoreButton)
|
|
1461
|
+
applyShowGreyAfterPass(showGreyAfterPass)
|
|
1462
|
+
if let showCompassEnabled {
|
|
1463
|
+
applyShowCompassEnabled(showCompassEnabled)
|
|
1464
|
+
}
|
|
1465
|
+
applyLineWidth(lineWidth)
|
|
1466
|
+
applyScreenAnchor(screenAnchor)
|
|
1467
|
+
applyMapViewModeType(mapViewModeType)
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
private func applyCustomAnnotationImages() {
|
|
1471
|
+
applyCarImageSource()
|
|
1472
|
+
applyCarCompassImageSource()
|
|
1473
|
+
applyStartPointImageSource()
|
|
1474
|
+
applyWayPointImageSource()
|
|
1475
|
+
applyEndPointImageSource()
|
|
1476
|
+
applyCameraImageSource()
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
private func applyCustomWaypointMarkerPayloads(_ payloads: [[String: Any]]?) {
|
|
1480
|
+
customWaypointMarkers = (payloads ?? []).compactMap { item in
|
|
1481
|
+
guard
|
|
1482
|
+
let latitude = item["latitude"] as? Double,
|
|
1483
|
+
let longitude = item["longitude"] as? Double
|
|
1484
|
+
else {
|
|
1485
|
+
return nil
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
let title = (item["title"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1489
|
+
return NaviCustomWaypointMarkerModel(
|
|
1490
|
+
latitude: latitude,
|
|
1491
|
+
longitude: longitude,
|
|
1492
|
+
title: (title?.isEmpty == false ? title : nil) ?? "途经"
|
|
1493
|
+
)
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
refreshCustomWaypointAnnotations()
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
private func clearCustomWaypointAnnotations() {
|
|
1500
|
+
guard let driveView else {
|
|
1501
|
+
renderedCustomWaypointAnnotations.removeAll()
|
|
1502
|
+
return
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
for annotation in renderedCustomWaypointAnnotations {
|
|
1506
|
+
driveView.remove(annotation)
|
|
1507
|
+
}
|
|
1508
|
+
renderedCustomWaypointAnnotations.removeAll()
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
private func refreshCustomWaypointAnnotations() {
|
|
1512
|
+
clearCustomWaypointAnnotations()
|
|
1513
|
+
|
|
1514
|
+
guard activeScene == .drive, let driveView else {
|
|
1515
|
+
return
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
renderedCustomWaypointAnnotations = customWaypointMarkers.compactMap { marker in
|
|
1519
|
+
guard !marker.arrived else {
|
|
1520
|
+
return nil
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
let coordinate = CLLocationCoordinate2D(
|
|
1524
|
+
latitude: marker.latitude,
|
|
1525
|
+
longitude: marker.longitude
|
|
1526
|
+
)
|
|
1527
|
+
let bubbleView = NaviCustomWaypointBubbleView(title: marker.title)
|
|
1528
|
+
guard let annotation = AMapNaviCompositeCustomAnnotation(
|
|
1529
|
+
coordinate: coordinate,
|
|
1530
|
+
view: bubbleView
|
|
1531
|
+
) else {
|
|
1532
|
+
return nil
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
driveView.add(annotation)
|
|
1536
|
+
return annotation
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
private func resetCustomWaypointArrivalState() {
|
|
1541
|
+
customWaypointMarkers = customWaypointMarkers.map { marker in
|
|
1542
|
+
var nextMarker = marker
|
|
1543
|
+
nextMarker.arrived = false
|
|
1544
|
+
return nextMarker
|
|
1545
|
+
}
|
|
1546
|
+
refreshCustomWaypointAnnotations()
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
private func markNearestCustomWaypointArrived(_ point: AMapNaviPoint) {
|
|
1550
|
+
guard !customWaypointMarkers.isEmpty else {
|
|
1551
|
+
return
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
let targetLatitude = Double(point.latitude)
|
|
1555
|
+
let targetLongitude = Double(point.longitude)
|
|
1556
|
+
let nextIndex = customWaypointMarkers.enumerated()
|
|
1557
|
+
.filter { !$0.element.arrived }
|
|
1558
|
+
.min { lhs, rhs in
|
|
1559
|
+
let lhsDistance = abs(lhs.element.latitude - targetLatitude) + abs(lhs.element.longitude - targetLongitude)
|
|
1560
|
+
let rhsDistance = abs(rhs.element.latitude - targetLatitude) + abs(rhs.element.longitude - targetLongitude)
|
|
1561
|
+
return lhsDistance < rhsDistance
|
|
1562
|
+
}?
|
|
1563
|
+
.offset
|
|
1564
|
+
|
|
1565
|
+
guard let nextIndex else {
|
|
1566
|
+
return
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
customWaypointMarkers[nextIndex].arrived = true
|
|
1570
|
+
refreshCustomWaypointAnnotations()
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
private func resolveLocalImage(_ source: String) -> UIImage? {
|
|
1574
|
+
if source.hasPrefix("file://") {
|
|
1575
|
+
let path = String(source.dropFirst(7))
|
|
1576
|
+
return UIImage(contentsOfFile: path)
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
return UIImage(named: source) ?? UIImage(contentsOfFile: source)
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
private func applyAnnotationImage(
|
|
1583
|
+
source: String?,
|
|
1584
|
+
currentSource: @escaping () -> String?,
|
|
1585
|
+
apply: @escaping (UIImage?) -> Void
|
|
1586
|
+
) {
|
|
1587
|
+
guard let source, !source.isEmpty else {
|
|
1588
|
+
apply(nil)
|
|
1589
|
+
return
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
if source.hasPrefix("http://") || source.hasPrefix("https://") {
|
|
1593
|
+
DispatchQueue.global().async {
|
|
1594
|
+
let image: UIImage? = {
|
|
1595
|
+
guard let url = URL(string: source),
|
|
1596
|
+
let data = try? Data(contentsOf: url) else {
|
|
1597
|
+
return nil
|
|
1598
|
+
}
|
|
1599
|
+
return UIImage(data: data)
|
|
1600
|
+
}()
|
|
1601
|
+
|
|
1602
|
+
DispatchQueue.main.async {
|
|
1603
|
+
guard currentSource() == source else {
|
|
1604
|
+
return
|
|
1605
|
+
}
|
|
1606
|
+
apply(image)
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
return
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
apply(resolveLocalImage(source))
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
private func resizeImageIfNeeded(_ image: UIImage?, targetSize: CGSize?) -> UIImage? {
|
|
1616
|
+
guard let image else {
|
|
1617
|
+
return nil
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
guard let targetSize, targetSize.width > 0, targetSize.height > 0 else {
|
|
1621
|
+
return image
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
let renderer = UIGraphicsImageRenderer(size: targetSize)
|
|
1625
|
+
return renderer.image { _ in
|
|
1626
|
+
image.draw(in: CGRect(origin: .zero, size: targetSize))
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
private func applyCarImageSource() {
|
|
1631
|
+
applyAnnotationImage(source: carImageSource, currentSource: { [weak self] in
|
|
1632
|
+
self?.carImageSource
|
|
1633
|
+
}) { [weak self, weak driveView, weak walkView, weak rideView] image in
|
|
1634
|
+
let resizedImage = self?.resizeImageIfNeeded(image, targetSize: self?.carImageSize) ?? image
|
|
1635
|
+
driveView?.setCarImage(resizedImage)
|
|
1636
|
+
walkView?.setCarImage(resizedImage)
|
|
1637
|
+
rideView?.setCarImage(resizedImage)
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
private func applyCarCompassImageSource() {
|
|
1642
|
+
applyAnnotationImage(source: carCompassImageSource, currentSource: { [weak self] in
|
|
1643
|
+
self?.carCompassImageSource
|
|
1644
|
+
}) { [weak driveView, weak walkView, weak rideView] image in
|
|
1645
|
+
driveView?.setCarCompassImage(image)
|
|
1646
|
+
walkView?.setCarCompassImage(image)
|
|
1647
|
+
rideView?.setCarCompassImage(image)
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
private func applyStartPointImageSource() {
|
|
1652
|
+
applyAnnotationImage(source: startPointImageSource, currentSource: { [weak self] in
|
|
1653
|
+
self?.startPointImageSource
|
|
1654
|
+
}) { [weak driveView, weak walkView, weak rideView] image in
|
|
1655
|
+
driveView?.setStartPointImage(image)
|
|
1656
|
+
walkView?.setStartPointImage(image)
|
|
1657
|
+
rideView?.setStartPointImage(image)
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
private func applyWayPointImageSource() {
|
|
1662
|
+
guard let source = wayPointImageSource?.trimmingCharacters(in: .whitespacesAndNewlines), !source.isEmpty else {
|
|
1663
|
+
return
|
|
1664
|
+
}
|
|
1665
|
+
applyAnnotationImage(source: wayPointImageSource, currentSource: { [weak self] in
|
|
1666
|
+
self?.wayPointImageSource
|
|
1667
|
+
}) { [weak driveView, weak walkView, weak rideView] image in
|
|
1668
|
+
driveView?.setWayPointImage(image)
|
|
1669
|
+
walkView?.setWayPointImage(image)
|
|
1670
|
+
rideView?.setWayPointImage(image)
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
private func applyEndPointImageSource() {
|
|
1675
|
+
applyAnnotationImage(source: endPointImageSource, currentSource: { [weak self] in
|
|
1676
|
+
self?.endPointImageSource
|
|
1677
|
+
}) { [weak driveView, weak walkView, weak rideView] image in
|
|
1678
|
+
driveView?.setEndPointImage(image)
|
|
1679
|
+
walkView?.setEndPointImage(image)
|
|
1680
|
+
rideView?.setEndPointImage(image)
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
private func applyCameraImageSource() {
|
|
1685
|
+
guard let driveView else {
|
|
1686
|
+
return
|
|
1687
|
+
}
|
|
1688
|
+
applyAnnotationImage(source: cameraImageSource, currentSource: { [weak self] in
|
|
1689
|
+
self?.cameraImageSource
|
|
1690
|
+
}) { [weak driveView] image in
|
|
1691
|
+
driveView?.setCameraImage(image)
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
private func applyShowUIElementsToActiveViewIfReady() {
|
|
1696
|
+
guard currentNaviView() != nil else {
|
|
1697
|
+
pendingShowUIElements = showUIElements
|
|
1698
|
+
return
|
|
1699
|
+
}
|
|
1700
|
+
let value = pendingShowUIElements ?? showUIElements
|
|
1701
|
+
pendingShowUIElements = nil
|
|
1702
|
+
|
|
1703
|
+
switch activeScene {
|
|
1704
|
+
case .drive:
|
|
1705
|
+
(driveView as? ExpoGaodeMapCustomDriveView)?.suppressBottomRightUI = value == false
|
|
1706
|
+
driveView?.showUIElements = value
|
|
1707
|
+
case .walk:
|
|
1708
|
+
walkView?.showUIElements = value
|
|
1709
|
+
case .ride:
|
|
1710
|
+
rideView?.showUIElements = value
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
applyCustomUILayoutOptionsIfNeeded()
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
private func applyCustomUILayoutOptionsIfNeeded() {
|
|
1717
|
+
guard showUIElements == false else {
|
|
1718
|
+
(driveView as? ExpoGaodeMapCustomDriveView)?.suppressBottomRightUI = false
|
|
1719
|
+
return
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
switch activeScene {
|
|
1723
|
+
case .drive:
|
|
1724
|
+
guard let driveView else {
|
|
1725
|
+
return
|
|
1726
|
+
}
|
|
1727
|
+
(driveView as? ExpoGaodeMapCustomDriveView)?.suppressBottomRightUI = true
|
|
1728
|
+
driveView.showCrossImage = realCrossDisplay
|
|
1729
|
+
driveView.showTrafficBar = showTrafficBar
|
|
1730
|
+
applyTrafficBarFrame(trafficBarFrame)
|
|
1731
|
+
applyTrafficBarColors(trafficBarColors)
|
|
1732
|
+
driveView.screenAnchor = screenAnchor
|
|
1733
|
+
driveView.setNeedsLayout()
|
|
1734
|
+
driveView.layoutIfNeeded()
|
|
1735
|
+
case .walk:
|
|
1736
|
+
guard let walkView else {
|
|
1737
|
+
return
|
|
1738
|
+
}
|
|
1739
|
+
walkView.screenAnchor = screenAnchor
|
|
1740
|
+
walkView.setNeedsLayout()
|
|
1741
|
+
walkView.layoutIfNeeded()
|
|
1742
|
+
case .ride:
|
|
1743
|
+
guard let rideView else {
|
|
1744
|
+
return
|
|
1745
|
+
}
|
|
1746
|
+
rideView.screenAnchor = screenAnchor
|
|
1747
|
+
rideView.setNeedsLayout()
|
|
1748
|
+
rideView.layoutIfNeeded()
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
scheduleOverviewRouteVisibleRegionRefresh()
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
private func refreshOverviewRouteVisibleRegionIfNeeded() {
|
|
1755
|
+
guard showUIElements == false else {
|
|
1756
|
+
return
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
switch activeScene {
|
|
1760
|
+
case .drive:
|
|
1761
|
+
guard let driveView, driveView.showMode == .overview else {
|
|
1762
|
+
return
|
|
1763
|
+
}
|
|
1764
|
+
driveView.updateRoutePolylineInTheVisualRangeWhenTheShowModeIsOverview()
|
|
1765
|
+
case .walk:
|
|
1766
|
+
guard let walkView, walkView.showMode == .overview else {
|
|
1767
|
+
return
|
|
1768
|
+
}
|
|
1769
|
+
walkView.updateRoutePolylineInTheVisualRangeWhenTheShowModeIsOverview()
|
|
1770
|
+
case .ride:
|
|
1771
|
+
guard let rideView, rideView.showMode == .overview else {
|
|
1772
|
+
return
|
|
1773
|
+
}
|
|
1774
|
+
rideView.updateRoutePolylineInTheVisualRangeWhenTheShowModeIsOverview()
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
private func scheduleOverviewRouteVisibleRegionRefresh() {
|
|
1779
|
+
DispatchQueue.main.async { [weak self] in
|
|
1780
|
+
self?.currentNaviView()?.setNeedsLayout()
|
|
1781
|
+
self?.currentNaviView()?.layoutIfNeeded()
|
|
1782
|
+
self?.refreshOverviewRouteVisibleRegionIfNeeded()
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
private func applyHideNativeLaneInfoLayout(_ hidden: Bool) {
|
|
1787
|
+
(driveView as? ExpoGaodeMapCustomDriveView)?.suppressLaneInfoUI = hidden
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
private func applyHideNativeTopInfoLayout(_ hidden: Bool) {
|
|
1791
|
+
(driveView as? ExpoGaodeMapCustomDriveView)?.suppressTopInfoUI = hidden
|
|
1792
|
+
driveView?.setNeedsLayout()
|
|
1793
|
+
driveView?.layoutIfNeeded()
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
private func applyTrafficBarFrame(_ frame: CGRect) {
|
|
1797
|
+
guard let driveView else {
|
|
1798
|
+
return
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
guard showTrafficBar, frame.width > 0, frame.height > 0 else {
|
|
1802
|
+
driveView.tmcRouteFrame = .zero
|
|
1803
|
+
return
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
driveView.tmcRouteFrame = frame
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
private func applyTrafficBarColors(_ colors: [String: Any]?) {
|
|
1810
|
+
guard let driveView, let colors else {
|
|
1811
|
+
return
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
let mappings: [(String, AMapNaviRouteStatus)] = [
|
|
1815
|
+
("unknown", .unknow),
|
|
1816
|
+
("smooth", .smooth),
|
|
1817
|
+
("fineOpen", .fineOpen),
|
|
1818
|
+
("slow", .slow),
|
|
1819
|
+
("jam", .jam),
|
|
1820
|
+
("seriousJam", .seriousJam),
|
|
1821
|
+
("defaultRoad", .default),
|
|
1822
|
+
]
|
|
1823
|
+
|
|
1824
|
+
let resolvedColors = mappings.compactMap { key, status -> AMapNaviTMCStatusColor? in
|
|
1825
|
+
guard let value = colors[key], let color = ColorParser.parseColor(value) else {
|
|
1826
|
+
return nil
|
|
1827
|
+
}
|
|
1828
|
+
let item = AMapNaviTMCStatusColor()
|
|
1829
|
+
item.status = status
|
|
1830
|
+
item.color = color
|
|
1831
|
+
return item
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
guard !resolvedColors.isEmpty else {
|
|
1835
|
+
return
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
driveView.tmcRouteColor = resolvedColors
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
private func emitTrafficStatusesUpdate(_ trafficStatuses: [AMapNaviTrafficStatus]?) {
|
|
1842
|
+
let totalLengthFromTraffic = (trafficStatuses ?? []).reduce(0) { partial, status in
|
|
1843
|
+
partial + max(status.length, 0)
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
if totalLengthFromTraffic > 0 {
|
|
1847
|
+
if let retainDistance = lastNavigationInfoPayload?["pathRetainDistance"] as? Int {
|
|
1848
|
+
if totalLengthFromTraffic >= retainDistance {
|
|
1849
|
+
trafficBarTotalLength = totalLengthFromTraffic
|
|
1850
|
+
}
|
|
1851
|
+
} else {
|
|
1852
|
+
trafficBarTotalLength = totalLengthFromTraffic
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
// iOS 会提供 fineStatus;统一事件结构时保留它,方便 RN 自绘层按需细化颜色策略。
|
|
1857
|
+
let items = (trafficStatuses ?? []).map { status in
|
|
1858
|
+
var payload: [String: Any] = [
|
|
1859
|
+
"status": Int(status.status.rawValue),
|
|
1860
|
+
"length": status.length
|
|
1861
|
+
]
|
|
1862
|
+
payload["fineStatus"] = status.trafficFineStatus
|
|
1863
|
+
return payload
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
var payload: [String: Any] = [
|
|
1867
|
+
"items": items
|
|
1868
|
+
]
|
|
1869
|
+
|
|
1870
|
+
let resolvedTotalLength = max(trafficBarTotalLength ?? 0, currentRouteTotalLength ?? 0)
|
|
1871
|
+
if resolvedTotalLength > 0 {
|
|
1872
|
+
payload["totalLength"] = resolvedTotalLength
|
|
1873
|
+
} else if totalLengthFromTraffic > 0 {
|
|
1874
|
+
payload["totalLength"] = totalLengthFromTraffic
|
|
1875
|
+
} else if let totalLength = currentRouteTotalLength, totalLength > 0 {
|
|
1876
|
+
payload["totalLength"] = totalLength
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
if let retainDistance = lastNavigationInfoPayload?["pathRetainDistance"] as? Int {
|
|
1880
|
+
payload["retainDistance"] = retainDistance
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
onTrafficStatusesUpdate(payload)
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
// MARK: - Prop Setters
|
|
1887
|
+
private func applyShowCamera(_ show: Bool) {
|
|
1888
|
+
driveView?.showCamera = show
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
private func applyEnableVoice(_ enabled: Bool) {
|
|
1892
|
+
driveManager?.isUseInternalTTS = enabled
|
|
1893
|
+
walkManager?.isUseInternalTTS = enabled
|
|
1894
|
+
rideManager?.isUseInternalTTS = enabled
|
|
1895
|
+
if enabled {
|
|
1896
|
+
activateNavigationAudioSessionIfNeeded(reason: "enable_voice_true")
|
|
1897
|
+
} else {
|
|
1898
|
+
deactivateNavigationAudioSessionIfNeeded(reason: "enable_voice_false")
|
|
1899
|
+
}
|
|
1900
|
+
// 内置语音会自动播报,不需要手动调用 startSpeak/stopSpeak
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
private func applyAutoLockCar(_ enabled: Bool) {
|
|
1904
|
+
driveView?.autoSwitchShowModeToCarPositionLocked = enabled
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
private func applyAutoChangeZoom(_ enabled: Bool) {
|
|
1908
|
+
driveView?.autoZoomMapLevel = enabled
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
private func applyTrafficLayerEnabled(_ enabled: Bool) {
|
|
1912
|
+
driveView?.mapShowTraffic = enabled
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
private func applyRealCrossDisplay(_ enabled: Bool) {
|
|
1916
|
+
driveView?.showCrossImage = enabled
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
private func applyNaviMode(_ mode: Int) {
|
|
1920
|
+
let trackingMode: AMapNaviViewTrackingMode = mode == 0 ? .carNorth : .mapNorth
|
|
1921
|
+
switch activeScene {
|
|
1922
|
+
case .drive:
|
|
1923
|
+
driveView?.trackingMode = trackingMode
|
|
1924
|
+
case .walk:
|
|
1925
|
+
walkView?.trackingMode = trackingMode
|
|
1926
|
+
case .ride:
|
|
1927
|
+
rideView?.trackingMode = trackingMode
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
private func applyShowMode(_ mode: Int) {
|
|
1932
|
+
switch activeScene {
|
|
1933
|
+
case .drive:
|
|
1934
|
+
switch mode {
|
|
1935
|
+
case 1:
|
|
1936
|
+
driveView?.showMode = .carPositionLocked
|
|
1937
|
+
case 2:
|
|
1938
|
+
driveView?.showMode = .overview
|
|
1939
|
+
case 3:
|
|
1940
|
+
driveView?.showMode = .normal
|
|
1941
|
+
default:
|
|
1942
|
+
break
|
|
1943
|
+
}
|
|
1944
|
+
case .walk:
|
|
1945
|
+
switch mode {
|
|
1946
|
+
case 1:
|
|
1947
|
+
walkView?.showMode = .carPositionLocked
|
|
1948
|
+
case 2:
|
|
1949
|
+
walkView?.showMode = .overview
|
|
1950
|
+
case 3:
|
|
1951
|
+
walkView?.showMode = .normal
|
|
1952
|
+
default:
|
|
1953
|
+
break
|
|
1954
|
+
}
|
|
1955
|
+
case .ride:
|
|
1956
|
+
switch mode {
|
|
1957
|
+
case 1:
|
|
1958
|
+
rideView?.showMode = .carPositionLocked
|
|
1959
|
+
case 2:
|
|
1960
|
+
rideView?.showMode = .overview
|
|
1961
|
+
case 3:
|
|
1962
|
+
rideView?.showMode = .normal
|
|
1963
|
+
default:
|
|
1964
|
+
break
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
scheduleOverviewRouteVisibleRegionRefresh()
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
private func applyNightMode(_ enabled: Bool) {
|
|
1971
|
+
applyMapViewModeType(enabled ? 1 : 0)
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
private func applyMapViewModeType(_ type: Int) {
|
|
1975
|
+
let resolvedMode: AMapNaviViewMapModeType
|
|
1976
|
+
switch type {
|
|
1977
|
+
case 0:
|
|
1978
|
+
resolvedMode = .day
|
|
1979
|
+
case 1:
|
|
1980
|
+
resolvedMode = .night
|
|
1981
|
+
case 2:
|
|
1982
|
+
resolvedMode = .dayNightAuto
|
|
1983
|
+
case 3:
|
|
1984
|
+
resolvedMode = .custom
|
|
1985
|
+
default:
|
|
1986
|
+
return
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
switch activeScene {
|
|
1990
|
+
case .drive:
|
|
1991
|
+
driveView?.mapViewModeType = resolvedMode
|
|
1992
|
+
case .walk:
|
|
1993
|
+
walkView?.mapViewModeType = resolvedMode
|
|
1994
|
+
case .ride:
|
|
1995
|
+
rideView?.mapViewModeType = resolvedMode
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
private func applyShowTurnArrow(_ visible: Bool) {
|
|
2000
|
+
switch activeScene {
|
|
2001
|
+
case .drive:
|
|
2002
|
+
driveView?.showTurnArrow = visible
|
|
2003
|
+
case .walk:
|
|
2004
|
+
walkView?.showTurnArrow = visible
|
|
2005
|
+
case .ride:
|
|
2006
|
+
rideView?.showTurnArrow = visible
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
private func applyShowBrowseRouteButton(_ visible: Bool) {
|
|
2011
|
+
switch activeScene {
|
|
2012
|
+
case .drive:
|
|
2013
|
+
driveView?.showBrowseRouteButton = visible
|
|
2014
|
+
case .walk:
|
|
2015
|
+
walkView?.showBrowseRouteButton = visible
|
|
2016
|
+
case .ride:
|
|
2017
|
+
rideView?.showBrowseRouteButton = visible
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
private func applyShowMoreButton(_ visible: Bool) {
|
|
2022
|
+
switch activeScene {
|
|
2023
|
+
case .drive:
|
|
2024
|
+
driveView?.showMoreButton = visible
|
|
2025
|
+
case .walk:
|
|
2026
|
+
walkView?.showMoreButton = visible
|
|
2027
|
+
case .ride:
|
|
2028
|
+
rideView?.showMoreButton = visible
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
private func applyShowGreyAfterPass(_ enabled: Bool) {
|
|
2033
|
+
switch activeScene {
|
|
2034
|
+
case .drive:
|
|
2035
|
+
driveView?.showGreyAfterPass = enabled
|
|
2036
|
+
case .walk:
|
|
2037
|
+
walkView?.showGreyAfterPass = enabled
|
|
2038
|
+
case .ride:
|
|
2039
|
+
rideView?.showGreyAfterPass = enabled
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
private func applyShowCompassEnabled(_ enabled: Bool) {
|
|
2044
|
+
switch activeScene {
|
|
2045
|
+
case .drive:
|
|
2046
|
+
driveView?.showCompass = enabled
|
|
2047
|
+
case .walk:
|
|
2048
|
+
walkView?.showCompass = enabled
|
|
2049
|
+
case .ride:
|
|
2050
|
+
rideView?.showCompass = enabled
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
private func applyLineWidth(_ width: CGFloat) {
|
|
2055
|
+
switch activeScene {
|
|
2056
|
+
case .drive:
|
|
2057
|
+
driveView?.lineWidth = width
|
|
2058
|
+
case .walk:
|
|
2059
|
+
walkView?.lineWidth = width
|
|
2060
|
+
case .ride:
|
|
2061
|
+
rideView?.lineWidth = width
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
private func applyScreenAnchor(_ anchor: CGPoint) {
|
|
2066
|
+
switch activeScene {
|
|
2067
|
+
case .drive:
|
|
2068
|
+
driveView?.screenAnchor = anchor
|
|
2069
|
+
case .walk:
|
|
2070
|
+
walkView?.screenAnchor = anchor
|
|
2071
|
+
case .ride:
|
|
2072
|
+
rideView?.screenAnchor = anchor
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
private func applyBackgroundLocationOptionsForActiveScene() {
|
|
2077
|
+
switch activeScene {
|
|
2078
|
+
case .drive:
|
|
2079
|
+
applyDriveManagerBackgroundLocationOptionsIfNeeded()
|
|
2080
|
+
case .walk:
|
|
2081
|
+
applyWalkManagerBackgroundLocationOptionsIfNeeded()
|
|
2082
|
+
case .ride:
|
|
2083
|
+
applyRideManagerBackgroundLocationOptionsIfNeeded()
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
private func handleNavigationStarted(_ naviMode: AMapNaviMode) {
|
|
2088
|
+
hasStartedNavi = true
|
|
2089
|
+
applyBackgroundLocationOptionsForActiveScene()
|
|
2090
|
+
activateNavigationAudioSessionIfNeeded(reason: "did_start_navi")
|
|
2091
|
+
onNavigationStarted([
|
|
2092
|
+
"type": naviMode == .emulator ? 1 : 0,
|
|
2093
|
+
"isEmulator": naviMode == .emulator
|
|
2094
|
+
])
|
|
2095
|
+
applyShowUIElementsToActiveViewIfReady()
|
|
2096
|
+
refreshCustomWaypointAnnotations()
|
|
2097
|
+
syncNavigationLiveActivityWithLastPayload()
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
private func handleNavigationLocationUpdate(_ naviLocation: AMapNaviLocation) {
|
|
2101
|
+
if !hasReceivedFirstNaviData {
|
|
2102
|
+
hasReceivedFirstNaviData = true
|
|
2103
|
+
applyShowUIElementsToActiveViewIfReady()
|
|
2104
|
+
}
|
|
2105
|
+
lastKnownSpeed = Int(naviLocation.speed)
|
|
2106
|
+
onLocationUpdate([
|
|
2107
|
+
"latitude": naviLocation.coordinate.latitude,
|
|
2108
|
+
"longitude": naviLocation.coordinate.longitude,
|
|
2109
|
+
"speed": naviLocation.speed,
|
|
2110
|
+
"bearing": naviLocation.heading
|
|
2111
|
+
])
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
private func navigationInfoPayload(from naviInfo: AMapNaviInfo) -> [String: Any] {
|
|
2115
|
+
var payload: [String: Any] = [
|
|
2116
|
+
"naviMode": naviInfo.naviMode.rawValue,
|
|
2117
|
+
"currentRoadName": naviInfo.currentRoadName ?? "",
|
|
2118
|
+
"nextRoadName": naviInfo.nextRoadName ?? "",
|
|
2119
|
+
"pathRetainDistance": naviInfo.routeRemainDistance,
|
|
2120
|
+
"pathRetainTime": naviInfo.routeRemainTime,
|
|
2121
|
+
"curStepRetainDistance": naviInfo.segmentRemainDistance,
|
|
2122
|
+
"curStepRetainTime": naviInfo.segmentRemainTime,
|
|
2123
|
+
"currentSpeed": lastKnownSpeed,
|
|
2124
|
+
"iconType": naviInfo.iconType.rawValue,
|
|
2125
|
+
"iconDirection": naviInfo.iconType.rawValue,
|
|
2126
|
+
"currentSegmentIndex": naviInfo.currentSegmentIndex,
|
|
2127
|
+
"currentLinkIndex": naviInfo.currentLinkIndex,
|
|
2128
|
+
"currentPointIndex": naviInfo.currentPointIndex,
|
|
2129
|
+
"routeRemainTrafficLightCount": naviInfo.routeRemainTrafficLightCount,
|
|
2130
|
+
"driveDistance": naviInfo.routeDriveDistance,
|
|
2131
|
+
"driveTime": naviInfo.routeDriveTime
|
|
2132
|
+
]
|
|
2133
|
+
|
|
2134
|
+
if let nextIconType = resolveNextTurnIconType(currentSegmentIndex: naviInfo.currentSegmentIndex) {
|
|
2135
|
+
payload["nextIconType"] = nextIconType
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
return payload
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
private func handleNavigationInfoUpdate(_ naviInfo: AMapNaviInfo) {
|
|
2142
|
+
if !hasReceivedFirstNaviData {
|
|
2143
|
+
hasReceivedFirstNaviData = true
|
|
2144
|
+
applyShowUIElementsToActiveViewIfReady()
|
|
2145
|
+
}
|
|
2146
|
+
updateCachedTurnIconForTravelSceneIfNeeded(iconType: Int(naviInfo.iconType.rawValue))
|
|
2147
|
+
emitNavigationInfoUpdate(navigationInfoPayload(from: naviInfo))
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
private func resolveNextTurnIconType(currentSegmentIndex: Int) -> Int? {
|
|
2151
|
+
guard currentSegmentIndex >= 0 else {
|
|
2152
|
+
return nil
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
guard let routeSegments = currentNaviRoute?.routeSegments else {
|
|
2156
|
+
return nil
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
let nextSegmentIndex = currentSegmentIndex + 1
|
|
2160
|
+
guard routeSegments.indices.contains(nextSegmentIndex) else {
|
|
2161
|
+
return nil
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
let nextIconType = Int(routeSegments[nextSegmentIndex].iconType.rawValue)
|
|
2165
|
+
return nextIconType > 0 ? nextIconType : nil
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
private func handleNavigationSound(_ soundString: String, soundStringType: AMapNaviSoundType) {
|
|
2169
|
+
activateNavigationAudioSessionIfNeeded(reason: "play_navi_sound")
|
|
2170
|
+
onNavigationText([
|
|
2171
|
+
"type": soundStringType.rawValue,
|
|
2172
|
+
"text": soundString
|
|
2173
|
+
])
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
private func handleGpsSignalUpdate(_ gpsSignalStrength: AMapNaviGPSSignalStrength) {
|
|
2177
|
+
let rawValue = gpsSignalStrength.rawValue
|
|
2178
|
+
let isWeak = rawValue == AMapNaviGPSSignalStrength.weak.rawValue || rawValue == 0
|
|
2179
|
+
onGpsSignalWeak([
|
|
2180
|
+
"isWeak": isWeak
|
|
2181
|
+
])
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
private func handleWayPointArrived(index: Int, point: AMapNaviPoint? = nil) {
|
|
2185
|
+
if let point {
|
|
2186
|
+
markNearestCustomWaypointArrived(point)
|
|
2187
|
+
}
|
|
2188
|
+
onWayPointArrived([
|
|
2189
|
+
"index": index
|
|
2190
|
+
])
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
private func handleDidEndEmulatorNavi(reason: String) {
|
|
2194
|
+
resetTransientNavigationState()
|
|
2195
|
+
deactivateNavigationAudioSessionIfNeeded(reason: reason)
|
|
2196
|
+
onNavigationEnded([:])
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
// MARK: - Public Methods
|
|
2200
|
+
func startNavigation(startLat: Double, startLng: Double, endLat: Double, endLng: Double, promise: Promise) {
|
|
404
2201
|
// 再次检查隐私状态和初始化状态
|
|
405
2202
|
do {
|
|
406
2203
|
try checkPrivacyReady()
|
|
@@ -419,7 +2216,10 @@ public class ExpoGaodeMapNaviView: ExpoView {
|
|
|
419
2216
|
promise.reject(code, formatError(error))
|
|
420
2217
|
return
|
|
421
2218
|
}
|
|
422
|
-
|
|
2219
|
+
|
|
2220
|
+
switchToScene(.drive)
|
|
2221
|
+
resetTransientNavigationState()
|
|
2222
|
+
|
|
423
2223
|
startCoordinate = AMapNaviPoint.location(withLatitude: CGFloat(startLat), longitude: CGFloat(startLng))
|
|
424
2224
|
endCoordinate = AMapNaviPoint.location(withLatitude: CGFloat(endLat), longitude: CGFloat(endLng))
|
|
425
2225
|
|
|
@@ -446,9 +2246,69 @@ public class ExpoGaodeMapNaviView: ExpoView {
|
|
|
446
2246
|
promise.reject("CALCULATE_FAILED", "启动路线规划失败")
|
|
447
2247
|
}
|
|
448
2248
|
}
|
|
2249
|
+
|
|
2250
|
+
func startNavigationWithIndependentPath(
|
|
2251
|
+
token: Int,
|
|
2252
|
+
routeId: Int?,
|
|
2253
|
+
routeIndex: Int?,
|
|
2254
|
+
naviType: Int?,
|
|
2255
|
+
promise: Promise
|
|
2256
|
+
) {
|
|
2257
|
+
do {
|
|
2258
|
+
try checkPrivacyReady()
|
|
2259
|
+
try checkAMapInitialization()
|
|
2260
|
+
try ensureBackgroundLocationModeForNavigation()
|
|
2261
|
+
} catch {
|
|
2262
|
+
let nsError = error as NSError
|
|
2263
|
+
let code: String
|
|
2264
|
+
if nsError.domain == "ExpoGaodeMapPrivacy" {
|
|
2265
|
+
code = "PRIVACY_NOT_AGREED"
|
|
2266
|
+
} else if nsError.code == -1003 {
|
|
2267
|
+
code = "BACKGROUND_LOCATION_NOT_ENABLED"
|
|
2268
|
+
} else {
|
|
2269
|
+
code = "AMAP_NOT_INITIALIZED"
|
|
2270
|
+
}
|
|
2271
|
+
promise.reject(code, formatError(error))
|
|
2272
|
+
return
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
let scene = NaviScene(rawValue: independentRouteManager.scene(for: token) ?? "") ?? .drive
|
|
2276
|
+
switchToScene(scene)
|
|
2277
|
+
resetTransientNavigationState()
|
|
2278
|
+
|
|
2279
|
+
if let naviType {
|
|
2280
|
+
self.naviType = naviType
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
let ok = independentRouteManager.start(
|
|
2284
|
+
token: token,
|
|
2285
|
+
naviType: self.naviType,
|
|
2286
|
+
routeId: routeId,
|
|
2287
|
+
routeIndex: routeIndex
|
|
2288
|
+
)
|
|
2289
|
+
|
|
2290
|
+
if ok {
|
|
2291
|
+
promise.resolve([
|
|
2292
|
+
"success": true,
|
|
2293
|
+
"message": "独立路径导航启动中...",
|
|
2294
|
+
"token": token,
|
|
2295
|
+
"naviType": self.naviType
|
|
2296
|
+
])
|
|
2297
|
+
} else {
|
|
2298
|
+
promise.reject("START_INDEPENDENT_NAVI_FAILED", "独立路径导航启动失败")
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
449
2301
|
|
|
450
2302
|
func stopNavigation(promise: Promise) {
|
|
451
|
-
|
|
2303
|
+
switch activeScene {
|
|
2304
|
+
case .drive:
|
|
2305
|
+
driveManager?.stopNavi()
|
|
2306
|
+
case .walk:
|
|
2307
|
+
walkManager?.stopNavi()
|
|
2308
|
+
case .ride:
|
|
2309
|
+
rideManager?.stopNavi()
|
|
2310
|
+
}
|
|
2311
|
+
resetTransientNavigationState()
|
|
452
2312
|
promise.resolve([
|
|
453
2313
|
"success": true,
|
|
454
2314
|
"message": "导航已停止"
|
|
@@ -456,7 +2316,21 @@ public class ExpoGaodeMapNaviView: ExpoView {
|
|
|
456
2316
|
}
|
|
457
2317
|
|
|
458
2318
|
func playCustomTTS(text: String, forcePlay: Bool, promise: Promise) {
|
|
459
|
-
|
|
2319
|
+
guard currentNaviManager() != nil else {
|
|
2320
|
+
promise.reject("NAVI_MANAGER_UNAVAILABLE", "导航管理器尚未初始化")
|
|
2321
|
+
return
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
if forcePlay {
|
|
2325
|
+
customSpeechSynthesizer.stopSpeaking(at: .immediate)
|
|
2326
|
+
} else if customSpeechSynthesizer.isSpeaking {
|
|
2327
|
+
promise.reject("PLAY_TTS_FAILED", "当前正在播报其他导航语音")
|
|
2328
|
+
return
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2331
|
+
let utterance = AVSpeechUtterance(string: text)
|
|
2332
|
+
utterance.voice = AVSpeechSynthesisVoice(language: "zh-CN")
|
|
2333
|
+
customSpeechSynthesizer.speak(utterance)
|
|
460
2334
|
promise.resolve([
|
|
461
2335
|
"success": true
|
|
462
2336
|
])
|
|
@@ -466,22 +2340,63 @@ public class ExpoGaodeMapNaviView: ExpoView {
|
|
|
466
2340
|
public override func layoutSubviews() {
|
|
467
2341
|
super.layoutSubviews()
|
|
468
2342
|
driveView?.frame = bounds
|
|
2343
|
+
walkView?.frame = bounds
|
|
2344
|
+
rideView?.frame = bounds
|
|
2345
|
+
scheduleOverviewRouteVisibleRegionRefresh()
|
|
469
2346
|
}
|
|
470
2347
|
|
|
471
2348
|
deinit {
|
|
2349
|
+
NavigationLiveActivityManager.shared.stop()
|
|
2350
|
+
deactivateNavigationAudioSessionIfNeeded(reason: "deinit")
|
|
472
2351
|
driveManager?.stopNavi()
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
2352
|
+
walkManager?.stopNavi()
|
|
2353
|
+
rideManager?.stopNavi()
|
|
2354
|
+
clearCustomWaypointAnnotations()
|
|
2355
|
+
destroyDriveManagerIfNeeded()
|
|
2356
|
+
destroyWalkManagerIfNeeded()
|
|
2357
|
+
destroyRideManagerIfNeeded()
|
|
2358
|
+
clearCachedTurnIconUris()
|
|
477
2359
|
}
|
|
478
2360
|
}
|
|
479
2361
|
|
|
480
2362
|
// MARK: - AMapNaviDriveManagerDelegate
|
|
481
2363
|
extension ExpoGaodeMapNaviView: AMapNaviDriveManagerDelegate {
|
|
2364
|
+
private func makeArrivedLiveActivitySnapshot() -> NavigationLiveActivitySnapshot {
|
|
2365
|
+
let remainDistance = payloadIntValue(lastNavigationInfoPayload?["pathRetainDistance"]) ?? 0
|
|
2366
|
+
let routeTotalDistance = max(
|
|
2367
|
+
trafficBarTotalLength ?? 0,
|
|
2368
|
+
currentRouteTotalLength ?? 0,
|
|
2369
|
+
remainDistance
|
|
2370
|
+
)
|
|
2371
|
+
return NavigationLiveActivitySnapshot(
|
|
2372
|
+
appName: resolveAppDisplayName(),
|
|
2373
|
+
currentRoadName: "",
|
|
2374
|
+
nextRoadName: "",
|
|
2375
|
+
pathRetainDistance: 0,
|
|
2376
|
+
routeTotalDistance: routeTotalDistance,
|
|
2377
|
+
pathRetainTime: 0,
|
|
2378
|
+
curStepRetainDistance: 0,
|
|
2379
|
+
iconType: 15,
|
|
2380
|
+
turnIconBase64: nil
|
|
2381
|
+
)
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
private func handleArrivedDestination(source: String) {
|
|
2385
|
+
NSLog("[ExpoGaodeMapNaviView][LiveActivity] arrived destination callback received: %@", source)
|
|
2386
|
+
hasStartedNavi = false
|
|
2387
|
+
if iosLiveActivityEnabled {
|
|
2388
|
+
let arrivedSnapshot = makeArrivedLiveActivitySnapshot()
|
|
2389
|
+
NavigationLiveActivityManager.shared.showArrivedAndStop(snapshot: arrivedSnapshot, dismissAfter: 6)
|
|
2390
|
+
} else {
|
|
2391
|
+
NavigationLiveActivityManager.shared.stop()
|
|
2392
|
+
}
|
|
2393
|
+
onArriveDestination([:])
|
|
2394
|
+
}
|
|
482
2395
|
|
|
483
2396
|
public func driveManager(onCalculateRouteSuccess driveManager: AMapNaviDriveManager) {
|
|
484
2397
|
hasStartedNavi = true
|
|
2398
|
+
applyDriveManagerBackgroundLocationOptionsIfNeeded()
|
|
2399
|
+
activateNavigationAudioSessionIfNeeded(reason: "calculate_route_success")
|
|
485
2400
|
onRouteCalculated([
|
|
486
2401
|
"success": true,
|
|
487
2402
|
"naviType": naviType
|
|
@@ -494,7 +2409,11 @@ extension ExpoGaodeMapNaviView: AMapNaviDriveManagerDelegate {
|
|
|
494
2409
|
driveManager.startGPSNavi()
|
|
495
2410
|
}
|
|
496
2411
|
|
|
497
|
-
|
|
2412
|
+
applyShowUIElementsToActiveViewIfReady()
|
|
2413
|
+
DispatchQueue.main.async { [weak self] in
|
|
2414
|
+
self?.applyCustomAnnotationImages()
|
|
2415
|
+
self?.refreshCustomWaypointAnnotations()
|
|
2416
|
+
}
|
|
498
2417
|
}
|
|
499
2418
|
|
|
500
2419
|
public func driveManager(_ driveManager: AMapNaviDriveManager, onCalculateRouteFailure error: Error) {
|
|
@@ -505,81 +2424,47 @@ extension ExpoGaodeMapNaviView: AMapNaviDriveManagerDelegate {
|
|
|
505
2424
|
}
|
|
506
2425
|
|
|
507
2426
|
public func driveManager(_ driveManager: AMapNaviDriveManager, didStartNavi naviMode: AMapNaviMode) {
|
|
508
|
-
|
|
509
|
-
onNavigationStarted([
|
|
510
|
-
"type": naviMode == .emulator ? 1 : 0,
|
|
511
|
-
"isEmulator": naviMode == .emulator
|
|
512
|
-
])
|
|
513
|
-
|
|
514
|
-
applyShowUIElementsToDriveViewIfReady()
|
|
2427
|
+
handleNavigationStarted(naviMode)
|
|
515
2428
|
}
|
|
516
2429
|
|
|
517
2430
|
public func driveManagerNavi(_ driveManager: AMapNaviDriveManager, didArrive wayPoint: AMapNaviPoint) {
|
|
518
|
-
|
|
519
|
-
"index": 0
|
|
520
|
-
])
|
|
2431
|
+
handleWayPointArrived(index: 0, point: wayPoint)
|
|
521
2432
|
}
|
|
522
2433
|
|
|
523
2434
|
public func driveManagerDidEndEmulatorNavi(_ driveManager: AMapNaviDriveManager) {
|
|
524
|
-
|
|
525
|
-
}
|
|
526
|
-
|
|
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
|
-
])
|
|
2435
|
+
handleDidEndEmulatorNavi(reason: "did_end_emulator_navi")
|
|
553
2436
|
}
|
|
554
2437
|
|
|
555
2438
|
public func driveManager(_ driveManager: AMapNaviDriveManager, playNaviSound soundString: String, soundStringType: AMapNaviSoundType) {
|
|
556
|
-
|
|
557
|
-
"type": soundStringType.rawValue,
|
|
558
|
-
"text": soundString
|
|
559
|
-
])
|
|
2439
|
+
handleNavigationSound(soundString, soundStringType: soundStringType)
|
|
560
2440
|
}
|
|
561
2441
|
|
|
2442
|
+
/// 兼容一部分 SDK/Swift 导入下的到达终点回调签名
|
|
562
2443
|
public func driveManager(onArrivedDestination driveManager: AMapNaviDriveManager) {
|
|
563
|
-
|
|
2444
|
+
handleArrivedDestination(source: "driveManager(onArrivedDestination:)")
|
|
2445
|
+
deactivateNavigationAudioSessionIfNeeded(reason: "arrived_destination_named")
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
/// AMapNaviDriveManagerDelegate 官方到达终点回调
|
|
2449
|
+
public func driveManagerOnArrivedDestination(_ driveManager: AMapNaviDriveManager) {
|
|
2450
|
+
handleArrivedDestination(source: "driveManagerOnArrivedDestination")
|
|
2451
|
+
deactivateNavigationAudioSessionIfNeeded(reason: "arrived_destination_official")
|
|
564
2452
|
}
|
|
565
2453
|
|
|
566
|
-
public func
|
|
2454
|
+
public func driveManagerNeedRecalculateRoute(forYaw driveManager: AMapNaviDriveManager) {
|
|
567
2455
|
onRouteRecalculate([
|
|
568
2456
|
"reason": "yaw"
|
|
569
2457
|
])
|
|
570
2458
|
}
|
|
571
2459
|
|
|
572
|
-
public func
|
|
2460
|
+
public func driveManagerNeedRecalculateRoute(forTrafficJam driveManager: AMapNaviDriveManager) {
|
|
573
2461
|
onRouteRecalculate([
|
|
574
2462
|
"reason": "traffic"
|
|
575
2463
|
])
|
|
576
2464
|
}
|
|
577
2465
|
|
|
578
2466
|
public func driveManager(_ driveManager: AMapNaviDriveManager, update gpsSignalStrength: AMapNaviGPSSignalStrength) {
|
|
579
|
-
|
|
580
|
-
onGpsSignalWeak([
|
|
581
|
-
"isWeak": isWeak
|
|
582
|
-
])
|
|
2467
|
+
handleGpsSignalUpdate(gpsSignalStrength)
|
|
583
2468
|
}
|
|
584
2469
|
}
|
|
585
2470
|
|
|
@@ -587,13 +2472,258 @@ extension ExpoGaodeMapNaviView: AMapNaviDriveManagerDelegate {
|
|
|
587
2472
|
extension ExpoGaodeMapNaviView: AMapNaviDriveViewDelegate {
|
|
588
2473
|
|
|
589
2474
|
public func driveViewDidLoad(_ driveView: AMapNaviDriveView) {
|
|
590
|
-
driveViewLoaded = true
|
|
591
2475
|
applyViewOptions()
|
|
592
|
-
|
|
2476
|
+
applyShowUIElementsToActiveViewIfReady()
|
|
2477
|
+
refreshCustomWaypointAnnotations()
|
|
2478
|
+
scheduleOverviewRouteVisibleRegionRefresh()
|
|
593
2479
|
onNavigationReady([:])
|
|
594
2480
|
}
|
|
595
2481
|
|
|
596
2482
|
public func driveViewEdgePadding(_ driveView: AMapNaviDriveView) -> UIEdgeInsets {
|
|
597
|
-
return
|
|
2483
|
+
return driveViewEdgePadding
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
public func driveView(_ driveView: AMapNaviDriveView, didChange showMode: AMapNaviDriveViewShowMode) {
|
|
2487
|
+
if showMode == .overview {
|
|
2488
|
+
scheduleOverviewRouteVisibleRegionRefresh()
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
extension ExpoGaodeMapNaviView: AMapNaviDriveDataRepresentable {
|
|
2494
|
+
public func driveManager(_ driveManager: AMapNaviDriveManager, update naviRoute: AMapNaviRoute?) {
|
|
2495
|
+
currentNaviRoute = naviRoute
|
|
2496
|
+
if let routeLength = naviRoute?.routeLength, routeLength > 0 {
|
|
2497
|
+
currentRouteTotalLength = routeLength
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
|
|
2501
|
+
public func driveManager(_ driveManager: AMapNaviDriveManager, update naviLocation: AMapNaviLocation?) {
|
|
2502
|
+
guard let naviLocation else {
|
|
2503
|
+
return
|
|
2504
|
+
}
|
|
2505
|
+
handleNavigationLocationUpdate(naviLocation)
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
public func driveManager(_ driveManager: AMapNaviDriveManager, update naviInfo: AMapNaviInfo?) {
|
|
2509
|
+
guard let naviInfo else {
|
|
2510
|
+
return
|
|
2511
|
+
}
|
|
2512
|
+
handleNavigationInfoUpdate(naviInfo)
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
public func driveManager(_ driveManager: AMapNaviDriveManager, showCross crossImage: UIImage?) {
|
|
2516
|
+
isCrossVisible = true
|
|
2517
|
+
emitVisualStateUpdate()
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
public func driveManagerHideCrossImage(_ driveManager: AMapNaviDriveManager) {
|
|
2521
|
+
isCrossVisible = false
|
|
2522
|
+
emitVisualStateUpdate()
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
public func driveManager(_ driveManager: AMapNaviDriveManager, showLaneBackInfo laneBackInfo: String, laneSelectInfo: String) {
|
|
2526
|
+
isLaneInfoVisible = true
|
|
2527
|
+
emitVisualStateUpdate()
|
|
2528
|
+
if let payload = serializeLaneInfo(laneBackInfo: laneBackInfo, laneSelectInfo: laneSelectInfo) {
|
|
2529
|
+
onLaneInfoUpdate(payload)
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
public func driveManagerHideLaneInfo(_ driveManager: AMapNaviDriveManager) {
|
|
2534
|
+
isLaneInfoVisible = false
|
|
2535
|
+
emitVisualStateUpdate()
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
public func driveManager(_ driveManager: AMapNaviDriveManager, updateTrafficStatus trafficStatus: [AMapNaviTrafficStatus]?) {
|
|
2539
|
+
emitTrafficStatusesUpdate(trafficStatus)
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
public func driveManager(_ driveManager: AMapNaviDriveManager, updateTurnIconImage turnIconImage: UIImage?, turn turnIconType: AMapNaviIconType) {
|
|
2543
|
+
lastTurnIconType = Int(turnIconType.rawValue)
|
|
2544
|
+
let encodedTurnIcon = encodeTurnIconForLiveActivity(turnIconImage)
|
|
2545
|
+
if let encodedTurnIcon {
|
|
2546
|
+
lastTurnIconBase64 = encodedTurnIcon
|
|
2547
|
+
} else if turnIconImage == nil {
|
|
2548
|
+
NSLog(
|
|
2549
|
+
"[ExpoGaodeMapNaviView][LiveActivity] turnIconImage is nil, keep previous turn icon snapshot"
|
|
2550
|
+
)
|
|
2551
|
+
}
|
|
2552
|
+
NSLog(
|
|
2553
|
+
"[ExpoGaodeMapNaviView][LiveActivity] turnIconType=%d, incomingBase64Length=%d, effectiveBase64Length=%d",
|
|
2554
|
+
Int(turnIconType.rawValue),
|
|
2555
|
+
encodedTurnIcon?.count ?? 0,
|
|
2556
|
+
lastTurnIconBase64?.count ?? 0
|
|
2557
|
+
)
|
|
2558
|
+
lastTurnIconImageUri = cacheTurnIconImage(
|
|
2559
|
+
turnIconImage,
|
|
2560
|
+
prefix: "turn_icon",
|
|
2561
|
+
previousUri: lastTurnIconImageUri
|
|
2562
|
+
)
|
|
2563
|
+
reemitLastNavigationInfoIfNeeded()
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
public func driveManager(_ driveManager: AMapNaviDriveManager, updateNextTurnIconImage turnIconImage: UIImage?, nextTurn turnIconType: AMapNaviIconType) {
|
|
2567
|
+
lastNextTurnIconType = Int(turnIconType.rawValue)
|
|
2568
|
+
lastNextTurnIconImageUri = cacheTurnIconImage(
|
|
2569
|
+
turnIconImage,
|
|
2570
|
+
prefix: "next_turn_icon",
|
|
2571
|
+
previousUri: lastNextTurnIconImageUri
|
|
2572
|
+
)
|
|
2573
|
+
reemitLastNavigationInfoIfNeeded()
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
// MARK: - AMapNaviWalkManagerDelegate
|
|
2578
|
+
extension ExpoGaodeMapNaviView: AMapNaviWalkManagerDelegate {
|
|
2579
|
+
public func walkManager(_ walkManager: AMapNaviWalkManager, didStartNavi naviMode: AMapNaviMode) {
|
|
2580
|
+
handleNavigationStarted(naviMode)
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
public func walkManagerDidEndEmulatorNavi(_ walkManager: AMapNaviWalkManager) {
|
|
2584
|
+
handleDidEndEmulatorNavi(reason: "walk_did_end_emulator_navi")
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
public func walkManager(_ walkManager: AMapNaviWalkManager, playNaviSound soundString: String, soundStringType: AMapNaviSoundType) {
|
|
2588
|
+
handleNavigationSound(soundString, soundStringType: soundStringType)
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
public func walkManager(onArrivedDestination walkManager: AMapNaviWalkManager) {
|
|
2592
|
+
handleArrivedDestination(source: "walkManagerOnArrivedDestination")
|
|
2593
|
+
deactivateNavigationAudioSessionIfNeeded(reason: "walk_arrived_destination")
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
public func walkManagerNeedRecalculateRoute(forYaw walkManager: AMapNaviWalkManager) {
|
|
2597
|
+
onRouteRecalculate([
|
|
2598
|
+
"reason": "yaw"
|
|
2599
|
+
])
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
public func walkManager(_ walkManager: AMapNaviWalkManager, update gpsSignalStrength: AMapNaviGPSSignalStrength) {
|
|
2603
|
+
handleGpsSignalUpdate(gpsSignalStrength)
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
public func walkManager(_ walkManager: AMapNaviWalkManager, onArrivedWayPoint wayPointIndex: Int32) {
|
|
2607
|
+
handleWayPointArrived(index: Int(wayPointIndex))
|
|
2608
|
+
}
|
|
2609
|
+
|
|
2610
|
+
public func walkManager(_ walkManager: AMapNaviWalkManager, onCalculateRouteFailure error: Error) {
|
|
2611
|
+
onRouteCalculated([
|
|
2612
|
+
"success": false,
|
|
2613
|
+
"errorInfo": error.localizedDescription
|
|
2614
|
+
])
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
// MARK: - AMapNaviWalkViewDelegate
|
|
2619
|
+
extension ExpoGaodeMapNaviView: AMapNaviWalkViewDelegate {
|
|
2620
|
+
public func walkViewEdgePadding(_ walkView: AMapNaviWalkView) -> UIEdgeInsets {
|
|
2621
|
+
driveViewEdgePadding
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
public func walkView(_ walkView: AMapNaviWalkView, didChange showMode: AMapNaviWalkViewShowMode) {
|
|
2625
|
+
if showMode == .overview {
|
|
2626
|
+
scheduleOverviewRouteVisibleRegionRefresh()
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
extension ExpoGaodeMapNaviView: AMapNaviWalkDataRepresentable {
|
|
2632
|
+
public func walkManager(_ walkManager: AMapNaviWalkManager, update naviRoute: AMapNaviRoute?) {
|
|
2633
|
+
currentNaviRoute = naviRoute
|
|
2634
|
+
if let routeLength = naviRoute?.routeLength, routeLength > 0 {
|
|
2635
|
+
currentRouteTotalLength = routeLength
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
public func walkManager(_ walkManager: AMapNaviWalkManager, update naviLocation: AMapNaviLocation?) {
|
|
2640
|
+
guard let naviLocation else {
|
|
2641
|
+
return
|
|
2642
|
+
}
|
|
2643
|
+
handleNavigationLocationUpdate(naviLocation)
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
public func walkManager(_ walkManager: AMapNaviWalkManager, update naviInfo: AMapNaviInfo?) {
|
|
2647
|
+
guard let naviInfo else {
|
|
2648
|
+
return
|
|
2649
|
+
}
|
|
2650
|
+
handleNavigationInfoUpdate(naviInfo)
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
// MARK: - AMapNaviRideManagerDelegate
|
|
2655
|
+
extension ExpoGaodeMapNaviView: AMapNaviRideManagerDelegate {
|
|
2656
|
+
public func rideManager(_ rideManager: AMapNaviRideManager, didStartNavi naviMode: AMapNaviMode) {
|
|
2657
|
+
handleNavigationStarted(naviMode)
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
public func rideManagerDidEndEmulatorNavi(_ rideManager: AMapNaviRideManager) {
|
|
2661
|
+
handleDidEndEmulatorNavi(reason: "ride_did_end_emulator_navi")
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2664
|
+
public func rideManager(_ rideManager: AMapNaviRideManager, playNaviSound soundString: String, soundStringType: AMapNaviSoundType) {
|
|
2665
|
+
handleNavigationSound(soundString, soundStringType: soundStringType)
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
public func rideManager(onArrivedDestination rideManager: AMapNaviRideManager) {
|
|
2669
|
+
handleArrivedDestination(source: "rideManagerOnArrivedDestination")
|
|
2670
|
+
deactivateNavigationAudioSessionIfNeeded(reason: "ride_arrived_destination")
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
public func rideManagerNeedRecalculateRoute(forYaw rideManager: AMapNaviRideManager) {
|
|
2674
|
+
onRouteRecalculate([
|
|
2675
|
+
"reason": "yaw"
|
|
2676
|
+
])
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
public func rideManager(_ rideManager: AMapNaviRideManager, update gpsSignalStrength: AMapNaviGPSSignalStrength) {
|
|
2680
|
+
handleGpsSignalUpdate(gpsSignalStrength)
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2683
|
+
public func rideManager(_ rideManager: AMapNaviRideManager, onArrivedWayPoint wayPointIndex: Int32) {
|
|
2684
|
+
handleWayPointArrived(index: Int(wayPointIndex))
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
public func rideManager(_ rideManager: AMapNaviRideManager, onCalculateRouteFailure error: Error) {
|
|
2688
|
+
onRouteCalculated([
|
|
2689
|
+
"success": false,
|
|
2690
|
+
"errorInfo": error.localizedDescription
|
|
2691
|
+
])
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
// MARK: - AMapNaviRideViewDelegate
|
|
2696
|
+
extension ExpoGaodeMapNaviView: AMapNaviRideViewDelegate {
|
|
2697
|
+
public func rideViewEdgePadding(_ rideView: AMapNaviRideView) -> UIEdgeInsets {
|
|
2698
|
+
driveViewEdgePadding
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
public func rideView(_ rideView: AMapNaviRideView, didChange showMode: AMapNaviRideViewShowMode) {
|
|
2702
|
+
if showMode == .overview {
|
|
2703
|
+
scheduleOverviewRouteVisibleRegionRefresh()
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
extension ExpoGaodeMapNaviView: AMapNaviRideDataRepresentable {
|
|
2709
|
+
public func rideManager(_ rideManager: AMapNaviRideManager, update naviRoute: AMapNaviRoute?) {
|
|
2710
|
+
currentNaviRoute = naviRoute
|
|
2711
|
+
if let routeLength = naviRoute?.routeLength, routeLength > 0 {
|
|
2712
|
+
currentRouteTotalLength = routeLength
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
public func rideManager(_ rideManager: AMapNaviRideManager, update naviLocation: AMapNaviLocation?) {
|
|
2717
|
+
guard let naviLocation else {
|
|
2718
|
+
return
|
|
2719
|
+
}
|
|
2720
|
+
handleNavigationLocationUpdate(naviLocation)
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2723
|
+
public func rideManager(_ rideManager: AMapNaviRideManager, update naviInfo: AMapNaviInfo?) {
|
|
2724
|
+
guard let naviInfo else {
|
|
2725
|
+
return
|
|
2726
|
+
}
|
|
2727
|
+
handleNavigationInfoUpdate(naviInfo)
|
|
598
2728
|
}
|
|
599
2729
|
}
|