@maydon_tech/react-native-nitro-maps 0.1.4 → 0.2.1

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 (222) 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/build.gradle +6 -2
  6. package/android/gradle.properties +4 -4
  7. package/android/src/main/cpp/ClusterEngineJNI.cpp +198 -0
  8. package/android/src/main/kotlin/com/margelo/nitro/nitromap/NitroMap.kt +397 -0
  9. package/android/src/main/kotlin/com/margelo/nitro/nitromap/NitroMapConfig.kt +53 -0
  10. package/android/src/main/{java → kotlin}/com/margelo/nitro/nitromap/NitroMapPackage.kt +4 -4
  11. package/android/src/main/kotlin/com/margelo/nitro/nitromap/NitroMapView.kt +73 -0
  12. package/android/src/main/kotlin/com/margelo/nitro/nitromap/UserLocationManager.kt +295 -0
  13. package/android/src/main/kotlin/com/margelo/nitro/nitromap/clustering/ClusterIconRenderer.kt +111 -0
  14. package/android/src/main/kotlin/com/margelo/nitro/nitromap/clustering/ClusteringManager.kt +104 -0
  15. package/android/src/main/kotlin/com/margelo/nitro/nitromap/clustering/NitroClusterEngine.kt +166 -0
  16. package/android/src/main/kotlin/com/margelo/nitro/nitromap/markers/MarkerIconFactory.kt +303 -0
  17. package/android/src/main/kotlin/com/margelo/nitro/nitromap/markers/MarkerSelectionHandler.kt +72 -0
  18. package/android/src/main/kotlin/com/margelo/nitro/nitromap/markers/PriceMarkerRenderer.kt +159 -0
  19. package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/MapProviderFactory.kt +24 -0
  20. package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/MapProviderInterface.kt +128 -0
  21. package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/google/GoogleMapDelegate.kt +317 -0
  22. package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/google/GoogleMapProvider+Clustering.kt +524 -0
  23. package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/google/GoogleMapProvider+Markers.kt +358 -0
  24. package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/google/GoogleMapProvider+Overlays.kt +272 -0
  25. package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/google/GoogleMapProvider+UserLocation.kt +296 -0
  26. package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/google/GoogleMapProvider.kt +815 -0
  27. package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/google/MarkerTagData.kt +19 -0
  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 +137 -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 +84 -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++/JCoordinateRing.hpp +77 -0
  125. package/nitrogen/generated/android/c++/JFunc_void_UserLocationChangeEvent.hpp +79 -0
  126. package/nitrogen/generated/android/c++/JFunc_void_UserTrackingMode.hpp +77 -0
  127. package/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
  128. package/nitrogen/generated/android/c++/JHybridNitroMapSpec.cpp +332 -21
  129. package/nitrogen/generated/android/c++/JHybridNitroMapSpec.hpp +53 -2
  130. package/nitrogen/generated/android/c++/JMarkerAnimation.hpp +3 -6
  131. package/nitrogen/generated/android/c++/JMarkerData.hpp +15 -3
  132. package/nitrogen/generated/android/c++/JPolygonData.hpp +133 -0
  133. package/nitrogen/generated/android/c++/JPolylineData.hpp +113 -0
  134. package/nitrogen/generated/android/c++/JUserLocationChangeEvent.hpp +70 -0
  135. package/nitrogen/generated/android/c++/JUserTrackingMode.hpp +62 -0
  136. package/nitrogen/generated/android/c++/views/JHybridNitroMapStateUpdater.cpp +72 -4
  137. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/CircleData.kt +62 -0
  138. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/ClusterConfig.kt +4 -4
  139. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/CoordinateRing.kt +38 -0
  140. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/Func_void_UserLocationChangeEvent.kt +80 -0
  141. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/Func_void_UserTrackingMode.kt +80 -0
  142. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/Func_void_std__string.kt +80 -0
  143. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/HybridNitroMapSpec.kt +228 -2
  144. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/MarkerAnimation.kt +1 -2
  145. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/MarkerData.kt +12 -3
  146. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/PolygonData.kt +62 -0
  147. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/PolylineData.kt +62 -0
  148. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/UserLocationChangeEvent.kt +47 -0
  149. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/{ClusterAnimationStyle.kt → UserTrackingMode.kt} +6 -8
  150. package/nitrogen/generated/android/nitromapOnLoad.cpp +6 -0
  151. package/nitrogen/generated/ios/NitroMap-Swift-Cxx-Bridge.cpp +24 -0
  152. package/nitrogen/generated/ios/NitroMap-Swift-Cxx-Bridge.hpp +178 -17
  153. package/nitrogen/generated/ios/NitroMap-Swift-Cxx-Umbrella.hpp +18 -3
  154. package/nitrogen/generated/ios/c++/HybridNitroMapSpecSwift.hpp +252 -16
  155. package/nitrogen/generated/ios/c++/views/HybridNitroMapComponent.mm +90 -5
  156. package/nitrogen/generated/ios/swift/CircleData.swift +143 -0
  157. package/nitrogen/generated/ios/swift/ClusterConfig.swift +22 -15
  158. package/nitrogen/generated/ios/swift/CoordinateRing.swift +48 -0
  159. package/nitrogen/generated/ios/swift/Func_void_UserLocationChangeEvent.swift +47 -0
  160. package/nitrogen/generated/ios/swift/Func_void_UserTrackingMode.swift +47 -0
  161. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +47 -0
  162. package/nitrogen/generated/ios/swift/HybridNitroMapSpec.swift +35 -1
  163. package/nitrogen/generated/ios/swift/HybridNitroMapSpec_cxx.swift +582 -8
  164. package/nitrogen/generated/ios/swift/MarkerAnimation.swift +4 -8
  165. package/nitrogen/generated/ios/swift/MarkerData.swift +54 -2
  166. package/nitrogen/generated/ios/swift/PolygonData.swift +167 -0
  167. package/nitrogen/generated/ios/swift/PolylineData.swift +155 -0
  168. package/nitrogen/generated/ios/swift/UserLocationChangeEvent.swift +69 -0
  169. package/nitrogen/generated/ios/swift/UserTrackingMode.swift +44 -0
  170. package/nitrogen/generated/shared/c++/CircleData.hpp +113 -0
  171. package/nitrogen/generated/shared/c++/ClusterConfig.hpp +5 -8
  172. package/nitrogen/generated/shared/c++/CoordinateRing.hpp +77 -0
  173. package/nitrogen/generated/shared/c++/HybridNitroMapSpec.cpp +53 -2
  174. package/nitrogen/generated/shared/c++/HybridNitroMapSpec.hpp +75 -6
  175. package/nitrogen/generated/shared/c++/MarkerAnimation.hpp +4 -8
  176. package/nitrogen/generated/shared/c++/MarkerData.hpp +14 -2
  177. package/nitrogen/generated/shared/c++/PolygonData.hpp +117 -0
  178. package/nitrogen/generated/shared/c++/PolylineData.hpp +114 -0
  179. package/nitrogen/generated/shared/c++/UserLocationChangeEvent.hpp +88 -0
  180. package/nitrogen/generated/shared/c++/UserTrackingMode.hpp +80 -0
  181. package/nitrogen/generated/shared/c++/views/HybridNitroMapComponent.cpp +216 -12
  182. package/nitrogen/generated/shared/c++/views/HybridNitroMapComponent.hpp +23 -1
  183. package/nitrogen/generated/shared/json/NitroMapConfig.json +18 -1
  184. package/package.json +36 -5
  185. package/src/components/ImageMarker.tsx +58 -42
  186. package/src/components/Marker.tsx +161 -0
  187. package/src/components/NitroCircle.tsx +183 -0
  188. package/src/components/NitroMap.tsx +328 -78
  189. package/src/components/NitroPolygon.tsx +229 -0
  190. package/src/components/NitroPolyline.tsx +208 -0
  191. package/src/components/PriceMarker.tsx +23 -48
  192. package/src/context/NitroMapContext.tsx +4 -0
  193. package/src/hooks/useNitroCircle.ts +25 -0
  194. package/src/hooks/useNitroMarker.ts +49 -10
  195. package/src/hooks/useNitroOverlay.ts +68 -0
  196. package/src/hooks/useNitroPolygon.ts +25 -0
  197. package/src/hooks/useNitroPolyline.ts +25 -0
  198. package/src/index.tsx +28 -2
  199. package/src/specs/NitroMap.nitro.ts +294 -5
  200. package/src/types/map.ts +36 -4
  201. package/src/types/marker.ts +24 -44
  202. package/src/types/overlay.ts +87 -0
  203. package/src/types/theme.ts +101 -0
  204. package/src/utils/colors.ts +48 -16
  205. package/src/utils/validation.ts +69 -0
  206. package/android/src/main/java/com/margelo/nitro/nitromap/ClusterIconGenerator.kt +0 -108
  207. package/android/src/main/java/com/margelo/nitro/nitromap/ColorUtils.kt +0 -63
  208. package/android/src/main/java/com/margelo/nitro/nitromap/HybridNitroMap.kt +0 -408
  209. package/android/src/main/java/com/margelo/nitro/nitromap/HybridNitroMapConfig.kt +0 -68
  210. package/android/src/main/java/com/margelo/nitro/nitromap/MarkerIconCache.kt +0 -176
  211. package/android/src/main/java/com/margelo/nitro/nitromap/MarkerIconFactory.kt +0 -252
  212. package/android/src/main/java/com/margelo/nitro/nitromap/clustering/NitroClusterEngine.kt +0 -252
  213. package/android/src/main/java/com/margelo/nitro/nitromap/clustering/QuadTree.kt +0 -195
  214. package/android/src/main/java/com/margelo/nitro/nitromap/providers/GoogleMapProvider.kt +0 -912
  215. package/android/src/main/java/com/margelo/nitro/nitromap/providers/MapProviderInterface.kt +0 -70
  216. package/cpp/QuadTree.hpp +0 -246
  217. package/ios/NitroMapConfig/HybridNitroMapConfig.swift +0 -33
  218. package/ios/Providers/GoogleMapProvider+Camera.swift +0 -164
  219. package/ios/Providers/GoogleMapProvider.swift +0 -924
  220. package/nitrogen/generated/android/c++/JClusterAnimationStyle.hpp +0 -68
  221. package/nitrogen/generated/ios/swift/ClusterAnimationStyle.swift +0 -52
  222. package/nitrogen/generated/shared/c++/ClusterAnimationStyle.hpp +0 -88
