@maydon_tech/react-native-nitro-maps 0.1.3 → 0.2.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 (218) hide show
  1. package/LICENSE +1 -1
  2. package/NitroMap.podspec +1 -1
  3. package/README.md +82 -9
  4. package/android/CMakeLists.txt +4 -1
  5. package/android/gradle.properties +4 -4
  6. package/android/src/main/cpp/ClusterEngineJNI.cpp +198 -0
  7. package/android/src/main/kotlin/com/margelo/nitro/nitromap/NitroMap.kt +397 -0
  8. package/android/src/main/kotlin/com/margelo/nitro/nitromap/NitroMapConfig.kt +53 -0
  9. package/android/src/main/{java → kotlin}/com/margelo/nitro/nitromap/NitroMapPackage.kt +4 -4
  10. package/android/src/main/kotlin/com/margelo/nitro/nitromap/NitroMapView.kt +73 -0
  11. package/android/src/main/kotlin/com/margelo/nitro/nitromap/UserLocationManager.kt +295 -0
  12. package/android/src/main/kotlin/com/margelo/nitro/nitromap/clustering/ClusterIconRenderer.kt +111 -0
  13. package/android/src/main/kotlin/com/margelo/nitro/nitromap/clustering/ClusteringManager.kt +104 -0
  14. package/android/src/main/kotlin/com/margelo/nitro/nitromap/clustering/NitroClusterEngine.kt +166 -0
  15. package/android/src/main/kotlin/com/margelo/nitro/nitromap/markers/MarkerIconFactory.kt +303 -0
  16. package/android/src/main/kotlin/com/margelo/nitro/nitromap/markers/MarkerSelectionHandler.kt +72 -0
  17. package/android/src/main/kotlin/com/margelo/nitro/nitromap/markers/PriceMarkerRenderer.kt +159 -0
  18. package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/MapProviderFactory.kt +24 -0
  19. package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/MapProviderInterface.kt +128 -0
  20. package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/google/GoogleMapDelegate.kt +317 -0
  21. package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/google/GoogleMapProvider+Clustering.kt +524 -0
  22. package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/google/GoogleMapProvider+Markers.kt +358 -0
  23. package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/google/GoogleMapProvider+Overlays.kt +272 -0
  24. package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/google/GoogleMapProvider+UserLocation.kt +296 -0
  25. package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/google/GoogleMapProvider.kt +815 -0
  26. package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/google/MarkerTagData.kt +19 -0
  27. package/ios/Clustering/ClusterIconRenderer.swift +3 -3
  28. package/ios/Location/NitroLocationManager.swift +116 -0
  29. package/ios/MarkerRenderer/MarkerIconFactory.swift +1 -3
  30. package/ios/MarkerRenderer/PriceMarkerRenderer.swift +10 -6
  31. package/ios/NitroMap.swift +279 -13
  32. package/ios/NitroMapConfig/NitroMapConfig.swift +45 -0
  33. package/ios/Providers/{GoogleMapDelegate.swift → Google/GoogleMapDelegate.swift} +48 -23
  34. package/ios/Providers/Google/GoogleMapProvider+Camera.swift +180 -0
  35. package/ios/Providers/Google/GoogleMapProvider+Clustering.swift +541 -0
  36. package/ios/Providers/Google/GoogleMapProvider+Markers.swift +270 -0
  37. package/ios/Providers/Google/GoogleMapProvider+Overlays.swift +245 -0
  38. package/ios/Providers/Google/GoogleMapProvider+UserLocation.swift +180 -0
  39. package/ios/Providers/Google/GoogleMapProvider.swift +342 -0
  40. package/ios/Providers/MapProviderFactory.swift +17 -0
  41. package/ios/Providers/MapProviderProtocol.swift +48 -1
  42. package/ios/Shared/ClusterConfig+Factory.swift +2 -2
  43. package/ios/Shared/MapStyleProvider.swift +6 -4
  44. package/ios/Shared/MarkerSelectionHandler.swift +4 -1
  45. package/ios/Utils/ColorValueExtension.swift +46 -67
  46. package/lib/module/components/ImageMarker.js +39 -29
  47. package/lib/module/components/ImageMarker.js.map +1 -1
  48. package/lib/module/components/Marker.js +118 -0
  49. package/lib/module/components/Marker.js.map +1 -0
  50. package/lib/module/components/NitroCircle.js +92 -0
  51. package/lib/module/components/NitroCircle.js.map +1 -0
  52. package/lib/module/components/NitroMap.js +216 -76
  53. package/lib/module/components/NitroMap.js.map +1 -1
  54. package/lib/module/components/NitroPolygon.js +135 -0
  55. package/lib/module/components/NitroPolygon.js.map +1 -0
  56. package/lib/module/components/NitroPolyline.js +115 -0
  57. package/lib/module/components/NitroPolyline.js.map +1 -0
  58. package/lib/module/components/PriceMarker.js +16 -29
  59. package/lib/module/components/PriceMarker.js.map +1 -1
  60. package/lib/module/context/NitroMapContext.js.map +1 -1
  61. package/lib/module/hooks/useNitroCircle.js +18 -0
  62. package/lib/module/hooks/useNitroCircle.js.map +1 -0
  63. package/lib/module/hooks/useNitroMarker.js +26 -9
  64. package/lib/module/hooks/useNitroMarker.js.map +1 -1
  65. package/lib/module/hooks/useNitroOverlay.js +59 -0
  66. package/lib/module/hooks/useNitroOverlay.js.map +1 -0
  67. package/lib/module/hooks/useNitroPolygon.js +18 -0
  68. package/lib/module/hooks/useNitroPolygon.js.map +1 -0
  69. package/lib/module/hooks/useNitroPolyline.js +18 -0
  70. package/lib/module/hooks/useNitroPolyline.js.map +1 -0
  71. package/lib/module/index.js +5 -0
  72. package/lib/module/index.js.map +1 -1
  73. package/lib/module/types/overlay.js +4 -0
  74. package/lib/module/types/overlay.js.map +1 -0
  75. package/lib/module/types/theme.js +4 -0
  76. package/lib/module/types/theme.js.map +1 -0
  77. package/lib/module/utils/colors.js +41 -13
  78. package/lib/module/utils/colors.js.map +1 -1
  79. package/lib/module/utils/validation.js +45 -0
  80. package/lib/module/utils/validation.js.map +1 -0
  81. package/lib/typescript/src/components/ImageMarker.d.ts.map +1 -1
  82. package/lib/typescript/src/components/Marker.d.ts +34 -0
  83. package/lib/typescript/src/components/Marker.d.ts.map +1 -0
  84. package/lib/typescript/src/components/NitroCircle.d.ts +70 -0
  85. package/lib/typescript/src/components/NitroCircle.d.ts.map +1 -0
  86. package/lib/typescript/src/components/NitroMap.d.ts +60 -3
  87. package/lib/typescript/src/components/NitroMap.d.ts.map +1 -1
  88. package/lib/typescript/src/components/NitroPolygon.d.ts +86 -0
  89. package/lib/typescript/src/components/NitroPolygon.d.ts.map +1 -0
  90. package/lib/typescript/src/components/NitroPolyline.d.ts +84 -0
  91. package/lib/typescript/src/components/NitroPolyline.d.ts.map +1 -0
  92. package/lib/typescript/src/components/PriceMarker.d.ts +0 -5
  93. package/lib/typescript/src/components/PriceMarker.d.ts.map +1 -1
  94. package/lib/typescript/src/context/NitroMapContext.d.ts +2 -0
  95. package/lib/typescript/src/context/NitroMapContext.d.ts.map +1 -1
  96. package/lib/typescript/src/hooks/useNitroCircle.d.ts +7 -0
  97. package/lib/typescript/src/hooks/useNitroCircle.d.ts.map +1 -0
  98. package/lib/typescript/src/hooks/useNitroMarker.d.ts +20 -0
  99. package/lib/typescript/src/hooks/useNitroMarker.d.ts.map +1 -1
  100. package/lib/typescript/src/hooks/useNitroOverlay.d.ts +26 -0
  101. package/lib/typescript/src/hooks/useNitroOverlay.d.ts.map +1 -0
  102. package/lib/typescript/src/hooks/useNitroPolygon.d.ts +7 -0
  103. package/lib/typescript/src/hooks/useNitroPolygon.d.ts.map +1 -0
  104. package/lib/typescript/src/hooks/useNitroPolyline.d.ts +7 -0
  105. package/lib/typescript/src/hooks/useNitroPolyline.d.ts.map +1 -0
  106. package/lib/typescript/src/index.d.ts +15 -2
  107. package/lib/typescript/src/index.d.ts.map +1 -1
  108. package/lib/typescript/src/specs/NitroMap.nitro.d.ts +248 -6
  109. package/lib/typescript/src/specs/NitroMap.nitro.d.ts.map +1 -1
  110. package/lib/typescript/src/types/map.d.ts +34 -4
  111. package/lib/typescript/src/types/map.d.ts.map +1 -1
  112. package/lib/typescript/src/types/marker.d.ts +24 -36
  113. package/lib/typescript/src/types/marker.d.ts.map +1 -1
  114. package/lib/typescript/src/types/overlay.d.ts +75 -0
  115. package/lib/typescript/src/types/overlay.d.ts.map +1 -0
  116. package/lib/typescript/src/types/theme.d.ts +93 -0
  117. package/lib/typescript/src/types/theme.d.ts.map +1 -0
  118. package/lib/typescript/src/utils/colors.d.ts +6 -8
  119. package/lib/typescript/src/utils/colors.d.ts.map +1 -1
  120. package/lib/typescript/src/utils/validation.d.ts +12 -0
  121. package/lib/typescript/src/utils/validation.d.ts.map +1 -0
  122. package/nitrogen/generated/android/c++/JCircleData.hpp +94 -0
  123. package/nitrogen/generated/android/c++/JClusterConfig.hpp +5 -7
  124. package/nitrogen/generated/android/c++/JFunc_void_UserLocationChangeEvent.hpp +79 -0
  125. package/nitrogen/generated/android/c++/JFunc_void_UserTrackingMode.hpp +77 -0
  126. package/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
  127. package/nitrogen/generated/android/c++/JHybridNitroMapSpec.cpp +328 -21
  128. package/nitrogen/generated/android/c++/JHybridNitroMapSpec.hpp +53 -2
  129. package/nitrogen/generated/android/c++/JMarkerAnimation.hpp +3 -6
  130. package/nitrogen/generated/android/c++/JMarkerData.hpp +15 -3
  131. package/nitrogen/generated/android/c++/JPolygonData.hpp +149 -0
  132. package/nitrogen/generated/android/c++/JPolylineData.hpp +113 -0
  133. package/nitrogen/generated/android/c++/JUserLocationChangeEvent.hpp +70 -0
  134. package/nitrogen/generated/android/c++/JUserTrackingMode.hpp +62 -0
  135. package/nitrogen/generated/android/c++/views/JHybridNitroMapStateUpdater.cpp +72 -4
  136. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/CircleData.kt +62 -0
  137. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/ClusterConfig.kt +4 -4
  138. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/Func_void_UserLocationChangeEvent.kt +80 -0
  139. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/Func_void_UserTrackingMode.kt +80 -0
  140. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/Func_void_std__string.kt +80 -0
  141. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/HybridNitroMapSpec.kt +228 -2
  142. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/MarkerAnimation.kt +1 -2
  143. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/MarkerData.kt +12 -3
  144. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/PolygonData.kt +62 -0
  145. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/PolylineData.kt +62 -0
  146. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/UserLocationChangeEvent.kt +47 -0
  147. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/{ClusterAnimationStyle.kt → UserTrackingMode.kt} +6 -8
  148. package/nitrogen/generated/android/nitromapOnLoad.cpp +6 -0
  149. package/nitrogen/generated/ios/NitroMap-Swift-Cxx-Bridge.cpp +24 -0
  150. package/nitrogen/generated/ios/NitroMap-Swift-Cxx-Bridge.hpp +175 -17
  151. package/nitrogen/generated/ios/NitroMap-Swift-Cxx-Umbrella.hpp +15 -3
  152. package/nitrogen/generated/ios/c++/HybridNitroMapSpecSwift.hpp +249 -16
  153. package/nitrogen/generated/ios/c++/views/HybridNitroMapComponent.mm +90 -5
  154. package/nitrogen/generated/ios/swift/CircleData.swift +143 -0
  155. package/nitrogen/generated/ios/swift/ClusterConfig.swift +22 -15
  156. package/nitrogen/generated/ios/swift/Func_void_UserLocationChangeEvent.swift +47 -0
  157. package/nitrogen/generated/ios/swift/Func_void_UserTrackingMode.swift +47 -0
  158. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +47 -0
  159. package/nitrogen/generated/ios/swift/HybridNitroMapSpec.swift +35 -1
  160. package/nitrogen/generated/ios/swift/HybridNitroMapSpec_cxx.swift +582 -8
  161. package/nitrogen/generated/ios/swift/MarkerAnimation.swift +4 -8
  162. package/nitrogen/generated/ios/swift/MarkerData.swift +54 -2
  163. package/nitrogen/generated/ios/swift/PolygonData.swift +179 -0
  164. package/nitrogen/generated/ios/swift/PolylineData.swift +155 -0
  165. package/nitrogen/generated/ios/swift/UserLocationChangeEvent.swift +69 -0
  166. package/nitrogen/generated/ios/swift/UserTrackingMode.swift +44 -0
  167. package/nitrogen/generated/shared/c++/CircleData.hpp +113 -0
  168. package/nitrogen/generated/shared/c++/ClusterConfig.hpp +5 -8
  169. package/nitrogen/generated/shared/c++/HybridNitroMapSpec.cpp +53 -2
  170. package/nitrogen/generated/shared/c++/HybridNitroMapSpec.hpp +75 -6
  171. package/nitrogen/generated/shared/c++/MarkerAnimation.hpp +4 -8
  172. package/nitrogen/generated/shared/c++/MarkerData.hpp +14 -2
  173. package/nitrogen/generated/shared/c++/PolygonData.hpp +114 -0
  174. package/nitrogen/generated/shared/c++/PolylineData.hpp +114 -0
  175. package/nitrogen/generated/shared/c++/UserLocationChangeEvent.hpp +88 -0
  176. package/nitrogen/generated/shared/c++/UserTrackingMode.hpp +80 -0
  177. package/nitrogen/generated/shared/c++/views/HybridNitroMapComponent.cpp +216 -12
  178. package/nitrogen/generated/shared/c++/views/HybridNitroMapComponent.hpp +23 -1
  179. package/nitrogen/generated/shared/json/NitroMapConfig.json +18 -1
  180. package/package.json +36 -5
  181. package/src/components/ImageMarker.tsx +58 -42
  182. package/src/components/Marker.tsx +161 -0
  183. package/src/components/NitroCircle.tsx +183 -0
  184. package/src/components/NitroMap.tsx +328 -78
  185. package/src/components/NitroPolygon.tsx +229 -0
  186. package/src/components/NitroPolyline.tsx +208 -0
  187. package/src/components/PriceMarker.tsx +23 -48
  188. package/src/context/NitroMapContext.tsx +4 -0
  189. package/src/hooks/useNitroCircle.ts +25 -0
  190. package/src/hooks/useNitroMarker.ts +49 -10
  191. package/src/hooks/useNitroOverlay.ts +68 -0
  192. package/src/hooks/useNitroPolygon.ts +25 -0
  193. package/src/hooks/useNitroPolyline.ts +25 -0
  194. package/src/index.tsx +23 -2
  195. package/src/specs/NitroMap.nitro.ts +294 -5
  196. package/src/types/map.ts +36 -4
  197. package/src/types/marker.ts +24 -44
  198. package/src/types/overlay.ts +77 -0
  199. package/src/types/theme.ts +101 -0
  200. package/src/utils/colors.ts +48 -16
  201. package/src/utils/validation.ts +69 -0
  202. package/android/src/main/java/com/margelo/nitro/nitromap/ClusterIconGenerator.kt +0 -108
  203. package/android/src/main/java/com/margelo/nitro/nitromap/ColorUtils.kt +0 -63
  204. package/android/src/main/java/com/margelo/nitro/nitromap/HybridNitroMap.kt +0 -408
  205. package/android/src/main/java/com/margelo/nitro/nitromap/HybridNitroMapConfig.kt +0 -68
  206. package/android/src/main/java/com/margelo/nitro/nitromap/MarkerIconCache.kt +0 -176
  207. package/android/src/main/java/com/margelo/nitro/nitromap/MarkerIconFactory.kt +0 -252
  208. package/android/src/main/java/com/margelo/nitro/nitromap/clustering/NitroClusterEngine.kt +0 -252
  209. package/android/src/main/java/com/margelo/nitro/nitromap/clustering/QuadTree.kt +0 -195
  210. package/android/src/main/java/com/margelo/nitro/nitromap/providers/GoogleMapProvider.kt +0 -912
  211. package/android/src/main/java/com/margelo/nitro/nitromap/providers/MapProviderInterface.kt +0 -70
  212. package/cpp/QuadTree.hpp +0 -246
  213. package/ios/NitroMapConfig/HybridNitroMapConfig.swift +0 -33
  214. package/ios/Providers/GoogleMapProvider+Camera.swift +0 -164
  215. package/ios/Providers/GoogleMapProvider.swift +0 -924
  216. package/nitrogen/generated/android/c++/JClusterAnimationStyle.hpp +0 -68
  217. package/nitrogen/generated/ios/swift/ClusterAnimationStyle.swift +0 -52
  218. package/nitrogen/generated/shared/c++/ClusterAnimationStyle.hpp +0 -88
