From 098edab1b44c93c3324fb45c129cc5187e340b80 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Tue, 8 Sep 2026 10:45:50 +0200 Subject: [PATCH] use disambiguated display name for avatar change events --- .../RoomSummary/RoomEventStringBuilder.swift | 3 +- .../RoomStateEventStringBuilder.swift | 37 ++++++++++++++----- .../RoomTimelineItemFactory.swift | 3 +- .../RoomStateEventStringBuilderTests.swift | 11 ++++-- 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomEventStringBuilder.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomEventStringBuilder.swift index e72c100a9..6c542313a 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomEventStringBuilder.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomEventStringBuilder.swift @@ -76,7 +76,8 @@ nonisolated struct RoomEventStringBuilder { previousDisplayName: prevDisplayName, avatarURLString: avatarUrl, previousAvatarURLString: prevAvatarUrl, - member: sender.id, + memberID: sender.id, + memberDisplayName: sender.disambiguatedDisplayName ?? sender.id, memberIsYou: isOutgoing) .map(AttributedString.init) case .callInvite: diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomStateEventStringBuilder.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomStateEventStringBuilder.swift index c2db635e5..63af99884 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomStateEventStringBuilder.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomStateEventStringBuilder.swift @@ -87,26 +87,39 @@ nonisolated struct RoomStateEventStringBuilder { L10n.stateEventRoomInviteYou(senderDisplayName) } - func buildProfileChangeString(displayName: String?, previousDisplayName: String?, - avatarURLString: String?, previousAvatarURLString: String?, - member: String, memberIsYou: Bool) -> String? { + /// - Parameters: + /// - displayName: The new display name, only set by the SDK when the display name changed. + /// - previousDisplayName: The old display name, only set by the SDK when the display name changed. + /// - avatarURLString: The new avatar URL, only set by the SDK when the avatar changed. + /// - previousAvatarURLString: The old avatar URL, only set by the SDK when the avatar changed. + /// - memberID: The member's user ID, used in the display name change strings. + /// - memberDisplayName: The member's disambiguated display name, used when only the avatar changed. + /// - memberIsYou: Whether the member is the current user, which selects the "You" variant of the strings. + func buildProfileChangeString(displayName: String?, + previousDisplayName: String?, + avatarURLString: String?, + previousAvatarURLString: String?, + memberID: String, + memberDisplayName: String, + memberIsYou: Bool) -> String? { let displayNameChanged = displayName != previousDisplayName let avatarChanged = avatarURLString != previousAvatarURLString switch (displayNameChanged, avatarChanged, memberIsYou) { case (true, false, false): if let displayName, let previousDisplayName { - return L10n.stateEventDisplayNameChangedFrom(member, previousDisplayName, displayName) + return L10n.stateEventDisplayNameChangedFrom(memberID, previousDisplayName, displayName) } else if let displayName { - return L10n.stateEventDisplayNameSet(member, displayName) + return L10n.stateEventDisplayNameSet(memberID, displayName) } else if let previousDisplayName { - return L10n.stateEventDisplayNameRemoved(member, previousDisplayName) + return L10n.stateEventDisplayNameRemoved(memberID, previousDisplayName) } else { MXLog.error("The display name changed from nil to nil, filtering the item.") return nil } case (false, true, false): - return L10n.stateEventAvatarUrlChanged(displayName ?? member) + // The SDK only sets displayName when it changed, so it's nil here and we need the member's current name. + return L10n.stateEventAvatarUrlChanged(memberDisplayName) case (true, false, true): if let displayName, let previousDisplayName { return L10n.stateEventDisplayNameChangedFromByYou(previousDisplayName, displayName) @@ -122,9 +135,13 @@ nonisolated struct RoomStateEventStringBuilder { return L10n.stateEventAvatarUrlChangedByYou case (true, true, _): // When both have changed, get the string for the display name and tack on that the avatar changed too. - guard let string = buildProfileChangeString(displayName: displayName, previousDisplayName: previousDisplayName, - avatarURLString: nil, previousAvatarURLString: nil, - member: member, memberIsYou: memberIsYou) else { return nil } + guard let string = buildProfileChangeString(displayName: displayName, + previousDisplayName: previousDisplayName, + avatarURLString: nil, + previousAvatarURLString: nil, + memberID: memberID, + memberDisplayName: memberDisplayName, + memberIsYou: memberIsYou) else { return nil } return string + "\n" + L10n.stateEventAvatarChangedToo case (false, false, _): MXLog.error("Nothing changed, shouldn't be possible. Filtering the item.") diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift index 05434ab28..cc6492ae4 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift @@ -948,7 +948,8 @@ nonisolated struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { previousDisplayName: previousDisplayName, avatarURLString: avatarURLString, previousAvatarURLString: previousAvatarURLString, - member: eventItemProxy.sender.id, + memberID: eventItemProxy.sender.id, + memberDisplayName: eventItemProxy.sender.disambiguatedDisplayName ?? eventItemProxy.sender.id, memberIsYou: isOutgoing) else { return nil } return buildStateTimelineItem(for: eventItemProxy, text: text, isOutgoing: isOutgoing) } diff --git a/UnitTests/Sources/RoomStateEventStringBuilderTests.swift b/UnitTests/Sources/RoomStateEventStringBuilderTests.swift index 50007f542..ddb67ff0d 100644 --- a/UnitTests/Sources/RoomStateEventStringBuilderTests.swift +++ b/UnitTests/Sources/RoomStateEventStringBuilderTests.swift @@ -47,7 +47,8 @@ struct RoomStateEventStringBuilderTests { previousDisplayName: oldName, avatarURLString: nil, previousAvatarURLString: nil, - member: sender.id, + memberID: sender.id, + memberDisplayName: sender.disambiguatedDisplayName ?? sender.id, memberIsYou: sender.id == userID) #expect(string == expectedString) } @@ -76,12 +77,14 @@ struct RoomStateEventStringBuilderTests { private func validateAvatarChange(senderID: String, senderName: String? = nil, oldAvatarURL: String?, newAvatarURL: String?, expectedString: String) { + // The SDK only includes the display name when it changed, so it is nil for an avatar-only change. let sender = TimelineItemSender(id: senderID, displayName: senderName) - let string = stringBuilder.buildProfileChangeString(displayName: senderName, - previousDisplayName: senderName, + let string = stringBuilder.buildProfileChangeString(displayName: nil, + previousDisplayName: nil, avatarURLString: newAvatarURL, previousAvatarURLString: oldAvatarURL, - member: sender.id, + memberID: sender.id, + memberDisplayName: sender.disambiguatedDisplayName ?? sender.id, memberIsYou: sender.id == userID) #expect(string == expectedString) }