Merge pull request #7663 from element-hq/feature/fga/maplibre-compose-update

Update maplibre compose to 0.15
This commit is contained in:
ganfra
2026-09-08 17:15:32 +02:00
committed by GitHub
13 changed files with 284 additions and 193 deletions
+5
View File
@@ -288,6 +288,10 @@ dependencies {
implementation(projects.libraries.pushproviders.unifiedpush)
}
// Google Play Services fused location backend. Discovered at runtime via ServiceLoader by the
// location feature.
"gplayImplementation"(libs.maplibre.compose.location.runtime.gms)
implementation(libs.appyx.core)
implementation(libs.androidx.splash)
implementation(libs.androidx.core)
@@ -325,6 +329,7 @@ licensee {
allow("BSD-3-Clause")
allow("EPL-1.0")
allowUrl("https://opensource.org/license/bsd-3-clause")
allowUrl("https://opensource.org/license/bsd-2-clause")
allowUrl("https://opensource.org/licenses/MIT")
allowUrl("https://developer.android.com/studio/terms.html")
allowUrl("https://www.zetetic.net/sqlcipher/license/")
+1
View File
@@ -29,6 +29,7 @@ dependencies {
implementation(projects.features.enterprise.api)
implementation(projects.features.messages.api)
implementation(libs.maplibre.compose)
runtimeOnly(libs.maplibre.compose.runtime.vulkan)
implementation(libs.coil)
implementation(projects.libraries.architecture)
implementation(projects.libraries.matrix.api)
@@ -9,11 +9,9 @@
package io.element.android.features.location.impl.common
import android.Manifest
import androidx.compose.ui.Alignment
import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.map.GestureOptions
import org.maplibre.compose.map.MapOptions
import org.maplibre.compose.map.OrnamentOptions
import org.maplibre.compose.map.RenderOptions
import org.maplibre.spatialk.geojson.Position
@@ -24,14 +22,6 @@ object MapDefaults {
val options = MapOptions(
renderOptions = RenderOptions.Standard,
gestureOptions = GestureOptions.Standard,
ornamentOptions = OrnamentOptions(
isLogoEnabled = true,
logoAlignment = Alignment.BottomStart,
isAttributionEnabled = true,
attributionAlignment = Alignment.BottomEnd,
isCompassEnabled = false,
isScaleBarEnabled = false,
)
)
val defaultCameraPosition = CameraPosition(
@@ -7,16 +7,19 @@
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.BoxScope
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.only
@@ -32,7 +35,6 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.rememberBottomSheetScaffoldState
import androidx.compose.material3.rememberBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
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.map.MapOptions
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.util.MaplibreComposable
import kotlin.math.roundToInt
@@ -115,10 +120,6 @@ fun MapBottomSheetScaffold(
PaddingValues(bottom = bottomPadding)
}
}
// Update camera position when sheet padding changes
LaunchedEffect(sheetPadding) {
cameraState.position = cameraState.position.copy(padding = sheetPadding)
}
BottomSheetScaffold(
modifier = Modifier,
sheetPeekHeight = sheetPeekHeight,
@@ -135,14 +136,27 @@ fun MapBottomSheetScaffold(
snackbarHost = snackbarHost,
topBar = topBar,
) {
val ornamentOptions = mapOptions.ornamentOptions.copy(padding = sheetPadding)
val mapOptions = mapOptions.copy(ornamentOptions = ornamentOptions)
Box {
when (customMapTilerConfig) {
is AsyncData.Success -> {
MaplibreMap(
options = mapOptions,
cameraPadding = sheetPadding,
contentWindowInsets = WindowInsets(bottom = sheetPadding.calculateBottomPadding()),
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(),
cameraState = cameraState,
content = mapContent,
@@ -7,14 +7,16 @@
package io.element.android.features.location.impl.common.userlocation
import android.annotation.SuppressLint
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.di.RoomScope
import org.maplibre.compose.location.DesiredAccuracy
import org.maplibre.compose.location.rememberNullLocationProvider
import kotlinx.coroutines.flow.mapNotNull
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 kotlin.time.Duration.Companion.seconds
@@ -22,17 +24,27 @@ import kotlin.time.Duration.Companion.seconds
class DefaultUserLocationStateFactory : UserLocationState.Factory {
@Composable
override fun create(hasLocationPermission: Boolean): UserLocationState {
val locationProvider = if (!hasLocationPermission) {
rememberNullLocationProvider()
val locationProvider = if (hasLocationPermission) {
rememberLocationProvider()
} else {
@SuppressLint("MissingPermission")
rememberPlatformLocationProvider(
updateInterval = 5.seconds,
desiredAccuracy = DesiredAccuracy.High,
minDistance = 5.meters,
)
rememberNotGrantedLocationProvider()
}
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)
}
}
@@ -0,0 +1,191 @@
/*
* 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 android.Manifest
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.location.Location
import android.location.LocationManager
import android.os.Handler
import android.os.HandlerThread
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.core.content.ContextCompat
import androidx.core.location.LocationListenerCompat
import androidx.core.location.LocationManagerCompat
import androidx.core.location.LocationRequestCompat
import androidx.core.os.ExecutorCompat
import io.element.android.libraries.core.data.tryOrNull
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flowOf
import org.maplibre.compose.location.AndroidLocationBackend
import org.maplibre.compose.location.LocationAccuracy
import org.maplibre.compose.location.LocationEvent
import org.maplibre.compose.location.LocationProvider
import org.maplibre.compose.location.LocationRequest
import org.maplibre.compose.location.LocationUnavailableReason
import org.maplibre.compose.location.asMapLibreLocation
import org.maplibre.spatialk.units.extensions.inMeters
import java.util.ServiceLoader
/**
* Creates the location provider to use.
*
* Mostly mirrors `createDefaultLocationProvider` from maplibre compose: any [AndroidLocationBackend]
* registered through [ServiceLoader] takes precedence, highest [priority]
* first. When no backend is available it falls back to [PlatformLocationProvider].
*/
fun createLocationProvider(context: Context): LocationProvider {
val backend = tryOrNull {
ServiceLoader.load(AndroidLocationBackend::class.java)
.filter { it.isAvailable(context) }
.minWithOrNull(compareByDescending<AndroidLocationBackend> { it.priority }.thenBy { it.id })
}
return backend?.createLocationProvider(context) ?: PlatformLocationProvider(context)
}
@Composable
fun rememberLocationProvider(context: Context = LocalContext.current): LocationProvider {
return remember(context) { createLocationProvider(context) }
}
class PlatformLocationProvider(
private val context: Context,
) : LocationProvider {
@SuppressLint("MissingPermission")
override fun updates(request: LocationRequest): Flow<LocationEvent> = callbackFlow {
if (!context.hasLocationPermission()) {
trySend(LocationEvent.Unavailable(LocationUnavailableReason.PermissionDenied))
close()
return@callbackFlow
}
val locationManager = context.getSystemService(LocationManager::class.java)
val listener = object : LocationListenerCompat {
override fun onLocationChanged(location: Location) {
trySend(LocationEvent.Fix(location.asMapLibreLocation()))
}
override fun onProviderDisabled(provider: String) {
trySend(LocationEvent.Unavailable(LocationUnavailableReason.ServicesDisabled))
}
}
var registered = false
fun refreshRegistration() {
if (registered) {
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()
LocationManagerCompat.requestLocationUpdates(
locationManager,
provider,
locationRequestCompat,
ExecutorCompat.create(Handler(handlerThread.looper)),
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 {
tryOrNull { context.unregisterReceiver(settingsReceiver) }
LocationManagerCompat.removeUpdates(locationManager, listener)
}
}
private companion object {
private val PROVIDERS_BY_PRIORITY = listOf(
LocationManager.FUSED_PROVIDER,
LocationManager.GPS_PROVIDER,
LocationManager.NETWORK_PROVIDER,
)
private val handlerThread by lazy {
HandlerThread("PlatformLocationProvider").apply { start() }
}
}
}
private fun Context.hasLocationPermission(): Boolean =
checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED ||
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
}
class NotGrantedLocationProvider : LocationProvider {
override fun updates(request: LocationRequest): Flow<LocationEvent> {
return flowOf(LocationEvent.Unavailable(LocationUnavailableReason.PermissionDenied))
}
}
@Composable
fun rememberNotGrantedLocationProvider(): NotGrantedLocationProvider {
return remember { NotGrantedLocationProvider() }
}
@@ -1,129 +0,0 @@
/*
* 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 android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.location.LocationManager
import android.os.Handler
import android.os.HandlerThread
import androidx.annotation.RequiresPermission
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
import androidx.core.location.LocationListenerCompat
import androidx.core.location.LocationManagerCompat
import androidx.core.location.LocationRequestCompat
import androidx.core.os.ExecutorCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.stateIn
import org.maplibre.compose.location.DesiredAccuracy
import org.maplibre.compose.location.Location
import org.maplibre.compose.location.LocationProvider
import org.maplibre.compose.location.PermissionException
import org.maplibre.compose.location.asMapLibreLocation
import org.maplibre.spatialk.units.Length
import org.maplibre.spatialk.units.extensions.inMeters
import kotlin.time.Duration
@SuppressLint("InlinedApi")
class PlatformLocationProvider(
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 {
override val location: StateFlow<Location?>
init {
if (!handlerThread.isAlive) handlerThread.start()
if (
context.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
context.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED
) {
throw PermissionException()
}
val locationManager = context.getSystemService(LocationManager::class.java)
val provider = PROVIDERS_BY_PRIORITY.firstOrNull { LocationManagerCompat.hasProvider(locationManager, it) }
val locationFlow = if (provider != null) {
createProviderFlow(locationManager, provider)
} else {
emptyFlow()
}
location = locationFlow.stateIn(coroutineScope, sharingStarted, null)
}
@RequiresPermission(
anyOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION]
)
private fun createProviderFlow(locationManager: LocationManager, provider: String) = callbackFlow {
send(locationManager.getLastKnownLocation(provider)?.asMapLibreLocation())
val listener = LocationListenerCompat { trySend(it.asMapLibreLocation()) }
val request = LocationRequestCompat.Builder(updateInterval.inWholeMilliseconds)
.setQuality(desiredAccuracy.toLocationRequestQuality())
.setMinUpdateDistanceMeters(minDistance.inMeters.toFloat())
.build()
LocationManagerCompat.requestLocationUpdates(
locationManager,
provider,
request,
ExecutorCompat.create(Handler(handlerThread.looper)),
listener,
)
awaitClose { LocationManagerCompat.removeUpdates(locationManager, listener) }
}
private companion object {
private val PROVIDERS_BY_PRIORITY = listOf(
LocationManager.FUSED_PROVIDER,
LocationManager.GPS_PROVIDER,
LocationManager.NETWORK_PROVIDER,
)
private val handlerThread by lazy { HandlerThread("PlatformLocationProvider") }
}
}
private fun DesiredAccuracy.toLocationRequestQuality(): Int = when (this) {
DesiredAccuracy.Highest, DesiredAccuracy.High -> LocationRequestCompat.QUALITY_HIGH_ACCURACY
DesiredAccuracy.Balanced -> LocationRequestCompat.QUALITY_BALANCED_POWER_ACCURACY
DesiredAccuracy.Low, DesiredAccuracy.Lowest -> LocationRequestCompat.QUALITY_LOW_POWER
}
@Composable
@RequiresPermission(
anyOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION]
)
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,
)
}
}
@@ -8,8 +8,10 @@
package io.element.android.features.location.impl.common.userlocation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import org.maplibre.compose.location.Location
@Immutable
data class UserLocationState(val location: Location?) {
fun interface Factory {
@Composable
@@ -14,7 +14,7 @@ import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION
import android.os.IBinder
import androidx.core.app.ServiceCompat
import dev.zacsweers.metro.Inject
import io.element.android.features.location.impl.common.userlocation.PlatformLocationProvider
import io.element.android.features.location.impl.common.userlocation.createLocationProvider
import io.element.android.features.location.impl.di.LocationBindings
import io.element.android.features.location.impl.live.notification.LiveLocationSharingNotificationCreator
import io.element.android.libraries.architecture.bindings
@@ -30,15 +30,14 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.maplibre.compose.location.DesiredAccuracy
import org.maplibre.compose.location.PermissionException
import org.maplibre.compose.location.LocationAccuracy
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.meters
import timber.log.Timber
@@ -91,31 +90,38 @@ class LiveLocationSharingService : Service() {
@OptIn(ExperimentalCoroutinesApi::class)
private fun startLocationUpdatesListener() {
Timber.d("LiveLocationSharingService listening to location updates")
val locationProvider = createLocationProvider(context = applicationContext)
appPreferencesStore.getLiveLocationMinimumDistanceInMetersUpdateFlow()
.flatMapLatest { minDistanceMeters ->
try {
PlatformLocationProvider(
context = applicationContext,
updateInterval = UPDATE_INTERVAL_IN_SECOND.seconds,
minDistance = minDistanceMeters.meters,
desiredAccuracy = DesiredAccuracy.Balanced,
coroutineScope = coroutineScope
).location
} catch (exception: PermissionException) {
Timber.e(exception, "Failed to create PlatformLocationProvider")
coordinator.dispatchUnrecoverableError()
emptyFlow()
val locationRequest = LocationRequest(
accuracy = LocationAccuracy.Balanced,
minimumInterval = UPDATE_INTERVAL_IN_SECOND.seconds,
minimumDistance = minDistanceMeters.meters
)
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()
}
}
}
}
.filterNotNull()
.map { location ->
ApiLocation(
lat = location.position.value.latitude,
lon = location.position.value.longitude,
accuracy = location.position.accuracy?.inMeters?.toFloat(),
)
}
.onEach(coordinator::dispatch)
.launchIn(coroutineScope)
}
@@ -107,6 +107,7 @@ fun ShareLocationView(
val scaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = rememberBottomSheetState(
initialValue = SheetValue.Expanded,
enabledValues = setOf(SheetValue.PartiallyExpanded, SheetValue.Expanded),
)
)
val cameraState = rememberCameraState(firstPosition = MapDefaults.defaultCameraPosition)
@@ -94,6 +94,7 @@ fun ShowLocationView(
val scaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = rememberBottomSheetState(
initialValue = SheetValue.Expanded,
enabledValues = setOf(SheetValue.PartiallyExpanded, SheetValue.Expanded),
)
)
LaunchedEffect(state.isSheetDraggable) {
@@ -154,7 +155,6 @@ fun ShowLocationView(
modifier = Modifier.clickable {
state.eventSink(ShowLocationEvent.TrackMyLocation(false))
val position = CameraPosition(
padding = sheetPaddings,
target = Position(locationShare.location.lon, locationShare.location.lat),
// Force pointing to NORTH
bearing = 0.0,
+4 -4
View File
@@ -49,6 +49,7 @@ sqldelight = "2.3.2"
wysiwyg = "2.42.1"
telephoto = "0.19.0"
haze = "1.7.2"
maplibre-compose = "0.15.0"
# Dependency analysis
dependencyAnalysis = "3.19.1"
@@ -215,10 +216,9 @@ vanniktech_blurhash = "com.vanniktech:blurhash:0.3.0"
telephoto_zoomableimage = { module = "me.saket.telephoto:zoomable-image-coil3", version.ref = "telephoto" }
telephoto_flick = { module = "me.saket.telephoto:flick-android", version.ref = "telephoto" }
statemachine = "com.freeletics.flowredux:compose:1.2.2"
maplibre = "org.maplibre.gl:android-sdk:13.5.0"
maplibre_ktx = "org.maplibre.gl:android-sdk-ktx-v7:3.0.2"
maplibre_compose = "org.maplibre.compose:maplibre-compose:0.14.0"
maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:3.0.2"
maplibre_compose = { module = "org.maplibre.compose:maplibre-compose", version.ref = "maplibre-compose" }
maplibre_compose_runtime_vulkan = { module = "org.maplibre.compose:maplibre-compose-runtime-vulkan-android", version.ref = "maplibre-compose" }
maplibre_compose_location_runtime_gms = { module = "org.maplibre.compose:location-runtime-gms", version.ref = "maplibre-compose" }
opusencoder = "io.element.android:opusencoder:1.2.0"
zxing_cpp = "io.github.zxing-cpp:android:3.1.1"
google_zxing = "com.google.zxing:core:3.5.4"
+1 -3
View File
@@ -18,7 +18,6 @@ uses-permission: name='android.permission.USE_FULL_SCREEN_INTENT'
uses-permission: name='android.permission.ACCESS_NETWORK_STATE'
uses-permission: name='android.permission.VIBRATE'
uses-permission: name='android.permission.ACCESS_LOCAL_NETWORK'
uses-permission: name='android.permission.ACCESS_WIFI_STATE'
uses-permission: name='android.permission.USE_BIOMETRIC'
uses-permission: name='android.permission.USE_FINGERPRINT'
uses-permission: name='com.google.android.c2dm.permission.RECEIVE'
@@ -81,8 +80,7 @@ uses-library-not-required:'androidx.window.sidecar'
feature-group: label=''
uses-feature-not-required: name='android.hardware.camera'
uses-feature-not-required: name='android.hardware.microphone'
uses-feature-not-required: name='android.hardware.vulkan.version' version='4194307'
uses-feature-not-required: name='android.hardware.wifi'
uses-feature-not-required: name='android.hardware.vulkan.version'
uses-feature: name='android.hardware.faketouch'
uses-implied-feature: name='android.hardware.faketouch' reason='default feature for all apps'
uses-feature: name='android.hardware.location'