@@ -0,0 +1,358 @@
1
+ package com.margelo.nitro.nitromap.providers.google
2
+
3
+ import android.util.Log
4
+ import com.google.android.gms.maps.CameraUpdateFactory
5
+ import com.google.android.gms.maps.model.BitmapDescriptor
6
+ import com.google.android.gms.maps.model.LatLng
7
+ import com.google.android.gms.maps.model.Marker
8
+ import com.google.android.gms.maps.model.MarkerOptions
9
+ import com.margelo.nitro.nitromap.*
10
+ import com.margelo.nitro.nitromap.markers.MarkerSelectionHandler
11
+
12
+ /**
13
+ * Marker CRUD + selection for GoogleMapProvider.
14
+ * Mirrors iOS GoogleMapProvider+Markers.swift.
15
+ *
16
+ * ALL markers are fed to the clustering engine (matches iOS behavior).
17
+ * The hideOnOverlap strategy needs visibility of all markers, including non-clusterable ones.
18
+ *
19
+ * Data structures:
20
+ * - markerData : source-of-truth for ALL marker data (survives detach)
21
+ * - clusteringManager : C++ spatial engine — receives ALL markers
22
+ * - nonClusteredMarkers : native markers for non-clusterable items (direct on map)
23
+ * - renderedSingleMarkers : native markers rendered by clustering pipeline
24
+ * - renderedClusterMarkers : native cluster icons rendered by clustering pipeline
25
+ *
26
+ * Threading: All native GoogleMap operations go through withMap {} for main-thread dispatch.
27
+ */
28
+
29
+ // MARK: - Shared MarkerOptions builder (M-1: single function used by all callers)
30
+
31
+ /**
32
+ * Build MarkerOptions from MarkerData. Used by addMarkerSync, reapplyMarkers,
33
+ * selectMarker, and the clustering pipeline (GoogleMapProvider+Clustering.kt).
34
+ */
35
+ fun GoogleMapProvider.buildMarkerOptions(data: MarkerData, icon: BitmapDescriptor? = null): MarkerOptions {
36
+ val opts = MarkerOptions()
37
+ .position(LatLng(data.coordinate.latitude, data.coordinate.longitude))
38
+ .title(data.title)
39
+ .snippet(data.description)
40
+ .draggable(data.draggable)
41
+ .alpha(data.opacity.toFloat())
42
+ .rotation(data.rotation.toFloat())
43
+ .zIndex(data.zIndex.toFloat())
44
+ .anchor(data.anchor.x.toFloat(), data.anchor.y.toFloat())
45
+
46
+ val resolvedIcon = icon ?: iconFactory?.createIcon(data)
47
+ if (resolvedIcon != null) opts.icon(resolvedIcon)
48
+
49
+ return opts
50
+ }
51
+
52
+ /**
53
+ * Update existing native Marker properties in-place from MarkerData.
54
+ * Avoids remove/re-add flicker for cosmetic updates.
55
+ */
56
+ fun GoogleMapProvider.applyMarkerProperties(marker: Marker, data: MarkerData) {
57
+ marker.position = LatLng(data.coordinate.latitude, data.coordinate.longitude)
58
+ marker.title = data.title
59
+ marker.snippet = data.description
60
+ marker.isDraggable = data.draggable
61
+ marker.alpha = data.opacity.toFloat()
62
+ marker.rotation = data.rotation.toFloat()
63
+ marker.zIndex = data.zIndex.toFloat()
64
+ marker.setAnchor(data.anchor.x.toFloat(), data.anchor.y.toFloat())
65
+
66
+ // Regenerate icon — cache handles deduplication (mirrors iOS updateGMSMarkerProperties)
67
+ iconFactory?.createIcon(data)?.let { marker.setIcon(it) }
68
+ }
69
+
70
+ // M-5: Apply marker appear animation (matches iOS GMSMarker.appearAnimation)
71
+ // H-5: Use WeakReference to prevent animator from holding marker in memory after removal
72
+ // Semantic intent: 'appear' → platform's best native animation (alpha fade on Android)
73
+ // Alpha animation is used because position animation causes expensive map layout
74
+ // recalculations per frame, which degrades performance with many markers.
75
+ internal fun applyMarkerAnimation(marker: Marker, animation: MarkerAnimation, durationSec: Double = 0.3) {
76
+ if (animation == MarkerAnimation.NONE) return
77
+
78
+ // 'appear' — alpha fade-in
79
+ val weakMarker = java.lang.ref.WeakReference(marker)
80
+ val durationMs = (durationSec * 1000).toLong().coerceIn(50, 5000)
81
+ val targetAlpha = marker.alpha
82
+ marker.alpha = 0f
83
+ android.animation.ValueAnimator.ofFloat(0f, targetAlpha).apply {
84
+ duration = durationMs
85
+ addUpdateListener { anim ->
86
+ val m = weakMarker.get() ?: run { anim.cancel(); return@addUpdateListener }
87
+ try { m.alpha = anim.animatedValue as Float } catch (_: Exception) { anim.cancel() }
88
+ }
89
+ start()
90
+ }
91
+ }
92
+
93
+ // MARK: - Marker CRUD
94
+
95
+ internal fun GoogleMapProvider.addMarkerInternal(marker: MarkerData) {
96
+ // M-1: Don't call performClustering() here — let onCameraIdle handle it naturally.
97
+ // This matches iOS behavior and avoids redundant clustering on each single add.
98
+ addMarkerSync(marker)
99
+ clusteringDirty = true
100
+ }
101
+
102
+ internal fun GoogleMapProvider.addMarkersInternal(markers: Array<MarkerData>) {
103
+ markers.forEach { addMarkerSync(it) }
104
+ clusteringDirty = true
105
+ performClustering()
106
+ }
107
+
108
+ internal fun GoogleMapProvider.updateMarkerInternal(marker: MarkerData) {
109
+ updateMarkerInPlace(marker)
110
+ }
111
+
112
+ internal fun GoogleMapProvider.removeMarkerInternal(id: String) {
113
+ removeMarkerSync(id)
114
+ clusteringDirty = true
115
+ performClustering()
116
+ }
117
+
118
+ internal fun GoogleMapProvider.clearMarkersInternal() {
119
+ clearMarkersSync()
120
+ }
121
+
122
+ // MARK: - Internal sync helpers
123
+
124
+ internal fun GoogleMapProvider.addMarkerSync(markerData: MarkerData) {
125
+ // Remove any existing marker with the same id first
126
+ removeMarkerSync(markerData.id)
127
+
128
+ // Store data (source of truth for ALL markers)
129
+ this.markerData[markerData.id] = markerData
130
+
131
+ // H-2: Add ALL markers to clustering engine (matches iOS behavior).
132
+ // hideOnOverlap strategy needs to see non-clusterable markers for overlap detection.
133
+ clusteringManager?.addMarker(markerData)
134
+
135
+ val shouldCluster = markerData.clusteringEnabled && clusterConfig?.enabled != false
136
+
137
+ if (!shouldCluster) {
138
+ // Non-clusterable → also place directly on the map (like iOS)
139
+ withMap { map ->
140
+ val icon = iconFactory?.createIcon(markerData)
141
+ val opts = buildMarkerOptions(markerData, icon)
142
+ val nativeMarker = map.addMarker(opts) ?: return@withMap
143
+ nativeMarker.tag = MarkerTagData.Regular(markerData.id)
144
+ nonClusteredMarkers[markerData.id] = nativeMarker
145
+ // M-5: Apply marker animation
146
+ if (markerData.animation != MarkerAnimation.NONE) {
147
+ applyMarkerAnimation(nativeMarker, markerData.animation, markerData.animationDuration)
148
+ }
149
+ }
150
+ }
151
+ }
152
+
153
+ internal fun GoogleMapProvider.updateMarkerInPlace(markerData: MarkerData) {
154
+ val id = markerData.id
155
+ val existing = this.markerData[id]
156
+ if (existing == null) {
157
+ // Not found — add as new
158
+ addMarkerSync(markerData)
159
+ performClustering()
160
+ return
161
+ }
162
+
163
+ // Check if clustering eligibility changed
164
+ val wasClusterable = existing.clusteringEnabled && clusterConfig?.enabled != false
165
+ val isClusterable = markerData.clusteringEnabled && clusterConfig?.enabled != false
166
+
167
+ if (wasClusterable != isClusterable) {
168
+ // Clustering state changed — full remove + re-add
169
+ removeMarkerSync(id)
170
+ addMarkerSync(markerData)
171
+ performClustering()
172
+ return
173
+ }
174
+
175
+ // Update stored data
176
+ this.markerData[id] = markerData
177
+
178
+ // Re-index in C++ engine only if coordinate changed
179
+ val coordChanged =
180
+ existing.coordinate.latitude != markerData.coordinate.latitude ||
181
+ existing.coordinate.longitude != markerData.coordinate.longitude
182
+ if (coordChanged) {
183
+ clusteringManager?.removeMarker(id)
184
+ clusteringManager?.addMarker(markerData)
185
+ performClustering()
186
+ }
187
+
188
+ // Update rendered marker in-place (avoids remove/re-add flicker)
189
+ withMap { _ ->
190
+ val nativeMarker = renderedSingleMarkers[id] ?: nonClusteredMarkers[id] ?: return@withMap
191
+ applyMarkerProperties(nativeMarker, markerData)
192
+ }
193
+ }
194
+
195
+ internal fun GoogleMapProvider.removeMarkerSync(id: String) {
196
+ this.markerData.remove(id)
197
+ clusteringManager?.removeMarker(id)
198
+
199
+ // Remove native marker (must be on main thread via withMap)
200
+ withMap { _ ->
201
+ renderedSingleMarkers.remove(id)?.remove()
202
+ nonClusteredMarkers.remove(id)?.remove()
203
+ }
204
+ }
205
+
206
+ internal fun GoogleMapProvider.clearMarkersSync() {
207
+ this.markerData.clear()
208
+ clusteringManager?.clearMarkers()
209
+ selectedMarkerId = null
210
+
211
+ // Remove all native markers (must be on main thread via withMap)
212
+ withMap { _ ->
213
+ clearRenderedClusterMarkers()
214
+ for ((_, marker) in nonClusteredMarkers) { marker.remove() }
215
+ nonClusteredMarkers.clear()
216
+ }
217
+ Log.d("NitroMap", "clearMarkers")
218
+ }
219
+
220
+ // MARK: - Selection
221
+
222
+ internal fun GoogleMapProvider.selectMarkerInternal(id: String) {
223
+ // Deselect any previously selected marker
224
+ if (selectedMarkerId != null && selectedMarkerId != id) {
225
+ deselectCurrentMarker()
226
+ }
227
+
228
+ selectedMarkerId = id
229
+
230
+ // Remove marker FROM clustering engine while selected —
231
+ // prevents clustering from destroying the native marker object
232
+ clusteringManager?.removeMarker(id)
233
+
234
+ // Update marker data to selected state
235
+ val data = this.markerData[id]
236
+ if (data != null) {
237
+ val updatedData = MarkerSelectionHandler.updateSelectionState(data, selected = true)
238
+ if (updatedData != null) {
239
+ this.markerData[id] = updatedData
240
+ withMap { _ ->
241
+ val nativeMarker = renderedSingleMarkers[id] ?: nonClusteredMarkers[id]
242
+ if (nativeMarker != null) {
243
+ iconFactory?.createIcon(updatedData)?.let { nativeMarker.setIcon(it) }
244
+ nativeMarker.zIndex = Float.MAX_VALUE
245
+ }
246
+ }
247
+ } else {
248
+ // Style doesn't support visual selection — just elevate zIndex
249
+ withMap { _ ->
250
+ val nativeMarker = renderedSingleMarkers[id] ?: nonClusteredMarkers[id]
251
+ nativeMarker?.zIndex = Float.MAX_VALUE
252
+ }
253
+ }
254
+ }
255
+
256
+ // Animate camera to marker + show info window
257
+ withMap { map ->
258
+ val nativeMarker = renderedSingleMarkers[id] ?: nonClusteredMarkers[id]
259
+ if (nativeMarker != null) {
260
+ map.animateCamera(
261
+ CameraUpdateFactory.newLatLng(nativeMarker.position),
262
+ 300,
263
+ null
264
+ )
265
+ nativeMarker.showInfoWindow()
266
+ } else if (data != null) {
267
+ // Marker was inside a cluster — create a single marker for it
268
+ val opts = buildMarkerOptions(data)
269
+ opts.zIndex(Float.MAX_VALUE)
270
+ val marker = map.addMarker(opts)
271
+ if (marker != null) {
272
+ marker.tag = MarkerTagData.Regular(id)
273
+ renderedSingleMarkers[id] = marker
274
+ map.animateCamera(
275
+ CameraUpdateFactory.newLatLng(marker.position),
276
+ 300,
277
+ null
278
+ )
279
+ marker.showInfoWindow()
280
+ }
281
+ }
282
+ Log.d("NitroMap", "selectMarker: $id")
283
+ }
284
+ }
285
+
286
+ internal fun GoogleMapProvider.deselectMarkerInternal() {
287
+ deselectCurrentMarker()
288
+ }
289
+
290
+ internal fun GoogleMapProvider.deselectCurrentMarker() {
291
+ val id = selectedMarkerId ?: return
292
+ selectedMarkerId = null
293
+
294
+ val data = this.markerData[id] ?: return
295
+ val updatedData = MarkerSelectionHandler.updateSelectionState(data, selected = false)
296
+ if (updatedData != null) {
297
+ this.markerData[id] = updatedData
298
+ withMap { _ ->
299
+ val nativeMarker = renderedSingleMarkers[id] ?: nonClusteredMarkers[id]
300
+ if (nativeMarker != null) {
301
+ iconFactory?.createIcon(updatedData)?.let { nativeMarker.setIcon(it) }
302
+ nativeMarker.zIndex = data.zIndex.toFloat()
303
+ nativeMarker.hideInfoWindow()
304
+ }
305
+ }
306
+ } else {
307
+ withMap { _ ->
308
+ val nativeMarker = renderedSingleMarkers[id] ?: nonClusteredMarkers[id]
309
+ if (nativeMarker != null) {
310
+ nativeMarker.zIndex = data.zIndex.toFloat()
311
+ nativeMarker.hideInfoWindow()
312
+ }
313
+ }
314
+ }
315
+
316
+ // Add marker back into clustering engine
317
+ clusteringManager?.addMarker(data)
318
+
319
+ Log.d("NitroMap", "deselectMarker")
320
+ }
321
+
322
+ // MARK: - Re-apply on map ready (called from applyAllSettings)
323
+
324
+ /**
325
+ * Re-adds all stored markers to the (new) GoogleMap after reattach.
326
+ * Called from GoogleMapProvider.applyAllSettings().
327
+ */
328
+ fun GoogleMapProvider.reapplyMarkers() {
329
+ if (markerData.isEmpty()) return
330
+
331
+ // Clear stale native references (old map instance)
332
+ nonClusteredMarkers.clear()
333
+
334
+ // H-2: Rebuild clustering engine from stored data — add ALL markers
335
+ clusteringManager?.clearMarkers()
336
+
337
+ for ((_, data) in markerData) {
338
+ // H-2: ALL markers go to clustering engine (matches iOS)
339
+ clusteringManager?.addMarker(data)
340
+
341
+ val shouldCluster = data.clusteringEnabled && clusterConfig?.enabled != false
342
+ if (!shouldCluster) {
343
+ // Re-create non-clustered marker
344
+ withMap { map ->
345
+ val opts = buildMarkerOptions(data)
346
+ val nativeMarker = map.addMarker(opts) ?: return@withMap
347
+ nativeMarker.tag = MarkerTagData.Regular(data.id)
348
+ if (data.id == selectedMarkerId) nativeMarker.zIndex = Float.MAX_VALUE
349
+ nonClusteredMarkers[data.id] = nativeMarker
350
+ }
351
+ }
352
+ }
353
+
354
+ // Clustered markers will be rendered when performClustering runs
355
+ performClustering()
356
+
357
+ Log.d("NitroMap", "reapplyMarkers: ${markerData.size} markers")
358
+ }
@@ -0,0 +1,272 @@
1
+ package com.margelo.nitro.nitromap.providers.google
2
+
3
+ import android.graphics.Color
4
+ import com.margelo.nitro.nitromap.*
5
+ import android.util.Log
6
+ import com.google.android.gms.maps.model.CircleOptions
7
+ import com.google.android.gms.maps.model.Dash
8
+ import com.google.android.gms.maps.model.Gap
9
+ import com.google.android.gms.maps.model.LatLng
10
+ import com.google.android.gms.maps.model.PatternItem
11
+ import com.google.android.gms.maps.model.PolygonOptions
12
+ import com.google.android.gms.maps.model.PolylineOptions
13
+
14
+ /**
15
+ * Overlay CRUD for GoogleMapProvider — Polylines, Polygons, Circles.
16
+ *
17
+ * Mirrors iOS GoogleMapProvider+Overlays.swift.
18
+ *
19
+ * Data dictionaries (polylineData, polygonData, circleData) survive detach/reattach.
20
+ * Native overlay objects (renderedPolylines, renderedPolygons, renderedCircles) are
21
+ * recreated from data on reattach via reapplyOverlays().
22
+ */
23
+
24
+ // ============================================================================
25
+ // MARK: - Shared Color Helper (M-4: single resolveColor for all overlay code)
26
+ // ============================================================================
27
+
28
+ /**
29
+ * Convert a MarkerColor to an Android ARGB int.
30
+ * Used by overlays and available to other files that need color conversion.
31
+ */
32
+ fun MarkerColor.toArgb(): Int =
33
+ Color.argb(a.toInt(), r.toInt(), g.toInt(), b.toInt())
34
+
35
+ // ============================================================================
36
+ // MARK: - Shared Overlay Builders (M-3: extract from duplicated code)
37
+ // ============================================================================
38
+
39
+ /**
40
+ * Build PolylineOptions from PolylineData. Used by both addPolyline and reapplyOverlays.
41
+ */
42
+ private fun GoogleMapProvider.buildPolylineOptions(data: PolylineData): PolylineOptions {
43
+ val opts = PolylineOptions()
44
+
45
+ for (coord in data.coordinates) {
46
+ opts.add(LatLng(coord.latitude, coord.longitude))
47
+ }
48
+
49
+ opts.width(data.strokeWidth.toFloat())
50
+ opts.color(data.strokeColor.toArgb())
51
+ opts.geodesic(data.geodesic)
52
+ opts.zIndex(data.zIndex.toFloat())
53
+ opts.clickable(data.tappable)
54
+
55
+ // Dashed line support
56
+ // M-6: Standardize density access
57
+ if (data.dashed) {
58
+ val density = context.resources.displayMetrics.density
59
+ val pattern: List<PatternItem> = listOf(
60
+ Dash(20f * density),
61
+ Gap(10f * density)
62
+ )
63
+ opts.pattern(pattern)
64
+ }
65
+
66
+ return opts
67
+ }
68
+
69
+ /**
70
+ * Build PolygonOptions from PolygonData. Used by both addPolygon and reapplyOverlays.
71
+ */
72
+ private fun buildPolygonOptions(data: PolygonData): PolygonOptions {
73
+ val opts = PolygonOptions()
74
+
75
+ for (coord in data.coordinates) {
76
+ opts.add(LatLng(coord.latitude, coord.longitude))
77
+ }
78
+
79
+ opts.fillColor(data.fillColor.toArgb())
80
+ opts.strokeColor(data.strokeColor.toArgb())
81
+ opts.strokeWidth(data.strokeWidth.toFloat())
82
+ opts.zIndex(data.zIndex.toFloat())
83
+ opts.clickable(data.tappable)
84
+
85
+ // Add holes
86
+ for (hole in data.holes) {
87
+ val holePath = hole.map { LatLng(it.latitude, it.longitude) }
88
+ opts.addHole(holePath)
89
+ }
90
+
91
+ return opts
92
+ }
93
+
94
+ /**
95
+ * Build CircleOptions from CircleData. Used by both addCircle and reapplyOverlays.
96
+ */
97
+ private fun buildCircleOptions(data: CircleData): CircleOptions {
98
+ return CircleOptions()
99
+ .center(LatLng(data.center.latitude, data.center.longitude))
100
+ .radius(data.radius)
101
+ .fillColor(data.fillColor.toArgb())
102
+ .strokeColor(data.strokeColor.toArgb())
103
+ .strokeWidth(data.strokeWidth.toFloat())
104
+ .zIndex(data.zIndex.toFloat())
105
+ .clickable(data.tappable)
106
+ }
107
+
108
+ // ============================================================================
109
+ // MARK: - Polyline CRUD
110
+ // ============================================================================
111
+
112
+ internal fun GoogleMapProvider.addPolylineInternal(polyline: PolylineData) {
113
+ // Store data (survives detach)
114
+ polylineData[polyline.id] = polyline
115
+
116
+ // Remove existing + place new native Polyline (all on main thread)
117
+ withMap { map ->
118
+ renderedPolylines.remove(polyline.id)?.remove()
119
+ val opts = buildPolylineOptions(polyline)
120
+ val nativePolyline = map.addPolyline(opts)
121
+ nativePolyline.tag = polyline.id
122
+ renderedPolylines[polyline.id] = nativePolyline
123
+ Log.d("NitroMap", "addPolyline: ${polyline.id}")
124
+ }
125
+ }
126
+
127
+ internal fun GoogleMapProvider.updatePolylineInternal(polyline: PolylineData) {
128
+ addPolylineInternal(polyline)
129
+ }
130
+
131
+ internal fun GoogleMapProvider.removePolylineInternal(id: String) {
132
+ polylineData.remove(id)
133
+ withMap { _ ->
134
+ renderedPolylines.remove(id)?.remove()
135
+ }
136
+ }
137
+
138
+ internal fun GoogleMapProvider.clearPolylinesInternal() {
139
+ polylineData.clear()
140
+ withMap { _ ->
141
+ renderedPolylines.values.forEach { it.remove() }
142
+ renderedPolylines.clear()
143
+ }
144
+ }
145
+
146
+ // ============================================================================
147
+ // MARK: - Polygon CRUD
148
+ // ============================================================================
149
+
150
+ internal fun GoogleMapProvider.addPolygonInternal(polygon: PolygonData) {
151
+ // Store data (survives detach)
152
+ polygonData[polygon.id] = polygon
153
+
154
+ // Remove existing + place new native Polygon (all on main thread)
155
+ withMap { map ->
156
+ renderedPolygons.remove(polygon.id)?.remove()
157
+ val opts = buildPolygonOptions(polygon)
158
+ val nativePolygon = map.addPolygon(opts)
159
+ nativePolygon.tag = polygon.id
160
+ renderedPolygons[polygon.id] = nativePolygon
161
+ Log.d("NitroMap", "addPolygon: ${polygon.id}")
162
+ }
163
+ }
164
+
165
+ internal fun GoogleMapProvider.updatePolygonInternal(polygon: PolygonData) {
166
+ addPolygonInternal(polygon)
167
+ }
168
+
169
+ internal fun GoogleMapProvider.removePolygonInternal(id: String) {
170
+ polygonData.remove(id)
171
+ withMap { _ ->
172
+ renderedPolygons.remove(id)?.remove()
173
+ }
174
+ }
175
+
176
+ internal fun GoogleMapProvider.clearPolygonsInternal() {
177
+ polygonData.clear()
178
+ withMap { _ ->
179
+ renderedPolygons.values.forEach { it.remove() }
180
+ renderedPolygons.clear()
181
+ }
182
+ }
183
+
184
+ // ============================================================================
185
+ // MARK: - Circle CRUD
186
+ // ============================================================================
187
+
188
+ internal fun GoogleMapProvider.addCircleInternal(circle: CircleData) {
189
+ // Store data (survives detach)
190
+ circleData[circle.id] = circle
191
+
192
+ // Remove existing + place new native Circle (all on main thread)
193
+ withMap { map ->
194
+ renderedCircles.remove(circle.id)?.remove()
195
+ val opts = buildCircleOptions(circle)
196
+ val nativeCircle = map.addCircle(opts)
197
+ nativeCircle.tag = circle.id
198
+ renderedCircles[circle.id] = nativeCircle
199
+ Log.d("NitroMap", "addCircle: ${circle.id}")
200
+ }
201
+ }
202
+
203
+ internal fun GoogleMapProvider.updateCircleInternal(circle: CircleData) {
204
+ addCircleInternal(circle)
205
+ }
206
+
207
+ internal fun GoogleMapProvider.removeCircleInternal(id: String) {
208
+ circleData.remove(id)
209
+ withMap { _ ->
210
+ renderedCircles.remove(id)?.remove()
211
+ }
212
+ }
213
+
214
+ internal fun GoogleMapProvider.clearCirclesInternal() {
215
+ circleData.clear()
216
+ withMap { _ ->
217
+ renderedCircles.values.forEach { it.remove() }
218
+ renderedCircles.clear()
219
+ }
220
+ }
221
+
222
+ // ============================================================================
223
+ // MARK: - Reapply on MapView Reattach (M-3: uses shared builder helpers)
224
+ // ============================================================================
225
+
226
+ /**
227
+ * Re-adds all stored overlays to the (new) GoogleMap after reattach.
228
+ * Called from GoogleMapProvider.applyAllSettings() after reapplyMarkers().
229
+ */
230
+ fun GoogleMapProvider.reapplyOverlays() {
231
+ // Clear old native references (invalid after detach)
232
+ renderedPolylines.clear()
233
+ renderedPolygons.clear()
234
+ renderedCircles.clear()
235
+
236
+ // Re-create from stored data using shared builder helpers
237
+ if (polylineData.isNotEmpty()) {
238
+ polylineData.values.toList().forEach { data ->
239
+ withMap { map ->
240
+ val opts = buildPolylineOptions(data)
241
+ val native = map.addPolyline(opts)
242
+ native.tag = data.id
243
+ renderedPolylines[data.id] = native
244
+ }
245
+ }
246
+ Log.d("NitroMap", "reapplyOverlays: ${polylineData.size} polylines")
247
+ }
248
+
249
+ if (polygonData.isNotEmpty()) {
250
+ polygonData.values.toList().forEach { data ->
251
+ withMap { map ->
252
+ val opts = buildPolygonOptions(data)
253
+ val native = map.addPolygon(opts)
254
+ native.tag = data.id
255
+ renderedPolygons[data.id] = native
256
+ }
257
+ }
258
+ Log.d("NitroMap", "reapplyOverlays: ${polygonData.size} polygons")
259
+ }
260
+
261
+ if (circleData.isNotEmpty()) {
262
+ circleData.values.toList().forEach { data ->
263
+ withMap { map ->
264
+ val opts = buildCircleOptions(data)
265
+ val native = map.addCircle(opts)
266
+ native.tag = data.id
267
+ renderedCircles[data.id] = native
268
+ }
269
+ }
270
+ Log.d("NitroMap", "reapplyOverlays: ${circleData.size} circles")
271
+ }
272
+ }