@@ -0,0 +1,342 @@
1
+ import Foundation
2
+ import GoogleMaps
3
+ import GoogleMapsUtils
4
+ import UIKit
5
+
6
+ /// Google Maps implementation of MapProviderProtocol
7
+ class GoogleMapProvider: MapProviderProtocol {
8
+
9
+ // MARK: - Constants
10
+
11
+ private static let defaultLatitude: Double = 41.2995
12
+ private static let defaultLongitude: Double = 69.2401
13
+
14
+ /// Notification fired when an async marker icon finishes loading.
15
+ static let markerIconLoadedNotification = Notification.Name("MarkerIconLoaded")
16
+
17
+ // MARK: - Properties
18
+
19
+ // Internal (not private) because extensions in separate files need access.
20
+ // External consumers interact via MapProviderProtocol.mapView (UIView).
21
+ let gmsMapView: GMSMapView
22
+ var mapView: UIView { return gmsMapView }
23
+
24
+ private var mapDelegate: GoogleMapDelegate?
25
+
26
+ // MARK: - Injected Dependencies (DIP)
27
+
28
+ let clusteringManager: ClusteringManagerProtocol
29
+ let selectionHandler: MarkerSelectionHandling
30
+
31
+ // Rendered markers (internal for cross-file extensions)
32
+ var renderedClusterMarkers: [GMSMarker] = []
33
+ var renderedSingleMarkers: [String: GMSMarker] = [:]
34
+ var nonClusteredMarkers: [String: GMSMarker] = [:]
35
+ var hiddenNonClusteredIds: Set<String> = [] // Non-clustered markers hidden due to overlap
36
+ var polylines: [String: GMSPolyline] = [:]
37
+ var dashedPolylineData: [String: PolylineData] = [:]
38
+ var polygons: [String: GMSPolygon] = [:]
39
+ var circles: [String: GMSCircle] = [:]
40
+ var customUserLocationMarker: GMSMarker?
41
+ var userLocationImageData: UIImage?
42
+ var clusterableMarkerData: [String: MarkerData] = [:]
43
+
44
+ var selectedMarkerId: String?
45
+
46
+ /// Last known user location — used by delegate for my-location button
47
+ var lastKnownUserLocation: CLLocation?
48
+
49
+ static let defaultMarkerSize: CGFloat = 44
50
+
51
+ // Cached URL to avoid re-downloading the same image
52
+ var cachedUserLocationImageUrl: String = ""
53
+
54
+ // Throttle timer for clustering (used by +Clustering extension)
55
+ var throttleTimer: Timer?
56
+
57
+ // Track zoom level to detect cluster structure changes (used by +Clustering animation)
58
+ var lastClusteredZoom: Float = -1
59
+ var clusteringDirty: Bool = false
60
+
61
+ // MARK: - Protocol Properties
62
+
63
+ var initialRegion: Region? = Region(latitude: defaultLatitude, longitude: defaultLongitude, latitudeDelta: 0.15, longitudeDelta: 0.15) {
64
+ didSet {
65
+ // Only move to initial region on first set, not on every re-render
66
+ guard oldValue == nil else { return }
67
+ updateCameraToInitialRegion()
68
+ }
69
+ }
70
+
71
+ var region: Region? {
72
+ didSet {
73
+ guard let region = region else { return }
74
+ // Guard: skip if camera is already at this position (prevent loops)
75
+ let currentPos = gmsMapView.camera
76
+ let latDiff = Swift.abs(currentPos.target.latitude - region.latitude)
77
+ let lonDiff = Swift.abs(currentPos.target.longitude - region.longitude)
78
+ guard latDiff > 0.0001 || lonDiff > 0.0001 else { return }
79
+
80
+ let camera = GMSCameraPosition.camera(
81
+ withLatitude: region.latitude,
82
+ longitude: region.longitude,
83
+ zoom: calculateZoom(for: region)
84
+ )
85
+ gmsMapView.animate(to: camera)
86
+ }
87
+ }
88
+
89
+ var showsUserLocation: Bool? {
90
+ didSet {
91
+ // Only show native blue dot when no custom image is set
92
+ if userLocationImage.isEmpty {
93
+ gmsMapView.isMyLocationEnabled = showsUserLocation ?? false
94
+ } else {
95
+ gmsMapView.isMyLocationEnabled = false
96
+ }
97
+ }
98
+ }
99
+
100
+ var zoomEnabled: Bool? {
101
+ didSet { gmsMapView.settings.zoomGestures = zoomEnabled ?? true }
102
+ }
103
+
104
+ var scrollEnabled: Bool? {
105
+ didSet { gmsMapView.settings.scrollGestures = scrollEnabled ?? true }
106
+ }
107
+
108
+ var rotateEnabled: Bool? {
109
+ didSet { gmsMapView.settings.rotateGestures = rotateEnabled ?? true }
110
+ }
111
+
112
+ var pitchEnabled: Bool? {
113
+ didSet { gmsMapView.settings.tiltGestures = pitchEnabled ?? true }
114
+ }
115
+
116
+ var mapType: MapType? {
117
+ didSet { gmsMapView.mapType = convertMapType(mapType) }
118
+ }
119
+
120
+ var clusterConfig: ClusterConfig? {
121
+ didSet {
122
+ updateClusterConfig()
123
+ }
124
+ }
125
+
126
+ var customMapStyle: [MapStyleElement]? {
127
+ didSet { applyMapStyle() }
128
+ }
129
+
130
+ var darkMode: Bool? {
131
+ didSet { applyDarkMode() }
132
+ }
133
+
134
+ var mapPadding: EdgePadding = EdgePadding(top: 0, right: 0, bottom: 0, left: 0) {
135
+ didSet {
136
+ gmsMapView.padding = UIEdgeInsets(
137
+ top: mapPadding.top,
138
+ left: mapPadding.left,
139
+ bottom: mapPadding.bottom,
140
+ right: mapPadding.right
141
+ )
142
+ }
143
+ }
144
+
145
+ var showsTraffic: Bool? {
146
+ didSet { gmsMapView.isTrafficEnabled = showsTraffic ?? false }
147
+ }
148
+
149
+ var showsBuildings: Bool? {
150
+ didSet { gmsMapView.isBuildingsEnabled = showsBuildings ?? true }
151
+ }
152
+
153
+ var showsCompass: Bool? {
154
+ didSet { gmsMapView.settings.compassButton = showsCompass ?? true }
155
+ }
156
+
157
+ var minZoom: Double = 1 {
158
+ didSet { applyZoomLimits() }
159
+ }
160
+
161
+ var maxZoom: Double = 22 {
162
+ didSet { applyZoomLimits() }
163
+ }
164
+
165
+ var userTrackingMode: UserTrackingMode?
166
+
167
+ var userLocationImage: String = "" {
168
+ didSet {
169
+ guard userLocationImage != oldValue else { return }
170
+ setupCustomUserLocationMarker()
171
+ }
172
+ }
173
+
174
+ var userLocationSize: Double = 40 {
175
+ didSet {
176
+ guard userLocationSize != oldValue else { return }
177
+ setupCustomUserLocationMarker()
178
+ }
179
+ }
180
+
181
+ var userLocationAnchor: Point? {
182
+ didSet {
183
+ // Update anchor on existing custom marker
184
+ if let anchor = userLocationAnchor {
185
+ customUserLocationMarker?.groundAnchor = CGPoint(x: anchor.x, y: anchor.y)
186
+ } else {
187
+ customUserLocationMarker?.groundAnchor = CGPoint(x: 0.5, y: 0.5)
188
+ }
189
+ }
190
+ }
191
+
192
+ private func applyZoomLimits() {
193
+ gmsMapView.setMinZoom(Float(minZoom), maxZoom: Float(maxZoom))
194
+ }
195
+
196
+ // MARK: - Callbacks
197
+
198
+ var onPress: ((MapPressEvent) -> Void)?
199
+ var onLongPress: ((MapPressEvent) -> Void)?
200
+ var onMapReady: (() -> Void)?
201
+ var onRegionChange: ((RegionChangeEvent) -> Void)?
202
+ var onRegionChangeComplete: ((RegionChangeEvent) -> Void)?
203
+ var onMarkerPress: ((MarkerPressEvent) -> Void)?
204
+ var onMarkerDragStart: ((MarkerDragEvent) -> Void)?
205
+ var onMarkerDrag: ((MarkerDragEvent) -> Void)?
206
+ var onMarkerDragEnd: ((MarkerDragEvent) -> Void)?
207
+ var onClusterPress: ((ClusterPressEvent) -> Void)?
208
+ var onError: ((MapError) -> Void)?
209
+ var onUserTrackingModeChange: ((UserTrackingMode) -> Void)?
210
+ var onMapIdle: (() -> Void)?
211
+ var onPolylinePress: ((String) -> Void)?
212
+ var onPolygonPress: ((String) -> Void)?
213
+ var onCirclePress: ((String) -> Void)?
214
+
215
+ // MARK: - Initialization
216
+
217
+ /// Creates a GoogleMapProvider with injected dependencies.
218
+ /// Uses default production implementations if none provided.
219
+ ///
220
+ /// - Parameters:
221
+ /// - clusteringManager: Clustering logic handler (default: ClusteringManager)
222
+ /// - selectionHandler: Marker selection handler (default: MarkerSelectionHandler.shared)
223
+ init(
224
+ clusteringManager: ClusteringManagerProtocol = ClusteringManager(),
225
+ selectionHandler: MarkerSelectionHandling = MarkerSelectionHandler.shared
226
+ ) {
227
+ self.clusteringManager = clusteringManager
228
+ self.selectionHandler = selectionHandler
229
+
230
+ let camera = GMSCameraPosition.camera(
231
+ withLatitude: Self.defaultLatitude,
232
+ longitude: Self.defaultLongitude,
233
+ zoom: 10
234
+ )
235
+
236
+ // Use GMSMapViewOptions (required for Google Maps SDK v10.0+)
237
+ let options = GMSMapViewOptions()
238
+ options.camera = camera
239
+ options.frame = .zero
240
+
241
+ let mapView = GMSMapView(options: options)
242
+ mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
243
+ self.gmsMapView = mapView
244
+
245
+ setupIconLoadNotification()
246
+ }
247
+
248
+ deinit {
249
+ NotificationCenter.default.removeObserver(self)
250
+ }
251
+
252
+ // MARK: - Lifecycle
253
+
254
+ func setup() {
255
+ gmsMapView.settings.myLocationButton = false // Always hidden — consumers use centerOnUserLocation()
256
+ mapDelegate = GoogleMapDelegate(provider: self)
257
+ gmsMapView.delegate = mapDelegate
258
+ }
259
+
260
+ // MARK: - Map Style
261
+
262
+ func setMapStyle(_ style: [MapStyleElement]?) {
263
+ customMapStyle = style
264
+ }
265
+
266
+ func setDarkMode(_ enabled: Bool) {
267
+ darkMode = enabled
268
+ }
269
+
270
+ private func applyMapStyle() {
271
+ guard let styleElements = customMapStyle, !styleElements.isEmpty else {
272
+ gmsMapView.mapStyle = nil
273
+ return
274
+ }
275
+
276
+ do {
277
+ let jsonArray = styleElements.map { element -> [String: Any] in
278
+ var dict: [String: Any] = [:]
279
+ if let featureType = element.featureType {
280
+ dict["featureType"] = featureType
281
+ }
282
+ if let elementType = element.elementType {
283
+ dict["elementType"] = elementType
284
+ }
285
+
286
+ dict["stylers"] = element.stylers.map { styler -> [String: Any] in
287
+ var s: [String: Any] = [:]
288
+ if let color = styler.color { s["color"] = color }
289
+ if let visibility = styler.visibility { s["visibility"] = visibility }
290
+ if let weight = styler.weight { s["weight"] = weight }
291
+ if let saturation = styler.saturation { s["saturation"] = saturation }
292
+ if let lightness = styler.lightness { s["lightness"] = lightness }
293
+ if let gamma = styler.gamma { s["gamma"] = gamma }
294
+ return s
295
+ }
296
+ return dict
297
+ }
298
+
299
+ let jsonData = try JSONSerialization.data(withJSONObject: jsonArray)
300
+ let jsonString = String(data: jsonData, encoding: .utf8) ?? "[]"
301
+ gmsMapView.mapStyle = try GMSMapStyle(jsonString: jsonString)
302
+ } catch {
303
+ // Surface errors via onError instead of silent print
304
+ onError?(MapError(code: "STYLE_ERROR", message: "Failed to apply map style: \(error.localizedDescription)"))
305
+ }
306
+ }
307
+
308
+ private func applyDarkMode() {
309
+ if let isDark = darkMode {
310
+ gmsMapView.overrideUserInterfaceStyle = isDark ? .dark : .light
311
+ }
312
+ }
313
+
314
+ // MARK: - Helper Methods
315
+
316
+ private func updateCameraToInitialRegion() {
317
+ guard let region = initialRegion else { return }
318
+
319
+ let camera = GMSCameraPosition.camera(
320
+ withLatitude: region.latitude,
321
+ longitude: region.longitude,
322
+ zoom: calculateZoom(for: region)
323
+ )
324
+ gmsMapView.animate(to: camera)
325
+ }
326
+
327
+ func calculateZoom(for region: Region) -> Float {
328
+ return Float(MapStyleProvider.shared.zoomFromDeltas(
329
+ latDelta: region.latitudeDelta,
330
+ lonDelta: region.longitudeDelta
331
+ ))
332
+ }
333
+
334
+ private func convertMapType(_ type: MapType?) -> GMSMapViewType {
335
+ switch type {
336
+ case .satellite: return .satellite
337
+ case .hybrid: return .hybrid
338
+ case .standard, .none: return .normal
339
+ }
340
+ }
341
+
342
+ }
@@ -0,0 +1,17 @@
1
+ import Foundation
2
+
3
+ /// Factory that creates the correct map provider based on the provider type.
4
+ /// When new providers are added (Apple, Yandex), extend this factory.
5
+ enum MapProviderFactory {
6
+
7
+ static func create(for provider: MapProvider) -> MapProviderProtocol {
8
+ switch provider {
9
+ case .google:
10
+ return GoogleMapProvider()
11
+ case .apple:
12
+ fatalError("Apple Maps provider is not yet implemented. Coming in Phase 4.")
13
+ case .yandex:
14
+ fatalError("Yandex Maps provider is not yet implemented.")
15
+ }
16
+ }
17
+ }
@@ -1,5 +1,6 @@
1
1
  import Foundation
