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.
- package/GoogleMapsNitro.podspec +34 -0
- package/LICENSE +20 -0
- package/README.md +40 -0
- package/android/CMakeLists.txt +32 -0
- package/android/build.gradle +135 -0
- package/android/fix-prefab.gradle +51 -0
- package/android/gradle.properties +8 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/cpp-adapter.cpp +6 -0
- package/android/src/main/java/com/googlemapsnitro/Color.kt +65 -0
- package/android/src/main/java/com/googlemapsnitro/GoogleMapsNitroPackage.kt +35 -0
- package/android/src/main/java/com/googlemapsnitro/GoogleMapsNitroViewImpl.kt +720 -0
- package/android/src/main/java/com/googlemapsnitro/HybridGoogleMapsNitroModule.kt +22 -0
- package/android/src/main/java/com/googlemapsnitro/HybridGoogleMapsNitroView.kt +337 -0
- package/android/src/main/java/com/googlemapsnitro/LocationHandler.kt +205 -0
- package/android/src/main/java/com/googlemapsnitro/MapMarker.kt +145 -0
- package/android/src/main/java/com/googlemapsnitro/MapPolygon.kt +36 -0
- package/android/src/main/java/com/googlemapsnitro/MapPolyline.kt +59 -0
- package/android/src/main/java/com/googlemapsnitro/PermissionHandler.kt +116 -0
- package/android/src/main/java/com/googlemapsnitro/PlayServicesHandler.kt +25 -0
- package/ios/Color.swift +109 -0
- package/ios/GoogleMapNitroViewImpl.swift +590 -0
- package/ios/HybridGoogleMapsNitroModule.swift +27 -0
- package/ios/HybridGoogleMapsNitroView.swift +348 -0
- package/ios/LocationHandler.swift +205 -0
- package/ios/MapHelper.swift +18 -0
- package/ios/MapMarker.swift +207 -0
- package/ios/MapPolygon.swift +55 -0
- package/ios/MapPolyline.swift +83 -0
- package/ios/PermissionHandler.swift +73 -0
- package/lib/module/GoogleMapsNitroModule.nitro.js +4 -0
- package/lib/module/GoogleMapsNitroModule.nitro.js.map +1 -0
- package/lib/module/GoogleMapsNitroView.nitro.js +4 -0
- package/lib/module/GoogleMapsNitroView.nitro.js.map +1 -0
- package/lib/module/index.js +8 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +78 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/GoogleMapsNitroModule.nitro.d.ts +12 -0
- package/lib/typescript/src/GoogleMapsNitroModule.nitro.d.ts.map +1 -0
- package/lib/typescript/src/GoogleMapsNitroView.nitro.d.ts +34 -0
- package/lib/typescript/src/GoogleMapsNitroView.nitro.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +7 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +113 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/nitro.json +28 -0
- package/package.json +13 -3
- package/src/GoogleMapsNitroModule.nitro.ts +13 -0
- package/src/GoogleMapsNitroView.nitro.ts +78 -0
- package/src/index.tsx +24 -0
- 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()
|