Skip to content

Commit 3ec30d7

Browse files
authored
Merge pull request #7539 from hayaksi1/fix/5356-sender-name-fallback
Name a sender the timeline has no profile for from the room member list
2 parents 0c65dce + e8e67dc commit 3ec30d7

2 files changed

Lines changed: 95 additions & 1 deletion

File tree

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@ import io.element.android.libraries.dateformatter.api.DateFormatterMode
2828
import io.element.android.libraries.designsystem.components.avatar.AvatarData
2929
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
3030
import io.element.android.libraries.matrix.api.MatrixClient
31+
import io.element.android.libraries.matrix.api.core.UserId
3132
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
3233
import io.element.android.libraries.matrix.api.room.RoomMember
3334
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
3435
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
36+
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails
3537
import io.element.android.libraries.matrix.api.timeline.item.event.getAvatarUrl
3638
import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName
3739
import io.element.android.libraries.matrix.ui.messages.reply.map
@@ -64,7 +66,7 @@ class TimelineItemEventFactory(
6466
val currentSender = currentTimelineItem.event.sender
6567
val groupPosition =
6668
computeGroupPosition(currentTimelineItem, timelineItems, index)
67-
val senderProfile = currentTimelineItem.event.senderProfile
69+
val senderProfile = currentTimelineItem.event.senderProfile.orFallbackTo(roomMembers, currentSender)
6870
val sentTime = dateFormatter.format(
6971
timestamp = currentTimelineItem.event.timestamp,
7072
mode = DateFormatterMode.TimeOnly,
@@ -138,11 +140,32 @@ class TimelineItemEventFactory(
138140
roomMembers: List<RoomMember>,
139141
renderReadReceipts: Boolean,
140142
): TimelineItem.Event {
143+
val senderProfile = receivedMatrixTimelineItem.event.senderProfile.orFallbackTo(roomMembers, timelineItem.senderId)
141144
return timelineItem.copy(
145+
senderProfile = senderProfile,
146+
senderAvatar = timelineItem.senderAvatar.copy(
147+
name = senderProfile.getDisambiguatedDisplayName(timelineItem.senderId),
148+
url = senderProfile.getAvatarUrl(),
149+
),
142150
readReceiptState = receivedMatrixTimelineItem.computeReadReceiptState(roomMembers, renderReadReceipts)
143151
)
144152
}
145153

154+
/**
155+
* The timeline only knows the profiles the SDK has resolved, so a sender it has nothing for is rendered as a raw
156+
* user ID. The room member list usually knows that user, so use it rather than showing the ID.
157+
*/
158+
private fun ProfileDetails.orFallbackTo(roomMembers: List<RoomMember>, senderId: UserId): ProfileDetails {
159+
if (this is ProfileDetails.Ready) return this
160+
val member = roomMembers.find { it.userId == senderId } ?: return this
161+
return ProfileDetails.Ready(
162+
displayName = member.displayName,
163+
displayNameAmbiguous = member.isNameAmbiguous,
164+
avatarUrl = member.avatarUrl,
165+
displayedStatus = member.displayedStatus,
166+
)
167+
}
168+
146169
private fun MatrixTimelineItem.Event.computeReactionsState(): TimelineItemReactions {
147170
if (!config.computeReactions) {
148171
return TimelineItemReactions(reactions = persistentListOf())

features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactoryTest.kt

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@ import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
2020
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
2121
import io.element.android.libraries.matrix.api.timeline.item.ThreadSummary
2222
import io.element.android.libraries.matrix.api.timeline.item.event.OtherMessageType
23+
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails
2324
import io.element.android.libraries.matrix.test.A_THREAD_ID
2425
import io.element.android.libraries.matrix.test.A_USER_ID
26+
import io.element.android.libraries.matrix.test.A_USER_ID_2
27+
import io.element.android.libraries.matrix.test.room.aRoomMember
2528
import io.element.android.libraries.matrix.test.timeline.aMessageContent
2629
import io.element.android.libraries.matrix.test.timeline.aRedactedContent
2730
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
@@ -207,6 +210,74 @@ class TimelineItemsFactoryTest {
207210
}
208211
}
209212

213+
@Test
214+
fun `a sender the timeline knows nothing about is named from the room member list`() = runTest {
215+
val factory = aTimelineItemsFactory(
216+
config = TimelineItemsFactoryConfig(
217+
computeReadReceipts = false,
218+
computeReactions = false,
219+
)
220+
)
221+
val items = listOf(
222+
MatrixTimelineItem.Event(
223+
uniqueId = UniqueId("event-0"),
224+
event = anEventTimelineItem(
225+
sender = A_USER_ID,
226+
senderProfile = ProfileDetails.Unavailable,
227+
content = aMessageContent(body = "A regular message"),
228+
),
229+
),
230+
)
231+
factory.timelineItems.test {
232+
factory.replaceWith(
233+
timelineItems = items,
234+
roomMembers = listOf(aRoomMember(userId = A_USER_ID, displayName = "Alice")),
235+
renderReadReceipts = false,
236+
)
237+
val event = awaitItem().filterIsInstance<TimelineItem.Event>().single()
238+
assertThat(event.senderProfile).isEqualTo(
239+
ProfileDetails.Ready(
240+
displayName = "Alice",
241+
displayNameAmbiguous = false,
242+
avatarUrl = null,
243+
displayedStatus = null,
244+
)
245+
)
246+
assertThat(event.senderAvatar.name).isEqualTo("Alice")
247+
cancelAndIgnoreRemainingEvents()
248+
}
249+
}
250+
251+
@Test
252+
fun `a sender the room member list knows nothing about keeps the profile the timeline gave`() = runTest {
253+
val factory = aTimelineItemsFactory(
254+
config = TimelineItemsFactoryConfig(
255+
computeReadReceipts = false,
256+
computeReactions = false,
257+
)
258+
)
259+
val items = listOf(
260+
MatrixTimelineItem.Event(
261+
uniqueId = UniqueId("event-0"),
262+
event = anEventTimelineItem(
263+
sender = A_USER_ID,
264+
senderProfile = ProfileDetails.Unavailable,
265+
content = aMessageContent(body = "A regular message"),
266+
),
267+
),
268+
)
269+
factory.timelineItems.test {
270+
factory.replaceWith(
271+
timelineItems = items,
272+
roomMembers = listOf(aRoomMember(userId = A_USER_ID_2, displayName = "Bob")),
273+
renderReadReceipts = false,
274+
)
275+
val event = awaitItem().filterIsInstance<TimelineItem.Event>().single()
276+
assertThat(event.senderProfile).isEqualTo(ProfileDetails.Unavailable)
277+
cancelAndIgnoreRemainingEvents()
278+
}
279+
}
280+
210281
private suspend fun TestScope.groupPositionsOf(timestamps: List<Long>): List<TimelineItemGroupPosition> {
211282
val factory = aTimelineItemsFactory(
212283
config = TimelineItemsFactoryConfig(

0 commit comments

Comments
 (0)