Move the custom tile server URL config to the Element wellknown content (#7416)

* Move the custom tile server URL config to the Element wellknown content

* Iteration: use whole `MapTilerConfig` in the remote config
This commit is contained in:
Jorge Martin Espinosa
2026-08-14 08:20:37 +02:00
committed by GitHub
parent ddbc6a2a33
commit 22195a47f2
35 changed files with 279 additions and 117 deletions
+1
View File
@@ -44,6 +44,7 @@ dependencies {
implementation(projects.libraries.sessionStorage.api) implementation(projects.libraries.sessionStorage.api)
implementation(projects.libraries.uiCommon) implementation(projects.libraries.uiCommon)
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
implementation(projects.libraries.wellknown.api)
implementation(projects.features.login.api) implementation(projects.features.login.api)
implementation(libs.coil) implementation(libs.coil)
@@ -11,8 +11,10 @@ package io.element.android.appnav
import android.os.Parcelable import android.os.Parcelable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@@ -49,11 +51,13 @@ import io.element.android.compound.colors.SemanticColorsLightDark
import io.element.android.features.createroom.api.CreateRoomEntryPoint import io.element.android.features.createroom.api.CreateRoomEntryPoint
import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.api.EnterpriseService
import io.element.android.features.enterprise.api.SessionEnterpriseService import io.element.android.features.enterprise.api.SessionEnterpriseService
import io.element.android.features.enterprise.api.remoteconfig.RemoteEnterpriseConfigProvider
import io.element.android.features.ftue.api.FtueEntryPoint import io.element.android.features.ftue.api.FtueEntryPoint
import io.element.android.features.ftue.api.state.FtueService import io.element.android.features.ftue.api.state.FtueService
import io.element.android.features.ftue.api.state.FtueState import io.element.android.features.ftue.api.state.FtueState
import io.element.android.features.home.api.HomeEntryPoint import io.element.android.features.home.api.HomeEntryPoint
import io.element.android.features.linknewdevice.api.LinkNewDeviceEntryPoint import io.element.android.features.linknewdevice.api.LinkNewDeviceEntryPoint
import io.element.android.features.location.api.LocalMapTilerConfig
import io.element.android.features.location.api.live.ActiveLiveLocationShareManager import io.element.android.features.location.api.live.ActiveLiveLocationShareManager
import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus import io.element.android.features.networkmonitor.api.NetworkStatus
@@ -153,6 +157,7 @@ class LoggedInFlowNode(
private val analyticsRoomListStateWatcher: AnalyticsRoomListStateWatcher, private val analyticsRoomListStateWatcher: AnalyticsRoomListStateWatcher,
private val createRoomEntryPoint: CreateRoomEntryPoint, private val createRoomEntryPoint: CreateRoomEntryPoint,
private val activeLiveLocationShareManager: ActiveLiveLocationShareManager, private val activeLiveLocationShareManager: ActiveLiveLocationShareManager,
private val remoteEnterpriseConfigProvider: RemoteEnterpriseConfigProvider,
) : BaseFlowNode<LoggedInFlowNode.NavTarget>( ) : BaseFlowNode<LoggedInFlowNode.NavTarget>(
backstack = BackStack( backstack = BackStack(
initialElement = NavTarget.Placeholder, initialElement = NavTarget.Placeholder,
@@ -677,6 +682,12 @@ class LoggedInFlowNode(
val colors by remember { val colors by remember {
enterpriseService.semanticColorsFlow(sessionId = matrixClient.sessionId) enterpriseService.semanticColorsFlow(sessionId = matrixClient.sessionId)
}.collectAsState(SemanticColorsLightDark.default) }.collectAsState(SemanticColorsLightDark.default)
val currentMapTilerConfig = LocalMapTilerConfig.current
val updatedMapTilerConfig by produceState(currentMapTilerConfig) {
value = remoteEnterpriseConfigProvider.get(matrixClient.sessionId).dataOrNull()?.mapTilerConfig ?: currentMapTilerConfig
}
ElementThemeApp( ElementThemeApp(
appPreferencesStore = appPreferencesStore, appPreferencesStore = appPreferencesStore,
featureFlagService = featureFlagService, featureFlagService = featureFlagService,
@@ -684,16 +695,18 @@ class LoggedInFlowNode(
compoundDark = colors.dark, compoundDark = colors.dark,
buildMeta = buildMeta, buildMeta = buildMeta,
) { ) {
val isOnline by syncService.isOnline.collectAsState() CompositionLocalProvider(LocalMapTilerConfig provides updatedMapTilerConfig) {
ConnectivityIndicatorContainer( val isOnline by syncService.isOnline.collectAsState()
isOnline = isOnline, ConnectivityIndicatorContainer(
modifier = modifier, isOnline = isOnline,
) { contentModifier -> modifier = modifier,
Box(modifier = contentModifier) { ) { contentModifier ->
val ftueState by ftueService.state.collectAsState() Box(modifier = contentModifier) {
BackstackView(transitionHandler = rememberLoggedInFlowTransitionHandler(backstack)) val ftueState by ftueService.state.collectAsState()
if (ftueState is FtueState.Complete) { BackstackView(transitionHandler = rememberLoggedInFlowTransitionHandler(backstack))
PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LoggedInPermanent) if (ftueState is FtueState.Complete) {
PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LoggedInPermanent)
}
} }
} }
} }
+1
View File
@@ -70,6 +70,7 @@ dependencies {
implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrix.api)
implementation(projects.libraries.matrixui) implementation(projects.libraries.matrixui)
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
implementation(projects.libraries.wellknown.api)
implementation(libs.coil.compose) implementation(libs.coil.compose)
implementation(libs.datetime) implementation(libs.datetime)
@@ -0,0 +1,23 @@
/*
* 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.api
import androidx.compose.runtime.staticCompositionLocalOf
import io.element.android.libraries.wellknown.api.MapTilerConfig
/**
* Provides a [MapTilerConfig] for the MapLibre maps.
*/
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
)
}
@@ -38,13 +38,14 @@ import coil3.request.ImageRequest
import coil3.request.SuccessResult import coil3.request.SuccessResult
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.features.location.api.internal.StaticMapPlaceholder import io.element.android.features.location.api.internal.StaticMapPlaceholder
import io.element.android.features.location.api.internal.StaticMapUrlBuilder
import io.element.android.features.location.api.internal.centerBottomEdge import io.element.android.features.location.api.internal.centerBottomEdge
import io.element.android.features.location.api.internal.rememberStaticMapBuilder
import io.element.android.libraries.designsystem.components.LocationPin import io.element.android.libraries.designsystem.components.LocationPin
import io.element.android.libraries.designsystem.components.PinVariant import io.element.android.libraries.designsystem.components.PinVariant
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.utils.CommonDrawables import io.element.android.libraries.designsystem.utils.CommonDrawables
import io.element.android.libraries.wellknown.api.MapTilerConfig
/** /**
* Shows a static map image downloaded via a third party service's static maps API. * Shows a static map image downloaded via a third party service's static maps API.
@@ -57,6 +58,7 @@ import io.element.android.libraries.designsystem.utils.CommonDrawables
*/ */
@Composable @Composable
fun StaticMapView( fun StaticMapView(
mapTilerConfig: MapTilerConfig?,
location: Location?, location: Location?,
zoom: Double, zoom: Double,
pinVariant: PinVariant, pinVariant: PinVariant,
@@ -94,6 +96,7 @@ fun StaticMapView(
} }
// Cases 3 & 4: Non-null location - fetch map // Cases 3 & 4: Non-null location - fetch map
else -> LoadableMapContent( else -> LoadableMapContent(
mapTilerConfig = mapTilerConfig,
location = location, location = location,
zoom = zoom, zoom = zoom,
pinVariant = pinVariant, pinVariant = pinVariant,
@@ -124,6 +127,7 @@ private fun BoxWithConstraintsScope.StaleMapContent(
@Composable @Composable
private fun BoxWithConstraintsScope.LoadableMapContent( private fun BoxWithConstraintsScope.LoadableMapContent(
mapTilerConfig: MapTilerConfig?,
location: Location, location: Location,
zoom: Double, zoom: Double,
pinVariant: PinVariant, pinVariant: PinVariant,
@@ -132,7 +136,7 @@ private fun BoxWithConstraintsScope.LoadableMapContent(
) { ) {
val context = LocalContext.current val context = LocalContext.current
var retryHash by remember { mutableIntStateOf(0) } var retryHash by remember { mutableIntStateOf(0) }
val builder = remember { StaticMapUrlBuilder() } val builder = rememberStaticMapBuilder(mapTilerConfig)
val (painter, state, contentScale) = if (LocalInspectionMode.current) { val (painter, state, contentScale) = if (LocalInspectionMode.current) {
val painter = painterResource(CommonDrawables.sample_map) val painter = painterResource(CommonDrawables.sample_map)
@@ -203,6 +207,7 @@ private fun BoxWithConstraintsScope.LoadableMapContent(
@Composable @Composable
internal fun StaticMapViewPreview() = ElementPreview { internal fun StaticMapViewPreview() = ElementPreview {
StaticMapView( StaticMapView(
mapTilerConfig = null,
location = Location(0.0, 0.0), location = Location(0.0, 0.0),
zoom = 0.0, zoom = 0.0,
contentDescription = null, contentDescription = null,
@@ -9,6 +9,7 @@
package io.element.android.features.location.api.internal package io.element.android.features.location.api.internal
import io.element.android.features.location.api.BuildConfig import io.element.android.features.location.api.BuildConfig
import io.element.android.libraries.wellknown.api.MapTilerConfig
import kotlin.math.roundToInt import kotlin.math.roundToInt
/** /**
@@ -29,6 +30,13 @@ internal class MapTilerStaticMapUrlBuilder(
darkMapId = BuildConfig.MAPTILER_DARK_MAP_ID, darkMapId = BuildConfig.MAPTILER_DARK_MAP_ID,
) )
constructor(mapTilerConfig: MapTilerConfig) : this(
baseUrl = (mapTilerConfig.baseUrl ?: BuildConfig.MAPTILER_BASE_URL).removeSuffix("/"),
apiKey = mapTilerConfig.apiKey.takeIf { it.isNotBlank() } ?: BuildConfig.MAPTILER_API_KEY,
lightMapId = mapTilerConfig.lightStyleId ?: BuildConfig.MAPTILER_LIGHT_MAP_ID,
darkMapId = mapTilerConfig.darkStyleId ?: BuildConfig.MAPTILER_DARK_MAP_ID,
)
override fun build( override fun build(
lat: Double, lat: Double,
lon: Double, lon: Double,
@@ -11,6 +11,7 @@
package io.element.android.features.location.api.internal package io.element.android.features.location.api.internal
import io.element.android.features.location.api.BuildConfig import io.element.android.features.location.api.BuildConfig
import io.element.android.libraries.wellknown.api.MapTilerConfig
internal class MapTilerTileServerStyleUriBuilder( internal class MapTilerTileServerStyleUriBuilder(
private val baseUrl: String, private val baseUrl: String,
@@ -26,14 +27,17 @@ internal class MapTilerTileServerStyleUriBuilder(
) )
override fun build( override fun build(
customMapStyleUrl: String?, customMapTilerConfig: MapTilerConfig?,
darkMode: Boolean, darkMode: Boolean,
): String { ): String {
return if (customMapStyleUrl.isNullOrBlank()) { val baseUrl = customMapTilerConfig?.baseUrl.takeIf { !it.isNullOrBlank() } ?: baseUrl
val mapId = if (darkMode) darkMapId else lightMapId val apiKey = customMapTilerConfig?.apiKey ?: apiKey
"$baseUrl/$mapId/style.json?key=$apiKey" val mapId = if (darkMode) {
customMapTilerConfig?.darkStyleId ?: darkMapId
} else { } else {
customMapStyleUrl customMapTilerConfig?.lightStyleId ?: lightMapId
} }
return "$baseUrl/$mapId/style.json?key=$apiKey"
} }
} }
@@ -8,6 +8,10 @@
package io.element.android.features.location.api.internal package io.element.android.features.location.api.internal
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import io.element.android.libraries.wellknown.api.MapTilerConfig
/** /**
* Builds an URL for a 3rd party service provider static maps API. * Builds an URL for a 3rd party service provider static maps API.
*/ */
@@ -25,4 +29,13 @@ interface StaticMapUrlBuilder {
fun isServiceAvailable(): Boolean fun isServiceAvailable(): Boolean
} }
fun StaticMapUrlBuilder(): StaticMapUrlBuilder = MapTilerStaticMapUrlBuilder() @Composable
fun rememberStaticMapBuilder(mapTilerConfig: MapTilerConfig?): StaticMapUrlBuilder {
return remember(mapTilerConfig) {
if (mapTilerConfig != null) {
MapTilerStaticMapUrlBuilder(mapTilerConfig)
} else {
MapTilerStaticMapUrlBuilder()
}
}
}
@@ -11,6 +11,7 @@ package io.element.android.features.location.api.internal
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.wellknown.api.MapTilerConfig
/** /**
* Builds a style URI for a MapLibre compatible tile server. * Builds a style URI for a MapLibre compatible tile server.
@@ -19,7 +20,7 @@ import io.element.android.compound.theme.ElementTheme
*/ */
interface TileServerStyleUriBuilder { interface TileServerStyleUriBuilder {
fun build( fun build(
customMapStyleUrl: String?, customMapTilerConfig: MapTilerConfig?,
darkMode: Boolean, darkMode: Boolean,
): String ): String
} }
@@ -31,12 +32,12 @@ interface TileServerStyleUriBuilder {
*/ */
@Composable @Composable
fun rememberTileStyleUrl( fun rememberTileStyleUrl(
customMapStyleUrl: String?, customMapTileConfig: MapTilerConfig?,
): String { ): String {
val darkMode = !ElementTheme.isLightTheme val darkMode = !ElementTheme.isLightTheme
return remember(darkMode, customMapStyleUrl) { return remember(darkMode, customMapTileConfig) {
MapTilerTileServerStyleUriBuilder().build( MapTilerTileServerStyleUriBuilder().build(
customMapStyleUrl = customMapStyleUrl, customMapTilerConfig = customMapTileConfig,
darkMode = darkMode, darkMode = darkMode,
) )
} }
@@ -9,6 +9,7 @@
package io.element.android.features.location.api.internal package io.element.android.features.location.api.internal
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.wellknown.api.MapTilerConfig
import org.junit.Test import org.junit.Test
class MapTilerTileServerStyleUriBuilderTest { class MapTilerTileServerStyleUriBuilderTest {
@@ -23,7 +24,7 @@ class MapTilerTileServerStyleUriBuilderTest {
fun `light map uri`() { fun `light map uri`() {
assertThat( assertThat(
builder.build( builder.build(
customMapStyleUrl = null, customMapTilerConfig = null,
darkMode = false, darkMode = false,
) )
).isEqualTo("https://base.url/aLightMapId/style.json?key=anApiKey") ).isEqualTo("https://base.url/aLightMapId/style.json?key=anApiKey")
@@ -33,7 +34,7 @@ class MapTilerTileServerStyleUriBuilderTest {
fun `dark map uri`() { fun `dark map uri`() {
assertThat( assertThat(
builder.build( builder.build(
customMapStyleUrl = null, customMapTilerConfig = null,
darkMode = true, darkMode = true,
) )
).isEqualTo("https://base.url/aDarkMapId/style.json?key=anApiKey") ).isEqualTo("https://base.url/aDarkMapId/style.json?key=anApiKey")
@@ -43,49 +44,44 @@ class MapTilerTileServerStyleUriBuilderTest {
fun `custom map uri light`() { fun `custom map uri light`() {
assertThat( assertThat(
builder.build( builder.build(
customMapStyleUrl = "https://custom.url/style.json", customMapTilerConfig = MapTilerConfig(
apiKey = "API_KEY",
lightStyleId = "light",
darkStyleId = "dark",
baseUrl = "https://custom.url/style.json"
),
darkMode = false, darkMode = false,
) )
).isEqualTo("https://custom.url/style.json") ).isEqualTo("https://custom.url/style.json/light/style.json?key=API_KEY")
} }
@Test @Test
fun `custom map uri dark`() { fun `custom map uri dark`() {
assertThat( assertThat(
builder.build( builder.build(
customMapStyleUrl = "https://custom.url/style.json", customMapTilerConfig = MapTilerConfig(
apiKey = "API_KEY",
lightStyleId = "light",
darkStyleId = "dark",
baseUrl = "https://custom.url/style.json"
),
darkMode = true, darkMode = true,
) )
).isEqualTo("https://custom.url/style.json") ).isEqualTo("https://custom.url/style.json/dark/style.json?key=API_KEY")
}
@Test
fun `custom map uri with its own api key is not altered`() {
assertThat(
builder.build(
customMapStyleUrl = "https://api.maptiler.com/maps/streets-v2/style.json?key=anOperatorKey",
darkMode = false,
)
).isEqualTo("https://api.maptiler.com/maps/streets-v2/style.json?key=anOperatorKey")
}
@Test
fun `custom map uri with an existing query is not altered`() {
assertThat(
builder.build(
customMapStyleUrl = "https://self.hosted/style.json?foo=bar",
darkMode = true,
)
).isEqualTo("https://self.hosted/style.json?foo=bar")
} }
@Test @Test
fun `blank custom map uri falls back to the built-in style`() { fun `blank custom map uri falls back to the built-in style`() {
assertThat( assertThat(
builder.build( builder.build(
customMapStyleUrl = " ", customMapTilerConfig = MapTilerConfig(
apiKey = "API_KEY",
lightStyleId = "light",
darkStyleId = "dark",
baseUrl = " "
),
darkMode = false, darkMode = false,
) )
).isEqualTo("https://base.url/aLightMapId/style.json?key=anApiKey") ).isEqualTo("https://base.url/light/style.json?key=API_KEY")
} }
} }
+4
View File
@@ -26,6 +26,7 @@ setupDependencyInjection()
dependencies { dependencies {
api(projects.features.location.api) api(projects.features.location.api)
implementation(projects.features.enterprise.api)
implementation(projects.features.messages.api) implementation(projects.features.messages.api)
implementation(libs.maplibre.compose) implementation(libs.maplibre.compose)
implementation(libs.coil) implementation(libs.coil)
@@ -45,6 +46,7 @@ dependencies {
implementation(projects.libraries.preferences.api) implementation(projects.libraries.preferences.api)
implementation(projects.libraries.push.api) implementation(projects.libraries.push.api)
implementation(projects.libraries.sessionStorage.api) implementation(projects.libraries.sessionStorage.api)
implementation(projects.libraries.wellknown.api)
implementation(libs.androidx.datastore.preferences) implementation(libs.androidx.datastore.preferences)
implementation(libs.datetime) implementation(libs.datetime)
@@ -58,5 +60,7 @@ dependencies {
testImplementation(projects.libraries.featureflag.test) testImplementation(projects.libraries.featureflag.test)
testImplementation(projects.libraries.preferences.test) testImplementation(projects.libraries.preferences.test)
testImplementation(projects.libraries.sessionStorage.test) testImplementation(projects.libraries.sessionStorage.test)
testImplementation(projects.libraries.wellknown.test)
testImplementation(projects.features.location.test) testImplementation(projects.features.location.test)
testImplementation(projects.features.enterprise.test)
} }
@@ -50,6 +50,7 @@ import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.designsystem.text.toDp import io.element.android.libraries.designsystem.text.toDp
import io.element.android.libraries.designsystem.theme.components.BottomSheetScaffold import io.element.android.libraries.designsystem.theme.components.BottomSheetScaffold
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.wellknown.api.MapTilerConfig
import org.maplibre.compose.camera.CameraState 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
@@ -66,7 +67,7 @@ import kotlin.math.roundToInt
* - Updating camera position padding based on sheet height * - Updating camera position padding based on sheet height
* - Rendering the MaplibreMap with proper ornament positioning * - Rendering the MaplibreMap with proper ornament positioning
* *
* @param customMapStyleUrl Optional custom style URL for the map * @param customMapTilerConfig Optional custom style URL for the map
* @param modifier Modifier for the root layout * @param modifier Modifier for the root layout
* @param scaffoldState State for the bottom sheet scaffold * @param scaffoldState State for the bottom sheet scaffold
* @param cameraState The camera state for the map * @param cameraState The camera state for the map
@@ -83,7 +84,7 @@ import kotlin.math.roundToInt
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun MapBottomSheetScaffold( fun MapBottomSheetScaffold(
customMapStyleUrl: AsyncData<String?>, customMapTilerConfig: AsyncData<MapTilerConfig?>,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
scaffoldState: BottomSheetScaffoldState = rememberBottomSheetScaffoldState( scaffoldState: BottomSheetScaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = rememberBottomSheetState( bottomSheetState = rememberBottomSheetState(
@@ -137,11 +138,11 @@ fun MapBottomSheetScaffold(
val ornamentOptions = mapOptions.ornamentOptions.copy(padding = sheetPadding) val ornamentOptions = mapOptions.ornamentOptions.copy(padding = sheetPadding)
val mapOptions = mapOptions.copy(ornamentOptions = ornamentOptions) val mapOptions = mapOptions.copy(ornamentOptions = ornamentOptions)
Box { Box {
when (customMapStyleUrl) { when (customMapTilerConfig) {
is AsyncData.Success -> { is AsyncData.Success -> {
MaplibreMap( MaplibreMap(
options = mapOptions, options = mapOptions,
baseStyle = BaseStyle.Uri(rememberTileStyleUrl(customMapStyleUrl.data)), baseStyle = BaseStyle.Uri(rememberTileStyleUrl(customMapTilerConfig.data)),
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
cameraState = cameraState, cameraState = cameraState,
content = mapContent, content = mapContent,
@@ -22,6 +22,7 @@ import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedFactory import dev.zacsweers.metro.AssistedFactory
import dev.zacsweers.metro.AssistedInject import dev.zacsweers.metro.AssistedInject
import im.vector.app.features.analytics.plan.Composer import im.vector.app.features.analytics.plan.Composer
import io.element.android.features.enterprise.api.remoteconfig.RemoteEnterpriseConfigProvider
import io.element.android.features.location.api.live.ActiveLiveLocationShareManager import io.element.android.features.location.api.live.ActiveLiveLocationShareManager
import io.element.android.features.location.impl.common.LocationConstraintsCheck import io.element.android.features.location.impl.common.LocationConstraintsCheck
import io.element.android.features.location.impl.common.MapDefaults import io.element.android.features.location.impl.common.MapDefaults
@@ -72,6 +73,7 @@ class ShareLocationPresenter(
private val liveLocationShareManager: ActiveLiveLocationShareManager, private val liveLocationShareManager: ActiveLiveLocationShareManager,
private val liveLocationStore: LiveLocationStore, private val liveLocationStore: LiveLocationStore,
private val userLocationStateFactory: UserLocationState.Factory, private val userLocationStateFactory: UserLocationState.Factory,
private val remoteEnterpriseConfigProvider: RemoteEnterpriseConfigProvider,
) : Presenter<ShareLocationState> { ) : Presenter<ShareLocationState> {
@AssistedFactory @AssistedFactory
fun interface Factory { fun interface Factory {
@@ -92,9 +94,9 @@ class ShareLocationPresenter(
var pendingLiveLocationShare by remember { mutableStateOf(false) } var pendingLiveLocationShare by remember { mutableStateOf(false) }
val startLiveLocationAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) } val startLiveLocationAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
val currentUser by client.userProfile.collectAsState() val currentUser by client.userProfile.collectAsState()
val customMapStyleUrl by produceState(AsyncData.Loading()) { val customMapConfig by produceState(AsyncData.Loading()) {
// Ignore errors // Ignore errors
value = AsyncData.Success(client.getMapStyleUrl().getOrNull()) value = AsyncData.Success(remoteEnterpriseConfigProvider.get(client.sessionId).dataOrNull()?.mapTilerConfig)
} }
val sendLiveLocationPermissions by room.permissionsAsState(SendLiveLocationPermissions.DEFAULT) { perms -> val sendLiveLocationPermissions by room.permissionsAsState(SendLiveLocationPermissions.DEFAULT) { perms ->
perms.sendLiveLocationPermissions() perms.sendLiveLocationPermissions()
@@ -199,7 +201,7 @@ class ShareLocationPresenter(
} }
return ShareLocationState( return ShareLocationState(
customMapStyleUrl = customMapStyleUrl, customMapTilerConfig = customMapConfig,
currentUser = currentUser, currentUser = currentUser,
dialogState = dialogState, dialogState = dialogState,
trackUserLocation = trackUserPosition, trackUserLocation = trackUserPosition,
@@ -13,10 +13,11 @@ import io.element.android.features.location.impl.common.userlocation.UserLocatio
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.wellknown.api.MapTilerConfig
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
data class ShareLocationState( data class ShareLocationState(
val customMapStyleUrl: AsyncData<String?>, val customMapTilerConfig: AsyncData<MapTilerConfig?>,
val currentUser: MatrixUser, val currentUser: MatrixUser,
val dialogState: Dialog, val dialogState: Dialog,
val trackUserLocation: Boolean, val trackUserLocation: Boolean,
@@ -15,6 +15,7 @@ import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.wellknown.api.MapTilerConfig
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.minutes
@@ -70,13 +71,13 @@ class ShareLocationStateProvider : PreviewParameterProvider<ShareLocationState>
canShareLiveLocation = true, canShareLiveLocation = true,
), ),
aShareLocationState( aShareLocationState(
customMapStyleUrl = AsyncData.Loading(), customMapTilerConfig = AsyncData.Loading(),
), ),
) )
} }
fun aShareLocationState( fun aShareLocationState(
customMapStyleUrl: AsyncData<String?> = AsyncData.Success(null), customMapTilerConfig: AsyncData<MapTilerConfig?> = AsyncData.Success(null),
currentUser: MatrixUser = MatrixUser(UserId("@user:matrix.org")), currentUser: MatrixUser = MatrixUser(UserId("@user:matrix.org")),
dialogState: ShareLocationState.Dialog = ShareLocationState.Dialog.None, dialogState: ShareLocationState.Dialog = ShareLocationState.Dialog.None,
trackUserPosition: Boolean = false, trackUserPosition: Boolean = false,
@@ -87,7 +88,7 @@ fun aShareLocationState(
eventSink: (ShareLocationEvent) -> Unit = {}, eventSink: (ShareLocationEvent) -> Unit = {},
): ShareLocationState { ): ShareLocationState {
return ShareLocationState( return ShareLocationState(
customMapStyleUrl = customMapStyleUrl, customMapTilerConfig = customMapTilerConfig,
currentUser = currentUser, currentUser = currentUser,
dialogState = dialogState, dialogState = dialogState,
trackUserLocation = trackUserPosition, trackUserLocation = trackUserPosition,
@@ -95,6 +96,6 @@ fun aShareLocationState(
canShareLiveLocation = canShareLiveLocation, canShareLiveLocation = canShareLiveLocation,
appName = appName, appName = appName,
startLiveLocationAction = startLiveLocationAction, startLiveLocationAction = startLiveLocationAction,
eventSink = eventSink eventSink = eventSink,
) )
} }
@@ -118,7 +118,7 @@ fun ShareLocationView(
} }
MapBottomSheetScaffold( MapBottomSheetScaffold(
customMapStyleUrl = state.customMapStyleUrl, customMapTilerConfig = state.customMapTilerConfig,
cameraState = cameraState, cameraState = cameraState,
modifier = modifier, modifier = modifier,
scaffoldState = scaffoldState, scaffoldState = scaffoldState,
@@ -22,6 +22,7 @@ import androidx.compose.runtime.setValue
import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedFactory import dev.zacsweers.metro.AssistedFactory
import dev.zacsweers.metro.AssistedInject import dev.zacsweers.metro.AssistedInject
import io.element.android.features.enterprise.api.remoteconfig.RemoteEnterpriseConfigProvider
import io.element.android.features.location.api.Location import io.element.android.features.location.api.Location
import io.element.android.features.location.api.ShowLocationMode import io.element.android.features.location.api.ShowLocationMode
import io.element.android.features.location.api.live.ActiveLiveLocationShareManager import io.element.android.features.location.api.live.ActiveLiveLocationShareManager
@@ -46,7 +47,7 @@ import io.element.android.libraries.dateformatter.api.DateFormatter
import io.element.android.libraries.dateformatter.api.DateFormatterMode import io.element.android.libraries.dateformatter.api.DateFormatterMode
import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.getBestName import io.element.android.libraries.matrix.api.room.getBestName
import io.element.android.libraries.matrix.api.room.joinedRoomMembers import io.element.android.libraries.matrix.api.room.joinedRoomMembers
@@ -64,10 +65,11 @@ import kotlinx.coroutines.launch
private val buildMeta: BuildMeta, private val buildMeta: BuildMeta,
private val dateFormatter: DateFormatter, private val dateFormatter: DateFormatter,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val client: MatrixClient, private val sessionId: SessionId,
private val joinedRoom: JoinedRoom, private val joinedRoom: JoinedRoom,
private val liveLocationShareManager: ActiveLiveLocationShareManager, private val liveLocationShareManager: ActiveLiveLocationShareManager,
private val userLocationStateFactory: UserLocationState.Factory, private val userLocationStateFactory: UserLocationState.Factory,
private val remoteEnterpriseConfigProvider: RemoteEnterpriseConfigProvider,
) : Presenter<ShowLocationState> { ) : Presenter<ShowLocationState> {
@AssistedFactory fun interface Factory { @AssistedFactory fun interface Factory {
fun create(mode: ShowLocationMode): ShowLocationPresenter fun create(mode: ShowLocationMode): ShowLocationPresenter
@@ -85,9 +87,9 @@ import kotlinx.coroutines.launch
mutableStateOf(LocationConstraintsDialogState.None) mutableStateOf(LocationConstraintsDialogState.None)
} }
val customMapStyleUrl by produceState(AsyncData.Loading()) { val customMapTilerConfig by produceState(AsyncData.Loading()) {
// Ignore errors // Ignore errors
value = AsyncData.Success(client.getMapStyleUrl().getOrNull()) value = AsyncData.Success(remoteEnterpriseConfigProvider.get(sessionId).dataOrNull()?.mapTilerConfig)
} }
fun checkLocationConstraints() { fun checkLocationConstraints() {
@@ -220,7 +222,7 @@ import kotlinx.coroutines.launch
userLocationStateFactory.create(hasLocationPermission = permissionsState.isAnyGranted) userLocationStateFactory.create(hasLocationPermission = permissionsState.isAnyGranted)
} }
return ShowLocationState( return ShowLocationState(
customMapStyleUrl = customMapStyleUrl, customMapTilerConfig = customMapTilerConfig,
dialogState = dialogState, dialogState = dialogState,
locationShares = locationShares, locationShares = locationShares,
focusedLocation = focusedLocation, focusedLocation = focusedLocation,
@@ -17,10 +17,11 @@ import io.element.android.libraries.designsystem.components.PinVariant
import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.location.AssetType import io.element.android.libraries.matrix.api.room.location.AssetType
import io.element.android.libraries.wellknown.api.MapTilerConfig
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
data class ShowLocationState( data class ShowLocationState(
val customMapStyleUrl: AsyncData<String?>, val customMapTilerConfig: AsyncData<MapTilerConfig?>,
val isLive: Boolean, val isLive: Boolean,
val dialogState: LocationConstraintsDialogState, val dialogState: LocationConstraintsDialogState,
val locationShares: ImmutableList<LocationShareItem>, val locationShares: ImmutableList<LocationShareItem>,
@@ -18,6 +18,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.location.AssetType import io.element.android.libraries.matrix.api.room.location.AssetType
import io.element.android.libraries.wellknown.api.MapTilerConfig
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
class ShowLocationStateProvider : PreviewParameterProvider<ShowLocationState> { class ShowLocationStateProvider : PreviewParameterProvider<ShowLocationState> {
@@ -36,14 +37,14 @@ class ShowLocationStateProvider : PreviewParameterProvider<ShowLocationState> {
constraintsDialogState = LocationConstraintsDialogState.LocationServiceDisabled, constraintsDialogState = LocationConstraintsDialogState.LocationServiceDisabled,
), ),
aShowLocationState(isTrackMyLocation = true), aShowLocationState(isTrackMyLocation = true),
aShowLocationState(customMapStyleUrl = AsyncData.Loading()), aShowLocationState(customMapTilerConfig = AsyncData.Loading()),
) )
} }
private const val APP_NAME = "ApplicationName" private const val APP_NAME = "ApplicationName"
fun aShowLocationState( fun aShowLocationState(
customMapStyleUrl: AsyncData<String?> = AsyncData.Success(null), customMapTilerConfig: AsyncData<MapTilerConfig?> = AsyncData.Success(null),
isLive: Boolean = false, isLive: Boolean = false,
constraintsDialogState: LocationConstraintsDialogState = LocationConstraintsDialogState.None, constraintsDialogState: LocationConstraintsDialogState = LocationConstraintsDialogState.None,
locationShares: List<LocationShareItem> = listOf(aLocationShareItem(isLive = isLive)), locationShares: List<LocationShareItem> = listOf(aLocationShareItem(isLive = isLive)),
@@ -55,7 +56,7 @@ fun aShowLocationState(
eventSink: (ShowLocationEvent) -> Unit = {}, eventSink: (ShowLocationEvent) -> Unit = {},
): ShowLocationState { ): ShowLocationState {
return ShowLocationState( return ShowLocationState(
customMapStyleUrl = customMapStyleUrl, customMapTilerConfig = customMapTilerConfig,
dialogState = constraintsDialogState, dialogState = constraintsDialogState,
locationShares = locationShares.toImmutableList(), locationShares = locationShares.toImmutableList(),
focusedLocation = focusedLocation, focusedLocation = focusedLocation,
@@ -102,7 +102,7 @@ fun ShowLocationView(
} }
} }
MapBottomSheetScaffold( MapBottomSheetScaffold(
customMapStyleUrl = state.customMapStyleUrl, customMapTilerConfig = state.customMapTilerConfig,
sheetDragHandle = if (state.isSheetDraggable) { sheetDragHandle = if (state.isSheetDraggable) {
{ BottomSheetDefaults.DragHandle() } { BottomSheetDefaults.DragHandle() }
} else { } else {
@@ -11,6 +11,7 @@ package io.element.android.features.location.impl.share
import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.enterprise.test.remoteconfig.FakeRemoteEnterpriseConfigProvider
import io.element.android.features.location.impl.common.FakeUserLocationStateFactory import io.element.android.features.location.impl.common.FakeUserLocationStateFactory
import io.element.android.features.location.impl.common.actions.FakeLocationActions import io.element.android.features.location.impl.common.actions.FakeLocationActions
import io.element.android.features.location.impl.common.permissions.FakePermissionsPresenter import io.element.android.features.location.impl.common.permissions.FakePermissionsPresenter
@@ -58,6 +59,7 @@ class DefaultShareLocationEntryPointTest {
sessionId = room.sessionId, sessionId = room.sessionId,
), ),
userLocationStateFactory = FakeUserLocationStateFactory(), userLocationStateFactory = FakeUserLocationStateFactory(),
remoteEnterpriseConfigProvider = FakeRemoteEnterpriseConfigProvider(),
) )
}, },
analyticsService = FakeAnalyticsService(), analyticsService = FakeAnalyticsService(),
@@ -19,6 +19,8 @@ import app.cash.turbine.ReceiveTurbine
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.Composer import im.vector.app.features.analytics.plan.Composer
import io.element.android.features.enterprise.api.remoteconfig.RemoteEnterpriseConfigProvider
import io.element.android.features.enterprise.test.remoteconfig.FakeRemoteEnterpriseConfigProvider
import io.element.android.features.location.api.Location import io.element.android.features.location.api.Location
import io.element.android.features.location.impl.aPermissionsState import io.element.android.features.location.impl.aPermissionsState
import io.element.android.features.location.impl.common.FakeUserLocationStateFactory import io.element.android.features.location.impl.common.FakeUserLocationStateFactory
@@ -30,6 +32,8 @@ import io.element.android.features.location.impl.common.ui.LocationConstraintsDi
import io.element.android.features.location.impl.live.LiveLocationStore import io.element.android.features.location.impl.live.LiveLocationStore
import io.element.android.features.location.test.FakeActiveLiveLocationShareManager import io.element.android.features.location.test.FakeActiveLiveLocationShareManager
import io.element.android.features.messages.test.FakeMessageComposerContext import io.element.android.features.messages.test.FakeMessageComposerContext
import io.element.android.features.wellknown.test.aMapTilerConfig
import io.element.android.features.wellknown.test.anElementWellKnown
import io.element.android.libraries.dateformatter.test.FakeDurationFormatter import io.element.android.libraries.dateformatter.test.FakeDurationFormatter
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
@@ -51,6 +55,7 @@ import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermiss
import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory
import io.element.android.libraries.preferences.test.FakePreferenceDataStoreFactory import io.element.android.libraries.preferences.test.FakePreferenceDataStoreFactory
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.lambda.assert import io.element.android.tests.testutils.lambda.assert
@@ -60,7 +65,6 @@ import io.element.android.tests.testutils.test
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Rule import org.junit.Rule
@@ -77,20 +81,18 @@ class ShareLocationPresenterTest {
private val fakeMessageComposerContext = FakeMessageComposerContext() private val fakeMessageComposerContext = FakeMessageComposerContext()
private val fakeLocationActions = FakeLocationActions() private val fakeLocationActions = FakeLocationActions()
private val fakeBuildMeta = aBuildMeta(applicationName = "app name") private val fakeBuildMeta = aBuildMeta(applicationName = "app name")
private val fakeMatrixClient = FakeMatrixClient( private val fakeMatrixClient = FakeMatrixClient(sessionId = A_USER_ID)
sessionId = A_USER_ID,
getMapStyleUrlResult = { Result.success(null) },
)
private val durationFormatter = FakeDurationFormatter() private val durationFormatter = FakeDurationFormatter()
private fun TestScope.createShareLocationPresenter( private fun createShareLocationPresenter(
joinedRoom: JoinedRoom = FakeJoinedRoom(), joinedRoom: JoinedRoom = FakeJoinedRoom(),
timelineMode: Timeline.Mode = Timeline.Mode.Live, timelineMode: Timeline.Mode = Timeline.Mode.Live,
locationActions: FakeLocationActions = fakeLocationActions, locationActions: FakeLocationActions = fakeLocationActions,
liveLocationShareManager: FakeActiveLiveLocationShareManager = FakeActiveLiveLocationShareManager(), liveLocationShareManager: FakeActiveLiveLocationShareManager = FakeActiveLiveLocationShareManager(),
liveLocationStore: LiveLocationStore = createLiveLocationStore(sessionId = joinedRoom.sessionId), liveLocationStore: LiveLocationStore = createLiveLocationStore(sessionId = joinedRoom.sessionId),
client: FakeMatrixClient = fakeMatrixClient, client: FakeMatrixClient = fakeMatrixClient,
remoteEnterpriseConfigProvider: RemoteEnterpriseConfigProvider = FakeRemoteEnterpriseConfigProvider(),
): ShareLocationPresenter = ShareLocationPresenter( ): ShareLocationPresenter = ShareLocationPresenter(
permissionsPresenterFactory = { fakePermissionsPresenter }, permissionsPresenterFactory = { fakePermissionsPresenter },
room = joinedRoom, room = joinedRoom,
@@ -104,6 +106,7 @@ class ShareLocationPresenterTest {
liveLocationShareManager = liveLocationShareManager, liveLocationShareManager = liveLocationShareManager,
liveLocationStore = liveLocationStore, liveLocationStore = liveLocationStore,
userLocationStateFactory = FakeUserLocationStateFactory(), userLocationStateFactory = FakeUserLocationStateFactory(),
remoteEnterpriseConfigProvider = remoteEnterpriseConfigProvider,
) )
@Test @Test
@@ -118,7 +121,7 @@ class ShareLocationPresenterTest {
val shareLocationPresenter = createShareLocationPresenter() val shareLocationPresenter = createShareLocationPresenter()
shareLocationPresenter.test { shareLocationPresenter.test {
val state = awaitFirstItem() val state = awaitFirstItem()
assertThat(state.customMapStyleUrl.isLoading()).isFalse() assertThat(state.customMapTilerConfig.isLoading()).isFalse()
assertThat(state.trackUserLocation).isTrue() assertThat(state.trackUserLocation).isTrue()
assertThat(state.dialogState).isEqualTo(ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.None)) assertThat(state.dialogState).isEqualTo(ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.None))
} }
@@ -126,18 +129,20 @@ class ShareLocationPresenterTest {
@Test @Test
fun `present - non-null customMapStyleUrl`() = runTest { fun `present - non-null customMapStyleUrl`() = runTest {
val mapTilerConfig = aMapTilerConfig(apiKey = "A KEY")
val shareLocationPresenter = createShareLocationPresenter( val shareLocationPresenter = createShareLocationPresenter(
client = FakeMatrixClient( client = FakeMatrixClient(
sessionId = A_USER_ID, sessionId = A_USER_ID,
getMapStyleUrlResult = { Result.success("aUrl") }, ),
remoteEnterpriseConfigProvider = FakeRemoteEnterpriseConfigProvider(
getResult = { WellknownRetrieverResult.Success(anElementWellKnown(mapTilerConfig = mapTilerConfig)) }
) )
) )
shareLocationPresenter.test { shareLocationPresenter.test {
skipItems(1)
val state = awaitItem() val state = awaitItem()
assertThat(state.customMapStyleUrl.isLoading()).isTrue() assertThat(state.customMapTilerConfig.isLoading()).isTrue()
val finalState = awaitItem() val finalState = awaitItem()
assertThat(finalState.customMapStyleUrl.dataOrNull()).isEqualTo("aUrl") assertThat(finalState.customMapTilerConfig.dataOrNull()).isEqualTo(mapTilerConfig)
} }
} }
@@ -709,7 +714,7 @@ class ShareLocationPresenterTest {
} }
private suspend fun <T> ReceiveTurbine<T>.awaitFirstItem(): T { private suspend fun <T> ReceiveTurbine<T>.awaitFirstItem(): T {
skipItems(2) skipItems(1)
return awaitItem() return awaitItem()
} }
} }
@@ -11,6 +11,7 @@ package io.element.android.features.location.impl.show
import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.enterprise.test.remoteconfig.FakeRemoteEnterpriseConfigProvider
import io.element.android.features.location.api.Location import io.element.android.features.location.api.Location
import io.element.android.features.location.api.ShowLocationEntryPoint import io.element.android.features.location.api.ShowLocationEntryPoint
import io.element.android.features.location.api.ShowLocationMode import io.element.android.features.location.api.ShowLocationMode
@@ -20,7 +21,7 @@ import io.element.android.features.location.impl.common.permissions.FakePermissi
import io.element.android.features.location.test.FakeActiveLiveLocationShareManager import io.element.android.features.location.test.FakeActiveLiveLocationShareManager
import io.element.android.libraries.dateformatter.test.FakeDateFormatter import io.element.android.libraries.dateformatter.test.FakeDateFormatter
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.test.core.aBuildMeta
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.services.analytics.test.FakeAnalyticsService
@@ -41,8 +42,8 @@ class DefaultShowLocationEntryPointTest {
ShowLocationNode( ShowLocationNode(
buildContext = buildContext, buildContext = buildContext,
plugins = plugins, plugins = plugins,
presenterFactory = object : ShowLocationPresenter.Factory { presenterFactory = { mode ->
override fun create(mode: ShowLocationMode) = ShowLocationPresenter( ShowLocationPresenter(
mode = mode, mode = mode,
permissionsPresenterFactory = { FakePermissionsPresenter() }, permissionsPresenterFactory = { FakePermissionsPresenter() },
locationActions = FakeLocationActions(), locationActions = FakeLocationActions(),
@@ -50,9 +51,10 @@ class DefaultShowLocationEntryPointTest {
dateFormatter = FakeDateFormatter(), dateFormatter = FakeDateFormatter(),
stringProvider = FakeStringProvider(), stringProvider = FakeStringProvider(),
joinedRoom = joinedRoom, joinedRoom = joinedRoom,
client = FakeMatrixClient(), sessionId = A_SESSION_ID,
liveLocationShareManager = FakeActiveLiveLocationShareManager(), liveLocationShareManager = FakeActiveLiveLocationShareManager(),
userLocationStateFactory = FakeUserLocationStateFactory(), userLocationStateFactory = FakeUserLocationStateFactory(),
remoteEnterpriseConfigProvider = FakeRemoteEnterpriseConfigProvider(),
) )
}, },
analyticsService = FakeAnalyticsService(), analyticsService = FakeAnalyticsService(),
@@ -12,6 +12,7 @@ import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow import app.cash.molecule.moleculeFlow
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.enterprise.test.remoteconfig.FakeRemoteEnterpriseConfigProvider
import io.element.android.features.location.api.Location import io.element.android.features.location.api.Location
import io.element.android.features.location.api.ShowLocationMode import io.element.android.features.location.api.ShowLocationMode
import io.element.android.features.location.impl.aPermissionsState import io.element.android.features.location.impl.aPermissionsState
@@ -22,17 +23,20 @@ import io.element.android.features.location.impl.common.permissions.PermissionsE
import io.element.android.features.location.impl.common.permissions.PermissionsState import io.element.android.features.location.impl.common.permissions.PermissionsState
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
import io.element.android.features.location.test.FakeActiveLiveLocationShareManager import io.element.android.features.location.test.FakeActiveLiveLocationShareManager
import io.element.android.features.wellknown.test.aMapTilerConfig
import io.element.android.features.wellknown.test.anElementWellKnown
import io.element.android.libraries.dateformatter.test.FakeDateFormatter import io.element.android.libraries.dateformatter.test.FakeDateFormatter
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.location.AssetType import io.element.android.libraries.matrix.api.room.location.AssetType
import io.element.android.libraries.matrix.api.room.location.LiveLocationShare import io.element.android.libraries.matrix.api.room.location.LiveLocationShare
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.test.core.aBuildMeta
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.libraries.matrix.test.room.location.aLiveLocationShare import io.element.android.libraries.matrix.test.room.location.aLiveLocationShare
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
import io.element.android.services.toolbox.test.strings.FakeStringProvider import io.element.android.services.toolbox.test.strings.FakeStringProvider
import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.test import io.element.android.tests.testutils.test
@@ -65,8 +69,9 @@ class ShowLocationPresenterTest {
), ),
locationActions: FakeLocationActions = fakeLocationActions, locationActions: FakeLocationActions = fakeLocationActions,
joinedRoom: JoinedRoom = FakeJoinedRoom(), joinedRoom: JoinedRoom = FakeJoinedRoom(),
client: MatrixClient = FakeMatrixClient(), sessionId: SessionId = A_SESSION_ID,
liveLocationShareManager: FakeActiveLiveLocationShareManager = FakeActiveLiveLocationShareManager(), liveLocationShareManager: FakeActiveLiveLocationShareManager = FakeActiveLiveLocationShareManager(),
remoteEnterpriseConfigProvider: FakeRemoteEnterpriseConfigProvider = FakeRemoteEnterpriseConfigProvider(),
) = ShowLocationPresenter( ) = ShowLocationPresenter(
mode = mode, mode = mode,
permissionsPresenterFactory = { fakePermissionsPresenter }, permissionsPresenterFactory = { fakePermissionsPresenter },
@@ -75,24 +80,26 @@ class ShowLocationPresenterTest {
dateFormatter = fakeDateFormatter, dateFormatter = fakeDateFormatter,
stringProvider = FakeStringProvider(), stringProvider = FakeStringProvider(),
joinedRoom = joinedRoom, joinedRoom = joinedRoom,
client = client, sessionId = sessionId,
liveLocationShareManager = liveLocationShareManager, liveLocationShareManager = liveLocationShareManager,
userLocationStateFactory = FakeUserLocationStateFactory(), userLocationStateFactory = FakeUserLocationStateFactory(),
remoteEnterpriseConfigProvider = remoteEnterpriseConfigProvider,
) )
@Test @Test
fun `present - non-null customMapStyleUrl`() = runTest { fun `present - non-null customMapStyleUrl`() = runTest {
val mapTilerConfig = aMapTilerConfig(apiKey = "A KEY")
val shareLocationPresenter = createShowLocationPresenter( val shareLocationPresenter = createShowLocationPresenter(
client = FakeMatrixClient( sessionId = A_SESSION_ID,
sessionId = A_USER_ID, remoteEnterpriseConfigProvider = FakeRemoteEnterpriseConfigProvider(
getMapStyleUrlResult = { Result.success("aUrl") }, getResult = { WellknownRetrieverResult.Success(anElementWellKnown(mapTilerConfig = mapTilerConfig)) }
) )
) )
shareLocationPresenter.test { shareLocationPresenter.test {
val state = awaitItem() val state = awaitItem()
assertThat(state.customMapStyleUrl.isLoading()).isTrue() assertThat(state.customMapTilerConfig.isLoading()).isTrue()
val finalState = awaitItem() val finalState = awaitItem()
assertThat(finalState.customMapStyleUrl.dataOrNull()).isEqualTo("aUrl") assertThat(finalState.customMapTilerConfig.dataOrNull()).isEqualTo(mapTilerConfig)
} }
} }
@@ -115,6 +122,8 @@ class ShowLocationPresenterTest {
initialState.eventSink(ShowLocationEvent.Share(location)) initialState.eventSink(ShowLocationEvent.Share(location))
assertThat(fakeLocationActions.sharedLocation).isEqualTo(location) assertThat(fakeLocationActions.sharedLocation).isEqualTo(location)
cancelAndIgnoreRemainingEvents()
} }
} }
@@ -123,10 +132,12 @@ class ShowLocationPresenterTest {
fakePermissionsPresenter.givenState(aPermissionsState(permissions = PermissionsState.Permissions.AllGranted)) fakePermissionsPresenter.givenState(aPermissionsState(permissions = PermissionsState.Permissions.AllGranted))
val presenter = createShowLocationPresenter( val presenter = createShowLocationPresenter(
client = FakeMatrixClient( sessionId = A_SESSION_ID,
getMapStyleUrlResult = { Result.success(null) } remoteEnterpriseConfigProvider = FakeRemoteEnterpriseConfigProvider(
getResult = { WellknownRetrieverResult.Success(anElementWellKnown(mapTilerConfig = aMapTilerConfig())) }
) )
) )
presenter.test { presenter.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.isTrackMyLocation).isFalse() assertThat(initialState.isTrackMyLocation).isFalse()
@@ -281,6 +292,8 @@ class ShowLocationPresenterTest {
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.appName).isEqualTo("app name") assertThat(initialState.appName).isEqualTo("app name")
cancelAndIgnoreRemainingEvents()
} }
} }
@@ -334,6 +347,8 @@ class ShowLocationPresenterTest {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.locationShares).isEmpty() assertThat(initialState.locationShares).isEmpty()
assertThat(initialState.isSheetDraggable).isFalse() assertThat(initialState.isSheetDraggable).isFalse()
cancelAndIgnoreRemainingEvents()
} }
} }
@@ -408,6 +423,9 @@ class ShowLocationPresenterTest {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.locationShares).isEmpty() assertThat(initialState.locationShares).isEmpty()
// Wait until we receive the custom map style URL result
skipItems(1)
// Emit a new live share // Emit a new live share
liveSharesFlow.value = listOf( liveSharesFlow.value = listOf(
aLiveLocationShare(userId = userId) aLiveLocationShare(userId = userId)
@@ -447,6 +465,8 @@ class ShowLocationPresenterTest {
assertThat(item.avatarData.id).isEqualTo(senderId.value) assertThat(item.avatarData.id).isEqualTo(senderId.value)
assertThat(item.avatarData.name).isEqualTo(senderName) assertThat(item.avatarData.name).isEqualTo(senderName)
assertThat(item.avatarData.url).isEqualTo(avatarUrl) assertThat(item.avatarData.url).isEqualTo(avatarUrl)
cancelAndIgnoreRemainingEvents()
} }
} }
@@ -456,6 +476,7 @@ class ShowLocationPresenterTest {
presenter.test { presenter.test {
val state = awaitItem() val state = awaitItem()
assertThat(state.isSheetDraggable).isFalse() assertThat(state.isSheetDraggable).isFalse()
cancelAndIgnoreRemainingEvents()
} }
} }
@@ -465,6 +486,8 @@ class ShowLocationPresenterTest {
presenter.test { presenter.test {
val state = awaitItem() val state = awaitItem()
assertThat(state.hideUserLocationPuck).isFalse() assertThat(state.hideUserLocationPuck).isFalse()
cancelAndIgnoreRemainingEvents()
} }
} }
@@ -476,6 +499,8 @@ class ShowLocationPresenterTest {
presenter.test { presenter.test {
val state = awaitItem() val state = awaitItem()
assertThat(state.hideUserLocationPuck).isFalse() assertThat(state.hideUserLocationPuck).isFalse()
cancelAndIgnoreRemainingEvents()
} }
} }
+1
View File
@@ -61,6 +61,7 @@ dependencies {
implementation(projects.libraries.push.api) implementation(projects.libraries.push.api)
implementation(projects.libraries.uiUtils) implementation(projects.libraries.uiUtils)
implementation(projects.libraries.testtags) implementation(projects.libraries.testtags)
implementation(projects.libraries.wellknown.api)
implementation(projects.features.networkmonitor.api) implementation(projects.features.networkmonitor.api)
implementation(projects.features.contentscanner.api) implementation(projects.features.contentscanner.api)
implementation(projects.services.analytics.compose) implementation(projects.services.analytics.compose)
@@ -28,6 +28,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.location.api.LocalMapTilerConfig
import io.element.android.features.location.api.StaticMapView import io.element.android.features.location.api.StaticMapView
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContentProvider import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContentProvider
@@ -50,6 +51,7 @@ fun TimelineItemLocationView(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.heightIn(max = 188.dp), .heightIn(max = 188.dp),
mapTilerConfig = LocalMapTilerConfig.current,
pinVariant = content.pinVariant, pinVariant = content.pinVariant,
location = content.location, location = content.location,
zoom = 15.0, zoom = 15.0,
@@ -255,11 +255,6 @@ interface MatrixClient : UrlContentFetcher {
*/ */
suspend fun performDatabaseVacuum(): Result<Unit> suspend fun performDatabaseVacuum(): Result<Unit>
/**
* Returns the URL of the map style configured on the server, if any.
*/
suspend fun getMapStyleUrl(): Result<String?>
/** /**
* Resets the cached client `well-known` config by the SDK. * Resets the cached client `well-known` config by the SDK.
*/ */
@@ -886,12 +886,6 @@ class RustMatrixClient(
} }
} }
override suspend fun getMapStyleUrl(): Result<String?> = withContext(sessionDispatcher) {
runCatchingExceptions {
innerClient.tileServer()?.mapStyleUrl
}
}
override suspend fun resetWellKnownConfig(): Result<Unit> { override suspend fun resetWellKnownConfig(): Result<Unit> {
return runCatchingExceptions { return runCatchingExceptions {
Timber.d("Resetting well-known config for session $sessionId") Timber.d("Resetting well-known config for session $sessionId")
@@ -129,7 +129,6 @@ class FakeMatrixClient(
private val markRoomAsFullyReadResult: (RoomId, EventId) -> Result<Unit> = { _, _ -> lambdaError() }, private val markRoomAsFullyReadResult: (RoomId, EventId) -> Result<Unit> = { _, _ -> lambdaError() },
private val markAllRoomsAsReadResult: () -> Result<Unit> = { Result.success(Unit) }, private val markAllRoomsAsReadResult: () -> Result<Unit> = { Result.success(Unit) },
private val performDatabaseVacuumLambda: () -> Result<Unit> = { lambdaError() }, private val performDatabaseVacuumLambda: () -> Result<Unit> = { lambdaError() },
private val getMapStyleUrlResult: () -> Result<String?> = { lambdaError() },
private val getDatabaseSizesLambda: () -> Result<SdkStoreSizes> = { lambdaError() }, private val getDatabaseSizesLambda: () -> Result<SdkStoreSizes> = { lambdaError() },
private val resetWellKnownConfigLambda: () -> Result<Unit> = { lambdaError() }, private val resetWellKnownConfigLambda: () -> Result<Unit> = { lambdaError() },
private val enableAutomaticCallStatusLambda: (Boolean) -> Unit = { }, private val enableAutomaticCallStatusLambda: (Boolean) -> Unit = { },
@@ -435,10 +434,6 @@ class FakeMatrixClient(
return performDatabaseVacuumLambda() return performDatabaseVacuumLambda()
} }
override suspend fun getMapStyleUrl(): Result<String?> = simulateLongTask {
getMapStyleUrlResult()
}
override suspend fun canLinkNewDevice(): Result<Boolean> = simulateLongTask { override suspend fun canLinkNewDevice(): Result<Boolean> = simulateLongTask {
return canLinkNewDeviceResult() return canLinkNewDeviceResult()
} }
@@ -18,4 +18,19 @@ data class ElementWellKnown(
val customRecoveryPassphrase: CustomRecoveryPassphrase?, val customRecoveryPassphrase: CustomRecoveryPassphrase?,
val contentScannerUrl: String?, val contentScannerUrl: String?,
val forceDisableE2EE: Boolean?, val forceDisableE2EE: Boolean?,
val mapTilerConfig: MapTilerConfig?,
)
/**
* A `MapTiler` configuration received from the server. This configuration is used to display maps in the app.
*/
data class MapTilerConfig(
/** The API key to use with MapTiler. This is always not null, but can be empty/blank. */
val apiKey: String,
/** The style ID to use for light mode. This is optional and can be null. */
val lightStyleId: String?,
/** The style ID to use for light mode. This is optional and can be null. */
val darkStyleId: String?,
/** The base URL to use for MapTiler. This is optional and can be null. */
val baseUrl: String?,
) )
@@ -48,6 +48,8 @@ data class InternalElementWellKnown(
val contentScannerUrl: String? = null, val contentScannerUrl: String? = null,
@SerialName("force_disable_e2ee") @SerialName("force_disable_e2ee")
val forceDisableE2EE: Boolean? = null, val forceDisableE2EE: Boolean? = null,
@SerialName("map_tiler")
val mapTilerConfig: InternalMapTilerConfig? = null,
) )
@Serializable @Serializable
@@ -55,3 +57,15 @@ data class InternalCustomRecoveryPassphrase(
@SerialName("min_character_count") @SerialName("min_character_count")
val minCharacterCount: Int? = null, val minCharacterCount: Int? = null,
) )
@Serializable
data class InternalMapTilerConfig(
@SerialName("api_key")
val apiKey: String,
@SerialName("light_style_id")
val lightStyleId: String? = null,
@SerialName("dark_style_id")
val darkStyleId: String? = null,
@SerialName("base_url")
val baseUrl: String? = null,
)
@@ -11,6 +11,7 @@ package io.element.android.libraries.wellknown.impl
import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.wellknown.api.CustomRecoveryPassphrase import io.element.android.libraries.wellknown.api.CustomRecoveryPassphrase
import io.element.android.libraries.wellknown.api.ElementWellKnown import io.element.android.libraries.wellknown.api.ElementWellKnown
import io.element.android.libraries.wellknown.api.MapTilerConfig
import timber.log.Timber import timber.log.Timber
private val loggerTag = LoggerTag("Wellknown") private val loggerTag = LoggerTag("Wellknown")
@@ -32,6 +33,14 @@ fun InternalElementWellKnown.map() = ElementWellKnown(
customRecoveryPassphrase = customRecoveryPassphrase?.toPublic(), customRecoveryPassphrase = customRecoveryPassphrase?.toPublic(),
contentScannerUrl = contentScannerUrl, contentScannerUrl = contentScannerUrl,
forceDisableE2EE = forceDisableE2EE, forceDisableE2EE = forceDisableE2EE,
mapTilerConfig = mapTilerConfig?.let {
MapTilerConfig(
apiKey = it.apiKey,
lightStyleId = it.lightStyleId,
darkStyleId = it.darkStyleId,
baseUrl = it.baseUrl,
)
}
) )
private fun InternalCustomRecoveryPassphrase.toPublic(): CustomRecoveryPassphrase { private fun InternalCustomRecoveryPassphrase.toPublic(): CustomRecoveryPassphrase {
@@ -13,6 +13,7 @@ package io.element.android.libraries.wellknown.impl
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.enterprise.test.FakeEnterpriseService import io.element.android.features.enterprise.test.FakeEnterpriseService
import io.element.android.features.wellknown.test.FakeElementWellknownStore import io.element.android.features.wellknown.test.FakeElementWellknownStore
import io.element.android.features.wellknown.test.aMapTilerConfig
import io.element.android.features.wellknown.test.anElementWellKnown import io.element.android.features.wellknown.test.anElementWellKnown
import io.element.android.libraries.core.uri.ensureProtocol import io.element.android.libraries.core.uri.ensureProtocol
import io.element.android.libraries.matrix.api.UrlContentFetcher import io.element.android.libraries.matrix.api.UrlContentFetcher
@@ -204,7 +205,11 @@ class DefaultWellknownRetrieverTest {
"notification_sound": "a_notification_sound.flac", "notification_sound": "a_notification_sound.flac",
"idp_app_scheme": "an_app_scheme", "idp_app_scheme": "an_app_scheme",
"content_scanner_url": "https://content-scanner.example.com", "content_scanner_url": "https://content-scanner.example.com",
"force_disable_e2ee": false "force_disable_e2ee": false,
"map_tiler": {
"api_key": "test_api_key",
"base_url": "https://tile-server.example.com"
}
}""" }"""
private val expectedElementWellKnown = ElementWellKnown( private val expectedElementWellKnown = ElementWellKnown(
@@ -217,6 +222,10 @@ class DefaultWellknownRetrieverTest {
contentScannerUrl = "https://content-scanner.example.com", contentScannerUrl = "https://content-scanner.example.com",
customRecoveryPassphrase = null, customRecoveryPassphrase = null,
forceDisableE2EE = false, forceDisableE2EE = false,
mapTilerConfig = aMapTilerConfig(
apiKey = "test_api_key",
baseUrl = "https://tile-server.example.com",
)
) )
} }
} }
@@ -10,6 +10,7 @@ package io.element.android.features.wellknown.test
import io.element.android.libraries.wellknown.api.CustomRecoveryPassphrase import io.element.android.libraries.wellknown.api.CustomRecoveryPassphrase
import io.element.android.libraries.wellknown.api.ElementWellKnown import io.element.android.libraries.wellknown.api.ElementWellKnown
import io.element.android.libraries.wellknown.api.MapTilerConfig
fun anElementWellKnown( fun anElementWellKnown(
registrationHelperUrl: String? = null, registrationHelperUrl: String? = null,
@@ -21,6 +22,7 @@ fun anElementWellKnown(
customRecoveryPassphrase: CustomRecoveryPassphrase? = null, customRecoveryPassphrase: CustomRecoveryPassphrase? = null,
contentScannerUrl: String? = null, contentScannerUrl: String? = null,
forceDisableE2EE: Boolean? = null, forceDisableE2EE: Boolean? = null,
mapTilerConfig: MapTilerConfig? = null,
) = ElementWellKnown( ) = ElementWellKnown(
registrationHelperUrl = registrationHelperUrl, registrationHelperUrl = registrationHelperUrl,
enforceElementPro = enforceElementPro, enforceElementPro = enforceElementPro,
@@ -31,6 +33,7 @@ fun anElementWellKnown(
customRecoveryPassphrase = customRecoveryPassphrase, customRecoveryPassphrase = customRecoveryPassphrase,
contentScannerUrl = contentScannerUrl, contentScannerUrl = contentScannerUrl,
forceDisableE2EE = forceDisableE2EE, forceDisableE2EE = forceDisableE2EE,
mapTilerConfig = mapTilerConfig,
) )
fun aCustomRecoveryPassphrase( fun aCustomRecoveryPassphrase(
@@ -38,3 +41,15 @@ fun aCustomRecoveryPassphrase(
) = CustomRecoveryPassphrase( ) = CustomRecoveryPassphrase(
minCharacterCount = minCharacterCount, minCharacterCount = minCharacterCount,
) )
fun aMapTilerConfig(
apiKey: String = "test_api_key",
baseUrl: String? = null,
lightStyleId: String? = null,
darkStyleId: String? = null,
) = MapTilerConfig(
apiKey = apiKey,
baseUrl = baseUrl,
lightStyleId = lightStyleId,
darkStyleId = darkStyleId,
)
+1
View File
@@ -230,6 +230,7 @@ Compose:
- LocalEventContentValidationState - LocalEventContentValidationState
- LocalSnackbarDispatcher - LocalSnackbarDispatcher
- LocalCameraPositionState - LocalCameraPositionState
- LocalMapTilerConfig
- LocalMediaItemPresenterFactories - LocalMediaItemPresenterFactories
- LocalTimelineItemPresenterFactories - LocalTimelineItemPresenterFactories
- LocalRoomMemberProfilesCache - LocalRoomMemberProfilesCache