expo-gaode-map-navigation 1.1.5-next.1 → 1.1.5-next.3
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/android/build.gradle +10 -0
- package/android/src/main/cpp/CMakeLists.txt +24 -0
- package/android/src/main/cpp/cluster_jni.cpp +848 -0
- package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapModule.kt +616 -92
- package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapOfflineModule.kt +493 -0
- package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapView.kt +230 -14
- package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapViewModule.kt +37 -27
- package/android/src/main/java/expo/modules/gaodemap/map/MapPreloadManager.kt +494 -0
- package/android/src/main/java/expo/modules/gaodemap/map/companion/BitmapDescriptorCache.kt +30 -0
- package/android/src/main/java/expo/modules/gaodemap/map/companion/IconBitmapCache.kt +37 -0
- package/android/src/main/java/expo/modules/gaodemap/map/managers/UIManager.kt +76 -0
- package/android/src/main/java/expo/modules/gaodemap/map/modules/LocationManager.kt +15 -3
- package/android/src/main/java/expo/modules/gaodemap/map/modules/SDKInitializer.kt +4 -59
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/CircleView.kt +9 -12
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/CircleViewModule.kt +5 -6
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/ClusterView.kt +539 -66
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/ClusterViewModule.kt +17 -1
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/HeatMapView.kt +165 -33
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/HeatMapViewModule.kt +15 -3
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerView.kt +1249 -672
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerViewModule.kt +40 -17
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/MultiPointView.kt +177 -22
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/MultiPointViewModule.kt +11 -3
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/PolygonView.kt +57 -14
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/PolygonViewModule.kt +9 -5
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/PolylineView.kt +90 -63
- package/android/src/main/java/expo/modules/gaodemap/map/overlays/PolylineViewModule.kt +7 -3
- package/android/src/main/java/expo/modules/gaodemap/map/services/LocationForegroundService.kt +3 -2
- package/android/src/main/java/expo/modules/gaodemap/map/utils/BitmapDescriptorCache.kt +20 -0
- package/android/src/main/java/expo/modules/gaodemap/map/utils/ClusterNative.kt +13 -0
- package/android/src/main/java/expo/modules/gaodemap/map/utils/ColorParser.kt +20 -0
- package/android/src/main/java/expo/modules/gaodemap/map/utils/GeometryUtils.kt +515 -0
- package/android/src/main/java/expo/modules/gaodemap/map/utils/LatLngParser.kt +91 -0
- package/android/src/main/java/expo/modules/gaodemap/map/utils/PermissionHelper.kt +248 -0
- package/build/ExpoGaodeMapNaviView.d.ts +7 -7
- package/build/ExpoGaodeMapNaviView.js +8 -8
- package/build/ExpoGaodeMapNavigationModule.d.ts +2 -1
- package/build/index.d.ts +35 -33
- package/build/index.js +70 -106
- package/build/map/ExpoGaodeMapModule.d.ts +2 -201
- package/build/map/ExpoGaodeMapModule.js +584 -14
- package/build/map/ExpoGaodeMapOfflineModule.d.ts +139 -0
- package/build/map/ExpoGaodeMapOfflineModule.js +8 -0
- package/build/map/ExpoGaodeMapView.js +66 -58
- package/build/map/components/FoldableMapView.d.ts +38 -0
- package/build/map/components/FoldableMapView.js +209 -0
- package/build/map/components/MapContext.d.ts +12 -0
- package/build/map/components/MapContext.js +54 -0
- package/build/map/components/MapUI.d.ts +18 -0
- package/build/map/components/MapUI.js +29 -0
- package/build/map/components/overlays/Circle.js +34 -3
- package/build/map/components/overlays/Cluster.d.ts +3 -1
- package/build/map/components/overlays/Cluster.js +31 -2
- package/build/map/components/overlays/HeatMap.d.ts +3 -1
- package/build/map/components/overlays/HeatMap.js +33 -3
- package/build/map/components/overlays/Marker.d.ts +1 -1
- package/build/map/components/overlays/Marker.js +37 -32
- package/build/map/components/overlays/MultiPoint.js +1 -1
- package/build/map/components/overlays/Polygon.js +30 -3
- package/build/map/components/overlays/Polyline.js +36 -3
- package/build/map/index.d.ts +25 -5
- package/build/map/index.js +59 -18
- package/build/map/types/common.types.d.ts +40 -0
- package/build/map/types/common.types.js +0 -4
- package/build/map/types/index.d.ts +3 -2
- package/build/map/types/map-view.types.d.ts +108 -3
- package/build/map/types/native-module.types.d.ts +363 -0
- package/build/map/types/native-module.types.js +5 -0
- package/build/map/types/offline.types.d.ts +132 -0
- package/build/map/types/offline.types.js +5 -0
- package/build/map/types/overlays.types.d.ts +137 -24
- package/build/map/utils/ErrorHandler.d.ts +110 -0
- package/build/map/utils/ErrorHandler.js +421 -0
- package/build/map/utils/GeoUtils.d.ts +20 -0
- package/build/map/utils/GeoUtils.js +76 -0
- package/build/map/utils/OfflineMapManager.d.ts +148 -0
- package/build/map/utils/OfflineMapManager.js +217 -0
- package/build/map/utils/PermissionUtils.d.ts +91 -0
- package/build/map/utils/PermissionUtils.js +255 -0
- package/build/map/utils/PlatformDetector.d.ts +102 -0
- package/build/map/utils/PlatformDetector.js +186 -0
- package/build/types/index.d.ts +1 -0
- package/build/types/index.js +1 -0
- package/build/types/native-module.types.d.ts +69 -0
- package/build/types/native-module.types.js +2 -0
- package/build/types/naviview.types.d.ts +1 -1
- package/expo-module.config.json +12 -10
- package/ios/ExpoGaodeMapNavigation.podspec +9 -0
- package/ios/map/ExpoGaodeMapModule.swift +485 -75
- package/ios/map/ExpoGaodeMapOfflineModule.swift +479 -0
- package/ios/map/ExpoGaodeMapView.swift +611 -62
- package/ios/map/ExpoGaodeMapViewModule.swift +48 -26
- package/ios/map/MapPreloadManager.swift +348 -0
- package/ios/map/cpp/ClusterEngine.cpp +110 -0
- package/ios/map/cpp/ClusterEngine.hpp +20 -0
- package/ios/map/cpp/ColorParser.cpp +135 -0
- package/ios/map/cpp/ColorParser.hpp +14 -0
- package/ios/map/cpp/GeometryEngine.cpp +574 -0
- package/ios/map/cpp/GeometryEngine.hpp +159 -0
- package/ios/map/cpp/QuadTree.cpp +92 -0
- package/ios/map/cpp/QuadTree.hpp +42 -0
- package/ios/map/cpp/README.md +55 -0
- package/ios/map/cpp/tests/benchmark_js.js +41 -0
- package/ios/map/cpp/tests/run.sh +17 -0
- package/ios/map/cpp/tests/test_main.cpp +276 -0
- package/ios/map/managers/UIManager.swift +72 -1
- package/ios/map/modules/LocationManager.swift +114 -165
- package/ios/map/overlays/CircleView.swift +16 -32
- package/ios/map/overlays/CircleViewModule.swift +12 -12
- package/ios/map/overlays/ClusterAnnotation.swift +32 -0
- package/ios/map/overlays/ClusterView.swift +331 -45
- package/ios/map/overlays/ClusterViewModule.swift +20 -6
- package/ios/map/overlays/HeatMapView.swift +135 -32
- package/ios/map/overlays/HeatMapViewModule.swift +20 -8
- package/ios/map/overlays/MarkerView.swift +613 -130
- package/ios/map/overlays/MarkerViewModule.swift +38 -18
- package/ios/map/overlays/MultiPointView.swift +168 -10
- package/ios/map/overlays/MultiPointViewModule.swift +27 -5
- package/ios/map/overlays/PolygonView.swift +62 -23
- package/ios/map/overlays/PolygonViewModule.swift +18 -12
- package/ios/map/overlays/PolylineView.swift +21 -13
- package/ios/map/overlays/PolylineViewModule.swift +18 -12
- package/ios/map/utils/ClusterNative.h +96 -0
- package/ios/map/utils/ClusterNative.mm +377 -0
- package/ios/map/utils/ColorParser.swift +12 -1
- package/ios/map/utils/CppBridging.mm +13 -0
- package/ios/map/utils/GeometryUtils.swift +34 -0
- package/ios/map/utils/LatLngParser.swift +87 -0
- package/ios/map/utils/PermissionManager.swift +135 -6
- package/package.json +1 -1
- package/build/map/ExpoGaodeMap.types.d.ts +0 -41
- package/build/map/ExpoGaodeMap.types.js +0 -24
- package/build/map/utils/EventManager.d.ts +0 -10
- package/build/map/utils/EventManager.js +0 -26
- package/build/map/utils/ModuleLoader.d.ts +0 -73
- package/build/map/utils/ModuleLoader.js +0 -112
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import AMapFoundationKit
|
|
3
|
+
import AMapNaviKit
|
|
4
|
+
import Foundation
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 高德地图离线地图模块 (iOS)
|
|
8
|
+
*
|
|
9
|
+
*
|
|
10
|
+
*/
|
|
11
|
+
public class ExpoGaodeMapOfflineModule: Module {
|
|
12
|
+
|
|
13
|
+
// ==================== 属性定义 ====================
|
|
14
|
+
|
|
15
|
+
// 线程安全的状态集合
|
|
16
|
+
private let stateQueue = DispatchQueue(label: "com.expo.gaodemap.offline.state", attributes: .concurrent)
|
|
17
|
+
private var _downloadingCities: Set<String> = []
|
|
18
|
+
private var _pausedCities: Set<String> = []
|
|
19
|
+
|
|
20
|
+
private var downloadingCities: Set<String> {
|
|
21
|
+
get { stateQueue.sync { _downloadingCities } }
|
|
22
|
+
set { stateQueue.async(flags: .barrier) { self._downloadingCities = newValue } }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private var pausedCities: Set<String> {
|
|
26
|
+
get { stateQueue.sync { _pausedCities } }
|
|
27
|
+
set { stateQueue.async(flags: .barrier) { self._pausedCities = newValue } }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private var offlineMapManager: MAOfflineMap {
|
|
31
|
+
return MAOfflineMap.shared()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 数据缓存
|
|
35
|
+
private var cachedCities: [MAOfflineCity]?
|
|
36
|
+
private var cachedProvinces: [MAOfflineProvince]?
|
|
37
|
+
private var cachedMunicipalities: [MAOfflineCity]?
|
|
38
|
+
|
|
39
|
+
// 初始化锁与等待队列
|
|
40
|
+
private var isSetupComplete = false
|
|
41
|
+
private var isSetupInProgress = false
|
|
42
|
+
private var setupWaiters: [(Bool) -> Void] = []
|
|
43
|
+
|
|
44
|
+
// ==================== 模块定义 ====================
|
|
45
|
+
|
|
46
|
+
public func definition() -> ModuleDefinition {
|
|
47
|
+
Name("ExpoGaodeMapOffline")
|
|
48
|
+
|
|
49
|
+
Events(
|
|
50
|
+
"onDownloadProgress",
|
|
51
|
+
"onDownloadComplete",
|
|
52
|
+
"onDownloadError",
|
|
53
|
+
"onUnzipProgress",
|
|
54
|
+
"onDownloadPaused",
|
|
55
|
+
"onDownloadCancelled"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
OnCreate {
|
|
59
|
+
// Module initialization
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
OnDestroy {
|
|
63
|
+
MAOfflineMap.shared().cancelAll()
|
|
64
|
+
self.downloadingCities.removeAll()
|
|
65
|
+
self.pausedCities.removeAll()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ==================== 1. 地图列表管理 ====================
|
|
69
|
+
|
|
70
|
+
AsyncFunction("getAvailableCities") { (promise: Promise) in
|
|
71
|
+
self.ensureSetup { success in
|
|
72
|
+
guard success else { promise.reject("ERR_SETUP", "Setup failed"); return }
|
|
73
|
+
let cities = self.cachedCities ?? self.offlineMapManager.cities ?? []
|
|
74
|
+
promise.resolve(cities.map { self.convertCityToDict($0) })
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
AsyncFunction("getAvailableProvinces") { (promise: Promise) in
|
|
79
|
+
self.ensureSetup { success in
|
|
80
|
+
guard success else { promise.reject("ERR_SETUP", "Setup failed"); return }
|
|
81
|
+
let provinces = self.cachedProvinces ?? self.offlineMapManager.provinces ?? []
|
|
82
|
+
promise.resolve(provinces.map { self.convertProvinceToDict($0) })
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
AsyncFunction("getCitiesByProvince") { (provinceCode: String, promise: Promise) in
|
|
87
|
+
self.ensureSetup { success in
|
|
88
|
+
guard success else { promise.reject("ERR_SETUP", "Setup failed"); return }
|
|
89
|
+
let provinces = self.cachedProvinces ?? self.offlineMapManager.provinces ?? []
|
|
90
|
+
if let province = provinces.first(where: { $0.adcode == provinceCode }) {
|
|
91
|
+
let cities = province.cities.compactMap { ($0 as? MAOfflineCity).map { self.convertCityToDict($0) } }
|
|
92
|
+
promise.resolve(cities)
|
|
93
|
+
} else {
|
|
94
|
+
promise.resolve([])
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
AsyncFunction("getDownloadedMaps") { (promise: Promise) in
|
|
100
|
+
self.ensureSetup { success in
|
|
101
|
+
guard success else { promise.reject("ERR_SETUP", "Setup failed"); return }
|
|
102
|
+
let allCities = self.cachedCities ?? self.offlineMapManager.cities ?? []
|
|
103
|
+
let downloaded = allCities.filter { $0.itemStatus == .installed }
|
|
104
|
+
promise.resolve(downloaded.map { self.convertCityToDict($0) })
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ==================== 2. 下载管理 ====================
|
|
109
|
+
|
|
110
|
+
AsyncFunction("startDownload") { (config: [String: Any], promise: Promise) in
|
|
111
|
+
guard let cityCode = config["cityCode"] as? String else {
|
|
112
|
+
promise.reject("ERR_ARGS", "cityCode required")
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
self.startDownloadInternal(cityCode: cityCode, promise: promise)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
AsyncFunction("resumeDownload") { (cityCode: String, promise: Promise) in
|
|
119
|
+
self.startDownloadInternal(cityCode: cityCode, promise: promise)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
Function("pauseDownload") { (cityCode: String) in
|
|
123
|
+
let allCities = self.cachedCities ?? self.offlineMapManager.cities ?? []
|
|
124
|
+
if let city = allCities.first(where: { $0.adcode == cityCode }) {
|
|
125
|
+
self.pausedCities.insert(cityCode)
|
|
126
|
+
self.downloadingCities.remove(cityCode)
|
|
127
|
+
self.offlineMapManager.pause(city)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
AsyncFunction("cancelDownload") { (cityCode: String) in
|
|
132
|
+
let allCities = self.cachedCities ?? self.offlineMapManager.cities ?? []
|
|
133
|
+
if let city = allCities.first(where: { $0.adcode == cityCode }) {
|
|
134
|
+
self.downloadingCities.remove(cityCode)
|
|
135
|
+
self.pausedCities.remove(cityCode)
|
|
136
|
+
self.offlineMapManager.pause(city) // iOS SDK 中 pause 停止网络
|
|
137
|
+
self.sendEvent("onDownloadCancelled", ["cityCode": cityCode, "cityName": city.name ?? ""])
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
AsyncFunction("deleteMap") { (cityCode: String) in
|
|
142
|
+
let allCities = self.cachedCities ?? self.offlineMapManager.cities ?? []
|
|
143
|
+
if let city = allCities.first(where: { $0.adcode == cityCode }) {
|
|
144
|
+
self.offlineMapManager.delete(city)
|
|
145
|
+
self.downloadingCities.remove(cityCode)
|
|
146
|
+
self.pausedCities.remove(cityCode)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
AsyncFunction("updateMap") { (cityCode: String, promise: Promise) in
|
|
151
|
+
self.startDownloadInternal(cityCode: cityCode, promise: promise)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
AsyncFunction("checkUpdate") { (cityCode: String, promise: Promise) in
|
|
155
|
+
// 检查特定城市或全局更新
|
|
156
|
+
self.offlineMapManager.checkNewestVersion { hasNewestVersion in
|
|
157
|
+
if hasNewestVersion {
|
|
158
|
+
// 刷新缓存以获取最新数据
|
|
159
|
+
self.stateQueue.async(flags: .barrier) {
|
|
160
|
+
self.cachedCities = nil
|
|
161
|
+
self.cachedProvinces = nil
|
|
162
|
+
}
|
|
163
|
+
self.parseOfflineMapData()
|
|
164
|
+
}
|
|
165
|
+
promise.resolve(hasNewestVersion)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ==================== 3. 状态查询 ====================
|
|
170
|
+
|
|
171
|
+
AsyncFunction("isMapDownloaded") { (cityCode: String) -> Bool in
|
|
172
|
+
let allCities = self.cachedCities ?? self.offlineMapManager.cities ?? []
|
|
173
|
+
return allCities.first(where: { $0.adcode == cityCode })?.itemStatus == .installed
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 恢复原有方法:getMapStatus
|
|
177
|
+
AsyncFunction("getMapStatus") { (cityCode: String) -> [String: Any] in
|
|
178
|
+
let allCities = self.cachedCities ?? self.offlineMapManager.cities ?? []
|
|
179
|
+
if let city = allCities.first(where: { $0.adcode == cityCode }) {
|
|
180
|
+
return self.convertCityToDict(city)
|
|
181
|
+
}
|
|
182
|
+
return [:]
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 恢复原有方法:getTotalProgress (iOS SDK 无总体进度,返回 0)
|
|
186
|
+
AsyncFunction("getTotalProgress") { () -> Double in
|
|
187
|
+
return 0.0
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
AsyncFunction("getDownloadingCities") { () -> [String] in
|
|
191
|
+
return Array(self.downloadingCities)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ==================== 4. 存储管理 ====================
|
|
195
|
+
|
|
196
|
+
AsyncFunction("getStorageSize") { () -> Int64 in
|
|
197
|
+
let allCities = self.cachedCities ?? self.offlineMapManager.cities ?? []
|
|
198
|
+
let installed = allCities.filter { $0.itemStatus == .installed }
|
|
199
|
+
// 修复类型转换报错:Int64(0)
|
|
200
|
+
return installed.reduce(Int64(0)) { $0 + ($1.downloadedSize > 0 ? $1.downloadedSize : $1.size) }
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 恢复原有方法:getStorageInfo
|
|
204
|
+
AsyncFunction("getStorageInfo") { () -> [String: Any] in
|
|
205
|
+
// 1. 计算离线地图占用
|
|
206
|
+
let allCities = self.cachedCities ?? self.offlineMapManager.cities ?? []
|
|
207
|
+
let installed = allCities.filter { $0.itemStatus == .installed }
|
|
208
|
+
let offlineMapSize = installed.reduce(Int64(0)) { $0 + ($1.downloadedSize > 0 ? $1.downloadedSize : $1.size) }
|
|
209
|
+
|
|
210
|
+
// 2. 获取系统存储信息
|
|
211
|
+
var totalSpace: Int64 = 0
|
|
212
|
+
var availableSpace: Int64 = 0
|
|
213
|
+
var usedSpace: Int64 = 0
|
|
214
|
+
|
|
215
|
+
do {
|
|
216
|
+
let fileURL = URL(fileURLWithPath: NSHomeDirectory() as String)
|
|
217
|
+
let values = try fileURL.resourceValues(forKeys: [.volumeTotalCapacityKey, .volumeAvailableCapacityKey])
|
|
218
|
+
if let total = values.volumeTotalCapacity { totalSpace = Int64(total) }
|
|
219
|
+
if let available = values.volumeAvailableCapacity { availableSpace = Int64(available) }
|
|
220
|
+
usedSpace = totalSpace - availableSpace
|
|
221
|
+
} catch {
|
|
222
|
+
// Failed to get storage info
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return [
|
|
226
|
+
"totalSpace": totalSpace,
|
|
227
|
+
"usedSpace": usedSpace,
|
|
228
|
+
"availableSpace": availableSpace,
|
|
229
|
+
"offlineMapSize": offlineMapSize
|
|
230
|
+
]
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
AsyncFunction("clearAllMaps") {
|
|
234
|
+
self.offlineMapManager.clearDisk()
|
|
235
|
+
self.downloadingCities.removeAll()
|
|
236
|
+
self.pausedCities.removeAll()
|
|
237
|
+
self.cachedCities = nil
|
|
238
|
+
self.parseOfflineMapData()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
Function("setStoragePath") { (path: String) in
|
|
242
|
+
// iOS does not support changing storage path
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// 恢复原有方法:getStoragePath
|
|
246
|
+
AsyncFunction("getStoragePath") { () -> String in
|
|
247
|
+
return NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first ?? ""
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ==================== 5. 批量操作 ====================
|
|
251
|
+
|
|
252
|
+
// 恢复原有方法:batchDownload
|
|
253
|
+
AsyncFunction("batchDownload") { (cityCodes: [String], allowCellular: Bool?) in
|
|
254
|
+
// 确保初始化
|
|
255
|
+
self.ensureSetup { success in
|
|
256
|
+
guard success else { return }
|
|
257
|
+
let allCities = self.cachedCities ?? self.offlineMapManager.cities ?? []
|
|
258
|
+
|
|
259
|
+
cityCodes.forEach { cityCode in
|
|
260
|
+
if let city = allCities.first(where: { $0.adcode == cityCode }) {
|
|
261
|
+
self.downloadingCities.insert(cityCode)
|
|
262
|
+
self.pausedCities.remove(cityCode)
|
|
263
|
+
// 批量下载也建议开启后台
|
|
264
|
+
self.offlineMapManager.downloadItem(city, shouldContinueWhenAppEntersBackground: true) { [weak self] item, status, info in
|
|
265
|
+
guard let self = self, let item = item else { return }
|
|
266
|
+
self.handleDownloadCallback(item: item, status: status, info: info)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// 恢复原有方法:batchDelete
|
|
274
|
+
AsyncFunction("batchDelete") { (cityCodes: [String]) in
|
|
275
|
+
let allCities = self.cachedCities ?? self.offlineMapManager.cities ?? []
|
|
276
|
+
cityCodes.forEach { cityCode in
|
|
277
|
+
if let city = allCities.first(where: { $0.adcode == cityCode }) {
|
|
278
|
+
self.offlineMapManager.delete(city)
|
|
279
|
+
}
|
|
280
|
+
self.downloadingCities.remove(cityCode)
|
|
281
|
+
self.pausedCities.remove(cityCode)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// 恢复原有方法:batchUpdate
|
|
286
|
+
AsyncFunction("batchUpdate") { (cityCodes: [String]) in
|
|
287
|
+
self.ensureSetup { success in
|
|
288
|
+
guard success else { return }
|
|
289
|
+
let allCities = self.cachedCities ?? self.offlineMapManager.cities ?? []
|
|
290
|
+
|
|
291
|
+
cityCodes.forEach { cityCode in
|
|
292
|
+
if let city = allCities.first(where: { $0.adcode == cityCode }) {
|
|
293
|
+
// 只更新已下载的地图
|
|
294
|
+
if city.itemStatus == .installed {
|
|
295
|
+
self.stateQueue.async(flags: .barrier) {
|
|
296
|
+
self._downloadingCities.insert(cityCode)
|
|
297
|
+
self._pausedCities.remove(cityCode)
|
|
298
|
+
}
|
|
299
|
+
self.offlineMapManager.downloadItem(city, shouldContinueWhenAppEntersBackground: true) { [weak self] item, status, info in
|
|
300
|
+
guard let self = self, let item = item else { return }
|
|
301
|
+
self.handleDownloadCallback(item: item, status: status, info: info)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
AsyncFunction("pauseAllDownloads") {
|
|
310
|
+
self.offlineMapManager.cancelAll()
|
|
311
|
+
for cityCode in self.downloadingCities {
|
|
312
|
+
self.pausedCities.insert(cityCode)
|
|
313
|
+
self.sendEvent("onDownloadPaused", ["cityCode": cityCode, "cityName": ""])
|
|
314
|
+
}
|
|
315
|
+
self.downloadingCities.removeAll()
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// 恢复原有方法:resumeAllDownloads
|
|
319
|
+
AsyncFunction("resumeAllDownloads") {
|
|
320
|
+
// iOS SDK 没有 resumeAll,只能尝试恢复 pausedCities 列表中的城市
|
|
321
|
+
self.ensureSetup { success in
|
|
322
|
+
guard success else { return }
|
|
323
|
+
let allCities = self.cachedCities ?? self.offlineMapManager.cities ?? []
|
|
324
|
+
let pausedList = Array(self.pausedCities)
|
|
325
|
+
|
|
326
|
+
pausedList.forEach { cityCode in
|
|
327
|
+
if let city = allCities.first(where: { $0.adcode == cityCode }) {
|
|
328
|
+
self.pausedCities.remove(cityCode)
|
|
329
|
+
self.downloadingCities.insert(cityCode)
|
|
330
|
+
self.offlineMapManager.downloadItem(city, shouldContinueWhenAppEntersBackground: true) { [weak self] item, status, info in
|
|
331
|
+
guard let self = self, let item = item else { return }
|
|
332
|
+
self.handleDownloadCallback(item: item, status: status, info: info)
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ==================== 内部私有方法 ====================
|
|
341
|
+
|
|
342
|
+
private func startDownloadInternal(cityCode: String, promise: Promise) {
|
|
343
|
+
self.ensureSetup { success in
|
|
344
|
+
guard success else {
|
|
345
|
+
promise.reject("ERR_SETUP", "SDK setup failed")
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
let allCities = self.cachedCities ?? self.offlineMapManager.cities ?? []
|
|
350
|
+
guard let city = allCities.first(where: { $0.adcode == cityCode }) else {
|
|
351
|
+
promise.reject("ERR_CITY", "City not found: \(cityCode)")
|
|
352
|
+
return
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
self.downloadingCities.insert(cityCode)
|
|
356
|
+
self.pausedCities.remove(cityCode)
|
|
357
|
+
|
|
358
|
+
// 开启后台下载
|
|
359
|
+
self.offlineMapManager.downloadItem(city, shouldContinueWhenAppEntersBackground: true) { [weak self] item, status, info in
|
|
360
|
+
guard let self = self, let item = item else { return }
|
|
361
|
+
self.handleDownloadCallback(item: item, status: status, info: info)
|
|
362
|
+
}
|
|
363
|
+
promise.resolve(true)
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private func ensureSetup(completion: @escaping (Bool) -> Void) {
|
|
368
|
+
if isSetupComplete { completion(true); return }
|
|
369
|
+
setupWaiters.append(completion)
|
|
370
|
+
if isSetupInProgress { return }
|
|
371
|
+
|
|
372
|
+
isSetupInProgress = true
|
|
373
|
+
MAOfflineMap.shared().setup { [weak self] success in
|
|
374
|
+
guard let self = self else { return }
|
|
375
|
+
self.isSetupInProgress = false
|
|
376
|
+
self.isSetupComplete = success
|
|
377
|
+
if success { self.parseOfflineMapData() }
|
|
378
|
+
let waiters = self.setupWaiters
|
|
379
|
+
self.setupWaiters.removeAll()
|
|
380
|
+
waiters.forEach { $0(success) }
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
private func parseOfflineMapData() {
|
|
385
|
+
let map = MAOfflineMap.shared()
|
|
386
|
+
stateQueue.async(flags: .barrier) {
|
|
387
|
+
self.cachedCities = map?.cities
|
|
388
|
+
self.cachedProvinces = map?.provinces
|
|
389
|
+
self.cachedMunicipalities = map?.municipalities
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
private func handleDownloadCallback(item: MAOfflineItem, status: MAOfflineMapDownloadStatus, info: Any?) {
|
|
394
|
+
guard let city = item as? MAOfflineCity, let cityCode = city.adcode else { return }
|
|
395
|
+
let cityName = city.name ?? ""
|
|
396
|
+
|
|
397
|
+
switch status {
|
|
398
|
+
case .progress:
|
|
399
|
+
if let infoDict = info as? [String: Any],
|
|
400
|
+
let received = infoDict[MAOfflineMapDownloadReceivedSizeKey] as? Int64,
|
|
401
|
+
let expected = infoDict[MAOfflineMapDownloadExpectedSizeKey] as? Int64,
|
|
402
|
+
expected > 0 {
|
|
403
|
+
let progress = Int((Double(received) / Double(expected)) * 100)
|
|
404
|
+
self.sendEvent("onDownloadProgress", [
|
|
405
|
+
"cityCode": cityCode, "cityName": cityName, "progress": progress, "receivedSize": received, "expectedSize": expected
|
|
406
|
+
])
|
|
407
|
+
}
|
|
408
|
+
case .unzip:
|
|
409
|
+
self.sendEvent("onUnzipProgress", ["cityCode": cityCode, "cityName": cityName])
|
|
410
|
+
case .finished:
|
|
411
|
+
// 只处理 .finished 状态,避免重复事件
|
|
412
|
+
self.stateQueue.async(flags: .barrier) {
|
|
413
|
+
self._downloadingCities.remove(cityCode)
|
|
414
|
+
self._pausedCities.remove(cityCode)
|
|
415
|
+
}
|
|
416
|
+
self.sendEvent("onDownloadComplete", ["cityCode": cityCode, "cityName": cityName])
|
|
417
|
+
case .completed:
|
|
418
|
+
// .completed 状态只更新内部状态,不发送事件
|
|
419
|
+
self.stateQueue.async(flags: .barrier) {
|
|
420
|
+
self._downloadingCities.remove(cityCode)
|
|
421
|
+
self._pausedCities.remove(cityCode)
|
|
422
|
+
}
|
|
423
|
+
case .cancelled:
|
|
424
|
+
let wasPaused = stateQueue.sync { _pausedCities.contains(cityCode) }
|
|
425
|
+
self.stateQueue.async(flags: .barrier) {
|
|
426
|
+
self._downloadingCities.remove(cityCode)
|
|
427
|
+
}
|
|
428
|
+
if wasPaused {
|
|
429
|
+
self.sendEvent("onDownloadPaused", ["cityCode": cityCode, "cityName": cityName])
|
|
430
|
+
} else {
|
|
431
|
+
self.sendEvent("onDownloadCancelled", ["cityCode": cityCode, "cityName": cityName])
|
|
432
|
+
}
|
|
433
|
+
case .error:
|
|
434
|
+
self.stateQueue.async(flags: .barrier) {
|
|
435
|
+
self._downloadingCities.remove(cityCode)
|
|
436
|
+
}
|
|
437
|
+
let err = (info as? NSError)?.localizedDescription ?? "Error"
|
|
438
|
+
self.sendEvent("onDownloadError", ["cityCode": cityCode, "cityName": cityName, "error": err])
|
|
439
|
+
default: break
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
private func convertCityToDict(_ city: MAOfflineCity) -> [String: Any] {
|
|
444
|
+
var status = "not_downloaded"
|
|
445
|
+
switch city.itemStatus {
|
|
446
|
+
case .installed: status = "downloaded"
|
|
447
|
+
case .cached: status = "downloading"
|
|
448
|
+
case .expired: status = "expired"
|
|
449
|
+
default: status = "not_downloaded"
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// 线程安全地检查状态
|
|
453
|
+
let isDownloading = stateQueue.sync { _downloadingCities.contains(city.adcode) }
|
|
454
|
+
let isPaused = stateQueue.sync { _pausedCities.contains(city.adcode) }
|
|
455
|
+
|
|
456
|
+
if isDownloading { status = "downloading" }
|
|
457
|
+
else if isPaused { status = "paused" }
|
|
458
|
+
|
|
459
|
+
return [
|
|
460
|
+
"cityCode": city.adcode ?? "",
|
|
461
|
+
"cityName": city.name ?? "",
|
|
462
|
+
"size": city.size,
|
|
463
|
+
"status": status,
|
|
464
|
+
"downloadedSize": city.downloadedSize,
|
|
465
|
+
"version": self.offlineMapManager.version ?? "",
|
|
466
|
+
"progress": city.size > 0 ? Int((Double(city.downloadedSize) / Double(city.size)) * 100) : 0
|
|
467
|
+
]
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
private func convertProvinceToDict(_ province: MAOfflineProvince) -> [String: Any] {
|
|
471
|
+
return [
|
|
472
|
+
"cityCode": province.adcode ?? "",
|
|
473
|
+
"cityName": province.name ?? "",
|
|
474
|
+
"size": province.size,
|
|
475
|
+
"status": (province.itemStatus == .installed) ? "downloaded" : "not_downloaded",
|
|
476
|
+
"downloadedSize": province.downloadedSize
|
|
477
|
+
]
|
|
478
|
+
}
|
|
479
|
+
}
|