mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 20:38:40 +00:00
Name a sender the timeline has no profile for from the room member list
The timeline renders a message with the sender's raw Matrix ID whenever the SDK has no profile for them, even though the room member list the same screen already holds usually knows their display name and avatar. The timeline item factory now falls back to the room member for a sender whose profile is not resolved, both when the item is first built and when the member list arrives afterwards, so the name and the avatar settle on the display name instead of staying on the ID.
This commit is contained in:
+24
-1
@@ -28,10 +28,12 @@ import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.getAvatarUrl
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.map
|
||||
@@ -64,7 +66,7 @@ class TimelineItemEventFactory(
|
||||
val currentSender = currentTimelineItem.event.sender
|
||||
val groupPosition =
|
||||
computeGroupPosition(currentTimelineItem, timelineItems, index)
|
||||
val senderProfile = currentTimelineItem.event.senderProfile
|
||||
val senderProfile = currentTimelineItem.event.senderProfile.orFallbackTo(roomMembers, currentSender)
|
||||
val sentTime = dateFormatter.format(
|
||||
timestamp = currentTimelineItem.event.timestamp,
|
||||
mode = DateFormatterMode.TimeOnly,
|
||||
@@ -138,11 +140,32 @@ class TimelineItemEventFactory(
|
||||
roomMembers: List<RoomMember>,
|
||||
renderReadReceipts: Boolean,
|
||||
): TimelineItem.Event {
|
||||
val senderProfile = receivedMatrixTimelineItem.event.senderProfile.orFallbackTo(roomMembers, timelineItem.senderId)
|
||||
return timelineItem.copy(
|
||||
senderProfile = senderProfile,
|
||||
senderAvatar = timelineItem.senderAvatar.copy(
|
||||
name = senderProfile.getDisambiguatedDisplayName(timelineItem.senderId),
|
||||
url = senderProfile.getAvatarUrl(),
|
||||
),
|
||||
readReceiptState = receivedMatrixTimelineItem.computeReadReceiptState(roomMembers, renderReadReceipts)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The timeline only knows the profiles the SDK has resolved, so a sender it has nothing for is rendered as a raw
|
||||
* user ID. The room member list usually knows that user, so use it rather than showing the ID.
|
||||
*/
|
||||
private fun ProfileDetails.orFallbackTo(roomMembers: List<RoomMember>, senderId: UserId): ProfileDetails {
|
||||
if (this is ProfileDetails.Ready) return this
|
||||
val member = roomMembers.find { it.userId == senderId } ?: return this
|
||||
return ProfileDetails.Ready(
|
||||
displayName = member.displayName,
|
||||
displayNameAmbiguous = member.isNameAmbiguous,
|
||||
avatarUrl = member.avatarUrl,
|
||||
displayedStatus = member.displayedStatus,
|
||||
)
|
||||
}
|
||||
|
||||
private fun MatrixTimelineItem.Event.computeReactionsState(): TimelineItemReactions {
|
||||
if (!config.computeReactions) {
|
||||
return TimelineItemReactions(reactions = persistentListOf())
|
||||
|
||||
+71
@@ -16,7 +16,10 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
||||
import io.element.android.libraries.matrix.api.core.UniqueId
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.OtherMessageType
|
||||
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.A_USER_ID_2
|
||||
import io.element.android.libraries.matrix.test.room.aRoomMember
|
||||
import io.element.android.libraries.matrix.test.timeline.aMessageContent
|
||||
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
@@ -128,6 +131,74 @@ class TimelineItemsFactoryTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a sender the timeline knows nothing about is named from the room member list`() = runTest {
|
||||
val factory = aTimelineItemsFactory(
|
||||
config = TimelineItemsFactoryConfig(
|
||||
computeReadReceipts = false,
|
||||
computeReactions = false,
|
||||
)
|
||||
)
|
||||
val items = listOf(
|
||||
MatrixTimelineItem.Event(
|
||||
uniqueId = UniqueId("event-0"),
|
||||
event = anEventTimelineItem(
|
||||
sender = A_USER_ID,
|
||||
senderProfile = ProfileDetails.Unavailable,
|
||||
content = aMessageContent(body = "A regular message"),
|
||||
),
|
||||
),
|
||||
)
|
||||
factory.timelineItems.test {
|
||||
factory.replaceWith(
|
||||
timelineItems = items,
|
||||
roomMembers = listOf(aRoomMember(userId = A_USER_ID, displayName = "Alice")),
|
||||
renderReadReceipts = false,
|
||||
)
|
||||
val event = awaitItem().filterIsInstance<TimelineItem.Event>().single()
|
||||
assertThat(event.senderProfile).isEqualTo(
|
||||
ProfileDetails.Ready(
|
||||
displayName = "Alice",
|
||||
displayNameAmbiguous = false,
|
||||
avatarUrl = null,
|
||||
displayedStatus = null,
|
||||
)
|
||||
)
|
||||
assertThat(event.senderAvatar.name).isEqualTo("Alice")
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a sender the room member list knows nothing about keeps the profile the timeline gave`() = runTest {
|
||||
val factory = aTimelineItemsFactory(
|
||||
config = TimelineItemsFactoryConfig(
|
||||
computeReadReceipts = false,
|
||||
computeReactions = false,
|
||||
)
|
||||
)
|
||||
val items = listOf(
|
||||
MatrixTimelineItem.Event(
|
||||
uniqueId = UniqueId("event-0"),
|
||||
event = anEventTimelineItem(
|
||||
sender = A_USER_ID,
|
||||
senderProfile = ProfileDetails.Unavailable,
|
||||
content = aMessageContent(body = "A regular message"),
|
||||
),
|
||||
),
|
||||
)
|
||||
factory.timelineItems.test {
|
||||
factory.replaceWith(
|
||||
timelineItems = items,
|
||||
roomMembers = listOf(aRoomMember(userId = A_USER_ID_2, displayName = "Bob")),
|
||||
renderReadReceipts = false,
|
||||
)
|
||||
val event = awaitItem().filterIsInstance<TimelineItem.Event>().single()
|
||||
assertThat(event.senderProfile).isEqualTo(ProfileDetails.Unavailable)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun TestScope.groupPositionsOf(timestamps: List<Long>): List<TimelineItemGroupPosition> {
|
||||
val factory = aTimelineItemsFactory(
|
||||
config = TimelineItemsFactoryConfig(
|
||||
|
||||
Reference in New Issue
Block a user