expo-gaode-map 2.2.38 → 2.2.39-next.0

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 (78) hide show
  1. package/android/build.gradle +6 -31
  2. package/android/src/main/java/expo/modules/gaodemap/ExpoGaodeMapView.kt +35 -12
  3. package/android/src/main/java/expo/modules/gaodemap/ExpoGaodeMapViewModule.kt +2 -0
  4. package/android/src/main/java/expo/modules/gaodemap/managers/UIManager.kt +8 -0
  5. package/android/src/main/java/expo/modules/gaodemap/overlays/MarkerView.kt +13 -5
  6. package/build/ExpoGaodeMapOfflineModule.js +1 -1
  7. package/build/ExpoGaodeMapOfflineModule.js.map +1 -1
  8. package/build/ExpoGaodeMapView.js +2 -8
  9. package/build/ExpoGaodeMapView.js.map +1 -1
  10. package/build/components/AreaMaskOverlay.d.ts +1 -2
  11. package/build/components/AreaMaskOverlay.d.ts.map +1 -1
  12. package/build/components/AreaMaskOverlay.js +2 -1
  13. package/build/components/AreaMaskOverlay.js.map +1 -1
  14. package/build/components/FoldableMapView.d.ts +3 -2
  15. package/build/components/FoldableMapView.d.ts.map +1 -1
  16. package/build/components/FoldableMapView.js +2 -1
  17. package/build/components/FoldableMapView.js.map +1 -1
  18. package/build/components/MapUI.js +2 -1
  19. package/build/components/MapUI.js.map +1 -1
  20. package/build/components/RouteOverlay.d.ts +1 -2
  21. package/build/components/RouteOverlay.d.ts.map +1 -1
  22. package/build/components/RouteOverlay.js +5 -5
  23. package/build/components/RouteOverlay.js.map +1 -1
  24. package/build/components/overlays/Circle.d.ts +1 -1
  25. package/build/components/overlays/Circle.d.ts.map +1 -1
  26. package/build/components/overlays/Circle.js +2 -1
  27. package/build/components/overlays/Circle.js.map +1 -1
  28. package/build/components/overlays/Cluster.d.ts +1 -1
  29. package/build/components/overlays/Cluster.d.ts.map +1 -1
  30. package/build/components/overlays/Cluster.js +2 -1
  31. package/build/components/overlays/Cluster.js.map +1 -1
  32. package/build/components/overlays/HeatMap.d.ts +1 -1
  33. package/build/components/overlays/HeatMap.d.ts.map +1 -1
  34. package/build/components/overlays/HeatMap.js +2 -1
  35. package/build/components/overlays/HeatMap.js.map +1 -1
  36. package/build/components/overlays/Marker.d.ts +1 -1
  37. package/build/components/overlays/Marker.d.ts.map +1 -1
  38. package/build/components/overlays/Marker.js +8 -7
  39. package/build/components/overlays/Marker.js.map +1 -1
  40. package/build/components/overlays/MultiPoint.d.ts +1 -2
  41. package/build/components/overlays/MultiPoint.d.ts.map +1 -1
  42. package/build/components/overlays/MultiPoint.js +2 -1
  43. package/build/components/overlays/MultiPoint.js.map +1 -1
  44. package/build/components/overlays/Polygon.d.ts +1 -1
  45. package/build/components/overlays/Polygon.d.ts.map +1 -1
  46. package/build/components/overlays/Polygon.js +2 -1
  47. package/build/components/overlays/Polygon.js.map +1 -1
  48. package/build/components/overlays/Polyline.d.ts +1 -1
  49. package/build/components/overlays/Polyline.d.ts.map +1 -1
  50. package/build/components/overlays/Polyline.js +2 -1
  51. package/build/components/overlays/Polyline.js.map +1 -1
  52. package/build/module/geometry.d.ts.map +1 -1
  53. package/build/module/geometry.js +2 -1
  54. package/build/module/geometry.js.map +1 -1
  55. package/build/search/ExpoGaodeMapSearchModule.d.ts +1 -1
  56. package/build/search/ExpoGaodeMapSearchModule.d.ts.map +1 -1
  57. package/build/search/ExpoGaodeMapSearchModule.js.map +1 -1
  58. package/build/types/common.types.d.ts +1 -1
  59. package/build/types/common.types.d.ts.map +1 -1
  60. package/build/types/common.types.js.map +1 -1
  61. package/build/types/index.d.ts +1 -1
  62. package/build/types/index.d.ts.map +1 -1
  63. package/build/types/index.js.map +1 -1
  64. package/build/types/map-view.types.d.ts +11 -0
  65. package/build/types/map-view.types.d.ts.map +1 -1
  66. package/build/types/map-view.types.js.map +1 -1
  67. package/build/utils/GeoUtils.d.ts +2 -2
  68. package/build/utils/GeoUtils.d.ts.map +1 -1
  69. package/build/utils/GeoUtils.js +3 -3
  70. package/build/utils/GeoUtils.js.map +1 -1
  71. package/build/utils/RouteUtils.d.ts.map +1 -1
  72. package/build/utils/RouteUtils.js +5 -2
  73. package/build/utils/RouteUtils.js.map +1 -1
  74. package/ios/ExpoGaodeMapView.swift +106 -10
  75. package/ios/ExpoGaodeMapViewModule.swift +5 -0
  76. package/ios/overlays/MarkerView.swift +104 -32
  77. package/package.json +9 -7
  78. package/plugin/build/withGaodeMap.js +3 -2
