@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,317 @@
1
+ package com.margelo.nitro.nitromap.providers.google
2
+
3
+ import android.util.Log
4
+ import com.google.android.gms.maps.GoogleMap
5
+ import com.google.android.gms.maps.model.LatLng
6
+ import com.margelo.nitro.nitromap.*
7
+ import kotlin.math.sqrt
8
+
9
+ /**
10
+ * Sets up all GoogleMap listeners and routes events to the provider's callbacks.
11
+ *
12
+ * Uses SDK's OnCameraMoveStartedListener.REASON_* constants to distinguish
13
+ * user gestures from programmatic camera moves — prevents region prop infinite loops.
14
+ */
15
+ class GoogleMapDelegate(private val provider: GoogleMapProvider) {
16
+
17
+ companion object {
18
+ private const val TAG = "NitroMap"
19
+ }
20
+
21
+ private var cameraMoveReason = -1
22
+ private var isGesture = false
23
+
24
+ fun setupListeners(map: GoogleMap) {
25
+ setupCameraListeners(map)
26
+ setupTapListeners(map)
27
+ setupMarkerListeners(map)
28
+ setupOverlayListeners(map)
29
+ }
30
+
31
+ // MARK: - Camera Listeners
32
+
33
+ private fun setupCameraListeners(map: GoogleMap) {
34
+ map.setOnCameraMoveStartedListener { reason ->
35
+ cameraMoveReason = reason
36
+ isGesture = reason == GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE
37
+
38
+ val reasonStr = when (reason) {
39
+ GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE -> "GESTURE"
40
+ GoogleMap.OnCameraMoveStartedListener.REASON_API_ANIMATION -> "API_ANIMATION"
41
+ GoogleMap.OnCameraMoveStartedListener.REASON_DEVELOPER_ANIMATION -> "DEVELOPER_ANIMATION"
42
+ else -> "UNKNOWN($reason)"
43
+ }
44
+ Log.d(TAG, "onCameraMoveStarted reason=$reasonStr isGesture=$isGesture")
45
+
46
+ // Auto-break tracking mode on user gesture
47
+ if (isGesture) {
48
+ val mode = provider.userTrackingMode
49
+ if (mode != null && mode != UserTrackingMode.NONE) {
50
+ provider.userTrackingMode = UserTrackingMode.NONE
51
+ provider.onUserTrackingModeChange?.invoke(UserTrackingMode.NONE)
52
+ }
53
+ }
54
+
55
+ // Fire onRegionChange (live, during movement)
56
+ val event = RegionChangeEvent(
57
+ region = provider.getCurrentRegion(),
58
+ isGesture = isGesture
59
+ )
60
+ provider.onRegionChange?.invoke(event)
61
+ }
62
+
63
+ // Continuous camera updates — throttled clustering for realtime mode
64
+ // M-7: Also fire onRegionChange continuously (matching iOS didChange position)
65
+ map.setOnCameraMoveListener {
66
+ if (provider.clusterConfig?.realtimeClustering == true) {
67
+ provider.performClusteringThrottled()
68
+ }
69
+
70
+ // M-7: Fire onRegionChange during movement (not just at start)
71
+ val event = RegionChangeEvent(
72
+ region = provider.getCurrentRegion(),
73
+ isGesture = isGesture
74
+ )
75
+ provider.onRegionChange?.invoke(event)
76
+ }
77
+
78
+ map.setOnCameraIdleListener {
79
+ val region = provider.getCurrentRegion()
80
+ val event = RegionChangeEvent(
81
+ region = region,
82
+ isGesture = isGesture
83
+ )
84
+
85
+ Log.d(TAG, "onCameraIdle lat=${String.format("%.4f", region.latitude)} lng=${String.format("%.4f", region.longitude)} isGesture=$isGesture")
86
+
87
+ // Fire onRegionChangeComplete (with isGesture for consumer filtering)
88
+ provider.onRegionChangeComplete?.invoke(event)
89
+
90
+ // Skip clustering when the camera was moved programmatically for
91
+ // user location following — zoom hasn't changed so cluster results
92
+ // are identical. This prevents a performance cascade with 3000+ markers
93
+ // where heading updates → camera animation → onCameraIdle → performClustering
94
+ // would saturate the main thread.
95
+ // M-3: Only skip if markers haven't changed since last cluster run.
96
+ if (provider.isFollowingUserCamera && !provider.clusteringDirty) {
97
+ provider.isFollowingUserCamera = false
98
+ } else {
99
+ provider.isFollowingUserCamera = false
100
+ provider.performClustering()
101
+ }
102
+
103
+ // Always fire onMapIdle
104
+ provider.onMapIdle?.invoke()
105
+
106
+ // Reset gesture tracking
107
+ isGesture = false
108
+ cameraMoveReason = -1
109
+ }
110
+ }
111
+
112
+ // MARK: - Proximity Cluster Detection
113
+
114
+ /**
115
+ * H-4: Check if a tap coordinate is near any rendered cluster marker.
116
+ * Mirrors iOS findNearbyCluster(at:in:).
117
+ * Returns the closest cluster marker within 60dp, or null.
118
+ */
119
+ private fun findNearbyCluster(latLng: LatLng, map: GoogleMap): com.google.android.gms.maps.model.Marker? {
120
+ val tapPoint = map.projection.toScreenLocation(latLng)
121
+ val proximityThreshold = 60.0
122
+
123
+ var closestMarker: com.google.android.gms.maps.model.Marker? = null
124
+ var closestDistance = Double.MAX_VALUE
125
+
126
+ for (clusterMarker in provider.renderedClusterMarkers) {
127
+ val markerPoint = map.projection.toScreenLocation(clusterMarker.position)
128
+ val dx = (tapPoint.x - markerPoint.x).toDouble()
129
+ val dy = (tapPoint.y - markerPoint.y).toDouble()
130
+ val distance = sqrt(dx * dx + dy * dy)
131
+
132
+ if (distance < proximityThreshold && distance < closestDistance) {
133
+ closestDistance = distance
134
+ closestMarker = clusterMarker
135
+ }
136
+ }
137
+
138
+ return closestMarker
139
+ }
140
+
141
+ /**
142
+ * H-4: Fire onClusterPress for a cluster marker.
143
+ */
144
+ private fun fireClusterPress(marker: com.google.android.gms.maps.model.Marker) {
145
+ val tag = marker.tag as? MarkerTagData.Cluster ?: return
146
+ provider.onClusterPress?.invoke(
147
+ ClusterPressEvent(
148
+ coordinate = Coordinate(
149
+ latitude = marker.position.latitude,
150
+ longitude = marker.position.longitude
151
+ ),
152
+ markerIds = tag.markerIds.toTypedArray(),
153
+ count = tag.count.toDouble()
154
+ )
155
+ )
156
+ }
157
+
158
+ // MARK: - Tap Listeners
159
+
160
+ private fun setupTapListeners(map: GoogleMap) {
161
+ map.setOnMapClickListener { latLng ->
162
+ // H-4: Proximity-based cluster tap fallback (matches iOS)
163
+ val nearbyCluster = findNearbyCluster(latLng, map)
164
+ if (nearbyCluster != null) {
165
+ fireClusterPress(nearbyCluster)
166
+ return@setOnMapClickListener
167
+ }
168
+
169
+ Log.d(TAG, "onPress lat=${String.format("%.4f", latLng.latitude)} lng=${String.format("%.4f", latLng.longitude)}")
170
+ val point = map.projection.toScreenLocation(latLng)
171
+ val event = MapPressEvent(
172
+ coordinate = Coordinate(
173
+ latitude = latLng.latitude,
174
+ longitude = latLng.longitude
175
+ ),
176
+ position = com.margelo.nitro.nitromap.Point(
177
+ x = point.x.toDouble(),
178
+ y = point.y.toDouble()
179
+ )
180
+ )
181
+ provider.onPress?.invoke(event)
182
+ }
183
+
184
+ map.setOnMapLongClickListener { latLng ->
185
+ Log.d(TAG, "onLongPress lat=${String.format("%.4f", latLng.latitude)} lng=${String.format("%.4f", latLng.longitude)}")
186
+ val point = map.projection.toScreenLocation(latLng)
187
+ val event = MapPressEvent(
188
+ coordinate = Coordinate(
189
+ latitude = latLng.latitude,
190
+ longitude = latLng.longitude
191
+ ),
192
+ position = com.margelo.nitro.nitromap.Point(
193
+ x = point.x.toDouble(),
194
+ y = point.y.toDouble()
195
+ )
196
+ )
197
+ provider.onLongPress?.invoke(event)
198
+ }
199
+
200
+ // POI tap — fire onPress (matches iOS didTapPOI behavior)
201
+ map.setOnPoiClickListener { poi ->
202
+ Log.d(TAG, "onPoiClick name=${poi.name}")
203
+ val point = map.projection.toScreenLocation(poi.latLng)
204
+ val event = MapPressEvent(
205
+ coordinate = Coordinate(
206
+ latitude = poi.latLng.latitude,
207
+ longitude = poi.latLng.longitude
208
+ ),
209
+ position = com.margelo.nitro.nitromap.Point(
210
+ x = point.x.toDouble(),
211
+ y = point.y.toDouble()
212
+ )
213
+ )
214
+ provider.onPress?.invoke(event)
215
+ }
216
+ }
217
+
218
+ // MARK: - Marker Listeners
219
+
220
+ private fun setupMarkerListeners(map: GoogleMap) {
221
+ map.setOnMarkerClickListener { marker ->
222
+ when (val tag = marker.tag as? MarkerTagData) {
223
+ is MarkerTagData.Cluster -> {
224
+ provider.onClusterPress?.invoke(
225
+ ClusterPressEvent(
226
+ coordinate = Coordinate(
227
+ latitude = marker.position.latitude,
228
+ longitude = marker.position.longitude
229
+ ),
230
+ markerIds = tag.markerIds.toTypedArray(),
231
+ count = tag.count.toDouble()
232
+ )
233
+ )
234
+ true
235
+ }
236
+
237
+ is MarkerTagData.Regular -> {
238
+ provider.onMarkerPress?.invoke(
239
+ MarkerPressEvent(
240
+ id = tag.markerId,
241
+ coordinate = Coordinate(
242
+ latitude = marker.position.latitude,
243
+ longitude = marker.position.longitude
244
+ )
245
+ )
246
+ )
247
+ true
248
+ }
249
+
250
+ is MarkerTagData.UserLocation, null -> false
251
+ }
252
+ }
253
+
254
+ map.setOnMarkerDragListener(object : GoogleMap.OnMarkerDragListener {
255
+ override fun onMarkerDragStart(marker: com.google.android.gms.maps.model.Marker) {
256
+ val tag = marker.tag as? MarkerTagData.Regular ?: return
257
+ provider.onMarkerDragStart?.invoke(
258
+ MarkerDragEvent(
259
+ id = tag.markerId,
260
+ coordinate = Coordinate(marker.position.latitude, marker.position.longitude)
261
+ )
262
+ )
263
+ }
264
+
265
+ override fun onMarkerDrag(marker: com.google.android.gms.maps.model.Marker) {
266
+ val tag = marker.tag as? MarkerTagData.Regular ?: return
267
+ provider.onMarkerDrag?.invoke(
268
+ MarkerDragEvent(
269
+ id = tag.markerId,
270
+ coordinate = Coordinate(marker.position.latitude, marker.position.longitude)
271
+ )
272
+ )
273
+ }
274
+
275
+ override fun onMarkerDragEnd(marker: com.google.android.gms.maps.model.Marker) {
276
+ val tag = marker.tag as? MarkerTagData.Regular ?: return
277
+ provider.onMarkerDragEnd?.invoke(
278
+ MarkerDragEvent(
279
+ id = tag.markerId,
280
+ coordinate = Coordinate(marker.position.latitude, marker.position.longitude)
281
+ )
282
+ )
283
+ }
284
+ })
285
+
286
+ // Info window close — fully revert marker visual state
287
+ // When the info window is closed (user tap on map, SDK auto-close, etc.),
288
+ // we must revert the icon — not just clear selectedMarkerId.
289
+ // If deselectCurrentMarker() already ran (programmatic deselect),
290
+ // selectedMarkerId is already null and this is a no-op.
291
+ map.setOnInfoWindowCloseListener { marker ->
292
+ val tag = marker.tag as? MarkerTagData.Regular ?: return@setOnInfoWindowCloseListener
293
+ if (provider.selectedMarkerId == tag.markerId) {
294
+ provider.deselectCurrentMarker()
295
+ }
296
+ }
297
+ }
298
+
299
+ // MARK: - Overlay Listeners
300
+
301
+ private fun setupOverlayListeners(map: GoogleMap) {
302
+ map.setOnPolylineClickListener { polyline ->
303
+ val id = polyline.tag as? String ?: return@setOnPolylineClickListener
304
+ provider.onPolylinePress?.invoke(id)
305
+ }
306
+
307
+ map.setOnPolygonClickListener { polygon ->
308
+ val id = polygon.tag as? String ?: return@setOnPolygonClickListener
309
+ provider.onPolygonPress?.invoke(id)
310
+ }
311
+
312
+ map.setOnCircleClickListener { circle ->
313
+ val id = circle.tag as? String ?: return@setOnCircleClickListener
314
+ provider.onCirclePress?.invoke(id)
315
+ }
316
+ }
317
+ }