@maydon_tech/react-native-nitro-maps 0.1.4 → 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.
- package/LICENSE +1 -1
- package/NitroMap.podspec +1 -1
- package/README.md +82 -9
- package/android/CMakeLists.txt +4 -1
- package/android/gradle.properties +4 -4
- package/android/src/main/cpp/ClusterEngineJNI.cpp +198 -0
- package/android/src/main/kotlin/com/margelo/nitro/nitromap/NitroMap.kt +397 -0
- package/android/src/main/kotlin/com/margelo/nitro/nitromap/NitroMapConfig.kt +53 -0
- package/android/src/main/{java → kotlin}/com/margelo/nitro/nitromap/NitroMapPackage.kt +4 -4
- package/android/src/main/kotlin/com/margelo/nitro/nitromap/NitroMapView.kt +73 -0
- package/android/src/main/kotlin/com/margelo/nitro/nitromap/UserLocationManager.kt +295 -0
- package/android/src/main/kotlin/com/margelo/nitro/nitromap/clustering/ClusterIconRenderer.kt +111 -0
- package/android/src/main/kotlin/com/margelo/nitro/nitromap/clustering/ClusteringManager.kt +104 -0
- package/android/src/main/kotlin/com/margelo/nitro/nitromap/clustering/NitroClusterEngine.kt +166 -0
- package/android/src/main/kotlin/com/margelo/nitro/nitromap/markers/MarkerIconFactory.kt +303 -0
- package/android/src/main/kotlin/com/margelo/nitro/nitromap/markers/MarkerSelectionHandler.kt +72 -0
- package/android/src/main/kotlin/com/margelo/nitro/nitromap/markers/PriceMarkerRenderer.kt +159 -0
- package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/MapProviderFactory.kt +24 -0
- package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/MapProviderInterface.kt +128 -0
- package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/google/GoogleMapDelegate.kt +317 -0
- package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/google/GoogleMapProvider+Clustering.kt +524 -0
- package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/google/GoogleMapProvider+Markers.kt +358 -0
- package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/google/GoogleMapProvider+Overlays.kt +272 -0
- package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/google/GoogleMapProvider+UserLocation.kt +296 -0
- package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/google/GoogleMapProvider.kt +815 -0
- package/android/src/main/kotlin/com/margelo/nitro/nitromap/providers/google/MarkerTagData.kt +19 -0
- package/ios/Location/NitroLocationManager.swift +116 -0
- package/ios/MarkerRenderer/MarkerIconFactory.swift +1 -3
- package/ios/MarkerRenderer/PriceMarkerRenderer.swift +10 -6
- package/ios/NitroMap.swift +279 -13
- package/ios/NitroMapConfig/NitroMapConfig.swift +45 -0
- package/ios/Providers/{GoogleMapDelegate.swift → Google/GoogleMapDelegate.swift} +48 -23
- package/ios/Providers/Google/GoogleMapProvider+Camera.swift +180 -0
- package/ios/Providers/Google/GoogleMapProvider+Clustering.swift +541 -0
- package/ios/Providers/Google/GoogleMapProvider+Markers.swift +270 -0
- package/ios/Providers/Google/GoogleMapProvider+Overlays.swift +245 -0
- package/ios/Providers/Google/GoogleMapProvider+UserLocation.swift +180 -0
- package/ios/Providers/Google/GoogleMapProvider.swift +342 -0
- package/ios/Providers/MapProviderFactory.swift +17 -0
- package/ios/Providers/MapProviderProtocol.swift +48 -1
- package/ios/Shared/ClusterConfig+Factory.swift +2 -2
- package/ios/Shared/MapStyleProvider.swift +6 -4
- package/ios/Shared/MarkerSelectionHandler.swift +4 -1
- package/ios/Utils/ColorValueExtension.swift +46 -67
- package/lib/module/components/ImageMarker.js +39 -29
- package/lib/module/components/ImageMarker.js.map +1 -1
- package/lib/module/components/Marker.js +118 -0
- package/lib/module/components/Marker.js.map +1 -0
- package/lib/module/components/NitroCircle.js +92 -0
- package/lib/module/components/NitroCircle.js.map +1 -0
- package/lib/module/components/NitroMap.js +216 -76
- package/lib/module/components/NitroMap.js.map +1 -1
- package/lib/module/components/NitroPolygon.js +135 -0
- package/lib/module/components/NitroPolygon.js.map +1 -0
- package/lib/module/components/NitroPolyline.js +115 -0
- package/lib/module/components/NitroPolyline.js.map +1 -0
- package/lib/module/components/PriceMarker.js +16 -29
- package/lib/module/components/PriceMarker.js.map +1 -1
- package/lib/module/context/NitroMapContext.js.map +1 -1
- package/lib/module/hooks/useNitroCircle.js +18 -0
- package/lib/module/hooks/useNitroCircle.js.map +1 -0
- package/lib/module/hooks/useNitroMarker.js +26 -9
- package/lib/module/hooks/useNitroMarker.js.map +1 -1
- package/lib/module/hooks/useNitroOverlay.js +59 -0
- package/lib/module/hooks/useNitroOverlay.js.map +1 -0
- package/lib/module/hooks/useNitroPolygon.js +18 -0
- package/lib/module/hooks/useNitroPolygon.js.map +1 -0
- package/lib/module/hooks/useNitroPolyline.js +18 -0
- package/lib/module/hooks/useNitroPolyline.js.map +1 -0
- package/lib/module/index.js +5 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/types/overlay.js +4 -0
- package/lib/module/types/overlay.js.map +1 -0
- package/lib/module/types/theme.js +4 -0
- package/lib/module/types/theme.js.map +1 -0
- package/lib/module/utils/colors.js +41 -13
- package/lib/module/utils/colors.js.map +1 -1
- package/lib/module/utils/validation.js +45 -0
- package/lib/module/utils/validation.js.map +1 -0
- package/lib/typescript/src/components/ImageMarker.d.ts.map +1 -1
- package/lib/typescript/src/components/Marker.d.ts +34 -0
- package/lib/typescript/src/components/Marker.d.ts.map +1 -0
- package/lib/typescript/src/components/NitroCircle.d.ts +70 -0
- package/lib/typescript/src/components/NitroCircle.d.ts.map +1 -0
- package/lib/typescript/src/components/NitroMap.d.ts +60 -3
- package/lib/typescript/src/components/NitroMap.d.ts.map +1 -1
- package/lib/typescript/src/components/NitroPolygon.d.ts +86 -0
- package/lib/typescript/src/components/NitroPolygon.d.ts.map +1 -0
- package/lib/typescript/src/components/NitroPolyline.d.ts +84 -0
- package/lib/typescript/src/components/NitroPolyline.d.ts.map +1 -0
- package/lib/typescript/src/components/PriceMarker.d.ts +0 -5
- package/lib/typescript/src/components/PriceMarker.d.ts.map +1 -1
- package/lib/typescript/src/context/NitroMapContext.d.ts +2 -0
- package/lib/typescript/src/context/NitroMapContext.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useNitroCircle.d.ts +7 -0
- package/lib/typescript/src/hooks/useNitroCircle.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useNitroMarker.d.ts +20 -0
- package/lib/typescript/src/hooks/useNitroMarker.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useNitroOverlay.d.ts +26 -0
- package/lib/typescript/src/hooks/useNitroOverlay.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useNitroPolygon.d.ts +7 -0
- package/lib/typescript/src/hooks/useNitroPolygon.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useNitroPolyline.d.ts +7 -0
- package/lib/typescript/src/hooks/useNitroPolyline.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +15 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/specs/NitroMap.nitro.d.ts +248 -6
- package/lib/typescript/src/specs/NitroMap.nitro.d.ts.map +1 -1
- package/lib/typescript/src/types/map.d.ts +34 -4
- package/lib/typescript/src/types/map.d.ts.map +1 -1
- package/lib/typescript/src/types/marker.d.ts +24 -36
- package/lib/typescript/src/types/marker.d.ts.map +1 -1
- package/lib/typescript/src/types/overlay.d.ts +75 -0
- package/lib/typescript/src/types/overlay.d.ts.map +1 -0
- package/lib/typescript/src/types/theme.d.ts +93 -0
- package/lib/typescript/src/types/theme.d.ts.map +1 -0
- package/lib/typescript/src/utils/colors.d.ts +6 -8
- package/lib/typescript/src/utils/colors.d.ts.map +1 -1
- package/lib/typescript/src/utils/validation.d.ts +12 -0
- package/lib/typescript/src/utils/validation.d.ts.map +1 -0
- package/nitrogen/generated/android/c++/JCircleData.hpp +94 -0
- package/nitrogen/generated/android/c++/JClusterConfig.hpp +5 -7
- package/nitrogen/generated/android/c++/JFunc_void_UserLocationChangeEvent.hpp +79 -0
- package/nitrogen/generated/android/c++/JFunc_void_UserTrackingMode.hpp +77 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
- package/nitrogen/generated/android/c++/JHybridNitroMapSpec.cpp +328 -21
- package/nitrogen/generated/android/c++/JHybridNitroMapSpec.hpp +53 -2
- package/nitrogen/generated/android/c++/JMarkerAnimation.hpp +3 -6
- package/nitrogen/generated/android/c++/JMarkerData.hpp +15 -3
- package/nitrogen/generated/android/c++/JPolygonData.hpp +149 -0
- package/nitrogen/generated/android/c++/JPolylineData.hpp +113 -0
- package/nitrogen/generated/android/c++/JUserLocationChangeEvent.hpp +70 -0
- package/nitrogen/generated/android/c++/JUserTrackingMode.hpp +62 -0
- package/nitrogen/generated/android/c++/views/JHybridNitroMapStateUpdater.cpp +72 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/CircleData.kt +62 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/ClusterConfig.kt +4 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/Func_void_UserLocationChangeEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/Func_void_UserTrackingMode.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/Func_void_std__string.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/HybridNitroMapSpec.kt +228 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/MarkerAnimation.kt +1 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/MarkerData.kt +12 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/PolygonData.kt +62 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/PolylineData.kt +62 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/UserLocationChangeEvent.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromap/{ClusterAnimationStyle.kt → UserTrackingMode.kt} +6 -8
- package/nitrogen/generated/android/nitromapOnLoad.cpp +6 -0
- package/nitrogen/generated/ios/NitroMap-Swift-Cxx-Bridge.cpp +24 -0
- package/nitrogen/generated/ios/NitroMap-Swift-Cxx-Bridge.hpp +175 -17
- package/nitrogen/generated/ios/NitroMap-Swift-Cxx-Umbrella.hpp +15 -3
- package/nitrogen/generated/ios/c++/HybridNitroMapSpecSwift.hpp +249 -16
- package/nitrogen/generated/ios/c++/views/HybridNitroMapComponent.mm +90 -5
- package/nitrogen/generated/ios/swift/CircleData.swift +143 -0
- package/nitrogen/generated/ios/swift/ClusterConfig.swift +22 -15
- package/nitrogen/generated/ios/swift/Func_void_UserLocationChangeEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_UserTrackingMode.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string.swift +47 -0
- package/nitrogen/generated/ios/swift/HybridNitroMapSpec.swift +35 -1
- package/nitrogen/generated/ios/swift/HybridNitroMapSpec_cxx.swift +582 -8
- package/nitrogen/generated/ios/swift/MarkerAnimation.swift +4 -8
- package/nitrogen/generated/ios/swift/MarkerData.swift +54 -2
- package/nitrogen/generated/ios/swift/PolygonData.swift +179 -0
- package/nitrogen/generated/ios/swift/PolylineData.swift +155 -0
- package/nitrogen/generated/ios/swift/UserLocationChangeEvent.swift +69 -0
- package/nitrogen/generated/ios/swift/UserTrackingMode.swift +44 -0
- package/nitrogen/generated/shared/c++/CircleData.hpp +113 -0
- package/nitrogen/generated/shared/c++/ClusterConfig.hpp +5 -8
- package/nitrogen/generated/shared/c++/HybridNitroMapSpec.cpp +53 -2
- package/nitrogen/generated/shared/c++/HybridNitroMapSpec.hpp +75 -6
- package/nitrogen/generated/shared/c++/MarkerAnimation.hpp +4 -8
- package/nitrogen/generated/shared/c++/MarkerData.hpp +14 -2
- package/nitrogen/generated/shared/c++/PolygonData.hpp +114 -0
- package/nitrogen/generated/shared/c++/PolylineData.hpp +114 -0
- package/nitrogen/generated/shared/c++/UserLocationChangeEvent.hpp +88 -0
- package/nitrogen/generated/shared/c++/UserTrackingMode.hpp +80 -0
- package/nitrogen/generated/shared/c++/views/HybridNitroMapComponent.cpp +216 -12
- package/nitrogen/generated/shared/c++/views/HybridNitroMapComponent.hpp +23 -1
- package/nitrogen/generated/shared/json/NitroMapConfig.json +18 -1
- package/package.json +36 -5
- package/src/components/ImageMarker.tsx +58 -42
- package/src/components/Marker.tsx +161 -0
- package/src/components/NitroCircle.tsx +183 -0
- package/src/components/NitroMap.tsx +328 -78
- package/src/components/NitroPolygon.tsx +229 -0
- package/src/components/NitroPolyline.tsx +208 -0
- package/src/components/PriceMarker.tsx +23 -48
- package/src/context/NitroMapContext.tsx +4 -0
- package/src/hooks/useNitroCircle.ts +25 -0
- package/src/hooks/useNitroMarker.ts +49 -10
- package/src/hooks/useNitroOverlay.ts +68 -0
- package/src/hooks/useNitroPolygon.ts +25 -0
- package/src/hooks/useNitroPolyline.ts +25 -0
- package/src/index.tsx +23 -2
- package/src/specs/NitroMap.nitro.ts +294 -5
- package/src/types/map.ts +36 -4
- package/src/types/marker.ts +24 -44
- package/src/types/overlay.ts +77 -0
- package/src/types/theme.ts +101 -0
- package/src/utils/colors.ts +48 -16
- package/src/utils/validation.ts +69 -0
- package/android/src/main/java/com/margelo/nitro/nitromap/ClusterIconGenerator.kt +0 -108
- package/android/src/main/java/com/margelo/nitro/nitromap/ColorUtils.kt +0 -63
- package/android/src/main/java/com/margelo/nitro/nitromap/HybridNitroMap.kt +0 -408
- package/android/src/main/java/com/margelo/nitro/nitromap/HybridNitroMapConfig.kt +0 -68
- package/android/src/main/java/com/margelo/nitro/nitromap/MarkerIconCache.kt +0 -176
- package/android/src/main/java/com/margelo/nitro/nitromap/MarkerIconFactory.kt +0 -252
- package/android/src/main/java/com/margelo/nitro/nitromap/clustering/NitroClusterEngine.kt +0 -252
- package/android/src/main/java/com/margelo/nitro/nitromap/clustering/QuadTree.kt +0 -195
- package/android/src/main/java/com/margelo/nitro/nitromap/providers/GoogleMapProvider.kt +0 -912
- package/android/src/main/java/com/margelo/nitro/nitromap/providers/MapProviderInterface.kt +0 -70
- package/cpp/QuadTree.hpp +0 -246
- package/ios/NitroMapConfig/HybridNitroMapConfig.swift +0 -33
- package/ios/Providers/GoogleMapProvider+Camera.swift +0 -164
- package/ios/Providers/GoogleMapProvider.swift +0 -924
- package/nitrogen/generated/android/c++/JClusterAnimationStyle.hpp +0 -68
- package/nitrogen/generated/ios/swift/ClusterAnimationStyle.swift +0 -52
- 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
|
|
20
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
}
|