2
2
  import UIKit
3
+ import CoreLocation
3
4
 
4
5
  /// Protocol that all map providers must implement
5
6
  /// This allows the library to support multiple map backends (Google, Apple, Yandex)
@@ -13,16 +14,26 @@ protocol MapProviderProtocol: AnyObject {
13
14
  // MARK: - Properties
14
15
 
15
16
  var initialRegion: Region? { get set }
17
+ var region: Region? { get set }
16
18
  var showsUserLocation: Bool? { get set }
17
19
  var zoomEnabled: Bool? { get set }
18
20
  var scrollEnabled: Bool? { get set }
19
21
  var rotateEnabled: Bool? { get set }
20
22
  var pitchEnabled: Bool? { get set }
21
23
  var mapType: MapType? { get set }
22
- var showsMyLocationButton: Bool? { get set }
23
24
  var clusterConfig: ClusterConfig? { get set }
24
25
  var customMapStyle: [MapStyleElement]? { get set }
25
26
  var darkMode: Bool? { get set }
27
+ var mapPadding: EdgePadding { get set }
28
+ var showsTraffic: Bool? { get set }
29
+ var showsBuildings: Bool? { get set }
30
+ var showsCompass: Bool? { get set }
31
+ var minZoom: Double { get set }
32
+ var maxZoom: Double { get set }
33
+ var userTrackingMode: UserTrackingMode? { get set }
34
+ var userLocationImage: String { get set }
35
+ var userLocationSize: Double { get set }
36
+ var userLocationAnchor: Point? { get set }
26
37
 
27
38
  // MARK: - Callbacks
28
39
 
@@ -30,6 +41,7 @@ protocol MapProviderProtocol: AnyObject {
30
41
  var onLongPress: ((MapPressEvent) -> Void)? { get set }
31
42
  var onMapReady: (() -> Void)? { get set }
32
43
  var onRegionChange: ((RegionChangeEvent) -> Void)? { get set }
44
+ var onUserTrackingModeChange: ((UserTrackingMode) -> Void)? { get set }
33
45
  var onRegionChangeComplete: ((RegionChangeEvent) -> Void)? { get set }
34
46
  var onMarkerPress: ((MarkerPressEvent) -> Void)? { get set }
35
47
  var onMarkerDragStart: ((MarkerDragEvent) -> Void)? { get set }
@@ -37,20 +49,30 @@ protocol MapProviderProtocol: AnyObject {
37
49
  var onMarkerDragEnd: ((MarkerDragEvent) -> Void)? { get set }
38
50
  var onClusterPress: ((ClusterPressEvent) -> Void)? { get set }
39
51
  var onError: ((MapError) -> Void)? { get set }
52
+ var onMapIdle: (() -> Void)? { get set }
53
+ var onPolylinePress: ((String) -> Void)? { get set }
54
+ var onPolygonPress: ((String) -> Void)? { get set }
55
+ var onCirclePress: ((String) -> Void)? { get set }
40
56
 
41
57
  // MARK: - Lifecycle
42
58
 
43
59
  func setup()
44
60
  func updateSettings()
61
+
62
+ // MARK: - User Location
63
+ func updateUserLocation(_ location: CLLocation, heading: CLHeading?)
45
64
 
46
65
  // MARK: - Camera Methods
47
66
 
48
67
  func animateToRegion(_ region: Region, duration: Double?)
49
68
  func fitToCoordinates(_ coordinates: [Coordinate], edgePadding: EdgePadding?, animated: Bool?)
69
+ func fitToSuppliedMarkers(_ markerIds: [String], edgePadding: EdgePadding?, animated: Bool?)
50
70
  func animateCamera(_ camera: Camera, duration: Double?)
51
71
  func setCamera(_ camera: Camera)
52
72
  func getCamera() -> Camera
53
73
  func getMapBoundaries() -> MapBoundaries?
74
+ func pointForCoordinate(_ coordinate: Coordinate) -> Point
75
+ func coordinateForPoint(_ point: Point) -> Coordinate
54
76
 
55
77
  // MARK: - Marker Management
56
78
 
@@ -60,6 +82,7 @@ protocol MapProviderProtocol: AnyObject {
60
82
  func removeMarker(_ id: String)
61
83
  func clearMarkers()
62
84
  func selectMarker(_ id: String)
85
+ func deselectMarker()
63
86
 
64
87
  // MARK: - Clustering
65
88
 
@@ -75,4 +98,28 @@ protocol MapProviderProtocol: AnyObject {
75
98
  // MARK: - Region
76
99
 
77
100
  func getCurrentRegion() -> Region
101
+
102
+ /// Animate camera to the user's current location
103
+ func centerOnUserLocation()
104
+
105
+ // MARK: - Polyline Management
106
+
107
+ func addPolyline(_ polyline: PolylineData)
108
+ func updatePolyline(_ polyline: PolylineData)
109
+ func removePolyline(_ id: String)
110
+ func clearPolylines()
111
+
112
+ // MARK: - Polygon Management
113
+
114
+ func addPolygon(_ polygon: PolygonData)
115
+ func updatePolygon(_ polygon: PolygonData)
116
+ func removePolygon(_ id: String)
117
+ func clearPolygons()
118
+
119
+ // MARK: - Circle Management
120
+
121
+ func addCircle(_ circle: CircleData)
122
+ func updateCircle(_ circle: CircleData)
123
+ func removeCircle(_ id: String)
124
+ func clearCircles()
78
125
  }
@@ -22,8 +22,8 @@ extension ClusterConfig {
22
22
  let bc: Variant_String_MarkerColor = existing?.borderColor
23
23
  ?? .second(MarkerColor(r: 255, g: 255, b: 255, a: 255))
24
24
  let anim: Bool = existing?.animatesClusters ?? true
25
+ let animReappear: Bool = existing?.animateOnReappear ?? true
25
26
  let dur: Double = existing?.animationDuration ?? 0.3
26
- let style: ClusterAnimationStyle = existing?.animationStyle ?? .default
27
27
  let realtime: Bool = existing?.realtimeClustering ?? false
28
28
  let vpPad: Double = existing?.renderBuffer ?? 0
29
29
  let throttle: Double = existing?.throttleInterval ?? 150
@@ -39,8 +39,8 @@ extension ClusterConfig {
39
39
  borderWidth: bw,
40
40
  borderColor: bc,
41
41
  animatesClusters: anim,
42
+ animateOnReappear: animReappear,
42
43
  animationDuration: dur,
43
- animationStyle: style,
44
44
  realtimeClustering: realtime,
45
45
  renderBuffer: vpPad,
46
46
  throttleInterval: throttle
@@ -16,8 +16,10 @@ protocol MapStyleProviding {
16
16
  /// Converts altitude to approximate zoom level
17
17
  func zoomFromAltitude(_ altitude: Double) -> Double
18
18
 
19
- /// Gets the dark mode JSON style for Google Maps
20
- var googleDarkModeStyle: String { get }
19
+ /// Gets the dark mode JSON style for the current provider.
20
+ /// Currently uses Google Maps JSON format; future providers may need
21
+ /// their own styling format (Apple: MKMapConfiguration, Yandex: custom).
22
+ var darkModeStyle: String { get }
21
23
  }
22
24
 
23
25
  // MARK: - Implementation
@@ -71,7 +73,7 @@ final class MapStyleProvider: MapStyleProviding {
71
73
  return clamp(zoom, min: minZoom, max: maxZoom)
72
74
  }
73
75
 
74
- var googleDarkModeStyle: String {
76
+ var darkModeStyle: String {
75
77
  return Self.darkModeJSON
76
78
  }
77
79
 
@@ -116,7 +118,7 @@ final class MockMapStyleProvider: MapStyleProviding {
116
118
  var mockZoom: Double = 10.0
117
119
  var mockAltitude: Double = 40000.0
118
120
 
119
- var googleDarkModeStyle: String {
121
+ var darkModeStyle: String {
120
122
  return "[]" // Empty JSON for testing
121
123
  }
122
124
 
@@ -80,8 +80,11 @@ final class MarkerSelectionHandler: MarkerSelectionHandling {
80
80
  zIndex: markerData.zIndex,
81
81
  anchor: markerData.anchor,
82
82
  config: updatedConfig,
83
+ accessibilityLabel: markerData.accessibilityLabel,
83
84
  clusteringEnabled: markerData.clusteringEnabled,
84
- animation: markerData.animation
85
+ animation: markerData.animation,
86
+ animationDuration: markerData.animationDuration,
87
+ animateOnReappear: markerData.animateOnReappear
85
88
  )
86
89
  }
87
90
  }
@@ -1,101 +1,80 @@
1
1
  import Foundation
2
2
  import UIKit
3
3
 
4
+ // MARK: - Shared Hex Parser (DRY)
5
+ // Single implementation used by both ColorValue and Variant_String_MarkerColor
6
+
7
+ private func parseHexColor(_ hex: String) -> MarkerColor {
8
+ var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
9
+ hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
10
+
11
+ var rgb: UInt64 = 0
12
+ Scanner(string: hexSanitized).scanHexInt64(&rgb)
13
+
14
+ let r, g, b: Double
15
+ if hexSanitized.count == 6 {
16
+ r = Double((rgb & 0xFF0000) >> 16)
17
+ g = Double((rgb & 0x00FF00) >> 8)
18
+ b = Double(rgb & 0x0000FF)
19
+ } else if hexSanitized.count == 3 {
20
+ r = Double((rgb & 0xF00) >> 8) * 17
21
+ g = Double((rgb & 0x0F0) >> 4) * 17
22
+ b = Double(rgb & 0x00F) * 17
23
+ } else {
24
+ r = 0; g = 0; b = 0
25
+ }
26
+
27
+ return MarkerColor(r: r, g: g, b: b, a: 255)
28
+ }
29
+
30
+ // MARK: - MarkerColor → UIColor
31
+
32
+ extension MarkerColor {
33
+ /// Convert MarkerColor directly to UIColor
34
+ func toUIColor() -> UIColor {
35
+ return UIColor(
36
+ red: CGFloat(self.r) / 255,
37
+ green: CGFloat(self.g) / 255,
38
+ blue: CGFloat(self.b) / 255,
39
+ alpha: CGFloat(self.a) / 255
40
+ )
41
+ }
42
+ }
43
+
4
44
  // MARK: - ColorValue Extension
5
- // Shared extension for converting ColorValue (hex string or MarkerColor) to MarkerColor
45
+
6
46
  extension ColorValue {
7
47
  /// Extract MarkerColor from ColorValue, parsing hex string if needed
8
48
  func toMarkerColor() -> MarkerColor {
9
49
  switch self {
10
50
  case .first(let hexString):
11
- return ColorValue.parseHex(hexString)
51
+ return parseHexColor(hexString)
12
52
  case .second(let markerColor):
13
53
  return markerColor
14
54
  }
15
55
  }
16
56
 
17
- /// Parse hex color string to MarkerColor
18
- private static func parseHex(_ hex: String) -> MarkerColor {
19
- var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
20
- hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
21
-
22
- var rgb: UInt64 = 0
23
- Scanner(string: hexSanitized).scanHexInt64(&rgb)
24
-
25
- let r, g, b: Double
26
- if hexSanitized.count == 6 {
27
- r = Double((rgb & 0xFF0000) >> 16)
28
- g = Double((rgb & 0x00FF00) >> 8)
29
- b = Double(rgb & 0x0000FF)
30
- } else if hexSanitized.count == 3 {
31
- r = Double((rgb & 0xF00) >> 8) * 17
32
- g = Double((rgb & 0x0F0) >> 4) * 17
33
- b = Double(rgb & 0x00F) * 17
34
- } else {
35
- r = 0; g = 0; b = 0
36
- }
37
-
38
- return MarkerColor(r: r, g: g, b: b, a: 255)
39
- }
40
- }
41
-
42
- // MARK: - UIColor Helper
43
- extension ColorValue {
44
57
  /// Convert ColorValue directly to UIColor
45
58
  func toUIColor() -> UIColor {
46
- let color = self.toMarkerColor()
47
- return UIColor(
48
- red: CGFloat(color.r) / 255,
49
- green: CGFloat(color.g) / 255,
50
- blue: CGFloat(color.b) / 255,
51
- alpha: CGFloat(color.a) / 255
52
- )
59
+ return self.toMarkerColor().toUIColor()
53
60
  }
54
61
  }
55
62
 
56
63
  // MARK: - Variant_String_MarkerColor Extension
57
64
  // Nitrogen generates this as a separate type from ColorValue (same shape)
58
65
  // when the field is optional in the struct. We mirror the same helpers.
66
+
59
67
  extension Variant_String_MarkerColor {
60
68
  func toMarkerColor() -> MarkerColor {
61
69
  switch self {
62
70
  case .first(let hexString):
63
- return Variant_String_MarkerColor.parseHex(hexString)
71
+ return parseHexColor(hexString)
64
72
  case .second(let markerColor):
65
73
  return markerColor
66
74
  }
67
75
  }
68
76
 
69
77
  func toUIColor() -> UIColor {
70
- let color = self.toMarkerColor()
71
- return UIColor(
72
- red: CGFloat(color.r) / 255,
73
- green: CGFloat(color.g) / 255,
74
- blue: CGFloat(color.b) / 255,
75
- alpha: CGFloat(color.a) / 255
76
- )
77
- }
78
-
79
- private static func parseHex(_ hex: String) -> MarkerColor {
80
- var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
81
- hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
82
-
83
- var rgb: UInt64 = 0
84
- Scanner(string: hexSanitized).scanHexInt64(&rgb)
85
-
86
- let r, g, b: Double
87
- if hexSanitized.count == 6 {
88
- r = Double((rgb & 0xFF0000) >> 16)
89
- g = Double((rgb & 0x00FF00) >> 8)
90
- b = Double(rgb & 0x0000FF)
91
- } else if hexSanitized.count == 3 {
92
- r = Double((rgb & 0xF00) >> 8) * 17
93
- g = Double((rgb & 0x0F0) >> 4) * 17
94
- b = Double(rgb & 0x00F) * 17
95
- } else {
96
- r = 0; g = 0; b = 0
97
- }
98
-
99
- return MarkerColor(r: r, g: g, b: b, a: 255)
78
+ return self.toMarkerColor().toUIColor()
100
79
  }
101
80
  }