Start using LocationPinMarker in Share and Show locations

This commit is contained in:
ganfra
2026-03-03 22:05:17 +01:00
parent ec1d6ebabb
commit f4bf596e3b
3 changed files with 74 additions and 52 deletions

View File

@@ -0,0 +1,37 @@
/*
* 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.impl.common.ui
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import org.maplibre.compose.camera.CameraState
import org.maplibre.spatialk.geojson.Position
@Composable
fun MapProjected(
target: Position,
cameraState: CameraState,
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
Box(
modifier = modifier
.graphicsLayer {
cameraState.position
val offset = cameraState.projection?.screenLocationFromPosition(target)
if (offset != null) {
translationX = offset.x.toPx() - size.width / 2
translationY = offset.y.toPx() - size.height
}
}
) {
content()
}
}

View File

@@ -8,7 +8,6 @@
package io.element.android.features.location.impl.share
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
@@ -28,7 +27,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
@@ -42,18 +40,20 @@ import io.element.android.features.location.impl.common.PermissionRationaleDialo
import io.element.android.features.location.impl.common.ui.LocationFloatingActionButton
import io.element.android.features.location.impl.common.ui.MapBottomSheetScaffold
import io.element.android.features.location.impl.common.ui.UserLocationPuck
import io.element.android.libraries.designsystem.components.LocationPinMarker
import io.element.android.libraries.designsystem.components.PinVariant
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.dialogs.ListDialog
import io.element.android.libraries.designsystem.components.list.ListItemContent
import io.element.android.libraries.designsystem.components.list.RadioButtonListItem
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconSource
import io.element.android.libraries.designsystem.theme.components.ListItem
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.designsystem.utils.CommonDrawables
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.ui.strings.CommonStrings
import org.maplibre.compose.camera.CameraMoveReason
import org.maplibre.compose.camera.CameraPosition
@@ -155,10 +155,13 @@ fun ShareLocationView(
.fillMaxSize()
.padding(sheetPadding)
) {
Icon(
resourceId = CommonDrawables.pin,
contentDescription = null,
tint = Color.Unspecified,
val variant = if (state.trackUserLocation) {
PinVariant.UserLocation(isLive = false, avatarData = state.currentUser.getAvatarData(AvatarSize.SelectedUser))
} else {
PinVariant.PinnedLocation
}
LocationPinMarker(
variant = variant,
modifier = Modifier.centerBottomEdge(this),
)
}

View File

@@ -9,6 +9,7 @@
package io.element.android.features.location.impl.show
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SheetValue
@@ -18,22 +19,26 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.compound.tokens.generated.TypographyTokens
import io.element.android.features.location.api.ShowLocationMode
import io.element.android.features.location.impl.R
import io.element.android.features.location.impl.common.MapDefaults
import io.element.android.features.location.impl.common.PermissionDeniedDialog
import io.element.android.features.location.impl.common.PermissionRationaleDialog
import io.element.android.features.location.impl.common.ui.LocationFloatingActionButton
import io.element.android.features.location.impl.common.ui.MapBottomSheetScaffold
import io.element.android.features.location.impl.common.ui.MapProjected
import io.element.android.features.location.impl.common.ui.UserLocationPuck
import io.element.android.libraries.designsystem.components.LocationPinMarker
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.AvatarSize
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
@@ -41,19 +46,15 @@ import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.api.room.location.AssetType
import io.element.android.libraries.ui.strings.CommonStrings
import org.maplibre.compose.camera.CameraMoveReason
import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.camera.rememberCameraState
import org.maplibre.compose.expressions.dsl.image
import org.maplibre.compose.layers.SymbolLayer
import org.maplibre.compose.location.DesiredAccuracy
import org.maplibre.compose.location.rememberDefaultLocationProvider
import org.maplibre.compose.location.rememberNullLocationProvider
import org.maplibre.compose.location.rememberUserLocationState
import org.maplibre.compose.sources.GeoJsonData
import org.maplibre.compose.sources.rememberGeoJsonSource
import org.maplibre.spatialk.geojson.Point
import org.maplibre.spatialk.geojson.Position
import kotlin.time.Duration.Companion.minutes
@@ -105,10 +106,9 @@ fun ShowLocationView(
}
val scaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = rememberStandardBottomSheetState()
bottomSheetState = rememberStandardBottomSheetState(skipHiddenState = false, initialValue = SheetValue.Hidden)
)
MapBottomSheetScaffold(
sheetPeekHeight = 180.dp,
scaffoldState = scaffoldState,
cameraState = cameraState,
modifier = modifier,
@@ -132,56 +132,38 @@ fun ShowLocationView(
}
)
},
sheetContent = {
when (val mode = state.mode) {
is ShowLocationMode.Static -> {
Text(
text = mode.senderName,
textAlign = TextAlign.Center,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
style = TypographyTokens.fontBodyMdRegular,
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
)
}
ShowLocationMode.Live -> {
// TODO: Show list of active live location sharers
}
}
},
mapContent = {
UserLocationPuck(
cameraState = cameraState,
locationState = userLocationState,
trackUserLocation = state.isTrackMyLocation
)
},
overlayContent = {
when (val mode = state.mode) {
is ShowLocationMode.Static -> {
val senderLocation = rememberGeoJsonSource(
data = GeoJsonData.Features(
Point(
Position(
latitude = mode.location.lat,
longitude = mode.location.lon
)
)
val pinVariant = if (mode.assetType == AssetType.PIN) {
PinVariant.PinnedLocation
} else {
PinVariant.UserLocation(
avatarData = AvatarData(mode.senderId.value, mode.senderName, mode.senderAvatarUrl, AvatarSize.UserListItem),
isLive = false
)
}
val position = Position(
latitude = mode.location.lat,
longitude = mode.location.lon
)
val marker = painterResource(R.drawable.pin_small)
SymbolLayer(
id = "sender_location",
source = senderLocation,
iconImage = image(marker)
)
MapProjected(target = position, cameraState = cameraState) {
LocationPinMarker(variant = pinVariant)
}
}
ShowLocationMode.Live -> {
// TODO: Show pins for all active live location sharers
}
}
},
overlayContent = {
LocationFloatingActionButton(
isMapCenteredOnUser = state.isTrackMyLocation,
onClick = { state.eventSink(ShowLocationEvents.TrackMyLocation(true)) },