@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.
- 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/Clustering/ClusterIconRenderer.swift +3 -3
- 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,303 @@
|
|
|
1
|
+
package com.margelo.nitro.nitromap.markers
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.Bitmap
|
|
5
|
+
import android.graphics.BitmapFactory
|
|
6
|
+
import android.graphics.Canvas
|
|
7
|
+
import android.graphics.Color
|
|
8
|
+
import android.graphics.Paint
|
|
9
|
+
import android.graphics.PorterDuff
|
|
10
|
+
import android.graphics.PorterDuffXfermode
|
|
11
|
+
import android.graphics.Rect
|
|
12
|
+
import android.graphics.RectF
|
|
13
|
+
import android.util.Base64
|
|
14
|
+
import android.util.Log
|
|
15
|
+
import android.util.LruCache
|
|
16
|
+
import com.google.android.gms.maps.model.BitmapDescriptor
|
|
17
|
+
import com.google.android.gms.maps.model.BitmapDescriptorFactory
|
|
18
|
+
import com.margelo.nitro.nitromap.ImageMarkerConfig
|
|
19
|
+
import com.margelo.nitro.nitromap.MarkerData
|
|
20
|
+
import com.margelo.nitro.nitromap.MarkerStyle
|
|
21
|
+
import kotlinx.coroutines.CoroutineScope
|
|
22
|
+
import kotlinx.coroutines.launch
|
|
23
|
+
import kotlinx.coroutines.withContext
|
|
24
|
+
import kotlinx.coroutines.Dispatchers
|
|
25
|
+
import java.net.HttpURLConnection
|
|
26
|
+
import java.net.URL
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Android equivalent of iOS MarkerIconFactory + MarkerIconCache.
|
|
30
|
+
*
|
|
31
|
+
* - Maps MarkerData → BitmapDescriptor (Google Maps icon).
|
|
32
|
+
* - LruCache for bitmap reuse (avoids texture exhaustion on large datasets).
|
|
33
|
+
* - Price markers: delegated to PriceMarkerRenderer (Canvas drawing).
|
|
34
|
+
* - Image markers: base64 inline or async URL loading.
|
|
35
|
+
*/
|
|
36
|
+
// C-3: Accept a CoroutineScope from the provider to avoid unstructured coroutine leaks
|
|
37
|
+
class MarkerIconFactory(private val context: Context, private val scope: CoroutineScope) {
|
|
38
|
+
|
|
39
|
+
companion object {
|
|
40
|
+
private const val TAG = "MarkerIconFactory"
|
|
41
|
+
private const val MAX_CACHE_ENTRIES = 500
|
|
42
|
+
private const val URL_CACHE_ENTRIES = 200
|
|
43
|
+
|
|
44
|
+
// Cache key helpers (deterministic, stable across launches)
|
|
45
|
+
fun priceMarkerKey(price: String, currency: String, selected: Boolean, config: com.margelo.nitro.nitromap.PriceMarkerStyle? = null): String {
|
|
46
|
+
// Include color hash to prevent cross-color cache collisions
|
|
47
|
+
val colorHash = if (config != null) {
|
|
48
|
+
stableHash(buildString {
|
|
49
|
+
config.backgroundColor?.let { append(it.hashCode()) }
|
|
50
|
+
config.selectedBackgroundColor?.let { append(it.hashCode()) }
|
|
51
|
+
config.textColor?.let { append(it.hashCode()) }
|
|
52
|
+
config.selectedTextColor?.let { append(it.hashCode()) }
|
|
53
|
+
append(config.fontSize ?: 0.0)
|
|
54
|
+
})
|
|
55
|
+
} else 0
|
|
56
|
+
return "price_${price}_${currency}_${selected}_$colorHash"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fun imageMarkerKey(source: String, width: Double, height: Double, cornerRadius: Double): String {
|
|
60
|
+
val w = (width / 5).toInt() * 5 // round to nearest 5
|
|
61
|
+
val h = (height / 5).toInt() * 5
|
|
62
|
+
val r = (cornerRadius / 5).toInt() * 5
|
|
63
|
+
val sourceHash = stableHash(source)
|
|
64
|
+
return "image_${sourceHash}_${w}_${h}_${r}"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// L-5: Math.abs(hash) returns negative for Integer.MIN_VALUE.
|
|
68
|
+
// Use bitwise AND to ensure non-negative result.
|
|
69
|
+
private fun stableHash(s: String): Int {
|
|
70
|
+
var hash = 5381
|
|
71
|
+
for (c in s.toByteArray()) {
|
|
72
|
+
hash = (hash shl 5) + hash + c.toInt()
|
|
73
|
+
}
|
|
74
|
+
return hash and Int.MAX_VALUE
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Lock object for thread-safe cache access.
|
|
79
|
+
// LruCache is not thread-safe — all get/put/evict calls must be synchronized.
|
|
80
|
+
private val cacheLock = Any()
|
|
81
|
+
|
|
82
|
+
// Icon bitmap cache (BitmapDescriptor wraps Bitmap — cache the Bitmap separately)
|
|
83
|
+
// L-2/L-3: Override entryRemoved to recycle evicted bitmaps and prevent memory leaks
|
|
84
|
+
private val bitmapCache = object : LruCache<String, Bitmap>(MAX_CACHE_ENTRIES) {
|
|
85
|
+
override fun entryRemoved(evicted: Boolean, key: String?, oldValue: Bitmap?, newValue: Bitmap?) {
|
|
86
|
+
if (evicted && oldValue != null && !oldValue.isRecycled) {
|
|
87
|
+
oldValue.recycle()
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Separate URL image cache (source bitmaps before rendering)
|
|
92
|
+
private val urlBitmapCache = object : LruCache<String, Bitmap>(URL_CACHE_ENTRIES) {
|
|
93
|
+
override fun entryRemoved(evicted: Boolean, key: String?, oldValue: Bitmap?, newValue: Bitmap?) {
|
|
94
|
+
if (evicted && oldValue != null && !oldValue.isRecycled) {
|
|
95
|
+
oldValue.recycle()
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Callback invoked when an async URL image finishes loading.
|
|
102
|
+
* Passes the marker id and updated BitmapDescriptor.
|
|
103
|
+
*/
|
|
104
|
+
var onIconLoaded: ((markerId: String, icon: BitmapDescriptor) -> Unit)? = null
|
|
105
|
+
|
|
106
|
+
// MARK: - Public API
|
|
107
|
+
|
|
108
|
+
fun createIcon(markerData: MarkerData): BitmapDescriptor? {
|
|
109
|
+
return when (markerData.config.style) {
|
|
110
|
+
MarkerStyle.PRICEMARKER -> createPriceMarkerIcon(markerData)
|
|
111
|
+
MarkerStyle.IMAGE -> createImageMarkerIcon(markerData)
|
|
112
|
+
MarkerStyle.DEFAULT -> null
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
fun clearCache() {
|
|
117
|
+
synchronized(cacheLock) {
|
|
118
|
+
bitmapCache.evictAll()
|
|
119
|
+
urlBitmapCache.evictAll()
|
|
120
|
+
}
|
|
121
|
+
Log.d(TAG, "Icon cache cleared")
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// MARK: - Price Marker
|
|
125
|
+
|
|
126
|
+
private fun createPriceMarkerIcon(markerData: MarkerData): BitmapDescriptor? {
|
|
127
|
+
val config = markerData.config.priceMarker ?: return null
|
|
128
|
+
val cacheKey = priceMarkerKey(config.price, config.currency, config.selected, config)
|
|
129
|
+
|
|
130
|
+
synchronized(cacheLock) {
|
|
131
|
+
bitmapCache.get(cacheKey)?.let {
|
|
132
|
+
return BitmapDescriptorFactory.fromBitmap(it)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
val bitmap = PriceMarkerRenderer.render(config, context.resources.displayMetrics)
|
|
137
|
+
synchronized(cacheLock) { bitmapCache.put(cacheKey, bitmap) }
|
|
138
|
+
return BitmapDescriptorFactory.fromBitmap(bitmap)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// MARK: - Image Marker
|
|
142
|
+
|
|
143
|
+
private fun createImageMarkerIcon(markerData: MarkerData): BitmapDescriptor? {
|
|
144
|
+
val config = markerData.config.image ?: return null
|
|
145
|
+
val imageSource = config.imageBase64 ?: config.imageUrl ?: return null
|
|
146
|
+
val cacheKey = imageMarkerKey(imageSource, config.width, config.height, config.cornerRadius)
|
|
147
|
+
|
|
148
|
+
// Cache hit
|
|
149
|
+
synchronized(cacheLock) {
|
|
150
|
+
bitmapCache.get(cacheKey)?.let {
|
|
151
|
+
return BitmapDescriptorFactory.fromBitmap(it)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Base64 inline — decode synchronously
|
|
156
|
+
if (config.imageBase64 != null) {
|
|
157
|
+
val sourceBitmap = decodeBase64(config.imageBase64) ?: return null
|
|
158
|
+
val rendered = renderImageMarker(sourceBitmap, config)
|
|
159
|
+
synchronized(cacheLock) { bitmapCache.put(cacheKey, rendered) }
|
|
160
|
+
return BitmapDescriptorFactory.fromBitmap(rendered)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// URL — check URL bitmap cache first
|
|
164
|
+
val urlString = config.imageUrl ?: return null
|
|
165
|
+
val cachedUrlBitmap = synchronized(cacheLock) { urlBitmapCache.get(urlString) }
|
|
166
|
+
if (cachedUrlBitmap != null) {
|
|
167
|
+
val rendered = renderImageMarker(cachedUrlBitmap, config)
|
|
168
|
+
synchronized(cacheLock) { bitmapCache.put(cacheKey, rendered) }
|
|
169
|
+
return BitmapDescriptorFactory.fromBitmap(rendered)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Async URL load — return placeholder, callback when done
|
|
173
|
+
loadUrlImageAsync(markerData.id, urlString, cacheKey, config)
|
|
174
|
+
val placeholder = createPlaceholder(config)
|
|
175
|
+
synchronized(cacheLock) { bitmapCache.put(cacheKey, placeholder) }
|
|
176
|
+
return BitmapDescriptorFactory.fromBitmap(placeholder)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// MARK: - Async URL Loading
|
|
180
|
+
|
|
181
|
+
private fun loadUrlImageAsync(
|
|
182
|
+
markerId: String,
|
|
183
|
+
urlString: String,
|
|
184
|
+
cacheKey: String,
|
|
185
|
+
config: ImageMarkerConfig
|
|
186
|
+
) {
|
|
187
|
+
scope.launch {
|
|
188
|
+
val sourceBitmap = withContext(Dispatchers.IO) {
|
|
189
|
+
var connection: HttpURLConnection? = null
|
|
190
|
+
try {
|
|
191
|
+
val url = URL(urlString)
|
|
192
|
+
connection = url.openConnection() as HttpURLConnection
|
|
193
|
+
connection.connectTimeout = 10_000
|
|
194
|
+
connection.readTimeout = 10_000
|
|
195
|
+
connection.useCaches = true
|
|
196
|
+
connection.connect()
|
|
197
|
+
if (connection.responseCode !in 200..299) {
|
|
198
|
+
Log.w(TAG, "HTTP ${connection.responseCode} loading $urlString")
|
|
199
|
+
return@withContext null
|
|
200
|
+
}
|
|
201
|
+
BitmapFactory.decodeStream(connection.inputStream)
|
|
202
|
+
} catch (e: Exception) {
|
|
203
|
+
Log.w(TAG, "Failed to load image from $urlString: ${e.message}")
|
|
204
|
+
null
|
|
205
|
+
} finally {
|
|
206
|
+
// H-6: Always disconnect HttpURLConnection
|
|
207
|
+
connection?.disconnect()
|
|
208
|
+
}
|
|
209
|
+
} ?: return@launch
|
|
210
|
+
|
|
211
|
+
// Back on main thread — cache and notify
|
|
212
|
+
val rendered = renderImageMarker(sourceBitmap, config)
|
|
213
|
+
synchronized(cacheLock) {
|
|
214
|
+
urlBitmapCache.put(urlString, sourceBitmap)
|
|
215
|
+
bitmapCache.put(cacheKey, rendered)
|
|
216
|
+
}
|
|
217
|
+
onIconLoaded?.invoke(markerId, BitmapDescriptorFactory.fromBitmap(rendered))
|
|
218
|
+
Log.d(TAG, "Icon loaded for marker $markerId from $urlString")
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// MARK: - Rendering
|
|
223
|
+
|
|
224
|
+
private fun renderImageMarker(sourceBitmap: Bitmap, config: ImageMarkerConfig): Bitmap {
|
|
225
|
+
val density = context.resources.displayMetrics.density
|
|
226
|
+
val widthPx = (config.width * density).toInt().coerceAtLeast(1)
|
|
227
|
+
val heightPx = (config.height * density).toInt().coerceAtLeast(1)
|
|
228
|
+
val radiusPx = (config.cornerRadius * density).toFloat()
|
|
229
|
+
val borderWidthPx = (config.borderWidth * density).toFloat()
|
|
230
|
+
|
|
231
|
+
val borderColor = PriceMarkerRenderer.resolveColorValue(
|
|
232
|
+
config.borderColor,
|
|
233
|
+
Color.rgb(200, 200, 200)
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
val output = Bitmap.createBitmap(widthPx, heightPx, Bitmap.Config.ARGB_8888)
|
|
237
|
+
val canvas = Canvas(output)
|
|
238
|
+
|
|
239
|
+
// Clip path for rounded corners
|
|
240
|
+
val rect = RectF(0f, 0f, widthPx.toFloat(), heightPx.toFloat())
|
|
241
|
+
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
|
242
|
+
|
|
243
|
+
// Draw rounded rect shape as clip mask
|
|
244
|
+
paint.color = Color.WHITE
|
|
245
|
+
canvas.drawRoundRect(rect, radiusPx, radiusPx, paint)
|
|
246
|
+
|
|
247
|
+
// Draw source image using SRC_IN (clips to rounded rect)
|
|
248
|
+
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
|
|
249
|
+
val scaled = Bitmap.createScaledBitmap(sourceBitmap, widthPx, heightPx, true)
|
|
250
|
+
canvas.drawBitmap(scaled, 0f, 0f, paint)
|
|
251
|
+
paint.xfermode = null
|
|
252
|
+
// L-7: Recycle the scaled intermediate bitmap if it's not the source
|
|
253
|
+
if (scaled !== sourceBitmap) {
|
|
254
|
+
scaled.recycle()
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Draw border
|
|
258
|
+
if (borderWidthPx > 0) {
|
|
259
|
+
val borderPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
260
|
+
style = Paint.Style.STROKE
|
|
261
|
+
color = borderColor
|
|
262
|
+
strokeWidth = borderWidthPx
|
|
263
|
+
}
|
|
264
|
+
// Inset border rect by half stroke width so it stays inside clip
|
|
265
|
+
val inset = borderWidthPx / 2f
|
|
266
|
+
val borderRect = RectF(inset, inset, widthPx.toFloat() - inset, heightPx.toFloat() - inset)
|
|
267
|
+
canvas.drawRoundRect(borderRect, radiusPx, radiusPx, borderPaint)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return output
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private fun createPlaceholder(config: ImageMarkerConfig): Bitmap {
|
|
274
|
+
val density = context.resources.displayMetrics.density
|
|
275
|
+
val widthPx = (config.width * density).toInt().coerceAtLeast(1)
|
|
276
|
+
val heightPx = (config.height * density).toInt().coerceAtLeast(1)
|
|
277
|
+
val radiusPx = (config.cornerRadius * density)
|
|
278
|
+
|
|
279
|
+
val bitmap = Bitmap.createBitmap(widthPx, heightPx, Bitmap.Config.ARGB_8888)
|
|
280
|
+
val canvas = Canvas(bitmap)
|
|
281
|
+
val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
282
|
+
color = Color.argb((0.3f * 255).toInt(), 200, 200, 200)
|
|
283
|
+
}
|
|
284
|
+
canvas.drawRoundRect(
|
|
285
|
+
RectF(0f, 0f, widthPx.toFloat(), heightPx.toFloat()),
|
|
286
|
+
radiusPx.toFloat(), radiusPx.toFloat(),
|
|
287
|
+
paint
|
|
288
|
+
)
|
|
289
|
+
return bitmap
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// MARK: - Utilities
|
|
293
|
+
|
|
294
|
+
private fun decodeBase64(base64: String): Bitmap? {
|
|
295
|
+
return try {
|
|
296
|
+
val bytes = Base64.decode(base64, Base64.DEFAULT)
|
|
297
|
+
BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
|
|
298
|
+
} catch (e: Exception) {
|
|
299
|
+
Log.w(TAG, "Failed to decode base64 image: ${e.message}")
|
|
300
|
+
null
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
package com.margelo.nitro.nitromap.markers
|
|
2
|
+
|
|
3
|
+
import com.margelo.nitro.nitromap.MarkerAnimation
|
|
4
|
+
import com.margelo.nitro.nitromap.MarkerConfig
|
|
5
|
+
import com.margelo.nitro.nitromap.MarkerData
|
|
6
|
+
import com.margelo.nitro.nitromap.MarkerStyle
|
|
7
|
+
import com.margelo.nitro.nitromap.PriceMarkerStyle
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Handles marker selection state changes in a pure, immutable fashion.
|
|
11
|
+
*
|
|
12
|
+
* Mirrors iOS MarkerSelectionHandler.swift — only PriceMarker style supports
|
|
13
|
+
* visual selection state. All other marker types return null (no change).
|
|
14
|
+
*
|
|
15
|
+
* Thread-safe: stateless, can be called from any thread.
|
|
16
|
+
*/
|
|
17
|
+
object MarkerSelectionHandler {
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates an updated MarkerData with the new selection state applied.
|
|
21
|
+
*
|
|
22
|
+
* @return Updated marker data with the new selection state, or null if the marker
|
|
23
|
+
* type doesn't support visual selection state OR if the state has not changed.
|
|
24
|
+
*/
|
|
25
|
+
fun updateSelectionState(markerData: MarkerData, selected: Boolean): MarkerData? {
|
|
26
|
+
// Only pricemarker style supports visual selection state
|
|
27
|
+
if (markerData.config.style != MarkerStyle.PRICEMARKER) return null
|
|
28
|
+
val priceConfig = markerData.config.priceMarker ?: return null
|
|
29
|
+
|
|
30
|
+
// Early exit if state hasn't changed
|
|
31
|
+
if (priceConfig.selected == selected) return null
|
|
32
|
+
|
|
33
|
+
// Create updated price config with new selection state (immutable copy)
|
|
34
|
+
val updatedPriceConfig = PriceMarkerStyle(
|
|
35
|
+
price = priceConfig.price,
|
|
36
|
+
currency = priceConfig.currency,
|
|
37
|
+
selected = selected,
|
|
38
|
+
backgroundColor = priceConfig.backgroundColor,
|
|
39
|
+
selectedBackgroundColor = priceConfig.selectedBackgroundColor,
|
|
40
|
+
textColor = priceConfig.textColor,
|
|
41
|
+
selectedTextColor = priceConfig.selectedTextColor,
|
|
42
|
+
fontSize = priceConfig.fontSize,
|
|
43
|
+
paddingHorizontal = priceConfig.paddingHorizontal,
|
|
44
|
+
paddingVertical = priceConfig.paddingVertical,
|
|
45
|
+
shadowOpacity = priceConfig.shadowOpacity
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
val updatedConfig = MarkerConfig(
|
|
49
|
+
style = markerData.config.style,
|
|
50
|
+
image = markerData.config.image,
|
|
51
|
+
priceMarker = updatedPriceConfig
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return MarkerData(
|
|
55
|
+
id = markerData.id,
|
|
56
|
+
coordinate = markerData.coordinate,
|
|
57
|
+
title = markerData.title,
|
|
58
|
+
description = markerData.description,
|
|
59
|
+
draggable = markerData.draggable,
|
|
60
|
+
opacity = markerData.opacity,
|
|
61
|
+
rotation = markerData.rotation,
|
|
62
|
+
zIndex = markerData.zIndex,
|
|
63
|
+
anchor = markerData.anchor,
|
|
64
|
+
config = updatedConfig,
|
|
65
|
+
accessibilityLabel = markerData.accessibilityLabel,
|
|
66
|
+
clusteringEnabled = markerData.clusteringEnabled,
|
|
67
|
+
animation = markerData.animation,
|
|
68
|
+
animationDuration = markerData.animationDuration,
|
|
69
|
+
animateOnReappear = markerData.animateOnReappear
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
package com.margelo.nitro.nitromap.markers
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap
|
|
4
|
+
import android.graphics.Canvas
|
|
5
|
+
import android.graphics.Color
|
|
6
|
+
import android.graphics.Paint
|
|
7
|
+
import android.graphics.RectF
|
|
8
|
+
import android.graphics.Typeface
|
|
9
|
+
import android.util.DisplayMetrics
|
|
10
|
+
import android.util.TypedValue
|
|
11
|
+
import com.margelo.nitro.nitromap.MarkerColor
|
|
12
|
+
import com.margelo.nitro.nitromap.PriceMarkerStyle
|
|
13
|
+
import com.margelo.nitro.nitromap.Variant_String_MarkerColor
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Android equivalent of iOS PriceMarkerRenderer.swift.
|
|
17
|
+
*
|
|
18
|
+
* Renders a pill-shaped label with price text.
|
|
19
|
+
* Uses Canvas + Paint for density-aware drawing.
|
|
20
|
+
* Mirrors the iOS shadow, padding, and typography.
|
|
21
|
+
*/
|
|
22
|
+
object PriceMarkerRenderer {
|
|
23
|
+
|
|
24
|
+
// Default style values (match iOS defaults)
|
|
25
|
+
private val DEFAULT_BG_COLOR = Color.WHITE
|
|
26
|
+
private val DEFAULT_SELECTED_BG_COLOR = Color.rgb(255, 59, 48) // iOS red
|
|
27
|
+
private val DEFAULT_TEXT_COLOR = Color.rgb(51, 51, 51)
|
|
28
|
+
private val DEFAULT_TEXT_COLOR_SELECTED = Color.WHITE
|
|
29
|
+
private const val DEFAULT_FONT_SIZE_SP = 10f
|
|
30
|
+
private const val DEFAULT_PADDING_H_DP = 8f
|
|
31
|
+
private const val DEFAULT_PADDING_V_DP = 6f
|
|
32
|
+
private const val DEFAULT_SHADOW_OPACITY = 0.12f
|
|
33
|
+
|
|
34
|
+
// Shadow constants (match iOS: blur=8, offset=2)
|
|
35
|
+
private const val SHADOW_BLUR_DP = 8f
|
|
36
|
+
private const val SHADOW_OFFSET_DP = 2f
|
|
37
|
+
|
|
38
|
+
fun render(config: PriceMarkerStyle?, displayMetrics: DisplayMetrics): Bitmap {
|
|
39
|
+
val selected = config?.selected ?: false
|
|
40
|
+
val price = config?.price ?: "0"
|
|
41
|
+
val currency = config?.currency ?: ""
|
|
42
|
+
val text = "$price $currency"
|
|
43
|
+
|
|
44
|
+
// Colors
|
|
45
|
+
val bgColor = resolveColor(config?.backgroundColor, DEFAULT_BG_COLOR)
|
|
46
|
+
val selectedBgColor = resolveColor(config?.selectedBackgroundColor, DEFAULT_SELECTED_BG_COLOR)
|
|
47
|
+
val textColor = resolveColor(config?.textColor, DEFAULT_TEXT_COLOR)
|
|
48
|
+
val selectedTextColor = resolveColor(config?.selectedTextColor, DEFAULT_TEXT_COLOR_SELECTED)
|
|
49
|
+
|
|
50
|
+
val activeBg = if (selected) selectedBgColor else bgColor
|
|
51
|
+
val activeText = if (selected) selectedTextColor else textColor
|
|
52
|
+
|
|
53
|
+
// Dimensions
|
|
54
|
+
val density = displayMetrics.density
|
|
55
|
+
val fontSizePx = TypedValue.applyDimension(
|
|
56
|
+
TypedValue.COMPLEX_UNIT_SP,
|
|
57
|
+
(config?.fontSize ?: DEFAULT_FONT_SIZE_SP.toDouble()).toFloat(),
|
|
58
|
+
displayMetrics
|
|
59
|
+
)
|
|
60
|
+
val paddingHPx = dp2px((config?.paddingHorizontal ?: DEFAULT_PADDING_H_DP.toDouble()).toFloat(), density)
|
|
61
|
+
val paddingVPx = dp2px((config?.paddingVertical ?: DEFAULT_PADDING_V_DP.toDouble()).toFloat(), density)
|
|
62
|
+
val shadowBlurPx = dp2px(SHADOW_BLUR_DP, density)
|
|
63
|
+
val shadowOffsetPx = dp2px(SHADOW_OFFSET_DP, density)
|
|
64
|
+
val shadowOpacity = (config?.shadowOpacity ?: DEFAULT_SHADOW_OPACITY.toDouble()).toFloat()
|
|
65
|
+
|
|
66
|
+
// Measure text
|
|
67
|
+
val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
68
|
+
this.textSize = fontSizePx
|
|
69
|
+
typeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL)
|
|
70
|
+
}
|
|
71
|
+
val textWidth = textPaint.measureText(text)
|
|
72
|
+
val fontMetrics = textPaint.fontMetrics
|
|
73
|
+
val textHeight = fontMetrics.descent - fontMetrics.ascent
|
|
74
|
+
|
|
75
|
+
// Pill dimensions
|
|
76
|
+
val pillWidth = textWidth + paddingHPx * 2
|
|
77
|
+
val pillHeight = textHeight + paddingVPx * 2
|
|
78
|
+
val cornerRadius = pillHeight / 2f
|
|
79
|
+
|
|
80
|
+
// Canvas size — add room for shadow (match iOS: blur*2 wide, blur+offset+blur/2 tall)
|
|
81
|
+
val canvasWidth = (pillWidth + shadowBlurPx * 2).toInt() + 1
|
|
82
|
+
val canvasHeight = (pillHeight + shadowBlurPx + shadowOffsetPx + shadowBlurPx / 2).toInt() + 1
|
|
83
|
+
|
|
84
|
+
val bitmap = Bitmap.createBitmap(canvasWidth, canvasHeight, Bitmap.Config.ARGB_8888)
|
|
85
|
+
val canvas = Canvas(bitmap)
|
|
86
|
+
|
|
87
|
+
// Pill rect (offset inward to leave room for shadow)
|
|
88
|
+
val left = shadowBlurPx
|
|
89
|
+
val top = shadowBlurPx / 2
|
|
90
|
+
val pillRect = RectF(left, top, left + pillWidth, top + pillHeight)
|
|
91
|
+
|
|
92
|
+
// Shadow paint
|
|
93
|
+
val shadowPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
94
|
+
color = activeBg
|
|
95
|
+
setShadowLayer(
|
|
96
|
+
shadowBlurPx,
|
|
97
|
+
0f,
|
|
98
|
+
shadowOffsetPx,
|
|
99
|
+
applyAlpha(Color.BLACK, shadowOpacity)
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
canvas.drawRoundRect(pillRect, cornerRadius, cornerRadius, shadowPaint)
|
|
103
|
+
|
|
104
|
+
// Background paint (overdraw without shadow so shape is crisp)
|
|
105
|
+
val bgPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
106
|
+
color = activeBg
|
|
107
|
+
}
|
|
108
|
+
canvas.drawRoundRect(pillRect, cornerRadius, cornerRadius, bgPaint)
|
|
109
|
+
|
|
110
|
+
// Text
|
|
111
|
+
textPaint.color = activeText
|
|
112
|
+
val textX = pillRect.left + paddingHPx
|
|
113
|
+
val textY = pillRect.top + paddingVPx - fontMetrics.ascent
|
|
114
|
+
canvas.drawText(text, textX, textY, textPaint)
|
|
115
|
+
|
|
116
|
+
return bitmap
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// MARK: - Helpers
|
|
120
|
+
|
|
121
|
+
private fun dp2px(dp: Float, density: Float) = dp * density
|
|
122
|
+
|
|
123
|
+
private fun applyAlpha(color: Int, alpha: Float): Int {
|
|
124
|
+
val a = (alpha * 255).toInt().coerceIn(0, 255)
|
|
125
|
+
return Color.argb(a, Color.red(color), Color.green(color), Color.blue(color))
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Resolves a Variant_String_MarkerColor to an Android Color int.
|
|
130
|
+
* Supports hex strings (#RRGGBB / #AARRGGBB) and MarkerColor structs.
|
|
131
|
+
*/
|
|
132
|
+
internal fun resolveColor(variant: Variant_String_MarkerColor?, default: Int): Int {
|
|
133
|
+
variant ?: return default
|
|
134
|
+
return variant.match(
|
|
135
|
+
first = { hex -> parseHexColor(hex) ?: default },
|
|
136
|
+
second = { mc -> markerColorToInt(mc) }
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
internal fun resolveColorValue(variant: com.margelo.nitro.nitromap.ColorValue?, default: Int): Int {
|
|
141
|
+
variant ?: return default
|
|
142
|
+
return variant.match(
|
|
143
|
+
first = { hex -> parseHexColor(hex) ?: default },
|
|
144
|
+
second = { mc -> markerColorToInt(mc) }
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private fun parseHexColor(hex: String): Int? {
|
|
149
|
+
return try {
|
|
150
|
+
Color.parseColor(hex)
|
|
151
|
+
} catch (e: IllegalArgumentException) {
|
|
152
|
+
null
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
internal fun markerColorToInt(mc: MarkerColor): Int {
|
|
157
|
+
return Color.argb(mc.a.toInt(), mc.r.toInt(), mc.g.toInt(), mc.b.toInt())
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
package com.margelo.nitro.nitromap.providers
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import com.margelo.nitro.nitromap.MapProvider
|
|
5
|
+
import com.margelo.nitro.nitromap.providers.google.GoogleMapProvider
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Factory for creating map provider instances.
|
|
9
|
+
* Mirrors iOS MapProviderFactory.swift.
|
|
10
|
+
*/
|
|
11
|
+
object MapProviderFactory {
|
|
12
|
+
|
|
13
|
+
fun create(provider: MapProvider, context: Context): MapProviderInterface {
|
|
14
|
+
return when (provider) {
|
|
15
|
+
MapProvider.GOOGLE -> GoogleMapProvider(context)
|
|
16
|
+
MapProvider.APPLE -> throw UnsupportedOperationException(
|
|
17
|
+
"Apple Maps provider is not available on Android"
|
|
18
|
+
)
|
|
19
|
+
MapProvider.YANDEX -> throw UnsupportedOperationException(
|
|
20
|
+
"Yandex Maps provider is not yet implemented"
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
package com.margelo.nitro.nitromap.providers
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.view.View
|
|
5
|
+
import com.margelo.nitro.nitromap.*
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Interface that all map providers must implement.
|
|
9
|
+
* Mirrors iOS MapProviderProtocol.swift.
|
|
10
|
+
*
|
|
11
|
+
* Allows the library to support multiple map backends (Google, Apple, Yandex).
|
|
12
|
+
* Implementation details (command queues, async map init, native view subclasses)
|
|
13
|
+
* stay internal to each provider.
|
|
14
|
+
*/
|
|
15
|
+
interface MapProviderInterface {
|
|
16
|
+
|
|
17
|
+
// MARK: - View Lifecycle
|
|
18
|
+
|
|
19
|
+
/** Create the underlying map view. Provider manages its own saved state. */
|
|
20
|
+
fun createMapView(context: Context): View
|
|
21
|
+
|
|
22
|
+
/** Called after the map view is added to the container. Provider starts async map init here. */
|
|
23
|
+
fun onViewAttached(view: View)
|
|
24
|
+
|
|
25
|
+
/** Called when the view is detached. Provider should pause and save state, but keep data. */
|
|
26
|
+
fun onViewDetaching()
|
|
27
|
+
|
|
28
|
+
/** Called on dispose. Provider should perform full cleanup. */
|
|
29
|
+
fun onViewDestroying()
|
|
30
|
+
|
|
31
|
+
// MARK: - Properties
|
|
32
|
+
|
|
33
|
+
var initialRegion: Region?
|
|
34
|
+
var region: Region?
|
|
35
|
+
var showsUserLocation: Boolean?
|
|
36
|
+
var zoomEnabled: Boolean?
|
|
37
|
+
var scrollEnabled: Boolean?
|
|
38
|
+
var rotateEnabled: Boolean?
|
|
39
|
+
var pitchEnabled: Boolean?
|
|
40
|
+
var mapType: MapType?
|
|
41
|
+
/** Currently uses Google Maps JSON format. Will need provider-specific handling for Apple/Yandex. */
|
|
42
|
+
var customMapStyle: Array<MapStyleElement>?
|
|
43
|
+
var clusterConfig: ClusterConfig?
|
|
44
|
+
var mapPadding: EdgePadding
|
|
45
|
+
var showsTraffic: Boolean?
|
|
46
|
+
var showsBuildings: Boolean?
|
|
47
|
+
var showsCompass: Boolean?
|
|
48
|
+
var minZoom: Double
|
|
49
|
+
var maxZoom: Double
|
|
50
|
+
var darkMode: Boolean?
|
|
51
|
+
var userTrackingMode: UserTrackingMode?
|
|
52
|
+
var userLocationImage: String
|
|
53
|
+
var userLocationSize: Double
|
|
54
|
+
var userLocationAnchor: Point?
|
|
55
|
+
|
|
56
|
+
// MARK: - Callbacks
|
|
57
|
+
|
|
58
|
+
var onPress: ((MapPressEvent) -> Unit)?
|
|
59
|
+
var onLongPress: ((MapPressEvent) -> Unit)?
|
|
60
|
+
var onMapReadyCallback: (() -> Unit)?
|
|
61
|
+
var onRegionChange: ((RegionChangeEvent) -> Unit)?
|
|
62
|
+
var onRegionChangeComplete: ((RegionChangeEvent) -> Unit)?
|
|
63
|
+
var onMarkerPress: ((MarkerPressEvent) -> Unit)?
|
|
64
|
+
var onMarkerDragStart: ((MarkerDragEvent) -> Unit)?
|
|
65
|
+
var onMarkerDrag: ((MarkerDragEvent) -> Unit)?
|
|
66
|
+
var onMarkerDragEnd: ((MarkerDragEvent) -> Unit)?
|
|
67
|
+
var onClusterPress: ((ClusterPressEvent) -> Unit)?
|
|
68
|
+
var onPolylinePress: ((String) -> Unit)?
|
|
69
|
+
var onPolygonPress: ((String) -> Unit)?
|
|
70
|
+
var onCirclePress: ((String) -> Unit)?
|
|
71
|
+
var onError: ((MapError) -> Unit)?
|
|
72
|
+
var onMapIdle: (() -> Unit)?
|
|
73
|
+
var onUserLocationChange: ((UserLocationChangeEvent) -> Unit)?
|
|
74
|
+
var onUserTrackingModeChange: ((UserTrackingMode) -> Unit)?
|
|
75
|
+
var onUserLocationError: ((MapError) -> Unit)?
|
|
76
|
+
|
|
77
|
+
// MARK: - User Location
|
|
78
|
+
|
|
79
|
+
fun updateUserLocation(location: android.location.Location, heading: Float?)
|
|
80
|
+
fun setupCustomUserLocationMarker(context: Context)
|
|
81
|
+
/** Disable user location display and clean up location-related resources. */
|
|
82
|
+
fun disableUserLocation()
|
|
83
|
+
|
|
84
|
+
// MARK: - Camera
|
|
85
|
+
|
|
86
|
+
fun animateToRegion(region: Region, duration: Double?)
|
|
87
|
+
fun fitToCoordinates(coordinates: Array<Coordinate>, edgePadding: EdgePadding?, animated: Boolean?)
|
|
88
|
+
fun fitToSuppliedMarkers(markerIds: Array<String>, edgePadding: EdgePadding?, animated: Boolean?)
|
|
89
|
+
fun animateCamera(camera: Camera, duration: Double?)
|
|
90
|
+
fun setCamera(camera: Camera)
|
|
91
|
+
fun getCamera(): Camera
|
|
92
|
+
fun getMapBoundaries(): MapBoundaries?
|
|
93
|
+
fun pointForCoordinate(coordinate: Coordinate): Point
|
|
94
|
+
fun coordinateForPoint(point: Point): Coordinate
|
|
95
|
+
fun centerOnUserLocation()
|
|
96
|
+
|
|
97
|
+
// MARK: - Markers
|
|
98
|
+
|
|
99
|
+
fun addMarker(marker: MarkerData)
|
|
100
|
+
fun addMarkers(markers: Array<MarkerData>)
|
|
101
|
+
fun updateMarker(marker: MarkerData)
|
|
102
|
+
fun removeMarker(id: String)
|
|
103
|
+
fun clearMarkers()
|
|
104
|
+
fun selectMarker(id: String)
|
|
105
|
+
fun deselectMarker()
|
|
106
|
+
|
|
107
|
+
// MARK: - Clustering
|
|
108
|
+
|
|
109
|
+
fun setClusteringEnabled(enabled: Boolean)
|
|
110
|
+
fun refreshClusters()
|
|
111
|
+
|
|
112
|
+
// MARK: - Overlays
|
|
113
|
+
|
|
114
|
+
fun addPolyline(polyline: PolylineData)
|
|
115
|
+
fun updatePolyline(polyline: PolylineData)
|
|
116
|
+
fun removePolyline(id: String)
|
|
117
|
+
fun clearPolylines()
|
|
118
|
+
|
|
119
|
+
fun addPolygon(polygon: PolygonData)
|
|
120
|
+
fun updatePolygon(polygon: PolygonData)
|
|
121
|
+
fun removePolygon(id: String)
|
|
122
|
+
fun clearPolygons()
|
|
123
|
+
|
|
124
|
+
fun addCircle(circle: CircleData)
|
|
125
|
+
fun updateCircle(circle: CircleData)
|
|
126
|
+
fun removeCircle(id: String)
|
|
127
|
+
fun clearCircles()
|
|
128
|
+
}
|