From db6bc7c04fde6ad1701f0150080e3b33f31bffed Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 30 Apr 2025 18:13:53 +0200 Subject: [PATCH] Render kick and ban reason in the timeline when available (#4642) * Map the reason to RoomMembershipContent * Create function to create RoomMembershipContent. * Render reason for kick and ban state event. --- .../impl/RoomMembershipContentFormatter.kt | 25 ++++- ...DefaultBaseRoomLastMessageFormatterTest.kt | 105 +++++++++++++----- ...efaultPinnedMessagesBannerFormatterTest.kt | 60 +++++----- .../api/timeline/item/event/EventContent.kt | 3 +- .../item/event/TimelineEventContentMapper.kt | 3 +- .../impl/timeline/postprocessor/Fixtures.kt | 6 +- .../test/timeline/item/event/Fixture.kt | 25 +++++ .../ui/messages/reply/InReplyToDetailTest.kt | 5 +- .../messages/reply/InReplyToMetadataKtTest.kt | 4 +- .../datasource/DefaultEventItemFactoryTest.kt | 6 +- 10 files changed, 164 insertions(+), 78 deletions(-) create mode 100644 libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/item/event/Fixture.kt 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 4e920ea265..5d6e695b7c 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 @@ -26,6 +26,7 @@ class RoomMembershipContentFormatter @Inject constructor( val userId = membershipContent.userId val memberIsYou = matrixClient.isMe(userId) val userDisplayNameOrId = membershipContent.userDisplayName ?: userId.value + val reason = membershipContent.reason?.takeIf { it.isNotBlank() } return when (membershipContent.change) { MembershipChange.JOINED -> if (memberIsYou) { sp.getString(R.string.state_event_room_join_by_you) @@ -38,9 +39,17 @@ class RoomMembershipContentFormatter @Inject constructor( sp.getString(R.string.state_event_room_leave, senderDisambiguatedDisplayName) } MembershipChange.BANNED, MembershipChange.KICKED_AND_BANNED -> if (senderIsYou) { - sp.getString(R.string.state_event_room_ban_by_you, userDisplayNameOrId) + if (reason != null) { + sp.getString(R.string.state_event_room_ban_by_you_with_reason, userDisplayNameOrId, reason) + } else { + sp.getString(R.string.state_event_room_ban_by_you, userDisplayNameOrId) + } } else { - sp.getString(R.string.state_event_room_ban, senderDisambiguatedDisplayName, userDisplayNameOrId) + if (reason != null) { + sp.getString(R.string.state_event_room_ban_with_reason, senderDisambiguatedDisplayName, userDisplayNameOrId, reason) + } else { + sp.getString(R.string.state_event_room_ban, senderDisambiguatedDisplayName, userDisplayNameOrId) + } } MembershipChange.UNBANNED -> if (senderIsYou) { sp.getString(R.string.state_event_room_unban_by_you, userDisplayNameOrId) @@ -48,9 +57,17 @@ class RoomMembershipContentFormatter @Inject constructor( sp.getString(R.string.state_event_room_unban, senderDisambiguatedDisplayName, userDisplayNameOrId) } MembershipChange.KICKED -> if (senderIsYou) { - sp.getString(R.string.state_event_room_remove_by_you, userDisplayNameOrId) + if (reason != null) { + sp.getString(R.string.state_event_room_remove_by_you_with_reason, userDisplayNameOrId, reason) + } else { + sp.getString(R.string.state_event_room_remove_by_you, userDisplayNameOrId) + } } else { - sp.getString(R.string.state_event_room_remove, senderDisambiguatedDisplayName, userDisplayNameOrId) + if (reason != null) { + sp.getString(R.string.state_event_room_remove_with_reason, senderDisambiguatedDisplayName, userDisplayNameOrId, reason) + } else { + sp.getString(R.string.state_event_room_remove, senderDisambiguatedDisplayName, userDisplayNameOrId) + } } MembershipChange.INVITED -> if (senderIsYou) { sp.getString(R.string.state_event_room_invite_by_you, userDisplayNameOrId) diff --git a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultBaseRoomLastMessageFormatterTest.kt b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultBaseRoomLastMessageFormatterTest.kt index 04693562cb..6d0ad1aa49 100644 --- a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultBaseRoomLastMessageFormatterTest.kt +++ b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultBaseRoomLastMessageFormatterTest.kt @@ -30,7 +30,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.OtherState 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 import io.element.android.libraries.matrix.api.timeline.item.event.StickerMessageType import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType @@ -38,6 +37,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.test.A_REASON import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.media.aMediaSource @@ -47,6 +47,7 @@ import io.element.android.libraries.matrix.test.timeline.aProfileChangeMessageCo import io.element.android.libraries.matrix.test.timeline.aProfileTimelineDetails import io.element.android.libraries.matrix.test.timeline.aStickerContent import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem +import io.element.android.libraries.matrix.test.timeline.item.event.aRoomMembershipContent import io.element.android.services.toolbox.impl.strings.AndroidStringProvider import org.junit.Before import org.junit.Test @@ -289,8 +290,8 @@ class DefaultBaseRoomLastMessageFormatterTest { @Config(qualifiers = "en") fun `Membership change - joined`() { val otherName = "Other" - val youContent = RoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED) - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.JOINED) + val youContent = aRoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.JOINED) val youJoinedRoomEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youJoinedRoom = formatter.format(youJoinedRoomEvent, false) @@ -305,8 +306,8 @@ class DefaultBaseRoomLastMessageFormatterTest { @Config(qualifiers = "en") fun `Membership change - left`() { val otherName = "Other" - val youContent = RoomMembershipContent(A_USER_ID, null, MembershipChange.LEFT) - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.LEFT) + val youContent = aRoomMembershipContent(A_USER_ID, null, MembershipChange.LEFT) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.LEFT) val youLeftRoomEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youLeftRoom = formatter.format(youLeftRoomEvent, false) @@ -322,10 +323,10 @@ class DefaultBaseRoomLastMessageFormatterTest { fun `Membership change - banned`() { val otherName = "Other" val third = "Someone" - val youContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.BANNED) - val youKickedContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED_AND_BANNED) - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.BANNED) - val someoneKickedContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED_AND_BANNED) + val youContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.BANNED) + val youKickedContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED_AND_BANNED) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.BANNED) + val someoneKickedContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED_AND_BANNED) val youBannedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youBanned = formatter.format(youBannedEvent, false) @@ -344,13 +345,40 @@ class DefaultBaseRoomLastMessageFormatterTest { assertThat(someoneKickBanned).isEqualTo("$otherName banned $third") } + @Test + @Config(qualifiers = "en") + fun `Membership change - banned with reason`() { + val otherName = "Other" + val third = "Someone" + val youContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.BANNED, A_REASON) + val youKickedContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED_AND_BANNED, A_REASON) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.BANNED, A_REASON) + val someoneKickedContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED_AND_BANNED, A_REASON) + + val youBannedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) + val youBanned = formatter.format(youBannedEvent, false) + assertThat(youBanned).isEqualTo("You banned $third: $A_REASON") + + val youKickBannedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youKickedContent) + val youKickedBanned = formatter.format(youKickBannedEvent, false) + assertThat(youKickedBanned).isEqualTo("You banned $third: $A_REASON") + + val someoneBannedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) + val someoneBanned = formatter.format(someoneBannedEvent, false) + assertThat(someoneBanned).isEqualTo("$otherName banned $third: $A_REASON") + + val someoneKickBannedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneKickedContent) + val someoneKickBanned = formatter.format(someoneKickBannedEvent, false) + assertThat(someoneKickBanned).isEqualTo("$otherName banned $third: $A_REASON") + } + @Test @Config(qualifiers = "en") fun `Membership change - unban`() { val otherName = "Other" val third = "Someone" - val youContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.UNBANNED) - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.UNBANNED) + val youContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.UNBANNED) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.UNBANNED) val youUnbannedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youUnbanned = formatter.format(youUnbannedEvent, false) @@ -366,8 +394,8 @@ class DefaultBaseRoomLastMessageFormatterTest { fun `Membership change - kicked`() { val otherName = "Other" val third = "Someone" - val youContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED) - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED) + val youContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED) val youKickedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youKicked = formatter.format(youKickedEvent, false) @@ -378,13 +406,30 @@ class DefaultBaseRoomLastMessageFormatterTest { assertThat(someoneKicked).isEqualTo("$otherName removed $third") } + @Test + @Config(qualifiers = "en") + fun `Membership change - kicked with reason`() { + val otherName = "Other" + val third = "Someone" + val youContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED, A_REASON) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED, A_REASON) + + val youKickedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) + val youKicked = formatter.format(youKickedEvent, false) + assertThat(youKicked).isEqualTo("You removed $third: $A_REASON") + + val someoneKickedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) + val someoneKicked = formatter.format(someoneKickedEvent, false) + assertThat(someoneKicked).isEqualTo("$otherName removed $third: $A_REASON") + } + @Test @Config(qualifiers = "en") fun `Membership change - invited`() { val otherName = "Other" val third = "Someone" - val youContent = RoomMembershipContent(A_USER_ID, null, MembershipChange.INVITED) - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.INVITED) + val youContent = aRoomMembershipContent(A_USER_ID, null, MembershipChange.INVITED) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.INVITED) val youWereInvitedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = youContent) val youWereInvited = formatter.format(youWereInvitedEvent, false) @@ -403,8 +448,8 @@ class DefaultBaseRoomLastMessageFormatterTest { @Config(qualifiers = "en") fun `Membership change - invitation accepted`() { val otherName = "Other" - val youContent = RoomMembershipContent(A_USER_ID, null, MembershipChange.INVITATION_ACCEPTED) - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.INVITATION_ACCEPTED) + val youContent = aRoomMembershipContent(A_USER_ID, null, MembershipChange.INVITATION_ACCEPTED) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.INVITATION_ACCEPTED) val youAcceptedInviteEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youAcceptedInvite = formatter.format(youAcceptedInviteEvent, false) @@ -419,8 +464,8 @@ class DefaultBaseRoomLastMessageFormatterTest { @Config(qualifiers = "en") fun `Membership change - invitation rejected`() { val otherName = "Other" - val youContent = RoomMembershipContent(A_USER_ID, null, MembershipChange.INVITATION_REJECTED) - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.INVITATION_REJECTED) + val youContent = aRoomMembershipContent(A_USER_ID, null, MembershipChange.INVITATION_REJECTED) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.INVITATION_REJECTED) val youRejectedInviteEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youRejectedInvite = formatter.format(youRejectedInviteEvent, false) @@ -436,7 +481,7 @@ class DefaultBaseRoomLastMessageFormatterTest { fun `Membership change - invitation revoked`() { val otherName = "Other" val third = "Someone" - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.INVITATION_REVOKED) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.INVITATION_REVOKED) val youRevokedInviteEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = someoneContent) val youRevokedInvite = formatter.format(youRevokedInviteEvent, false) @@ -451,8 +496,8 @@ class DefaultBaseRoomLastMessageFormatterTest { @Config(qualifiers = "en") fun `Membership change - knocked`() { val otherName = "Other" - val youContent = RoomMembershipContent(A_USER_ID, null, MembershipChange.KNOCKED) - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.KNOCKED) + val youContent = aRoomMembershipContent(A_USER_ID, null, MembershipChange.KNOCKED) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.KNOCKED) val youKnockedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youKnocked = formatter.format(youKnockedEvent, false) @@ -468,7 +513,7 @@ class DefaultBaseRoomLastMessageFormatterTest { fun `Membership change - knock accepted`() { val otherName = "Other" val third = "Someone" - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KNOCK_ACCEPTED) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KNOCK_ACCEPTED) val youAcceptedKnockEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = someoneContent) val youAcceptedKnock = formatter.format(youAcceptedKnockEvent, false) @@ -483,8 +528,8 @@ class DefaultBaseRoomLastMessageFormatterTest { @Config(qualifiers = "en") fun `Membership change - knock retracted`() { val otherName = "Other" - val youContent = RoomMembershipContent(A_USER_ID, null, MembershipChange.KNOCK_RETRACTED) - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), null, MembershipChange.KNOCK_RETRACTED) + val youContent = aRoomMembershipContent(A_USER_ID, null, MembershipChange.KNOCK_RETRACTED) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), null, MembershipChange.KNOCK_RETRACTED) val youRetractedKnockEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youRetractedKnock = formatter.format(youRetractedKnockEvent, false) @@ -500,8 +545,8 @@ class DefaultBaseRoomLastMessageFormatterTest { fun `Membership change - knock denied`() { val otherName = "Other" val third = "Someone" - val youContent = RoomMembershipContent(A_USER_ID, third, MembershipChange.KNOCK_DENIED) - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KNOCK_DENIED) + val youContent = aRoomMembershipContent(A_USER_ID, third, MembershipChange.KNOCK_DENIED) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KNOCK_DENIED) val youDeniedKnockEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = someoneContent) val youDeniedKnock = formatter.format(youDeniedKnockEvent, false) @@ -520,8 +565,8 @@ class DefaultBaseRoomLastMessageFormatterTest { @Config(qualifiers = "en") fun `Membership change - None`() { val otherName = "Other" - val youContent = RoomMembershipContent(A_USER_ID, null, MembershipChange.NONE) - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.NONE) + val youContent = aRoomMembershipContent(A_USER_ID, null, MembershipChange.NONE) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.NONE) val youNoneRoomEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youNoneRoom = formatter.format(youNoneRoomEvent, false) @@ -538,7 +583,7 @@ class DefaultBaseRoomLastMessageFormatterTest { val otherChanges = arrayOf(MembershipChange.ERROR, MembershipChange.NOT_IMPLEMENTED, null) val results = otherChanges.map { change -> - val content = RoomMembershipContent(A_USER_ID, null, change) + val content = aRoomMembershipContent(A_USER_ID, null, change) val event = createRoomEvent(sentByYou = false, senderDisplayName = "Someone", content = content) val result = formatter.format(event, false) change to result diff --git a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatterTest.kt b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatterTest.kt index ee4b827098..4ff71608f8 100644 --- a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatterTest.kt +++ b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatterTest.kt @@ -30,7 +30,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.OtherState 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 import io.element.android.libraries.matrix.api.timeline.item.event.StickerMessageType import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType @@ -47,6 +46,7 @@ import io.element.android.libraries.matrix.test.timeline.aProfileChangeMessageCo import io.element.android.libraries.matrix.test.timeline.aProfileTimelineDetails import io.element.android.libraries.matrix.test.timeline.aStickerContent import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem +import io.element.android.libraries.matrix.test.timeline.item.event.aRoomMembershipContent import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.services.toolbox.impl.strings.AndroidStringProvider import org.junit.Before @@ -198,8 +198,8 @@ class DefaultPinnedMessagesBannerFormatterTest { @Config(qualifiers = "en") fun `Membership change - joined`() { val otherName = "Other" - val youContent = RoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED) - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.JOINED) + val youContent = aRoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.JOINED) val youJoinedRoomEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youJoinedRoom = formatter.format(youJoinedRoomEvent) @@ -214,8 +214,8 @@ class DefaultPinnedMessagesBannerFormatterTest { @Config(qualifiers = "en") fun `Membership change - left`() { val otherName = "Other" - val youContent = RoomMembershipContent(A_USER_ID, null, MembershipChange.LEFT) - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.LEFT) + val youContent = aRoomMembershipContent(A_USER_ID, null, MembershipChange.LEFT) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.LEFT) val youLeftRoomEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youLeftRoom = formatter.format(youLeftRoomEvent) @@ -231,10 +231,10 @@ class DefaultPinnedMessagesBannerFormatterTest { fun `Membership change - banned`() { val otherName = "Other" val third = "Someone" - val youContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.BANNED) - val youKickedContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED_AND_BANNED) - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.BANNED) - val someoneKickedContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED_AND_BANNED) + val youContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.BANNED) + val youKickedContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED_AND_BANNED) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.BANNED) + val someoneKickedContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED_AND_BANNED) val youBannedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youBanned = formatter.format(youBannedEvent) @@ -258,8 +258,8 @@ class DefaultPinnedMessagesBannerFormatterTest { fun `Membership change - unban`() { val otherName = "Other" val third = "Someone" - val youContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.UNBANNED) - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.UNBANNED) + val youContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.UNBANNED) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.UNBANNED) val youUnbannedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youUnbanned = formatter.format(youUnbannedEvent) @@ -275,8 +275,8 @@ class DefaultPinnedMessagesBannerFormatterTest { fun `Membership change - kicked`() { val otherName = "Other" val third = "Someone" - val youContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED) - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED) + val youContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED) val youKickedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youKicked = formatter.format(youKickedEvent) @@ -292,8 +292,8 @@ class DefaultPinnedMessagesBannerFormatterTest { fun `Membership change - invited`() { val otherName = "Other" val third = "Someone" - val youContent = RoomMembershipContent(A_USER_ID, null, MembershipChange.INVITED) - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.INVITED) + val youContent = aRoomMembershipContent(A_USER_ID, null, MembershipChange.INVITED) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.INVITED) val youWereInvitedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = youContent) val youWereInvited = formatter.format(youWereInvitedEvent) @@ -312,8 +312,8 @@ class DefaultPinnedMessagesBannerFormatterTest { @Config(qualifiers = "en") fun `Membership change - invitation accepted`() { val otherName = "Other" - val youContent = RoomMembershipContent(A_USER_ID, null, MembershipChange.INVITATION_ACCEPTED) - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.INVITATION_ACCEPTED) + val youContent = aRoomMembershipContent(A_USER_ID, null, MembershipChange.INVITATION_ACCEPTED) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.INVITATION_ACCEPTED) val youAcceptedInviteEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youAcceptedInvite = formatter.format(youAcceptedInviteEvent) @@ -328,8 +328,8 @@ class DefaultPinnedMessagesBannerFormatterTest { @Config(qualifiers = "en") fun `Membership change - invitation rejected`() { val otherName = "Other" - val youContent = RoomMembershipContent(A_USER_ID, null, MembershipChange.INVITATION_REJECTED) - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.INVITATION_REJECTED) + val youContent = aRoomMembershipContent(A_USER_ID, null, MembershipChange.INVITATION_REJECTED) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.INVITATION_REJECTED) val youRejectedInviteEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youRejectedInvite = formatter.format(youRejectedInviteEvent) @@ -345,7 +345,7 @@ class DefaultPinnedMessagesBannerFormatterTest { fun `Membership change - invitation revoked`() { val otherName = "Other" val third = "Someone" - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.INVITATION_REVOKED) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.INVITATION_REVOKED) val youRevokedInviteEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = someoneContent) val youRevokedInvite = formatter.format(youRevokedInviteEvent) @@ -360,8 +360,8 @@ class DefaultPinnedMessagesBannerFormatterTest { @Config(qualifiers = "en") fun `Membership change - knocked`() { val otherName = "Other" - val youContent = RoomMembershipContent(A_USER_ID, null, MembershipChange.KNOCKED) - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.KNOCKED) + val youContent = aRoomMembershipContent(A_USER_ID, null, MembershipChange.KNOCKED) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.KNOCKED) val youKnockedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youKnocked = formatter.format(youKnockedEvent) @@ -377,7 +377,7 @@ class DefaultPinnedMessagesBannerFormatterTest { fun `Membership change - knock accepted`() { val otherName = "Other" val third = "Someone" - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KNOCK_ACCEPTED) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KNOCK_ACCEPTED) val youAcceptedKnockEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = someoneContent) val youAcceptedKnock = formatter.format(youAcceptedKnockEvent) @@ -392,8 +392,8 @@ class DefaultPinnedMessagesBannerFormatterTest { @Config(qualifiers = "en") fun `Membership change - knock retracted`() { val otherName = "Other" - val youContent = RoomMembershipContent(A_USER_ID, null, MembershipChange.KNOCK_RETRACTED) - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), null, MembershipChange.KNOCK_RETRACTED) + val youContent = aRoomMembershipContent(A_USER_ID, null, MembershipChange.KNOCK_RETRACTED) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), null, MembershipChange.KNOCK_RETRACTED) val youRetractedKnockEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youRetractedKnock = formatter.format(youRetractedKnockEvent) @@ -409,8 +409,8 @@ class DefaultPinnedMessagesBannerFormatterTest { fun `Membership change - knock denied`() { val otherName = "Other" val third = "Someone" - val youContent = RoomMembershipContent(A_USER_ID, third, MembershipChange.KNOCK_DENIED) - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KNOCK_DENIED) + val youContent = aRoomMembershipContent(A_USER_ID, third, MembershipChange.KNOCK_DENIED) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KNOCK_DENIED) val youDeniedKnockEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = someoneContent) val youDeniedKnock = formatter.format(youDeniedKnockEvent) @@ -429,8 +429,8 @@ class DefaultPinnedMessagesBannerFormatterTest { @Config(qualifiers = "en") fun `Membership change - None`() { val otherName = "Other" - val youContent = RoomMembershipContent(A_USER_ID, null, MembershipChange.NONE) - val someoneContent = RoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.NONE) + val youContent = aRoomMembershipContent(A_USER_ID, null, MembershipChange.NONE) + val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.NONE) val youNoneRoomEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youNoneRoom = formatter.format(youNoneRoomEvent) @@ -447,7 +447,7 @@ class DefaultPinnedMessagesBannerFormatterTest { val otherChanges = arrayOf(MembershipChange.ERROR, MembershipChange.NOT_IMPLEMENTED, null) val results = otherChanges.map { change -> - val content = RoomMembershipContent(A_USER_ID, null, change) + val content = aRoomMembershipContent(A_USER_ID, null, change) val event = createRoomEvent(sentByYou = false, senderDisplayName = "Someone", content = content) val result = formatter.format(event) change to result diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt index c0f191a85b..bf062b76e0 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt @@ -70,7 +70,8 @@ data class UnableToDecryptContent( data class RoomMembershipContent( val userId: UserId, val userDisplayName: String?, - val change: MembershipChange? + val change: MembershipChange?, + val reason: String?, ) : EventContent data class ProfileChangeContent( diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt index f383df5ccf..a62f18ddd1 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt @@ -105,7 +105,8 @@ class TimelineEventContentMapper( RoomMembershipContent( userId = UserId(it.userId), userDisplayName = it.userDisplayName, - change = it.change?.map() + change = it.change?.map(), + reason = it.reason, ) } is TimelineItemContent.State -> { diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/Fixtures.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/Fixtures.kt index 34cb1ac9e1..63deb34530 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/Fixtures.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/Fixtures.kt @@ -11,13 +11,13 @@ 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.MembershipChange import io.element.android.libraries.matrix.api.timeline.item.event.OtherState -import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent import io.element.android.libraries.matrix.api.timeline.item.event.StateContent import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem 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.timeline.aMessageContent import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem +import io.element.android.libraries.matrix.test.timeline.item.event.aRoomMembershipContent internal val timelineStartEvent = MatrixTimelineItem.Virtual( uniqueId = UniqueId("timeline_start"), @@ -29,11 +29,11 @@ internal val roomCreateEvent = MatrixTimelineItem.Event( ) internal val roomCreatorJoinEvent = MatrixTimelineItem.Event( uniqueId = UniqueId("m.room.member"), - event = anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED)) + event = anEventTimelineItem(content = aRoomMembershipContent(userId = A_USER_ID, change = MembershipChange.JOINED)) ) internal val otherMemberJoinEvent = MatrixTimelineItem.Event( uniqueId = UniqueId("m.room.member_other"), - event = anEventTimelineItem(content = RoomMembershipContent(A_USER_ID_2, null, MembershipChange.JOINED)) + event = anEventTimelineItem(content = aRoomMembershipContent(userId = A_USER_ID_2, change = MembershipChange.JOINED)) ) internal val messageEvent = MatrixTimelineItem.Event( uniqueId = UniqueId("m.room.message"), diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/item/event/Fixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/item/event/Fixture.kt new file mode 100644 index 0000000000..c9e7c304d5 --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/item/event/Fixture.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.test.timeline.item.event + +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange +import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent +import io.element.android.libraries.matrix.test.A_USER_ID + +fun aRoomMembershipContent( + userId: UserId = A_USER_ID, + userDisplayName: String? = null, + change: MembershipChange? = null, + reason: String? = null, +) = RoomMembershipContent( + userId = userId, + userDisplayName = userDisplayName, + change = change, + reason = reason, +) diff --git a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToDetailTest.kt b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToDetailTest.kt index 20e26cd125..6fdff3ad1c 100644 --- a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToDetailTest.kt +++ b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToDetailTest.kt @@ -13,12 +13,12 @@ import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent import io.element.android.libraries.matrix.api.timeline.item.event.MessageFormat -import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType 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 io.element.android.libraries.matrix.test.timeline.item.event.aRoomMembershipContent import org.junit.Test class InReplyToDetailTest { @@ -47,9 +47,8 @@ class InReplyToDetailTest { eventId = AN_EVENT_ID, senderId = A_USER_ID, senderProfile = aProfileTimelineDetails(), - content = RoomMembershipContent( + content = aRoomMembershipContent( userId = A_USER_ID, - userDisplayName = null, change = MembershipChange.INVITED, ) ) diff --git a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadataKtTest.kt b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadataKtTest.kt index 288d0b3d9c..c96e55c6fa 100644 --- a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadataKtTest.kt +++ b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadataKtTest.kt @@ -30,7 +30,6 @@ 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 import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent @@ -43,6 +42,7 @@ 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.test.timeline.item.event.aRoomMembershipContent 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 @@ -496,7 +496,7 @@ class InReplyToMetadataKtTest { fun `room membership content`() = runTest { moleculeFlow(RecompositionMode.Immediate) { anInReplyToDetailsReady( - eventContent = RoomMembershipContent(A_USER_ID, null, null) + eventContent = aRoomMembershipContent(userId = A_USER_ID) ).metadata(hideImage = false) }.test { awaitItem().let { diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/DefaultEventItemFactoryTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/DefaultEventItemFactoryTest.kt index 40e6721c10..16e6057df6 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/DefaultEventItemFactoryTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/DefaultEventItemFactoryTest.kt @@ -31,7 +31,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.OtherState 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 import io.element.android.libraries.matrix.api.timeline.item.event.StickerMessageType import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType @@ -47,6 +46,7 @@ import io.element.android.libraries.matrix.test.timeline.aPollContent import io.element.android.libraries.matrix.test.timeline.aProfileChangeMessageContent import io.element.android.libraries.matrix.test.timeline.aStickerContent import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem +import io.element.android.libraries.matrix.test.timeline.item.event.aRoomMembershipContent import io.element.android.libraries.mediaviewer.api.MediaInfo import io.element.android.libraries.mediaviewer.impl.model.MediaItem import io.element.android.libraries.mediaviewer.test.util.FileExtensionExtractorWithoutValidation @@ -67,10 +67,8 @@ class DefaultEventItemFactoryTest { aPollContent(), aProfileChangeMessageContent(), RedactedContent, - RoomMembershipContent( + aRoomMembershipContent( userId = A_USER_ID, - userDisplayName = null, - change = null, ), StateContent("", OtherState.RoomCreate), aStickerContent(