Adapt location feature to updated maplibre-compose API

This commit is contained in:
ganfra
2026-09-04 18:16:20 +02:00
parent 7045465ffe
commit 3754258d88
7 changed files with 217 additions and 134 deletions
@@ -9,11 +9,9 @@
package io.element.android.features.location.impl.common package io.element.android.features.location.impl.common
import android.Manifest import android.Manifest
import androidx.compose.ui.Alignment
import org.maplibre.compose.camera.CameraPosition import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.map.GestureOptions import org.maplibre.compose.map.GestureOptions
import org.maplibre.compose.map.MapOptions import org.maplibre.compose.map.MapOptions
import org.maplibre.compose.map.OrnamentOptions
import org.maplibre.compose.map.RenderOptions import org.maplibre.compose.map.RenderOptions
import org.maplibre.spatialk.geojson.Position import org.maplibre.spatialk.geojson.Position
@@ -24,14 +22,6 @@ object MapDefaults {
val options = MapOptions( val options = MapOptions(
renderOptions = RenderOptions.Standard, renderOptions = RenderOptions.Standard,
gestureOptions = GestureOptions.Standard, gestureOptions = GestureOptions.Standard,
ornamentOptions = OrnamentOptions(
isLogoEnabled = true,
logoAlignment = Alignment.BottomStart,
isAttributionEnabled = true,
attributionAlignment = Alignment.BottomEnd,
isCompassEnabled = false,
isScaleBarEnabled = false,
)
) )
val defaultCameraPosition = CameraPosition( val defaultCameraPosition = CameraPosition(
@@ -7,16 +7,19 @@
package io.element.android.features.location.impl.common.ui package io.element.android.features.location.impl.common.ui
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.only
@@ -32,7 +35,6 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.rememberBottomSheetScaffoldState import androidx.compose.material3.rememberBottomSheetScaffoldState
import androidx.compose.material3.rememberBottomSheetState import androidx.compose.material3.rememberBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@@ -55,6 +57,9 @@ import org.maplibre.compose.camera.CameraState
import org.maplibre.compose.camera.rememberCameraState import org.maplibre.compose.camera.rememberCameraState
import org.maplibre.compose.map.MapOptions import org.maplibre.compose.map.MapOptions
import org.maplibre.compose.map.MaplibreMap import org.maplibre.compose.map.MaplibreMap
import org.maplibre.compose.overlay.ExpandingAttributionButton
import org.maplibre.compose.overlay.MapOverlay
import org.maplibre.compose.overlay.MaplibreLogo
import org.maplibre.compose.style.BaseStyle import org.maplibre.compose.style.BaseStyle
import org.maplibre.compose.util.MaplibreComposable import org.maplibre.compose.util.MaplibreComposable
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -115,10 +120,6 @@ fun MapBottomSheetScaffold(
PaddingValues(bottom = bottomPadding) PaddingValues(bottom = bottomPadding)
} }
} }
// Update camera position when sheet padding changes
LaunchedEffect(sheetPadding) {
cameraState.position = cameraState.position.copy(padding = sheetPadding)
}
BottomSheetScaffold( BottomSheetScaffold(
modifier = Modifier, modifier = Modifier,
sheetPeekHeight = sheetPeekHeight, sheetPeekHeight = sheetPeekHeight,
@@ -135,14 +136,27 @@ fun MapBottomSheetScaffold(
snackbarHost = snackbarHost, snackbarHost = snackbarHost,
topBar = topBar, topBar = topBar,
) { ) {
val ornamentOptions = mapOptions.ornamentOptions.copy(padding = sheetPadding)
val mapOptions = mapOptions.copy(ornamentOptions = ornamentOptions)
Box { Box {
when (customMapTilerConfig) { when (customMapTilerConfig) {
is AsyncData.Success -> { is AsyncData.Success -> {
MaplibreMap( MaplibreMap(
options = mapOptions, options = mapOptions,
cameraPadding = sheetPadding,
contentWindowInsets = WindowInsets(bottom = sheetPadding.calculateBottomPadding()),
baseStyle = BaseStyle.Uri(rememberTileStyleUrl(customMapTilerConfig.data)), baseStyle = BaseStyle.Uri(rememberTileStyleUrl(customMapTilerConfig.data)),
overlay = MapOverlay {
val style = styleState
Row(
Modifier
.align(Alignment.BottomStart)
.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
MaplibreLogo()
ExpandingAttributionButton(cameraState = cameraState, styleState = style)
}
},
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
cameraState = cameraState, cameraState = cameraState,
content = mapContent, content = mapContent,
@@ -7,14 +7,16 @@
package io.element.android.features.location.impl.common.userlocation package io.element.android.features.location.impl.common.userlocation
import android.annotation.SuppressLint
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.RoomScope
import org.maplibre.compose.location.DesiredAccuracy import kotlinx.coroutines.flow.mapNotNull
import org.maplibre.compose.location.rememberNullLocationProvider import org.maplibre.compose.location.LocationAccuracy
import org.maplibre.compose.location.LocationEvent
import org.maplibre.compose.location.LocationRequest
import org.maplibre.spatialk.units.extensions.meters import org.maplibre.spatialk.units.extensions.meters
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@@ -22,17 +24,27 @@ import kotlin.time.Duration.Companion.seconds
class DefaultUserLocationStateFactory : UserLocationState.Factory { class DefaultUserLocationStateFactory : UserLocationState.Factory {
@Composable @Composable
override fun create(hasLocationPermission: Boolean): UserLocationState { override fun create(hasLocationPermission: Boolean): UserLocationState {
val locationProvider = if (!hasLocationPermission) { val locationProvider = if (hasLocationPermission) {
rememberNullLocationProvider() rememberPlatformLocationProvider()
} else { } else {
@SuppressLint("MissingPermission") rememberNotGrantedLocationProvider()
rememberPlatformLocationProvider(
updateInterval = 5.seconds,
desiredAccuracy = DesiredAccuracy.High,
minDistance = 5.meters,
)
} }
val location by locationProvider.location.collectAsState() val locationFlow = remember(locationProvider) {
val locationRequest = LocationRequest(
accuracy = LocationAccuracy.High,
minimumInterval = 5.seconds,
minimumDistance = 5.meters,
)
locationProvider
.updates(locationRequest)
.mapNotNull { event ->
when (event) {
is LocationEvent.Fix -> event.location
is LocationEvent.Unavailable -> null
}
}
}
val location by locationFlow.collectAsState(null)
return UserLocationState(location) return UserLocationState(location)
} }
} }
@@ -0,0 +1,28 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.location.impl.common.userlocation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import org.maplibre.compose.location.LocationEvent
import org.maplibre.compose.location.LocationProvider
import org.maplibre.compose.location.LocationRequest
import org.maplibre.compose.location.LocationUnavailableReason
class NotGrantedLocationProvider : LocationProvider {
override fun updates(request: LocationRequest): Flow<LocationEvent> {
return flowOf(LocationEvent.Unavailable(LocationUnavailableReason.PermissionDenied))
}
}
@Composable
fun rememberNotGrantedLocationProvider(): NotGrantedLocationProvider {
return remember { NotGrantedLocationProvider() }
}
@@ -9,83 +9,126 @@ package io.element.android.features.location.impl.common.userlocation
import android.Manifest import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.location.Location
import android.location.LocationManager import android.location.LocationManager
import android.os.Handler import android.os.Handler
import android.os.HandlerThread import android.os.HandlerThread
import androidx.annotation.RequiresPermission
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.core.content.ContextCompat
import androidx.core.location.LocationListenerCompat import androidx.core.location.LocationListenerCompat
import androidx.core.location.LocationManagerCompat import androidx.core.location.LocationManagerCompat
import androidx.core.location.LocationRequestCompat import androidx.core.location.LocationRequestCompat
import androidx.core.os.ExecutorCompat import androidx.core.os.ExecutorCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.emptyFlow import org.maplibre.compose.location.LocationAccuracy
import kotlinx.coroutines.flow.stateIn import org.maplibre.compose.location.LocationEvent
import org.maplibre.compose.location.DesiredAccuracy
import org.maplibre.compose.location.Location
import org.maplibre.compose.location.LocationProvider import org.maplibre.compose.location.LocationProvider
import org.maplibre.compose.location.PermissionException import org.maplibre.compose.location.LocationRequest
import org.maplibre.compose.location.LocationUnavailableReason
import org.maplibre.compose.location.asMapLibreLocation import org.maplibre.compose.location.asMapLibreLocation
import org.maplibre.spatialk.units.Length
import org.maplibre.spatialk.units.extensions.inMeters import org.maplibre.spatialk.units.extensions.inMeters
import kotlin.time.Duration
@SuppressLint("InlinedApi")
class PlatformLocationProvider( class PlatformLocationProvider(
context: Context, private val context: Context,
private val updateInterval: Duration,
private val minDistance: Length,
private val desiredAccuracy: DesiredAccuracy = DesiredAccuracy.High,
coroutineScope: CoroutineScope,
sharingStarted: SharingStarted = SharingStarted.WhileSubscribed(stopTimeoutMillis = 1000),
) : LocationProvider { ) : LocationProvider {
override val location: StateFlow<Location?>
init { @SuppressLint("MissingPermission")
if (!handlerThread.isAlive) handlerThread.start() override fun updates(request: LocationRequest): Flow<LocationEvent> = callbackFlow {
if ( if (!context.hasLocationPermission()) {
context.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && trySend(LocationEvent.Unavailable(LocationUnavailableReason.PermissionDenied))
context.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED close()
) { return@callbackFlow
throw PermissionException()
} }
val locationManager = context.getSystemService(LocationManager::class.java) val locationManager = context.getSystemService(LocationManager::class.java)
val provider = PROVIDERS_BY_PRIORITY.firstOrNull { LocationManagerCompat.hasProvider(locationManager, it) } val listener = object : LocationListenerCompat {
val locationFlow = if (provider != null) { override fun onLocationChanged(location: Location) {
createProviderFlow(locationManager, provider) trySend(LocationEvent.Fix(location.asMapLibreLocation()))
} else {
emptyFlow()
}
location = locationFlow.stateIn(coroutineScope, sharingStarted, null)
} }
@RequiresPermission( override fun onProviderDisabled(provider: String) {
anyOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION] trySend(LocationEvent.Unavailable(LocationUnavailableReason.ServicesDisabled))
) }
private fun createProviderFlow(locationManager: LocationManager, provider: String) = callbackFlow { }
send(locationManager.getLastKnownLocation(provider)?.asMapLibreLocation()) var registered = false
val listener = LocationListenerCompat { trySend(it.asMapLibreLocation()) }
val request = LocationRequestCompat.Builder(updateInterval.inWholeMilliseconds) fun refreshRegistration() {
.setQuality(desiredAccuracy.toLocationRequestQuality()) if (registered) {
.setMinUpdateDistanceMeters(minDistance.inMeters.toFloat()) LocationManagerCompat.removeUpdates(locationManager, listener)
registered = false
}
if (!LocationManagerCompat.isLocationEnabled(locationManager)) {
trySend(LocationEvent.Unavailable(LocationUnavailableReason.ServicesDisabled))
return
}
val provider = PROVIDERS_BY_PRIORITY.firstOrNull { LocationManagerCompat.hasProvider(locationManager, it) }
if (provider == null) {
trySend(LocationEvent.Unavailable(LocationUnavailableReason.Unsupported))
return
}
locationManager.getLastKnownLocation(provider)?.let { location ->
trySend(LocationEvent.Fix(location.asMapLibreLocation()))
}
val locationRequestCompat = LocationRequestCompat.Builder(request.minimumInterval.inWholeMilliseconds)
.setQuality(request.accuracy.toLocationRequestQuality())
.setMinUpdateDistanceMeters(request.minimumDistance.inMeters.toFloat())
.build() .build()
LocationManagerCompat.requestLocationUpdates( LocationManagerCompat.requestLocationUpdates(
locationManager, locationManager,
provider, provider,
request, locationRequestCompat,
ExecutorCompat.create(Handler(handlerThread.looper)), ExecutorCompat.create(Handler(handlerThread.looper)),
listener, listener,
) )
awaitClose { LocationManagerCompat.removeUpdates(locationManager, listener) } registered = true
}
// Re-select and re-register when the user toggles location settings, so updates resume
// automatically once a usable provider comes back.
val settingsReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
try {
refreshRegistration()
} catch (error: SecurityException) {
trySend(LocationEvent.Unavailable(LocationUnavailableReason.PermissionDenied, error))
close()
} catch (error: IllegalArgumentException) {
trySend(LocationEvent.Unavailable(LocationUnavailableReason.UnexpectedFailure, error))
close()
}
}
}
try {
ContextCompat.registerReceiver(
context,
settingsReceiver,
IntentFilter().apply {
addAction(LocationManager.MODE_CHANGED_ACTION)
addAction(LocationManager.PROVIDERS_CHANGED_ACTION)
},
ContextCompat.RECEIVER_NOT_EXPORTED,
)
refreshRegistration()
} catch (error: SecurityException) {
trySend(LocationEvent.Unavailable(LocationUnavailableReason.PermissionDenied, error))
close()
} catch (error: IllegalArgumentException) {
trySend(LocationEvent.Unavailable(LocationUnavailableReason.UnexpectedFailure, error))
close()
}
awaitClose {
runCatching { context.unregisterReceiver(settingsReceiver) }
LocationManagerCompat.removeUpdates(locationManager, listener)
}
} }
private companion object { private companion object {
@@ -94,36 +137,27 @@ class PlatformLocationProvider(
LocationManager.GPS_PROVIDER, LocationManager.GPS_PROVIDER,
LocationManager.NETWORK_PROVIDER, LocationManager.NETWORK_PROVIDER,
) )
private val handlerThread by lazy { HandlerThread("PlatformLocationProvider") } private val handlerThread by lazy {
HandlerThread("PlatformLocationProvider").apply { start() }
}
} }
} }
private fun DesiredAccuracy.toLocationRequestQuality(): Int = when (this) { private fun Context.hasLocationPermission(): Boolean =
DesiredAccuracy.Highest, DesiredAccuracy.High -> LocationRequestCompat.QUALITY_HIGH_ACCURACY checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) ==
DesiredAccuracy.Balanced -> LocationRequestCompat.QUALITY_BALANCED_POWER_ACCURACY PackageManager.PERMISSION_GRANTED ||
DesiredAccuracy.Low, DesiredAccuracy.Lowest -> LocationRequestCompat.QUALITY_LOW_POWER checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
private fun LocationAccuracy.toLocationRequestQuality(): Int = when (this) {
LocationAccuracy.BestForNavigation, LocationAccuracy.High -> LocationRequestCompat.QUALITY_HIGH_ACCURACY
LocationAccuracy.Balanced -> LocationRequestCompat.QUALITY_BALANCED_POWER_ACCURACY
LocationAccuracy.Low, LocationAccuracy.Lowest -> LocationRequestCompat.QUALITY_LOW_POWER
} }
@Composable @Composable
@RequiresPermission( fun rememberPlatformLocationProvider(context: Context = LocalContext.current): PlatformLocationProvider {
anyOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION] return remember(context) {
) PlatformLocationProvider(context = context)
fun rememberPlatformLocationProvider(
updateInterval: Duration,
minDistance: Length,
desiredAccuracy: DesiredAccuracy = DesiredAccuracy.High,
context: Context = LocalContext.current,
coroutineScope: CoroutineScope = rememberCoroutineScope(),
sharingStarted: SharingStarted = SharingStarted.WhileSubscribed(stopTimeoutMillis = 1000),
): PlatformLocationProvider {
return remember(context, updateInterval, minDistance, desiredAccuracy, coroutineScope, sharingStarted) {
PlatformLocationProvider(
context = context,
updateInterval = updateInterval,
minDistance = minDistance,
desiredAccuracy = desiredAccuracy,
coroutineScope = coroutineScope,
sharingStarted = sharingStarted,
)
} }
} }
@@ -30,15 +30,14 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.maplibre.compose.location.DesiredAccuracy import org.maplibre.compose.location.LocationAccuracy
import org.maplibre.compose.location.PermissionException import org.maplibre.compose.location.LocationEvent
import org.maplibre.compose.location.LocationRequest
import org.maplibre.compose.location.LocationUnavailableReason
import org.maplibre.spatialk.units.extensions.inMeters import org.maplibre.spatialk.units.extensions.inMeters
import org.maplibre.spatialk.units.extensions.meters import org.maplibre.spatialk.units.extensions.meters
import timber.log.Timber import timber.log.Timber
@@ -91,31 +90,38 @@ class LiveLocationSharingService : Service() {
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
private fun startLocationUpdatesListener() { private fun startLocationUpdatesListener() {
Timber.d("LiveLocationSharingService listening to location updates") Timber.d("LiveLocationSharingService listening to location updates")
val locationProvider = PlatformLocationProvider(context = applicationContext)
appPreferencesStore.getLiveLocationMinimumDistanceInMetersUpdateFlow() appPreferencesStore.getLiveLocationMinimumDistanceInMetersUpdateFlow()
.flatMapLatest { minDistanceMeters -> .flatMapLatest { minDistanceMeters ->
try { val locationRequest = LocationRequest(
PlatformLocationProvider( accuracy = LocationAccuracy.Balanced,
context = applicationContext, minimumInterval = UPDATE_INTERVAL_IN_SECOND.seconds,
updateInterval = UPDATE_INTERVAL_IN_SECOND.seconds, minimumDistance = minDistanceMeters.meters
minDistance = minDistanceMeters.meters,
desiredAccuracy = DesiredAccuracy.Balanced,
coroutineScope = coroutineScope
).location
} catch (exception: PermissionException) {
Timber.e(exception, "Failed to create PlatformLocationProvider")
coordinator.dispatchUnrecoverableError()
emptyFlow()
}
}
.filterNotNull()
.map { location ->
ApiLocation(
lat = location.position.value.latitude,
lon = location.position.value.longitude,
accuracy = location.position.accuracy?.inMeters?.toFloat(),
) )
locationProvider.updates(locationRequest)
}
.onEach { locationEvent ->
when (locationEvent) {
is LocationEvent.Fix -> {
val location = ApiLocation(
lat = locationEvent.location.position.value.latitude,
lon = locationEvent.location.position.value.longitude,
accuracy = locationEvent.location.position.accuracy?.inMeters?.toFloat(),
)
coordinator.dispatch(location)
}
is LocationEvent.Unavailable -> {
when (locationEvent.reason) {
LocationUnavailableReason.ServicesDisabled,
LocationUnavailableReason.TemporarilyUnavailable -> Unit
LocationUnavailableReason.Unsupported,
LocationUnavailableReason.Misconfigured,
LocationUnavailableReason.PermissionDenied,
LocationUnavailableReason.UnexpectedFailure -> coordinator.dispatchUnrecoverableError()
}
}
}
} }
.onEach(coordinator::dispatch)
.launchIn(coroutineScope) .launchIn(coroutineScope)
} }
@@ -154,7 +154,6 @@ fun ShowLocationView(
modifier = Modifier.clickable { modifier = Modifier.clickable {
state.eventSink(ShowLocationEvent.TrackMyLocation(false)) state.eventSink(ShowLocationEvent.TrackMyLocation(false))
val position = CameraPosition( val position = CameraPosition(
padding = sheetPaddings,
target = Position(locationShare.location.lon, locationShare.location.lat), target = Position(locationShare.location.lon, locationShare.location.lat),
// Force pointing to NORTH // Force pointing to NORTH
bearing = 0.0, bearing = 0.0,