react-native-google-maps-plus 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/GoogleMapsNitro.podspec +34 -0
  2. package/LICENSE +20 -0
  3. package/README.md +40 -0
  4. package/android/CMakeLists.txt +32 -0
  5. package/android/build.gradle +135 -0
  6. package/android/fix-prefab.gradle +51 -0
  7. package/android/gradle.properties +8 -0
  8. package/android/src/main/AndroidManifest.xml +2 -0
  9. package/android/src/main/cpp/cpp-adapter.cpp +6 -0
  10. package/android/src/main/java/com/googlemapsnitro/Color.kt +65 -0
  11. package/android/src/main/java/com/googlemapsnitro/GoogleMapsNitroPackage.kt +35 -0
  12. package/android/src/main/java/com/googlemapsnitro/GoogleMapsNitroViewImpl.kt +720 -0
  13. package/android/src/main/java/com/googlemapsnitro/HybridGoogleMapsNitroModule.kt +22 -0
  14. package/android/src/main/java/com/googlemapsnitro/HybridGoogleMapsNitroView.kt +337 -0
  15. package/android/src/main/java/com/googlemapsnitro/LocationHandler.kt +205 -0
  16. package/android/src/main/java/com/googlemapsnitro/MapMarker.kt +145 -0
  17. package/android/src/main/java/com/googlemapsnitro/MapPolygon.kt +36 -0
  18. package/android/src/main/java/com/googlemapsnitro/MapPolyline.kt +59 -0
  19. package/android/src/main/java/com/googlemapsnitro/PermissionHandler.kt +116 -0
  20. package/android/src/main/java/com/googlemapsnitro/PlayServicesHandler.kt +25 -0
  21. package/ios/Color.swift +109 -0
  22. package/ios/GoogleMapNitroViewImpl.swift +590 -0
  23. package/ios/HybridGoogleMapsNitroModule.swift +27 -0
  24. package/ios/HybridGoogleMapsNitroView.swift +348 -0
  25. package/ios/LocationHandler.swift +205 -0
  26. package/ios/MapHelper.swift +18 -0
  27. package/ios/MapMarker.swift +207 -0
  28. package/ios/MapPolygon.swift +55 -0
  29. package/ios/MapPolyline.swift +83 -0
  30. package/ios/PermissionHandler.swift +73 -0
  31. package/lib/module/GoogleMapsNitroModule.nitro.js +4 -0
  32. package/lib/module/GoogleMapsNitroModule.nitro.js.map +1 -0
  33. package/lib/module/GoogleMapsNitroView.nitro.js +4 -0
  34. package/lib/module/GoogleMapsNitroView.nitro.js.map +1 -0
  35. package/lib/module/index.js +8 -0
  36. package/lib/module/index.js.map +1 -0
  37. package/lib/module/package.json +1 -0
  38. package/lib/module/types.js +78 -0
  39. package/lib/module/types.js.map +1 -0
  40. package/lib/typescript/package.json +1 -0
  41. package/lib/typescript/src/GoogleMapsNitroModule.nitro.d.ts +12 -0
  42. package/lib/typescript/src/GoogleMapsNitroModule.nitro.d.ts.map +1 -0
  43. package/lib/typescript/src/GoogleMapsNitroView.nitro.d.ts +34 -0
  44. package/lib/typescript/src/GoogleMapsNitroView.nitro.d.ts.map +1 -0
  45. package/lib/typescript/src/index.d.ts +7 -0
  46. package/lib/typescript/src/index.d.ts.map +1 -0
  47. package/lib/typescript/src/types.d.ts +113 -0
  48. package/lib/typescript/src/types.d.ts.map +1 -0
  49. package/nitro.json +28 -0
  50. package/package.json +13 -3
  51. package/src/GoogleMapsNitroModule.nitro.ts +13 -0
  52. package/src/GoogleMapsNitroView.nitro.ts +78 -0
  53. package/src/index.tsx +24 -0
  54. package/src/types.ts +174 -0
