Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 122 additions & 29 deletions android/src/main/java/com/rngooglemapsplus/GoogleMapsViewImpl.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.rngooglemapsplus

import android.annotation.SuppressLint
import android.location.Location
import android.widget.FrameLayout
import com.facebook.react.bridge.LifecycleEventListener
Expand All @@ -24,12 +25,14 @@ import com.google.android.gms.maps.model.Polygon
import com.google.android.gms.maps.model.PolygonOptions
import com.google.android.gms.maps.model.Polyline
import com.google.android.gms.maps.model.PolylineOptions
import com.rngooglemapsplus.extensions.toGooglePriority
import com.rngooglemapsplus.extensions.toLocationErrorCode

class GoogleMapsViewImpl(
val reactContext: ThemedReactContext,
val locationHandler: LocationHandler,
val playServiceHandler: PlayServicesHandler,
val markerOptions: com.rngooglemapsplus.MarkerOptions,
val markerBuilder: MarkerBuilder,
) : FrameLayout(reactContext),
GoogleMap.OnCameraMoveStartedListener,
GoogleMap.OnCameraMoveListener,
Expand Down Expand Up @@ -252,12 +255,37 @@ class GoogleMapsViewImpl(
it.bottom.dpToPx().toInt(),
)
}

uiSettings?.let { v ->
googleMap?.uiSettings?.apply {
v.allGesturesEnabled?.let { setAllGesturesEnabled(it) }
v.compassEnabled?.let { isCompassEnabled = it }
v.indoorLevelPickerEnabled?.let { isIndoorLevelPickerEnabled = it }
v.mapToolbarEnabled?.let { isMapToolbarEnabled = it }
v.myLocationButtonEnabled?.let {
googleMap?.setLocationSource(locationHandler)
isMyLocationButtonEnabled = it
}
v.rotateEnabled?.let { isRotateGesturesEnabled = it }
v.scrollEnabled?.let { isScrollGesturesEnabled = it }
v.scrollDuringRotateOrZoomEnabled?.let {
isScrollGesturesEnabledDuringRotateOrZoom = it
}
v.tiltEnabled?.let { isTiltGesturesEnabled = it }
v.zoomControlsEnabled?.let { isZoomControlsEnabled = it }
v.zoomGesturesEnabled?.let { isZoomGesturesEnabled = it }
}
}

buildingEnabled?.let {
googleMap?.isBuildingsEnabled = it
}
trafficEnabled?.let {
googleMap?.isTrafficEnabled = it
}
indoorEnabled?.let {
googleMap?.isIndoorEnabled = it
}
googleMap?.setMapStyle(customMapStyle)
mapType?.let {
googleMap?.mapType = it
Expand All @@ -273,6 +301,12 @@ class GoogleMapsViewImpl(
}
}

locationConfig?.let {
locationHandler.priority = it.android?.priority?.toGooglePriority()
locationHandler.interval = it.android?.interval?.toLong()
locationHandler.minUpdateInterval = it.android?.minUpdateInterval?.toLong()
}

if (pendingMarkers.isNotEmpty()) {
pendingMarkers.forEach { (id, opts) ->
internalAddMarker(id, opts)
Expand Down Expand Up @@ -302,6 +336,69 @@ class GoogleMapsViewImpl(
}
}

var uiSettings: RNMapUiSettings? = null
set(value) {
field = value
onUi {
value?.let { v ->
googleMap?.uiSettings?.apply {
v.allGesturesEnabled?.let { setAllGesturesEnabled(it) }
v.compassEnabled?.let { isCompassEnabled = it }
v.indoorLevelPickerEnabled?.let { isIndoorLevelPickerEnabled = it }
v.mapToolbarEnabled?.let { isMapToolbarEnabled = it }
v.myLocationButtonEnabled?.let {
googleMap?.setLocationSource(locationHandler)
isMyLocationButtonEnabled = it
}
v.rotateEnabled?.let { isRotateGesturesEnabled = it }
v.scrollEnabled?.let { isScrollGesturesEnabled = it }
v.scrollDuringRotateOrZoomEnabled?.let {
isScrollGesturesEnabledDuringRotateOrZoom = it
}
v.tiltEnabled?.let { isTiltGesturesEnabled = it }
v.zoomControlsEnabled?.let { isZoomControlsEnabled = it }
v.zoomGesturesEnabled?.let { isZoomGesturesEnabled = it }
}
}
?: run {
googleMap?.uiSettings?.apply {
setAllGesturesEnabled(true)
isCompassEnabled = false
isIndoorLevelPickerEnabled = false
isMapToolbarEnabled = false
isMyLocationButtonEnabled = false
googleMap?.setLocationSource(null)
isRotateGesturesEnabled = true
isScrollGesturesEnabled = true
isScrollGesturesEnabledDuringRotateOrZoom = true
isTiltGesturesEnabled = true
isZoomControlsEnabled = false
isZoomGesturesEnabled = false
}
}
}
}

@SuppressLint("MissingPermission")
var myLocationEnabled: Boolean? = null
set(value) {
onUi {
try {
value?.let {
googleMap?.isMyLocationEnabled = it
}
?: run {
googleMap?.isMyLocationEnabled = false
}
} catch (se: SecurityException) {
onLocationError?.invoke(RNLocationErrorCode.PERMISSION_DENIED)
} catch (ex: Exception) {
val error = ex.toLocationErrorCode(context)
onLocationError?.invoke(error)
}
}
}

var buildingEnabled: Boolean? = null
set(value) {
field = value
Expand All @@ -327,6 +424,19 @@ class GoogleMapsViewImpl(
}
}

var indoorEnabled: Boolean? = null
set(value) {
field = value
onUi {
value?.let {
googleMap?.isIndoorEnabled = it
}
?: run {
googleMap?.isIndoorEnabled = false
}
}
}

var customMapStyle: MapStyleOptions? = null
set(value) {
field = value
Expand Down Expand Up @@ -400,6 +510,14 @@ class GoogleMapsViewImpl(
}
}

var locationConfig: RNLocationConfig? = null
set(value) {
field = value
locationHandler.priority = value?.android?.priority?.toGooglePriority()
locationHandler.interval = value?.android?.interval?.toLong()
locationHandler.minUpdateInterval = value?.android?.minUpdateInterval?.toLong()
}

var onMapError: ((RNMapErrorCode) -> Unit)? = null
var onMapReady: ((Boolean) -> Unit)? = null
var onLocationUpdate: ((RNLocation) -> Unit)? = null
Expand All @@ -414,7 +532,7 @@ class GoogleMapsViewImpl(
var onCameraChangeComplete: ((RNRegion, RNCamera, Boolean) -> Unit)? = null

fun setCamera(
camera: RNCamera,
cameraPosition: CameraPosition,
animated: Boolean,
durationMS: Int,
) {
Expand All @@ -423,33 +541,8 @@ class GoogleMapsViewImpl(
if (current == null) {
return@onUi
}
val camPosBuilder =
CameraPosition.Builder(
current,
)

camera.center?.let {
camPosBuilder.target(
LatLng(
it.latitude,
it.longitude,
),
)
}

camera.zoom?.let {
camPosBuilder.zoom(it.toFloat())
}
camera.bearing?.let {
camPosBuilder.bearing(it.toFloat())
}
camera.tilt?.let {
camPosBuilder.tilt(it.toFloat())
}

val camPos = camPosBuilder.build()

val update = CameraUpdateFactory.newCameraPosition(camPos)
val update = CameraUpdateFactory.newCameraPosition(cameraPosition)

if (animated) {
googleMap?.animateCamera(update, durationMS, null)
Expand Down Expand Up @@ -746,7 +839,7 @@ class GoogleMapsViewImpl(

fun destroyInternal() {
onUi {
markerOptions.cancelAllJobs()
markerBuilder.cancelAllJobs()
clearMarkers()
clearPolylines()
clearPolygons()
Expand Down
88 changes: 44 additions & 44 deletions android/src/main/java/com/rngooglemapsplus/LocationHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,50 @@ import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.UiThreadUtil
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
import com.google.android.gms.common.api.ApiException
import com.google.android.gms.common.api.CommonStatusCodes
import com.google.android.gms.common.api.ResolvableApiException
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices
import com.google.android.gms.location.LocationSettingsRequest
import com.google.android.gms.location.LocationSettingsStatusCodes
import com.google.android.gms.location.Priority
import com.google.android.gms.maps.LocationSource
import com.google.android.gms.tasks.OnSuccessListener
import com.rngooglemapsplus.extensions.toLocationErrorCode

private const val REQ_LOCATION_SETTINGS = 2001
private const val PRIORITY_DEFAULT = Priority.PRIORITY_BALANCED_POWER_ACCURACY
private const val INTERVAL_DEFAULT = 600000L
private const val MIN_UPDATE_INTERVAL = 3600000L

class LocationHandler(
val context: ReactContext,
) {
) : LocationSource {
private val fusedLocationClientProviderClient: FusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(context)
private var listener: LocationSource.OnLocationChangedListener? = null
private var locationRequest: LocationRequest? = null
private var locationCallback: LocationCallback? = null
private var priority = Priority.PRIORITY_HIGH_ACCURACY
private var interval: Long = 5000
private var minUpdateInterval: Long = 5000

var priority: Int? = PRIORITY_DEFAULT
set(value) {
field = value
start()
}

var interval: Long? = INTERVAL_DEFAULT
set(value) {
field = value
buildLocationRequest()
}

var minUpdateInterval: Long? = MIN_UPDATE_INTERVAL
set(value) {
field = value
buildLocationRequest()
}

var onUpdate: ((Location) -> Unit)? = null
var onError: ((RNLocationErrorCode) -> Unit)? = null

Expand Down Expand Up @@ -90,6 +109,10 @@ class LocationHandler(

@Suppress("deprecation")
private fun buildLocationRequest() {
val priority = priority ?: Priority.PRIORITY_BALANCED_POWER_ACCURACY
val interval = interval ?: INTERVAL_DEFAULT
val minUpdateInterval = minUpdateInterval ?: MIN_UPDATE_INTERVAL

locationRequest =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
LocationRequest
Expand All @@ -106,21 +129,6 @@ class LocationHandler(
restartLocationUpdates()
}

fun setPriority(priority: Int) {
this.priority = priority
buildLocationRequest()
}

fun setInterval(interval: Int) {
this.interval = interval.toLong()
buildLocationRequest()
}

fun setFastestInterval(fastestInterval: Int) {
this.minUpdateInterval = fastestInterval.toLong()
buildLocationRequest()
}

private fun restartLocationUpdates() {
stop()
// 4) Google Play Services checken – früh zurückmelden
Expand All @@ -146,14 +154,15 @@ class LocationHandler(
}
},
).addOnFailureListener { e ->
val error = mapThrowableToCode(e)
val error = e.toLocationErrorCode(context)
onError?.invoke(error)
}
locationCallback =
object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
val location = locationResult.lastLocation
if (location != null) {
listener?.onLocationChanged(location)
onUpdate?.invoke(location)
} else {
onError?.invoke(RNLocationErrorCode.POSITION_UNAVAILABLE)
Expand All @@ -166,40 +175,31 @@ class LocationHandler(
locationCallback!!,
Looper.getMainLooper(),
).addOnFailureListener { e ->
val error = mapThrowableToCode(e)
val error = e.toLocationErrorCode(context)
onError?.invoke(error)
}
} catch (se: SecurityException) {
onError?.invoke(RNLocationErrorCode.PERMISSION_DENIED)
} catch (ex: Exception) {
val error = mapThrowableToCode(ex)
val error = ex.toLocationErrorCode(context)
onError?.invoke(error)
}
}

private fun mapThrowableToCode(t: Throwable): RNLocationErrorCode {
if (t is SecurityException) return RNLocationErrorCode.PERMISSION_DENIED
if (t.message?.contains("GoogleApi", ignoreCase = true) == true) {
val gms = GoogleApiAvailability.getInstance()
val status = gms.isGooglePlayServicesAvailable(context)
if (status != ConnectionResult.SUCCESS) return RNLocationErrorCode.PLAY_SERVICE_NOT_AVAILABLE
}
if (t is ApiException) {
when (t.statusCode) {
CommonStatusCodes.NETWORK_ERROR -> return RNLocationErrorCode.POSITION_UNAVAILABLE
LocationSettingsStatusCodes.RESOLUTION_REQUIRED,
LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE,
-> return RNLocationErrorCode.SETTINGS_NOT_SATISFIED
}
return RNLocationErrorCode.INTERNAL_ERROR
}
return RNLocationErrorCode.INTERNAL_ERROR
}

fun stop() {
listener = null
if (locationCallback != null) {
fusedLocationClientProviderClient.removeLocationUpdates(locationCallback!!)
locationCallback = null
}
}

override fun activate(listener: LocationSource.OnLocationChangedListener) {
this.listener = listener
start()
}

override fun deactivate() {
stop()
}
}
Loading
Loading