Start cleaning up location code
This commit is contained in:
parent
fa9f0a93c0
commit
91626bd217
5 changed files with 80 additions and 85 deletions
|
|
@ -36,6 +36,8 @@ import org.maplibre.spatialk.geojson.Point
|
||||||
import org.maplibre.spatialk.geojson.Position
|
import org.maplibre.spatialk.geojson.Position
|
||||||
import org.maplibre.spatialk.geojson.toJson
|
import org.maplibre.spatialk.geojson.toJson
|
||||||
|
|
||||||
|
private const val LOCATION_MARKER_ID = "LOCATION_MARKER_ID"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data class representing a marker on the map.
|
* Data class representing a marker on the map.
|
||||||
*
|
*
|
||||||
|
|
@ -81,7 +83,7 @@ fun LocationPinMarkers(
|
||||||
id = JsonPrimitive(marker.id),
|
id = JsonPrimitive(marker.id),
|
||||||
geometry = Point(Position(marker.location.lon, marker.location.lat)),
|
geometry = Point(Position(marker.location.lon, marker.location.lat)),
|
||||||
properties = mapOf(
|
properties = mapOf(
|
||||||
"id" to JsonPrimitive(marker.id),
|
LOCATION_MARKER_ID to JsonPrimitive(marker.id),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -153,7 +155,7 @@ private fun LocationPinMarkerLayer(
|
||||||
SymbolLayer(
|
SymbolLayer(
|
||||||
id = "pin-marker-${marker.id}",
|
id = "pin-marker-${marker.id}",
|
||||||
source = source,
|
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),
|
iconImage = image(imageBitmap),
|
||||||
iconAnchor = const(SymbolAnchor.Bottom),
|
iconAnchor = const(SymbolAnchor.Bottom),
|
||||||
iconAllowOverlap = const(true),
|
iconAllowOverlap = const(true),
|
||||||
|
|
|
||||||
|
|
@ -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.PermissionsEvents
|
||||||
import io.element.android.features.location.impl.common.permissions.PermissionsPresenter
|
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.permissions.PermissionsState
|
||||||
|
import io.element.android.features.location.impl.common.ui.LocationMarkerData
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
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
|
@AssistedInject
|
||||||
class ShowLocationPresenter(
|
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(
|
return ShowLocationState(
|
||||||
permissionDialog = permissionDialog,
|
permissionDialog = permissionDialog,
|
||||||
mode = mode,
|
mode = mode,
|
||||||
|
markers = markers,
|
||||||
hasLocationPermission = permissionsState.isAnyGranted,
|
hasLocationPermission = permissionsState.isAnyGranted,
|
||||||
isTrackMyLocation = isTrackMyLocation,
|
isTrackMyLocation = isTrackMyLocation,
|
||||||
appName = appName,
|
appName = appName,
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,12 @@
|
||||||
package io.element.android.features.location.impl.show
|
package io.element.android.features.location.impl.show
|
||||||
|
|
||||||
import io.element.android.features.location.api.ShowLocationMode
|
import io.element.android.features.location.api.ShowLocationMode
|
||||||
|
import io.element.android.features.location.impl.common.ui.LocationMarkerData
|
||||||
|
|
||||||
data class ShowLocationState(
|
data class ShowLocationState(
|
||||||
val permissionDialog: Dialog,
|
val permissionDialog: Dialog,
|
||||||
val mode: ShowLocationMode,
|
val mode: ShowLocationMode,
|
||||||
|
val markers: List<LocationMarkerData>,
|
||||||
val hasLocationPermission: Boolean,
|
val hasLocationPermission: Boolean,
|
||||||
val isTrackMyLocation: Boolean,
|
val isTrackMyLocation: Boolean,
|
||||||
val appName: String,
|
val appName: String,
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,10 @@ package io.element.android.features.location.impl.show
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.location.api.Location
|
import io.element.android.features.location.api.Location
|
||||||
import io.element.android.features.location.api.ShowLocationMode
|
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.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.room.location.AssetType
|
import io.element.android.libraries.matrix.api.room.location.AssetType
|
||||||
|
|
||||||
|
|
@ -50,18 +54,44 @@ class ShowLocationStateProvider : PreviewParameterProvider<ShowLocationState> {
|
||||||
fun aShowLocationState(
|
fun aShowLocationState(
|
||||||
permissionDialog: ShowLocationState.Dialog = ShowLocationState.Dialog.None,
|
permissionDialog: ShowLocationState.Dialog = ShowLocationState.Dialog.None,
|
||||||
mode: ShowLocationMode = aStaticLocationMode(),
|
mode: ShowLocationMode = aStaticLocationMode(),
|
||||||
|
markers: List<LocationMarkerData>? = null,
|
||||||
hasLocationPermission: Boolean = false,
|
hasLocationPermission: Boolean = false,
|
||||||
isTrackMyLocation: Boolean = false,
|
isTrackMyLocation: Boolean = false,
|
||||||
appName: String = APP_NAME,
|
appName: String = APP_NAME,
|
||||||
eventSink: (ShowLocationEvents) -> Unit = {},
|
eventSink: (ShowLocationEvents) -> Unit = {},
|
||||||
) = ShowLocationState(
|
): ShowLocationState {
|
||||||
permissionDialog = permissionDialog,
|
val effectiveMarkers = markers ?: when (mode) {
|
||||||
mode = mode,
|
is ShowLocationMode.Static -> listOf(
|
||||||
hasLocationPermission = hasLocationPermission,
|
LocationMarkerData(
|
||||||
isTrackMyLocation = isTrackMyLocation,
|
id = mode.senderId.value,
|
||||||
appName = appName,
|
location = mode.location,
|
||||||
eventSink = eventSink,
|
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(
|
fun aStaticLocationMode(
|
||||||
location: Location = Location(1.23, 2.34, 4f),
|
location: Location = Location(1.23, 2.34, 4f),
|
||||||
|
|
|
||||||
|
|
@ -15,33 +15,26 @@ import androidx.compose.material3.rememberBottomSheetScaffoldState
|
||||||
import androidx.compose.material3.rememberStandardBottomSheetState
|
import androidx.compose.material3.rememberStandardBottomSheetState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
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.api.ShowLocationMode
|
||||||
import io.element.android.features.location.impl.common.MapDefaults
|
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.PermissionDeniedDialog
|
||||||
import io.element.android.features.location.impl.common.PermissionRationaleDialog
|
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.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.LocationPinMarkers
|
||||||
import io.element.android.features.location.impl.common.ui.MapBottomSheetScaffold
|
import io.element.android.features.location.impl.common.ui.MapBottomSheetScaffold
|
||||||
import io.element.android.features.location.impl.common.ui.UserLocationPuck
|
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.components.button.BackButton
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
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.Icon
|
||||||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
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 io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import org.maplibre.compose.camera.CameraMoveReason
|
import org.maplibre.compose.camera.CameraMoveReason
|
||||||
import org.maplibre.compose.camera.CameraPosition
|
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.rememberNullLocationProvider
|
||||||
import org.maplibre.compose.location.rememberUserLocationState
|
import org.maplibre.compose.location.rememberUserLocationState
|
||||||
import org.maplibre.spatialk.geojson.Position
|
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
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|
@ -105,7 +94,7 @@ fun ShowLocationView(
|
||||||
}
|
}
|
||||||
|
|
||||||
val scaffoldState = rememberBottomSheetScaffoldState(
|
val scaffoldState = rememberBottomSheetScaffoldState(
|
||||||
bottomSheetState = rememberStandardBottomSheetState(skipHiddenState = false, initialValue = SheetValue.Hidden)
|
bottomSheetState = rememberStandardBottomSheetState(initialValue = SheetValue.PartiallyExpanded)
|
||||||
)
|
)
|
||||||
MapBottomSheetScaffold(
|
MapBottomSheetScaffold(
|
||||||
scaffoldState = scaffoldState,
|
scaffoldState = scaffoldState,
|
||||||
|
|
@ -137,69 +126,7 @@ fun ShowLocationView(
|
||||||
locationState = userLocationState,
|
locationState = userLocationState,
|
||||||
trackUserLocation = state.isTrackMyLocation
|
trackUserLocation = state.isTrackMyLocation
|
||||||
)
|
)
|
||||||
when (val mode = state.mode) {
|
LocationPinMarkers(state.markers)
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
},
|
||||||
overlayContent = {
|
overlayContent = {
|
||||||
LocationFloatingActionButton(
|
LocationFloatingActionButton(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue