@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,296 @@
|
|
|
1
|
+
package com.margelo.nitro.nitromap.providers.google
|
|
2
|
+
|
|
3
|
+
import android.animation.ValueAnimator
|
|
4
|
+
import com.margelo.nitro.nitromap.*
|
|
5
|
+
import android.content.Context
|
|
6
|
+
import android.graphics.Bitmap
|
|
7
|
+
import android.graphics.BitmapFactory
|
|
8
|
+
import android.graphics.Canvas
|
|
9
|
+
import android.graphics.Paint
|
|
10
|
+
import android.graphics.RectF
|
|
11
|
+
import android.location.Location
|
|
12
|
+
import android.util.Log
|
|
13
|
+
import android.view.animation.LinearInterpolator
|
|
14
|
+
import com.google.android.gms.maps.CameraUpdateFactory
|
|
15
|
+
import com.google.android.gms.maps.model.BitmapDescriptorFactory
|
|
16
|
+
import com.google.android.gms.maps.model.LatLng
|
|
17
|
+
import com.google.android.gms.maps.model.Marker
|
|
18
|
+
import com.google.android.gms.maps.model.MarkerOptions
|
|
19
|
+
import kotlinx.coroutines.launch
|
|
20
|
+
import kotlinx.coroutines.withContext
|
|
21
|
+
import kotlinx.coroutines.Dispatchers
|
|
22
|
+
import java.net.HttpURLConnection
|
|
23
|
+
import java.net.URL
|
|
24
|
+
import kotlin.math.abs
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* User location management for GoogleMapProvider.
|
|
28
|
+
* Mirrors iOS GoogleMapProvider+UserLocation.swift.
|
|
29
|
+
*
|
|
30
|
+
* Handles:
|
|
31
|
+
* - Custom user location marker (URL or local asset image)
|
|
32
|
+
* - Tracking modes (none, follow, followWithHeading)
|
|
33
|
+
* - Camera following
|
|
34
|
+
* - centerOnUserLocation() method
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// MARK: - User Location State (stored in GoogleMapProvider)
|
|
38
|
+
// These fields are already declared in GoogleMapProvider:
|
|
39
|
+
// - var lastKnownUserLocation: Location? (add to GoogleMapProvider)
|
|
40
|
+
// - var customUserLocationMarker: Marker? (add to GoogleMapProvider)
|
|
41
|
+
|
|
42
|
+
// MARK: - Location Update Handler
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Called when the UserLocationManager fires a location/heading update.
|
|
46
|
+
* Updates custom marker position + rotation and handles camera following.
|
|
47
|
+
*/
|
|
48
|
+
/** Animation duration matching iOS CATransaction (0.3s) */
|
|
49
|
+
private const val LOCATION_ANIMATION_MS = 300L
|
|
50
|
+
|
|
51
|
+
internal fun GoogleMapProvider.updateUserLocationInternal(location: Location, heading: Float?) {
|
|
52
|
+
lastKnownUserLocation = location
|
|
53
|
+
if (heading != null && heading >= 0) lastKnownHeading = heading
|
|
54
|
+
|
|
55
|
+
val marker = customUserLocationMarker
|
|
56
|
+
val shouldFollow = userTrackingMode != null && userTrackingMode != UserTrackingMode.NONE
|
|
57
|
+
val targetLatLng = LatLng(location.latitude, location.longitude)
|
|
58
|
+
|
|
59
|
+
val targetRotation: Float? = when {
|
|
60
|
+
heading != null && heading >= 0 -> heading
|
|
61
|
+
location.hasBearing() -> location.bearing
|
|
62
|
+
else -> null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
withMap { map ->
|
|
66
|
+
if (marker != null) {
|
|
67
|
+
animateMarkerPosition(marker, targetLatLng)
|
|
68
|
+
targetRotation?.let { animateMarkerRotation(marker, it) }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Camera following — mark as programmatic so onCameraIdle skips clustering
|
|
72
|
+
if (shouldFollow) {
|
|
73
|
+
isFollowingUserCamera = true
|
|
74
|
+
when (userTrackingMode) {
|
|
75
|
+
UserTrackingMode.FOLLOW -> {
|
|
76
|
+
map.animateCamera(
|
|
77
|
+
CameraUpdateFactory.newLatLng(targetLatLng),
|
|
78
|
+
LOCATION_ANIMATION_MS.toInt(),
|
|
79
|
+
null
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
UserTrackingMode.FOLLOWWITHHEADING -> {
|
|
83
|
+
val bearing = targetRotation ?: map.cameraPosition.bearing
|
|
84
|
+
val cameraPos = com.google.android.gms.maps.model.CameraPosition.Builder()
|
|
85
|
+
.target(targetLatLng)
|
|
86
|
+
.zoom(map.cameraPosition.zoom)
|
|
87
|
+
.bearing(bearing)
|
|
88
|
+
.tilt(map.cameraPosition.tilt)
|
|
89
|
+
.build()
|
|
90
|
+
map.animateCamera(
|
|
91
|
+
CameraUpdateFactory.newCameraPosition(cameraPos),
|
|
92
|
+
LOCATION_ANIMATION_MS.toInt(),
|
|
93
|
+
null
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
else -> {}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Smoothly animate marker position from current to target.
|
|
104
|
+
* Cancels any in-flight position animation to avoid conflicts.
|
|
105
|
+
*/
|
|
106
|
+
private fun GoogleMapProvider.animateMarkerPosition(marker: Marker, target: LatLng) {
|
|
107
|
+
userLocationPositionAnimator?.cancel()
|
|
108
|
+
|
|
109
|
+
val startLat = marker.position.latitude
|
|
110
|
+
val startLng = marker.position.longitude
|
|
111
|
+
|
|
112
|
+
val animator = ValueAnimator.ofFloat(0f, 1f).apply {
|
|
113
|
+
duration = LOCATION_ANIMATION_MS
|
|
114
|
+
interpolator = LinearInterpolator()
|
|
115
|
+
addUpdateListener { animation ->
|
|
116
|
+
val t = animation.animatedFraction.toDouble()
|
|
117
|
+
val lat = startLat + (target.latitude - startLat) * t
|
|
118
|
+
val lng = startLng + (target.longitude - startLng) * t
|
|
119
|
+
marker.position = LatLng(lat, lng)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
userLocationPositionAnimator = animator
|
|
123
|
+
animator.start()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Smoothly animate marker rotation from current to target.
|
|
128
|
+
* Uses shortest-path rotation (handles 0°/360° wraparound).
|
|
129
|
+
*/
|
|
130
|
+
private fun GoogleMapProvider.animateMarkerRotation(marker: Marker, target: Float) {
|
|
131
|
+
userLocationRotationAnimator?.cancel()
|
|
132
|
+
|
|
133
|
+
val start = marker.rotation
|
|
134
|
+
// Shortest angular path
|
|
135
|
+
var delta = (target - start) % 360f
|
|
136
|
+
if (delta > 180f) delta -= 360f
|
|
137
|
+
if (delta < -180f) delta += 360f
|
|
138
|
+
val end = start + delta
|
|
139
|
+
|
|
140
|
+
val animator = ValueAnimator.ofFloat(start, end).apply {
|
|
141
|
+
duration = LOCATION_ANIMATION_MS
|
|
142
|
+
interpolator = LinearInterpolator()
|
|
143
|
+
addUpdateListener { animation ->
|
|
144
|
+
var rot = animation.animatedValue as Float
|
|
145
|
+
rot = ((rot % 360f) + 360f) % 360f
|
|
146
|
+
marker.rotation = rot
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
userLocationRotationAnimator = animator
|
|
150
|
+
animator.start()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// MARK: - Custom User Location Marker
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Sets up a custom user location marker with the provided image.
|
|
157
|
+
* Called when userLocationImage or userLocationSize changes.
|
|
158
|
+
*/
|
|
159
|
+
internal fun GoogleMapProvider.setupCustomUserLocationMarkerInternal(context: Context) {
|
|
160
|
+
// Remove old custom marker
|
|
161
|
+
customUserLocationMarker?.remove()
|
|
162
|
+
customUserLocationMarker = null
|
|
163
|
+
|
|
164
|
+
if (userLocationImage.isEmpty()) {
|
|
165
|
+
// No custom image — re-enable default blue dot
|
|
166
|
+
cachedUserLocationImageUrl = ""
|
|
167
|
+
// H-1: Recycle old bitmap before clearing reference
|
|
168
|
+
userLocationImageBitmap?.recycle()
|
|
169
|
+
userLocationImageBitmap = null
|
|
170
|
+
withMap { map ->
|
|
171
|
+
try {
|
|
172
|
+
map.isMyLocationEnabled = showsUserLocation ?: false
|
|
173
|
+
} catch (_: SecurityException) {}
|
|
174
|
+
}
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Disable native blue dot — custom marker replaces it
|
|
179
|
+
withMap { map ->
|
|
180
|
+
try {
|
|
181
|
+
map.isMyLocationEnabled = false
|
|
182
|
+
} catch (_: SecurityException) {}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Create custom marker
|
|
186
|
+
withMap { map ->
|
|
187
|
+
val opts = MarkerOptions()
|
|
188
|
+
.position(
|
|
189
|
+
lastKnownUserLocation?.let {
|
|
190
|
+
LatLng(it.latitude, it.longitude)
|
|
191
|
+
} ?: LatLng(0.0, 0.0)
|
|
192
|
+
)
|
|
193
|
+
.anchor(
|
|
194
|
+
userLocationAnchor?.x?.toFloat() ?: 0.5f,
|
|
195
|
+
userLocationAnchor?.y?.toFloat() ?: 0.5f
|
|
196
|
+
)
|
|
197
|
+
.flat(true)
|
|
198
|
+
.zIndex(Float.MAX_VALUE)
|
|
199
|
+
|
|
200
|
+
// Reuse cached image if URL is the same (only size changed)
|
|
201
|
+
val cachedBitmap = userLocationImageBitmap
|
|
202
|
+
if (cachedBitmap != null && cachedUserLocationImageUrl == userLocationImage) {
|
|
203
|
+
val size = userLocationSize.toInt().coerceAtLeast(1)
|
|
204
|
+
val density = context.resources.displayMetrics.density
|
|
205
|
+
val sizePx = (size * density).toInt()
|
|
206
|
+
val resized = Bitmap.createScaledBitmap(cachedBitmap, sizePx, sizePx, true)
|
|
207
|
+
opts.icon(BitmapDescriptorFactory.fromBitmap(resized))
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
val marker = map.addMarker(opts)
|
|
211
|
+
if (marker != null) {
|
|
212
|
+
marker.tag = MarkerTagData.UserLocation
|
|
213
|
+
// Restore last known heading so rotation isn't lost on marker recreation
|
|
214
|
+
lastKnownHeading?.let { marker.rotation = it }
|
|
215
|
+
customUserLocationMarker = marker
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Skip download if same URL and we already have cached data
|
|
219
|
+
if (cachedUserLocationImageUrl == userLocationImage && userLocationImageBitmap != null) {
|
|
220
|
+
return@withMap
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Load image
|
|
224
|
+
val size = userLocationSize.toInt().coerceAtLeast(1)
|
|
225
|
+
val density = context.resources.displayMetrics.density
|
|
226
|
+
val sizePx = (size * density).toInt()
|
|
227
|
+
|
|
228
|
+
if (userLocationImage.startsWith("http://") || userLocationImage.startsWith("https://")) {
|
|
229
|
+
// Remote image — async load
|
|
230
|
+
val imageUrl = userLocationImage
|
|
231
|
+
cachedUserLocationImageUrl = imageUrl
|
|
232
|
+
|
|
233
|
+
// C-3: Use providerScope instead of unstructured CoroutineScope
|
|
234
|
+
providerScope.launch {
|
|
235
|
+
val bitmap = withContext(Dispatchers.IO) {
|
|
236
|
+
var connection: HttpURLConnection? = null
|
|
237
|
+
try {
|
|
238
|
+
val url = URL(imageUrl)
|
|
239
|
+
connection = url.openConnection() as HttpURLConnection
|
|
240
|
+
connection.connectTimeout = 10_000
|
|
241
|
+
connection.readTimeout = 10_000
|
|
242
|
+
connection.connect()
|
|
243
|
+
if (connection.responseCode in 200..299) {
|
|
244
|
+
BitmapFactory.decodeStream(connection.inputStream)
|
|
245
|
+
} else null
|
|
246
|
+
} catch (e: Exception) {
|
|
247
|
+
Log.w("NitroMap", "Failed to load user location image: ${e.message}")
|
|
248
|
+
null
|
|
249
|
+
} finally {
|
|
250
|
+
// H-6: Always disconnect HttpURLConnection
|
|
251
|
+
connection?.disconnect()
|
|
252
|
+
}
|
|
253
|
+
} ?: return@launch
|
|
254
|
+
|
|
255
|
+
// H-1/H-2: Recycle old bitmap before replacing
|
|
256
|
+
userLocationImageBitmap?.recycle()
|
|
257
|
+
userLocationImageBitmap = bitmap
|
|
258
|
+
val resized = Bitmap.createScaledBitmap(bitmap, sizePx, sizePx, true)
|
|
259
|
+
customUserLocationMarker?.setIcon(BitmapDescriptorFactory.fromBitmap(resized))
|
|
260
|
+
}
|
|
261
|
+
} else {
|
|
262
|
+
// Local asset (from resources)
|
|
263
|
+
cachedUserLocationImageUrl = userLocationImage
|
|
264
|
+
val resId = context.resources.getIdentifier(
|
|
265
|
+
userLocationImage, "drawable", context.packageName
|
|
266
|
+
)
|
|
267
|
+
if (resId != 0) {
|
|
268
|
+
val bitmap = BitmapFactory.decodeResource(context.resources, resId)
|
|
269
|
+
if (bitmap != null) {
|
|
270
|
+
// H-1/H-2: Recycle old bitmap before replacing
|
|
271
|
+
userLocationImageBitmap?.recycle()
|
|
272
|
+
userLocationImageBitmap = bitmap
|
|
273
|
+
val resized = Bitmap.createScaledBitmap(bitmap, sizePx, sizePx, true)
|
|
274
|
+
marker?.setIcon(BitmapDescriptorFactory.fromBitmap(resized))
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// MARK: - Center on User Location
|
|
282
|
+
|
|
283
|
+
internal fun GoogleMapProvider.centerOnUserLocationInternal() {
|
|
284
|
+
val location = lastKnownUserLocation ?: return
|
|
285
|
+
|
|
286
|
+
withMap { map ->
|
|
287
|
+
map.animateCamera(
|
|
288
|
+
CameraUpdateFactory.newLatLng(
|
|
289
|
+
LatLng(location.latitude, location.longitude)
|
|
290
|
+
),
|
|
291
|
+
300,
|
|
292
|
+
null
|
|
293
|
+
)
|
|
294
|
+
}
|
|
295
|
+
Log.d("NitroMap", "centerOnUserLocation: lat=${location.latitude}, lng=${location.longitude}")
|
|
296
|
+
}
|