@@ -77,6 +77,8 @@ class MarkerView: ExpoView {
77
77
  private var pendingSubviewRefreshTask: DispatchWorkItem?
78
78
  /// 最近一次应用到 annotationView 的 children 结构签名
79
79
  private var lastRenderedChildrenSignature: String?
80
+ /// 最近一次成功渲染的 children 图片,用于缓存未命中时避免闪烁
81
+ private var lastRenderedChildrenImage: UIImage?
80
82
  /// 上次设置的地图引用(防止重复调用)
81
83
  private weak var lastSetMapView: MAMapView?
82
84
 
@@ -156,7 +158,11 @@ class MarkerView: ExpoView {
156
158
  func setCacheKey(_ key: String?) {
157
159
  guard cacheKey != key else { return }
158
160
  self.cacheKey = key
159
- refreshAnnotationAppearance(invalidateChildrenCache: !subviews.isEmpty)
161
+ if !subviews.isEmpty {
162
+ scheduleChildrenImageRefresh()
163
+ } else {
164
+ refreshAnnotationAppearance()
165
+ }
160
166
  }
161
167
 
162
168
  /**
@@ -233,9 +239,8 @@ class MarkerView: ExpoView {
233
239
  let size = resolvedContentSubviewSize(defaultSize: CGSize(width: 200, height: 60))
234
240
  let key = childrenCacheKey(for: size)
235
241
  if let cached = IconBitmapCache.shared.image(forKey: key) {
236
- annotationView?.image = cached
237
242
  if let annotationView = annotationView {
238
- applyCenterOffset(to: annotationView, defaultOffset: .zero)
243
+ applyChildrenImage(cached, to: annotationView, signature: childrenRenderSignature())
239
244
  }
240
245
  return annotationView
241
246
  }
@@ -244,9 +249,8 @@ class MarkerView: ExpoView {
244
249
  DispatchQueue.main.async { [weak self, weak annotationView] in
245
250
  guard let self = self, let annotationView = annotationView else { return }
246
251
  guard self.isAnnotationView(annotationView, boundTo: annotation) else { return }
247
- if let generated = self.createImageFromSubviews() {
248
- annotationView.image = generated
249
- self.applyCenterOffset(to: annotationView, defaultOffset: .zero)
252
+ if let generated = self.createImageFromSubviews(size: size) {
253
+ self.applyChildrenImage(generated, to: annotationView, signature: self.childrenRenderSignature())
250
254
  }
251
255
  }
252
256
  return annotationView
@@ -307,9 +311,7 @@ class MarkerView: ExpoView {
307
311
 
308
312
  // 🔑 如果有 children,使用自定义视图
309
313
  if self.subviews.count > 0 {
310
- let reuseToken = cacheKey?.replacingOccurrences(of: "|", with: "_")
311
- ?? String(ObjectIdentifier(self).hashValue)
312
- let reuseId = "custom_marker_children_\(reuseToken)" + (growAnimation ? "_grow" : "")
314
+ let reuseId = "custom_marker_children_\(ObjectIdentifier(self).hashValue)" + (growAnimation ? "_grow" : "")
313
315
  var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId)
314
316
  if annotationView == nil {
315
317
  if growAnimation {
@@ -331,37 +333,44 @@ class MarkerView: ExpoView {
331
333
  // 生成 cacheKey 或 fallback 到 identifier
332
334
  let size = resolvedContentSubviewSize(defaultSize: CGSize(width: 200, height: 40))
333
335
  let key = childrenCacheKey(for: size)
336
+ let signature = childrenRenderSignature()
334
337
 
335
338
  // 1) 如果缓存命中,直接同步返回图像(fast path)
336
339
  if let cached = IconBitmapCache.shared.image(forKey: key) {
337
- annotationView?.image = cached
338
340
  if let annotationView = annotationView {
339
- applyCenterOffset(to: annotationView, defaultOffset: .zero)
341
+ applyChildrenImage(cached, to: annotationView, signature: signature)
340
342
  }
341
343
  return annotationView
342
344
  }
343
345
 
344
- // 2) 缓存未命中:返回占位(透明),并异步在主线程生成图像然后回填
345
- UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
346
- let transparentImage = UIGraphicsGetImageFromCurrentImageContext()
347
- UIGraphicsEndImageContext()
348
- annotationView?.image = transparentImage
346
+ // 2) 缓存未命中:先尝试同步渲染,失败时保留旧图,避免点击切态闪烁。
347
+ if let generated = createImageFromSubviews(size: size) {
348
+ if let annotationView = annotationView {
349
+ applyChildrenImage(generated, to: annotationView, signature: signature)
350
+ }
351
+ return annotationView
352
+ }
349
353
 
350
- // 🔑 修复:延长延迟时间,给 React Native Image 更多加载时间
354
+ if annotationView?.image == nil, let previousImage = lastRenderedChildrenImage {
355
+ annotationView?.image = previousImage
356
+ if let annotationView = annotationView {
357
+ applyCenterOffset(to: annotationView, defaultOffset: .zero)
358
+ }
359
+ }
360
+
361
+ // 图片内容可能尚未就绪,保留旧图并延迟重试,避免透明闪烁。
351
362
  DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self, weak annotationView] in
352
363
  guard let self = self, let annotationView = annotationView else { return }
353
364
  guard self.isAnnotationView(annotationView, boundTo: annotation) else { return }
354
365
  // 再次检查缓存(避免重复渲染)
355
366
  if let cached = IconBitmapCache.shared.image(forKey: key) {
356
- annotationView.image = cached
357
- self.applyCenterOffset(to: annotationView, defaultOffset: .zero)
367
+ self.applyChildrenImage(cached, to: annotationView, signature: signature)
358
368
  return
359
369
  }
360
370
 
361
371
  // 调用你的原生渲染逻辑(保留空白检测、多次 layout)
362
- if let generated = self.createImageFromSubviews() {
363
- annotationView.image = generated
364
- self.applyCenterOffset(to: annotationView, defaultOffset: .zero)
372
+ if let generated = self.createImageFromSubviews(size: size) {
373
+ self.applyChildrenImage(generated, to: annotationView, signature: signature)
365
374
  } else if self.hasPendingImageContent() {
366
375
  self.scheduleSubviewRefresh(allowFallbackToDefault: false)
367
376
  }
@@ -499,8 +508,8 @@ class MarkerView: ExpoView {
499
508
  /**
500
509
  * 将子视图转换为图片
501
510
  */
502
- private func createImageFromSubviews() -> UIImage? {
503
- let size = resolvedContentSubviewSize(defaultSize: CGSize(width: 200, height: 60))
511
+ private func createImageFromSubviews(size explicitSize: CGSize? = nil) -> UIImage? {
512
+ let size = explicitSize ?? resolvedContentSubviewSize(defaultSize: CGSize(width: 200, height: 60))
504
513
  let key = childrenCacheKey(for: size)
505
514
 
506
515
  if let cachedImage = IconBitmapCache.shared.image(forKey: key) {
@@ -549,6 +558,70 @@ class MarkerView: ExpoView {
549
558
  return image
550
559
  }
551
560
 
561
+ private func applyChildrenImage(_ image: UIImage, to annotationView: MAAnnotationView, signature: String? = nil) {
562
+ annotationView.image = image
563
+ applyCenterOffset(to: annotationView, defaultOffset: .zero)
564
+ annotationView.canShowCallout = false
565
+ annotationView.isDraggable = draggable
566
+ lastRenderedChildrenImage = image
567
+ if let resolvedSignature = signature {
568
+ lastRenderedChildrenSignature = resolvedSignature
569
+ }
570
+ }
571
+
572
+ private func refreshChildrenImageInPlace(invalidateChildrenCache: Bool = false) {
573
+ guard !isRemoving else { return }
574
+
575
+ let refresh = { [weak self] in
576
+ guard let self = self, !self.isRemoving, !self.subviews.isEmpty else { return }
577
+
578
+ if invalidateChildrenCache {
579
+ self.lastRenderedChildrenSignature = nil
580
+ self.invalidateCurrentChildrenCache()
581
+ }
582
+
583
+ guard let targetView = self.annotationView ?? self.animatedAnnotationView else {
584
+ self.refreshAnnotationAppearance(invalidateChildrenCache: invalidateChildrenCache)
585
+ return
586
+ }
587
+
588
+ let size = self.resolvedContentSubviewSize(defaultSize: CGSize(width: 200, height: 40))
589
+ let key = self.childrenCacheKey(for: size)
590
+ let signature = self.childrenRenderSignature()
591
+
592
+ if let cached = IconBitmapCache.shared.image(forKey: key) {
593
+ self.applyChildrenImage(cached, to: targetView, signature: signature)
594
+ return
595
+ }
596
+
597
+ if let generated = self.createImageFromSubviews(size: size) {
598
+ self.applyChildrenImage(generated, to: targetView, signature: signature)
599
+ return
600
+ }
601
+
602
+ if self.hasPendingImageContent() {
603
+ self.scheduleSubviewRefresh(allowFallbackToDefault: false)
604
+ }
605
+ }
606
+
607
+ if Thread.isMainThread {
608
+ refresh()
609
+ } else {
610
+ DispatchQueue.main.async(execute: refresh)
611
+ }
612
+ }
613
+
614
+ private func scheduleChildrenImageRefresh(invalidateChildrenCache: Bool = false) {
615
+ pendingSubviewRefreshTask?.cancel()
616
+
617
+ let task = DispatchWorkItem { [weak self] in
618
+ self?.refreshChildrenImageInPlace(invalidateChildrenCache: invalidateChildrenCache)
619
+ }
620
+
621
+ pendingSubviewRefreshTask = task
622
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.02, execute: task)
623
+ }
624
+
552
625
  private func iconCacheKey(for iconUri: String) -> String {
553
626
  let baseKey = cacheKey ?? "icon|\(iconUri)"
554
627
  return "\(baseKey)|\(Int(iconWidth.rounded()))x\(Int(iconHeight.rounded()))"
@@ -769,6 +842,8 @@ class MarkerView: ExpoView {
769
842
  animatedAnnotationView?.annotation = nil
770
843
  annotationView = nil
771
844
  animatedAnnotationView = nil
845
+ lastRenderedChildrenImage = nil
846
+ lastRenderedChildrenSignature = nil
772
847
  isAnimating = false
773
848
  lastSetMapView = nil
774
849
  self.mapView = nil
@@ -850,12 +925,9 @@ class MarkerView: ExpoView {
850
925
  return
851
926
  }
852
927
 
853
- if let image = createImageFromSubviews() {
854
- annotationView.image = image
855
- applyCenterOffset(to: annotationView, defaultOffset: .zero)
856
- annotationView.canShowCallout = false
857
- annotationView.isDraggable = draggable
858
- lastRenderedChildrenSignature = signature
928
+ let size = resolvedContentSubviewSize(defaultSize: CGSize(width: 200, height: 40))
929
+ if let image = createImageFromSubviews(size: size) {
930
+ applyChildrenImage(image, to: annotationView, signature: signature)
859
931
  }
860
932
  }
861
933
 
@@ -1023,7 +1095,7 @@ class MarkerView: ExpoView {
1023
1095
  guard contentWidth != width else { return }
1024
1096
  contentWidth = width
1025
1097
  if !subviews.isEmpty {
1026
- refreshAnnotationAppearance(invalidateChildrenCache: true)
1098
+ scheduleChildrenImageRefresh(invalidateChildrenCache: true)
1027
1099
  }
1028
1100
  }
1029
1101
 
@@ -1031,7 +1103,7 @@ class MarkerView: ExpoView {
1031
1103
  guard contentHeight != height else { return }
1032
1104
  contentHeight = height
1033
1105
  if !subviews.isEmpty {
1034
- refreshAnnotationAppearance(invalidateChildrenCache: true)
1106
+ scheduleChildrenImageRefresh(invalidateChildrenCache: true)
1035
1107
  }
1036
1108
  }
1037
1109
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-gaode-map",
3
- "version": "2.2.38",
3
+ "version": "2.2.39-next.0",
4
4
  "description": "Expo Modules AMap (Gaode Map) stack for Expo/React Native: Config Plugin, New Architecture support, maps, location, search, offline maps, and react-native-amap3d migration guidance.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -18,7 +18,7 @@
18
18
  "build": "expo-module build && yarn build:plugin",
19
19
  "build:plugin": "tsc --project plugin/tsconfig.json",
20
20
  "clean": "expo-module clean && rm -rf plugin/build",
21
- "lint": "expo-module lint",
21
+ "lint": "eslint \"src/**/*.{ts,tsx}\"",
22
22
  "test": "npx jest --no-watchman --config jest.config.js --runInBand",
23
23
  "prepare": "expo-module prepare && yarn build:plugin",
24
24
  "postinstall": "node scripts/check-expo-modules.js",
@@ -75,13 +75,15 @@
75
75
  "@babel/runtime": "^7.25.7",
76
76
  "@testing-library/react-native": "^13.3.3",
77
77
  "@types/jest": "^29.5.14",
78
+ "@types/node": "^22.18.0",
78
79
  "@types/react": "~19.2.10",
79
- "expo": "^55.0.8",
80
- "expo-module-scripts": "^55.0.2",
80
+ "eslint": "^9.25.1",
81
+ "expo": "^57.0.1",
82
+ "expo-module-scripts": "^56.0.3",
81
83
  "jest": "~29.7.0",
82
- "jest-expo": "~55.0.11",
83
- "react-native": "0.83.2",
84
- "typescript": "^5.9.3"
84
+ "jest-expo": "~57.0.0",
85
+ "react-native": "0.86.0",
86
+ "typescript": "^6.0.3"
85
87
  },
86
88
  "peerDependencies": {
87
89
  "expo": "*",
@@ -146,8 +146,9 @@ const withGaodeMapAndroidManifest = (config, props) => {
146
146
  else {
147
147
  // 更新现有的 API Key
148
148
  const apiKeyIndex = mainApplication['meta-data'].findIndex((item) => item.$?.['android:name'] === 'com.amap.api.v2.apikey');
149
- if (apiKeyIndex !== -1) {
150
- mainApplication['meta-data'][apiKeyIndex].$ = {
149
+ const apiKeyMetaData = mainApplication['meta-data'][apiKeyIndex];
150
+ if (apiKeyMetaData) {
151
+ apiKeyMetaData.$ = {
151
152
  'android:name': 'com.amap.api.v2.apikey',
152
153
  'android:value': props.androidKey,
153
154
  };