Live location : ensure it's not sorted randomly
This commit is contained in:
parent
fbfeeae084
commit
8182a149d0
6 changed files with 124 additions and 26 deletions
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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.show
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.location.LiveLocationShare
|
||||
|
||||
class LiveLocationShareComparator(private val currentUser: UserId) : Comparator<LiveLocationShare> {
|
||||
override fun compare(p0: LiveLocationShare, p1: LiveLocationShare): Int {
|
||||
val p0IsCurrentUser = p0.userId == currentUser
|
||||
val p1IsCurrentUser = p1.userId == currentUser
|
||||
if (p0IsCurrentUser != p1IsCurrentUser) return if (p0IsCurrentUser) -1 else 1
|
||||
return p1.startTimestamp.compareTo(p0.startTimestamp)
|
||||
}
|
||||
}
|
||||
|
|
@ -133,35 +133,39 @@ class ShowLocationPresenter(
|
|||
}
|
||||
is ShowLocationMode.Live -> {
|
||||
produceState(persistentListOf()) {
|
||||
val comparator = LiveLocationShareComparator(currentUser = joinedRoom.sessionId)
|
||||
val liveLocationSharesFlow = joinedRoom.subscribeToLiveLocationShares()
|
||||
val membersStateFlow = joinedRoom.membersStateFlow.mapState { it.joinedRoomMembers() }
|
||||
combine(liveLocationSharesFlow, membersStateFlow) { liveShares, members ->
|
||||
liveShares.mapNotNull { share ->
|
||||
val lastLocation = share.lastLocation ?: return@mapNotNull null
|
||||
val location = Location.fromGeoUri(lastLocation.geoUri) ?: return@mapNotNull null
|
||||
val member = members.find { it.userId == share.userId }
|
||||
val displayName = member?.getBestName() ?: share.userId.value
|
||||
val avatarUrl = member?.avatarUrl
|
||||
val relativeTime = dateFormatter.format(timestamp = lastLocation.timestamp, mode = DateFormatterMode.Full, useRelative = true)
|
||||
val formattedTimestamp = stringProvider.getString(
|
||||
CommonStrings.screen_static_location_sheet_timestamp_description,
|
||||
relativeTime
|
||||
)
|
||||
LocationShareItem(
|
||||
userId = share.userId,
|
||||
displayName = displayName,
|
||||
avatarData = AvatarData(
|
||||
id = share.userId.value,
|
||||
name = displayName,
|
||||
url = avatarUrl,
|
||||
size = AvatarSize.UserListItem,
|
||||
),
|
||||
formattedTimestamp = formattedTimestamp,
|
||||
location = location,
|
||||
isLive = true,
|
||||
assetType = lastLocation.assetType,
|
||||
)
|
||||
}.toImmutableList()
|
||||
liveShares
|
||||
.sortedWith(comparator)
|
||||
.mapNotNull { share ->
|
||||
val lastLocation = share.lastLocation ?: return@mapNotNull null
|
||||
val location = Location.fromGeoUri(lastLocation.geoUri) ?: return@mapNotNull null
|
||||
val member = members.find { it.userId == share.userId }
|
||||
val displayName = member?.getBestName() ?: share.userId.value
|
||||
val avatarUrl = member?.avatarUrl
|
||||
val relativeTime = dateFormatter.format(timestamp = lastLocation.timestamp, mode = DateFormatterMode.Full, useRelative = true)
|
||||
val formattedTimestamp = stringProvider.getString(
|
||||
CommonStrings.screen_static_location_sheet_timestamp_description,
|
||||
relativeTime
|
||||
)
|
||||
LocationShareItem(
|
||||
userId = share.userId,
|
||||
displayName = displayName,
|
||||
avatarData = AvatarData(
|
||||
id = share.userId.value,
|
||||
name = displayName,
|
||||
url = avatarUrl,
|
||||
size = AvatarSize.UserListItem,
|
||||
),
|
||||
formattedTimestamp = formattedTimestamp,
|
||||
location = location,
|
||||
isLive = true,
|
||||
assetType = lastLocation.assetType,
|
||||
)
|
||||
}
|
||||
.toImmutableList()
|
||||
}.collect { value = it }
|
||||
}.value
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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.show
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.location.LiveLocationShare
|
||||
import org.junit.Test
|
||||
|
||||
class LiveLocationShareComparatorTest {
|
||||
private val currentUser = UserId("@me:matrix.org")
|
||||
private val comparator = LiveLocationShareComparator(currentUser)
|
||||
|
||||
@Test
|
||||
fun `compare returns zero when comparing the same current user share`() {
|
||||
val share = aLiveLocationShare(userId = currentUser, startTimestamp = 123L)
|
||||
|
||||
val result = comparator.compare(share, share)
|
||||
|
||||
assertThat(result).isEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `compare orders current user share before another user share`() {
|
||||
val otherShare = aLiveLocationShare(userId = UserId("@alice:matrix.org"), startTimestamp = 200L)
|
||||
val currentUserShare = aLiveLocationShare(userId = currentUser, startTimestamp = 100L)
|
||||
|
||||
val sortedShares = listOf(otherShare, currentUserShare).sortedWith(comparator)
|
||||
|
||||
assertThat(sortedShares).containsExactly(currentUserShare, otherShare).inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `compare orders current user shares by newest start timestamp first`() {
|
||||
val newerShare = aLiveLocationShare(userId = currentUser, startTimestamp = 200L)
|
||||
val olderShare = aLiveLocationShare(userId = currentUser, startTimestamp = 100L)
|
||||
|
||||
val sortedShares = listOf(olderShare, newerShare).sortedWith(comparator)
|
||||
|
||||
assertThat(sortedShares).containsExactly(newerShare, olderShare).inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `compare orders non current user shares by newest start timestamp first`() {
|
||||
val newerShare = aLiveLocationShare(userId = UserId("@alice:matrix.org"), startTimestamp = 200L)
|
||||
val olderShare = aLiveLocationShare(userId = UserId("@bob:matrix.org"), startTimestamp = 100L)
|
||||
|
||||
val sortedShares = listOf(olderShare, newerShare).sortedWith(comparator)
|
||||
|
||||
assertThat(sortedShares).containsExactly(newerShare, olderShare).inOrder()
|
||||
}
|
||||
}
|
||||
|
||||
private fun aLiveLocationShare(
|
||||
userId: UserId,
|
||||
startTimestamp: Long,
|
||||
): LiveLocationShare {
|
||||
return LiveLocationShare(
|
||||
userId = userId,
|
||||
lastLocation = null,
|
||||
startTimestamp = startTimestamp,
|
||||
endTimestamp = startTimestamp + 1_000L,
|
||||
)
|
||||
}
|
||||
|
|
@ -469,6 +469,7 @@ private fun aLiveLocationShare(
|
|||
userId: UserId,
|
||||
geoUri: String = "geo:48.8584,2.2945",
|
||||
timestamp: Long = 0L,
|
||||
startTimestamp: Long = 0L,
|
||||
endTimestamp: Long = Long.MAX_VALUE,
|
||||
assetType: AssetType = AssetType.SENDER,
|
||||
): LiveLocationShare {
|
||||
|
|
@ -479,6 +480,7 @@ private fun aLiveLocationShare(
|
|||
timestamp = timestamp,
|
||||
assetType = assetType,
|
||||
),
|
||||
startTimestamp = startTimestamp,
|
||||
endTimestamp = endTimestamp,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue