Add focused location tracking when opening the map

This commit is contained in:
ganfra 2026-04-10 21:11:30 +02:00
parent 5f3226107a
commit f6eeea2134
6 changed files with 28 additions and 16 deletions

View file

@ -24,5 +24,7 @@ sealed interface ShowLocationMode : Parcelable {
) : ShowLocationMode ) : ShowLocationMode
@Parcelize @Parcelize
data object Live : ShowLocationMode data class Live(
val senderId: UserId
) : ShowLocationMode
} }

View file

@ -131,8 +131,8 @@ class ShowLocationPresenter(
) )
} }
} }
ShowLocationMode.Live -> { is ShowLocationMode.Live -> {
val liveShares by produceState(persistentListOf()) { produceState(persistentListOf()) {
val liveLocationSharesFlow = joinedRoom.subscribeToLiveLocationShares() val liveLocationSharesFlow = joinedRoom.subscribeToLiveLocationShares()
val membersStateFlow = joinedRoom.membersStateFlow.mapState { it.joinedRoomMembers() } val membersStateFlow = joinedRoom.membersStateFlow.mapState { it.joinedRoomMembers() }
combine(liveLocationSharesFlow, membersStateFlow) { liveShares, members -> combine(liveLocationSharesFlow, membersStateFlow) { liveShares, members ->
@ -158,14 +158,19 @@ class ShowLocationPresenter(
) )
}.toPersistentList() }.toPersistentList()
}.collect { value = it } }.collect { value = it }
} }.value
liveShares
} }
} }
val focusedLocation = when (mode) {
is ShowLocationMode.Static -> locationShares.firstOrNull()
is ShowLocationMode.Live -> locationShares.firstOrNull { it.userId == mode.senderId }
}
return ShowLocationState( return ShowLocationState(
dialogState = dialogState, dialogState = dialogState,
locationShares = locationShares, locationShares = locationShares,
focusedLocation = focusedLocation,
hasLocationPermission = permissionsState.isAnyGranted, hasLocationPermission = permissionsState.isAnyGranted,
isTrackMyLocation = isTrackMyLocation, isTrackMyLocation = isTrackMyLocation,
isLive = mode is ShowLocationMode.Live, isLive = mode is ShowLocationMode.Live,

View file

@ -9,7 +9,6 @@
package io.element.android.features.location.impl.show package io.element.android.features.location.impl.show
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.impl.common.ui.LocationConstraintsDialogState import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
import io.element.android.features.location.impl.common.ui.LocationMarkerData 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.PinVariant
@ -22,6 +21,7 @@ data class ShowLocationState(
val isLive: Boolean, val isLive: Boolean,
val dialogState: LocationConstraintsDialogState, val dialogState: LocationConstraintsDialogState,
val locationShares: ImmutableList<LocationShareItem>, val locationShares: ImmutableList<LocationShareItem>,
val focusedLocation: LocationShareItem?,
val hasLocationPermission: Boolean, val hasLocationPermission: Boolean,
val isTrackMyLocation: Boolean, val isTrackMyLocation: Boolean,
val appName: String, val appName: String,

View file

@ -50,6 +50,7 @@ fun aShowLocationState(
isLive: Boolean = false, isLive: Boolean = false,
constraintsDialogState: LocationConstraintsDialogState = LocationConstraintsDialogState.None, constraintsDialogState: LocationConstraintsDialogState = LocationConstraintsDialogState.None,
locationShares: List<LocationShareItem> = listOf(aLocationShareItem(isLive = isLive)), locationShares: List<LocationShareItem> = listOf(aLocationShareItem(isLive = isLive)),
focusedLocation: LocationShareItem? = locationShares.firstOrNull(),
hasLocationPermission: Boolean = false, hasLocationPermission: Boolean = false,
isTrackMyLocation: Boolean = false, isTrackMyLocation: Boolean = false,
appName: String = APP_NAME, appName: String = APP_NAME,
@ -58,6 +59,7 @@ fun aShowLocationState(
return ShowLocationState( return ShowLocationState(
dialogState = constraintsDialogState, dialogState = constraintsDialogState,
locationShares = locationShares.toImmutableList(), locationShares = locationShares.toImmutableList(),
focusedLocation = focusedLocation,
hasLocationPermission = hasLocationPermission, hasLocationPermission = hasLocationPermission,
isTrackMyLocation = isTrackMyLocation, isTrackMyLocation = isTrackMyLocation,
appName = appName, appName = appName,

View file

@ -22,8 +22,11 @@ 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.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
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
@ -67,25 +70,25 @@ fun ShowLocationView(
onDismiss = { state.eventSink(ShowLocationEvent.DismissDialog) }, onDismiss = { state.eventSink(ShowLocationEvent.DismissDialog) },
) )
val initialPosition = remember { val cameraState = rememberCameraState(firstPosition = MapDefaults.defaultCameraPosition)
if (state.locationShares.isEmpty()) { var hasAnimatedToFocusedLocation by remember { mutableStateOf(false) }
MapDefaults.defaultCameraPosition LaunchedEffect(state.focusedLocation) {
} else { if (state.focusedLocation != null && !hasAnimatedToFocusedLocation) {
val firstLocation = state.locationShares.first().location hasAnimatedToFocusedLocation = true
CameraPosition( val position = CameraPosition(
target = Position(latitude = firstLocation.lat, longitude = firstLocation.lon), target = Position(latitude = state.focusedLocation.location.lat, longitude = state.focusedLocation.location.lon),
zoom = MapDefaults.DEFAULT_ZOOM zoom = MapDefaults.DEFAULT_ZOOM
) )
cameraState.position = position
} }
} }
val cameraState = rememberCameraState(firstPosition = initialPosition)
val userLocationState = rememberUserLocationState(state.hasLocationPermission)
LaunchedEffect(cameraState.isCameraMoving) { LaunchedEffect(cameraState.isCameraMoving) {
if (cameraState.moveReason == CameraMoveReason.GESTURE) { if (cameraState.moveReason == CameraMoveReason.GESTURE) {
state.eventSink(ShowLocationEvent.TrackMyLocation(false)) state.eventSink(ShowLocationEvent.TrackMyLocation(false))
} }
} }
val userLocationState = rememberUserLocationState(state.hasLocationPermission)
val scaffoldState = rememberBottomSheetScaffoldState( val scaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = rememberStandardBottomSheetState( bottomSheetState = rememberStandardBottomSheetState(
initialValue = initialValue =

View file

@ -571,7 +571,7 @@ class MessagesFlowNode(
} }
is TimelineItemLocationContent -> { is TimelineItemLocationContent -> {
val mode = when(event.content.mode){ val mode = when(event.content.mode){
is TimelineItemLocationContent.Mode.Live -> ShowLocationMode.Live is TimelineItemLocationContent.Mode.Live -> ShowLocationMode.Live(event.senderId)
is TimelineItemLocationContent.Mode.Static -> ShowLocationMode.Static( is TimelineItemLocationContent.Mode.Static -> ShowLocationMode.Static(
location = event.content.mode.location, location = event.content.mode.location,
senderName = event.safeSenderName, senderName = event.safeSenderName,