mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-03 16:10:00 +00:00
Support Google Play Services fused location backend
This commit is contained in:
@@ -288,6 +288,10 @@ dependencies {
|
|||||||
implementation(projects.libraries.pushproviders.unifiedpush)
|
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.appyx.core)
|
||||||
implementation(libs.androidx.splash)
|
implementation(libs.androidx.splash)
|
||||||
implementation(libs.androidx.core)
|
implementation(libs.androidx.core)
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@ 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) {
|
||||||
rememberPlatformLocationProvider()
|
rememberLocationProvider()
|
||||||
} else {
|
} else {
|
||||||
rememberNotGrantedLocationProvider()
|
rememberNotGrantedLocationProvider()
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-6
@@ -26,9 +26,12 @@ 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 io.element.android.libraries.core.data.tryOrNull
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
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.LocationAccuracy
|
||||||
import org.maplibre.compose.location.LocationEvent
|
import org.maplibre.compose.location.LocationEvent
|
||||||
import org.maplibre.compose.location.LocationProvider
|
import org.maplibre.compose.location.LocationProvider
|
||||||
@@ -36,11 +39,33 @@ import org.maplibre.compose.location.LocationRequest
|
|||||||
import org.maplibre.compose.location.LocationUnavailableReason
|
import org.maplibre.compose.location.LocationUnavailableReason
|
||||||
import org.maplibre.compose.location.asMapLibreLocation
|
import org.maplibre.compose.location.asMapLibreLocation
|
||||||
import org.maplibre.spatialk.units.extensions.inMeters
|
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(
|
class PlatformLocationProvider(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
) : LocationProvider {
|
) : LocationProvider {
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
override fun updates(request: LocationRequest): Flow<LocationEvent> = callbackFlow {
|
override fun updates(request: LocationRequest): Flow<LocationEvent> = callbackFlow {
|
||||||
if (!context.hasLocationPermission()) {
|
if (!context.hasLocationPermission()) {
|
||||||
@@ -58,6 +83,7 @@ class PlatformLocationProvider(
|
|||||||
trySend(LocationEvent.Unavailable(LocationUnavailableReason.ServicesDisabled))
|
trySend(LocationEvent.Unavailable(LocationUnavailableReason.ServicesDisabled))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var registered = false
|
var registered = false
|
||||||
|
|
||||||
fun refreshRegistration() {
|
fun refreshRegistration() {
|
||||||
@@ -126,7 +152,7 @@ class PlatformLocationProvider(
|
|||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
awaitClose {
|
awaitClose {
|
||||||
runCatching { context.unregisterReceiver(settingsReceiver) }
|
tryOrNull { context.unregisterReceiver(settingsReceiver) }
|
||||||
LocationManagerCompat.removeUpdates(locationManager, listener)
|
LocationManagerCompat.removeUpdates(locationManager, listener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -155,9 +181,13 @@ private fun LocationAccuracy.toLocationRequestQuality(): Int = when (this) {
|
|||||||
LocationAccuracy.Low, LocationAccuracy.Lowest -> LocationRequestCompat.QUALITY_LOW_POWER
|
LocationAccuracy.Low, LocationAccuracy.Lowest -> LocationRequestCompat.QUALITY_LOW_POWER
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
class NotGrantedLocationProvider : LocationProvider {
|
||||||
fun rememberPlatformLocationProvider(context: Context = LocalContext.current): PlatformLocationProvider {
|
override fun updates(request: LocationRequest): Flow<LocationEvent> {
|
||||||
return remember(context) {
|
return flowOf(LocationEvent.Unavailable(LocationUnavailableReason.PermissionDenied))
|
||||||
PlatformLocationProvider(context = context)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@Composable
|
||||||
|
|
||||||
|
fun rememberNotGrantedLocationProvider(): NotGrantedLocationProvider {
|
||||||
|
return remember { NotGrantedLocationProvider() }
|
||||||
|
}
|
||||||
-28
@@ -1,28 +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 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() }
|
|
||||||
}
|
|
||||||
+2
-2
@@ -14,7 +14,7 @@ import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION
|
|||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import androidx.core.app.ServiceCompat
|
import androidx.core.app.ServiceCompat
|
||||||
import dev.zacsweers.metro.Inject
|
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.di.LocationBindings
|
||||||
import io.element.android.features.location.impl.live.notification.LiveLocationSharingNotificationCreator
|
import io.element.android.features.location.impl.live.notification.LiveLocationSharingNotificationCreator
|
||||||
import io.element.android.libraries.architecture.bindings
|
import io.element.android.libraries.architecture.bindings
|
||||||
@@ -90,7 +90,7 @@ 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)
|
val locationProvider = createLocationProvider(context = applicationContext)
|
||||||
appPreferencesStore.getLiveLocationMinimumDistanceInMetersUpdateFlow()
|
appPreferencesStore.getLiveLocationMinimumDistanceInMetersUpdateFlow()
|
||||||
.flatMapLatest { minDistanceMeters ->
|
.flatMapLatest { minDistanceMeters ->
|
||||||
val locationRequest = LocationRequest(
|
val locationRequest = LocationRequest(
|
||||||
|
|||||||
@@ -218,6 +218,7 @@ telephoto_flick = { module = "me.saket.telephoto:flick-android", version.ref = "
|
|||||||
statemachine = "com.freeletics.flowredux:compose:1.2.2"
|
statemachine = "com.freeletics.flowredux:compose:1.2.2"
|
||||||
maplibre_compose = { module = "org.maplibre.compose:maplibre-compose", version.ref = "maplibre-compose" }
|
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_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"
|
opusencoder = "io.element.android:opusencoder:1.2.0"
|
||||||
zxing_cpp = "io.github.zxing-cpp:android:3.1.1"
|
zxing_cpp = "io.github.zxing-cpp:android:3.1.1"
|
||||||
google_zxing = "com.google.zxing:core:3.5.4"
|
google_zxing = "com.google.zxing:core:3.5.4"
|
||||||
|
|||||||
Reference in New Issue
Block a user