@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,397 @@
1
+ package com.margelo.nitro.nitromap
2
+
3
+ import android.content.Context
4
+ import android.os.Handler
5
+ import android.os.Looper
6
+ import android.util.Log
7
+ import android.view.View
8
+ import android.widget.FrameLayout
9
+ import com.margelo.nitro.core.Promise
10
+ import com.margelo.nitro.nitromap.providers.MapProviderFactory
11
+ import com.margelo.nitro.nitromap.providers.MapProviderInterface
12
+ import java.util.concurrent.CountDownLatch
13
+ import java.util.concurrent.TimeUnit
14
+ import java.util.concurrent.atomic.AtomicReference
15
+
16
+ /**
17
+ * HybridNitroMap — Android map implementation for Nitro Modules.
18
+ *
19
+ * Provider-agnostic facade: delegates all map operations to a MapProviderInterface
20
+ * implementation created via MapProviderFactory. Currently supports Google Maps,
21
+ * with Apple Maps (N/A on Android) and Yandex Maps planned.
22
+ *
23
+ * Lifecycle:
24
+ * onViewAttachedToWindow → create provider MapView + init
25
+ * onViewDetachedFromWindow → pause MapView (DO NOT destroy — preserves state for navigation)
26
+ * dispose() → destroy MapView + full cleanup (only on actual unmount)
27
+ *
28
+ * Threading:
29
+ * Props from React render → UI Thread (safe)
30
+ * Props from hybridRef → may be JS Thread (dispatch to main)
31
+ * Methods from hybridRef → JS Thread (dispatch to main)
32
+ */
33
+ class HybridNitroMap(context: Context) : HybridNitroMapSpec() {
34
+
35
+ companion object {
36
+ private const val TAG = "NitroMap"
37
+ }
38
+
39
+ // MARK: - View
40
+
41
+ private val mapContext: Context = context
42
+ private val container = FrameLayout(context).apply {
43
+ clipChildren = false
44
+ clipToPadding = false
45
+ }
46
+ private val mainHandler = Handler(Looper.getMainLooper())
47
+
48
+ override val view: View get() = container
49
+
50
+ // MARK: - Provider + Lifecycle State
51
+
52
+ private val mapProvider: MapProviderInterface by lazy {
53
+ MapProviderFactory.create(provider ?: MapProvider.GOOGLE, mapContext)
54
+ }
55
+ private val locationManager = UserLocationManager(mapContext)
56
+ private var currentMapView: View? = null
57
+ @Volatile
58
+ private var isDisposed = false
59
+
60
+ init {
61
+ container.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
62
+ override fun onViewAttachedToWindow(v: View) {
63
+ if (!isDisposed) {
64
+ attachMapView()
65
+ }
66
+ }
67
+
68
+ override fun onViewDetachedFromWindow(v: View) {
69
+ detachMapView()
70
+ }
71
+ })
72
+ }
73
+
74
+ private fun attachMapView() {
75
+ if (currentMapView != null) return // already attached
76
+
77
+ val mv = mapProvider.createMapView(mapContext)
78
+ container.addView(mv, FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
79
+
80
+ if (!isDisposed) {
81
+ mapProvider.onViewAttached(mv)
82
+ }
83
+ currentMapView = mv
84
+ Log.d(TAG, "MapView attached")
85
+ }
86
+
87
+ private fun detachMapView() {
88
+ currentMapView?.let { mv ->
89
+ mapProvider.onViewDetaching()
90
+ container.removeView(mv)
91
+ }
92
+ currentMapView = null
93
+ Log.d(TAG, "MapView detached (paused, state saved)")
94
+ }
95
+
96
+ override fun dispose() {
97
+ isDisposed = true
98
+ locationManager.destroy()
99
+ currentMapView?.let { mv ->
100
+ mapProvider.onViewDestroying()
101
+ container.removeView(mv)
102
+ }
103
+ currentMapView = null
104
+ Log.d(TAG, "MapView disposed (destroyed)")
105
+ super.dispose()
106
+ }
107
+
108
+ // MARK: - Properties (dispatch to mapProvider)
109
+
110
+ override var provider: MapProvider? = null
111
+
112
+ override var initialRegion: Region? = null
113
+ set(value) {
114
+ // Negative deltas = JS sentinel meaning "unset/cleared" (Fabric null workaround)
115
+ if (value != null && (value.latitudeDelta < 0 || value.longitudeDelta < 0)) return
116
+ field = value
117
+ mapProvider.initialRegion = value
118
+ }
119
+
120
+ override var region: Region? = null
121
+ set(value) {
122
+ // Negative deltas = JS sentinel meaning "unset/cleared" (Fabric null workaround)
123
+ if (value != null && (value.latitudeDelta < 0 || value.longitudeDelta < 0)) {
124
+ if (field == null) return // already cleared
125
+ field = null
126
+ mapProvider.region = null
127
+ return
128
+ }
129
+ // Skip if same value re-applied (Fabric re-commits all props on every
130
+ // cycle, unlike iOS which only calls didSet for changed props — without
131
+ // this guard, panning away from a controlled region snaps back)
132
+ if (field == value) return
133
+ field = value
134
+ mapProvider.region = value
135
+ }
136
+
137
+ override var showsUserLocation: Boolean? = null
138
+ set(value) {
139
+ val changed = field != value
140
+ field = value
141
+ mapProvider.showsUserLocation = value
142
+ if (value == true) {
143
+ locationManager.onLocationUpdate = { location, heading ->
144
+ mapProvider.updateUserLocation(location, heading)
145
+ onUserLocationChange?.invoke(
146
+ UserLocationChangeEvent(
147
+ Coordinate(location.latitude, location.longitude),
148
+ location.accuracy.toDouble(),
149
+ heading?.toDouble() ?: -1.0,
150
+ location.speed.toDouble()
151
+ )
152
+ )
153
+ }
154
+ locationManager.onLocationError = { code, message ->
155
+ onUserLocationError?.invoke(MapError(code, message))
156
+ }
157
+ locationManager.startUpdating()
158
+ // Only create the marker on actual change (not re-renders),
159
+ // otherwise it resets rotation to 0 and heading is lost on idle
160
+ if (changed) {
161
+ mapProvider.setupCustomUserLocationMarker(mapContext)
162
+ }
163
+ } else {
164
+ locationManager.stopUpdating()
165
+ mapProvider.disableUserLocation()
166
+ }
167
+ }
168
+
169
+ override var zoomEnabled: Boolean? = null
170
+ set(value) { field = value; mapProvider.zoomEnabled = value }
171
+
172
+ override var scrollEnabled: Boolean? = null
173
+ set(value) { field = value; mapProvider.scrollEnabled = value }
174
+
175
+ override var rotateEnabled: Boolean? = null
176
+ set(value) { field = value; mapProvider.rotateEnabled = value }
177
+
178
+ override var pitchEnabled: Boolean? = null
179
+ set(value) { field = value; mapProvider.pitchEnabled = value }
180
+
181
+ override var mapType: MapType? = null
182
+ set(value) { field = value; mapProvider.mapType = value }
183
+
184
+ override var customMapStyle: Array<MapStyleElement>? = null
185
+ set(value) { field = value; mapProvider.customMapStyle = value }
186
+
187
+ override var clusterConfig: ClusterConfig? = null
188
+ set(value) { field = value; mapProvider.clusterConfig = value }
189
+
190
+ override var mapPadding: EdgePadding = EdgePadding(0.0, 0.0, 0.0, 0.0)
191
+ set(value) { field = value; mapProvider.mapPadding = value }
192
+
193
+ override var showsTraffic: Boolean? = null
194
+ set(value) { field = value; mapProvider.showsTraffic = value }
195
+
196
+ override var showsBuildings: Boolean? = null
197
+ set(value) { field = value; mapProvider.showsBuildings = value }
198
+
199
+ override var showsCompass: Boolean? = null
200
+ set(value) { field = value; mapProvider.showsCompass = value }
201
+
202
+ // L-2: Align with iOS default (1.0, not 0.0)
203
+ override var minZoom: Double = 1.0
204
+ set(value) { field = value; mapProvider.minZoom = value }
205
+
206
+ override var maxZoom: Double = 22.0
207
+ set(value) { field = value; mapProvider.maxZoom = value }
208
+
209
+ override var darkMode: Boolean? = null
210
+ set(value) { field = value; mapProvider.darkMode = value }
211
+
212
+ override var userTrackingMode: UserTrackingMode? = null
213
+ set(value) { field = value; mapProvider.userTrackingMode = value }
214
+
215
+ override var userLocationImage: String = ""
216
+ set(value) {
217
+ if (field == value) return
218
+ field = value
219
+ mapProvider.userLocationImage = value
220
+ if (showsUserLocation == true) {
221
+ mapProvider.setupCustomUserLocationMarker(mapContext)
222
+ }
223
+ }
224
+
225
+ override var userLocationSize: Double = 0.0
226
+ set(value) {
227
+ if (field == value) return
228
+ field = value
229
+ mapProvider.userLocationSize = value
230
+ if (showsUserLocation == true) {
231
+ mapProvider.setupCustomUserLocationMarker(mapContext)
232
+ }
233
+ }
234
+
235
+ override var userLocationAnchor: Point? = null
236
+ set(value) { field = value; mapProvider.userLocationAnchor = value }
237
+
238
+ // MARK: - Callback Properties (wire directly to mapProvider)
239
+
240
+ override var onPress: ((event: MapPressEvent) -> Unit)? = null
241
+ set(value) { field = value; mapProvider.onPress = value }
242
+
243
+ override var onLongPress: ((event: MapPressEvent) -> Unit)? = null
244
+ set(value) { field = value; mapProvider.onLongPress = value }
245
+
246
+ override var onMapReady: (() -> Unit)? = null
247
+ set(value) { field = value; mapProvider.onMapReadyCallback = value }
248
+
249
+ override var onRegionChange: ((region: RegionChangeEvent) -> Unit)? = null
250
+ set(value) { field = value; mapProvider.onRegionChange = value }
251
+
252
+ override var onRegionChangeComplete: ((region: RegionChangeEvent) -> Unit)? = null
253
+ set(value) { field = value; mapProvider.onRegionChangeComplete = value }
254
+
255
+ override var onMarkerPress: ((event: MarkerPressEvent) -> Unit)? = null
256
+ set(value) { field = value; mapProvider.onMarkerPress = value }
257
+
258
+ override var onMarkerDragStart: ((event: MarkerDragEvent) -> Unit)? = null
259
+ set(value) { field = value; mapProvider.onMarkerDragStart = value }
260
+
261
+ override var onMarkerDrag: ((event: MarkerDragEvent) -> Unit)? = null
262
+ set(value) { field = value; mapProvider.onMarkerDrag = value }
263
+
264
+ override var onMarkerDragEnd: ((event: MarkerDragEvent) -> Unit)? = null
265
+ set(value) { field = value; mapProvider.onMarkerDragEnd = value }
266
+
267
+ override var onClusterPress: ((event: ClusterPressEvent) -> Unit)? = null
268
+ set(value) { field = value; mapProvider.onClusterPress = value }
269
+
270
+ override var onPolylinePress: ((id: String) -> Unit)? = null
271
+ set(value) { field = value; mapProvider.onPolylinePress = value }
272
+
273
+ override var onPolygonPress: ((id: String) -> Unit)? = null
274
+ set(value) { field = value; mapProvider.onPolygonPress = value }
275
+
276
+ override var onCirclePress: ((id: String) -> Unit)? = null
277
+ set(value) { field = value; mapProvider.onCirclePress = value }
278
+
279
+ override var onError: ((error: MapError) -> Unit)? = null
280
+ set(value) { field = value; mapProvider.onError = value }
281
+
282
+ override var onUserLocationChange: ((event: UserLocationChangeEvent) -> Unit)? = null
283
+ set(value) { field = value; mapProvider.onUserLocationChange = value }
284
+
285
+ override var onUserTrackingModeChange: ((mode: UserTrackingMode) -> Unit)? = null
286
+ set(value) { field = value; mapProvider.onUserTrackingModeChange = value }
287
+
288
+ override var onUserLocationError: ((error: MapError) -> Unit)? = null
289
+ set(value) { field = value; mapProvider.onUserLocationError = value }
290
+
291
+ override var onMapIdle: (() -> Unit)? = null
292
+ set(value) { field = value; mapProvider.onMapIdle = value }
293
+
294
+ // MARK: - Thread Dispatch Helper
295
+
296
+ /**
297
+ * Run a block on the main thread and return the result.
298
+ * If already on the main thread, executes directly.
299
+ * Otherwise, posts to mainHandler and waits with CountDownLatch.
300
+ */
301
+ // C-2: Increased timeout to 10s and added graceful handling.
302
+ // Using indefinite await risks permanent JS thread block if main thread is dead.
303
+ // 10s is generous enough to survive heavy main-thread load without ANR (5s threshold).
304
+ private fun <T> runOnMainThread(caller: String = "unknown", block: () -> T): T {
305
+ if (Looper.myLooper() == Looper.getMainLooper()) return block()
306
+ val ref = AtomicReference<T>()
307
+ val errorRef = AtomicReference<Throwable>()
308
+ val latch = CountDownLatch(1)
309
+ mainHandler.post {
310
+ try {
311
+ ref.set(block())
312
+ } catch (e: Throwable) {
313
+ errorRef.set(e)
314
+ } finally {
315
+ latch.countDown()
316
+ }
317
+ }
318
+ val completed = latch.await(10, TimeUnit.SECONDS)
319
+ if (!completed) {
320
+ Log.w(TAG, "$caller: main thread dispatch timed out after 10s")
321
+ throw RuntimeException("NitroMap.$caller: main thread dispatch timed out after 10s")
322
+ }
323
+ errorRef.get()?.let { throw it }
324
+ return ref.get()
325
+ }
326
+
327
+ // MARK: - Methods (dispatch to mapProvider)
328
+
329
+ override fun animateToRegion(region: Region, duration: Double?) =
330
+ mapProvider.animateToRegion(region, duration)
331
+
332
+ override fun fitToCoordinates(coordinates: Array<Coordinate>, edgePadding: EdgePadding?, animated: Boolean?) =
333
+ mapProvider.fitToCoordinates(coordinates, edgePadding, animated)
334
+
335
+ override fun fitToSuppliedMarkers(markerIds: Array<String>, edgePadding: EdgePadding?, animated: Boolean?) =
336
+ mapProvider.fitToSuppliedMarkers(markerIds, edgePadding, animated)
337
+
338
+ override fun animateCamera(camera: Camera, duration: Double?) =
339
+ mapProvider.animateCamera(camera, duration)
340
+
341
+ override fun getCamera(): Promise<Camera> = Promise.async {
342
+ runOnMainThread("getCamera") { mapProvider.getCamera() }
343
+ }
344
+
345
+ override fun setCamera(camera: Camera) = mapProvider.setCamera(camera)
346
+
347
+ override fun setMapStyle(style: Array<MapStyleElement>?) { customMapStyle = style }
348
+
349
+ override fun getMapBoundaries(): Promise<MapBoundaries> = Promise.async {
350
+ runOnMainThread("getMapBoundaries") {
351
+ mapProvider.getMapBoundaries() ?: MapBoundaries(Coordinate(0.0, 0.0), Coordinate(0.0, 0.0))
352
+ }
353
+ }
354
+
355
+ override fun centerOnUserLocation() = mapProvider.centerOnUserLocation()
356
+
357
+ override fun pointForCoordinate(coordinate: Coordinate): Point =
358
+ runOnMainThread("pointForCoordinate") { mapProvider.pointForCoordinate(coordinate) }
359
+
360
+ override fun coordinateForPoint(point: Point): Coordinate =
361
+ runOnMainThread("coordinateForPoint") { mapProvider.coordinateForPoint(point) }
362
+
363
+ override fun setIsDarkMode(enabled: Boolean) { darkMode = enabled }
364
+
365
+ // MARK: - Marker Methods (Batch 5)
366
+ // C-1/C-2: All mutating methods dispatch to main thread to match iOS DispatchQueue.main.async.
367
+ // These methods return Unit so fire-and-forget via mainHandler.post is safe.
368
+ override fun addMarker(marker: MarkerData) { mainHandler.post { mapProvider.addMarker(marker) } }
369
+ override fun addMarkers(markers: Array<MarkerData>) { mainHandler.post { mapProvider.addMarkers(markers) } }
370
+ override fun updateMarker(marker: MarkerData) { mainHandler.post { mapProvider.updateMarker(marker) } }
371
+ override fun removeMarker(id: String) { mainHandler.post { mapProvider.removeMarker(id) } }
372
+ override fun selectMarker(id: String) { mainHandler.post { mapProvider.selectMarker(id) } }
373
+ override fun deselectMarker() { mainHandler.post { mapProvider.deselectMarker() } }
374
+ override fun clearMarkers() { mainHandler.post { mapProvider.clearMarkers() } }
375
+
376
+
377
+ // MARK: - Clustering Methods
378
+ override fun setClusteringEnabled(enabled: Boolean) { mainHandler.post { mapProvider.setClusteringEnabled(enabled) } }
379
+ override fun refreshClusters() { mainHandler.post { mapProvider.refreshClusters() } }
380
+
381
+ // MARK: - Overlay Methods (Polyline, Polygon, Circle)
382
+ override fun addPolyline(polyline: PolylineData) { mainHandler.post { mapProvider.addPolyline(polyline) } }
383
+ override fun updatePolyline(polyline: PolylineData) { mainHandler.post { mapProvider.updatePolyline(polyline) } }
384
+ override fun removePolyline(id: String) { mainHandler.post { mapProvider.removePolyline(id) } }
385
+ override fun clearPolylines() { mainHandler.post { mapProvider.clearPolylines() } }
386
+ override fun addPolygon(polygon: PolygonData) { mainHandler.post { mapProvider.addPolygon(polygon) } }
387
+ override fun updatePolygon(polygon: PolygonData) { mainHandler.post { mapProvider.updatePolygon(polygon) } }
388
+ override fun removePolygon(id: String) { mainHandler.post { mapProvider.removePolygon(id) } }
389
+ override fun clearPolygons() { mainHandler.post { mapProvider.clearPolygons() } }
390
+ override fun addCircle(circle: CircleData) { mainHandler.post { mapProvider.addCircle(circle) } }
391
+ override fun updateCircle(circle: CircleData) { mainHandler.post { mapProvider.updateCircle(circle) } }
392
+ override fun removeCircle(id: String) { mainHandler.post { mapProvider.removeCircle(id) } }
393
+ override fun clearCircles() { mainHandler.post { mapProvider.clearCircles() } }
394
+
395
+ override val memorySize: Long
396
+ get() = 0L
397
+ }
@@ -0,0 +1,53 @@
1
+ package com.margelo.nitro.nitromap
2
+
3
+ import android.util.Log
4
+ import com.google.android.gms.maps.MapsInitializer
5
+ import com.margelo.nitro.NitroModules
6
+
7
+ class HybridNitroMapConfig : HybridNitroMapConfigSpec() {
8
+
9
+ companion object {
10
+ private var isInitialized = false
11
+ /** The provider that was initialized. Used for consistency validation. */
12
+ var initializedProvider: MapProvider? = null
13
+ private set
14
+ }
15
+
16
+ override fun NitroMapInitialize(apiKey: String, provider: MapProvider) {
17
+ if (isInitialized) {
18
+ Log.d("NitroMap", "Already initialized, skipping...")
19
+ return
20
+ }
21
+
22
+ when (provider) {
23
+ MapProvider.GOOGLE -> {
24
+ try {
25
+ // On Android, the API key is read from AndroidManifest.xml <meta-data>
26
+ // at build time — not passed at runtime like iOS.
27
+ val context = NitroModules.applicationContext
28
+ ?: throw IllegalStateException("Application context not available")
29
+ MapsInitializer.initialize(context)
30
+ isInitialized = true
31
+ initializedProvider = provider
32
+ Log.d("NitroMap", "Google Maps initialized")
33
+ } catch (e: Exception) {
34
+ Log.e("NitroMap", "Failed to initialize Google Maps: ${e.message}")
35
+ }
36
+ }
37
+ MapProvider.APPLE -> {
38
+ Log.e("NitroMap", "Apple Maps is not available on Android")
39
+ }
40
+ MapProvider.YANDEX -> {
41
+ // Future: YandexMapKit initialization with apiKey
42
+ Log.e("NitroMap", "Yandex Maps is not yet implemented")
43
+ }
44
+ }
45
+ }
46
+
47
+ override fun IsNitroMapInitialized(): Boolean {
48
+ return isInitialized
49
+ }
50
+
51
+ override val memorySize: Long
52
+ get() = 0L
53
+ }
@@ -8,10 +8,12 @@ import com.facebook.react.uimanager.ViewManager
8
8
 
