mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 13:37:11 +00:00
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:
parent
944fe37fde
commit
a2242c63e2
@ -93,7 +93,8 @@ class MediaEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
|
||||
appMediator: appMediator,
|
||||
emojiProvider: emojiProvider,
|
||||
userIndicatorController: userIndicatorController,
|
||||
timelineControllerFactory: timelineControllerFactory)
|
||||
timelineControllerFactory: timelineControllerFactory,
|
||||
clientProxy: userSession.clientProxy)
|
||||
|
||||
let coordinator = MediaEventsTimelineScreenCoordinator(parameters: parameters)
|
||||
|
||||
|
@ -78,7 +78,8 @@ class PinnedEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
|
||||
voiceMessageMediaManager: userSession.voiceMessageMediaManager,
|
||||
appMediator: appMediator,
|
||||
emojiProvider: emojiProvider,
|
||||
timelineControllerFactory: timelineControllerFactory))
|
||||
timelineControllerFactory: timelineControllerFactory,
|
||||
clientProxy: userSession.clientProxy))
|
||||
|
||||
coordinator.actions
|
||||
.sink { [weak self] action in
|
||||
|
@ -3225,6 +3225,146 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable {
|
||||
return roomPreviewForIdentifierViaReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - roomSummaryForIdentifier
|
||||
|
||||
var roomSummaryForIdentifierUnderlyingCallsCount = 0
|
||||
var roomSummaryForIdentifierCallsCount: Int {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return roomSummaryForIdentifierUnderlyingCallsCount
|
||||
} else {
|
||||
var returnValue: Int? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = roomSummaryForIdentifierUnderlyingCallsCount
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
}
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
roomSummaryForIdentifierUnderlyingCallsCount = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
roomSummaryForIdentifierUnderlyingCallsCount = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var roomSummaryForIdentifierCalled: Bool {
|
||||
return roomSummaryForIdentifierCallsCount > 0
|
||||
}
|
||||
var roomSummaryForIdentifierReceivedIdentifier: String?
|
||||
var roomSummaryForIdentifierReceivedInvocations: [String] = []
|
||||
|
||||
var roomSummaryForIdentifierUnderlyingReturnValue: RoomSummary?
|
||||
var roomSummaryForIdentifierReturnValue: RoomSummary? {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return roomSummaryForIdentifierUnderlyingReturnValue
|
||||
} else {
|
||||
var returnValue: RoomSummary?? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = roomSummaryForIdentifierUnderlyingReturnValue
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
}
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
roomSummaryForIdentifierUnderlyingReturnValue = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
roomSummaryForIdentifierUnderlyingReturnValue = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var roomSummaryForIdentifierClosure: ((String) -> RoomSummary?)?
|
||||
|
||||
func roomSummaryForIdentifier(_ identifier: String) -> RoomSummary? {
|
||||
roomSummaryForIdentifierCallsCount += 1
|
||||
roomSummaryForIdentifierReceivedIdentifier = identifier
|
||||
DispatchQueue.main.async {
|
||||
self.roomSummaryForIdentifierReceivedInvocations.append(identifier)
|
||||
}
|
||||
if let roomSummaryForIdentifierClosure = roomSummaryForIdentifierClosure {
|
||||
return roomSummaryForIdentifierClosure(identifier)
|
||||
} else {
|
||||
return roomSummaryForIdentifierReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - roomSummaryForAlias
|
||||
|
||||
var roomSummaryForAliasUnderlyingCallsCount = 0
|
||||
var roomSummaryForAliasCallsCount: Int {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return roomSummaryForAliasUnderlyingCallsCount
|
||||
} else {
|
||||
var returnValue: Int? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = roomSummaryForAliasUnderlyingCallsCount
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
}
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
roomSummaryForAliasUnderlyingCallsCount = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
roomSummaryForAliasUnderlyingCallsCount = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var roomSummaryForAliasCalled: Bool {
|
||||
return roomSummaryForAliasCallsCount > 0
|
||||
}
|
||||
var roomSummaryForAliasReceivedAlias: String?
|
||||
var roomSummaryForAliasReceivedInvocations: [String] = []
|
||||
|
||||
var roomSummaryForAliasUnderlyingReturnValue: RoomSummary?
|
||||
var roomSummaryForAliasReturnValue: RoomSummary? {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return roomSummaryForAliasUnderlyingReturnValue
|
||||
} else {
|
||||
var returnValue: RoomSummary?? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = roomSummaryForAliasUnderlyingReturnValue
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
}
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
roomSummaryForAliasUnderlyingReturnValue = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
roomSummaryForAliasUnderlyingReturnValue = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var roomSummaryForAliasClosure: ((String) -> RoomSummary?)?
|
||||
|
||||
func roomSummaryForAlias(_ alias: String) -> RoomSummary? {
|
||||
roomSummaryForAliasCallsCount += 1
|
||||
roomSummaryForAliasReceivedAlias = alias
|
||||
DispatchQueue.main.async {
|
||||
self.roomSummaryForAliasReceivedInvocations.append(alias)
|
||||
}
|
||||
if let roomSummaryForAliasClosure = roomSummaryForAliasClosure {
|
||||
return roomSummaryForAliasClosure(alias)
|
||||
} else {
|
||||
return roomSummaryForAliasReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - loadUserDisplayName
|
||||
|
||||
var loadUserDisplayNameUnderlyingCallsCount = 0
|
||||
|
@ -67,6 +67,31 @@ extension RoomSummaryProviderMock {
|
||||
}
|
||||
}
|
||||
|
||||
extension RoomSummary {
|
||||
static func mock(id: String,
|
||||
name: String,
|
||||
canonicalAlias: String? = nil) -> RoomSummary {
|
||||
RoomSummary(roomListItem: RoomListItemSDKMock(),
|
||||
id: id,
|
||||
joinRequestType: nil,
|
||||
name: name,
|
||||
isDirect: false,
|
||||
avatarURL: nil,
|
||||
heroes: [],
|
||||
lastMessage: AttributedString("I do not wish to take the trouble to understand mysticism"),
|
||||
lastMessageFormattedTimestamp: "14:56",
|
||||
unreadMessagesCount: 0,
|
||||
unreadMentionsCount: 0,
|
||||
unreadNotificationsCount: 0,
|
||||
notificationMode: .allMessages,
|
||||
canonicalAlias: canonicalAlias,
|
||||
alternativeAliases: [],
|
||||
hasOngoingCall: false,
|
||||
isMarkedUnread: false,
|
||||
isFavourite: false)
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element == RoomSummary {
|
||||
static let mockRooms: [Element] = [
|
||||
RoomSummary(roomListItem: RoomListItemSDKMock(),
|
||||
@ -83,6 +108,7 @@ extension Array where Element == RoomSummary {
|
||||
unreadNotificationsCount: 0,
|
||||
notificationMode: .allMessages,
|
||||
canonicalAlias: nil,
|
||||
alternativeAliases: [],
|
||||
hasOngoingCall: false,
|
||||
isMarkedUnread: false,
|
||||
isFavourite: false),
|
||||
@ -99,7 +125,8 @@ extension Array where Element == RoomSummary {
|
||||
unreadMentionsCount: 0,
|
||||
unreadNotificationsCount: 2,
|
||||
notificationMode: .mute,
|
||||
canonicalAlias: nil,
|
||||
canonicalAlias: "#foundation-and-empire:matrix.org",
|
||||
alternativeAliases: [],
|
||||
hasOngoingCall: false,
|
||||
isMarkedUnread: false,
|
||||
isFavourite: false),
|
||||
@ -117,6 +144,7 @@ extension Array where Element == RoomSummary {
|
||||
unreadNotificationsCount: 0,
|
||||
notificationMode: .mentionsAndKeywordsOnly,
|
||||
canonicalAlias: nil,
|
||||
alternativeAliases: [],
|
||||
hasOngoingCall: false,
|
||||
isMarkedUnread: false,
|
||||
isFavourite: false),
|
||||
@ -134,6 +162,7 @@ extension Array where Element == RoomSummary {
|
||||
unreadNotificationsCount: 2,
|
||||
notificationMode: .allMessages,
|
||||
canonicalAlias: nil,
|
||||
alternativeAliases: [],
|
||||
hasOngoingCall: false,
|
||||
isMarkedUnread: false,
|
||||
isFavourite: false),
|
||||
@ -151,6 +180,7 @@ extension Array where Element == RoomSummary {
|
||||
unreadNotificationsCount: 1,
|
||||
notificationMode: .allMessages,
|
||||
canonicalAlias: nil,
|
||||
alternativeAliases: [],
|
||||
hasOngoingCall: true,
|
||||
isMarkedUnread: false,
|
||||
isFavourite: false),
|
||||
@ -168,6 +198,7 @@ extension Array where Element == RoomSummary {
|
||||
unreadNotificationsCount: 0,
|
||||
notificationMode: .mute,
|
||||
canonicalAlias: nil,
|
||||
alternativeAliases: [],
|
||||
hasOngoingCall: true,
|
||||
isMarkedUnread: false,
|
||||
isFavourite: false),
|
||||
@ -185,6 +216,7 @@ extension Array where Element == RoomSummary {
|
||||
unreadNotificationsCount: 0,
|
||||
notificationMode: nil,
|
||||
canonicalAlias: nil,
|
||||
alternativeAliases: [],
|
||||
hasOngoingCall: false,
|
||||
isMarkedUnread: false,
|
||||
isFavourite: false)
|
||||
@ -235,6 +267,7 @@ extension Array where Element == RoomSummary {
|
||||
unreadNotificationsCount: 0,
|
||||
notificationMode: nil,
|
||||
canonicalAlias: "#footest:somewhere.org",
|
||||
alternativeAliases: [],
|
||||
hasOngoingCall: false,
|
||||
isMarkedUnread: false,
|
||||
isFavourite: false),
|
||||
@ -252,6 +285,7 @@ extension Array where Element == RoomSummary {
|
||||
unreadNotificationsCount: 0,
|
||||
notificationMode: nil,
|
||||
canonicalAlias: nil,
|
||||
alternativeAliases: [],
|
||||
hasOngoingCall: false,
|
||||
isMarkedUnread: false,
|
||||
isFavourite: false)
|
||||
|
@ -224,7 +224,8 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
||||
case .atRoom:
|
||||
attributedString.addAttribute(.MatrixAllUsersMention, value: true, range: match.range)
|
||||
case .roomAlias(let alias):
|
||||
if let url = try? matrixToRoomAliasPermalink(roomAlias: alias) {
|
||||
if let urlString = try? matrixToRoomAliasPermalink(roomAlias: alias),
|
||||
let url = URL(string: urlString) {
|
||||
attributedString.addAttribute(.link, value: url, range: match.range)
|
||||
}
|
||||
case .matrixURI(let uri):
|
||||
@ -282,13 +283,13 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
||||
case .user(let userID):
|
||||
mentionBuilder.handleUserMention(for: attributedString, in: range, url: url, userID: userID, userDisplayName: nil)
|
||||
case .room(let roomID):
|
||||
attributedString.addAttributes([.MatrixRoomID: roomID], range: range)
|
||||
mentionBuilder.handleRoomIDMention(for: attributedString, in: range, url: url, roomID: roomID)
|
||||
case .roomAlias(let alias):
|
||||
attributedString.addAttributes([.MatrixRoomAlias: alias], range: range)
|
||||
mentionBuilder.handleRoomAliasMention(for: attributedString, in: range, url: url, roomAlias: alias)
|
||||
case .eventOnRoomId(let roomID, let eventID):
|
||||
attributedString.addAttributes([.MatrixEventOnRoomID: EventOnRoomIDAttribute.Value(roomID: roomID, eventID: eventID)], range: range)
|
||||
mentionBuilder.handleEventOnRoomIDMention(for: attributedString, in: range, url: url, eventID: eventID, roomID: roomID)
|
||||
case .eventOnRoomAlias(let alias, let eventID):
|
||||
attributedString.addAttributes([.MatrixEventOnRoomAlias: EventOnRoomAliasAttribute.Value(alias: alias, eventID: eventID)], range: range)
|
||||
mentionBuilder.handleEventOnRoomAliasMention(for: attributedString, in: range, url: url, eventID: eventID, roomAlias: alias)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -364,6 +365,10 @@ extension NSAttributedString.Key {
|
||||
|
||||
protocol MentionBuilderProtocol {
|
||||
func handleUserMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, userID: String, userDisplayName: String?)
|
||||
func handleRoomIDMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, roomID: String)
|
||||
func handleRoomAliasMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, roomAlias: String)
|
||||
func handleEventOnRoomAliasMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, eventID: String, roomAlias: String)
|
||||
func handleEventOnRoomIDMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, eventID: String, roomID: String)
|
||||
func handleAllUsersMention(for attributedString: NSMutableAttributedString, in range: NSRange)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
let attachmentData = PillTextAttachmentData(type: .user(userID: userID), font: font)
|
||||
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: 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 attributesToRestore = getAttributesToRestore(for: attributedString, in: range)
|
||||
|
||||
let attachmentData = PillTextAttachmentData(type: .allUsers, font: font)
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ final class PillAttachmentViewProvider: NSTextAttachmentViewProvider, NSSecureCo
|
||||
let mediaProvider: MediaProviderProtocol?
|
||||
if ProcessInfo.isXcodePreview || ProcessInfo.isRunningTests {
|
||||
// The mock viewModel simulates the loading logic for testing purposes
|
||||
context = PillContext.mock(type: .loadUser(isOwn: false))
|
||||
context = PillContext.mock(viewState: .mention(isOwnMention: false, displayText: "Alice"), delay: .seconds(2))
|
||||
mediaProvider = MediaProviderMock(configuration: .init())
|
||||
} else if let timelineContext = delegate?.timelineContext {
|
||||
context = PillContext(timelineContext: timelineContext, data: pillData)
|
||||
|
@ -10,75 +10,77 @@ import Foundation
|
||||
|
||||
@MainActor
|
||||
final class PillContext: ObservableObject {
|
||||
struct PillViewState: Equatable {
|
||||
let isOwnMention: Bool
|
||||
let displayText: String
|
||||
}
|
||||
@Published var viewState: PillViewState = .undefined
|
||||
|
||||
@Published private(set) var viewState: PillViewState
|
||||
|
||||
private var cancellable: AnyCancellable?
|
||||
let data: PillTextAttachmentData
|
||||
var cancellable: AnyCancellable?
|
||||
|
||||
init(timelineContext: TimelineViewModel.Context, data: PillTextAttachmentData) {
|
||||
switch data.type {
|
||||
case let .user(id):
|
||||
let isOwnMention = id == timelineContext.viewState.ownUserID
|
||||
if let profile = timelineContext.viewState.members[id] {
|
||||
var name = id
|
||||
if let displayName = profile.displayName {
|
||||
name = "@\(displayName)"
|
||||
}
|
||||
viewState = PillViewState(isOwnMention: isOwnMention, displayText: name)
|
||||
} else {
|
||||
viewState = PillViewState(isOwnMention: isOwnMention, displayText: id)
|
||||
cancellable = timelineContext.$viewState.sink { [weak self] viewState in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let profile = viewState.members[id] {
|
||||
var name = id
|
||||
if let displayName = profile.displayName {
|
||||
name = "@\(displayName)"
|
||||
}
|
||||
self.viewState = PillViewState(isOwnMention: isOwnMention, displayText: name)
|
||||
cancellable = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
case .allUsers:
|
||||
viewState = PillViewState(isOwnMention: true, displayText: PillConstants.atRoom)
|
||||
}
|
||||
self.data = data
|
||||
timelineContext.viewState.pillContextUpdater?(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension PillContext {
|
||||
enum MockType {
|
||||
case loadUser(isOwn: Bool)
|
||||
case loadedUser(isOwn: Bool)
|
||||
case allUsers
|
||||
static func mock(viewState: PillViewState, delay: Duration? = nil) -> PillContext {
|
||||
// This is just for previews so the internal data doesn't really matter
|
||||
let viewModel = PillContext(timelineContext: TimelineViewModel.mock.context, data: PillTextAttachmentData(type: .allUsers, font: .preferredFont(forTextStyle: .body)))
|
||||
if let delay {
|
||||
viewModel.viewState = .mention(isOwnMention: false, displayText: "placeholder")
|
||||
Task {
|
||||
try? await Task.sleep(for: delay)
|
||||
viewModel.viewState = viewState
|
||||
}
|
||||
} else {
|
||||
viewModel.viewState = viewState
|
||||
}
|
||||
return viewModel
|
||||
}
|
||||
}
|
||||
|
||||
enum PillViewState: Equatable {
|
||||
enum PillImage: Equatable {
|
||||
case link
|
||||
case roomAvatar(RoomAvatar)
|
||||
}
|
||||
|
||||
static func mock(type: MockType) -> PillContext {
|
||||
let testID = "@test:test.com"
|
||||
let pillType: PillType
|
||||
switch type {
|
||||
case .loadUser(let isOwn):
|
||||
pillType = .user(userID: testID)
|
||||
let viewModel = PillContext(timelineContext: TimelineViewModel.mock.context, data: PillTextAttachmentData(type: pillType, font: .preferredFont(forTextStyle: .body)))
|
||||
viewModel.viewState = PillViewState(isOwnMention: isOwn, displayText: testID)
|
||||
Task {
|
||||
try? await Task.sleep(for: .seconds(2))
|
||||
viewModel.viewState = PillViewState(isOwnMention: isOwn, displayText: "@Test Long Display Text")
|
||||
}
|
||||
return viewModel
|
||||
case .loadedUser(let isOwn):
|
||||
pillType = .user(userID: "@test:test.com")
|
||||
let viewModel = PillContext(timelineContext: TimelineViewModel.mock.context, data: PillTextAttachmentData(type: pillType, font: .preferredFont(forTextStyle: .body)))
|
||||
viewModel.viewState = PillViewState(isOwnMention: isOwn, displayText: "@Very Very Long Test Display Text")
|
||||
return viewModel
|
||||
case .allUsers:
|
||||
pillType = .allUsers
|
||||
return PillContext(timelineContext: TimelineViewModel.mock.context, data: PillTextAttachmentData(type: pillType, font: .preferredFont(forTextStyle: .body)))
|
||||
case mention(isOwnMention: Bool, displayText: String)
|
||||
case reference(avatar: PillImage, displayText: String)
|
||||
case undefined
|
||||
|
||||
var isOwnMention: Bool {
|
||||
switch self {
|
||||
case .mention(let isOwnMention, _):
|
||||
return isOwnMention
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var displayText: String {
|
||||
switch self {
|
||||
case .mention(_, let displayText), .reference(_, let displayText):
|
||||
return displayText
|
||||
case .undefined:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
var isUndefined: Bool {
|
||||
switch self {
|
||||
case .undefined:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var image: PillImage? {
|
||||
switch self {
|
||||
case .reference(let avatar, _):
|
||||
return avatar
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,21 @@ import Foundation
|
||||
import UIKit
|
||||
|
||||
enum PillType: Codable, Equatable {
|
||||
enum EventRoom: Codable, Equatable {
|
||||
case roomAlias(String)
|
||||
case roomID(String)
|
||||
|
||||
var value: String {
|
||||
switch self {
|
||||
case .roomAlias(let value), .roomID(let value):
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case event(room: EventRoom)
|
||||
case roomAlias(String)
|
||||
case roomID(String)
|
||||
/// A pill that mentions a user
|
||||
case user(userID: String)
|
||||
/// A pill that mentions all users in a room
|
||||
|
@ -5,6 +5,7 @@
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Compound
|
||||
import SwiftUI
|
||||
|
||||
struct PillView: View {
|
||||
@ -22,18 +23,42 @@ struct PillView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Text(context.viewState.displayText)
|
||||
.font(.compound.bodyLGSemibold)
|
||||
.foregroundColor(textColor)
|
||||
.lineLimit(1)
|
||||
.padding(.leading, 4)
|
||||
.padding(.trailing, 6)
|
||||
.padding(.vertical, 1)
|
||||
.background { Capsule().foregroundColor(backgroundColor) }
|
||||
mainContent
|
||||
.onChange(of: context.viewState.displayText) {
|
||||
didChangeText()
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var mainContent: some View {
|
||||
HStack(spacing: 4) {
|
||||
image
|
||||
Text(context.viewState.displayText)
|
||||
.font(.compound.bodyLGSemibold)
|
||||
.foregroundColor(textColor)
|
||||
.lineLimit(1)
|
||||
}
|
||||
.padding(.leading, 4)
|
||||
.padding(.trailing, 6)
|
||||
.padding(.vertical, 1)
|
||||
.background { Capsule().foregroundColor(backgroundColor) }
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var image: some View {
|
||||
if let image = context.viewState.image {
|
||||
switch image {
|
||||
case .link:
|
||||
CompoundIcon(\.link, size: .custom(12), relativeTo: .compound.bodyLGSemibold)
|
||||
.padding(2)
|
||||
.foregroundStyle(.compound.bgCanvasDefault)
|
||||
.background(.compound.textLinkExternal)
|
||||
.clipShape(Circle())
|
||||
case .roomAvatar(let avatar):
|
||||
RoomAvatarImage(avatar: avatar, avatarSize: .custom(16), mediaProvider: mediaProvider)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PillView_Previews: PreviewProvider, TestablePreview {
|
||||
@ -41,24 +66,27 @@ struct PillView_Previews: PreviewProvider, TestablePreview {
|
||||
|
||||
static var previews: some View {
|
||||
PillView(mediaProvider: mockMediaProvider,
|
||||
context: PillContext.mock(type: .loadUser(isOwn: false))) { }
|
||||
context: PillContext.mock(viewState: .mention(isOwnMention: false,
|
||||
displayText: "@Alice"))) { }
|
||||
.frame(maxWidth: PillConstants.mockMaxWidth)
|
||||
.previewDisplayName("Loading")
|
||||
.previewDisplayName("User")
|
||||
PillView(mediaProvider: mockMediaProvider,
|
||||
context: PillContext.mock(type: .loadUser(isOwn: true))) { }
|
||||
context: PillContext.mock(viewState: .mention(isOwnMention: false,
|
||||
displayText: "@Alice but with a very very long name"))) { }
|
||||
.frame(maxWidth: PillConstants.mockMaxWidth)
|
||||
.previewDisplayName("Loading Own")
|
||||
.previewDisplayName("User with a long name")
|
||||
PillView(mediaProvider: mockMediaProvider,
|
||||
context: PillContext.mock(type: .loadedUser(isOwn: false))) { }
|
||||
context: PillContext.mock(viewState: .mention(isOwnMention: true,
|
||||
displayText: "@Alice"))) { }
|
||||
.frame(maxWidth: PillConstants.mockMaxWidth)
|
||||
.previewDisplayName("Loaded Long")
|
||||
.previewDisplayName("Own user")
|
||||
PillView(mediaProvider: mockMediaProvider,
|
||||
context: PillContext.mock(type: .loadedUser(isOwn: true))) { }
|
||||
context: PillContext.mock(viewState: .reference(avatar: .roomAvatar(.room(id: "roomID", name: "Room", avatarURL: nil)), displayText: "Room"))) { }
|
||||
.frame(maxWidth: PillConstants.mockMaxWidth)
|
||||
.previewDisplayName("Loaded Long Own")
|
||||
.previewDisplayName("Room")
|
||||
PillView(mediaProvider: mockMediaProvider,
|
||||
context: PillContext.mock(type: .allUsers)) { }
|
||||
context: PillContext.mock(viewState: .reference(avatar: .link, displayText: L10n.screenRoomEventPill("Room")))) { }
|
||||
.frame(maxWidth: PillConstants.mockMaxWidth)
|
||||
.previewDisplayName("All Users")
|
||||
.previewDisplayName("Message link")
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,12 @@ import Foundation
|
||||
|
||||
// In the future we might use this to do some customisation in what is plain text used to represent mentions.
|
||||
struct PlainMentionBuilder: MentionBuilderProtocol {
|
||||
func handleEventOnRoomAliasMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, eventID: String, roomAlias: String) { }
|
||||
|
||||
func handleEventOnRoomIDMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, eventID: String, roomID: String) { }
|
||||
|
||||
func handleRoomAliasMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, roomAlias: String) { }
|
||||
|
||||
func handleAllUsersMention(for attributedString: NSMutableAttributedString, in range: NSRange) { }
|
||||
|
||||
func handleUserMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, userID: String, userDisplayName: String?) {
|
||||
@ -17,4 +23,6 @@ struct PlainMentionBuilder: MentionBuilderProtocol {
|
||||
}
|
||||
attributedString.insert(NSAttributedString(string: "@"), at: range.location)
|
||||
}
|
||||
|
||||
func handleRoomIDMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, roomID: String) { }
|
||||
}
|
||||
|
@ -193,6 +193,7 @@ private extension HomeScreenRoom {
|
||||
unreadNotificationsCount: 0,
|
||||
notificationMode: nil,
|
||||
canonicalAlias: "#footest:somewhere.org",
|
||||
alternativeAliases: [],
|
||||
hasOngoingCall: false,
|
||||
isMarkedUnread: false,
|
||||
isFavourite: false)
|
||||
@ -220,6 +221,7 @@ private extension HomeScreenRoom {
|
||||
unreadNotificationsCount: 0,
|
||||
notificationMode: nil,
|
||||
canonicalAlias: alias,
|
||||
alternativeAliases: [],
|
||||
hasOngoingCall: false,
|
||||
isMarkedUnread: false,
|
||||
isFavourite: false)
|
||||
|
@ -157,6 +157,7 @@ private extension HomeScreenRoom {
|
||||
unreadNotificationsCount: 0,
|
||||
notificationMode: nil,
|
||||
canonicalAlias: "#footest:somewhere.org",
|
||||
alternativeAliases: [],
|
||||
hasOngoingCall: false,
|
||||
isMarkedUnread: false,
|
||||
isFavourite: false)
|
||||
@ -184,6 +185,7 @@ private extension HomeScreenRoom {
|
||||
unreadNotificationsCount: 0,
|
||||
notificationMode: nil,
|
||||
canonicalAlias: alias,
|
||||
alternativeAliases: [],
|
||||
hasOngoingCall: false,
|
||||
isMarkedUnread: false,
|
||||
isFavourite: false)
|
||||
|
@ -19,6 +19,7 @@ struct MediaEventsTimelineScreenCoordinatorParameters {
|
||||
let emojiProvider: EmojiProviderProtocol
|
||||
let userIndicatorController: UserIndicatorControllerProtocol
|
||||
let timelineControllerFactory: TimelineControllerFactoryProtocol
|
||||
let clientProxy: ClientProxyProtocol
|
||||
}
|
||||
|
||||
enum MediaEventsTimelineScreenCoordinatorAction {
|
||||
@ -49,7 +50,8 @@ final class MediaEventsTimelineScreenCoordinator: CoordinatorProtocol {
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: parameters.emojiProvider,
|
||||
timelineControllerFactory: parameters.timelineControllerFactory)
|
||||
timelineControllerFactory: parameters.timelineControllerFactory,
|
||||
clientProxy: parameters.clientProxy)
|
||||
|
||||
let filesTimelineViewModel = TimelineViewModel(roomProxy: parameters.roomProxy,
|
||||
timelineController: parameters.filesTimelineController,
|
||||
@ -61,7 +63,8 @@ final class MediaEventsTimelineScreenCoordinator: CoordinatorProtocol {
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: parameters.emojiProvider,
|
||||
timelineControllerFactory: parameters.timelineControllerFactory)
|
||||
timelineControllerFactory: parameters.timelineControllerFactory,
|
||||
clientProxy: parameters.clientProxy)
|
||||
|
||||
viewModel = MediaEventsTimelineScreenViewModel(mediaTimelineViewModel: mediaTimelineViewModel,
|
||||
filesTimelineViewModel: filesTimelineViewModel,
|
||||
|
@ -274,6 +274,7 @@ struct MediaEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: ClientProxyMock(.init()))
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ struct PinnedEventsTimelineScreenCoordinatorParameters {
|
||||
let appMediator: AppMediatorProtocol
|
||||
let emojiProvider: EmojiProviderProtocol
|
||||
let timelineControllerFactory: TimelineControllerFactoryProtocol
|
||||
let clientProxy: ClientProxyProtocol
|
||||
}
|
||||
|
||||
enum PinnedEventsTimelineScreenCoordinatorAction {
|
||||
@ -53,7 +54,8 @@ final class PinnedEventsTimelineScreenCoordinator: CoordinatorProtocol {
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: parameters.emojiProvider,
|
||||
timelineControllerFactory: parameters.timelineControllerFactory)
|
||||
timelineControllerFactory: parameters.timelineControllerFactory,
|
||||
clientProxy: parameters.clientProxy)
|
||||
}
|
||||
|
||||
func start() {
|
||||
|
@ -99,7 +99,8 @@ struct PinnedEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: ClientProxyMock(.init()))
|
||||
}()
|
||||
|
||||
static var previews: some View {
|
||||
|
@ -86,7 +86,8 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
||||
appSettings: parameters.appSettings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: parameters.emojiProvider,
|
||||
timelineControllerFactory: parameters.timelineControllerFactory)
|
||||
timelineControllerFactory: parameters.timelineControllerFactory,
|
||||
clientProxy: parameters.clientProxy)
|
||||
|
||||
wysiwygViewModel = WysiwygComposerViewModel(minHeight: ComposerConstant.minHeight,
|
||||
maxCompressedHeight: ComposerConstant.maxHeight,
|
||||
|
@ -266,7 +266,8 @@ struct RoomScreen_Previews: PreviewProvider, TestablePreview {
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: ClientProxyMock(.init()))
|
||||
|
||||
static var previews: some View {
|
||||
NavigationStack {
|
||||
|
@ -41,6 +41,7 @@ class TimelineInteractionHandler {
|
||||
private let emojiProvider: EmojiProviderProtocol
|
||||
private let timelineControllerFactory: TimelineControllerFactoryProtocol
|
||||
private let pollInteractionHandler: PollInteractionHandlerProtocol
|
||||
private let clientProxy: ClientProxyProtocol
|
||||
|
||||
private let actionsSubject: PassthroughSubject<TimelineInteractionHandlerAction, Never> = .init()
|
||||
var actions: AnyPublisher<TimelineInteractionHandlerAction, Never> {
|
||||
@ -66,7 +67,8 @@ class TimelineInteractionHandler {
|
||||
appSettings: AppSettings,
|
||||
analyticsService: AnalyticsService,
|
||||
emojiProvider: EmojiProviderProtocol,
|
||||
timelineControllerFactory: TimelineControllerFactoryProtocol) {
|
||||
timelineControllerFactory: TimelineControllerFactoryProtocol,
|
||||
clientProxy: ClientProxyProtocol) {
|
||||
self.roomProxy = roomProxy
|
||||
self.timelineController = timelineController
|
||||
self.mediaProvider = mediaProvider
|
||||
@ -79,6 +81,7 @@ class TimelineInteractionHandler {
|
||||
self.analyticsService = analyticsService
|
||||
self.emojiProvider = emojiProvider
|
||||
self.timelineControllerFactory = timelineControllerFactory
|
||||
self.clientProxy = clientProxy
|
||||
pollInteractionHandler = PollInteractionHandler(analyticsService: analyticsService, roomProxy: roomProxy)
|
||||
}
|
||||
|
||||
@ -580,7 +583,8 @@ class TimelineInteractionHandler {
|
||||
appSettings: appSettings,
|
||||
analyticsService: analyticsService,
|
||||
emojiProvider: emojiProvider,
|
||||
timelineControllerFactory: timelineControllerFactory)
|
||||
timelineControllerFactory: timelineControllerFactory,
|
||||
clientProxy: clientProxy)
|
||||
|
||||
return .displayMediaPreview(item: item, timelineViewModel: .new(timelineViewModel))
|
||||
} else {
|
||||
|
@ -114,6 +114,9 @@ struct TimelineViewState: BindableState {
|
||||
/// A closure providing the associated audio player state for an item in the timeline.
|
||||
var audioPlayerStateProvider: (@MainActor (_ itemId: TimelineItemIdentifier) -> AudioPlayerState?)?
|
||||
|
||||
/// A closure that updates the associated pill context
|
||||
var pillContextUpdater: (@MainActor (PillContext) -> Void)?
|
||||
|
||||
var emojiProvider: EmojiProviderProtocol
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
||||
private let analyticsService: AnalyticsService
|
||||
private let emojiProvider: EmojiProviderProtocol
|
||||
private let timelineControllerFactory: TimelineControllerFactoryProtocol
|
||||
private let clientProxy: ClientProxyProtocol
|
||||
|
||||
private let timelineInteractionHandler: TimelineInteractionHandler
|
||||
|
||||
@ -55,7 +56,8 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
||||
appSettings: AppSettings,
|
||||
analyticsService: AnalyticsService,
|
||||
emojiProvider: EmojiProviderProtocol,
|
||||
timelineControllerFactory: TimelineControllerFactoryProtocol) {
|
||||
timelineControllerFactory: TimelineControllerFactoryProtocol,
|
||||
clientProxy: ClientProxyProtocol) {
|
||||
self.timelineController = timelineController
|
||||
self.mediaProvider = mediaProvider
|
||||
self.mediaPlayerProvider = mediaPlayerProvider
|
||||
@ -66,6 +68,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
||||
self.appMediator = appMediator
|
||||
self.emojiProvider = emojiProvider
|
||||
self.timelineControllerFactory = timelineControllerFactory
|
||||
self.clientProxy = clientProxy
|
||||
|
||||
let voiceMessageRecorder = VoiceMessageRecorder(audioRecorder: AudioRecorder(), mediaPlayerProvider: mediaPlayerProvider)
|
||||
|
||||
@ -80,7 +83,8 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
||||
appSettings: appSettings,
|
||||
analyticsService: analyticsService,
|
||||
emojiProvider: emojiProvider,
|
||||
timelineControllerFactory: timelineControllerFactory)
|
||||
timelineControllerFactory: timelineControllerFactory,
|
||||
clientProxy: clientProxy)
|
||||
|
||||
super.init(initialViewState: TimelineViewState(timelineKind: timelineController.timelineKind,
|
||||
roomID: roomProxy.id,
|
||||
@ -113,6 +117,10 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
||||
return self.timelineInteractionHandler.audioPlayerState(for: itemID)
|
||||
}
|
||||
|
||||
state.pillContextUpdater = { [weak self] pillContext in
|
||||
self?.pillContextUpdater(pillContext)
|
||||
}
|
||||
|
||||
state.timelineState.paginationState = timelineController.paginationState
|
||||
buildTimelineViews(timelineItems: timelineController.timelineItems)
|
||||
|
||||
@ -826,6 +834,72 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
||||
actionsSubject.send(.displayMessageForwarding(forwardingItem: .init(id: itemID, roomID: roomProxy.id, content: content)))
|
||||
}
|
||||
|
||||
// MARK: Pills
|
||||
|
||||
private func pillContextUpdater(_ pillContext: PillContext) {
|
||||
switch pillContext.data.type {
|
||||
case let .user(id):
|
||||
let isOwnMention = id == state.ownUserID
|
||||
if let profile = state.members[id] {
|
||||
var name = id
|
||||
if let displayName = profile.displayName {
|
||||
name = "@\(displayName)"
|
||||
}
|
||||
pillContext.viewState = .mention(isOwnMention: isOwnMention, displayText: name)
|
||||
} else {
|
||||
pillContext.viewState = .mention(isOwnMention: isOwnMention, displayText: id)
|
||||
pillContext.cancellable = context.$viewState
|
||||
.compactMap { $0.members[id] }
|
||||
.sink { [weak pillContext] profile in
|
||||
guard let pillContext else {
|
||||
return
|
||||
}
|
||||
|
||||
var name = id
|
||||
if let displayName = profile.displayName {
|
||||
name = "@\(displayName)"
|
||||
}
|
||||
pillContext.viewState = .mention(isOwnMention: isOwnMention, displayText: name)
|
||||
pillContext.cancellable = nil
|
||||
}
|
||||
}
|
||||
case .allUsers:
|
||||
pillContext.viewState = .mention(isOwnMention: true, displayText: PillConstants.atRoom)
|
||||
case .event(let room):
|
||||
var pillViewState: PillViewState = .reference(avatar: .link, displayText: L10n.screenRoomEventPill(room.value))
|
||||
defer {
|
||||
pillContext.viewState = pillViewState
|
||||
}
|
||||
|
||||
switch room {
|
||||
case .roomAlias(let alias):
|
||||
guard let roomSummary = clientProxy.roomSummaryForAlias(alias) else {
|
||||
return
|
||||
}
|
||||
// We always show the link image for event permalinks
|
||||
pillViewState = .reference(avatar: .link, displayText: L10n.screenRoomEventPill(roomSummary.name))
|
||||
case .roomID(let id):
|
||||
guard let roomSummary = clientProxy.roomSummaryForIdentifier(id) else {
|
||||
return
|
||||
}
|
||||
// We always show the link image for event permalinks
|
||||
pillViewState = .reference(avatar: .link, displayText: L10n.screenRoomEventPill(roomSummary.name))
|
||||
}
|
||||
case .roomAlias(let alias):
|
||||
guard let roomSummary = clientProxy.roomSummaryForAlias(alias) else {
|
||||
pillContext.viewState = .reference(avatar: .link, displayText: alias)
|
||||
return
|
||||
}
|
||||
pillContext.viewState = .reference(avatar: .roomAvatar(roomSummary.avatar), displayText: roomSummary.name)
|
||||
case .roomID(let id):
|
||||
guard let roomSummary = clientProxy.roomSummaryForIdentifier(id) else {
|
||||
pillContext.viewState = .reference(avatar: .link, displayText: id)
|
||||
return
|
||||
}
|
||||
pillContext.viewState = .reference(avatar: .roomAvatar(roomSummary.avatar), displayText: roomSummary.name)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - User Indicators
|
||||
|
||||
private func showFocusLoadingIndicator() {
|
||||
@ -896,7 +970,8 @@ extension TimelineViewModel {
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: ClientProxyMock(.init()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,8 @@ struct ReadReceiptsSummaryView_Previews: PreviewProvider, TestablePreview {
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: ClientProxyMock(.init()))
|
||||
return mock
|
||||
}()
|
||||
|
||||
|
@ -339,7 +339,8 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: ClientProxyMock(.init()))
|
||||
}()
|
||||
|
||||
static var previews: some View {
|
||||
|
@ -88,7 +88,8 @@ struct TimelineReadReceiptsView_Previews: PreviewProvider, TestablePreview {
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: ClientProxyMock(.init()))
|
||||
|
||||
static let singleReceipt = [ReadReceipt(userID: RoomMemberProxyMock.mockAlice.userID, formattedTimestamp: "Now")]
|
||||
static let doubleReceipt = [ReadReceipt(userID: RoomMemberProxyMock.mockAlice.userID, formattedTimestamp: "Now"),
|
||||
|
@ -142,8 +142,6 @@ struct FormattedBodyText_Previews: PreviewProvider, TestablePreview {
|
||||
let htmlStrings = [
|
||||
"""
|
||||
Plain text\n
|
||||
@bob:matrix.org\n
|
||||
#room:matrix.org\n
|
||||
!room:matrix.org\n
|
||||
https://www.matrix.org\n
|
||||
www.matrix.org\n
|
||||
|
@ -98,7 +98,8 @@ struct HighlightedTimelineItemTimeline_Previews: PreviewProvider {
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: ClientProxyMock(.init()))
|
||||
|
||||
static var previews: some View {
|
||||
NavigationStack {
|
||||
|
@ -91,7 +91,8 @@ struct TimelineView_Previews: PreviewProvider, TestablePreview {
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: ClientProxyMock(.init()))
|
||||
|
||||
static var previews: some View {
|
||||
NavigationStack {
|
||||
|
@ -516,6 +516,18 @@ class ClientProxy: ClientProxyProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func roomSummaryForIdentifier(_ identifier: String) -> RoomSummary? {
|
||||
// the alternate room summary provider is not impacted by filtering
|
||||
alternateRoomSummaryProvider?.roomListPublisher.value.first { $0.id == identifier }
|
||||
}
|
||||
|
||||
func roomSummaryForAlias(_ alias: String) -> RoomSummary? {
|
||||
// the alternate room summary provider is not impacted by filtering
|
||||
alternateRoomSummaryProvider?.roomListPublisher.value.first { roomSummary in
|
||||
roomSummary.canonicalAlias == alias || roomSummary.alternativeAliases.contains(alias)
|
||||
}
|
||||
}
|
||||
|
||||
func loadUserDisplayName() async -> Result<Void, ClientProxyError> {
|
||||
do {
|
||||
let displayName = try await client.displayName()
|
||||
|
@ -142,6 +142,10 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
|
||||
|
||||
func roomPreviewForIdentifier(_ identifier: String, via: [String]) async -> Result<RoomPreviewProxyProtocol, ClientProxyError>
|
||||
|
||||
func roomSummaryForIdentifier(_ identifier: String) -> RoomSummary?
|
||||
|
||||
func roomSummaryForAlias(_ alias: String) -> RoomSummary?
|
||||
|
||||
@discardableResult func loadUserDisplayName() async -> Result<Void, ClientProxyError>
|
||||
|
||||
func setUserDisplayName(_ name: String) async -> Result<Void, ClientProxyError>
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
/// A preview object for the Room. useful to get all the possible info for rooms to which the user is not invited to
|
||||
// sourcery: AutoMockable
|
||||
protocol RoomPreviewProxyProtocol {
|
||||
var info: RoomPreviewInfoProxy { get }
|
||||
|
@ -18,6 +18,7 @@ enum RoomProxyError: Error {
|
||||
case missingTransactionID
|
||||
}
|
||||
|
||||
/// An enum that describes the relationship between the current user and the room, and contains a reference to the specific implementation of the `RoomProxy`.
|
||||
enum RoomProxyType {
|
||||
case joined(JoinedRoomProxyProtocol)
|
||||
case invited(InvitedRoomProxyProtocol)
|
||||
|
@ -8,6 +8,7 @@
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
/// A quick summary of a Room, useful to describe and give quick informations for the room list
|
||||
struct RoomSummary {
|
||||
enum JoinRequestType {
|
||||
case invite(inviter: RoomMemberProxyProtocol?)
|
||||
@ -45,6 +46,7 @@ struct RoomSummary {
|
||||
let unreadNotificationsCount: UInt
|
||||
let notificationMode: RoomNotificationModeProxy?
|
||||
let canonicalAlias: String?
|
||||
let alternativeAliases: Set<String>
|
||||
|
||||
let hasOngoingCall: Bool
|
||||
|
||||
@ -99,6 +101,7 @@ extension RoomSummary {
|
||||
unreadNotificationsCount = hasUnreadNotifications ? 1 : 0
|
||||
notificationMode = settingsMode
|
||||
canonicalAlias = nil
|
||||
alternativeAliases = []
|
||||
hasOngoingCall = false
|
||||
|
||||
joinRequestType = nil
|
||||
|
@ -275,6 +275,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
||||
unreadNotificationsCount: UInt(roomInfo.numUnreadNotifications),
|
||||
notificationMode: notificationMode,
|
||||
canonicalAlias: roomInfo.canonicalAlias,
|
||||
alternativeAliases: .init(roomInfo.alternativeAliases),
|
||||
hasOngoingCall: roomInfo.hasRoomCall,
|
||||
isMarkedUnread: roomInfo.isMarkedUnread,
|
||||
isFavourite: roomInfo.isFavourite)
|
||||
|
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_formattedBodyText-iPad-en-GB.1.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_formattedBodyText-iPad-en-GB.1.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_formattedBodyText-iPad-pseudo.1.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_formattedBodyText-iPad-pseudo.1.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_formattedBodyText-iPhone-16-en-GB.1.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_formattedBodyText-iPhone-16-en-GB.1.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_formattedBodyText-iPhone-16-pseudo.1.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_formattedBodyText-iPhone-16-pseudo.1.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_globalSearchScreen-iPad-en-GB.1.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_globalSearchScreen-iPad-en-GB.1.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_globalSearchScreen-iPad-pseudo.1.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_globalSearchScreen-iPad-pseudo.1.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_globalSearchScreen-iPhone-16-en-GB.1.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_globalSearchScreen-iPhone-16-en-GB.1.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_globalSearchScreen-iPhone-16-pseudo.1.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_globalSearchScreen-iPhone-16-pseudo.1.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageForwardingScreen-iPad-en-GB.1.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageForwardingScreen-iPad-en-GB.1.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageForwardingScreen-iPad-pseudo.1.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageForwardingScreen-iPad-pseudo.1.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageForwardingScreen-iPhone-16-en-GB.1.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageForwardingScreen-iPhone-16-en-GB.1.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageForwardingScreen-iPhone-16-pseudo.1.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageForwardingScreen-iPhone-16-pseudo.1.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageText-iPad-en-GB.Custom-Attachment.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageText-iPad-en-GB.Custom-Attachment.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageText-iPad-pseudo.Custom-Attachment.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageText-iPad-pseudo.Custom-Attachment.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageText-iPhone-16-en-GB.Custom-Attachment.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageText-iPhone-16-en-GB.Custom-Attachment.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageText-iPhone-16-pseudo.Custom-Attachment.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageText-iPhone-16-pseudo.Custom-Attachment.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.All-Users.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.All-Users.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Loaded-Long-Own.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Loaded-Long-Own.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Loaded-Long.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Loaded-Long.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Loading-Own.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Loading-Own.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Loading.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Loading.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Message-link.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Message-link.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Own-user.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Own-user.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Room.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Room.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.User-with-a-long-name.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.User-with-a-long-name.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.User.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.User.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.All-Users.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.All-Users.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Loaded-Long-Own.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Loaded-Long-Own.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Loaded-Long.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Loaded-Long.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Loading-Own.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Loading-Own.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Loading.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Loading.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Message-link.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Message-link.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Own-user.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Own-user.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Room.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Room.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.User-with-a-long-name.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.User-with-a-long-name.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.User.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.User.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.All-Users.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.All-Users.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Loaded-Long-Own.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Loaded-Long-Own.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Loaded-Long.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Loaded-Long.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Loading-Own.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Loading-Own.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Loading.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Loading.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Message-link.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Message-link.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Own-user.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Own-user.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Room.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Room.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.User-with-a-long-name.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.User-with-a-long-name.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.User.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.User.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.All-Users.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.All-Users.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Loaded-Long-Own.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Loaded-Long-Own.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Loaded-Long.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Loaded-Long.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Loading-Own.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Loading-Own.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Loading.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Loading.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Message-link.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Message-link.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Own-user.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Own-user.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Room.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Room.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.User-with-a-long-name.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.User-with-a-long-name.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.User.png
(Stored with Git LFS)
Normal file
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.User.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_roomSelectionScreen-iPad-en-GB.1.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_roomSelectionScreen-iPad-en-GB.1.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_roomSelectionScreen-iPad-pseudo.1.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_roomSelectionScreen-iPad-pseudo.1.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_roomSelectionScreen-iPhone-16-en-GB.1.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_roomSelectionScreen-iPhone-16-en-GB.1.png
(Stored with Git LFS)
Binary file not shown.
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_roomSelectionScreen-iPhone-16-pseudo.1.png
(Stored with Git LFS)
BIN
PreviewTests/Sources/__Snapshots__/PreviewTests/test_roomSelectionScreen-iPhone-16-pseudo.1.png
(Stored with Git LFS)
Binary file not shown.
@ -425,14 +425,88 @@ class AttributedStringBuilderTests: XCTestCase {
|
||||
XCTAssertEqual(numberOfBlockquotes, 3, "Couldn't find all the blockquotes")
|
||||
}
|
||||
|
||||
func testUserMentionAtachment() {
|
||||
func testUserPermalinkMentionAtachment() {
|
||||
let string = "https://matrix.to/#/@test:matrix.org"
|
||||
let attributedStringFromHTML = attributedStringBuilder.fromHTML(string)
|
||||
XCTAssertNotNil(attributedStringFromHTML?.attachment)
|
||||
XCTAssertNotNil(attributedStringFromHTML?.link)
|
||||
XCTAssertEqual(attributedStringFromHTML?.userID, "@test:matrix.org")
|
||||
XCTAssertEqual(attributedStringFromHTML?.link?.absoluteString, string)
|
||||
let attributedStringFromPlain = attributedStringBuilder.fromPlain(string)
|
||||
XCTAssertNotNil(attributedStringFromPlain?.attachment)
|
||||
XCTAssertNotNil(attributedStringFromHTML?.link)
|
||||
XCTAssertEqual(attributedStringFromPlain?.userID, "@test:matrix.org")
|
||||
XCTAssertEqual(attributedStringFromPlain?.link?.absoluteString, string)
|
||||
}
|
||||
|
||||
func testUserIDMentionAtachment() {
|
||||
let string = "@test:matrix.org"
|
||||
let attributedStringFromHTML = attributedStringBuilder.fromHTML(string)
|
||||
XCTAssertNotNil(attributedStringFromHTML?.attachment)
|
||||
XCTAssertEqual(attributedStringFromHTML?.userID, "@test:matrix.org")
|
||||
XCTAssertEqual(attributedStringFromHTML?.link?.absoluteString, "https://matrix.to/#/@test:matrix.org")
|
||||
let attributedStringFromPlain = attributedStringBuilder.fromPlain(string)
|
||||
XCTAssertNotNil(attributedStringFromPlain?.attachment)
|
||||
XCTAssertEqual(attributedStringFromPlain?.userID, "@test:matrix.org")
|
||||
XCTAssertEqual(attributedStringFromPlain?.link?.absoluteString, "https://matrix.to/#/@test:matrix.org")
|
||||
}
|
||||
|
||||
func testRoomIDPermalinkMentionAttachment() {
|
||||
let string = "https://matrix.to/#/!test:matrix.org"
|
||||
let attributedStringFromHTML = attributedStringBuilder.fromHTML(string)
|
||||
XCTAssertNotNil(attributedStringFromHTML?.attachment)
|
||||
XCTAssertEqual(attributedStringFromHTML?.roomID, "!test:matrix.org")
|
||||
XCTAssertEqual(attributedStringFromHTML?.link?.absoluteString, string)
|
||||
let attributedStringFromPlain = attributedStringBuilder.fromPlain(string)
|
||||
XCTAssertNotNil(attributedStringFromPlain?.attachment)
|
||||
XCTAssertEqual(attributedStringFromHTML?.roomID, "!test:matrix.org")
|
||||
XCTAssertEqual(attributedStringFromPlain?.link?.absoluteString, string)
|
||||
}
|
||||
|
||||
func testRoomAliasPermalinkMentionAttachment() {
|
||||
let string = "https://matrix.to/#/#test:matrix.org"
|
||||
let attributedStringFromHTML = attributedStringBuilder.fromHTML(string)
|
||||
XCTAssertNotNil(attributedStringFromHTML?.attachment)
|
||||
XCTAssertEqual(attributedStringFromHTML?.roomAlias, "#test:matrix.org")
|
||||
XCTAssertEqual(attributedStringFromHTML?.link?.absoluteString, "https://matrix.to/#/%23test:matrix.org")
|
||||
let attributedStringFromPlain = attributedStringBuilder.fromPlain(string)
|
||||
XCTAssertNotNil(attributedStringFromPlain?.attachment)
|
||||
XCTAssertEqual(attributedStringFromHTML?.roomAlias, "#test:matrix.org")
|
||||
XCTAssertEqual(attributedStringFromPlain?.link?.absoluteString, "https://matrix.to/#/%23test:matrix.org")
|
||||
}
|
||||
|
||||
func testRoomAliasMentionAttachment() {
|
||||
let string = "#test:matrix.org"
|
||||
let attributedStringFromHTML = attributedStringBuilder.fromHTML(string)
|
||||
XCTAssertNotNil(attributedStringFromHTML?.attachment)
|
||||
XCTAssertEqual(attributedStringFromHTML?.roomAlias, "#test:matrix.org")
|
||||
XCTAssertEqual(attributedStringFromHTML?.link?.absoluteString, "https://matrix.to/#/%23test:matrix.org")
|
||||
let attributedStringFromPlain = attributedStringBuilder.fromPlain(string)
|
||||
XCTAssertNotNil(attributedStringFromPlain?.attachment)
|
||||
XCTAssertEqual(attributedStringFromHTML?.roomAlias, "#test:matrix.org")
|
||||
XCTAssertEqual(attributedStringFromPlain?.link?.absoluteString, "https://matrix.to/#/%23test:matrix.org")
|
||||
}
|
||||
|
||||
func testEventRoomIDPermalinkMentionAttachment() {
|
||||
let string = "https://matrix.to/#/!test:matrix.org/$test"
|
||||
let attributedStringFromHTML = attributedStringBuilder.fromHTML(string)
|
||||
XCTAssertNotNil(attributedStringFromHTML?.attachment)
|
||||
XCTAssertEqual(attributedStringFromHTML?.eventOnRoomID, .some(.init(roomID: "!test:matrix.org", eventID: "$test")))
|
||||
XCTAssertEqual(attributedStringFromHTML?.link?.absoluteString, string)
|
||||
let attributedStringFromPlain = attributedStringBuilder.fromPlain(string)
|
||||
XCTAssertNotNil(attributedStringFromPlain?.attachment)
|
||||
XCTAssertEqual(attributedStringFromPlain?.eventOnRoomID, .some(.init(roomID: "!test:matrix.org", eventID: "$test")))
|
||||
XCTAssertEqual(attributedStringFromPlain?.link?.absoluteString, string)
|
||||
}
|
||||
|
||||
func testEventRoomAliasPermalinkMentionAttachment() {
|
||||
let string = "https://matrix.to/#/#test:matrix.org/$test"
|
||||
let attributedStringFromHTML = attributedStringBuilder.fromHTML(string)
|
||||
XCTAssertNotNil(attributedStringFromHTML?.attachment)
|
||||
XCTAssertEqual(attributedStringFromHTML?.eventOnRoomAlias, .some(.init(alias: "#test:matrix.org", eventID: "$test")))
|
||||
XCTAssertEqual(attributedStringFromHTML?.link?.absoluteString, "https://matrix.to/#/%23test:matrix.org/$test")
|
||||
let attributedStringFromPlain = attributedStringBuilder.fromPlain(string)
|
||||
XCTAssertNotNil(attributedStringFromPlain?.attachment)
|
||||
XCTAssertEqual(attributedStringFromPlain?.eventOnRoomAlias, .some(.init(alias: "#test:matrix.org", eventID: "$test")))
|
||||
XCTAssertEqual(attributedStringFromPlain?.link?.absoluteString, "https://matrix.to/#/%23test:matrix.org/$test")
|
||||
}
|
||||
|
||||
func testUserMentionAtachmentInBlockQuotes() {
|
||||
|
@ -34,6 +34,7 @@ class HomeScreenRoomTests: XCTestCase {
|
||||
unreadNotificationsCount: unreadNotificationsCount,
|
||||
notificationMode: notificationMode,
|
||||
canonicalAlias: nil,
|
||||
alternativeAliases: [],
|
||||
hasOngoingCall: hasOngoingCall,
|
||||
isMarkedUnread: isMarkedUnread,
|
||||
isFavourite: false)
|
||||
|
@ -92,6 +92,7 @@ class LoggingTests: XCTestCase {
|
||||
unreadNotificationsCount: 0,
|
||||
notificationMode: nil,
|
||||
canonicalAlias: nil,
|
||||
alternativeAliases: [],
|
||||
hasOngoingCall: false,
|
||||
isMarkedUnread: false,
|
||||
isFavourite: false)
|
||||
|
@ -12,7 +12,7 @@ import XCTest
|
||||
|
||||
@MainActor
|
||||
class PillContextTests: XCTestCase {
|
||||
func testUser() async throws {
|
||||
func testUser() async {
|
||||
let id = "@test:matrix.org"
|
||||
let proxyMock = JoinedRoomProxyMock(.init(name: "Test"))
|
||||
let subject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([])
|
||||
@ -27,7 +27,8 @@ class PillContextTests: XCTestCase {
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: ClientProxyMock(.init()))
|
||||
let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .user(userID: id), font: .preferredFont(forTextStyle: .body)))
|
||||
|
||||
XCTAssertFalse(context.viewState.isOwnMention)
|
||||
@ -39,10 +40,11 @@ class PillContextTests: XCTestCase {
|
||||
await Task.yield()
|
||||
|
||||
XCTAssertFalse(context.viewState.isOwnMention)
|
||||
XCTAssertNil(context.viewState.image)
|
||||
XCTAssertEqual(context.viewState.displayText, "@\(name)")
|
||||
}
|
||||
|
||||
func testOwnUser() async throws {
|
||||
func testOwnUser() {
|
||||
let id = "@test:matrix.org"
|
||||
let proxyMock = JoinedRoomProxyMock(.init(name: "Test", ownUserID: id))
|
||||
let subject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([])
|
||||
@ -57,13 +59,15 @@ class PillContextTests: XCTestCase {
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: ClientProxyMock(.init()))
|
||||
let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .user(userID: id), font: .preferredFont(forTextStyle: .body)))
|
||||
|
||||
XCTAssertNil(context.viewState.image)
|
||||
XCTAssertTrue(context.viewState.isOwnMention)
|
||||
}
|
||||
|
||||
func testAllUsers() async throws {
|
||||
func testAllUsers() {
|
||||
let avatarURL = URL(string: "https://matrix.jpg")
|
||||
let id = "test_room"
|
||||
let displayName = "Test"
|
||||
@ -80,10 +84,216 @@ class PillContextTests: XCTestCase {
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: ClientProxyMock(.init()))
|
||||
let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .allUsers, font: .preferredFont(forTextStyle: .body)))
|
||||
|
||||
XCTAssertTrue(context.viewState.isOwnMention)
|
||||
XCTAssertNil(context.viewState.image)
|
||||
XCTAssertEqual(context.viewState.displayText, PillConstants.atRoom)
|
||||
}
|
||||
|
||||
func testRoomIDMention() {
|
||||
let proxyMock = JoinedRoomProxyMock(.init())
|
||||
let mockController = MockTimelineController()
|
||||
let clientMock = ClientProxyMock(.init())
|
||||
clientMock.roomSummaryForIdentifierReturnValue = .mock(id: "1", name: "Foundation 🔭🪐🌌")
|
||||
mockController.roomProxy = proxyMock
|
||||
let mock = TimelineViewModel(roomProxy: proxyMock,
|
||||
timelineController: mockController,
|
||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: clientMock)
|
||||
let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .roomID("1"), font: .preferredFont(forTextStyle: .body)))
|
||||
|
||||
XCTAssertFalse(context.viewState.isOwnMention)
|
||||
XCTAssertFalse(context.viewState.isUndefined)
|
||||
XCTAssertEqual(context.viewState.image, .roomAvatar(.room(id: "1", name: "Foundation 🔭🪐🌌", avatarURL: nil)))
|
||||
XCTAssertEqual(context.viewState.displayText, "Foundation 🔭🪐🌌")
|
||||
}
|
||||
|
||||
func testRoomIDMentionMissingRoom() {
|
||||
let proxyMock = JoinedRoomProxyMock(.init())
|
||||
let mockController = MockTimelineController()
|
||||
mockController.roomProxy = proxyMock
|
||||
let mock = TimelineViewModel(roomProxy: proxyMock,
|
||||
timelineController: mockController,
|
||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: ClientProxyMock(.init()))
|
||||
let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .roomID("1"), font: .preferredFont(forTextStyle: .body)))
|
||||
|
||||
XCTAssertFalse(context.viewState.isOwnMention)
|
||||
XCTAssertFalse(context.viewState.isUndefined)
|
||||
XCTAssertEqual(context.viewState.image, .link)
|
||||
XCTAssertEqual(context.viewState.displayText, "1")
|
||||
}
|
||||
|
||||
func testRoomAliasMention() {
|
||||
let proxyMock = JoinedRoomProxyMock(.init())
|
||||
let mockController = MockTimelineController()
|
||||
mockController.roomProxy = proxyMock
|
||||
let clientMock = ClientProxyMock(.init())
|
||||
clientMock.roomSummaryForAliasReturnValue = .mock(id: "2",
|
||||
name: "Foundation and Empire",
|
||||
canonicalAlias: "#foundation-and-empire:matrix.org")
|
||||
let mock = TimelineViewModel(roomProxy: proxyMock,
|
||||
timelineController: mockController,
|
||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: clientMock)
|
||||
let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .roomAlias("#foundation-and-empire:matrix.org"), font: .preferredFont(forTextStyle: .body)))
|
||||
|
||||
XCTAssertFalse(context.viewState.isOwnMention)
|
||||
XCTAssertFalse(context.viewState.isUndefined)
|
||||
XCTAssertEqual(context.viewState.image, .roomAvatar(.room(id: "2", name: "Foundation and Empire", avatarURL: nil)))
|
||||
XCTAssertEqual(context.viewState.displayText, "Foundation and Empire")
|
||||
}
|
||||
|
||||
func testRoomAliasMentionMissingRoom() {
|
||||
let proxyMock = JoinedRoomProxyMock(.init())
|
||||
let mockController = MockTimelineController()
|
||||
mockController.roomProxy = proxyMock
|
||||
let mock = TimelineViewModel(roomProxy: proxyMock,
|
||||
timelineController: mockController,
|
||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: ClientProxyMock(.init()))
|
||||
let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .roomAlias("#foundation-and-empire:matrix.org"), font: .preferredFont(forTextStyle: .body)))
|
||||
|
||||
XCTAssertFalse(context.viewState.isOwnMention)
|
||||
XCTAssertFalse(context.viewState.isUndefined)
|
||||
XCTAssertEqual(context.viewState.image, .link)
|
||||
XCTAssertEqual(context.viewState.displayText, "#foundation-and-empire:matrix.org")
|
||||
}
|
||||
|
||||
func testEventOnRoomIDMention() {
|
||||
let proxyMock = JoinedRoomProxyMock(.init())
|
||||
let mockController = MockTimelineController()
|
||||
mockController.roomProxy = proxyMock
|
||||
let clientMock = ClientProxyMock(.init())
|
||||
clientMock.roomSummaryForIdentifierReturnValue = .mock(id: "1", name: "Foundation 🔭🪐🌌")
|
||||
let mock = TimelineViewModel(roomProxy: proxyMock,
|
||||
timelineController: mockController,
|
||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: clientMock)
|
||||
let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .event(room: .roomID("1")), font: .preferredFont(forTextStyle: .body)))
|
||||
|
||||
XCTAssertFalse(context.viewState.isOwnMention)
|
||||
XCTAssertFalse(context.viewState.isUndefined)
|
||||
XCTAssertEqual(context.viewState.image, .link)
|
||||
XCTAssertEqual(context.viewState.displayText, L10n.screenRoomEventPill("Foundation 🔭🪐🌌"))
|
||||
}
|
||||
|
||||
func testEventOnRoomIDMentionMissingRoom() {
|
||||
let proxyMock = JoinedRoomProxyMock(.init())
|
||||
let mockController = MockTimelineController()
|
||||
mockController.roomProxy = proxyMock
|
||||
let mock = TimelineViewModel(roomProxy: proxyMock,
|
||||
timelineController: mockController,
|
||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: ClientProxyMock(.init()))
|
||||
let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .event(room: .roomID("1")), font: .preferredFont(forTextStyle: .body)))
|
||||
|
||||
XCTAssertFalse(context.viewState.isOwnMention)
|
||||
XCTAssertFalse(context.viewState.isUndefined)
|
||||
XCTAssertEqual(context.viewState.image, .link)
|
||||
XCTAssertEqual(context.viewState.displayText, L10n.screenRoomEventPill("1"))
|
||||
}
|
||||
|
||||
func testEventOnRoomAliasMention() async throws {
|
||||
let proxyMock = JoinedRoomProxyMock(.init())
|
||||
let mockController = MockTimelineController()
|
||||
mockController.roomProxy = proxyMock
|
||||
let clientMock = ClientProxyMock(.init())
|
||||
clientMock.roomSummaryForAliasReturnValue = .mock(id: "2",
|
||||
name: "Foundation and Empire",
|
||||
canonicalAlias: "#foundation-and-empire:matrix.org")
|
||||
let mock = TimelineViewModel(roomProxy: proxyMock,
|
||||
timelineController: mockController,
|
||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: clientMock)
|
||||
let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .event(room: .roomAlias("#foundation-and-empire:matrix.org")), font: .preferredFont(forTextStyle: .body)))
|
||||
|
||||
XCTAssertFalse(context.viewState.isOwnMention)
|
||||
XCTAssertFalse(context.viewState.isUndefined)
|
||||
XCTAssertEqual(context.viewState.image, .link)
|
||||
XCTAssertEqual(context.viewState.displayText, L10n.screenRoomEventPill("Foundation and Empire"))
|
||||
}
|
||||
|
||||
func testEventOnRoomAliasMentionMissingRoom() async throws {
|
||||
let proxyMock = JoinedRoomProxyMock(.init())
|
||||
let mockController = MockTimelineController()
|
||||
mockController.roomProxy = proxyMock
|
||||
let mock = TimelineViewModel(roomProxy: proxyMock,
|
||||
timelineController: mockController,
|
||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: ClientProxyMock(.init()))
|
||||
let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .event(room: .roomAlias("#foundation-and-empire:matrix.org")), font: .preferredFont(forTextStyle: .body)))
|
||||
|
||||
XCTAssertFalse(context.viewState.isOwnMention)
|
||||
XCTAssertFalse(context.viewState.isUndefined)
|
||||
XCTAssertEqual(context.viewState.image, .link)
|
||||
XCTAssertEqual(context.viewState.displayText, L10n.screenRoomEventPill("#foundation-and-empire:matrix.org"))
|
||||
}
|
||||
}
|
||||
|
@ -68,6 +68,7 @@ class RoomSummaryTests: XCTestCase {
|
||||
unreadNotificationsCount: 0,
|
||||
notificationMode: nil,
|
||||
canonicalAlias: nil,
|
||||
alternativeAliases: [],
|
||||
hasOngoingCall: false,
|
||||
isMarkedUnread: false,
|
||||
isFavourite: false)
|
||||
|
@ -312,7 +312,8 @@ class TimelineViewModelTests: XCTestCase {
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: ClientProxyMock(.init()))
|
||||
return (viewModel, roomProxy, timelineProxy, timelineController)
|
||||
}
|
||||
|
||||
@ -338,7 +339,8 @@ class TimelineViewModelTests: XCTestCase {
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: ClientProxyMock(.init()))
|
||||
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { value in
|
||||
value.bindings.readReceiptsSummaryInfo?.orderedReceipts == receipts
|
||||
@ -367,7 +369,8 @@ class TimelineViewModelTests: XCTestCase {
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: ClientProxyMock(.init()))
|
||||
XCTAssertEqual(configuration.pinnedEventIDs, viewModel.context.viewState.pinnedEventIDs)
|
||||
|
||||
configuration.pinnedEventIDs = ["test1", "test2"]
|
||||
@ -394,7 +397,8 @@ class TimelineViewModelTests: XCTestCase {
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: ClientProxyMock(.init()))
|
||||
|
||||
var deferred = deferFulfillment(viewModel.context.$viewState) { value in
|
||||
value.canCurrentUserPin
|
||||
@ -425,7 +429,8 @@ class TimelineViewModelTests: XCTestCase {
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
clientProxy: ClientProxyMock(.init()))
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user