Display most recent activity in room list (#220)

* Create `RoomLastMessageFormatter` to produce readable room summaries.

* Add unit tests using Robolectric, fix bugs

* Add changelog

* Move `RoomLastMessageFormatter` back to `impl` module, allow it to receive an `EventTimelineItem` instead of `MessageContent`.
This commit is contained in:
Jorge Martin Espinosa 2023-03-20 11:18:25 +01:00 committed by GitHub
parent 2c478f0e49
commit d9183e4092
24 changed files with 1311 additions and 48 deletions

View file

@ -17,6 +17,7 @@
package io.element.android.libraries.matrix.api.room
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.message.RoomMessage
sealed interface RoomSummary {
data class Empty(val identifier: String) : RoomSummary
@ -35,7 +36,7 @@ data class RoomSummaryDetails(
val name: String,
val isDirect: Boolean,
val avatarURLString: String?,
val lastMessage: CharSequence?,
val lastMessage: RoomMessage?,
val lastMessageTimestamp: Long?,
val unreadNotificationCount: Int,
)

View file

@ -18,10 +18,11 @@ 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 body: String,
val event: EventTimelineItem,
val sender: UserId,
val originServerTs: Long,
)

View file

@ -16,6 +16,7 @@
package io.element.android.libraries.matrix.impl.room
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomSummaryDetails
import io.element.android.libraries.matrix.impl.room.message.RoomMessageFactory
@ -28,19 +29,16 @@ class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFacto
val latestRoomMessage = slidingSyncRoom.latestRoomMessage()?.use {
roomMessageFactory.create(it)
}
val computedLastMessage = when {
latestRoomMessage == null -> null
slidingSyncRoom.isDm() == true -> latestRoomMessage.body
else -> "${latestRoomMessage.sender.value}: ${latestRoomMessage.body}"
}
return RoomSummaryDetails(
roomId = RoomId(slidingSyncRoom.roomId()),
name = slidingSyncRoom.name() ?: slidingSyncRoom.roomId(),
isDirect = slidingSyncRoom.isDm() ?: false,
avatarURLString = room?.avatarUrl(),
unreadNotificationCount = slidingSyncRoom.unreadNotifications().use { it.notificationCount().toInt() },
lastMessage = computedLastMessage,
lastMessage = latestRoomMessage,
lastMessageTimestamp = latestRoomMessage?.originServerTs
)
}
}

View file

@ -16,19 +16,19 @@
package io.element.android.libraries.matrix.impl.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.room.message.RoomMessage
import org.matrix.rustcomponents.sdk.EventTimelineItem
import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper
import org.matrix.rustcomponents.sdk.EventTimelineItem as RustEventTimelineItem
class RoomMessageFactory {
fun create(eventTimelineItem: EventTimelineItem?): RoomMessage? {
fun create(eventTimelineItem: RustEventTimelineItem?): RoomMessage? {
eventTimelineItem ?: return null
val mappedTimelineItem = EventTimelineItemMapper().map(eventTimelineItem)
return RoomMessage(
eventId = EventId(eventTimelineItem.eventId() ?: ""),
body = eventTimelineItem.content().asMessage()?.body() ?: "",
sender = UserId(eventTimelineItem.sender()),
originServerTs = eventTimelineItem.timestamp().toLong()
eventId = mappedTimelineItem.eventId!!,
event = mappedTimelineItem,
sender = mappedTimelineItem.sender,
originServerTs = mappedTimelineItem.timestamp,
)
}
}

View file

@ -33,6 +33,7 @@ val A_SPACE_ID = SpaceId("!aSpaceId")
val A_ROOM_ID = RoomId("!aRoomId")
val A_THREAD_ID = ThreadId("\$aThreadId")
val AN_EVENT_ID = EventId("\$anEventId")
const val A_UNIQUE_ID = "aUniqueId"
const val A_ROOM_NAME = "A room name"
const val A_MESSAGE = "Hello world!"

View file

@ -16,19 +16,33 @@
package io.element.android.libraries.matrix.test.room
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.RoomSummary
import io.element.android.libraries.matrix.api.room.RoomSummaryDetails
import io.element.android.libraries.matrix.api.room.message.RoomMessage
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
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.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_MESSAGE
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_UNIQUE_ID
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.A_USER_NAME
fun aRoomSummaryFilled(
roomId: RoomId = A_ROOM_ID,
name: String = A_ROOM_NAME,
isDirect: Boolean = false,
avatarURLString: String? = null,
lastMessage: CharSequence? = A_MESSAGE,
lastMessage: RoomMessage? = aRoomMessage(),
lastMessageTimestamp: Long? = null,
unreadNotificationCount: Int = 2,
) = RoomSummary.Filled(
@ -48,7 +62,7 @@ fun aRoomSummaryDetail(
name: String = A_ROOM_NAME,
isDirect: Boolean = false,
avatarURLString: String? = null,
lastMessage: CharSequence? = A_MESSAGE,
lastMessage: RoomMessage? = aRoomMessage(),
lastMessageTimestamp: Long? = null,
unreadNotificationCount: Int = 2,
) = RoomSummaryDetails(
@ -60,3 +74,65 @@ fun aRoomSummaryDetail(
lastMessageTimestamp = lastMessageTimestamp,
unreadNotificationCount = unreadNotificationCount,
)
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,
)
fun anEventTimelineItem(
uniqueIdentifier: String = A_UNIQUE_ID,
eventId: EventId = AN_EVENT_ID,
isEditable: Boolean = false,
isLocal: Boolean = false,
isOwn: Boolean = false,
isRemote: Boolean = false,
localSendState: EventSendState? = null,
reactions: List<EventReaction> = emptyList(),
sender: UserId = A_USER_ID,
senderProfile: ProfileTimelineDetails = aProfileTimelineDetails(),
timestamp: Long = 0L,
content: EventContent = aProfileChangeMessageContent(),
) = EventTimelineItem(
uniqueIdentifier = uniqueIdentifier,
eventId = eventId,
isEditable = isEditable,
isLocal = isLocal,
isOwn = isOwn,
isRemote = isRemote,
localSendState = localSendState,
reactions = reactions,
sender = sender,
senderProfile = senderProfile,
timestamp = timestamp,
content = content,
)
fun aProfileTimelineDetails(
displayName: String? = A_USER_NAME,
displayNameAmbiguous: Boolean = false,
avatarUrl: String? = null
): ProfileTimelineDetails = ProfileTimelineDetails.Ready(
displayName = displayName,
displayNameAmbiguous = displayNameAmbiguous,
avatarUrl = avatarUrl,
)
fun aProfileChangeMessageContent(
displayName: String? = null,
prevDisplayName: String? = null,
avatarUrl: String? = null,
prevAvatarUrl: String? = null,
) = ProfileChangeContent(
displayName = displayName,
prevDisplayName = prevDisplayName,
avatarUrl = avatarUrl,
prevAvatarUrl = prevAvatarUrl,
)