9
9
  import com.margelo.nitro.nitromap.views.HybridNitroMapManager
10
10
 
11
+ /**
12
+ * React Native Package for NitroMap.
13
+ * Registers the ViewManager and loads the native C++ library.
14
+ */
11
15
  class NitroMapPackage : BaseReactPackage() {
12
16
  override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
13
- // Set context for HybridNitroMapConfig on first module access
14
- HybridNitroMapConfig.setContext(reactContext)
15
17
  return null
16
18
  }
17
19
 
@@ -20,8 +22,6 @@ class NitroMapPackage : BaseReactPackage() {
20
22
  }
21
23
 
22
24
  override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
23
- // Set context for HybridNitroMapConfig
24
- HybridNitroMapConfig.setContext(reactContext)
25
25
  return listOf(HybridNitroMapManager())
26
26
  }
27
27
 
@@ -0,0 +1,73 @@
1
+ package com.margelo.nitro.nitromap
2
+
3
+ import android.content.Context
4
+ import android.view.Choreographer
5
+ import android.view.View
6
+ import android.view.ViewGroup
7
+ import com.google.android.gms.maps.MapView
8
+
9
+ /**
10
+ * Custom MapView subclass that fixes two Fabric layout issues:
11
+ *
12
+ * 1. **Viewport clipping**: When GoogleMap.setPadding() is called, the SDK internally
13
+ * calls View.setPadding() on this MapView. In Fabric, that View-level padding
14
+ * causes the viewport to shrink/clip. We block it here.
15
+ *
16
+ * 2. **Controls not measured**: In Fabric, requestLayout() is intercepted by Yoga,
17
+ * so when the compass transitions INVISIBLE→VISIBLE, it never gets re-measured
18
+ * (stays 0x0). We use Choreographer to force a manual layout pass whenever
19
+ * requestLayout is called, ensuring all internal SDK views get measured.
20
+ */
21
+ class NitroMapView(context: Context) : MapView(context) {
22
+
23
+ // Flag to track if construction is complete (requestLayout is called
24
+ // during super constructor before our fields are initialized)
25
+ private var isConstructed = false
26
+
27
+ private var layoutRequested = false
28
+
29
+ private val layoutCallback = Choreographer.FrameCallback {
30
+ layoutRequested = false
31
+ // Manually measure and layout this view and all children.
32
+ // Fabric's Yoga sets our dimensions, so we use them as-is.
33
+ if (width > 0 && height > 0) {
34
+ measure(
35
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
36
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
37
+ )
38
+ layout(left, top, right, bottom)
39
+ }
40
+ }
41
+
42
+ init {
43
+ isConstructed = true
44
+ }
45
+
46
+ /**
47
+ * Block View-level padding changes.
48
+ * GoogleMap.setPadding() internally calls this, which in Fabric causes
49
+ * the viewport to shrink. The SDK still positions controls (compass, logo)
50
+ * via their own LayoutParams using the stored GoogleMap padding values.
51
+ */
52
+ override fun setPadding(left: Int, top: Int, right: Int, bottom: Int) {
53
+ // Intentionally blocked — prevents Fabric viewport shrinking
54
+ }
55
+
56
+ /**
57
+ * Override requestLayout to schedule a manual layout pass via Choreographer.
58
+ * In Fabric, the default requestLayout() is intercepted by Yoga and may not
59
+ * trigger a real measure/layout pass for this view's children. By using
60
+ * Choreographer, we ensure internal SDK views (compass, logo, etc.) get
61
+ * measured when they change visibility or position.
62
+ */
63
+ override fun requestLayout() {
64
+ super.requestLayout()
65
+ // Guard: skip during super constructor (fields not yet initialized)
66
+ if (!isConstructed) return
67
+ // Avoid scheduling multiple callbacks per frame
68
+ if (!layoutRequested) {
69
+ layoutRequested = true
70
+ Choreographer.getInstance().postFrameCallback(layoutCallback)
71
+ }
72
+ }
73
+ }