Merge pull request #886 from vector-im/feature/cjs/location-api-key
This commit is contained in:
commit
1d99189955
9 changed files with 128 additions and 218 deletions
|
|
@ -34,9 +34,8 @@ 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.AttributionPlacement
|
||||
import io.element.android.features.location.api.internal.StaticMapPlaceholder
|
||||
import io.element.android.features.location.api.internal.buildStaticMapsApiUrl
|
||||
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
|
||||
|
|
@ -64,6 +63,7 @@ fun StaticMapView(
|
|||
modifier = modifier,
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var retryHash by remember { mutableStateOf(0) }
|
||||
val painter = rememberAsyncImagePainter(
|
||||
model = if (constraints.isZero) {
|
||||
|
|
@ -72,17 +72,16 @@ fun StaticMapView(
|
|||
} else {
|
||||
ImageRequest.Builder(LocalContext.current)
|
||||
.data(
|
||||
buildStaticMapsApiUrl(
|
||||
staticMapUrl(
|
||||
context = context,
|
||||
lat = lat,
|
||||
lon = lon,
|
||||
desiredZoom = zoom,
|
||||
zoom = zoom,
|
||||
darkMode = darkMode,
|
||||
attributionPlacement = AttributionPlacement.BottomLeft,
|
||||
// Size the map based on DP rather than pixels, as otherwise the features and attribution
|
||||
// end up being illegibly tiny on high density displays.
|
||||
desiredWidth = constraints.maxWidth.toDp().value.toInt(),
|
||||
desiredHeight = constraints.maxHeight.toDp().value.toInt(),
|
||||
doubleScale = true,
|
||||
width = constraints.maxWidth.toDp().value.toInt(),
|
||||
height = constraints.maxHeight.toDp().value.toInt(),
|
||||
)
|
||||
)
|
||||
.size(width = constraints.maxWidth, height = constraints.maxHeight)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
/**
|
||||
* 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 "${baseUrl(darkMode)}/static/${lon},${lat},${zoom}/${width}x${height}@2x.webp?key=${context.apiKey}&attribution=bottomleft"
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the URL to a MapLibre style document, used for rendering dynamic maps.
|
||||
*/
|
||||
fun tileStyleUrl(
|
||||
context: Context,
|
||||
darkMode: Boolean,
|
||||
): String {
|
||||
return "${baseUrl(darkMode)}/style.json?key=${context.apiKey}"
|
||||
}
|
||||
|
||||
private fun baseUrl(darkMode: Boolean) =
|
||||
"https://api.maptiler.com/maps/" +
|
||||
if (darkMode)
|
||||
"dea61faf-292b-4774-9660-58fcef89a7f3"
|
||||
else
|
||||
"9bc819c8-e627-474a-a348-ec144fe3d810"
|
||||
|
||||
private val Context.apiKey: String
|
||||
get() = getString(R.string.maptiler_api_key)
|
||||
|
|
@ -1,91 +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 kotlin.math.roundToInt
|
||||
|
||||
private const val API_KEY = "fU3vlMsMn4Jb6dnEIFsx"
|
||||
private const val BASE_URL = "https://api.maptiler.com"
|
||||
private const val LIGHT_MAP_ID = "9bc819c8-e627-474a-a348-ec144fe3d810"
|
||||
private const val DARK_MAP_ID = "dea61faf-292b-4774-9660-58fcef89a7f3"
|
||||
private const val STATIC_MAP_FORMAT = "webp"
|
||||
private const val STATIC_MAP_SCALE_2X = "@2x"
|
||||
private const val STATIC_MAP_MAX_WIDTH_HEIGHT = 2048
|
||||
private const val STATIC_MAP_MAX_ZOOM = 22.0
|
||||
|
||||
fun buildTileServerUrl(
|
||||
darkMode: Boolean
|
||||
): String = if (!darkMode) {
|
||||
"$BASE_URL/maps/$LIGHT_MAP_ID/style.json?key=$API_KEY"
|
||||
} else {
|
||||
"$BASE_URL/maps/$DARK_MAP_ID/style.json?key=$API_KEY"
|
||||
}
|
||||
|
||||
internal enum class AttributionPlacement(val value: String) {
|
||||
BottomRight("bottomright"),
|
||||
BottomLeft("bottomleft"),
|
||||
TopLeft("topleft"),
|
||||
TopRight("topright"),
|
||||
Hidden("false"),
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a valid URL for maptiler.com static map api based on the given params.
|
||||
*
|
||||
* Coerces width and height to the API maximum of 2048 keeping the requested aspect ratio.
|
||||
* Coerces zoom to the API maximum of 22.
|
||||
*
|
||||
* NB: This will throw if either width or height are <= 0. You need to handle this case upstream
|
||||
* (hint: views can't have negative width or height but can have 0 width or height sometimes).
|
||||
*/
|
||||
internal fun buildStaticMapsApiUrl(
|
||||
lat: Double,
|
||||
lon: Double,
|
||||
desiredZoom: Double,
|
||||
desiredWidth: Int,
|
||||
desiredHeight: Int,
|
||||
darkMode: Boolean,
|
||||
doubleScale: Boolean,
|
||||
attributionPlacement: AttributionPlacement,
|
||||
): String {
|
||||
require(desiredWidth > 0 && desiredHeight > 0) {
|
||||
"Width ($desiredHeight) and height ($desiredHeight) must be > 0"
|
||||
}
|
||||
require(desiredZoom >= 0) { "Zoom ($desiredZoom) must be >= 0" }
|
||||
val zoom = desiredZoom.coerceAtMost(STATIC_MAP_MAX_ZOOM) // API will error if outside 0-22 range.
|
||||
val width: Int
|
||||
val height: Int
|
||||
if (desiredWidth <= STATIC_MAP_MAX_WIDTH_HEIGHT && desiredHeight <= STATIC_MAP_MAX_WIDTH_HEIGHT) {
|
||||
width = desiredWidth
|
||||
height = desiredHeight
|
||||
} else {
|
||||
val aspectRatio = desiredWidth.toDouble() / desiredHeight.toDouble()
|
||||
if (desiredWidth >= desiredHeight) {
|
||||
width = desiredWidth.coerceAtMost(STATIC_MAP_MAX_WIDTH_HEIGHT)
|
||||
height = (width / aspectRatio).roundToInt()
|
||||
} else {
|
||||
height = desiredHeight.coerceAtMost(STATIC_MAP_MAX_WIDTH_HEIGHT)
|
||||
width = (height * aspectRatio).roundToInt()
|
||||
}
|
||||
}
|
||||
|
||||
val mapId = if (darkMode) DARK_MAP_ID else LIGHT_MAP_ID
|
||||
val scaleSuffix = if (doubleScale) STATIC_MAP_SCALE_2X else ""
|
||||
|
||||
return "$BASE_URL/maps/$mapId/static/${lon},${lat},${zoom}/${width}x${height}${scaleSuffix}.$STATIC_MAP_FORMAT" +
|
||||
"?key=$API_KEY&attribution=${attributionPlacement.value}"
|
||||
}
|
||||
|
|
@ -1,117 +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 com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
|
||||
class BuildStaticMapsApiUrlTest {
|
||||
@Test
|
||||
fun `buildStaticMapsApiUrl builds light mode url`() {
|
||||
assertThat(
|
||||
buildStaticMapsApiUrl(
|
||||
lat = 1.234,
|
||||
lon = 5.678,
|
||||
desiredZoom = 1.2,
|
||||
desiredWidth = 100,
|
||||
desiredHeight = 200,
|
||||
darkMode = false,
|
||||
doubleScale = false,
|
||||
attributionPlacement = AttributionPlacement.BottomLeft,
|
||||
)
|
||||
).isEqualTo(
|
||||
"https://api.maptiler.com/maps/9bc819c8-e627-474a-a348-ec144fe3d810/static/5.678,1.234,1.2/100x200.webp" +
|
||||
"?key=fU3vlMsMn4Jb6dnEIFsx&attribution=bottomleft"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `buildStaticMapsApiUrl builds dark mode url`() {
|
||||
assertThat(
|
||||
buildStaticMapsApiUrl(
|
||||
lat = 1.234,
|
||||
lon = 5.678,
|
||||
desiredZoom = 1.2,
|
||||
desiredWidth = 100,
|
||||
desiredHeight = 200,
|
||||
darkMode = true,
|
||||
doubleScale = false,
|
||||
attributionPlacement = AttributionPlacement.BottomLeft,
|
||||
)
|
||||
).isEqualTo(
|
||||
"https://api.maptiler.com/maps/dea61faf-292b-4774-9660-58fcef89a7f3/static/5.678,1.234,1.2/100x200.webp" +
|
||||
"?key=fU3vlMsMn4Jb6dnEIFsx&attribution=bottomleft"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `buildStaticMapsApiUrl builds double scale mode url`() {
|
||||
assertThat(
|
||||
buildStaticMapsApiUrl(
|
||||
lat = 1.234,
|
||||
lon = 5.678,
|
||||
desiredZoom = 1.2,
|
||||
desiredWidth = 100,
|
||||
desiredHeight = 200,
|
||||
darkMode = false,
|
||||
doubleScale = true,
|
||||
attributionPlacement = AttributionPlacement.BottomLeft,
|
||||
)
|
||||
).isEqualTo(
|
||||
"https://api.maptiler.com/maps/9bc819c8-e627-474a-a348-ec144fe3d810/static/5.678,1.234,1.2/100x200@2x.webp" +
|
||||
"?key=fU3vlMsMn4Jb6dnEIFsx&attribution=bottomleft"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `buildStaticMapsApiUrl builds no attribution url`() {
|
||||
assertThat(
|
||||
buildStaticMapsApiUrl(
|
||||
lat = 1.234,
|
||||
lon = 5.678,
|
||||
desiredZoom = 1.2,
|
||||
desiredWidth = 100,
|
||||
desiredHeight = 200,
|
||||
darkMode = false,
|
||||
doubleScale = false,
|
||||
attributionPlacement = AttributionPlacement.Hidden,
|
||||
)
|
||||
).isEqualTo(
|
||||
"https://api.maptiler.com/maps/9bc819c8-e627-474a-a348-ec144fe3d810/static/5.678,1.234,1.2/100x200.webp" +
|
||||
"?key=fU3vlMsMn4Jb6dnEIFsx&attribution=false"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `buildStaticMapsApiUrl coerces zoom at 22 and width and height at max 2048 keeping aspect ratio`() {
|
||||
assertThat(
|
||||
buildStaticMapsApiUrl(
|
||||
lat = 1.234,
|
||||
lon = 5.678,
|
||||
desiredZoom = 100.0,
|
||||
desiredWidth = 8192,
|
||||
desiredHeight = 4096,
|
||||
darkMode = false,
|
||||
doubleScale = false,
|
||||
attributionPlacement = AttributionPlacement.BottomLeft,
|
||||
)
|
||||
).isEqualTo(
|
||||
"https://api.maptiler.com/maps/9bc819c8-e627-474a-a348-ec144fe3d810/static/5.678,1.234,22.0/2048x1024.webp" +
|
||||
"?key=fU3vlMsMn4Jb6dnEIFsx&attribution=bottomleft"
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue