Skip to content

Commit 796be0b

Browse files
authored
feat: more map features
feat: add location config support feat: add myLocationEnabled support feat: add indoorEnabled support refactor: code optimizations and cleanup chore: updated example
2 parents 9cc361b + 7921f49 commit 796be0b

35 files changed

+856
-491
lines changed

android/src/main/java/com/rngooglemapsplus/GoogleMapsViewImpl.kt

Lines changed: 122 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.rngooglemapsplus
22

3+
import android.annotation.SuppressLint
34
import android.location.Location
45
import android.widget.FrameLayout
56
import com.facebook.react.bridge.LifecycleEventListener
@@ -24,12 +25,14 @@ import com.google.android.gms.maps.model.Polygon
2425
import com.google.android.gms.maps.model.PolygonOptions
2526
import com.google.android.gms.maps.model.Polyline
2627
import com.google.android.gms.maps.model.PolylineOptions
28+
import com.rngooglemapsplus.extensions.toGooglePriority
29+
import com.rngooglemapsplus.extensions.toLocationErrorCode
2730

2831
class GoogleMapsViewImpl(
2932
val reactContext: ThemedReactContext,
3033
val locationHandler: LocationHandler,
3134
val playServiceHandler: PlayServicesHandler,
32-
val markerOptions: com.rngooglemapsplus.MarkerOptions,
35+
val markerBuilder: MarkerBuilder,
3336
) : FrameLayout(reactContext),
3437
GoogleMap.OnCameraMoveStartedListener,
3538
GoogleMap.OnCameraMoveListener,
@@ -252,12 +255,37 @@ class GoogleMapsViewImpl(
252255
it.bottom.dpToPx().toInt(),
253256
)
254257
}
258+
259+
uiSettings?.let { v ->
260+
googleMap?.uiSettings?.apply {
261+
v.allGesturesEnabled?.let { setAllGesturesEnabled(it) }
262+
v.compassEnabled?.let { isCompassEnabled = it }
263+
v.indoorLevelPickerEnabled?.let { isIndoorLevelPickerEnabled = it }
264+
v.mapToolbarEnabled?.let { isMapToolbarEnabled = it }
265+
v.myLocationButtonEnabled?.let {
266+
googleMap?.setLocationSource(locationHandler)
267+
isMyLocationButtonEnabled = it
268+
}
269+
v.rotateEnabled?.let { isRotateGesturesEnabled = it }
270+
v.scrollEnabled?.let { isScrollGesturesEnabled = it }
271+
v.scrollDuringRotateOrZoomEnabled?.let {
272+
isScrollGesturesEnabledDuringRotateOrZoom = it
273+
}
274+
v.tiltEnabled?.let { isTiltGesturesEnabled = it }
275+
v.zoomControlsEnabled?.let { isZoomControlsEnabled = it }
276+
v.zoomGesturesEnabled?.let { isZoomGesturesEnabled = it }
277+
}
278+
}
279+
255280
buildingEnabled?.let {
256281
googleMap?.isBuildingsEnabled = it
257282
}
258283
trafficEnabled?.let {
259284
googleMap?.isTrafficEnabled = it
260285
}
286+
indoorEnabled?.let {
287+
googleMap?.isIndoorEnabled = it
288+
}
261289
googleMap?.setMapStyle(customMapStyle)
262290
mapType?.let {
263291
googleMap?.mapType = it
@@ -273,6 +301,12 @@ class GoogleMapsViewImpl(
273301
}
274302
}
275303

304+
locationConfig?.let {
305+
locationHandler.priority = it.android?.priority?.toGooglePriority()
306+
locationHandler.interval = it.android?.interval?.toLong()
307+
locationHandler.minUpdateInterval = it.android?.minUpdateInterval?.toLong()
308+
}
309+
276310
if (pendingMarkers.isNotEmpty()) {
277311
pendingMarkers.forEach { (id, opts) ->
278312
internalAddMarker(id, opts)
@@ -302,6 +336,69 @@ class GoogleMapsViewImpl(
302336
}
303337
}
304338

339+
var uiSettings: RNMapUiSettings? = null
340+
set(value) {
341+
field = value
342+
onUi {
343+
value?.let { v ->
344+
googleMap?.uiSettings?.apply {
345+
v.allGesturesEnabled?.let { setAllGesturesEnabled(it) }
346+
v.compassEnabled?.let { isCompassEnabled = it }
347+
v.indoorLevelPickerEnabled?.let { isIndoorLevelPickerEnabled = it }
348+
v.mapToolbarEnabled?.let { isMapToolbarEnabled = it }
349+
v.myLocationButtonEnabled?.let {
350+
googleMap?.setLocationSource(locationHandler)
351+
isMyLocationButtonEnabled = it
352+
}
353+
v.rotateEnabled?.let { isRotateGesturesEnabled = it }
354+
v.scrollEnabled?.let { isScrollGesturesEnabled = it }
355+
v.scrollDuringRotateOrZoomEnabled?.let {
356+
isScrollGesturesEnabledDuringRotateOrZoom = it
357+
}
358+
v.tiltEnabled?.let { isTiltGesturesEnabled = it }
359+
v.zoomControlsEnabled?.let { isZoomControlsEnabled = it }
360+
v.zoomGesturesEnabled?.let { isZoomGesturesEnabled = it }
361+
}
362+
}
363+
?: run {
364+
googleMap?.uiSettings?.apply {
365+
setAllGesturesEnabled(true)
366+
isCompassEnabled = false
367+
isIndoorLevelPickerEnabled = false
368+
isMapToolbarEnabled = false
369+
isMyLocationButtonEnabled = false
370+
googleMap?.setLocationSource(null)
371+
isRotateGesturesEnabled = true
372+
isScrollGesturesEnabled = true
373+
isScrollGesturesEnabledDuringRotateOrZoom = true
374+
isTiltGesturesEnabled = true
375+
isZoomControlsEnabled = false
376+
isZoomGesturesEnabled = false
377+
}
378+
}
379+
}
380+
}
381+
382+
@SuppressLint("MissingPermission")
383+
var myLocationEnabled: Boolean? = null
384+
set(value) {
385+
onUi {
386+
try {
387+
value?.let {
388+
googleMap?.isMyLocationEnabled = it
389+
}
390+
?: run {
391+
googleMap?.isMyLocationEnabled = false
392+
}
393+
} catch (se: SecurityException) {
394+
onLocationError?.invoke(RNLocationErrorCode.PERMISSION_DENIED)
395+
} catch (ex: Exception) {
396+
val error = ex.toLocationErrorCode(context)
397+
onLocationError?.invoke(error)
398+
}
399+
}
400+
}
401+
305402
var buildingEnabled: Boolean? = null
306403
set(value) {
307404
field = value
@@ -327,6 +424,19 @@ class GoogleMapsViewImpl(
327424
}
328425
}
329426

427+
var indoorEnabled: Boolean? = null
428+
set(value) {
429+
field = value
430+
onUi {
431+
value?.let {
432+
googleMap?.isIndoorEnabled = it
433+
}
434+
?: run {
435+
googleMap?.isIndoorEnabled = false
436+
}
437+
}
438+
}
439+
330440
var customMapStyle: MapStyleOptions? = null
331441
set(value) {
332442
field = value
@@ -400,6 +510,14 @@ class GoogleMapsViewImpl(
400510
}
401511
}
402512

513+
var locationConfig: RNLocationConfig? = null
514+
set(value) {
515+
field = value
516+
locationHandler.priority = value?.android?.priority?.toGooglePriority()
517+
locationHandler.interval = value?.android?.interval?.toLong()
518+
locationHandler.minUpdateInterval = value?.android?.minUpdateInterval?.toLong()
519+
}
520+
403521
var onMapError: ((RNMapErrorCode) -> Unit)? = null
404522
var onMapReady: ((Boolean) -> Unit)? = null
405523
var onLocationUpdate: ((RNLocation) -> Unit)? = null
@@ -414,7 +532,7 @@ class GoogleMapsViewImpl(
414532
var onCameraChangeComplete: ((RNRegion, RNCamera, Boolean) -> Unit)? = null
415533

416534
fun setCamera(
417-
camera: RNCamera,
535+
cameraPosition: CameraPosition,
418536
animated: Boolean,
419537
durationMS: Int,
420538
) {
@@ -423,33 +541,8 @@ class GoogleMapsViewImpl(
423541
if (current == null) {
424542
return@onUi
425543
}
426-
val camPosBuilder =
427-
CameraPosition.Builder(
428-
current,
429-
)
430-
431-
camera.center?.let {
432-
camPosBuilder.target(
433-
LatLng(
434-
it.latitude,
435-
it.longitude,
436-
),
437-
)
438-
}
439-
440-
camera.zoom?.let {
441-
camPosBuilder.zoom(it.toFloat())
442-
}
443-
camera.bearing?.let {
444-
camPosBuilder.bearing(it.toFloat())
445-
}
446-
camera.tilt?.let {
447-
camPosBuilder.tilt(it.toFloat())
448-
}
449-
450-
val camPos = camPosBuilder.build()
451544

452-
val update = CameraUpdateFactory.newCameraPosition(camPos)
545+
val update = CameraUpdateFactory.newCameraPosition(cameraPosition)
453546

454547
if (animated) {
455548
googleMap?.animateCamera(update, durationMS, null)
@@ -746,7 +839,7 @@ class GoogleMapsViewImpl(
746839

747840
fun destroyInternal() {
748841
onUi {
749-
markerOptions.cancelAllJobs()
842+
markerBuilder.cancelAllJobs()
750843
clearMarkers()
751844
clearPolylines()
752845
clearPolygons()

android/src/main/java/com/rngooglemapsplus/LocationHandler.kt

Lines changed: 44 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,50 @@ import com.facebook.react.bridge.ReactContext
1010
import com.facebook.react.bridge.UiThreadUtil
1111
import com.google.android.gms.common.ConnectionResult
1212
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
1513
import com.google.android.gms.common.api.ResolvableApiException
1614
import com.google.android.gms.location.FusedLocationProviderClient
1715
import com.google.android.gms.location.LocationCallback
1816
import com.google.android.gms.location.LocationRequest
1917
import com.google.android.gms.location.LocationResult
2018
import com.google.android.gms.location.LocationServices
2119
import com.google.android.gms.location.LocationSettingsRequest
22-
import com.google.android.gms.location.LocationSettingsStatusCodes
2320
import com.google.android.gms.location.Priority
21+
import com.google.android.gms.maps.LocationSource
2422
import com.google.android.gms.tasks.OnSuccessListener
23+
import com.rngooglemapsplus.extensions.toLocationErrorCode
2524

2625
private const val REQ_LOCATION_SETTINGS = 2001
26+
private const val PRIORITY_DEFAULT = Priority.PRIORITY_BALANCED_POWER_ACCURACY
27+
private const val INTERVAL_DEFAULT = 600000L
28+
private const val MIN_UPDATE_INTERVAL = 3600000L
2729

2830
class LocationHandler(
2931
val context: ReactContext,
30-
) {
32+
) : LocationSource {
3133
private val fusedLocationClientProviderClient: FusedLocationProviderClient =
3234
LocationServices.getFusedLocationProviderClient(context)
35+
private var listener: LocationSource.OnLocationChangedListener? = null
3336
private var locationRequest: LocationRequest? = null
3437
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+
39+
var priority: Int? = PRIORITY_DEFAULT
40+
set(value) {
41+
field = value
42+
start()
43+
}
44+
45+
var interval: Long? = INTERVAL_DEFAULT
46+
set(value) {
47+
field = value
48+
buildLocationRequest()
49+
}
50+
51+
var minUpdateInterval: Long? = MIN_UPDATE_INTERVAL
52+
set(value) {
53+
field = value
54+
buildLocationRequest()
55+
}
56+
3857
var onUpdate: ((Location) -> Unit)? = null
3958
var onError: ((RNLocationErrorCode) -> Unit)? = null
4059

@@ -90,6 +109,10 @@ class LocationHandler(
90109

91110
@Suppress("deprecation")
92111
private fun buildLocationRequest() {
112+
val priority = priority ?: Priority.PRIORITY_BALANCED_POWER_ACCURACY
113+
val interval = interval ?: INTERVAL_DEFAULT
114+
val minUpdateInterval = minUpdateInterval ?: MIN_UPDATE_INTERVAL
115+
93116
locationRequest =
94117
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
95118
LocationRequest
@@ -106,21 +129,6 @@ class LocationHandler(
106129
restartLocationUpdates()
107130
}
108131

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-
124132
private fun restartLocationUpdates() {
125133
stop()
126134
// 4) Google Play Services checken – früh zurückmelden
@@ -146,14 +154,15 @@ class LocationHandler(
146154
}
147155
},
148156
).addOnFailureListener { e ->
149-
val error = mapThrowableToCode(e)
157+
val error = e.toLocationErrorCode(context)
150158
onError?.invoke(error)
151159
}
152160
locationCallback =
153161
object : LocationCallback() {
154162
override fun onLocationResult(locationResult: LocationResult) {
155163
val location = locationResult.lastLocation
156164
if (location != null) {
165+
listener?.onLocationChanged(location)
157166
onUpdate?.invoke(location)
158167
} else {
159168
onError?.invoke(RNLocationErrorCode.POSITION_UNAVAILABLE)
@@ -166,40 +175,31 @@ class LocationHandler(
166175
locationCallback!!,
167176
Looper.getMainLooper(),
168177
).addOnFailureListener { e ->
169-
val error = mapThrowableToCode(e)
178+
val error = e.toLocationErrorCode(context)
170179
onError?.invoke(error)
171180
}
172181
} catch (se: SecurityException) {
173182
onError?.invoke(RNLocationErrorCode.PERMISSION_DENIED)
174183
} catch (ex: Exception) {
175-
val error = mapThrowableToCode(ex)
184+
val error = ex.toLocationErrorCode(context)
176185
onError?.invoke(error)
177186
}
178187
}
179188

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-
199189
fun stop() {
190+
listener = null
200191
if (locationCallback != null) {
201192
fusedLocationClientProviderClient.removeLocationUpdates(locationCallback!!)
202193
locationCallback = null
203194
}
204195
}
196+
197+
override fun activate(listener: LocationSource.OnLocationChangedListener) {
198+
this.listener = listener
199+
start()
200+
}
201+
202+
override fun deactivate() {
203+
stop()
204+
}
205205
}

0 commit comments

Comments
 (0)