Skip to content

Let element enterprise be able to configure id for mapTiler. #4446

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions features/location/api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* Please see LICENSE files in the repository root for full details.
*/

import config.BuildTimeConfig
import extension.readLocalProperty

plugins {
Expand All @@ -19,24 +20,36 @@ android {
resValue(
type = "string",
name = "maptiler_api_key",
value = System.getenv("ELEMENT_ANDROID_MAPTILER_API_KEY")
?: readLocalProperty("services.maptiler.apikey")
value = if (isEnterpriseBuild) {
BuildTimeConfig.SERVICES_MAPTILER_APIKEY
} else {
System.getenv("ELEMENT_ANDROID_MAPTILER_API_KEY")
?: readLocalProperty("services.maptiler.apikey")
}
?: ""
)
resValue(
type = "string",
name = "maptiler_light_map_id",
value = System.getenv("ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID")
?: readLocalProperty("services.maptiler.lightMapId")
// fall back to maptiler's default light map.
value = if (isEnterpriseBuild) {
BuildTimeConfig.SERVICES_MAPTILER_LIGHT_MAPID
} else {
System.getenv("ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID")
?: readLocalProperty("services.maptiler.lightMapId")
}
// fall back to maptiler's default light map.
?: "basic-v2"
)
resValue(
type = "string",
name = "maptiler_dark_map_id",
value = System.getenv("ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID")
?: readLocalProperty("services.maptiler.darkMapId")
// fall back to maptiler's default dark map.
value = if (isEnterpriseBuild) {
BuildTimeConfig.SERVICES_MAPTILER_DARK_MAPID
} else {
System.getenv("ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID")
?: readLocalProperty("services.maptiler.darkMapId")
}
// fall back to maptiler's default dark map.
?: "basic-v2-dark"
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* Copyright 2025 New Vector 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

interface LocationService {
fun isServiceAvailable(): Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ fun StaticMapView(
} else {
StaticMapPlaceholder(
showProgress = collectedState.value.isLoading(),
canReload = builder.isServiceAvailable(),
contentDescription = contentDescription,
width = maxWidth,
height = maxHeight,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ internal class MapTilerStaticMapUrlBuilder(
// to keep the perceived content size constant at the expense of sharpness.
return "$MAPTILER_BASE_URL/$mapId/static/$lon,$lat,$finalZoom/${finalWidth}x${finalHeight}$scale.webp?key=$apiKey&attribution=bottomleft"
}

override fun isServiceAvailable() = apiKey.isNotEmpty()
}

private fun coerceWidthAndHeight(width: Int, height: Int, is2x: Boolean): Pair<Int, Int> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@ package io.element.android.features.location.api.internal

import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import io.element.android.compound.tokens.generated.CompoundIcons
Expand All @@ -28,12 +29,12 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.utils.BooleanProvider
import io.element.android.libraries.ui.strings.CommonStrings

@Composable
internal fun StaticMapPlaceholder(
showProgress: Boolean,
canReload: Boolean,
contentDescription: String?,
width: Dp,
height: Dp,
Expand All @@ -54,7 +55,7 @@ internal fun StaticMapPlaceholder(
)
if (showProgress) {
CircularProgressIndicator()
} else {
} else if (canReload) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Expand All @@ -70,14 +71,24 @@ internal fun StaticMapPlaceholder(

@PreviewsDayNight
@Composable
internal fun StaticMapPlaceholderPreview(
@PreviewParameter(BooleanProvider::class) values: Boolean
) = ElementPreview {
StaticMapPlaceholder(
showProgress = values,
contentDescription = null,
width = 400.dp,
height = 400.dp,
onLoadMapClick = {},
)
internal fun StaticMapPlaceholderPreview() = ElementPreview {
Column(
modifier = Modifier.padding(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
listOf(
true to false,
false to true,
false to false,
).forEach { (showProgress, canReload) ->
StaticMapPlaceholder(
showProgress = showProgress,
canReload = canReload,
contentDescription = null,
width = 400.dp,
height = 200.dp,
onLoadMapClick = {},
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ interface StaticMapUrlBuilder {
height: Int,
density: Float,
): String

fun isServiceAvailable(): Boolean
}

fun StaticMapUrlBuilder(context: Context): StaticMapUrlBuilder = MapTilerStaticMapUrlBuilder(context = context)
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,21 @@ class MapTilerStaticMapUrlBuilderTest {
darkMapId = "aDarkMapId",
)

@Test
fun `isServiceAvailable returns true if api key is not empty`() {
assertThat(builder.isServiceAvailable()).isTrue()
}

@Test
fun `isServiceAvailable returns false if api key is empty`() {
val builderWithoutKey = MapTilerStaticMapUrlBuilder(
apiKey = "",
lightMapId = "aLightMapId",
darkMapId = "aDarkMapId",
)
assertThat(builderWithoutKey.isServiceAvailable()).isFalse()
}

@Test
fun `static map 1x density`() {
assertThat(
Expand Down
1 change: 1 addition & 0 deletions features/location/impl/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ dependencies {
testImplementation(projects.libraries.testtags)
testImplementation(projects.services.analytics.test)
testImplementation(projects.features.messages.test)
testImplementation(projects.services.toolbox.test)
testImplementation(projects.tests.testutils)
testImplementation(libs.androidx.compose.ui.test.junit)
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2025 New Vector 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

import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.location.api.LocationService
import io.element.android.features.location.api.R
import io.element.android.libraries.di.AppScope
import io.element.android.services.toolbox.api.strings.StringProvider
import javax.inject.Inject

@ContributesBinding(AppScope::class)
class DefaultLocationService @Inject constructor(
private val stringProvider: StringProvider,
) : LocationService {
override fun isServiceAvailable(): Boolean {
return stringProvider.getString(R.string.maptiler_api_key).isNotEmpty()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2025 New Vector 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

import com.google.common.truth.Truth.assertThat
import io.element.android.features.location.api.R
import io.element.android.services.toolbox.test.strings.FakeStringProvider
import org.junit.Test

class DefaultLocationServiceTest {
@Test
fun `if apiKey is empty, isServiceAvailable should return false`() {
val fakeStringProvider = FakeStringProvider(
defaultResult = ""
)
val locationService = DefaultLocationService(
stringProvider = fakeStringProvider,
)
assertThat(locationService.isServiceAvailable()).isFalse()
assertThat(fakeStringProvider.lastResIdParam).isEqualTo(R.string.maptiler_api_key)
}

@Test
fun `if apiKey is not empty, isServiceAvailable should return true`() {
val locationService = DefaultLocationService(
stringProvider = FakeStringProvider(
defaultResult = "aKey"
)
)
assertThat(locationService.isServiceAvailable()).isTrue()
}
}
18 changes: 18 additions & 0 deletions features/location/test/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/

plugins {
id("io.element.android-library")
}

android {
namespace = "io.element.android.features.location.test"
}

dependencies {
implementation(projects.features.location.api)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright 2025 New Vector 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.test

import io.element.android.features.location.api.LocationService

class FakeLocationService(
private val isServiceAvailable: Boolean,
) : LocationService {
override fun isServiceAvailable() = isServiceAvailable
}
1 change: 1 addition & 0 deletions features/messages/impl/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ dependencies {
testImplementation(libs.test.turbine)
testImplementation(projects.libraries.matrix.test)
testImplementation(projects.libraries.dateformatter.test)
testImplementation(projects.features.location.test)
testImplementation(projects.features.networkmonitor.test)
testImplementation(projects.features.messages.test)
testImplementation(projects.services.analytics.test)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import io.element.android.features.call.api.CallType
import io.element.android.features.call.api.ElementCallEntryPoint
import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint
import io.element.android.features.location.api.Location
import io.element.android.features.location.api.LocationService
import io.element.android.features.location.api.SendLocationEntryPoint
import io.element.android.features.location.api.ShowLocationEntryPoint
import io.element.android.features.messages.api.MessagesEntryPoint
Expand Down Expand Up @@ -96,6 +97,7 @@ class MessagesFlowNode @AssistedInject constructor(
private val elementCallEntryPoint: ElementCallEntryPoint,
private val mediaViewerEntryPoint: MediaViewerEntryPoint,
private val analyticsService: AnalyticsService,
private val locationService: LocationService,
private val room: MatrixRoom,
private val roomMemberProfilesCache: RoomMemberProfilesCache,
private val mentionSpanTheme: MentionSpanTheme,
Expand Down Expand Up @@ -409,7 +411,7 @@ class MessagesFlowNode @AssistedInject constructor(
NavTarget.LocationViewer(
location = event.content.location,
description = event.content.description,
)
).takeIf { locationService.isServiceAvailable() }
}
else -> null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.features.analytics.plan.Composer
import im.vector.app.features.analytics.plan.Interaction
import io.element.android.features.location.api.LocationService
import io.element.android.features.messages.impl.MessagesNavigator
import io.element.android.features.messages.impl.attachments.Attachment
import io.element.android.features.messages.impl.attachments.preview.error.sendAttachmentError
Expand Down Expand Up @@ -104,6 +105,7 @@ class MessageComposerPresenter @AssistedInject constructor(
private val mediaSender: MediaSender,
private val snackbarDispatcher: SnackbarDispatcher,
private val analyticsService: AnalyticsService,
private val locationService: LocationService,
private val messageComposerContext: DefaultMessageComposerContext,
private val richTextEditorStateFactory: RichTextEditorStateFactory,
private val roomAliasSuggestionsDataSource: RoomAliasSuggestionsDataSource,
Expand Down Expand Up @@ -155,7 +157,8 @@ class MessageComposerPresenter @AssistedInject constructor(

val canShareLocation = remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
canShareLocation.value = featureFlagService.isFeatureEnabled(FeatureFlags.LocationSharing)
canShareLocation.value = featureFlagService.isFeatureEnabled(FeatureFlags.LocationSharing) &&
locationService.isServiceAvailable()
}

val canCreatePoll = remember { mutableStateOf(false) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.Composer
import im.vector.app.features.analytics.plan.Interaction
import io.element.android.features.location.api.LocationService
import io.element.android.features.location.test.FakeLocationService
import io.element.android.features.messages.impl.FakeMessagesNavigator
import io.element.android.features.messages.impl.MessagesNavigator
import io.element.android.features.messages.impl.attachments.Attachment
Expand Down Expand Up @@ -1536,6 +1538,7 @@ class MessageComposerPresenterTest {
navigator: MessagesNavigator = FakeMessagesNavigator(),
pickerProvider: PickerProvider = this.pickerProvider,
featureFlagService: FeatureFlagService = this.featureFlagService,
locationService: LocationService = FakeLocationService(true),
sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(),
mediaPreProcessor: MediaPreProcessor = this.mediaPreProcessor,
snackbarDispatcher: SnackbarDispatcher = this.snackbarDispatcher,
Expand All @@ -1558,6 +1561,7 @@ class MessageComposerPresenterTest {
mediaSender = MediaSender(mediaPreProcessor, room, InMemorySessionPreferencesStore()),
snackbarDispatcher = snackbarDispatcher,
analyticsService = analyticsService,
locationService = locationService,
messageComposerContext = DefaultMessageComposerContext(),
richTextEditorStateFactory = TestRichTextEditorStateFactory(),
roomAliasSuggestionsDataSource = FakeRoomAliasSuggestionsDataSource(),
Expand Down
1 change: 0 additions & 1 deletion libraries/pushstore/impl/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,4 @@ dependencies {
androidTestImplementation(libs.test.junit)
androidTestImplementation(libs.test.truth)
androidTestImplementation(libs.test.runner)
androidTestImplementation(projects.libraries.sessionStorage.test)
}
Loading
Loading