From 03abfcaadb98fbf71fc7abc835fc4875d9d6f2a1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 22 Apr 2024 11:52:53 +0200 Subject: [PATCH] Format state Event: use disambiguated Display name #2722. - Rename some parameter and val from `senderDisplayName` (and consort) to `senderDisambiguatedDisplayName`. - In `InReplyToDetails`, replace `senderDisplayName` and `senderAvatarUrl` by `senderProfile`. --- .../impl/actionlist/ActionListView.kt | 4 +- .../impl/timeline/TimelineStateProvider.kt | 4 +- .../components/TimelineItemEventRow.kt | 11 ++--- ...melineItemEventRowLongSenderNamePreview.kt | 2 +- .../TimelineItemEventRowTimestampPreview.kt | 2 +- .../TimelineItemEventRowWithReplyPreview.kt | 8 +++- .../event/TimelineItemContentFactory.kt | 8 +++- .../TimelineItemContentMessageFactory.kt | 12 +++-- .../event/TimelineItemEventFactory.kt | 14 +++--- .../impl/timeline/model/InReplyToDetails.kt | 7 ++- .../impl/timeline/model/TimelineItem.kt | 4 +- .../impl/fixtures/MessageEventFixtures.kt | 2 +- .../TimelineItemContentMessageFactoryTest.kt | 44 +++++++++---------- .../groups/TimelineItemGrouperTest.kt | 2 +- .../timeline/model/InReplyToDetailTest.kt | 10 ++--- .../timeline/model/InReplyToMetadataKtTest.kt | 8 ++-- .../impl/DefaultRoomLastMessageFormatter.kt | 44 +++++++++++-------- .../impl/DefaultTimelineEventFormatter.kt | 8 ++-- .../impl/ProfileChangeContentFormatter.kt | 11 +++-- .../impl/RoomMembershipContentFormatter.kt | 22 +++++----- .../impl/StateContentFormatter.kt | 18 ++++---- .../api/notification/NotificationData.kt | 5 ++- .../api/timeline/item/event/InReplyTo.kt | 3 +- .../timeline/item/event/EventMessageMapper.kt | 5 +-- .../notifications/NotifiableEventResolver.kt | 18 ++++---- .../notifications/RoomGroupMessageCreator.kt | 12 ++--- .../model/NotifiableMessageEvent.kt | 3 +- .../NotifiableEventResolverTest.kt | 4 +- .../fixtures/NotifiableEventFixture.kt | 2 +- 29 files changed, 157 insertions(+), 140 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt index d27263f989..613fb9370f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt @@ -269,9 +269,9 @@ private fun MessageSummary(event: TimelineItem.Event, modifier: Modifier = Modif Spacer(modifier = Modifier.width(8.dp)) Column(modifier = Modifier.weight(1f)) { Row { - if (event.senderDisplayName != null) { + if (event.senderDisambiguatedDisplayName != null) { Text( - text = event.senderDisplayName, + text = event.senderDisambiguatedDisplayName, style = ElementTheme.typography.fontBodySmMedium, color = MaterialTheme.colorScheme.primary ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt index ae7f62ebd7..e9322a6087 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt @@ -130,7 +130,7 @@ internal fun aTimelineItemEvent( transactionId: TransactionId? = null, isMine: Boolean = false, isEditable: Boolean = false, - senderDisplayName: String = "Sender", + senderDisambiguatedDisplayName: String = "Sender", content: TimelineItemEventContent = aTimelineItemTextContent(), groupPosition: TimelineItemGroupPosition = TimelineItemGroupPosition.None, sendState: LocalEventSendState? = null, @@ -152,7 +152,7 @@ internal fun aTimelineItemEvent( sentTime = "12:34", isMine = isMine, isEditable = isEditable, - senderDisplayName = senderDisplayName, + senderDisambiguatedDisplayName = senderDisambiguatedDisplayName, groupPosition = groupPosition, localSendState = sendState, inReplyTo = inReplyTo, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index 22bce0e873..d5cdd36dab 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -106,6 +106,7 @@ import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text 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.getDisambiguatedDisplayName import io.element.android.libraries.matrix.ui.components.AttachmentThumbnail import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.ui.strings.CommonStrings @@ -561,10 +562,10 @@ private fun MessageEventBubbleContent( } } val inReplyTo = @Composable { inReplyTo: InReplyToDetails -> - val senderName = inReplyTo.senderDisplayName ?: inReplyTo.senderId.value + val senderDisambiguatedDisplayName = inReplyTo.senderProfile.getDisambiguatedDisplayName(inReplyTo.senderId) val topPadding = if (showThreadDecoration) 0.dp else 8.dp ReplyToContent( - senderName = senderName, + senderDisambiguatedDisplayName = senderDisambiguatedDisplayName, metadata = inReplyTo.metadata(), modifier = Modifier .padding(top = topPadding, start = 8.dp, end = 8.dp) @@ -609,7 +610,7 @@ private fun MessageEventBubbleContent( @Composable private fun ReplyToContent( - senderName: String, + senderDisambiguatedDisplayName: String, metadata: InReplyToMetadata?, modifier: Modifier = Modifier, ) { @@ -633,13 +634,13 @@ private fun ReplyToContent( ) Spacer(modifier = Modifier.width(8.dp)) } - val a11InReplyToText = stringResource(CommonStrings.common_in_reply_to, senderName) + val a11InReplyToText = stringResource(CommonStrings.common_in_reply_to, senderDisambiguatedDisplayName) Column(verticalArrangement = Arrangement.SpaceBetween) { Text( modifier = Modifier.semantics { contentDescription = a11InReplyToText }, - text = senderName, + text = senderDisambiguatedDisplayName, style = ElementTheme.typography.fontBodySmMedium, textAlign = TextAlign.Start, color = ElementTheme.materialColors.primary, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowLongSenderNamePreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowLongSenderNamePreview.kt index 061ad75690..c374d15709 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowLongSenderNamePreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowLongSenderNamePreview.kt @@ -27,7 +27,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight internal fun TimelineItemEventRowLongSenderNamePreview() = ElementPreviewLight { ATimelineItemEventRow( event = aTimelineItemEvent( - senderDisplayName = "a long sender display name to test single line and ellipsis at the end of the line", + senderDisambiguatedDisplayName = "a long sender display name to test single line and ellipsis at the end of the line", ), ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowTimestampPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowTimestampPreview.kt index e6525584b8..b9bb881bb6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowTimestampPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowTimestampPreview.kt @@ -43,7 +43,7 @@ internal fun TimelineItemEventRowTimestampPreview( body = str, ), reactionsState = aTimelineItemReactions(count = 0), - senderDisplayName = "A sender", + senderDisambiguatedDisplayName = "A sender", ), ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyPreview.kt index 20b9a01e3f..ca81acbd72 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyPreview.kt @@ -42,6 +42,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.MessageConten import io.element.android.libraries.matrix.api.timeline.item.event.MessageType import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType import io.element.android.libraries.matrix.api.timeline.item.event.PollContent +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails import io.element.android.libraries.matrix.api.timeline.item.event.StickerMessageType import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType @@ -167,8 +168,11 @@ open class InReplyToDetailsProvider : PreviewParameterProvider eventId = EventId("\$event"), eventContent = eventContent, senderId = UserId("@Sender:domain"), - senderDisplayName = "Sender", - senderAvatarUrl = null, + senderProfile = ProfileTimelineDetails.Ready( + displayName = "Sender", + displayNameAmbiguous = false, + avatarUrl = null, + ), textContent = (eventContent as? MessageContent)?.body.orEmpty(), ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt index 4d9cfbea42..807137a978 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt @@ -52,8 +52,12 @@ class TimelineItemContentFactory @Inject constructor( is FailedToParseMessageLikeContent -> failedToParseMessageFactory.create(itemContent) is FailedToParseStateContent -> failedToParseStateFactory.create(itemContent) is MessageContent -> { - val senderDisplayName = eventTimelineItem.senderProfile.getDisambiguatedDisplayName(eventTimelineItem.sender) - messageFactory.create(itemContent, senderDisplayName, eventTimelineItem.eventId) + val senderDisambiguatedDisplayName = eventTimelineItem.senderProfile.getDisambiguatedDisplayName(eventTimelineItem.sender) + messageFactory.create( + content = itemContent, + senderDisambiguatedDisplayName = senderDisambiguatedDisplayName, + eventId = eventTimelineItem.eventId, + ) } is ProfileChangeContent -> profileChangeFactory.create(eventTimelineItem) is RedactedContent -> redactedMessageFactory.create(itemContent) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt index acd99d3704..2aba9c3669 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt @@ -70,17 +70,21 @@ class TimelineItemContentMessageFactory @Inject constructor( private val htmlConverterProvider: HtmlConverterProvider, private val permalinkParser: PermalinkParser, ) { - suspend fun create(content: MessageContent, senderDisplayName: String, eventId: EventId?): TimelineItemEventContent { + suspend fun create( + content: MessageContent, + senderDisambiguatedDisplayName: String, + eventId: EventId?, + ): TimelineItemEventContent { return when (val messageType = content.type) { is EmoteMessageType -> { - val emoteBody = "* $senderDisplayName ${messageType.body.trimEnd()}" + val emoteBody = "* $senderDisambiguatedDisplayName ${messageType.body.trimEnd()}" TimelineItemEmoteContent( body = emoteBody, htmlDocument = messageType.formatted?.toHtmlDocument( permalinkParser = permalinkParser, - prefix = "* $senderDisplayName", + prefix = "* $senderDisambiguatedDisplayName", ), - formattedBody = parseHtml(messageType.formatted, prefix = "* $senderDisplayName") ?: emoteBody.withLinks(), + formattedBody = parseHtml(messageType.formatted, prefix = "* $senderDisambiguatedDisplayName") ?: emoteBody.withLinks(), isEdited = content.isEdited, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt index 0522379f72..7945a88876 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt @@ -55,14 +55,14 @@ class TimelineItemEventFactory @Inject constructor( val currentSender = currentTimelineItem.event.sender val groupPosition = computeGroupPosition(currentTimelineItem, timelineItems, index) - val (senderDisplayName, senderAvatarUrl) = currentTimelineItem.getSenderInfo() + val (senderDisambiguatedDisplayName, senderAvatarUrl) = currentTimelineItem.getSenderInfo() val timeFormatter = DateFormat.getTimeInstance(DateFormat.SHORT) val sentTime = timeFormatter.format(Date(currentTimelineItem.event.timestamp)) val senderAvatarData = AvatarData( id = currentSender.value, - name = senderDisplayName ?: currentSender.value, + name = senderDisambiguatedDisplayName ?: currentSender.value, url = senderAvatarUrl, size = AvatarSize.TimelineSender ) @@ -72,7 +72,7 @@ class TimelineItemEventFactory @Inject constructor( eventId = currentTimelineItem.eventId, transactionId = currentTimelineItem.transactionId, senderId = currentSender, - senderDisplayName = senderDisplayName, + senderDisambiguatedDisplayName = senderDisambiguatedDisplayName, senderAvatar = senderAvatarData, content = contentFactory.create(currentTimelineItem.event), isMine = currentTimelineItem.event.isOwn, @@ -100,23 +100,23 @@ class TimelineItemEventFactory @Inject constructor( } private fun MatrixTimelineItem.Event.getSenderInfo(): Pair { - val senderDisplayName: String? + val senderDisambiguatedDisplayName: String? val senderAvatarUrl: String? when (val senderProfile = event.senderProfile) { ProfileTimelineDetails.Unavailable, ProfileTimelineDetails.Pending, is ProfileTimelineDetails.Error -> { - senderDisplayName = null + senderDisambiguatedDisplayName = null senderAvatarUrl = null } is ProfileTimelineDetails.Ready -> { - senderDisplayName = senderProfile.getDisambiguatedDisplayName(event.sender) + senderDisambiguatedDisplayName = senderProfile.getDisambiguatedDisplayName(event.sender) senderAvatarUrl = senderProfile.avatarUrl } } - return senderDisplayName to senderAvatarUrl + return senderDisambiguatedDisplayName to senderAvatarUrl } private fun MatrixTimelineItem.Event.computeReactionsState(): TimelineItemReactions { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToDetails.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToDetails.kt index 3c629f23fc..83627b5b25 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToDetails.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToDetails.kt @@ -22,6 +22,7 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.timeline.item.event.EventContent import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType import io.element.android.libraries.matrix.ui.messages.toPlainText @@ -29,8 +30,7 @@ import io.element.android.libraries.matrix.ui.messages.toPlainText data class InReplyToDetails( val eventId: EventId, val senderId: UserId, - val senderDisplayName: String?, - val senderAvatarUrl: String?, + val senderProfile: ProfileTimelineDetails, val eventContent: EventContent?, val textContent: String?, ) @@ -41,8 +41,7 @@ fun InReplyTo.map( is InReplyTo.Ready -> InReplyToDetails( eventId = eventId, senderId = senderId, - senderDisplayName = senderDisplayName, - senderAvatarUrl = senderAvatarUrl, + senderProfile = senderProfile, eventContent = content, textContent = when (content) { is MessageContent -> { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt index fa00c26760..aa21702e35 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt @@ -57,7 +57,7 @@ sealed interface TimelineItem { val eventId: EventId? = null, val transactionId: TransactionId? = null, val senderId: UserId, - val senderDisplayName: String?, + val senderDisambiguatedDisplayName: String?, val senderAvatar: AvatarData, val content: TimelineItemEventContent, val sentTime: String = "", @@ -74,7 +74,7 @@ sealed interface TimelineItem { ) : TimelineItem { val showSenderInformation = groupPosition.isNew() && !isMine - val safeSenderName: String = senderDisplayName ?: senderId.value + val safeSenderName: String = senderDisambiguatedDisplayName ?: senderId.value val failedToSend: Boolean = localSendState is LocalEventSendState.SendingFailed diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt index f700ed34c3..2a4e7e5319 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt @@ -48,7 +48,7 @@ internal fun aMessageEvent( id = eventId?.value.orEmpty(), eventId = eventId, senderId = A_USER_ID, - senderDisplayName = A_USER_NAME, + senderDisambiguatedDisplayName = A_USER_NAME, senderAvatar = AvatarData(A_USER_ID.value, A_USER_NAME, size = AvatarSize.TimelineSender), content = content, sentTime = "", diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt index 6e475707ad..35de78f65b 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt @@ -82,7 +82,7 @@ class TimelineItemContentMessageFactoryTest { val sut = createTimelineItemContentMessageFactory() val result = sut.create( content = createMessageContent(type = OtherMessageType(msgType = "a_type", body = "body")), - senderDisplayName = "Bob", + senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) val expected = TimelineItemTextContent( @@ -100,7 +100,7 @@ class TimelineItemContentMessageFactoryTest { val sut = createTimelineItemContentMessageFactory() val result = sut.create( content = createMessageContent(type = LocationMessageType("body", "geo:1,2", "description")), - senderDisplayName = "Bob", + senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) val expected = TimelineItemLocationContent( @@ -116,7 +116,7 @@ class TimelineItemContentMessageFactoryTest { val sut = createTimelineItemContentMessageFactory() val result = sut.create( content = createMessageContent(type = LocationMessageType("body", "", null)), - senderDisplayName = "Bob", + senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) val expected = TimelineItemTextContent( @@ -134,7 +134,7 @@ class TimelineItemContentMessageFactoryTest { val sut = createTimelineItemContentMessageFactory() val result = sut.create( content = createMessageContent(type = TextMessageType("body", null)), - senderDisplayName = "Bob", + senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) val expected = TimelineItemTextContent( @@ -152,7 +152,7 @@ class TimelineItemContentMessageFactoryTest { val sut = createTimelineItemContentMessageFactory() val result = sut.create( content = createMessageContent(type = TextMessageType("https://www.example.org", null)), - senderDisplayName = "Bob", + senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) as TimelineItemTextContent val expected = TimelineItemTextContent( @@ -200,7 +200,7 @@ class TimelineItemContentMessageFactoryTest { formatted = FormattedBody(MessageFormat.HTML, expected.toString()) ) ), - senderDisplayName = "Bob", + senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) assertThat((result as TimelineItemTextContent).formattedBody).isEqualTo(expected) @@ -218,7 +218,7 @@ class TimelineItemContentMessageFactoryTest { formatted = FormattedBody(MessageFormat.UNKNOWN, "formatted") ) ), - senderDisplayName = "Bob", + senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) assertThat((result as TimelineItemTextContent).formattedBody).isNull() @@ -229,7 +229,7 @@ class TimelineItemContentMessageFactoryTest { val sut = createTimelineItemContentMessageFactory() val result = sut.create( content = createMessageContent(type = VideoMessageType("body", null, null, MediaSource("url"), null)), - senderDisplayName = "Bob", + senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) val expected = TimelineItemVideoContent( @@ -277,7 +277,7 @@ class TimelineItemContentMessageFactoryTest { ), ) ), - senderDisplayName = "Bob", + senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) val expected = TimelineItemVideoContent( @@ -303,7 +303,7 @@ class TimelineItemContentMessageFactoryTest { val sut = createTimelineItemContentMessageFactory() val result = sut.create( content = createMessageContent(type = AudioMessageType("body", MediaSource("url"), null)), - senderDisplayName = "Bob", + senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) val expected = TimelineItemAudioContent( @@ -332,7 +332,7 @@ class TimelineItemContentMessageFactoryTest { ) ) ), - senderDisplayName = "Bob", + senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) val expected = TimelineItemAudioContent( @@ -351,7 +351,7 @@ class TimelineItemContentMessageFactoryTest { val sut = createTimelineItemContentMessageFactory() val result = sut.create( content = createMessageContent(type = VoiceMessageType("body", MediaSource("url"), null, null)), - senderDisplayName = "Bob", + senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) val expected = TimelineItemVoiceContent( @@ -384,7 +384,7 @@ class TimelineItemContentMessageFactoryTest { ), ) ), - senderDisplayName = "Bob", + senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) val expected = TimelineItemVoiceContent( @@ -409,7 +409,7 @@ class TimelineItemContentMessageFactoryTest { ) val result = sut.create( content = createMessageContent(type = VoiceMessageType("body", MediaSource("url"), null, null)), - senderDisplayName = "Bob", + senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) val expected = TimelineItemAudioContent( @@ -428,7 +428,7 @@ class TimelineItemContentMessageFactoryTest { val sut = createTimelineItemContentMessageFactory() val result = sut.create( content = createMessageContent(type = ImageMessageType("body", null, null, MediaSource("url"), null)), - senderDisplayName = "Bob", + senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) val expected = TimelineItemImageContent( @@ -499,7 +499,7 @@ class TimelineItemContentMessageFactoryTest { ) ) ), - senderDisplayName = "Bob", + senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) val expected = TimelineItemImageContent( @@ -524,7 +524,7 @@ class TimelineItemContentMessageFactoryTest { val sut = createTimelineItemContentMessageFactory() val result = sut.create( content = createMessageContent(type = FileMessageType("body", MediaSource("url"), null)), - senderDisplayName = "Bob", + senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) val expected = TimelineItemFileContent( @@ -559,7 +559,7 @@ class TimelineItemContentMessageFactoryTest { ) ) ), - senderDisplayName = "Bob", + senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) val expected = TimelineItemFileContent( @@ -578,7 +578,7 @@ class TimelineItemContentMessageFactoryTest { val sut = createTimelineItemContentMessageFactory() val result = sut.create( content = createMessageContent(type = NoticeMessageType("body", null)), - senderDisplayName = "Bob", + senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) val expected = TimelineItemNoticeContent( @@ -601,7 +601,7 @@ class TimelineItemContentMessageFactoryTest { formatted = FormattedBody(MessageFormat.HTML, "formatted") ) ), - senderDisplayName = "Bob", + senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) assertThat((result as TimelineItemNoticeContent).formattedBody).isEqualTo("formatted") @@ -612,7 +612,7 @@ class TimelineItemContentMessageFactoryTest { val sut = createTimelineItemContentMessageFactory() val result = sut.create( content = createMessageContent(type = EmoteMessageType("body", null)), - senderDisplayName = "Bob", + senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) val expected = TimelineItemEmoteContent( @@ -635,7 +635,7 @@ class TimelineItemContentMessageFactoryTest { formatted = FormattedBody(MessageFormat.HTML, "formatted") ) ), - senderDisplayName = "Bob", + senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) assertThat((result as TimelineItemEmoteContent).formattedBody).isEqualTo(SpannableString("* Bob formatted")) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt index 42d4b3388a..d560a87e1b 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt @@ -39,7 +39,7 @@ class TimelineItemGrouperTest { id = "0", senderId = A_USER_ID, senderAvatar = anAvatarData(), - senderDisplayName = "", + senderDisambiguatedDisplayName = "", content = TimelineItemStateEventContent(body = "a state event"), reactionsState = aTimelineItemReactions(count = 0), readReceiptState = TimelineItemReadReceipts(emptyList().toImmutableList()), diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToDetailTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToDetailTest.kt index bf287341ad..86355f82dc 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToDetailTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToDetailTest.kt @@ -27,6 +27,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageTy import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser +import io.element.android.libraries.matrix.test.timeline.aProfileTimelineDetails import org.junit.Test class InReplyToDetailTest { @@ -54,8 +55,7 @@ class InReplyToDetailTest { val inReplyTo = InReplyTo.Ready( eventId = AN_EVENT_ID, senderId = A_USER_ID, - senderDisplayName = "senderDisplayName", - senderAvatarUrl = "senderAvatarUrl", + senderProfile = aProfileTimelineDetails(), content = RoomMembershipContent( userId = A_USER_ID, change = MembershipChange.INVITED, @@ -73,8 +73,7 @@ class InReplyToDetailTest { val inReplyTo = InReplyTo.Ready( eventId = AN_EVENT_ID, senderId = A_USER_ID, - senderDisplayName = "senderDisplayName", - senderAvatarUrl = "senderAvatarUrl", + senderProfile = aProfileTimelineDetails(), content = MessageContent( body = "**Hello!**", inReplyTo = null, @@ -101,8 +100,7 @@ class InReplyToDetailTest { val inReplyTo = InReplyTo.Ready( eventId = AN_EVENT_ID, senderId = A_USER_ID, - senderDisplayName = "senderDisplayName", - senderAvatarUrl = "senderAvatarUrl", + senderProfile = aProfileTimelineDetails(), content = MessageContent( body = "**Hello!**", inReplyTo = null, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToMetadataKtTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToMetadataKtTest.kt index 53a0a0eec3..dc91de52b0 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToMetadataKtTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToMetadataKtTest.kt @@ -42,6 +42,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageT import io.element.android.libraries.matrix.api.timeline.item.event.LocationMessageType import io.element.android.libraries.matrix.api.timeline.item.event.OtherState 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.RedactedContent import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent import io.element.android.libraries.matrix.api.timeline.item.event.StateContent @@ -55,6 +56,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.media.aMediaSource import io.element.android.libraries.matrix.test.timeline.aMessageContent import io.element.android.libraries.matrix.test.timeline.aPollContent +import io.element.android.libraries.matrix.test.timeline.aProfileTimelineDetails import io.element.android.libraries.matrix.ui.components.A_BLUR_HASH import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType @@ -430,15 +432,13 @@ class InReplyToMetadataKtTest { fun anInReplyToDetails( eventId: EventId = AN_EVENT_ID, senderId: UserId = A_USER_ID, - senderDisplayName: String? = "senderDisplayName", - senderAvatarUrl: String? = "senderAvatarUrl", + senderProfile: ProfileTimelineDetails = aProfileTimelineDetails(), eventContent: EventContent? = aMessageContent(), textContent: String? = "textContent", ) = InReplyToDetails( eventId = eventId, senderId = senderId, - senderDisplayName = senderDisplayName, - senderAvatarUrl = senderAvatarUrl, + senderProfile = senderProfile, eventContent = eventContent, textContent = textContent, ) diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt index ffaabb45ea..f541941818 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt @@ -41,7 +41,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessage import io.element.android.libraries.matrix.api.timeline.item.event.OtherMessageType 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.RedactedContent import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent import io.element.android.libraries.matrix.api.timeline.item.event.StateContent @@ -52,6 +51,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecry import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType +import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName import io.element.android.libraries.matrix.ui.messages.toPlainText import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.services.toolbox.api.strings.StringProvider @@ -72,15 +72,13 @@ class DefaultRoomLastMessageFormatter @Inject constructor( override fun format(event: EventTimelineItem, isDmRoom: Boolean): CharSequence? { val isOutgoing = event.isOwn - // Note: we do not use disambiguated display name here, see - // https://github.com/element-hq/element-x-ios/issues/1845#issuecomment-1888707428 - val senderDisplayName = (event.senderProfile as? ProfileTimelineDetails.Ready)?.displayName ?: event.sender.value + val senderDisambiguatedDisplayName = event.senderProfile.getDisambiguatedDisplayName(event.sender) return when (val content = event.content) { - is MessageContent -> processMessageContents(content, senderDisplayName, isDmRoom) + is MessageContent -> processMessageContents(content, senderDisambiguatedDisplayName, isDmRoom) RedactedContent -> { val message = sp.getString(CommonStrings.common_message_removed) if (!isDmRoom) { - prefix(message, senderDisplayName) + prefix(message, senderDisambiguatedDisplayName) } else { message } @@ -91,36 +89,40 @@ class DefaultRoomLastMessageFormatter @Inject constructor( is UnableToDecryptContent -> { val message = sp.getString(CommonStrings.common_waiting_for_decryption_key) if (!isDmRoom) { - prefix(message, senderDisplayName) + prefix(message, senderDisambiguatedDisplayName) } else { message } } is RoomMembershipContent -> { - roomMembershipContentFormatter.format(content, senderDisplayName, isOutgoing) + roomMembershipContentFormatter.format(content, senderDisambiguatedDisplayName, isOutgoing) } is ProfileChangeContent -> { - profileChangeContentFormatter.format(content, event.sender, senderDisplayName, isOutgoing) + profileChangeContentFormatter.format(content, event.sender, senderDisambiguatedDisplayName, isOutgoing) } is StateContent -> { - stateContentFormatter.format(content, senderDisplayName, isOutgoing, RenderingMode.RoomList) + stateContentFormatter.format(content, senderDisambiguatedDisplayName, isOutgoing, RenderingMode.RoomList) } is PollContent -> { val message = sp.getString(CommonStrings.common_poll_summary, content.question) - prefixIfNeeded(message, senderDisplayName, isDmRoom) + prefixIfNeeded(message, senderDisambiguatedDisplayName, isDmRoom) } is FailedToParseMessageLikeContent, is FailedToParseStateContent, is UnknownContent -> { - prefixIfNeeded(sp.getString(CommonStrings.common_unsupported_event), senderDisplayName, isDmRoom) + prefixIfNeeded(sp.getString(CommonStrings.common_unsupported_event), senderDisambiguatedDisplayName, isDmRoom) } is LegacyCallInviteContent -> sp.getString(CommonStrings.common_call_invite) }?.take(MAX_SAFE_LENGTH) } - private fun processMessageContents(messageContent: MessageContent, senderDisplayName: String, isDmRoom: Boolean): CharSequence? { + private fun processMessageContents( + messageContent: MessageContent, + senderDisambiguatedDisplayName: String, + isDmRoom: Boolean, + ): CharSequence { val internalMessage = when (val messageType: MessageType = messageContent.type) { // Doesn't need a prefix is EmoteMessageType -> { - return "* $senderDisplayName ${messageType.body}" + return "* $senderDisambiguatedDisplayName ${messageType.body}" } is TextMessageType -> { messageType.toPlainText(permalinkParser) @@ -153,19 +155,23 @@ class DefaultRoomLastMessageFormatter @Inject constructor( messageType.body } } - return prefixIfNeeded(internalMessage, senderDisplayName, isDmRoom) + return prefixIfNeeded(internalMessage, senderDisambiguatedDisplayName, isDmRoom) } - private fun prefixIfNeeded(message: String, senderDisplayName: String, isDmRoom: Boolean): CharSequence = if (isDmRoom) { + private fun prefixIfNeeded( + message: String, + senderDisambiguatedDisplayName: String, + isDmRoom: Boolean, + ): CharSequence = if (isDmRoom) { message } else { - prefix(message, senderDisplayName) + prefix(message, senderDisambiguatedDisplayName) } - private fun prefix(message: String, senderDisplayName: String): AnnotatedString { + private fun prefix(message: String, senderDisambiguatedDisplayName: String): AnnotatedString { return buildAnnotatedString { withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { - append(senderDisplayName) + append(senderDisambiguatedDisplayName) } append(": ") append(message) diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt index a4f0d25af0..e77659a99e 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt @@ -49,16 +49,16 @@ class DefaultTimelineEventFormatter @Inject constructor( ) : TimelineEventFormatter { override fun format(event: EventTimelineItem): CharSequence? { val isOutgoing = event.isOwn - val senderDisplayName = event.senderProfile.getDisambiguatedDisplayName(event.sender) + val senderDisambiguatedDisplayName = event.senderProfile.getDisambiguatedDisplayName(event.sender) return when (val content = event.content) { is RoomMembershipContent -> { - roomMembershipContentFormatter.format(content, senderDisplayName, isOutgoing) + roomMembershipContentFormatter.format(content, senderDisambiguatedDisplayName, isOutgoing) } is ProfileChangeContent -> { - profileChangeContentFormatter.format(content, event.sender, senderDisplayName, isOutgoing) + profileChangeContentFormatter.format(content, event.sender, senderDisambiguatedDisplayName, isOutgoing) } is StateContent -> { - stateContentFormatter.format(content, senderDisplayName, isOutgoing, RenderingMode.Timeline) + stateContentFormatter.format(content, senderDisambiguatedDisplayName, isOutgoing, RenderingMode.Timeline) } is LegacyCallInviteContent -> { sp.getString(CommonStrings.common_call_invite) diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/ProfileChangeContentFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/ProfileChangeContentFormatter.kt index 1b27c4ec15..c5d8a5fab6 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/ProfileChangeContentFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/ProfileChangeContentFormatter.kt @@ -27,14 +27,19 @@ class ProfileChangeContentFormatter @Inject constructor( fun format( profileChangeContent: ProfileChangeContent, senderId: UserId, - senderDisplayName: String, + senderDisambiguatedDisplayName: String, senderIsYou: Boolean, ): String? = profileChangeContent.run { val displayNameChanged = displayName != prevDisplayName val avatarChanged = avatarUrl != prevAvatarUrl return when { avatarChanged && displayNameChanged -> { - val message = format(profileChangeContent.copy(avatarUrl = null, prevAvatarUrl = null), senderId, senderDisplayName, senderIsYou) + val message = format( + profileChangeContent = profileChangeContent.copy(avatarUrl = null, prevAvatarUrl = null), + senderId = senderId, + senderDisambiguatedDisplayName = senderDisambiguatedDisplayName, + senderIsYou = senderIsYou, + ) val avatarChangedToo = sp.getString(R.string.state_event_avatar_changed_too) "$message\n$avatarChangedToo" } @@ -63,7 +68,7 @@ class ProfileChangeContentFormatter @Inject constructor( if (senderIsYou) { sp.getString(R.string.state_event_avatar_url_changed_by_you) } else { - sp.getString(R.string.state_event_avatar_url_changed, senderDisplayName) + sp.getString(R.string.state_event_avatar_url_changed, senderDisambiguatedDisplayName) } } else -> null diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/RoomMembershipContentFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/RoomMembershipContentFormatter.kt index 926648458f..b4469fe886 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/RoomMembershipContentFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/RoomMembershipContentFormatter.kt @@ -29,7 +29,7 @@ class RoomMembershipContentFormatter @Inject constructor( ) { fun format( membershipContent: RoomMembershipContent, - senderDisplayName: String, + senderDisambiguatedDisplayName: String, senderIsYou: Boolean, ): CharSequence? { val userId = membershipContent.userId @@ -48,24 +48,24 @@ class RoomMembershipContentFormatter @Inject constructor( MembershipChange.BANNED, MembershipChange.KICKED_AND_BANNED -> if (senderIsYou) { sp.getString(R.string.state_event_room_ban_by_you, userId.value) } else { - sp.getString(R.string.state_event_room_ban, senderDisplayName, userId.value) + sp.getString(R.string.state_event_room_ban, senderDisambiguatedDisplayName, userId.value) } MembershipChange.UNBANNED -> if (senderIsYou) { sp.getString(R.string.state_event_room_unban_by_you, userId.value) } else { - sp.getString(R.string.state_event_room_unban, senderDisplayName, userId.value) + sp.getString(R.string.state_event_room_unban, senderDisambiguatedDisplayName, userId.value) } MembershipChange.KICKED -> if (senderIsYou) { sp.getString(R.string.state_event_room_remove_by_you, userId.value) } else { - sp.getString(R.string.state_event_room_remove, senderDisplayName, userId.value) + sp.getString(R.string.state_event_room_remove, senderDisambiguatedDisplayName, userId.value) } MembershipChange.INVITED -> if (senderIsYou) { sp.getString(R.string.state_event_room_invite_by_you, userId.value) } else if (memberIsYou) { - sp.getString(R.string.state_event_room_invite_you, senderDisplayName) + sp.getString(R.string.state_event_room_invite_you, senderDisambiguatedDisplayName) } else { - sp.getString(R.string.state_event_room_invite, senderDisplayName, userId.value) + sp.getString(R.string.state_event_room_invite, senderDisambiguatedDisplayName, userId.value) } MembershipChange.INVITATION_ACCEPTED -> if (memberIsYou) { sp.getString(R.string.state_event_room_invite_accepted_by_you) @@ -80,7 +80,7 @@ class RoomMembershipContentFormatter @Inject constructor( MembershipChange.INVITATION_REVOKED -> if (senderIsYou) { sp.getString(R.string.state_event_room_third_party_revoked_invite_by_you, userId.value) } else { - sp.getString(R.string.state_event_room_third_party_revoked_invite, senderDisplayName, userId.value) + sp.getString(R.string.state_event_room_third_party_revoked_invite, senderDisambiguatedDisplayName, userId.value) } MembershipChange.KNOCKED -> if (memberIsYou) { sp.getString(R.string.state_event_room_knock_by_you) @@ -90,7 +90,7 @@ class RoomMembershipContentFormatter @Inject constructor( MembershipChange.KNOCK_ACCEPTED -> if (senderIsYou) { sp.getString(R.string.state_event_room_knock_accepted_by_you, userId.value) } else { - sp.getString(R.string.state_event_room_knock_accepted, senderDisplayName, userId.value) + sp.getString(R.string.state_event_room_knock_accepted, senderDisambiguatedDisplayName, userId.value) } MembershipChange.KNOCK_RETRACTED -> if (memberIsYou) { sp.getString(R.string.state_event_room_knock_retracted_by_you) @@ -100,14 +100,14 @@ class RoomMembershipContentFormatter @Inject constructor( MembershipChange.KNOCK_DENIED -> if (senderIsYou) { sp.getString(R.string.state_event_room_knock_denied_by_you, userId.value) } else if (memberIsYou) { - sp.getString(R.string.state_event_room_knock_denied_you, senderDisplayName) + sp.getString(R.string.state_event_room_knock_denied_you, senderDisambiguatedDisplayName) } else { - sp.getString(R.string.state_event_room_knock_denied, senderDisplayName, userId.value) + sp.getString(R.string.state_event_room_knock_denied, senderDisambiguatedDisplayName, userId.value) } MembershipChange.NONE -> if (senderIsYou) { sp.getString(R.string.state_event_room_none_by_you) } else { - sp.getString(R.string.state_event_room_none, senderDisplayName) + sp.getString(R.string.state_event_room_none, senderDisambiguatedDisplayName) } MembershipChange.ERROR -> { Timber.v("Filtering timeline item for room membership: $membershipContent") diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/StateContentFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/StateContentFormatter.kt index 2e86648f7b..ef15216a66 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/StateContentFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/StateContentFormatter.kt @@ -29,7 +29,7 @@ class StateContentFormatter @Inject constructor( ) { fun format( stateContent: StateContent, - senderDisplayName: String, + senderDisambiguatedDisplayName: String, senderIsYou: Boolean, renderingMode: RenderingMode, ): CharSequence? { @@ -39,15 +39,15 @@ class StateContentFormatter @Inject constructor( when { senderIsYou && hasAvatarUrl -> sp.getString(R.string.state_event_room_avatar_changed_by_you) senderIsYou && !hasAvatarUrl -> sp.getString(R.string.state_event_room_avatar_removed_by_you) - !senderIsYou && hasAvatarUrl -> sp.getString(R.string.state_event_room_avatar_changed, senderDisplayName) - else -> sp.getString(R.string.state_event_room_avatar_removed, senderDisplayName) + !senderIsYou && hasAvatarUrl -> sp.getString(R.string.state_event_room_avatar_changed, senderDisambiguatedDisplayName) + else -> sp.getString(R.string.state_event_room_avatar_removed, senderDisambiguatedDisplayName) } } is OtherState.RoomCreate -> { if (senderIsYou) { sp.getString(R.string.state_event_room_created_by_you) } else { - sp.getString(R.string.state_event_room_created, senderDisplayName) + sp.getString(R.string.state_event_room_created, senderDisambiguatedDisplayName) } } is OtherState.RoomEncryption -> sp.getString(CommonStrings.common_encryption_enabled) @@ -56,8 +56,8 @@ class StateContentFormatter @Inject constructor( when { senderIsYou && hasRoomName -> sp.getString(R.string.state_event_room_name_changed_by_you, content.name) senderIsYou && !hasRoomName -> sp.getString(R.string.state_event_room_name_removed_by_you) - !senderIsYou && hasRoomName -> sp.getString(R.string.state_event_room_name_changed, senderDisplayName, content.name) - else -> sp.getString(R.string.state_event_room_name_removed, senderDisplayName) + !senderIsYou && hasRoomName -> sp.getString(R.string.state_event_room_name_changed, senderDisambiguatedDisplayName, content.name) + else -> sp.getString(R.string.state_event_room_name_removed, senderDisambiguatedDisplayName) } } is OtherState.RoomThirdPartyInvite -> { @@ -68,7 +68,7 @@ class StateContentFormatter @Inject constructor( if (senderIsYou) { sp.getString(R.string.state_event_room_third_party_invite_by_you, content.displayName) } else { - sp.getString(R.string.state_event_room_third_party_invite, senderDisplayName, content.displayName) + sp.getString(R.string.state_event_room_third_party_invite, senderDisambiguatedDisplayName, content.displayName) } } is OtherState.RoomTopic -> { @@ -76,8 +76,8 @@ class StateContentFormatter @Inject constructor( when { senderIsYou && hasRoomTopic -> sp.getString(R.string.state_event_room_topic_changed_by_you, content.topic) senderIsYou && !hasRoomTopic -> sp.getString(R.string.state_event_room_topic_removed_by_you) - !senderIsYou && hasRoomTopic -> sp.getString(R.string.state_event_room_topic_changed, senderDisplayName, content.topic) - else -> sp.getString(R.string.state_event_room_topic_removed, senderDisplayName) + !senderIsYou && hasRoomTopic -> sp.getString(R.string.state_event_room_topic_changed, senderDisambiguatedDisplayName, content.topic) + else -> sp.getString(R.string.state_event_room_topic_removed, senderDisambiguatedDisplayName) } } is OtherState.Custom -> when (renderingMode) { diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt index 39c8709a99..9df9698eec 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt @@ -27,7 +27,7 @@ data class NotificationData( val roomId: RoomId, // mxc url val senderAvatarUrl: String?, - // private, must use `getSenderName` + // private, must use `getDisambiguatedDisplayName` private val senderDisplayName: String?, private val senderIsNameAmbiguous: Boolean, val roomAvatarUrl: String?, @@ -39,7 +39,7 @@ data class NotificationData( val content: NotificationContent, val hasMention: Boolean, ) { - fun getSenderName(userId: UserId): String = when { + fun getDisambiguatedDisplayName(userId: UserId): String = when { senderDisplayName.isNullOrBlank() -> userId.value senderIsNameAmbiguous -> "$senderDisplayName ($userId)" else -> senderDisplayName @@ -52,6 +52,7 @@ sealed interface NotificationContent { data class CallInvite( val senderId: UserId, ) : MessageLike + data object CallHangup : MessageLike data object CallCandidates : MessageLike data object KeyVerificationReady : MessageLike diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/InReplyTo.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/InReplyTo.kt index 6965ed9f1e..1337491ff8 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/InReplyTo.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/InReplyTo.kt @@ -33,8 +33,7 @@ sealed interface InReplyTo { val eventId: EventId, val content: EventContent, val senderId: UserId, - val senderDisplayName: String?, - val senderAvatarUrl: String?, + val senderProfile: ProfileTimelineDetails, ) : InReplyTo /** diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt index d30125dbb7..d3a9294cc2 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt @@ -35,7 +35,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageT import io.element.android.libraries.matrix.impl.media.map import org.matrix.rustcomponents.sdk.Message import org.matrix.rustcomponents.sdk.MessageType -import org.matrix.rustcomponents.sdk.ProfileDetails import org.matrix.rustcomponents.sdk.RepliedToEventDetails import org.matrix.rustcomponents.sdk.use import org.matrix.rustcomponents.sdk.FormattedBody as RustFormattedBody @@ -51,13 +50,11 @@ class EventMessageMapper { val inReplyToId = EventId(details.eventId) when (val event = details.event) { is RepliedToEventDetails.Ready -> { - val senderProfile = event.senderProfile as? ProfileDetails.Ready InReplyTo.Ready( eventId = inReplyToId, content = timelineEventContentMapper.map(event.content), senderId = UserId(event.sender), - senderDisplayName = senderProfile?.displayName, - senderAvatarUrl = senderProfile?.avatarUrl, + senderProfile = event.senderProfile.map(), ) } is RepliedToEventDetails.Error -> InReplyTo.Error diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt index 8a285e3b32..edc773b89b 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt @@ -93,8 +93,8 @@ class NotifiableEventResolver @Inject constructor( ): NotifiableEvent? { return when (val content = this.content) { is NotificationContent.MessageLike.RoomMessage -> { - val senderName = getSenderName(content.senderId) - val messageBody = descriptionFromMessageContent(content, senderName) + val senderDisambiguatedDisplayName = getDisambiguatedDisplayName(content.senderId) + val messageBody = descriptionFromMessageContent(content, senderDisambiguatedDisplayName) val notificationBody = if (hasMention) { stringProvider.getString(R.string.notification_mentioned_you_body, messageBody) } else { @@ -107,7 +107,7 @@ class NotifiableEventResolver @Inject constructor( eventId = eventId, noisy = isNoisy, timestamp = this.timestamp, - senderName = senderName, + senderDisambiguatedDisplayName = senderDisambiguatedDisplayName, body = notificationBody, imageUriString = fetchImageIfPresent(client)?.toString(), roomName = roomDisplayName, @@ -154,7 +154,7 @@ class NotifiableEventResolver @Inject constructor( eventId = eventId, noisy = isNoisy, timestamp = this.timestamp, - senderName = null, + senderDisambiguatedDisplayName = getDisambiguatedDisplayName(content.senderId), body = stringProvider.getString(CommonStrings.common_call_invite), imageUriString = fetchImageIfPresent(client)?.toString(), roomName = roomDisplayName, @@ -180,7 +180,7 @@ class NotifiableEventResolver @Inject constructor( eventId = eventId, noisy = isNoisy, timestamp = this.timestamp, - senderName = getSenderName(content.senderId), + senderDisambiguatedDisplayName = getDisambiguatedDisplayName(content.senderId), body = stringProvider.getString(CommonStrings.common_poll_summary, content.question), imageUriString = null, roomName = roomDisplayName, @@ -244,12 +244,12 @@ class NotifiableEventResolver @Inject constructor( private fun descriptionFromMessageContent( content: NotificationContent.MessageLike.RoomMessage, - senderDisplayName: String, + senderDisambiguatedDisplayName: String, ): String { return when (val messageType = content.messageType) { is AudioMessageType -> messageType.body is VoiceMessageType -> stringProvider.getString(CommonStrings.common_voice_message) - is EmoteMessageType -> "* $senderDisplayName ${messageType.body}" + is EmoteMessageType -> "* $senderDisambiguatedDisplayName ${messageType.body}" is FileMessageType -> messageType.body is ImageMessageType -> messageType.body is StickerMessageType -> messageType.body @@ -310,7 +310,7 @@ private fun buildNotifiableMessageEvent( canBeReplaced: Boolean = false, noisy: Boolean, timestamp: Long, - senderName: String?, + senderDisambiguatedDisplayName: String?, body: String?, // We cannot use Uri? type here, as that could trigger a // NotSerializableException when persisting this to storage @@ -335,7 +335,7 @@ private fun buildNotifiableMessageEvent( canBeReplaced = canBeReplaced, noisy = noisy, timestamp = timestamp, - senderName = senderName, + senderDisambiguatedDisplayName = senderDisambiguatedDisplayName, body = body, imageUriString = imageUriString, threadId = threadId, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt index 27c8875a0c..65930f7652 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt @@ -45,7 +45,7 @@ class RoomGroupMessageCreator @Inject constructor( imageLoader: ImageLoader, ): RoomNotification.Message { val lastKnownRoomEvent = events.last() - val roomName = lastKnownRoomEvent.roomName ?: lastKnownRoomEvent.senderName ?: "Room name (${roomId.value.take(8)}…)" + val roomName = lastKnownRoomEvent.roomName ?: lastKnownRoomEvent.senderDisambiguatedDisplayName ?: "Room name (${roomId.value.take(8)}…)" val roomIsGroup = !lastKnownRoomEvent.roomIsDirect val style = NotificationCompat.MessagingStyle( Person.Builder() @@ -60,9 +60,9 @@ class RoomGroupMessageCreator @Inject constructor( } val tickerText = if (roomIsGroup) { - stringProvider.getString(R.string.notification_ticker_text_group, roomName, events.last().senderName, events.last().description) + stringProvider.getString(R.string.notification_ticker_text_group, roomName, events.last().senderDisambiguatedDisplayName, events.last().description) } else { - stringProvider.getString(R.string.notification_ticker_text_dm, events.last().senderName, events.last().description) + stringProvider.getString(R.string.notification_ticker_text_dm, events.last().senderDisambiguatedDisplayName, events.last().description) } val largeBitmap = getRoomBitmap(events, imageLoader) @@ -108,7 +108,7 @@ class RoomGroupMessageCreator @Inject constructor( null } else { Person.Builder() - .setName(event.senderName?.annotateForDebug(70)) + .setName(event.senderDisambiguatedDisplayName?.annotateForDebug(70)) .setIcon(bitmapLoader.getUserIcon(event.senderAvatarPath, imageLoader)) .setKey(event.senderId.value) .build() @@ -152,7 +152,7 @@ class RoomGroupMessageCreator @Inject constructor( private fun createFirstMessageSummaryLine(event: NotifiableMessageEvent, roomName: String, roomIsDirect: Boolean): CharSequence { return if (roomIsDirect) { buildSpannedString { - event.senderName?.let { + event.senderDisambiguatedDisplayName?.let { inSpans(StyleSpan(Typeface.BOLD)) { append(it) append(": ") @@ -165,7 +165,7 @@ class RoomGroupMessageCreator @Inject constructor( inSpans(StyleSpan(Typeface.BOLD)) { append(roomName) append(": ") - event.senderName?.let { + event.senderDisambiguatedDisplayName?.let { append(it) append(" ") } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableMessageEvent.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableMessageEvent.kt index cd972e31b1..015e24d5e6 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableMessageEvent.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableMessageEvent.kt @@ -36,7 +36,7 @@ data class NotifiableMessageEvent( val senderId: UserId, val noisy: Boolean, val timestamp: Long, - val senderName: String?, + val senderDisambiguatedDisplayName: String?, val body: String?, // We cannot use Uri? type here, as that could trigger a // NotSerializableException when persisting this to storage @@ -55,7 +55,6 @@ data class NotifiableMessageEvent( ) : NotifiableEvent { val type: String = EventType.MESSAGE override val description: String = body ?: "" - val title: String = senderName ?: "" // Example of value: // content://io.element.android.x.debug.notifications.fileprovider/downloads/temp/notif/matrix.org/XGItzSDOnSyXjYtOPfiKexDJ diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolverTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolverTest.kt index b2f954091e..6bac49983b 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolverTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolverTest.kt @@ -462,7 +462,7 @@ class NotifiableEventResolverTest { senderId = A_USER_ID_2, noisy = false, timestamp = A_TIMESTAMP, - senderName = null, + senderDisambiguatedDisplayName = null, body = "Call in progress (unsupported)", imageUriString = null, threadId = null, @@ -586,7 +586,7 @@ class NotifiableEventResolverTest { senderId = A_USER_ID_2, noisy = false, timestamp = A_TIMESTAMP, - senderName = "Bob", + senderDisambiguatedDisplayName = "Bob", body = body, imageUriString = null, threadId = null, diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotifiableEventFixture.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotifiableEventFixture.kt index 44a2873465..096ae254b8 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotifiableEventFixture.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotifiableEventFixture.kt @@ -85,7 +85,7 @@ fun aNotifiableMessageEvent( editedEventId = null, noisy = false, timestamp = timestamp, - senderName = "sender-name", + senderDisambiguatedDisplayName = "sender-name", senderId = UserId("@sending-id:domain.com"), body = "message-body", roomId = roomId,