misc : use new_latest_event api (+ some renaming)

This commit is contained in:
ganfra 2025-11-17 20:53:55 +01:00
parent 43152fc073
commit ca526580fd
42 changed files with 352 additions and 290 deletions

View file

@ -1,20 +0,0 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector 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.libraries.matrix.api.room.message
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
data class RoomMessage(
val eventId: EventId,
val event: EventTimelineItem,
val sender: UserId,
val originServerTs: Long,
)

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2025 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.libraries.matrix.api.roomlist
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails
sealed interface LatestEventValue {
data object None : LatestEventValue
data class Remote(
val timestamp: Long,
val content: EventContent,
val senderId: UserId,
val senderProfile: ProfileDetails,
val isOwn: Boolean,
) : LatestEventValue
data class Local(
val timestamp: Long,
val content: EventContent,
val senderId: UserId,
val senderProfile: ProfileDetails,
val isSending: Boolean,
) : LatestEventValue
}

View file

@ -9,13 +9,16 @@
package io.element.android.libraries.matrix.api.roomlist
import io.element.android.libraries.matrix.api.room.RoomInfo
import io.element.android.libraries.matrix.api.room.message.RoomMessage
data class RoomSummary(
val info: RoomInfo,
val lastMessage: RoomMessage?,
val latestEvent: LatestEventValue,
) {
val roomId = info.id
val lastMessageTimestamp = lastMessage?.originServerTs
val latestEventTimestamp = when (latestEvent) {
is LatestEventValue.None -> null
is LatestEventValue.Local -> latestEvent.timestamp
is LatestEventValue.Remote -> latestEvent.timestamp
}
val isOneToOne get() = info.activeMembersCount == 2L
}

View file

@ -13,7 +13,7 @@ import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails
sealed interface EventThreadInfo {
data class ThreadRoot(val summary: ThreadSummary) : EventThreadInfo
@ -29,6 +29,6 @@ data class EmbeddedEventInfo(
val eventOrTransactionId: EventOrTransactionId,
val content: EventContent,
val senderId: UserId,
val senderProfile: ProfileTimelineDetails,
val senderProfile: ProfileDetails,
val timestamp: Long,
)

View file

@ -27,7 +27,7 @@ data class EventTimelineItem(
val reactions: ImmutableList<EventReaction>,
val receipts: ImmutableList<Receipt>,
val sender: UserId,
val senderProfile: ProfileTimelineDetails,
val senderProfile: ProfileDetails,
val timestamp: Long,
val content: EventContent,
val origin: TimelineItemEventOrigin?,

View file

@ -25,7 +25,7 @@ sealed interface InReplyTo {
val eventId: EventId,
val content: EventContent,
val senderId: UserId,
val senderProfile: ProfileTimelineDetails,
val senderProfile: ProfileDetails,
) : InReplyTo
/**

View file

@ -12,20 +12,20 @@ import androidx.compose.runtime.Immutable
import io.element.android.libraries.matrix.api.core.UserId
@Immutable
sealed interface ProfileTimelineDetails {
data object Unavailable : ProfileTimelineDetails
sealed interface ProfileDetails {
data object Unavailable : ProfileDetails
data object Pending : ProfileTimelineDetails
data object Pending : ProfileDetails
data class Ready(
val displayName: String?,
val displayNameAmbiguous: Boolean,
val avatarUrl: String?
) : ProfileTimelineDetails
) : ProfileDetails
data class Error(
val message: String
) : ProfileTimelineDetails
) : ProfileDetails
}
/**
@ -34,9 +34,9 @@ sealed interface ProfileTimelineDetails {
* If the display name is ambiguous, the user ID is appended in parentheses.
* Otherwise, the display name is returned.
*/
fun ProfileTimelineDetails.getDisambiguatedDisplayName(userId: UserId): String {
fun ProfileDetails.getDisambiguatedDisplayName(userId: UserId): String {
return when (this) {
is ProfileTimelineDetails.Ready -> when {
is ProfileDetails.Ready -> when {
displayName == null -> userId.value
displayNameAmbiguous -> "$displayName ($userId)"
else -> displayName
@ -45,16 +45,16 @@ fun ProfileTimelineDetails.getDisambiguatedDisplayName(userId: UserId): String {
}
}
fun ProfileTimelineDetails.getDisplayName(): String? {
fun ProfileDetails.getDisplayName(): String? {
return when (this) {
is ProfileTimelineDetails.Ready -> displayName
is ProfileDetails.Ready -> displayName
else -> null
}
}
fun ProfileTimelineDetails.getAvatarUrl(): String? {
fun ProfileDetails.getAvatarUrl(): String? {
return when (this) {
is ProfileTimelineDetails.Ready -> avatarUrl
is ProfileDetails.Ready -> avatarUrl
else -> null
}
}

View file

@ -18,23 +18,23 @@ private val aUserId = UserId(A_USER_ID)
class ProfileTimelineDetailsTest {
@Test
fun `getDisambiguatedDisplayName of Unavailable should be equal to userId`() {
assertThat(ProfileTimelineDetails.Unavailable.getDisambiguatedDisplayName(aUserId)).isEqualTo(A_USER_ID)
assertThat(ProfileDetails.Unavailable.getDisambiguatedDisplayName(aUserId)).isEqualTo(A_USER_ID)
}
@Test
fun `getDisambiguatedDisplayName of Error should be equal to userId`() {
assertThat(ProfileTimelineDetails.Error("An error").getDisambiguatedDisplayName(aUserId)).isEqualTo(A_USER_ID)
assertThat(ProfileDetails.Error("An error").getDisambiguatedDisplayName(aUserId)).isEqualTo(A_USER_ID)
}
@Test
fun `getDisambiguatedDisplayName of Pending should be equal to userId`() {
assertThat(ProfileTimelineDetails.Pending.getDisambiguatedDisplayName(aUserId)).isEqualTo(A_USER_ID)
assertThat(ProfileDetails.Pending.getDisambiguatedDisplayName(aUserId)).isEqualTo(A_USER_ID)
}
@Test
fun `getDisambiguatedDisplayName of Ready without display name should be equal to userId`() {
assertThat(
ProfileTimelineDetails.Ready(
ProfileDetails.Ready(
displayName = null,
displayNameAmbiguous = false,
avatarUrl = null,
@ -45,7 +45,7 @@ class ProfileTimelineDetailsTest {
@Test
fun `getDisambiguatedDisplayName of Ready with display name should be equal to display name`() {
assertThat(
ProfileTimelineDetails.Ready(
ProfileDetails.Ready(
displayName = "Alice",
displayNameAmbiguous = false,
avatarUrl = null,
@ -56,7 +56,7 @@ class ProfileTimelineDetailsTest {
@Test
fun `getDisambiguatedDisplayName of Ready with display name and ambiguous should be equal to display name with user id`() {
assertThat(
ProfileTimelineDetails.Ready(
ProfileDetails.Ready(
displayName = "Alice",
displayNameAmbiguous = true,
avatarUrl = null,

View file

@ -1,28 +0,0 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector 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.libraries.matrix.impl.room.message
import io.element.android.libraries.matrix.api.room.message.RoomMessage
import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper
import org.matrix.rustcomponents.sdk.EventTimelineItem as RustEventTimelineItem
class RoomMessageFactory(
private val eventTimelineItemMapper: EventTimelineItemMapper = EventTimelineItemMapper(),
) {
fun create(eventTimelineItem: RustEventTimelineItem?): RoomMessage? {
eventTimelineItem ?: return null
val mappedTimelineItem = eventTimelineItemMapper.map(eventTimelineItem)
return RoomMessage(
eventId = mappedTimelineItem.eventId ?: return null,
event = mappedTimelineItem,
sender = mappedTimelineItem.sender,
originServerTs = mappedTimelineItem.timestamp,
)
}
}

View file

@ -40,7 +40,7 @@ internal class RoomListFactory(
private val sessionCoroutineScope: CoroutineScope,
private val analyticsService: AnalyticsService,
) {
private val roomSummaryDetailsFactory: RoomSummaryFactory = RoomSummaryFactory()
private val roomSummaryFactory: RoomSummaryFactory = RoomSummaryFactory()
/**
* Creates a room list that can be used to load more rooms and filter them dynamically.
@ -55,7 +55,7 @@ internal class RoomListFactory(
val loadingStateFlow: MutableStateFlow<RoomList.LoadingState> = MutableStateFlow(RoomList.LoadingState.NotLoaded)
val filteredSummariesFlow = MutableSharedFlow<List<RoomSummary>>(replay = 1, extraBufferCapacity = 1)
val summariesFlow = MutableSharedFlow<List<RoomSummary>>(replay = 1, extraBufferCapacity = 1)
val processor = RoomSummaryListProcessor(summariesFlow, innerRoomListService, coroutineContext, roomSummaryDetailsFactory)
val processor = RoomSummaryListProcessor(summariesFlow, innerRoomListService, coroutineContext, roomSummaryFactory)
// Makes sure we don't miss any events
val dynamicEvents = MutableSharedFlow<RoomListDynamicEvents>(replay = 100)
val currentFilter = MutableStateFlow(initialFilter)

View file

@ -8,24 +8,44 @@
package io.element.android.libraries.matrix.impl.roomlist
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.roomlist.LatestEventValue
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.impl.room.RoomInfoMapper
import io.element.android.libraries.matrix.impl.room.message.RoomMessageFactory
import io.element.android.libraries.matrix.impl.timeline.item.event.TimelineEventContentMapper
import io.element.android.libraries.matrix.impl.timeline.item.event.map
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.use
import org.matrix.rustcomponents.sdk.LatestEventValue as RustLatestEventValue
class RoomSummaryFactory(
private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory(),
private val contentMapper: TimelineEventContentMapper = TimelineEventContentMapper(),
private val roomInfoMapper: RoomInfoMapper = RoomInfoMapper(),
) {
suspend fun create(room: Room): RoomSummary {
val roomInfo = room.roomInfo().let(roomInfoMapper::map)
val latestRoomMessage = room.latestEvent().use { event ->
roomMessageFactory.create(event)
val latestEvent = room.newLatestEvent().use { event ->
when (event) {
is RustLatestEventValue.None -> LatestEventValue.None
is RustLatestEventValue.Local -> LatestEventValue.Local(
timestamp = event.timestamp.toLong(),
content = contentMapper.map(event.content),
isSending = event.isSending,
senderId = UserId(event.sender),
senderProfile = event.profile.map(),
)
is RustLatestEventValue.Remote -> LatestEventValue.Remote(
timestamp = event.timestamp.toLong(),
content = contentMapper.map(event.content),
senderId = UserId(event.sender),
senderProfile = event.profile.map(),
isOwn = event.isOwn,
)
}
}
return RoomSummary(
info = roomInfo,
lastMessage = latestRoomMessage,
latestEvent = latestEvent,
)
}
}

View file

@ -24,7 +24,7 @@ class RoomSummaryListProcessor(
private val roomSummaries: MutableSharedFlow<List<RoomSummary>>,
private val roomListService: RoomListServiceInterface,
private val coroutineContext: CoroutineContext,
private val roomSummaryDetailsFactory: RoomSummaryFactory = RoomSummaryFactory(),
private val roomSummaryFactory: RoomSummaryFactory,
) {
private val mutex = Mutex()
@ -103,7 +103,7 @@ class RoomSummaryListProcessor(
}
private suspend fun buildSummaryForRoomListEntry(entry: Room): RoomSummary {
return entry.use { roomSummaryDetailsFactory.create(room = it) }
return entry.use { roomSummaryFactory.create(room = it) }
}
private suspend fun buildRoomSummaryForIdentifier(identifier: String): RoomSummary? {

View file

@ -17,7 +17,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails
import io.element.android.libraries.matrix.api.timeline.item.event.ReactionSender
import io.element.android.libraries.matrix.api.timeline.item.event.Receipt
import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin
@ -64,12 +64,12 @@ class EventTimelineItemMapper(
}
}
fun RustProfileDetails.map(): ProfileTimelineDetails {
fun RustProfileDetails.map(): ProfileDetails {
return when (this) {
RustProfileDetails.Pending -> ProfileTimelineDetails.Pending
RustProfileDetails.Unavailable -> ProfileTimelineDetails.Unavailable
is RustProfileDetails.Error -> ProfileTimelineDetails.Error(message)
is RustProfileDetails.Ready -> ProfileTimelineDetails.Ready(
RustProfileDetails.Pending -> ProfileDetails.Pending
RustProfileDetails.Unavailable -> ProfileDetails.Unavailable
is RustProfileDetails.Error -> ProfileDetails.Error(message)
is RustProfileDetails.Ready -> ProfileDetails.Ready(
displayName = displayName,
displayNameAmbiguous = displayNameAmbiguous,
avatarUrl = avatarUrl

View file

@ -192,6 +192,6 @@ class RoomSummaryListProcessorTest {
summaries,
FakeFfiRoomListService(),
coroutineContext = StandardTestDispatcher(testScheduler),
roomSummaryDetailsFactory = RoomSummaryFactory(),
roomSummaryFactory = RoomSummaryFactory(),
)
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2025 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.libraries.matrix.test.room
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.roomlist.LatestEventValue
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.timeline.aMessageContent
import io.element.android.libraries.matrix.test.timeline.aProfileDetails
fun aRemoteLatestEvent(
content: EventContent = aMessageContent(),
timestamp: Long = 0L,
isOwn: Boolean = false,
senderId: UserId = A_USER_ID,
senderProfile: ProfileDetails = aProfileDetails(),
): LatestEventValue.Remote {
return LatestEventValue.Remote(
timestamp = timestamp,
content = content,
senderId = senderId,
senderProfile = senderProfile,
isOwn = isOwn,
)
}
fun aLocalLatestEvent(
content: EventContent = aMessageContent(),
timestamp: Long = 0L,
isSending: Boolean = false,
senderId: UserId = A_USER_ID,
senderProfile: ProfileDetails = aProfileDetails(),
): LatestEventValue.Local {
return LatestEventValue.Local(
timestamp = timestamp,
content = content,
senderId = senderId,
senderProfile = senderProfile,
isSending = isSending,
)
}

View file

@ -18,28 +18,24 @@ import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
import io.element.android.libraries.matrix.api.room.join.JoinRule
import io.element.android.libraries.matrix.api.room.message.RoomMessage
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
import io.element.android.libraries.matrix.api.roomlist.LatestEventValue
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_NAME
import io.element.android.libraries.matrix.test.A_ROOM_RAW_NAME
import io.element.android.libraries.matrix.test.A_ROOM_TOPIC
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableList
fun aRoomSummary(
info: RoomInfo = aRoomInfo(),
lastMessage: RoomMessage? = aRoomMessage(),
latestEventValue: LatestEventValue = aRemoteLatestEvent(),
) = RoomSummary(
info = info,
lastMessage = lastMessage,
latestEvent = latestEventValue,
)
fun aRoomSummary(
@ -79,7 +75,7 @@ fun aRoomSummary(
numUnreadNotifications: Long = 0,
numUnreadMentions: Long = 0,
historyVisibility: RoomHistoryVisibility = RoomHistoryVisibility.Joined,
lastMessage: RoomMessage? = aRoomMessage(),
latestEvent: LatestEventValue = aRemoteLatestEvent(),
roomVersion: String? = "11",
privilegedCreatorRole: Boolean = false,
) = RoomSummary(
@ -120,17 +116,5 @@ fun aRoomSummary(
roomVersion = roomVersion,
privilegedCreatorRole = privilegedCreatorRole,
),
lastMessage = lastMessage,
)
fun aRoomMessage(
eventId: EventId = AN_EVENT_ID,
event: EventTimelineItem = anEventTimelineItem(),
userId: UserId = A_USER_ID,
timestamp: Long = 0L,
) = RoomMessage(
eventId = eventId,
event = event,
sender = userId,
originServerTs = timestamp,
latestEvent = latestEvent,
)

View file

@ -27,7 +27,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
import io.element.android.libraries.matrix.api.timeline.item.event.MessageType
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails
import io.element.android.libraries.matrix.api.timeline.item.event.Receipt
import io.element.android.libraries.matrix.api.timeline.item.event.SendHandleProvider
import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent
@ -53,7 +53,7 @@ fun anEventTimelineItem(
reactions: ImmutableList<EventReaction> = persistentListOf(),
receipts: ImmutableList<Receipt> = persistentListOf(),
sender: UserId = A_USER_ID,
senderProfile: ProfileTimelineDetails = aProfileTimelineDetails(),
senderProfile: ProfileDetails = aProfileDetails(),
timestamp: Long = 0L,
content: EventContent = aProfileChangeMessageContent(),
debugInfoProvider: TimelineItemDebugInfoProvider = TimelineItemDebugInfoProvider { aTimelineItemDebugInfo() },
@ -79,11 +79,11 @@ fun anEventTimelineItem(
sendHandleProvider = sendHandleProvider,
)
fun aProfileTimelineDetails(
fun aProfileDetails(
displayName: String? = A_USER_NAME,
displayNameAmbiguous: Boolean = false,
avatarUrl: String? = null
): ProfileTimelineDetails = ProfileTimelineDetails.Ready(
): ProfileDetails = ProfileDetails.Ready(
displayName = displayName,
displayNameAmbiguous = displayNameAmbiguous,
avatarUrl = avatarUrl,