expo-gaode-map-navigation 2.0.10 → 2.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +54 -2
  2. package/android/build.gradle +4 -0
  3. package/android/src/main/AndroidManifest.xml +2 -1
  4. package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNaviView.kt +501 -27
  5. package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNaviViewModule.kt +35 -0
  6. package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNavigationModule.kt +10 -23
  7. package/android/src/main/java/expo/modules/gaodemap/navigation/listeners/IndependentRouteListener.kt +24 -0
  8. package/android/src/main/java/expo/modules/gaodemap/navigation/managers/IndependentRouteManager.kt +24 -7
  9. package/android/src/main/java/expo/modules/gaodemap/navigation/routes/drive/DriveTruckRouteCalculator.kt +22 -35
  10. package/android/src/main/java/expo/modules/gaodemap/navigation/services/IndependentRouteService.kt +45 -35
  11. package/android/src/main/java/expo/modules/gaodemap/navigation/services/NavigationForegroundService.kt +661 -0
  12. package/android/src/main/java/expo/modules/gaodemap/navigation/utils/Converters.kt +2 -2
  13. package/android/src/main/res/drawable/ic_nav_notification_small.xml +10 -0
  14. package/android/src/main/res/drawable/nav_notification_brand_icon.xml +16 -0
  15. package/android/src/main/res/drawable/navi_lane_shape_bg_center.xml +4 -4
  16. package/android/src/main/res/drawable/navi_lane_shape_bg_left.xml +7 -7
  17. package/android/src/main/res/drawable/navi_lane_shape_bg_over.xml +5 -5
  18. package/android/src/main/res/drawable/navi_lane_shape_bg_right.xml +7 -7
  19. package/android/src/main/res/drawable-nodpi/nav_tracker_car.png +0 -0
  20. package/build/ExpoGaodeMapNaviView.d.ts +9 -1
  21. package/build/ExpoGaodeMapNaviView.d.ts.map +1 -1
  22. package/build/ExpoGaodeMapNaviView.js +39 -3
  23. package/build/ExpoGaodeMapNaviView.js.map +1 -1
  24. package/build/index.d.ts +32 -6
  25. package/build/index.d.ts.map +1 -1
  26. package/build/index.js +32 -6
  27. package/build/index.js.map +1 -1
  28. package/build/types/independent.types.d.ts +18 -3
  29. package/build/types/independent.types.d.ts.map +1 -1
  30. package/build/types/independent.types.js.map +1 -1
  31. package/build/types/naviview.types.d.ts +49 -3
  32. package/build/types/naviview.types.d.ts.map +1 -1
  33. package/build/types/naviview.types.js.map +1 -1
  34. package/build/types/route.types.d.ts +10 -2
  35. package/build/types/route.types.d.ts.map +1 -1
  36. package/build/types/route.types.js.map +1 -1
  37. package/ios/ExpoGaodeMapNaviView.swift +1526 -246
  38. package/ios/ExpoGaodeMapNaviViewModule.swift +22 -0
  39. package/ios/ExpoGaodeMapNavigationModule.swift +6 -4
  40. package/ios/managers/IndependentRouteManager.swift +89 -26
  41. package/ios/map/ExpoGaodeMapModule.swift +25 -11
  42. package/ios/map/modules/LocationManager.swift +10 -1
  43. package/ios/map/utils/PermissionManager.swift +104 -0
  44. package/ios/routes/drive/DriveTruckRouteCalculator.swift +157 -78
  45. package/ios/routes/walkride/WalkRideRouteCalculator.swift +97 -1
  46. package/ios/services/IndependentRouteService.swift +165 -32
  47. package/ios/services/NavigationLiveActivityAttributes.swift +48 -0
  48. package/ios/services/NavigationLiveActivityManager.swift +359 -0
  49. package/package.json +2 -1
  50. package/plugin/build/withGaodeMap.d.ts +8 -0
  51. package/plugin/build/withGaodeMap.js +48 -4
  52. package/widget-template/README.md +46 -0
  53. package/widget-template/ios/NavigationLiveActivityWidget.swift +367 -0
@@ -51,6 +51,16 @@ public class ExpoGaodeMapNaviViewModule: Module {
51
51
  view.carImageSource = value
52
52
  }
53
53
 
54
+ Prop("carImageSize") { (view: ExpoGaodeMapNaviView, value: [String: Double]?) in
55
+ let width = CGFloat(value?["width"] ?? 0)
56
+ let height = CGFloat(value?["height"] ?? 0)
57
+ if width > 0, height > 0 {
58
+ view.carImageSize = CGSize(width: width, height: height)
59
+ } else {
60
+ view.carImageSize = nil
61
+ }
62
+ }
63
+
54
64
  Prop("carCompassImage") { (view: ExpoGaodeMapNaviView, value: String?) in
55
65
  view.carCompassImageSource = value
56
66
  }
@@ -63,6 +73,10 @@ public class ExpoGaodeMapNaviViewModule: Module {
63
73
  view.wayPointImageSource = value
64
74
  }
65
75
 
76
+ Prop("customWaypointMarkers") { (view: ExpoGaodeMapNaviView, value: [[String: Any]]?) in
77
+ view.customWaypointMarkerPayloads = value
78
+ }
79
+
66
80
  Prop("endPointImage") { (view: ExpoGaodeMapNaviView, value: String?) in
67
81
  view.endPointImageSource = value
68
82
  }
@@ -179,6 +193,14 @@ public class ExpoGaodeMapNaviViewModule: Module {
179
193
  Prop("hideNativeLaneInfoLayout") { (view: ExpoGaodeMapNaviView, value: Bool) in
180
194
  view.hideNativeLaneInfoLayout = value
181
195
  }
196
+
197
+ // Android-only prop, keep a no-op mapping on iOS for cross-platform prop compatibility.
198
+ Prop("androidBackgroundNavigationNotificationEnabled") { (_: ExpoGaodeMapNaviView, _: Bool) in
199
+ }
200
+
201
+ Prop("iosLiveActivityEnabled") { (view: ExpoGaodeMapNaviView, value: Bool) in
202
+ view.iosLiveActivityEnabled = value
203
+ }
182
204
 
183
205
  Prop("mapViewModeType") { (view: ExpoGaodeMapNaviView, value: Int) in
184
206
  view.mapViewModeType = value
@@ -309,8 +309,11 @@ public class ExpoGaodeMapNavigationModule: Module {
309
309
  AsyncFunction("calculateMotorcycleRoute") { (options: [String: Any], promise: Promise) in
310
310
  do {
311
311
  try self.ensureInitialized()
312
- // 摩托车复用驾车算路
313
- self.ensureDriveTruck().calculateDriveRoute(options: options, promise: promise)
312
+ var motorcycleOptions = options
313
+ if motorcycleOptions["carType"] == nil && motorcycleOptions["type"] == nil {
314
+ motorcycleOptions["carType"] = 11
315
+ }
316
+ self.ensureDriveTruck().calculateDriveRoute(options: motorcycleOptions, promise: promise)
314
317
  } catch {
315
318
  self.rejectInitializationError(promise, error: error)
316
319
  }
@@ -321,8 +324,7 @@ public class ExpoGaodeMapNavigationModule: Module {
321
324
  AsyncFunction("calculateEBikeRoute") { (options: [String: Any], promise: Promise) in
322
325
  do {
323
326
  try self.ensureInitialized()
324
- // 电动车复用骑行算路
325
- self.ensureWalkRide().calculateRideRoute(options: options, promise: promise)
327
+ self.ensureWalkRide().calculateResolvedEleBikeRoute(options: options, promise: promise)
326
328
  } catch {
327
329
  self.rejectInitializationError(promise, error: error)
328
330
  }
@@ -11,8 +11,14 @@ import AMapNaviKit
11
11
 
12
12
  class IndependentRouteManager {
13
13
  static let shared = IndependentRouteManager()
14
+
15
+ private struct Entry {
16
+ let scene: String
17
+ let routeGroup: AMapNaviRouteGroup?
18
+ let routeIds: [Int]
19
+ }
14
20
 
15
- private var routeGroups: [Int: AMapNaviRouteGroup] = [:]
21
+ private var routeGroups: [Int: Entry] = [:]
16
22
  private var nextToken: Int = 1
17
23
  private let lock = NSLock()
18
24
 
@@ -26,10 +32,18 @@ class IndependentRouteManager {
26
32
  }
27
33
 
28
34
  /// 存储路线组(使用指定 token)
29
- func storeRouteGroup(token: Int, routeGroup: AMapNaviRouteGroup) {
35
+ func storeRouteGroup(token: Int, scene: String = "drive", routeGroup: AMapNaviRouteGroup) {
36
+ lock.lock()
37
+ defer { lock.unlock() }
38
+ let routeIds = routeGroup.naviRouteIDs?.map { $0.intValue } ?? []
39
+ routeGroups[token] = Entry(scene: scene, routeGroup: routeGroup, routeIds: routeIds)
40
+ }
41
+
42
+ /// 存储非驾车场景的独立路径元信息
43
+ func storeSceneRoutes(token: Int, scene: String, routeIds: [Int]) {
30
44
  lock.lock()
31
45
  defer { lock.unlock() }
32
- routeGroups[token] = routeGroup
46
+ routeGroups[token] = Entry(scene: scene, routeGroup: nil, routeIds: routeIds)
33
47
  }
34
48
 
35
49
  /// 存储路线组(自动生成 token)
@@ -38,7 +52,8 @@ class IndependentRouteManager {
38
52
  defer { lock.unlock() }
39
53
  let token = nextToken
40
54
  nextToken += 1
41
- routeGroups[token] = routeGroup
55
+ let routeIds = routeGroup.naviRouteIDs?.map { $0.intValue } ?? []
56
+ routeGroups[token] = Entry(scene: "drive", routeGroup: routeGroup, routeIds: routeIds)
42
57
  return token
43
58
  }
44
59
 
@@ -46,7 +61,14 @@ class IndependentRouteManager {
46
61
  func get(_ token: Int) -> AMapNaviRouteGroup? {
47
62
  lock.lock()
48
63
  defer { lock.unlock() }
49
- return routeGroups[token]
64
+ return routeGroups[token]?.routeGroup
65
+ }
66
+
67
+ /// 获取路线场景
68
+ func scene(for token: Int) -> String? {
69
+ lock.lock()
70
+ defer { lock.unlock() }
71
+ return routeGroups[token]?.scene
50
72
  }
51
73
 
52
74
  /// 行前选路 - 选择指定路线
@@ -55,32 +77,45 @@ class IndependentRouteManager {
55
77
  lock.lock()
56
78
  defer { lock.unlock() }
57
79
 
58
- guard let routeGroup = routeGroups[token] else { return false }
59
-
60
- // 优先使用 routeId
61
- if let routeId = routeId {
62
- routeGroup.selectNaviRoute(withRouteID: routeId)
63
- return true
80
+ guard let entry = routeGroups[token] else { return false }
81
+
82
+ let resolvedRouteId: Int? = {
83
+ if let routeId {
84
+ return routeId
85
+ }
86
+ if let routeIndex, routeIndex >= 0, routeIndex < entry.routeIds.count {
87
+ return entry.routeIds[routeIndex]
88
+ }
89
+ return nil
90
+ }()
91
+
92
+ guard let resolvedRouteId else {
93
+ return false
64
94
  }
65
-
66
- // 使用 routeIndex
67
- if let routeIndex = routeIndex, let routeIDs = routeGroup.naviRouteIDs, routeIndex < routeIDs.count {
68
- let routeId = routeIDs[routeIndex].intValue
69
- routeGroup.selectNaviRoute(withRouteID: routeId)
95
+
96
+ if let routeGroup = entry.routeGroup {
97
+ routeGroup.selectNaviRoute(withRouteID: resolvedRouteId)
70
98
  return true
71
99
  }
72
-
73
- return false
100
+
101
+ switch entry.scene {
102
+ case "walk":
103
+ return AMapNaviWalkManager.sharedInstance().selectNaviRoute(withRouteID: resolvedRouteId)
104
+ case "ride":
105
+ return AMapNaviRideManager.sharedInstance().selectNaviRoute(withRouteID: resolvedRouteId)
106
+ default:
107
+ return false
108
+ }
74
109
  }
75
110
 
76
111
  /// 启动导航
77
112
  /// 官方方法:startGPSNavi: / startEmulatorNavi:
78
113
  func start(token: Int, naviType: Int, routeId: Int?, routeIndex: Int?) -> Bool {
79
114
  lock.lock()
80
- let routeGroup = routeGroups[token]
115
+ let entry = routeGroups[token]
81
116
  lock.unlock()
82
117
 
83
- guard let routeGroup = routeGroup else { return false }
118
+ guard let entry = entry else { return false }
84
119
 
85
120
  // 先选择路线
86
121
  if routeId != nil || routeIndex != nil {
@@ -89,13 +124,41 @@ class IndependentRouteManager {
89
124
 
90
125
  // 启动导航
91
126
  // naviType: 0 = GPS导航, 1 = 模拟导航
92
- if naviType == 1 {
93
- AMapNaviDriveManager.sharedInstance().startEmulatorNavi(routeGroup)
94
- } else {
95
- AMapNaviDriveManager.sharedInstance().startGPSNavi(routeGroup)
127
+ switch entry.scene {
128
+ case "walk":
129
+ if let routeGroup = entry.routeGroup {
130
+ if naviType == 1 {
131
+ return AMapNaviWalkManager.sharedInstance().startEmulatorNavi(routeGroup)
132
+ } else {
133
+ return AMapNaviWalkManager.sharedInstance().startGPSNavi(routeGroup)
134
+ }
135
+ }
136
+ if naviType == 1 {
137
+ return AMapNaviWalkManager.sharedInstance().startEmulatorNavi()
138
+ } else {
139
+ return AMapNaviWalkManager.sharedInstance().startGPSNavi()
140
+ }
141
+ case "ride":
142
+ if let routeGroup = entry.routeGroup {
143
+ if naviType == 1 {
144
+ return AMapNaviRideManager.sharedInstance().startEmulatorNavi(routeGroup)
145
+ } else {
146
+ return AMapNaviRideManager.sharedInstance().startGPSNavi(routeGroup)
147
+ }
148
+ }
149
+ if naviType == 1 {
150
+ return AMapNaviRideManager.sharedInstance().startEmulatorNavi()
151
+ } else {
152
+ return AMapNaviRideManager.sharedInstance().startGPSNavi()
153
+ }
154
+ default:
155
+ guard let routeGroup = entry.routeGroup else { return false }
156
+ if naviType == 1 {
157
+ return AMapNaviDriveManager.sharedInstance().startEmulatorNavi(routeGroup)
158
+ } else {
159
+ return AMapNaviDriveManager.sharedInstance().startGPSNavi(routeGroup)
160
+ }
96
161
  }
97
-
98
- return true
99
162
  }
100
163
 
101
164
  /// 清除指定路线组
@@ -736,17 +736,31 @@ public class ExpoGaodeMapModule: Module {
736
736
  promise.reject("FOREGROUND_PERMISSION_REQUIRED", "必须先授予前台位置权限才能请求后台位置权限")
737
737
  return
738
738
  }
739
-
740
- // iOS 上后台权限通过 Info.plist 配置 + 系统设置
741
- // 这里返回当前状态
742
- let hasBackground = status == .authorizedAlways
743
-
744
- promise.resolve([
745
- "granted": hasBackground,
746
- "backgroundLocation": hasBackground,
747
- "status": self.getAuthorizationStatusString(status),
748
- "message": hasBackground ? "已授予后台权限" : "需要在系统设置中手动授予'始终'权限"
749
- ])
739
+
740
+ if self.permissionManager == nil {
741
+ self.permissionManager = PermissionManager()
742
+ }
743
+
744
+ guard let permissionManager = self.permissionManager else {
745
+ promise.reject("PERMISSION_MANAGER_INIT_FAILED", "权限管理器初始化失败")
746
+ return
747
+ }
748
+
749
+ permissionManager.requestAlwaysPermission { _, _ in
750
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
751
+ let finalStatus = self.currentAuthorizationStatus()
752
+ let hasBackground = finalStatus == .authorizedAlways
753
+
754
+ promise.resolve([
755
+ "granted": hasBackground,
756
+ "backgroundLocation": hasBackground,
757
+ "status": self.getAuthorizationStatusString(finalStatus),
758
+ "message": hasBackground
759
+ ? "已授予后台权限"
760
+ : "后台权限未授予,请在系统设置中将定位权限改为“始终允许”"
761
+ ])
762
+ }
763
+ }
750
764
  }
751
765
 
752
766
 
@@ -90,7 +90,16 @@ class LocationManager: NSObject, AMapLocationManagerDelegate {
90
90
  return
91
91
  }
92
92
  }
93
- ensureLocationManager()?.allowsBackgroundLocationUpdates = allows
93
+ guard let manager = ensureLocationManager() else {
94
+ return
95
+ }
96
+ manager.allowsBackgroundLocationUpdates = allows
97
+ if #available(iOS 11.0, *) {
98
+ let indicatorSelector = NSSelectorFromString("setShowsBackgroundLocationIndicator:")
99
+ if manager.responds(to: indicatorSelector) {
100
+ manager.setValue(allows, forKey: "showsBackgroundLocationIndicator")
101
+ }
102
+ }
94
103
  }
95
104
 
96
105
  func setGeoLanguage(_ language: Int) {
@@ -39,6 +39,39 @@ class PermissionManager: NSObject, CLLocationManagerDelegate {
39
39
  private var locationManager: CLLocationManager?
40
40
  /// 权限请求回调
41
41
  private var permissionCallback: ((Bool, String) -> Void)?
42
+ /// 后台(始终)权限请求回调
43
+ private var backgroundPermissionCallback: ((Bool, String) -> Void)?
44
+ /// 在 notDetermined 状态下,先申请前台权限,再升级为始终权限
45
+ private var pendingAlwaysAuthorizationUpgrade = false
46
+
47
+ private func resolveBackgroundPermissionIfNeeded(_ status: CLAuthorizationStatus) {
48
+ guard let backgroundPermissionCallback else {
49
+ return
50
+ }
51
+ let backgroundGranted = status == .authorizedAlways
52
+ backgroundPermissionCallback(backgroundGranted, getAuthorizationStatusString(status))
53
+ self.backgroundPermissionCallback = nil
54
+ }
55
+
56
+ private func scheduleAlwaysAuthorizationFallback(_ manager: CLLocationManager) {
57
+ DispatchQueue.main.asyncAfter(deadline: .now() + 1.2) { [weak self] in
58
+ guard let self else {
59
+ return
60
+ }
61
+ guard self.backgroundPermissionCallback != nil else {
62
+ return
63
+ }
64
+
65
+ let latestStatus: CLAuthorizationStatus
66
+ if #available(iOS 14.0, *) {
67
+ latestStatus = manager.authorizationStatus
68
+ } else {
69
+ latestStatus = CLLocationManager.authorizationStatus()
70
+ }
71
+ self.pendingAlwaysAuthorizationUpgrade = false
72
+ self.resolveBackgroundPermissionIfNeeded(latestStatus)
73
+ }
74
+ }
42
75
 
43
76
  /**
44
77
  * 请求位置权限
@@ -92,6 +125,61 @@ class PermissionManager: NSObject, CLLocationManagerDelegate {
92
125
  locationManager.requestWhenInUseAuthorization()
93
126
  }
94
127
  }
128
+
129
+ /**
130
+ * 请求后台(始终)位置权限
131
+ * @param callback 权限结果回调 (granted, status)
132
+ */
133
+ func requestAlwaysPermission(callback: @escaping (Bool, String) -> Void) {
134
+ self.backgroundPermissionCallback = callback
135
+
136
+ DispatchQueue.main.async {
137
+ if self.locationManager == nil {
138
+ self.locationManager = CLLocationManager()
139
+ }
140
+
141
+ guard let locationManager = self.locationManager else {
142
+ self.backgroundPermissionCallback?(false, "unknown")
143
+ self.backgroundPermissionCallback = nil
144
+ self.pendingAlwaysAuthorizationUpgrade = false
145
+ return
146
+ }
147
+
148
+ locationManager.delegate = self
149
+
150
+ let currentStatus: CLAuthorizationStatus
151
+ if #available(iOS 14.0, *) {
152
+ currentStatus = locationManager.authorizationStatus
153
+ } else {
154
+ currentStatus = CLLocationManager.authorizationStatus()
155
+ }
156
+
157
+ if currentStatus == .authorizedAlways {
158
+ self.backgroundPermissionCallback?(true, self.getAuthorizationStatusString(currentStatus))
159
+ self.backgroundPermissionCallback = nil
160
+ self.pendingAlwaysAuthorizationUpgrade = false
161
+ return
162
+ }
163
+
164
+ if currentStatus == .denied || currentStatus == .restricted {
165
+ self.backgroundPermissionCallback?(false, self.getAuthorizationStatusString(currentStatus))
166
+ self.backgroundPermissionCallback = nil
167
+ self.pendingAlwaysAuthorizationUpgrade = false
168
+ return
169
+ }
170
+
171
+ if currentStatus == .authorizedWhenInUse {
172
+ self.pendingAlwaysAuthorizationUpgrade = false
173
+ locationManager.requestAlwaysAuthorization()
174
+ self.scheduleAlwaysAuthorizationFallback(locationManager)
175
+ return
176
+ }
177
+
178
+ // notDetermined: 先请求前台定位,再在回调中升级到始终权限
179
+ self.pendingAlwaysAuthorizationUpgrade = true
180
+ locationManager.requestWhenInUseAuthorization()
181
+ }
182
+ }
95
183
 
96
184
  /**
97
185
  * 权限状态变化回调 (iOS 14+)
@@ -115,6 +203,18 @@ class PermissionManager: NSObject, CLLocationManagerDelegate {
115
203
  if status == .notDetermined {
116
204
  return
117
205
  }
206
+
207
+ if pendingAlwaysAuthorizationUpgrade && status == .authorizedWhenInUse {
208
+ pendingAlwaysAuthorizationUpgrade = false
209
+ if let locationManager {
210
+ locationManager.requestAlwaysAuthorization()
211
+ scheduleAlwaysAuthorizationFallback(locationManager)
212
+ } else {
213
+ resolveBackgroundPermissionIfNeeded(status)
214
+ }
215
+ return
216
+ }
217
+ pendingAlwaysAuthorizationUpgrade = false
118
218
 
119
219
  // 状态已确定(授予或拒绝),返回结果
120
220
  var granted = false
@@ -125,6 +225,8 @@ class PermissionManager: NSObject, CLLocationManagerDelegate {
125
225
  }
126
226
  let statusString = getAuthorizationStatusString(status)
127
227
 
228
+ resolveBackgroundPermissionIfNeeded(status)
229
+
128
230
  permissionCallback?(granted, statusString)
129
231
  permissionCallback = nil
130
232
  }
@@ -236,6 +338,8 @@ class PermissionManager: NSObject, CLLocationManagerDelegate {
236
338
  locationManager?.delegate = nil
237
339
  locationManager = nil
238
340
  permissionCallback = nil
341
+ backgroundPermissionCallback = nil
342
+ pendingAlwaysAuthorizationUpgrade = false
239
343
  }
240
344
  }
241
345
  #endif