Disable location sharing when no MapTiler config is available

LocationService now exposes the MapTiler config to use: the one provided by the
homeserver takes precedence over the build time one, and null means that no map
provider is configured. It is now session scoped, and isServiceAvailable()
returns a StateFlow derived from this config.

When no config is available:
- the location sharing option is hidden from the composer, and location events
  cannot be opened in the location viewer;
- the "Location sharing" entry is hidden from the settings;
- location events in the timeline render a placeholder, without performing any
  request to MapTiler.

The show and share location screens now get the config from LocationService
rather than fetching the homeserver config by themselves.

Closes element-hq/element-android-enterprise#69

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benoit Marty
2026-10-13 17:15:37 +02:00
parent 5104e044ff
commit 4b97d6ab96
28 changed files with 212 additions and 106 deletions
@@ -11,13 +11,22 @@ import androidx.compose.runtime.staticCompositionLocalOf
import io.element.android.features.enterprise.api.remoteconfig.MapTilerConfig
/**
* Provides a [MapTilerConfig] for the MapLibre maps.
* Provides the [MapTilerConfig] for the MapLibre maps, or null if no map provider is configured.
*/
val LocalMapTilerConfig = staticCompositionLocalOf {
MapTilerConfig(
apiKey = BuildConfig.MAPTILER_API_KEY,
lightStyleId = BuildConfig.MAPTILER_LIGHT_MAP_ID,
darkStyleId = BuildConfig.MAPTILER_DARK_MAP_ID,
baseUrl = BuildConfig.MAPTILER_BASE_URL
)
val LocalMapTilerConfig = staticCompositionLocalOf { buildTimeMapTilerConfig() }
/**
* The [MapTilerConfig] defined at build time, or null if no MapTiler API key has been provided.
*/
fun buildTimeMapTilerConfig(): MapTilerConfig? {
return BuildConfig.MAPTILER_API_KEY
.takeIf { it.isNotEmpty() }
?.let { apiKey ->
MapTilerConfig(
apiKey = apiKey,
lightStyleId = BuildConfig.MAPTILER_LIGHT_MAP_ID,
darkStyleId = BuildConfig.MAPTILER_DARK_MAP_ID,
baseUrl = BuildConfig.MAPTILER_BASE_URL
)
}
}
@@ -8,10 +8,21 @@
package io.element.android.features.location.api
import io.element.android.features.enterprise.api.remoteconfig.MapTilerConfig
import io.element.android.libraries.core.coroutine.mapState
import kotlinx.coroutines.flow.StateFlow
/**
* Tells whether the location features can be used at all, which depends on the build having a map provider configured.
* Tells whether the location features can be used at all, which depends on a map provider being configured,
* either by the homeserver or at build time.
*/
interface LocationService {
/**
* The [MapTilerConfig] to use to render the maps, or null if no map provider is configured.
* The configuration provided by the homeserver takes precedence over the one defined at build time.
*/
val mapTilerConfig: StateFlow<MapTilerConfig?>
/** Whether location sharing and map rendering are available; `false` disables the whole feature in the UI. */
fun isServiceAvailable(): Boolean
fun isServiceAvailable(): StateFlow<Boolean> = mapTilerConfig.mapState { it != null }
}
@@ -50,11 +50,12 @@ import io.element.android.libraries.designsystem.utils.CommonDrawables
/**
* Shows a static map image downloaded via a third party service's static maps API.
*
* Handles 4 distinct cases:
* Handles 5 distinct cases:
* 1. Stale location (pinVariant is StaleLocation) - shows stale map with stale pin, no fetching
* 2. Null location - shows blurred placeholder, no pin, no loading
* 3. Loading (location != null, fetching) - shows blurred placeholder with loading indicator
* 4. Success (location != null, loaded) - shows actual map with pin
* 3. Null mapTilerConfig (no map provider configured) - shows blurred placeholder, no pin, no fetching
* 4. Loading (location != null, fetching) - shows blurred placeholder with loading indicator
* 5. Success (location != null, loaded) - shows actual map with pin
*/
@Composable
fun StaticMapView(
@@ -84,7 +85,8 @@ fun StaticMapView(
)
}
// Case 2: Null location - show blurred placeholder, no pin, no loading
location == null -> {
// Case 3: No map provider configured - show blurred placeholder, no pin, no fetching
location == null || mapTilerConfig == null && !LocalInspectionMode.current -> {
StaticMapPlaceholder(
painter = painterResource(R.drawable.blurred_map),
canReload = false,
@@ -94,7 +96,7 @@ fun StaticMapView(
onLoadMapClick = {}
)
}
// Cases 3 & 4: Non-null location - fetch map
// Cases 4 & 5: Non-null location - fetch map
else -> LoadableMapContent(
mapTilerConfig = mapTilerConfig,
location = location,