Fix a bug where your own emotes showed as '* You emoted'. (#4038)

This commit is contained in:
Doug
2025-04-17 12:39:41 +01:00
committed by GitHub
parent 4079549803
commit 7cf1d08b20
4 changed files with 85 additions and 37 deletions
@@ -17,9 +17,7 @@ struct RoomEventStringBuilder {
func buildAttributedString(for eventItemProxy: EventTimelineItemProxy) -> AttributedString? { func buildAttributedString(for eventItemProxy: EventTimelineItemProxy) -> AttributedString? {
let sender = eventItemProxy.sender let sender = eventItemProxy.sender
let isOutgoing = eventItemProxy.isOwn let isOutgoing = eventItemProxy.isOwn
let displayName = if isOutgoing { let displayName = if shouldDisambiguateDisplayNames {
L10n.commonYou
} else if shouldDisambiguateDisplayNames {
sender.disambiguatedDisplayName ?? sender.id sender.disambiguatedDisplayName ?? sender.id
} else { } else {
sender.displayName ?? sender.id sender.displayName ?? sender.id
@@ -29,14 +27,14 @@ struct RoomEventStringBuilder {
case .msgLike(let messageLikeContent): case .msgLike(let messageLikeContent):
switch messageLikeContent.kind { switch messageLikeContent.kind {
case .message(let messageContent): case .message(let messageContent):
return messageEventStringBuilder.buildAttributedString(for: messageContent.msgType, senderDisplayName: displayName) return messageEventStringBuilder.buildAttributedString(for: messageContent.msgType, senderDisplayName: displayName, isOutgoing: isOutgoing)
case .sticker: case .sticker:
if messageEventStringBuilder.destination == .pinnedEvent { if messageEventStringBuilder.destination == .pinnedEvent {
var string = AttributedString(L10n.commonSticker) var string = AttributedString(L10n.commonSticker)
string.bold() string.bold()
return string return string
} }
return prefix(L10n.commonSticker, with: displayName) return prefix(L10n.commonSticker, with: displayName, isOutgoing: isOutgoing)
case .poll(let question, _, _, _, _, _, _): case .poll(let question, _, _, _, _, _, _):
if messageEventStringBuilder.destination == .pinnedEvent { if messageEventStringBuilder.destination == .pinnedEvent {
let questionPlaceholder = "{question}" let questionPlaceholder = "{question}"
@@ -46,9 +44,9 @@ struct RoomEventStringBuilder {
finalString.replace(questionPlaceholder, with: normalString) finalString.replace(questionPlaceholder, with: normalString)
return finalString return finalString
} }
return prefix(L10n.commonPollSummary(question), with: displayName) return prefix(L10n.commonPollSummary(question), with: displayName, isOutgoing: isOutgoing)
case .redacted: case .redacted:
return prefix(L10n.commonMessageRemoved, with: displayName) return prefix(L10n.commonMessageRemoved, with: displayName, isOutgoing: isOutgoing)
case .unableToDecrypt(let encryptedMessage): case .unableToDecrypt(let encryptedMessage):
let errorMessage = switch encryptedMessage { let errorMessage = switch encryptedMessage {
case .megolmV1AesSha2(_, .sentBeforeWeJoined): L10n.commonUnableToDecryptNoAccess case .megolmV1AesSha2(_, .sentBeforeWeJoined): L10n.commonUnableToDecryptNoAccess
@@ -56,10 +54,10 @@ struct RoomEventStringBuilder {
case .megolmV1AesSha2(_, .unknownDevice), .megolmV1AesSha2(_, .unsignedDevice): L10n.commonUnableToDecryptInsecureDevice case .megolmV1AesSha2(_, .unknownDevice), .megolmV1AesSha2(_, .unsignedDevice): L10n.commonUnableToDecryptInsecureDevice
default: L10n.commonWaitingForDecryptionKey default: L10n.commonWaitingForDecryptionKey
} }
return prefix(errorMessage, with: displayName) return prefix(errorMessage, with: displayName, isOutgoing: isOutgoing)
} }
case .failedToParseMessageLike, .failedToParseState: case .failedToParseMessageLike, .failedToParseState:
return prefix(L10n.commonUnsupportedEvent, with: displayName) return prefix(L10n.commonUnsupportedEvent, with: displayName, isOutgoing: isOutgoing)
case .state(_, let state): case .state(_, let state):
return stateEventStringBuilder return stateEventStringBuilder
.buildString(for: state, sender: sender, isOutgoing: isOutgoing) .buildString(for: state, sender: sender, isOutgoing: isOutgoing)
@@ -78,19 +76,19 @@ struct RoomEventStringBuilder {
memberIsYou: isOutgoing) memberIsYou: isOutgoing)
.map(AttributedString.init) .map(AttributedString.init)
case .callInvite: case .callInvite:
return prefix(L10n.commonUnsupportedCall, with: displayName) return prefix(L10n.commonUnsupportedCall, with: displayName, isOutgoing: isOutgoing)
case .callNotify: case .callNotify:
return prefix(L10n.commonCallStarted, with: displayName) return prefix(L10n.commonCallStarted, with: displayName, isOutgoing: isOutgoing)
} }
} }
private func prefix(_ eventSummary: String, with senderDisplayName: String) -> AttributedString { private func prefix(_ eventSummary: String, with senderDisplayName: String, isOutgoing: Bool) -> AttributedString {
guard shouldPrefixSenderName else { guard shouldPrefixSenderName else {
return AttributedString(eventSummary) return AttributedString(eventSummary)
} }
let attributedEventSummary = AttributedString(eventSummary.trimmingCharacters(in: .whitespacesAndNewlines)) let attributedEventSummary = AttributedString(eventSummary.trimmingCharacters(in: .whitespacesAndNewlines))
var attributedSenderDisplayName = AttributedString(senderDisplayName) var attributedSenderDisplayName = AttributedString(isOutgoing ? L10n.commonYou : senderDisplayName)
attributedSenderDisplayName.bold() attributedSenderDisplayName.bold()
// Don't include the message body in the markdown otherwise it makes tappable links. // Don't include the message body in the markdown otherwise it makes tappable links.
@@ -24,7 +24,7 @@ struct RoomMessageEventStringBuilder {
let attributedStringBuilder: AttributedStringBuilderProtocol let attributedStringBuilder: AttributedStringBuilderProtocol
let destination: Destination let destination: Destination
func buildAttributedString(for messageType: MessageType, senderDisplayName: String) -> AttributedString { func buildAttributedString(for messageType: MessageType, senderDisplayName: String, isOutgoing: Bool) -> AttributedString {
let message: AttributedString let message: AttributedString
switch messageType { switch messageType {
case .emote(content: let content): case .emote(content: let content):
@@ -69,7 +69,7 @@ struct RoomMessageEventStringBuilder {
} }
if destination == .roomList { if destination == .roomList {
return prefix(message, with: senderDisplayName) return prefix(message, with: isOutgoing ? L10n.commonYou : senderDisplayName)
} else { } else {
return message return message
} }
+1 -1
View File
@@ -104,7 +104,7 @@ struct NotificationContentBuilder {
var notification = try await processCommonRoomMessage(notificationItem: notificationItem, mediaProvider: mediaProvider) var notification = try await processCommonRoomMessage(notificationItem: notificationItem, mediaProvider: mediaProvider)
let displayName = notificationItem.senderDisplayName ?? notificationItem.roomDisplayName let displayName = notificationItem.senderDisplayName ?? notificationItem.roomDisplayName
notification.body = String(messageEventStringBuilder.buildAttributedString(for: messageType, senderDisplayName: displayName).characters) notification.body = String(messageEventStringBuilder.buildAttributedString(for: messageType, senderDisplayName: displayName, isOutgoing: false).characters)
guard settings.timelineMediaVisibility == .always || guard settings.timelineMediaVisibility == .always ||
(settings.timelineMediaVisibility == .privateOnly && notificationItem.isRoomPrivate) (settings.timelineMediaVisibility == .privateOnly && notificationItem.isRoomPrivate)
@@ -26,43 +26,93 @@ class RoomEventStringBuilderTests: XCTestCase {
} }
func testSenderPrefix() { func testSenderPrefix() {
let ownMessageString = stringBuilder.buildAttributedString(for: makeTextMessageItem(senderID: ownUserID, senderDisplayName: "Alice")) let ownMessageString = stringBuilder.buildAttributedString(for: makeMessageItem(senderID: ownUserID, senderDisplayName: "Alice"))
XCTAssertEqual(ownMessageString?.string, "You: Hello, World!", XCTAssertEqual(ownMessageString?.string, "You: Hello, World!", "Your own messages should be prefixed with 'You'")
"Your own messages should be prefixed with 'You'")
let otherMessageString = stringBuilder.buildAttributedString(for: makeTextMessageItem(senderID: "@bob:matrix.org", senderDisplayName: "Bob")) let otherMessageString = stringBuilder.buildAttributedString(for: makeMessageItem(senderID: "@bob:matrix.org", senderDisplayName: "Bob"))
XCTAssertEqual(otherMessageString?.string, "Bob: Hello, World!", XCTAssertEqual(otherMessageString?.string, "Bob: Hello, World!", "Everyone else's messages should be prefixed with their display name.")
"Everyone else's messages should be prefixed with their display name.")
let ambiguousMessageString = stringBuilder.buildAttributedString(for: makeTextMessageItem(senderID: "@charlie:matrix.org", let ambiguousMessageString = stringBuilder.buildAttributedString(for: makeMessageItem(senderID: "@charlie:matrix.org",
senderDisplayName: "Charlie", senderDisplayName: "Charlie",
senderDisplayNameAmbiguous: true)) senderDisplayNameAmbiguous: true))
XCTAssertEqual(ambiguousMessageString?.string, "Charlie (@charlie:matrix.org): Hello, World!", XCTAssertEqual(ambiguousMessageString?.string, "Charlie (@charlie:matrix.org): Hello, World!",
"Messages from senders with ambiguous display names should include their user ID in the prefix.") "Messages from senders with ambiguous display names should include their user ID in the prefix.")
let ownEmoteString = stringBuilder.buildAttributedString(for: makeMessageItem(senderID: ownUserID,
senderDisplayName: "Alice",
type: .emote,
message: "laughs"))
XCTAssertEqual(ownEmoteString?.string, "* Alice laughs", "Your own emotes shouldn't contain 'You'")
let otherEmoteString = stringBuilder.buildAttributedString(for: makeMessageItem(senderID: "@bob:matrix.org",
senderDisplayName: "Bob",
type: .emote,
message: "sighs"))
XCTAssertEqual(otherEmoteString?.string, "* Bob sighs", "Everyone else's emotes should contain their display name.")
let ownPollString = stringBuilder.buildAttributedString(for: makePollItem(senderID: ownUserID, senderDisplayName: "Alice"))
XCTAssertEqual(ownPollString?.string, "You: Poll: Which is better?", "Your own polls should be prefixed with 'You'")
let otherPollString = stringBuilder.buildAttributedString(for: makePollItem(senderID: "@bob:matrix.org", senderDisplayName: "Bob"))
XCTAssertEqual(otherPollString?.string, "Bob: Poll: Which is better?", "Everyone else's polls should be prefixed with their display name.")
} }
// MARK: - Helpers // MARK: - Helpers
private func makeTextMessageItem(senderID: String, private enum MockMessageType { case textMessage, emote }
senderDisplayName: String? = nil,
senderDisplayNameAmbiguous: Bool = false, private func makeMessageItem(senderID: String,
message: String = "Hello, World!") -> EventTimelineItemProxy { senderDisplayName: String? = nil,
senderDisplayNameAmbiguous: Bool = false,
type: MockMessageType = .textMessage,
message: String = "Hello, World!") -> EventTimelineItemProxy {
let content = switch type {
case .textMessage: makeTextContent(message: message)
case .emote: makeEmoteContent(message: message)
}
return .init(item: .init(configuration: .init(eventID: "1234",
sender: senderID,
senderProfile: .ready(displayName: senderDisplayName, displayNameAmbiguous: senderDisplayNameAmbiguous, avatarUrl: nil),
isOwn: senderID == ownUserID,
content: .msgLike(content: .init(kind: .message(content: .init(msgType: content,
body: message,
isEdited: false,
mentions: nil)),
reactions: [],
inReplyTo: nil,
threadRoot: nil,
threadSummary: nil)))),
uniqueID: .init("0"))
}
private func makeTextContent(message: String) -> MessageType {
.text(content: .init(body: message, formatted: nil))
}
private func makeEmoteContent(message: String) -> MessageType {
.emote(content: .init(body: message, formatted: nil))
}
private func makePollItem(senderID: String,
senderDisplayName: String? = nil,
senderDisplayNameAmbiguous: Bool = false,
question: String = "Which is better?") -> EventTimelineItemProxy {
.init(item: .init(configuration: .init(eventID: "1234", .init(item: .init(configuration: .init(eventID: "1234",
sender: senderID, sender: senderID,
senderProfile: .ready(displayName: senderDisplayName, displayNameAmbiguous: senderDisplayNameAmbiguous, avatarUrl: nil), senderProfile: .ready(displayName: senderDisplayName, displayNameAmbiguous: senderDisplayNameAmbiguous, avatarUrl: nil),
isOwn: senderID == ownUserID, isOwn: senderID == ownUserID,
content: .msgLike(content: .init(kind: .message(content: .init(msgType: makeTextContent(message: message), content: .msgLike(content: .init(kind: .poll(question: question,
body: message, kind: .disclosed,
isEdited: false, maxSelections: 1,
mentions: nil)), answers: [],
votes: [:],
endTime: nil,
hasBeenEdited: false),
reactions: [], reactions: [],
inReplyTo: nil, inReplyTo: nil,
threadRoot: nil, threadRoot: nil,
threadSummary: nil)))), threadSummary: nil)))),
uniqueID: .init("0")) uniqueID: .init("0"))
} }
private func makeTextContent(message: String) -> MessageType {
.text(content: .init(body: message, formatted: nil))
}
} }