From f4bf596e3bfb28911fd1b6ae564581695b883ec5 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 3 Mar 2026 22:05:17 +0100 Subject: [PATCH] Start using LocationPinMarker in Share and Show locations --- .../location/impl/common/ui/MapProjected.kt | 37 ++++++++++ .../location/impl/share/ShareLocationView.kt | 19 ++--- .../location/impl/show/ShowLocationView.kt | 70 +++++++------------ 3 files changed, 74 insertions(+), 52 deletions(-) create mode 100644 features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/ui/MapProjected.kt diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/ui/MapProjected.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/ui/MapProjected.kt new file mode 100644 index 0000000000..503e6ea3d7 --- /dev/null +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/ui/MapProjected.kt @@ -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() + } +} diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/share/ShareLocationView.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/share/ShareLocationView.kt index f1e782cb26..02b209ae43 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/share/ShareLocationView.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/share/ShareLocationView.kt @@ -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), ) } 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 fc00f00c79..69ac04e0c5 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 @@ -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)) },