diff --git a/docs/maps.md b/docs/maps.md index cc00905986..789d455f22 100644 --- a/docs/maps.md +++ b/docs/maps.md @@ -27,16 +27,21 @@ Place your API key in `local.properties` with the key services.maptiler.apikey=abCd3fGhijK1mN0pQr5t ``` +Optionally you can also place your custom MapTyler style ids for light and dark maps +in the `local.properties` with the keys `services.maptiler.lightMapId` and +`services.maptiler.darkMapId`. If you don't specify these, the default MapTiler "basic-v2" +styles will be used. + ## Making releasable builds with MapTiler To insert the MapTiler API key when building an APK, set the `ELEMENT_ANDROID_MAPTILER_API_KEY` environment variable in your build -environment. +environment. +If you've added custom styles also set the `ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID` +and `ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID` environment variables accordingly. ## Using other map sources or MapTiler styles -If you wish to use an alternative map provider, or custom MapTiler styles, -you can customise the functions in -`features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapUrls.kt`. -We've kept this file small and self contained to minimise the chances of merge -collisions in forks. +If you wish to use an alternative map provider, you can provide your own implementations of +`TileServerStyleUriBuilder` and `StaticMapUrlBuilder` in +`features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/`. diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt index 03aa21b4c8..39b3e8a9a1 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt @@ -29,16 +29,16 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import coil.compose.AsyncImagePainter import coil.compose.rememberAsyncImagePainter import coil.request.ImageRequest 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.staticMapUrl import io.element.android.libraries.designsystem.preview.DayNightPreviews import io.element.android.libraries.designsystem.preview.ElementPreview -import io.element.android.libraries.designsystem.text.toDp import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.theme.ElementTheme import timber.log.Timber @@ -65,23 +65,22 @@ fun StaticMapView( ) { val context = LocalContext.current var retryHash by remember { mutableStateOf(0) } + val builder = remember { StaticMapUrlBuilder(context) } val painter = rememberAsyncImagePainter( model = if (constraints.isZero) { // Avoid building a URL if any of the size constraints is zero (else it will thrown an exception). null } else { - ImageRequest.Builder(LocalContext.current) + ImageRequest.Builder(context) .data( - staticMapUrl( - context = context, + builder.build( lat = lat, lon = lon, zoom = zoom, darkMode = darkMode, - // Size the map based on DP rather than pixels, as otherwise the features and attribution - // end up being illegibly tiny on high density displays. - width = constraints.maxWidth.toDp().value.toInt(), - height = constraints.maxHeight.toDp().value.toInt(), + width = constraints.maxWidth, + height = constraints.maxHeight, + density = LocalDensity.current.density, ) ) .size(width = constraints.maxWidth, height = constraints.maxHeight) diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerConfig.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerConfig.kt new file mode 100644 index 0000000000..9e20d5179b --- /dev/null +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerConfig.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.location.api.internal + +import android.content.Context +import io.element.android.features.location.api.R + +internal const val MAPTILER_BASE_URL = "https://api.maptiler.com/maps" + +internal fun Context.mapId(darkMode: Boolean) = when (darkMode) { + true -> getString(R.string.maptiler_dark_map_id) + false -> getString(R.string.maptiler_light_map_id) +} + +internal val Context.apiKey: String + get() = getString(R.string.maptiler_api_key) diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilder.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilder.kt new file mode 100644 index 0000000000..72fcaeb06c --- /dev/null +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilder.kt @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.location.api.internal + +import android.content.Context +import kotlin.math.roundToInt + +/** + * Builds an URL for MapTiler's Static Maps API. + * + * https://docs.maptiler.com/cloud/api/static-maps/ + */ +internal class MapTilerStaticMapUrlBuilder( + private val apiKey: String, + private val lightMapId: String, + private val darkMapId: String, +) : StaticMapUrlBuilder { + + constructor(context: Context) : this( + apiKey = context.apiKey, + lightMapId = context.mapId(darkMode = false), + darkMapId = context.mapId(darkMode = true), + ) + + override fun build( + lat: Double, + lon: Double, + zoom: Double, + darkMode: Boolean, + width: Int, + height: Int, + density: Float + ): String { + val mapId = if (darkMode) darkMapId else lightMapId + val zoom = zoom.coerceIn(zoomRange) + + // Request @2x density for xhdpi and above (xhdpi == 320dpi == 2x density). + val is2x = density >= 2 + + // Scale requested width/height according to the reported display density. + val (width, height) = coerceWidthAndHeight( + width = (width / density).roundToInt(), + height = (height / density).roundToInt(), + is2x = is2x, + ) + + val scale = if (is2x) "@2x" else "" + + // Since Maptiler doesn't support arbitrary dpi scaling, we stick to 2x sized + // images even on displays with density higher than 2x, thereby yielding an + // image smaller than the available space in pixels. + // The resulting image will have to be scaled to fit the available space in order + // to keep the perceived content size constant at the expense of sharpness. + return "$MAPTILER_BASE_URL/${mapId}/static/${lon},${lat},${zoom}/${width}x${height}${scale}.webp?key=${apiKey}&attribution=bottomleft" + } +} + +private fun coerceWidthAndHeight(width: Int, height: Int, is2x: Boolean): Pair { + if (width <= 0 || height <= 0) { + // This effectively yields an URL which asks for a 0x0 image which will result in an HTTP error, + // but it's better than e.g. asking for a 1x1 image which would be unreadable and increase usage costs. + return 0 to 0 + } + val aspectRatio = width.toDouble() / height.toDouble() + val range = if (is2x) widthHeightRange2x else widthHeightRange + return if (width >= height) { + width.coerceIn(range).let { width -> + width to (width / aspectRatio).roundToInt() + } + } else { + height.coerceIn(range).let { height -> + (height * aspectRatio).roundToInt() to height + } + } +} + +private val widthHeightRange = 1..2048 // API will error if outside 1-2048 range @1x. +private val widthHeightRange2x = 1..1024 // API will error if outside 1-1024 range @2x. +private val zoomRange = 0.0..22.0 // API will error if outside 0-22 range. diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilder.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilder.kt new file mode 100644 index 0000000000..6972e45330 --- /dev/null +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilder.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:JvmName("TileServerStyleUriBuilderKt") + +package io.element.android.features.location.api.internal + +import android.content.Context + +internal class MapTilerTileServerStyleUriBuilder( + private val apiKey: String, + private val lightMapId: String, + private val darkMapId: String, +) : TileServerStyleUriBuilder { + + constructor(context: Context) : this( + apiKey = context.apiKey, + lightMapId = context.mapId(darkMode = false), + darkMapId = context.mapId(darkMode = true), + ) + + override fun build(darkMode: Boolean): String { + val mapId = if (darkMode) darkMapId else lightMapId + return "${MAPTILER_BASE_URL}/${mapId}/style.json?key=${apiKey}" + } +} diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapUrls.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapUrls.kt deleted file mode 100644 index 2640896a78..0000000000 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapUrls.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2023 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.element.android.features.location.api.internal - -import android.content.Context -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.platform.LocalContext -import io.element.android.features.location.api.R -import io.element.android.libraries.theme.ElementTheme - -/** - * Provides the URL to an image that contains a statically-generated map of the given location. - */ -fun staticMapUrl( - context: Context, - lat: Double, - lon: Double, - zoom: Double, - width: Int, - height: Int, - darkMode: Boolean, -): String { - return "${context.baseUrl(darkMode)}/static/${lon},${lat},${zoom}/${width}x${height}@2x.webp?key=${context.apiKey}&attribution=bottomleft" -} - -/** - * Utility function to remember the tile server URL based on the current theme. - */ -@Composable -fun rememberTileStyleUrl(): String { - val context = LocalContext.current - val darkMode = !ElementTheme.isLightTheme - return remember(darkMode) { - tileStyleUrl( - context = context, - darkMode = darkMode - ) - } -} - -/** - * Provides the URL to a MapLibre style document, used for rendering dynamic maps. - */ -private fun tileStyleUrl( - context: Context, - darkMode: Boolean, -): String { - return "${context.baseUrl(darkMode)}/style.json?key=${context.apiKey}" -} - -private fun Context.baseUrl(darkMode: Boolean) = - "https://api.maptiler.com/maps/" + - if (darkMode) - getString(R.string.maptiler_dark_map_id) - else - getString(R.string.maptiler_light_map_id) - -private val Context.apiKey: String - get() = getString(R.string.maptiler_api_key) diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapUrlBuilder.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapUrlBuilder.kt new file mode 100644 index 0000000000..8c29b5c13d --- /dev/null +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapUrlBuilder.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.location.api.internal + +import android.content.Context + +/** + * Builds an URL for a 3rd party service provider static maps API. + */ +interface StaticMapUrlBuilder { + fun build( + lat: Double, + lon: Double, + zoom: Double, + darkMode: Boolean, + width: Int, + height: Int, + density: Float, + ): String +} + +fun StaticMapUrlBuilder(context: Context): StaticMapUrlBuilder = MapTilerStaticMapUrlBuilder(context = context) diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/TileServerStyleUriBuilder.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/TileServerStyleUriBuilder.kt new file mode 100644 index 0000000000..ece32cbf5a --- /dev/null +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/TileServerStyleUriBuilder.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.location.api.internal + +import android.content.Context +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import io.element.android.libraries.theme.ElementTheme + +/** + * Builds a style URI for a MapLibre compatible tile server. + * + * Used for rendering dynamic maps. + */ +interface TileServerStyleUriBuilder { + fun build( + darkMode: Boolean, + ): String +} + +fun TileServerStyleUriBuilder(context: Context): TileServerStyleUriBuilder = MapTilerTileServerStyleUriBuilder(context = context) + +/** + * Provides and remembers a style URI for a MapLibre compatible tile server. + * + * Used for rendering dynamic maps. + */ +@Composable +fun rememberTileStyleUrl(): String { + val context = LocalContext.current + val darkMode = !ElementTheme.isLightTheme + return remember(darkMode) { + TileServerStyleUriBuilder(context).build(darkMode) + } +} diff --git a/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilderTest.kt b/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilderTest.kt new file mode 100644 index 0000000000..c359777ee7 --- /dev/null +++ b/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilderTest.kt @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.location.api.internal + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class MapTilerStaticMapUrlBuilderTest { + + private val builder = MapTilerStaticMapUrlBuilder( + apiKey = "anApiKey", + lightMapId = "aLightMapId", + darkMapId = "aDarkMapId", + ) + + @Test + fun `static map 1x density`() { + assertThat( + builder.build( + lat = 1.23, + lon = -4.56, + zoom = 7.8, + darkMode = false, + width = 800, + height = 600, + density = 1f, + ) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/800x600.webp?key=anApiKey&attribution=bottomleft") + } + + @Test + fun `static map 1,5x density`() { + assertThat( + builder.build( + lat = 1.23, + lon = -4.56, + zoom = 7.8, + darkMode = false, + width = 1200, + height = 900, + density = 1.5f, + ) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/800x600.webp?key=anApiKey&attribution=bottomleft") + } + + @Test + fun `static map 2x density`() { + assertThat( + builder.build( + lat = 1.23, + lon = -4.56, + zoom = 7.8, + darkMode = false, + width = 1600, + height = 1200, + density = 2f, + ) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/800x600@2x.webp?key=anApiKey&attribution=bottomleft") + } + + @Test + fun `static map 3x density`() { + assertThat( + builder.build( + lat = 1.23, + lon = -4.56, + zoom = 7.8, + darkMode = false, + width = 2400, + height = 1800, + density = 3f, + ) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/800x600@2x.webp?key=anApiKey&attribution=bottomleft") + } + + @Test + fun `too big image is coerced keeping aspect ratio`() { + assertThat( + builder.build( + lat = 1.23, + lon = -4.56, + zoom = 7.8, + darkMode = false, + width = 4096, + height = 2048, + density = 1f, + ) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/2048x1024.webp?key=anApiKey&attribution=bottomleft") + + assertThat( + builder.build( + lat = 1.23, + lon = -4.56, + zoom = 7.8, + darkMode = false, + width = 2048, + height = 4096, + density = 1f, + ) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/1024x2048.webp?key=anApiKey&attribution=bottomleft") + + assertThat( + builder.build( + lat = 1.23, + lon = -4.56, + zoom = 7.8, + darkMode = false, + width = 4096, + height = 2048, + density = 2f, + ) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/1024x512@2x.webp?key=anApiKey&attribution=bottomleft") + + assertThat( + builder.build( + lat = 1.23, + lon = -4.56, + zoom = 7.8, + darkMode = false, + width = 2048, + height = 4096, + density = 2f, + ) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/512x1024@2x.webp?key=anApiKey&attribution=bottomleft") + + assertThat( + builder.build( + lat = 1.23, + lon = -4.56, + zoom = 7.8, + darkMode = false, + width = Int.MAX_VALUE, + height = Int.MAX_VALUE, + density = 2f, + ) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/1024x1024@2x.webp?key=anApiKey&attribution=bottomleft") + } + + @Test + fun `too small image is coerced to 0x0`() { + assertThat( + builder.build( + lat = 1.23, + lon = -4.56, + zoom = 7.8, + darkMode = false, + width = 0, + height = 0, + density = 1f, + ) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/0x0.webp?key=anApiKey&attribution=bottomleft") + + assertThat( + builder.build( + lat = 1.23, + lon = -4.56, + zoom = 7.8, + darkMode = false, + width = 0, + height = 0, + density = 2f, + ) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/0x0@2x.webp?key=anApiKey&attribution=bottomleft") + + assertThat( + builder.build( + lat = 1.23, + lon = -4.56, + zoom = 7.8, + darkMode = false, + width = Int.MIN_VALUE, + height = Int.MIN_VALUE, + density = 1f, + ) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/0x0.webp?key=anApiKey&attribution=bottomleft") + } +} diff --git a/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilderTest.kt b/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilderTest.kt new file mode 100644 index 0000000000..abff83c582 --- /dev/null +++ b/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilderTest.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.location.api.internal + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class MapTilerTileServerStyleUriBuilderTest { + + private val builder = MapTilerTileServerStyleUriBuilder( + apiKey = "anApiKey", + lightMapId = "aLightMapId", + darkMapId = "aDarkMapId", + ) + + @Test + fun `light map uri`() { + assertThat( + builder.build(darkMode = false) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/style.json?key=anApiKey") + } + + @Test + fun `dark map uri`() { + assertThat( + builder.build(darkMode = true) + ).isEqualTo("https://api.maptiler.com/maps/aDarkMapId/style.json?key=anApiKey") + } +} diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationView.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationView.kt index 7726bbf9cc..0e114a43b9 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationView.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationView.kt @@ -40,7 +40,6 @@ import com.mapbox.mapboxsdk.camera.CameraPosition import com.mapbox.mapboxsdk.geometry.LatLng import io.element.android.features.location.api.internal.rememberTileStyleUrl import io.element.android.features.location.impl.MapDefaults -import io.element.android.features.location.impl.send.SendLocationState import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight