Render Room and Message Pills (#3809)

* added a way to render the room and the message

pills, but is WIP

* permalinks now get converted into pills!

* fixed an issue where room address mentions

were not adding a URL properly but a string

* updated tests

* c

* Revert "c"

This reverts commit 5c80252fa23dba7e4d44f2a07fbf1e9500e37c82.

* updated tests

* more tests

* created APIs to get a specific RoomSummary

given the id or the alias

* small mention builder improvement

* pr suggestions
This commit is contained in:
Mauro
2025-02-25 14:46:01 +01:00
committed by GitHub
parent 944fe37fde
commit a2242c63e2
100 changed files with 1017 additions and 247 deletions
+149 -25
View File
@@ -9,13 +9,20 @@ import Foundation
import UIKit
struct MentionBuilder: MentionBuilderProtocol {
func handleUserMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, userID: String, userDisplayName: String?) {
let attributes = attributedString.attributes(at: 0, longestEffectiveRange: nil, in: range)
let font = attributes[.font] as? UIFont ?? .preferredFont(forTextStyle: .body)
let blockquote = attributes[.MatrixBlockquote]
let foregroundColor = attributes[.foregroundColor] as? UIColor ?? .compound.textPrimary
struct AttributesToRestore {
let font: UIFont
let blockquote: Bool?
let foregroundColor: UIColor
}
func handleUserMention(for attributedString: NSMutableAttributedString,
in range: NSRange,
url: URL,
userID: String,
userDisplayName: String?) {
let attributesToRestore = getAttributesToRestore(for: attributedString, in: range)
let attachmentData = PillTextAttachmentData(type: .user(userID: userID), font: font)
let attachmentData = PillTextAttachmentData(type: .user(userID: userID), font: attributesToRestore.font)
guard let attachment = PillTextAttachment(attachmentData: attachmentData) else {
attributedString.addAttribute(.MatrixUserID, value: userID, range: range)
@@ -26,34 +33,151 @@ struct MentionBuilder: MentionBuilderProtocol {
return
}
var attachmentAttributes: [NSAttributedString.Key: Any] = [.link: url, .MatrixUserID: userID, .font: font, .foregroundColor: foregroundColor]
if let blockquote {
// mentions can be in blockquotes, so if the replaced string was in one, we keep the attribute
attachmentAttributes[.MatrixBlockquote] = blockquote
}
let attachmentString = NSMutableAttributedString(attachment: attachment)
attachmentString.addAttributes(attachmentAttributes, range: NSRange(location: 0, length: attachmentString.length))
attributedString.replaceCharacters(in: range, with: attachmentString)
var attachmentAttributes: [NSAttributedString.Key: Any] = [.link: url,
.MatrixUserID: userID,
.font: attributesToRestore.font,
.foregroundColor: attributesToRestore.foregroundColor]
attachmentAttributes.addBlockquoteIfNeeded(attributesToRestore.blockquote)
attachmentAttributes.addMatrixUsernameIfNeeded(userDisplayName)
setPillAttachment(attachment: attachment,
attributedString: attributedString,
in: range,
with: attachmentAttributes)
}
func handleAllUsersMention(for attributedString: NSMutableAttributedString, in range: NSRange) {
let attributes = attributedString.attributes(at: 0, longestEffectiveRange: nil, in: range)
let font = attributes[.font] as? UIFont ?? .preferredFont(forTextStyle: .body)
let blockquote = attributes[.MatrixBlockquote]
let foregroundColor = attributes[.foregroundColor] as? UIColor ?? .compound.textPrimary
let attachmentData = PillTextAttachmentData(type: .allUsers, font: font)
let attributesToRestore = getAttributesToRestore(for: attributedString, in: range)
let attachmentData = PillTextAttachmentData(type: .allUsers, font: attributesToRestore.font)
guard let attachment = PillTextAttachment(attachmentData: attachmentData) else {
return
}
var attachmentAttributes: [NSAttributedString.Key: Any] = [.font: font, .MatrixAllUsersMention: true, .foregroundColor: foregroundColor]
if let blockquote {
// mentions can be in blockquotes, so if the replaced string was in one, we keep the attribute
attachmentAttributes[.MatrixBlockquote] = blockquote
var attachmentAttributes: [NSAttributedString.Key: Any] = [.font: attributesToRestore.font,
.MatrixAllUsersMention: true,
.foregroundColor: attributesToRestore.foregroundColor]
attachmentAttributes.addBlockquoteIfNeeded(attributesToRestore.blockquote)
setPillAttachment(attachment: attachment,
attributedString: attributedString,
in: range,
with: attachmentAttributes)
}
func handleRoomIDMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, roomID: String) {
let attributesToRestore = getAttributesToRestore(for: attributedString, in: range)
let attachmentData = PillTextAttachmentData(type: .roomID(roomID), font: attributesToRestore.font)
guard let attachment = PillTextAttachment(attachmentData: attachmentData) else {
attributedString.addAttribute(.MatrixRoomID, value: roomID, range: range)
return
}
var attachmentAttributes: [NSAttributedString.Key: Any] = [.link: url,
.MatrixRoomID: roomID,
.font: attributesToRestore.font,
.foregroundColor: attributesToRestore.foregroundColor]
attachmentAttributes.addBlockquoteIfNeeded(attributesToRestore.blockquote)
setPillAttachment(attachment: attachment,
attributedString: attributedString,
in: range,
with: attachmentAttributes)
}
func handleRoomAliasMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, roomAlias: String) {
let attributesToRestore = getAttributesToRestore(for: attributedString, in: range)
let attachmentData = PillTextAttachmentData(type: .roomAlias(roomAlias), font: attributesToRestore.font)
guard let attachment = PillTextAttachment(attachmentData: attachmentData) else {
attributedString.addAttribute(.MatrixRoomAlias, value: roomAlias, range: range)
return
}
var attachmentAttributes: [NSAttributedString.Key: Any] = [.link: url,
.MatrixRoomAlias: roomAlias,
.font: attributesToRestore.font,
.foregroundColor: attributesToRestore.foregroundColor]
attachmentAttributes.addBlockquoteIfNeeded(attributesToRestore.blockquote)
setPillAttachment(attachment: attachment,
attributedString: attributedString,
in: range,
with: attachmentAttributes)
}
func handleEventOnRoomAliasMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, eventID: String, roomAlias: String) {
let attributesToRestore = getAttributesToRestore(for: attributedString, in: range)
let attachmentData = PillTextAttachmentData(type: .event(room: .roomAlias(roomAlias)), font: attributesToRestore.font)
guard let attachment = PillTextAttachment(attachmentData: attachmentData) else {
attributedString.addAttribute(.MatrixEventOnRoomAlias, value: EventOnRoomAliasAttribute.Value(alias: roomAlias, eventID: eventID), range: range)
return
}
var attachmentAttributes: [NSAttributedString.Key: Any] = [.link: url,
.MatrixEventOnRoomAlias: EventOnRoomAliasAttribute.Value(alias: roomAlias, eventID: eventID),
.font: attributesToRestore.font,
.foregroundColor: attributesToRestore.foregroundColor]
attachmentAttributes.addBlockquoteIfNeeded(attributesToRestore.blockquote)
setPillAttachment(attachment: attachment,
attributedString: attributedString,
in: range,
with: attachmentAttributes)
}
func handleEventOnRoomIDMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, eventID: String, roomID: String) {
let attributesToRestore = getAttributesToRestore(for: attributedString, in: range)
let attachmentData = PillTextAttachmentData(type: .event(room: .roomID(roomID)), font: attributesToRestore.font)
guard let attachment = PillTextAttachment(attachmentData: attachmentData) else {
attributedString.addAttribute(.MatrixEventOnRoomID, value: EventOnRoomIDAttribute.Value(roomID: roomID, eventID: eventID), range: range)
return
}
var attachmentAttributes: [NSAttributedString.Key: Any] = [.link: url,
.MatrixEventOnRoomID: EventOnRoomIDAttribute.Value(roomID: roomID, eventID: eventID),
.font: attributesToRestore.font,
.foregroundColor: attributesToRestore.foregroundColor]
attachmentAttributes.addBlockquoteIfNeeded(attributesToRestore.blockquote)
setPillAttachment(attachment: attachment,
attributedString: attributedString,
in: range,
with: attachmentAttributes)
}
private func getAttributesToRestore(for attributedString: NSMutableAttributedString, in range: NSRange) -> AttributesToRestore {
let attributes = attributedString.attributes(at: 0, longestEffectiveRange: nil, in: range)
let font = attributes[.font] as? UIFont ?? .preferredFont(forTextStyle: .body)
let blockquote = attributes[.MatrixBlockquote] as? Bool
let foregroundColor = attributes[.foregroundColor] as? UIColor ?? .compound.textPrimary
return AttributesToRestore(font: font, blockquote: blockquote, foregroundColor: foregroundColor)
}
private func setPillAttachment(attachment: PillTextAttachment,
attributedString: NSMutableAttributedString,
in range: NSRange,
with attributes: [NSAttributedString.Key: Any]) {
let attachmentString = NSMutableAttributedString(attachment: attachment)
attachmentString.addAttributes(attachmentAttributes, range: NSRange(location: 0, length: attachmentString.length))
attachmentString.addAttributes(attributes, range: NSRange(location: 0, length: attachmentString.length))
attributedString.replaceCharacters(in: range, with: attachmentString)
}
}
private extension Dictionary where Key == NSAttributedString.Key, Value == Any {
mutating func addBlockquoteIfNeeded(_ value: Bool?) {
if let value {
self[.MatrixBlockquote] = value
}
}
mutating func addMatrixUsernameIfNeeded(_ value: String?) {
if let value {
self[.MatrixUserDisplayName] = value
}
}
}