@@ -0,0 +1,22 @@
1
+ package com.googlemapsnitro
2
+
3
+ import com.margelo.nitro.core.Promise
4
+
5
+ class HybridGoogleMapsNitroModule : HybridGoogleMapsNitroModuleSpec() {
6
+ val context = GoogleMapsNitroPackage.AppContextHolder.context
7
+ private val locationHandler: LocationHandler = LocationHandler(context)
8
+ private val permissionHandler: PermissionHandler = PermissionHandler(context)
9
+ private val playServicesHandler: PlayServicesHandler = PlayServicesHandler(context)
10
+
11
+ override fun showLocationDialog() {
12
+ locationHandler.showLocationDialog()
13
+ }
14
+
15
+ override fun openLocationSettings() {
16
+ locationHandler.openLocationSettings()
17
+ }
18
+
19
+ override fun requestLocationPermission(): Promise<RNLocationPermissionResult> = permissionHandler.requestLocationPermission()
20
+
21
+ override fun isGooglePlayServicesAvailable(): Boolean = playServicesHandler.isPlayServicesAvailable()
22
+ }
@@ -0,0 +1,337 @@
1
+ package com.googlemapsnitro
2
+
3
+ import com.facebook.proguard.annotations.DoNotStrip
4
+ import com.facebook.react.bridge.UiThreadUtil
5
+ import com.facebook.react.uimanager.PixelUtil.dpToPx
6
+ import com.facebook.react.uimanager.ThemedReactContext
7
+ import com.google.android.gms.maps.model.CameraPosition
8
+ import com.google.android.gms.maps.model.MapColorScheme
9
+ import com.google.android.gms.maps.model.MapStyleOptions
10
+ import com.margelo.nitro.core.Promise
11
+
12
+ @DoNotStrip
13
+ class HybridGoogleMapsNitroView(
14
+ val context: ThemedReactContext,
15
+ ) : HybridGoogleMapsNitroViewSpec() {
16
+ private var currentCustomMapStyle: String = ""
17
+ private var permissionHandler = PermissionHandler(context)
18
+ private var locationHandler = LocationHandler(context)
19
+ private var playServiceHandler = PlayServicesHandler(context)
20
+ private val markerOptions = MarkerOptions()
21
+
22
+ override val view =
23
+ GoogleMapsNitroViewImpl(context, locationHandler, playServiceHandler, markerOptions)
24
+
25
+ private val polylineOptions = MapPolylineOptions()
26
+ private val polygonOptions = MapPolygonOptions()
27
+
28
+ override var buildingEnabled: Boolean
29
+ get() = view.buildingEnabled
30
+ set(value) {
31
+ view.buildingEnabled = value
32
+ }
33
+
34
+ override var trafficEnabled: Boolean
35
+ get() = view.trafficEnabled
36
+ set(value) {
37
+ view.trafficEnabled = value
38
+ }
39
+
40
+ override var customMapStyle: String
41
+ get() = currentCustomMapStyle
42
+ set(value) {
43
+ currentCustomMapStyle = value
44
+ view.customMapStyle = MapStyleOptions(value)
45
+ }
46
+
47
+ override var initialCamera: RNCamera
48
+ get() = mapCameraPotionToCamera(view.initialCamera)
49
+ set(value) {
50
+ view.initialCamera = mapCameraToCameraPosition(value)
51
+ }
52
+
53
+ override var userInterfaceStyle: RNUserInterfaceStyle
54
+ get() = mapColorSchemeToUserInterfaceStyle(view.userInterfaceStyle)
55
+ set(value) {
56
+ view.userInterfaceStyle = userInterfaceStyleToMapColorScheme(value)
57
+ }
58
+
59
+ override var minZoomLevel: Double
60
+ get() = view.minZoomLevel
61
+ set(value) {
62
+ view.minZoomLevel = value
63
+ }
64
+
65
+ override var maxZoomLevel: Double
66
+ get() = view.maxZoomLevel
67
+ set(value) {
68
+ view.maxZoomLevel = value
69
+ }
70
+
71
+ override var mapPadding: RNMapPadding
72
+ get() = view.mapPadding
73
+ set(value) {
74
+ view.mapPadding = value
75
+ }
76
+
77
+ override var markers: Array<RNMarker> = emptyArray()
78
+ set(value) {
79
+ val prevById = field.associateBy { it.id }
80
+ val nextById = value.associateBy { it.id }
81
+
82
+ (prevById.keys - nextById.keys).forEach { id ->
83
+ markerOptions.cancelIconJob(id)
84
+ view.removeMarker(id)
85
+ }
86
+
87
+ nextById.forEach { (id, next) ->
88
+ val prev = prevById[id]
89
+ if (prev == null) {
90
+ markerOptions.buildIconAsync(id, next) { icon ->
91
+ view.addMarker(
92
+ id,
93
+ markerOptions.build(next, icon),
94
+ )
95
+ }
96
+ } else if (!prev.markerEquals(next)) {
97
+ view.updateMarker(id) { m ->
98
+ onUi {
99
+ if (prev.coordinate != next.coordinate) {
100
+ m.position =
101
+ com.google.android.gms.maps.model.LatLng(
102
+ next.coordinate.latitude,
103
+ next.coordinate.longitude,
104
+ )
105
+ }
106
+ if (prev.zIndex != next.zIndex) {
107
+ m.zIndex = next.zIndex.toFloat()
108
+ }
109
+ if (!prev.markerStyleEquals(next)) {
110
+ markerOptions.buildIconAsync(id, next) { icon ->
111
+ m.setIcon(icon)
112
+ }
113
+ }
114
+ if (prev.anchor != next.anchor) {
115
+ m.setAnchor(
116
+ (next.anchor?.x ?: 0.5).toFloat(),
117
+ (next.anchor?.y ?: 0.5).toFloat(),
118
+ )
119
+ }
120
+ }
121
+ }
122
+ }
123
+ }
124
+ field = value
125
+ }
126
+
127
+ override var polylines: Array<RNPolyline> = emptyArray()
128
+ set(value) {
129
+ val prevById = field.associateBy { it.id }
130
+ val nextById = value.associateBy { it.id }
131
+
132
+ (prevById.keys - nextById.keys).forEach { id ->
133
+ view.removePolyline(id)
134
+ }
135
+
136
+ nextById.forEach { (id, next) ->
137
+ val prev = prevById[id]
138
+ if (prev == null) {
139
+ view.addPolyline(id, polylineOptions.buildPolylineOptions(next))
140
+ } else if (!prev.polylineEquals(next)) {
141
+ view.updatePolyline(id) { gms ->
142
+ onUi {
143
+ gms.points =
144
+ next.coordinates.map {
145
+ com.google.android.gms.maps.model
146
+ .LatLng(it.latitude, it.longitude)
147
+ }
148
+ next.width?.let { gms.width = it.dpToPx() }
149
+ next.lineCap?.let {
150
+ val cap = polylineOptions.mapLineCap(it)
151
+ gms.startCap = cap
152
+ gms.endCap = cap
153
+ }
154
+ next.lineJoin?.let { gms.jointType = polylineOptions.mapLineJoin(it) }
155
+ next.color?.let { gms.color = it.toColor() }
156
+ gms.zIndex = next.zIndex.toFloat()
157
+ }
158
+ }
159
+ }
160
+ }
161
+ field = value
162
+ }
163
+
164
+ override var polygons: Array<RNPolygon> = emptyArray()
165
+ set(value) {
166
+ val prevById = field.associateBy { it.id }
167
+ val nextById = value.associateBy { it.id }
168
+
169
+ (prevById.keys - nextById.keys).forEach { id ->
170
+ view.removePolygon(id)
171
+ }
172
+
173
+ nextById.forEach { (id, next) ->
174
+ val prev = prevById[id]
175
+ if (prev == null) {
176
+ view.addPolygon(id, polygonOptions.buildPolygonOptions(next))
177
+ } else if (!prev.polygonEquals(next)) {
178
+ view.updatePolygon(id) { gmsPoly ->
179
+ onUi {
180
+ gmsPoly.points =
181
+ next.coordinates.map {
182
+ com.google.android.gms.maps.model
183
+ .LatLng(it.latitude, it.longitude)
184
+ }
185
+ next.fillColor?.let { gmsPoly.fillColor = it.toColor() }
186
+ next.strokeColor?.let { gmsPoly.strokeColor = it.toColor() }
187
+ next.strokeWidth?.let { gmsPoly.strokeWidth = it.dpToPx() }
188
+ gmsPoly.zIndex = next.zIndex.toFloat()
189
+ }
190
+ }
191
+ }
192
+ }
193
+ field = value
194
+ }
195
+
196
+ override var onMapError: ((RNMapErrorCode) -> Unit)?
197
+ get() = view.onMapError
198
+ set(cb) {
199
+ view.onMapError = cb
200
+ }
201
+
202
+ override var onMapReady: ((Boolean) -> Unit)?
203
+ get() = view.onMapReady
204
+ set(cb) {
205
+ view.onMapReady = cb
206
+ }
207
+ override var onLocationUpdate: ((RNLocation) -> Unit)?
208
+ get() = view.onLocationUpdate
209
+ set(cb) {
210
+ view.onLocationUpdate = cb
211
+ }
212
+
213
+ override var onLocationError: ((RNLocationErrorCode) -> Unit)?
214
+ get() = view.onLocationError
215
+ set(cb) {
216
+ view.onLocationError = cb
217
+ }
218
+
219
+ override var onMapPress: ((RNLatLng) -> Unit)?
220
+ get() = view.onMapPress
221
+ set(cb) {
222
+ view.onMapPress = cb
223
+ }
224
+
225
+ override var onMarkerPress: ((String) -> Unit)?
226
+ get() = view.onMarkerPress
227
+ set(cb) {
228
+ view.onMarkerPress = cb
229
+ }
230
+
231
+ override var onCameraChangeStart: ((RNRegion, RNCamera, Boolean) -> Unit)?
232
+ get() = view.onCameraChangeStart
233
+ set(cb) {
234
+ view.onCameraChangeStart = cb
235
+ }
236
+
237
+ override var onCameraChange: ((RNRegion, RNCamera, Boolean) -> Unit)?
238
+ get() = view.onCameraChange
239
+ set(cb) {
240
+ view.onCameraChange = cb
241
+ }
242
+
243
+ override var onCameraChangeComplete: ((RNRegion, RNCamera, Boolean) -> Unit)?
244
+ get() = view.onCameraChangeComplete
245
+ set(cb) {
246
+ view.onCameraChangeComplete = cb
247
+ }
248
+
249
+ override fun setCamera(
250
+ camera: RNCamera,
251
+ animated: Boolean?,
252
+ durationMS: Double?,
253
+ ) {
254
+ view.setCamera(camera, animated == true, durationMS?.toInt() ?: 3000)
255
+ }
256
+
257
+ override fun setCameraToCoordinates(
258
+ coordinates: Array<RNLatLng>,
259
+ padding: RNMapPadding?,
260
+ animated: Boolean?,
261
+ durationMS: Double?,
262
+ ) {
263
+ view.setCameraToCoordinates(
264
+ coordinates,
265
+ padding = padding ?: RNMapPadding(0.0, 0.0, 0.0, 0.0),
266
+ animated == true,
267
+ durationMS?.toInt() ?: 3000,
268
+ )
269
+ }
270
+
271
+ override fun showLocationDialog() {
272
+ locationHandler.showLocationDialog()
273
+ }
274
+
275
+ override fun openLocationSettings() {
276
+ locationHandler.openLocationSettings()
277
+ }
278
+
279
+ override fun requestLocationPermission(): Promise<RNLocationPermissionResult> = permissionHandler.requestLocationPermission()
280
+
281
+ override fun isGooglePlayServicesAvailable(): Boolean = playServiceHandler.isPlayServicesAvailable()
282
+
283
+ fun userInterfaceStyleToMapColorScheme(value: RNUserInterfaceStyle): Int =
284
+ when (value) {
285
+ RNUserInterfaceStyle.LIGHT -> {
286
+ MapColorScheme.LIGHT
287
+ }
288
+
289
+ RNUserInterfaceStyle.DARK -> {
290
+ MapColorScheme.DARK
291
+ }
292
+
293
+ RNUserInterfaceStyle.DEFAULT -> {
294
+ MapColorScheme.FOLLOW_SYSTEM
295
+ }
296
+ }
297
+
298
+ fun mapCameraToCameraPosition(camera: RNCamera): CameraPosition {
299
+ val builder = CameraPosition.builder()
300
+ camera.center?.let {
301
+ builder.target(
302
+ com.google.android.gms.maps.model.LatLng(
303
+ camera.center.latitude,
304
+ camera.center.longitude,
305
+ ),
306
+ )
307
+ }
308
+ camera.zoom?.let { builder.zoom(it.toFloat()) }
309
+ camera.bearing?.let { builder.bearing(it.toFloat()) }
310
+ camera.tilt?.let { builder.tilt(it.toFloat()) }
311
+
312
+ return builder.build()
313
+ }
314
+
315
+ fun mapCameraPotionToCamera(cameraPosition: CameraPosition): RNCamera =
316
+ RNCamera(
317
+ center = RNLatLng(cameraPosition.target.latitude, cameraPosition.target.longitude),
318
+ zoom = cameraPosition.zoom.toDouble(),
319
+ bearing = cameraPosition.bearing.toDouble(),
320
+ tilt = cameraPosition.tilt.toDouble(),
321
+ )
322
+
323
+ fun mapColorSchemeToUserInterfaceStyle(value: Int): RNUserInterfaceStyle =
324
+ when (value) {
325
+ MapColorScheme.LIGHT -> RNUserInterfaceStyle.LIGHT
326
+ MapColorScheme.DARK -> RNUserInterfaceStyle.DARK
327
+ else -> RNUserInterfaceStyle.DEFAULT
328
+ }
329
+ }
330
+
331
+ private inline fun onUi(crossinline block: () -> Unit) {
332
+ if (UiThreadUtil.isOnUiThread()) {
333
+ block()
334
+ } else {
335
+ UiThreadUtil.runOnUiThread { block() }
336
+ }
337
+ }
@@ -0,0 +1,205 @@
1
+ package com.googlemapsnitro
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.content.Intent
5
+ import android.location.Location
6
+ import android.os.Build
7
+ import android.os.Looper
8
+ import android.provider.Settings
9
+ import com.facebook.react.bridge.ReactContext
10
+ import com.facebook.react.bridge.UiThreadUtil
11
+ import com.google.android.gms.common.ConnectionResult
12
+ import com.google.android.gms.common.GoogleApiAvailability
13
+ import com.google.android.gms.common.api.ApiException
14
+ import com.google.android.gms.common.api.CommonStatusCodes
15
+ import com.google.android.gms.common.api.ResolvableApiException
16
+ import com.google.android.gms.location.FusedLocationProviderClient
17
+ import com.google.android.gms.location.LocationCallback
18
+ import com.google.android.gms.location.LocationRequest
19
+ import com.google.android.gms.location.LocationResult
20
+ import com.google.android.gms.location.LocationServices
21
+ import com.google.android.gms.location.LocationSettingsRequest
22
+ import com.google.android.gms.location.LocationSettingsStatusCodes
23
+ import com.google.android.gms.location.Priority
24
+ import com.google.android.gms.tasks.OnSuccessListener
25
+
26
+ private const val REQ_LOCATION_SETTINGS = 2001
27
+
28
+ class LocationHandler(
29
+ val context: ReactContext,
30
+ ) {
31
+ private val fusedLocationClientProviderClient: FusedLocationProviderClient =
32
+ LocationServices.getFusedLocationProviderClient(context)
33
+ private var locationRequest: LocationRequest? = null
34
+ private var locationCallback: LocationCallback? = null
35
+ private var priority = Priority.PRIORITY_HIGH_ACCURACY
36
+ private var interval: Long = 5000
37
+ private var minUpdateInterval: Long = 5000
38
+ var onUpdate: ((Location) -> Unit)? = null
39
+ var onError: ((RNLocationErrorCode) -> Unit)? = null
40
+
41
+ init {
42
+ buildLocationRequest()
43
+ }
44
+
45
+ fun showLocationDialog() {
46
+ UiThreadUtil.runOnUiThread {
47
+ val activity = context.currentActivity ?: run { return@runOnUiThread }
48
+
49
+ val lr =
50
+ if (Build.VERSION.SDK_INT >= 31) {
51
+ LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 10_000L).build()
52
+ } else {
53
+ @Suppress("DEPRECATION")
54
+ LocationRequest.create().apply { priority = Priority.PRIORITY_HIGH_ACCURACY }
55
+ }
56
+
57
+ val req =
58
+ LocationSettingsRequest
59
+ .Builder()
60
+ .addLocationRequest(lr)
61
+ .setAlwaysShow(true)
62
+ .build()
63
+
64
+ val settingsClient = LocationServices.getSettingsClient(activity)
65
+ settingsClient
66
+ .checkLocationSettings(req)
67
+ .addOnSuccessListener {
68
+ }.addOnFailureListener { ex ->
69
+ if (ex is ResolvableApiException) {
70
+ try {
71
+ ex.startResolutionForResult(activity, REQ_LOCATION_SETTINGS)
72
+ } catch (_: Exception) {
73
+ onError?.invoke(RNLocationErrorCode.SETTINGS_NOT_SATISFIED)
74
+ }
75
+ } else {
76
+ onError?.invoke(RNLocationErrorCode.SETTINGS_NOT_SATISFIED)
77
+ openLocationSettings()
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ fun openLocationSettings() {
84
+ UiThreadUtil.runOnUiThread {
85
+ val intent =
86
+ Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
87
+ context.currentActivity?.startActivity(intent)
88
+ }
89
+ }
90
+
91
+ @Suppress("deprecation")
92
+ private fun buildLocationRequest() {
93
+ locationRequest =
94
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
95
+ LocationRequest
96
+ .Builder(priority, interval)
97
+ .setMinUpdateIntervalMillis(minUpdateInterval)
98
+ .build()
99
+ } else {
100
+ LocationRequest
101
+ .create()
102
+ .setPriority(priority)
103
+ .setInterval(interval)
104
+ .setFastestInterval(minUpdateInterval)
105
+ }
106
+ restartLocationUpdates()
107
+ }
108
+
109
+ fun setPriority(priority: Int) {
110
+ this.priority = priority
111
+ buildLocationRequest()
112
+ }
113
+
114
+ fun setInterval(interval: Int) {
115
+ this.interval = interval.toLong()
116
+ buildLocationRequest()
117
+ }
118
+
119
+ fun setFastestInterval(fastestInterval: Int) {
120
+ this.minUpdateInterval = fastestInterval.toLong()
121
+ buildLocationRequest()
122
+ }
123
+
124
+ private fun restartLocationUpdates() {
125
+ stop()
126
+ // 4) Google Play Services checken – früh zurückmelden
127
+ val playServicesStatus =
128
+ GoogleApiAvailability
129
+ .getInstance()
130
+ .isGooglePlayServicesAvailable(context)
131
+ if (playServicesStatus != ConnectionResult.SUCCESS) {
132
+ onError?.invoke(RNLocationErrorCode.PLAY_SERVICE_NOT_AVAILABLE)
133
+ return
134
+ }
135
+ start()
136
+ }
137
+
138
+ @SuppressLint("MissingPermission")
139
+ fun start() {
140
+ try {
141
+ fusedLocationClientProviderClient.lastLocation
142
+ .addOnSuccessListener(
143
+ OnSuccessListener { location ->
144
+ if (location != null) {
145
+ onUpdate?.invoke(location)
146
+ }
147
+ },
148
+ ).addOnFailureListener { e ->
149
+ val error = mapThrowableToCode(e)
150
+ onError?.invoke(error)
151
+ }
152
+ locationCallback =
153
+ object : LocationCallback() {
154
+ override fun onLocationResult(locationResult: LocationResult) {
155
+ val location = locationResult.lastLocation
156
+ if (location != null) {
157
+ onUpdate?.invoke(location)
158
+ } else {
159
+ onError?.invoke(RNLocationErrorCode.POSITION_UNAVAILABLE)
160
+ }
161
+ }
162
+ }
163
+ fusedLocationClientProviderClient
164
+ .requestLocationUpdates(
165
+ locationRequest!!,
166
+ locationCallback!!,
167
+ Looper.getMainLooper(),
168
+ ).addOnFailureListener { e ->
169
+ val error = mapThrowableToCode(e)
170
+ onError?.invoke(error)
171
+ }
172
+ } catch (se: SecurityException) {
173
+ onError?.invoke(RNLocationErrorCode.PERMISSION_DENIED)
174
+ } catch (ex: Exception) {
175
+ val error = mapThrowableToCode(ex)
176
+ onError?.invoke(error)
177
+ }
178
+ }
179
+
180
+ private fun mapThrowableToCode(t: Throwable): RNLocationErrorCode {
181
+ if (t is SecurityException) return RNLocationErrorCode.PERMISSION_DENIED
182
+ if (t.message?.contains("GoogleApi", ignoreCase = true) == true) {
183
+ val gms = GoogleApiAvailability.getInstance()
184
+ val status = gms.isGooglePlayServicesAvailable(context)
185
+ if (status != ConnectionResult.SUCCESS) return RNLocationErrorCode.PLAY_SERVICE_NOT_AVAILABLE
186
+ }
187
+ if (t is ApiException) {
188
+ when (t.statusCode) {
189
+ CommonStatusCodes.NETWORK_ERROR -> return RNLocationErrorCode.POSITION_UNAVAILABLE
190
+ LocationSettingsStatusCodes.RESOLUTION_REQUIRED,
191
+ LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE,
192
+ -> return RNLocationErrorCode.SETTINGS_NOT_SATISFIED
193
+ }
194
+ return RNLocationErrorCode.INTERNAL_ERROR
195
+ }
196
+ return RNLocationErrorCode.INTERNAL_ERROR
197
+ }
198
+
199
+ fun stop() {
200
+ if (locationCallback != null) {
201
+ fusedLocationClientProviderClient.removeLocationUpdates(locationCallback!!)
202
+ locationCallback = null
203
+ }
204
+ }
205
+ }
@@ -0,0 +1,145 @@
1
+ package com.googlemapsnitro
2
+
3
+ import android.graphics.Bitmap
4
+ import android.graphics.Canvas
5
+ import android.util.LruCache
6
+ import androidx.core.graphics.createBitmap
7
+ import com.caverock.androidsvg.SVG
8
+ import com.facebook.react.uimanager.PixelUtil.dpToPx
9
+ import com.google.android.gms.maps.model.BitmapDescriptor
10
+ import com.google.android.gms.maps.model.BitmapDescriptorFactory
11
+ import com.google.android.gms.maps.model.LatLng
12
+ import com.google.android.gms.maps.model.MarkerOptions
13
+ import kotlinx.coroutines.CoroutineScope
14
+ import kotlinx.coroutines.Dispatchers
15
+ import kotlinx.coroutines.Job
16
+ import kotlinx.coroutines.SupervisorJob
17
+ import kotlinx.coroutines.ensureActive
18
+ import kotlinx.coroutines.launch
19
+ import kotlinx.coroutines.withContext
20
+ import kotlin.coroutines.coroutineContext
21
+
22
+ class MarkerOptions(
23
+ private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default),
24
+ ) {
25
+ private val iconCache =
26
+ object : LruCache<Int, BitmapDescriptor>(512) {
27
+ override fun sizeOf(
28
+ key: Int,
29
+ value: BitmapDescriptor,
30
+ ): Int = 1
31
+ }
32
+
33
+ private val jobsById = mutableMapOf<String, Job>()
34
+
35
+ fun build(
36
+ m: RNMarker,
37
+ icon: BitmapDescriptor,
38
+ ): MarkerOptions =
39
+ MarkerOptions()
40
+ .position(LatLng(m.coordinate.latitude, m.coordinate.longitude))
41
+ .zIndex(m.zIndex.toFloat())
42
+ .icon(icon)
43
+ .anchor((m.anchor?.x ?: 0.5).toFloat(), (m.anchor?.y ?: 0.5).toFloat())
44
+
45
+ fun buildIconAsync(
46
+ id: String,
47
+ m: RNMarker,
48
+ onReady: (BitmapDescriptor) -> Unit,
49
+ ) {
50
+ jobsById[id]?.cancel()
51
+
52
+ val key = m.styleHash()
53
+ iconCache.get(key)?.let { cached ->
54
+ onReady(cached)
55
+ return
56
+ }
57
+
58
+ val job =
59
+ scope.launch {
60
+ try {
61
+ ensureActive()
62
+ val bmp = renderBitmap(m)
63
+ if (bmp != null) {
64
+ ensureActive()
65
+ val desc = BitmapDescriptorFactory.fromBitmap(bmp)
66
+ iconCache.put(key, desc)
67
+ bmp.recycle()
68
+ withContext(Dispatchers.Main) {
69
+ ensureActive()
70
+ onReady(desc)
71
+ }
72
+ }
73
+ } catch (_: OutOfMemoryError) {
74
+ iconCache.evictAll()
75
+ } catch (_: Throwable) {
76
+ } finally {
77
+ jobsById.remove(id)
78
+ }
79
+ }
80
+
81
+ jobsById[id] = job
82
+ }
83
+
84
+ fun cancelIconJob(id: String) {
85
+ jobsById[id]?.cancel()
86
+ jobsById.remove(id)
87
+ }
88
+
89
+ fun cancelAllJobs() {
90
+ val ids = jobsById.keys.toList()
91
+ ids.forEach { id ->
92
+ jobsById[id]?.cancel()
93
+ }
94
+ jobsById.clear()
95
+ iconCache.evictAll()
96
+ }
97
+
98
+ private suspend fun renderBitmap(m: RNMarker): Bitmap? {
99
+ var bmp: Bitmap? = null
100
+ try {
101
+ coroutineContext.ensureActive()
102
+ val svg = SVG.getFromString(m.iconSvg)
103
+
104
+ coroutineContext.ensureActive()
105
+ svg.setDocumentWidth(m.width.dpToPx())
106
+ svg.setDocumentHeight(m.height.dpToPx())
107
+
108
+ coroutineContext.ensureActive()
109
+ bmp =
110
+ createBitmap(m.width.dpToPx().toInt(), m.height.dpToPx().toInt(), Bitmap.Config.ARGB_8888)
111
+
112
+ coroutineContext.ensureActive()
113
+ val canvas = Canvas(bmp)
114
+ svg.renderToCanvas(canvas)
115
+
116
+ coroutineContext.ensureActive()
117
+ return bmp
118
+ } catch (t: Throwable) {
119
+ try {
120
+ bmp?.recycle()
121
+ } catch (_: Throwable) {
122
+ }
123
+ throw t
124
+ }
125
+ }
126
+ }
127
+
128
+ fun RNMarker.markerEquals(b: RNMarker): Boolean =
129
+ id == b.id &&
130
+ zIndex == b.zIndex &&
131
+ coordinate == b.coordinate &&
132
+ anchor == b.anchor &&
133
+ markerStyleEquals(b)
134
+
135
+ fun RNMarker.markerStyleEquals(b: RNMarker): Boolean =
136
+ width == b.width &&
137
+ height == b.height &&
138
+ iconSvg == b.iconSvg
139
+
140
+ fun RNMarker.styleHash(): Int =
141
+ arrayOf<Any?>(
142
+ width,
143
+ height,
144
+ iconSvg,
145
+ ).contentHashCode()