From c8b8c08c9926af2bfc160f9ba25cf7c61c21058b Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 8 Nov 2022 16:09:24 +0100 Subject: [PATCH] Timeline: add avatar and displayname (not dynamic...) --- .../MessageTimelineItemStateMapper.kt | 105 ++++++++++++------ .../x/features/messages/MessagesScreen.kt | 59 +++++++--- .../x/features/messages/MessagesViewModel.kt | 21 +--- .../model/MessagesItemGroupPosition.kt | 5 + .../model/MessagesTimelineItemState.kt | 13 ++- .../x/features/roomlist/RoomListViewModel.kt | 18 +-- .../designsystem/components/avatar/Avatar.kt | 3 +- .../components/avatar/AvatarData.kt | 6 +- .../element/android/x/matrix/MatrixClient.kt | 6 + .../element/android/x/matrix/core/EventId.kt | 4 +- .../element/android/x/matrix/core/RoomId.kt | 4 +- .../element/android/x/matrix/core/UserId.kt | 4 +- .../android/x/matrix/media/MediaResolver.kt | 36 ++++++ .../android/x/matrix/room/MatrixRoom.kt | 15 +++ 14 files changed, 206 insertions(+), 93 deletions(-) create mode 100644 libraries/matrix/src/main/java/io/element/android/x/matrix/media/MediaResolver.kt diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/MessageTimelineItemStateMapper.kt b/features/messages/src/main/java/io/element/android/x/features/messages/MessageTimelineItemStateMapper.kt index de088ea8dc..3a4185b0fa 100644 --- a/features/messages/src/main/java/io/element/android/x/features/messages/MessageTimelineItemStateMapper.kt +++ b/features/messages/src/main/java/io/element/android/x/features/messages/MessageTimelineItemStateMapper.kt @@ -1,8 +1,11 @@ package io.element.android.x.features.messages +import io.element.android.x.designsystem.components.avatar.AvatarData +import io.element.android.x.designsystem.components.avatar.AvatarSize import io.element.android.x.features.messages.model.MessagesItemGroupPosition import io.element.android.x.features.messages.model.MessagesTimelineItemState -import io.element.android.x.matrix.core.UserId +import io.element.android.x.matrix.MatrixClient +import io.element.android.x.matrix.media.MediaResolver import io.element.android.x.matrix.room.MatrixRoom import io.element.android.x.matrix.timeline.MatrixTimelineItem import kotlinx.coroutines.CoroutineDispatcher @@ -10,7 +13,7 @@ import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.MessageType class MessageTimelineItemStateMapper( - private val myUserId: UserId, + private val client: MatrixClient, private val room: MatrixRoom, private val dispatcher: CoroutineDispatcher, ) { @@ -22,36 +25,7 @@ class MessageTimelineItemStateMapper( val currentTimelineItem = timelineItems[index] val timelineItemState = when (currentTimelineItem) { is MatrixTimelineItem.Event -> { - val prevTimelineItem = - timelineItems.getOrNull(index - 1) as? MatrixTimelineItem.Event - val nextTimelineItem = - timelineItems.getOrNull(index + 1) as? MatrixTimelineItem.Event - val currentSender = currentTimelineItem.event.sender() - val previousSender = prevTimelineItem?.event?.sender() - val nextSender = nextTimelineItem?.event?.sender() - - val groupPosition = when { - previousSender != currentSender && nextSender == currentSender -> MessagesItemGroupPosition.First - previousSender == currentSender && nextSender == currentSender -> MessagesItemGroupPosition.Middle - previousSender == currentSender && nextSender != currentSender -> MessagesItemGroupPosition.Last - else -> MessagesItemGroupPosition.None - } - val messageType = - currentTimelineItem.event.content().asMessage()?.msgtype() - val contentStr = when (messageType) { - is MessageType.Emote -> messageType.content.body - is MessageType.Image -> messageType.content.body - is MessageType.Notice -> messageType.content.body - is MessageType.Text -> messageType.content.body - null -> null - } - MessagesTimelineItemState.MessageEvent( - id = currentTimelineItem.event.eventId() ?: "", - sender = currentTimelineItem.event.sender(), - content = contentStr, - isMine = currentTimelineItem.event.isOwn(), - groupPosition = groupPosition - ) + buildMessageEvent(currentTimelineItem, index, timelineItems) } is MatrixTimelineItem.Virtual -> MessagesTimelineItemState.Virtual( "virtual_item_$index" @@ -63,5 +37,72 @@ class MessageTimelineItemStateMapper( messagesTimelineItemState } + private suspend fun buildMessageEvent( + currentTimelineItem: MatrixTimelineItem.Event, + index: Int, + timelineItems: List + ): MessagesTimelineItemState.MessageEvent { + val currentSender = currentTimelineItem.event.sender() + val groupPosition = + computeGroupPosition(currentTimelineItem, timelineItems, index) + val senderDisplayName = room.userDisplayName(currentSender).getOrNull() + val senderAvatarUrl = room.userAvatarUrl(currentSender).getOrNull() + val senderAvatarData = + loadAvatarData(senderDisplayName ?: currentSender, senderAvatarUrl) + + return MessagesTimelineItemState.MessageEvent( + id = currentTimelineItem.event.eventId() ?: "", + senderId = currentSender, + senderDisplayName = senderDisplayName, + senderAvatar = senderAvatarData, + content = currentTimelineItem.computeContent(), + isMine = currentTimelineItem.event.isOwn(), + groupPosition = groupPosition + ) + } + + private fun MatrixTimelineItem.Event.computeContent(): String? { + val messageType = + event.content().asMessage()?.msgtype() + return when (messageType) { + is MessageType.Emote -> messageType.content.body + is MessageType.Image -> messageType.content.body + is MessageType.Notice -> messageType.content.body + is MessageType.Text -> messageType.content.body + null -> null + } + } + + private fun computeGroupPosition( + currentTimelineItem: MatrixTimelineItem.Event, + timelineItems: List, + index: Int + ): MessagesItemGroupPosition { + val prevTimelineItem = + timelineItems.getOrNull(index - 1) as? MatrixTimelineItem.Event + val nextTimelineItem = + timelineItems.getOrNull(index + 1) as? MatrixTimelineItem.Event + val currentSender = currentTimelineItem.event.sender() + val previousSender = prevTimelineItem?.event?.sender() + val nextSender = nextTimelineItem?.event?.sender() + + return when { + previousSender != currentSender && nextSender == currentSender -> MessagesItemGroupPosition.First + previousSender == currentSender && nextSender == currentSender -> MessagesItemGroupPosition.Middle + previousSender == currentSender && nextSender != currentSender -> MessagesItemGroupPosition.Last + else -> MessagesItemGroupPosition.None + } + } + + private suspend fun loadAvatarData( + name: String, + url: String?, + size: AvatarSize = AvatarSize.SMALL + ): AvatarData { + val model = client.mediaResolver() + .resolve(url, kind = MediaResolver.Kind.Thumbnail(size.value)) + return AvatarData(name, model, size) + } + } \ No newline at end of file diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/MessagesScreen.kt b/features/messages/src/main/java/io/element/android/x/features/messages/MessagesScreen.kt index 4c8fdae58d..35b211fe7b 100644 --- a/features/messages/src/main/java/io/element/android/x/features/messages/MessagesScreen.kt +++ b/features/messages/src/main/java/io/element/android/x/features/messages/MessagesScreen.kt @@ -22,6 +22,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.layout.LastBaseline @@ -29,6 +30,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.zIndex import com.airbnb.mvrx.compose.collectAsState import com.airbnb.mvrx.compose.mavericksViewModel import io.element.android.x.core.data.LogCompositions @@ -170,47 +172,51 @@ fun MessageEventRow( .wrapContentHeight(), contentAlignment = contentAlignment ) { - Row(modifier = modifier - .widthIn(max = 300.dp) - .clickable( - onClick = { }, - indication = rememberRipple(), - interactionSource = remember { MutableInteractionSource() } - )) { + Row( + modifier = modifier + .widthIn(max = 300.dp) + ) { if (!messageEvent.isMine) { Spacer(modifier = Modifier.width(16.dp)) } Column { if (messageEvent.showSenderInformation) { - MessageSenderInformation(messageEvent.sender, messageEvent.senderAvatar) + MessageSenderInformation( + messageEvent.safeSenderName, + messageEvent.senderAvatar, + Modifier.zIndex(1f) + ) } - MessageEventBubble(messageEvent) + MessageEventBubble(messageEvent, Modifier.zIndex(-1f)) } if (messageEvent.isMine) { Spacer(modifier = Modifier.width(16.dp)) } } } - if (messageEvent.groupPosition is MessagesItemGroupPosition.First) { - Spacer(modifier = Modifier.height(16.dp)) + if (messageEvent.groupPosition.isNew()) { + Spacer(modifier = Modifier.height(8.dp)) } else { Spacer(modifier = Modifier.height(2.dp)) } } @Composable -private fun MessageSenderInformation(sender: String, senderAvatar: AvatarData?) { - Row { +private fun MessageSenderInformation( + sender: String, + senderAvatar: AvatarData?, + modifier: Modifier = Modifier +) { + Row(modifier = modifier) { if (senderAvatar != null) { Avatar(senderAvatar) - Spacer(modifier = Modifier.width(8.dp)) + Spacer(modifier = Modifier.width(4.dp)) } Text( text = sender, style = MaterialTheme.typography.titleMedium, modifier = Modifier .alignBy(LastBaseline) - .paddingFrom(LastBaseline, after = 8.dp) ) } } @@ -218,6 +224,7 @@ private fun MessageSenderInformation(sender: String, senderAvatar: AvatarData?) @Composable fun MessageEventBubble( messageEvent: MessagesTimelineItemState.MessageEvent, + modifier: Modifier = Modifier, ) { fun MessagesTimelineItemState.MessageEvent.bubbleShape(): Shape { @@ -247,10 +254,28 @@ fun MessageEventBubble( } else { Pair(Color.Transparent, BorderStroke(1.dp, MaterialTheme.colorScheme.surfaceVariant)) } + + fun Modifier.offsetForItem(messageEvent: MessagesTimelineItemState.MessageEvent): Modifier { + return if (messageEvent.isMine) { + offset(y = -(12.dp)) + } else { + offset(x = 20.dp, y = -(12.dp)) + } + } + + val bubbleShape = remember { messageEvent.bubbleShape() } Surface( - modifier = Modifier.widthIn(min = 80.dp), + modifier = modifier + .widthIn(min = 80.dp) + .offsetForItem(messageEvent) + .clip(bubbleShape) + .clickable( + onClick = { }, + indication = rememberRipple(), + interactionSource = remember { MutableInteractionSource() } + ), color = backgroundBubbleColor, - shape = messageEvent.bubbleShape(), + shape = bubbleShape, border = border ) { Text( diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/MessagesViewModel.kt b/features/messages/src/main/java/io/element/android/x/features/messages/MessagesViewModel.kt index 7c4fd23d43..0e79e28802 100644 --- a/features/messages/src/main/java/io/element/android/x/features/messages/MessagesViewModel.kt +++ b/features/messages/src/main/java/io/element/android/x/features/messages/MessagesViewModel.kt @@ -8,6 +8,7 @@ import io.element.android.x.designsystem.components.avatar.AvatarSize import io.element.android.x.features.messages.model.MessagesViewState import io.element.android.x.matrix.MatrixClient import io.element.android.x.matrix.MatrixInstance +import io.element.android.x.matrix.media.MediaResolver import io.element.android.x.matrix.room.MatrixRoom import io.element.android.x.matrix.timeline.MatrixTimeline import kotlinx.coroutines.Dispatchers @@ -15,7 +16,6 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch -import org.matrix.rustcomponents.sdk.mediaSourceFromUrl private const val PAGINATION_COUNT = 50 @@ -38,7 +38,7 @@ class MessagesViewModel( val client = matrix.activeClient() val room = client.getRoom(state.roomId) ?: return null val messageTimelineItemStateMapper = - MessageTimelineItemStateMapper(client.userId(), room, Dispatchers.Default) + MessageTimelineItemStateMapper(client, room, Dispatchers.Default) return MessagesViewModel( client, room, @@ -84,20 +84,9 @@ class MessagesViewModel( url: String?, size: AvatarSize = AvatarSize.MEDIUM ): AvatarData { - val mediaContent = url?.let { - val mediaSource = mediaSourceFromUrl(it) - client.loadMediaThumbnailForSource( - mediaSource, - size.value.toLong(), - size.value.toLong() - ) - } - return mediaContent?.fold( - { it }, - { null } - ).let { model -> - AvatarData(name.first().uppercase(), model, size) - } + val model = client.mediaResolver() + .resolve(url, kind = MediaResolver.Kind.Thumbnail(size.value)) + return AvatarData(name, model, size) } override fun onCleared() { diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesItemGroupPosition.kt b/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesItemGroupPosition.kt index 63f873773e..7a4fd642db 100644 --- a/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesItemGroupPosition.kt +++ b/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesItemGroupPosition.kt @@ -6,4 +6,9 @@ sealed interface MessagesItemGroupPosition { object Last : MessagesItemGroupPosition object None : MessagesItemGroupPosition + fun isNew(): Boolean = when (this) { + First, None -> true + else -> false + } + } diff --git a/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesTimelineItemState.kt b/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesTimelineItemState.kt index da5e028593..4a5ef94165 100644 --- a/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesTimelineItemState.kt +++ b/features/messages/src/main/java/io/element/android/x/features/messages/model/MessagesTimelineItemState.kt @@ -9,18 +9,19 @@ sealed interface MessagesTimelineItemState { data class MessageEvent( val id: String = "", - val sender: String = "", - val senderAvatar: AvatarData? = null, + val senderId: String, + val senderDisplayName: String?, + val senderAvatar: AvatarData, val content: String? = null, val sentTime: String = "", val isMine: Boolean = false, val groupPosition: MessagesItemGroupPosition = MessagesItemGroupPosition.None ) : MessagesTimelineItemState { - val showSenderInformation: Boolean = when (groupPosition) { - MessagesItemGroupPosition.First, MessagesItemGroupPosition.None -> !isMine - else -> false - } + val showSenderInformation = groupPosition.isNew() && !isMine + + val safeSenderName: String = senderDisplayName ?: senderId + } } diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt index 806a0c4d7b..66c3ec73c0 100644 --- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt +++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt @@ -9,6 +9,7 @@ import io.element.android.x.features.roomlist.model.RoomListRoomSummary import io.element.android.x.features.roomlist.model.RoomListViewState import io.element.android.x.matrix.MatrixClient import io.element.android.x.matrix.MatrixInstance +import io.element.android.x.matrix.media.MediaResolver import io.element.android.x.matrix.room.RoomSummary import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -103,20 +104,9 @@ class RoomListViewModel(initialState: RoomListViewState) : url: String?, size: AvatarSize = AvatarSize.MEDIUM ): AvatarData { - val mediaContent = url?.let { - val mediaSource = mediaSourceFromUrl(it) - client.loadMediaThumbnailForSource( - mediaSource, - size.value.toLong(), - size.value.toLong() - ) - } - return mediaContent?.fold( - { it }, - { null } - ).let { model -> - AvatarData(name.first().uppercase(), model, size) - } + val model = client.mediaResolver() + .resolve(url, kind = MediaResolver.Kind.Thumbnail(size.value)) + return AvatarData(name, model, size) } private fun handleLogout() { diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/avatar/Avatar.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/avatar/Avatar.kt index 4dbcd857a5..c5eb17164a 100644 --- a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/avatar/Avatar.kt +++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/avatar/Avatar.kt @@ -12,7 +12,6 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import coil.compose.AsyncImage import io.element.android.x.designsystem.AvatarGradientEnd @@ -27,7 +26,7 @@ fun Avatar(avatarData: AvatarData, modifier: Modifier = Modifier) { if (avatarData.model == null) { InitialsAvatar( modifier = commonModifier, - initials = avatarData.initials + initials = avatarData.name.first().uppercase() ) } else { ImageAvatar( diff --git a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/avatar/AvatarData.kt b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/avatar/AvatarData.kt index 9f2350cefa..ce1fd06dba 100644 --- a/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/avatar/AvatarData.kt +++ b/libraries/designsystem/src/main/java/io/element/android/x/designsystem/components/avatar/AvatarData.kt @@ -4,7 +4,7 @@ import androidx.compose.runtime.Stable @Stable data class AvatarData( - val initials: String = "", + val name: String = "", val model: ByteArray? = null, val size: AvatarSize = AvatarSize.MEDIUM ) { @@ -14,7 +14,7 @@ data class AvatarData( other as AvatarData - if (initials != other.initials) return false + if (name != other.name) return false if (model != null) { if (other.model == null) return false if (!model.contentEquals(other.model)) return false @@ -25,7 +25,7 @@ data class AvatarData( } override fun hashCode(): Int { - var result = initials.hashCode() + var result = name.hashCode() result = 31 * result + (model?.contentHashCode() ?: 0) result = 31 * result + size.value return result diff --git a/libraries/matrix/src/main/java/io/element/android/x/matrix/MatrixClient.kt b/libraries/matrix/src/main/java/io/element/android/x/matrix/MatrixClient.kt index 15712fa720..8e68aa4f69 100644 --- a/libraries/matrix/src/main/java/io/element/android/x/matrix/MatrixClient.kt +++ b/libraries/matrix/src/main/java/io/element/android/x/matrix/MatrixClient.kt @@ -2,6 +2,8 @@ package io.element.android.x.matrix import io.element.android.x.core.data.CoroutineDispatchers import io.element.android.x.matrix.core.UserId +import io.element.android.x.matrix.media.MediaResolver +import io.element.android.x.matrix.media.RustMediaResolver import io.element.android.x.matrix.room.MatrixRoom import io.element.android.x.matrix.room.RoomSummaryDataSource import io.element.android.x.matrix.room.RustRoomSummaryDataSource @@ -64,6 +66,8 @@ class MatrixClient internal constructor( ) private var slidingSyncObserverToken: StoppableSpawn? = null + private val mediaResolver = RustMediaResolver(this) + init { client.setDelegate(clientDelegate) } @@ -93,6 +97,8 @@ class MatrixClient internal constructor( fun roomSummaryDataSource(): RoomSummaryDataSource = roomSummaryDataSource + fun mediaResolver(): MediaResolver = mediaResolver + override fun close() { stopSync() roomSummaryDataSource.close() diff --git a/libraries/matrix/src/main/java/io/element/android/x/matrix/core/EventId.kt b/libraries/matrix/src/main/java/io/element/android/x/matrix/core/EventId.kt index 55afdd934a..ee90c7d1d0 100644 --- a/libraries/matrix/src/main/java/io/element/android/x/matrix/core/EventId.kt +++ b/libraries/matrix/src/main/java/io/element/android/x/matrix/core/EventId.kt @@ -1,4 +1,6 @@ package io.element.android.x.matrix.core +import java.io.Serializable + @JvmInline -value class EventId(val value: String) \ No newline at end of file +value class EventId(val value: String) : Serializable \ No newline at end of file diff --git a/libraries/matrix/src/main/java/io/element/android/x/matrix/core/RoomId.kt b/libraries/matrix/src/main/java/io/element/android/x/matrix/core/RoomId.kt index 48ea0ccfde..cea9a9e8d6 100644 --- a/libraries/matrix/src/main/java/io/element/android/x/matrix/core/RoomId.kt +++ b/libraries/matrix/src/main/java/io/element/android/x/matrix/core/RoomId.kt @@ -1,4 +1,6 @@ package io.element.android.x.matrix.core +import java.io.Serializable + @JvmInline -value class RoomId(val value: String) \ No newline at end of file +value class RoomId(val value: String): Serializable \ No newline at end of file diff --git a/libraries/matrix/src/main/java/io/element/android/x/matrix/core/UserId.kt b/libraries/matrix/src/main/java/io/element/android/x/matrix/core/UserId.kt index 4e8b8cf858..3c499f8487 100644 --- a/libraries/matrix/src/main/java/io/element/android/x/matrix/core/UserId.kt +++ b/libraries/matrix/src/main/java/io/element/android/x/matrix/core/UserId.kt @@ -1,4 +1,6 @@ package io.element.android.x.matrix.core +import java.io.Serializable + @JvmInline -value class UserId(val value: String) \ No newline at end of file +value class UserId(val value: String): Serializable \ No newline at end of file diff --git a/libraries/matrix/src/main/java/io/element/android/x/matrix/media/MediaResolver.kt b/libraries/matrix/src/main/java/io/element/android/x/matrix/media/MediaResolver.kt new file mode 100644 index 0000000000..c72000290f --- /dev/null +++ b/libraries/matrix/src/main/java/io/element/android/x/matrix/media/MediaResolver.kt @@ -0,0 +1,36 @@ +package io.element.android.x.matrix.media + +import io.element.android.x.matrix.MatrixClient +import org.matrix.rustcomponents.sdk.mediaSourceFromUrl + +interface MediaResolver { + + sealed interface Kind { + data class Thumbnail(val width: Int, val height: Int) : Kind { + constructor(size: Int) : this(size, size) + } + + object Content : Kind + } + + suspend fun resolve(url: String?, kind: Kind): ByteArray? +} + + +internal class RustMediaResolver(private val client: MatrixClient) : MediaResolver { + + override suspend fun resolve(url: String?, kind: MediaResolver.Kind): ByteArray? { + if (url.isNullOrEmpty()) return null + val mediaSource = mediaSourceFromUrl(url) + return when (kind) { + is MediaResolver.Kind.Content -> client.loadMediaContentForSource(mediaSource) + is MediaResolver.Kind.Thumbnail -> client.loadMediaThumbnailForSource( + mediaSource, + kind.width.toLong(), + kind.height.toLong() + ) + }.getOrNull() + } + + +} \ No newline at end of file diff --git a/libraries/matrix/src/main/java/io/element/android/x/matrix/room/MatrixRoom.kt b/libraries/matrix/src/main/java/io/element/android/x/matrix/room/MatrixRoom.kt index a6bbca534f..19ff07ddaf 100644 --- a/libraries/matrix/src/main/java/io/element/android/x/matrix/room/MatrixRoom.kt +++ b/libraries/matrix/src/main/java/io/element/android/x/matrix/room/MatrixRoom.kt @@ -2,11 +2,13 @@ package io.element.android.x.matrix.room import io.element.android.x.core.data.CoroutineDispatchers import io.element.android.x.matrix.core.RoomId +import io.element.android.x.matrix.core.UserId import io.element.android.x.matrix.timeline.MatrixTimeline import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.SlidingSyncRoom import org.matrix.rustcomponents.sdk.UpdateSummary @@ -53,5 +55,18 @@ class MatrixRoom( return room.avatarUrl() } + suspend fun userDisplayName(userId: String): Result = + withContext(coroutineDispatchers.io) { + runCatching { + room.memberDisplayName(userId) + } + } + + suspend fun userAvatarUrl(userId: String): Result = + withContext(coroutineDispatchers.io) { + runCatching { + room.memberAvatarUrl(userId) + } + } } \ No newline at end of file