Start cleaning up location code

This commit is contained in:
ganfra 2026-03-06 16:38:06 +01:00
parent e6b18668a6
commit 4a271f339a
5 changed files with 80 additions and 85 deletions

View file

@ -36,6 +36,8 @@ import org.maplibre.spatialk.geojson.Point
import org.maplibre.spatialk.geojson.Position
import org.maplibre.spatialk.geojson.toJson
private const val LOCATION_MARKER_ID = "LOCATION_MARKER_ID"
/**
* Data class representing a marker on the map.
*
@ -81,7 +83,7 @@ fun LocationPinMarkers(
id = JsonPrimitive(marker.id),
geometry = Point(Position(marker.location.lon, marker.location.lat)),
properties = mapOf(
"id" to JsonPrimitive(marker.id),
LOCATION_MARKER_ID to JsonPrimitive(marker.id),
)
)
}
@ -153,7 +155,7 @@ private fun LocationPinMarkerLayer(
SymbolLayer(
id = "pin-marker-${marker.id}",
source = source,
filter = !feature.has("point_count") and (feature["id"].asString() eq const(marker.id)),
filter = !feature.has("point_count") and (feature[LOCATION_MARKER_ID].asString() eq const(marker.id)),
iconImage = image(imageBitmap),
iconAnchor = const(SymbolAnchor.Bottom),
iconAllowOverlap = const(true),

View file

@ -24,8 +24,13 @@ import io.element.android.features.location.impl.common.actions.LocationActions
import io.element.android.features.location.impl.common.permissions.PermissionsEvents
import io.element.android.features.location.impl.common.permissions.PermissionsPresenter
import io.element.android.features.location.impl.common.permissions.PermissionsState
import io.element.android.features.location.impl.common.ui.LocationMarkerData
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.meta.BuildMeta
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.matrix.api.room.location.AssetType
@AssistedInject
class ShowLocationPresenter(
@ -88,9 +93,38 @@ class ShowLocationPresenter(
}
}
val markers = remember(mode) {
when (mode) {
is ShowLocationMode.Static -> {
val pinVariant = if (mode.assetType == AssetType.PIN) {
PinVariant.PinnedLocation
} else {
PinVariant.UserLocation(
avatarData = AvatarData(
id = mode.senderId.value,
name = mode.senderName,
url = mode.senderAvatarUrl,
size = AvatarSize.UserListItem,
),
isLive = false,
)
}
listOf(
LocationMarkerData(
id = mode.senderId.value,
location = mode.location,
variant = pinVariant,
)
)
}
ShowLocationMode.Live -> emptyList()
}
}
return ShowLocationState(
permissionDialog = permissionDialog,
mode = mode,
markers = markers,
hasLocationPermission = permissionsState.isAnyGranted,
isTrackMyLocation = isTrackMyLocation,
appName = appName,

View file

@ -9,10 +9,12 @@
package io.element.android.features.location.impl.show
import io.element.android.features.location.api.ShowLocationMode
import io.element.android.features.location.impl.common.ui.LocationMarkerData
data class ShowLocationState(
val permissionDialog: Dialog,
val mode: ShowLocationMode,
val markers: List<LocationMarkerData>,
val hasLocationPermission: Boolean,
val isTrackMyLocation: Boolean,
val appName: String,

View file

@ -11,6 +11,10 @@ package io.element.android.features.location.impl.show
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.location.api.Location
import io.element.android.features.location.api.ShowLocationMode
import io.element.android.features.location.impl.common.ui.LocationMarkerData
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.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.location.AssetType
@ -50,18 +54,44 @@ class ShowLocationStateProvider : PreviewParameterProvider<ShowLocationState> {
fun aShowLocationState(
permissionDialog: ShowLocationState.Dialog = ShowLocationState.Dialog.None,
mode: ShowLocationMode = aStaticLocationMode(),
markers: List<LocationMarkerData>? = null,
hasLocationPermission: Boolean = false,
isTrackMyLocation: Boolean = false,
appName: String = APP_NAME,
eventSink: (ShowLocationEvents) -> Unit = {},
) = ShowLocationState(
permissionDialog = permissionDialog,
mode = mode,
hasLocationPermission = hasLocationPermission,
isTrackMyLocation = isTrackMyLocation,
appName = appName,
eventSink = eventSink,
)
): ShowLocationState {
val effectiveMarkers = markers ?: when (mode) {
is ShowLocationMode.Static -> listOf(
LocationMarkerData(
id = mode.senderId.value,
location = mode.location,
variant = if (mode.assetType == AssetType.PIN) {
PinVariant.PinnedLocation
} else {
PinVariant.UserLocation(
avatarData = AvatarData(
id = mode.senderId.value,
name = mode.senderName,
url = mode.senderAvatarUrl,
size = AvatarSize.UserListItem,
),
isLive = true,
)
}
)
)
ShowLocationMode.Live -> emptyList()
}
return ShowLocationState(
permissionDialog = permissionDialog,
mode = mode,
markers = effectiveMarkers,
hasLocationPermission = hasLocationPermission,
isTrackMyLocation = isTrackMyLocation,
appName = appName,
eventSink = eventSink,
)
}
fun aStaticLocationMode(
location: Location = Location(1.23, 2.34, 4f),

View file

@ -15,33 +15,26 @@ import androidx.compose.material3.rememberBottomSheetScaffoldState
import androidx.compose.material3.rememberStandardBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.location.api.Location
import io.element.android.features.location.api.ShowLocationMode
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.LocationMarkerData
import io.element.android.features.location.impl.common.ui.LocationPinMarkers
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.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
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.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
@ -51,10 +44,6 @@ import org.maplibre.compose.location.rememberDefaultLocationProvider
import org.maplibre.compose.location.rememberNullLocationProvider
import org.maplibre.compose.location.rememberUserLocationState
import org.maplibre.spatialk.geojson.Position
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
import kotlin.random.Random
import kotlin.time.Duration.Companion.minutes
@OptIn(ExperimentalMaterial3Api::class)
@ -105,7 +94,7 @@ fun ShowLocationView(
}
val scaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = rememberStandardBottomSheetState(skipHiddenState = false, initialValue = SheetValue.Hidden)
bottomSheetState = rememberStandardBottomSheetState(initialValue = SheetValue.PartiallyExpanded)
)
MapBottomSheetScaffold(
scaffoldState = scaffoldState,
@ -137,69 +126,7 @@ fun ShowLocationView(
locationState = userLocationState,
trackUserLocation = state.isTrackMyLocation
)
when (val mode = state.mode) {
is ShowLocationMode.Static -> {
val pinVariant = if (mode.assetType == AssetType.PIN) {
PinVariant.PinnedLocation
} else {
PinVariant.UserLocation(
avatarData = AvatarData(mode.senderId.value, mode.senderName, mode.senderAvatarUrl, AvatarSize.UserListItem),
isLive = true
)
}
// Generate test markers around the original location
val testMarkers = remember {
buildList {
// Add the original marker
add(
LocationMarkerData(
id = "original",
location = mode.location,
variant = pinVariant
)
)
// Generate 10 random points within 50 meters
val radiusInMeters = 50.0
val metersPerDegreeLat = 111_320.0
val metersPerDegreeLon = 111_320.0 * cos(Math.toRadians(mode.location.lat))
val variants = listOf(
PinVariant.StaleLocation,
PinVariant.UserLocation(AvatarData("@alice", "Alice", null, AvatarSize.TimelineSender), isLive = true),
PinVariant.UserLocation(AvatarData("@bob", "Bob", null, AvatarSize.TimelineSender), isLive = true),
PinVariant.UserLocation(AvatarData("@cassy", "Cassy", null, AvatarSize.TimelineSender), isLive = true),
PinVariant.UserLocation(AvatarData("@daisy", "Daisy", null, AvatarSize.TimelineSender), isLive = true),
PinVariant.UserLocation(AvatarData("@en", "G", null, AvatarSize.TimelineSender), isLive = true),
PinVariant.UserLocation(AvatarData("@f", "H", null, AvatarSize.TimelineSender), isLive = true),
PinVariant.UserLocation(AvatarData("@g", "I", null, AvatarSize.TimelineSender), isLive = true),
PinVariant.UserLocation(AvatarData("@h", "J", null, AvatarSize.TimelineSender), isLive = true),
PinVariant.UserLocation(AvatarData("@i", "K", null, AvatarSize.TimelineSender), isLive = true),
)
repeat(10) { index ->
// Random point in a circle using sqrt for uniform distribution
val angle = Random.nextDouble() * 2 * Math.PI
val distance = sqrt(Random.nextDouble()) * radiusInMeters
val latOffset = (distance * cos(angle)) / metersPerDegreeLat
val lonOffset = (distance * sin(angle)) / metersPerDegreeLon
add(
LocationMarkerData(
id = "test_$index",
location = Location(
lat = mode.location.lat + latOffset,
lon = mode.location.lon + lonOffset
),
variant = variants[index % (variants.size-1)]
)
)
}
}
}
LocationPinMarkers(testMarkers)
}
ShowLocationMode.Live -> {
// TODO: Show pins for all active live location sharers
}
}
LocationPinMarkers(state.markers)
},
overlayContent = {
LocationFloatingActionButton(