mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 21:39:12 +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,
|
appMediator: appMediator,
|
||||||
emojiProvider: emojiProvider,
|
emojiProvider: emojiProvider,
|
||||||
userIndicatorController: userIndicatorController,
|
userIndicatorController: userIndicatorController,
|
||||||
timelineControllerFactory: timelineControllerFactory)
|
timelineControllerFactory: timelineControllerFactory,
|
||||||
|
clientProxy: userSession.clientProxy)
|
||||||
|
|
||||||
let coordinator = MediaEventsTimelineScreenCoordinator(parameters: parameters)
|
let coordinator = MediaEventsTimelineScreenCoordinator(parameters: parameters)
|
||||||
|
|
||||||
|
@ -78,7 +78,8 @@ class PinnedEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
voiceMessageMediaManager: userSession.voiceMessageMediaManager,
|
voiceMessageMediaManager: userSession.voiceMessageMediaManager,
|
||||||
appMediator: appMediator,
|
appMediator: appMediator,
|
||||||
emojiProvider: emojiProvider,
|
emojiProvider: emojiProvider,
|
||||||
timelineControllerFactory: timelineControllerFactory))
|
timelineControllerFactory: timelineControllerFactory,
|
||||||
|
clientProxy: userSession.clientProxy))
|
||||||
|
|
||||||
coordinator.actions
|
coordinator.actions
|
||||||
.sink { [weak self] action in
|
.sink { [weak self] action in
|
||||||
|
@ -3225,6 +3225,146 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable {
|
|||||||
return roomPreviewForIdentifierViaReturnValue
|
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
|
//MARK: - loadUserDisplayName
|
||||||
|
|
||||||
var loadUserDisplayNameUnderlyingCallsCount = 0
|
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 {
|
extension Array where Element == RoomSummary {
|
||||||
static let mockRooms: [Element] = [
|
static let mockRooms: [Element] = [
|
||||||
RoomSummary(roomListItem: RoomListItemSDKMock(),
|
RoomSummary(roomListItem: RoomListItemSDKMock(),
|
||||||
@ -83,6 +108,7 @@ extension Array where Element == RoomSummary {
|
|||||||
unreadNotificationsCount: 0,
|
unreadNotificationsCount: 0,
|
||||||
notificationMode: .allMessages,
|
notificationMode: .allMessages,
|
||||||
canonicalAlias: nil,
|
canonicalAlias: nil,
|
||||||
|
alternativeAliases: [],
|
||||||
hasOngoingCall: false,
|
hasOngoingCall: false,
|
||||||
isMarkedUnread: false,
|
isMarkedUnread: false,
|
||||||
isFavourite: false),
|
isFavourite: false),
|
||||||
@ -99,7 +125,8 @@ extension Array where Element == RoomSummary {
|
|||||||
unreadMentionsCount: 0,
|
unreadMentionsCount: 0,
|
||||||
unreadNotificationsCount: 2,
|
unreadNotificationsCount: 2,
|
||||||
notificationMode: .mute,
|
notificationMode: .mute,
|
||||||
canonicalAlias: nil,
|
canonicalAlias: "#foundation-and-empire:matrix.org",
|
||||||
|
alternativeAliases: [],
|
||||||
hasOngoingCall: false,
|
hasOngoingCall: false,
|
||||||
isMarkedUnread: false,
|
isMarkedUnread: false,
|
||||||
isFavourite: false),
|
isFavourite: false),
|
||||||
@ -117,6 +144,7 @@ extension Array where Element == RoomSummary {
|
|||||||
unreadNotificationsCount: 0,
|
unreadNotificationsCount: 0,
|
||||||
notificationMode: .mentionsAndKeywordsOnly,
|
notificationMode: .mentionsAndKeywordsOnly,
|
||||||
canonicalAlias: nil,
|
canonicalAlias: nil,
|
||||||
|
alternativeAliases: [],
|
||||||
hasOngoingCall: false,
|
hasOngoingCall: false,
|
||||||
isMarkedUnread: false,
|
isMarkedUnread: false,
|
||||||
isFavourite: false),
|
isFavourite: false),
|
||||||
@ -134,6 +162,7 @@ extension Array where Element == RoomSummary {
|
|||||||
unreadNotificationsCount: 2,
|
unreadNotificationsCount: 2,
|
||||||
notificationMode: .allMessages,
|
notificationMode: .allMessages,
|
||||||
canonicalAlias: nil,
|
canonicalAlias: nil,
|
||||||
|
alternativeAliases: [],
|
||||||
hasOngoingCall: false,
|
hasOngoingCall: false,
|
||||||
isMarkedUnread: false,
|
isMarkedUnread: false,
|
||||||
isFavourite: false),
|
isFavourite: false),
|
||||||
@ -151,6 +180,7 @@ extension Array where Element == RoomSummary {
|
|||||||
unreadNotificationsCount: 1,
|
unreadNotificationsCount: 1,
|
||||||
notificationMode: .allMessages,
|
notificationMode: .allMessages,
|
||||||
canonicalAlias: nil,
|
canonicalAlias: nil,
|
||||||
|
alternativeAliases: [],
|
||||||
hasOngoingCall: true,
|
hasOngoingCall: true,
|
||||||
isMarkedUnread: false,
|
isMarkedUnread: false,
|
||||||
isFavourite: false),
|
isFavourite: false),
|
||||||
@ -168,6 +198,7 @@ extension Array where Element == RoomSummary {
|
|||||||
unreadNotificationsCount: 0,
|
unreadNotificationsCount: 0,
|
||||||
notificationMode: .mute,
|
notificationMode: .mute,
|
||||||
canonicalAlias: nil,
|
canonicalAlias: nil,
|
||||||
|
alternativeAliases: [],
|
||||||
hasOngoingCall: true,
|
hasOngoingCall: true,
|
||||||
isMarkedUnread: false,
|
isMarkedUnread: false,
|
||||||
isFavourite: false),
|
isFavourite: false),
|
||||||
@ -185,6 +216,7 @@ extension Array where Element == RoomSummary {
|
|||||||
unreadNotificationsCount: 0,
|
unreadNotificationsCount: 0,
|
||||||
notificationMode: nil,
|
notificationMode: nil,
|
||||||
canonicalAlias: nil,
|
canonicalAlias: nil,
|
||||||
|
alternativeAliases: [],
|
||||||
hasOngoingCall: false,
|
hasOngoingCall: false,
|
||||||
isMarkedUnread: false,
|
isMarkedUnread: false,
|
||||||
isFavourite: false)
|
isFavourite: false)
|
||||||
@ -235,6 +267,7 @@ extension Array where Element == RoomSummary {
|
|||||||
unreadNotificationsCount: 0,
|
unreadNotificationsCount: 0,
|
||||||
notificationMode: nil,
|
notificationMode: nil,
|
||||||
canonicalAlias: "#footest:somewhere.org",
|
canonicalAlias: "#footest:somewhere.org",
|
||||||
|
alternativeAliases: [],
|
||||||
hasOngoingCall: false,
|
hasOngoingCall: false,
|
||||||
isMarkedUnread: false,
|
isMarkedUnread: false,
|
||||||
isFavourite: false),
|
isFavourite: false),
|
||||||
@ -252,6 +285,7 @@ extension Array where Element == RoomSummary {
|
|||||||
unreadNotificationsCount: 0,
|
unreadNotificationsCount: 0,
|
||||||
notificationMode: nil,
|
notificationMode: nil,
|
||||||
canonicalAlias: nil,
|
canonicalAlias: nil,
|
||||||
|
alternativeAliases: [],
|
||||||
hasOngoingCall: false,
|
hasOngoingCall: false,
|
||||||
isMarkedUnread: false,
|
isMarkedUnread: false,
|
||||||
isFavourite: false)
|
isFavourite: false)
|
||||||
|
@ -224,7 +224,8 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
|||||||
case .atRoom:
|
case .atRoom:
|
||||||
attributedString.addAttribute(.MatrixAllUsersMention, value: true, range: match.range)
|
attributedString.addAttribute(.MatrixAllUsersMention, value: true, range: match.range)
|
||||||
case .roomAlias(let alias):
|
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)
|
attributedString.addAttribute(.link, value: url, range: match.range)
|
||||||
}
|
}
|
||||||
case .matrixURI(let uri):
|
case .matrixURI(let uri):
|
||||||
@ -282,13 +283,13 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
|||||||
case .user(let userID):
|
case .user(let userID):
|
||||||
mentionBuilder.handleUserMention(for: attributedString, in: range, url: url, userID: userID, userDisplayName: nil)
|
mentionBuilder.handleUserMention(for: attributedString, in: range, url: url, userID: userID, userDisplayName: nil)
|
||||||
case .room(let roomID):
|
case .room(let roomID):
|
||||||
attributedString.addAttributes([.MatrixRoomID: roomID], range: range)
|
mentionBuilder.handleRoomIDMention(for: attributedString, in: range, url: url, roomID: roomID)
|
||||||
case .roomAlias(let alias):
|
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):
|
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):
|
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 {
|
protocol MentionBuilderProtocol {
|
||||||
func handleUserMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, userID: String, userDisplayName: String?)
|
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)
|
func handleAllUsersMention(for attributedString: NSMutableAttributedString, in range: NSRange)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,13 +9,20 @@ import Foundation
|
|||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
struct MentionBuilder: MentionBuilderProtocol {
|
struct MentionBuilder: MentionBuilderProtocol {
|
||||||
func handleUserMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, userID: String, userDisplayName: String?) {
|
struct AttributesToRestore {
|
||||||
let attributes = attributedString.attributes(at: 0, longestEffectiveRange: nil, in: range)
|
let font: UIFont
|
||||||
let font = attributes[.font] as? UIFont ?? .preferredFont(forTextStyle: .body)
|
let blockquote: Bool?
|
||||||
let blockquote = attributes[.MatrixBlockquote]
|
let foregroundColor: UIColor
|
||||||
let foregroundColor = attributes[.foregroundColor] as? UIColor ?? .compound.textPrimary
|
}
|
||||||
|
|
||||||
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 {
|
guard let attachment = PillTextAttachment(attachmentData: attachmentData) else {
|
||||||
attributedString.addAttribute(.MatrixUserID, value: userID, range: range)
|
attributedString.addAttribute(.MatrixUserID, value: userID, range: range)
|
||||||
|
|
||||||
@ -26,34 +33,151 @@ struct MentionBuilder: MentionBuilderProtocol {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var attachmentAttributes: [NSAttributedString.Key: Any] = [.link: url, .MatrixUserID: userID, .font: font, .foregroundColor: foregroundColor]
|
var attachmentAttributes: [NSAttributedString.Key: Any] = [.link: url,
|
||||||
if let blockquote {
|
.MatrixUserID: userID,
|
||||||
// mentions can be in blockquotes, so if the replaced string was in one, we keep the attribute
|
.font: attributesToRestore.font,
|
||||||
attachmentAttributes[.MatrixBlockquote] = blockquote
|
.foregroundColor: attributesToRestore.foregroundColor]
|
||||||
}
|
attachmentAttributes.addBlockquoteIfNeeded(attributesToRestore.blockquote)
|
||||||
let attachmentString = NSMutableAttributedString(attachment: attachment)
|
attachmentAttributes.addMatrixUsernameIfNeeded(userDisplayName)
|
||||||
attachmentString.addAttributes(attachmentAttributes, range: NSRange(location: 0, length: attachmentString.length))
|
|
||||||
attributedString.replaceCharacters(in: range, with: attachmentString)
|
setPillAttachment(attachment: attachment,
|
||||||
|
attributedString: attributedString,
|
||||||
|
in: range,
|
||||||
|
with: attachmentAttributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleAllUsersMention(for attributedString: NSMutableAttributedString, in range: NSRange) {
|
func handleAllUsersMention(for attributedString: NSMutableAttributedString, in range: NSRange) {
|
||||||
let attributes = attributedString.attributes(at: 0, longestEffectiveRange: nil, in: range)
|
let attributesToRestore = getAttributesToRestore(for: attributedString, in: range)
|
||||||
let font = attributes[.font] as? UIFont ?? .preferredFont(forTextStyle: .body)
|
|
||||||
let blockquote = attributes[.MatrixBlockquote]
|
|
||||||
let foregroundColor = attributes[.foregroundColor] as? UIColor ?? .compound.textPrimary
|
|
||||||
|
|
||||||
let attachmentData = PillTextAttachmentData(type: .allUsers, font: font)
|
let attachmentData = PillTextAttachmentData(type: .allUsers, font: attributesToRestore.font)
|
||||||
guard let attachment = PillTextAttachment(attachmentData: attachmentData) else {
|
guard let attachment = PillTextAttachment(attachmentData: attachmentData) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var attachmentAttributes: [NSAttributedString.Key: Any] = [.font: font, .MatrixAllUsersMention: true, .foregroundColor: foregroundColor]
|
var attachmentAttributes: [NSAttributedString.Key: Any] = [.font: attributesToRestore.font,
|
||||||
if let blockquote {
|
.MatrixAllUsersMention: true,
|
||||||
// mentions can be in blockquotes, so if the replaced string was in one, we keep the attribute
|
.foregroundColor: attributesToRestore.foregroundColor]
|
||||||
attachmentAttributes[.MatrixBlockquote] = blockquote
|
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)
|
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)
|
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?
|
let mediaProvider: MediaProviderProtocol?
|
||||||
if ProcessInfo.isXcodePreview || ProcessInfo.isRunningTests {
|
if ProcessInfo.isXcodePreview || ProcessInfo.isRunningTests {
|
||||||
// The mock viewModel simulates the loading logic for testing purposes
|
// 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())
|
mediaProvider = MediaProviderMock(configuration: .init())
|
||||||
} else if let timelineContext = delegate?.timelineContext {
|
} else if let timelineContext = delegate?.timelineContext {
|
||||||
context = PillContext(timelineContext: timelineContext, data: pillData)
|
context = PillContext(timelineContext: timelineContext, data: pillData)
|
||||||
|
@ -10,75 +10,77 @@ import Foundation
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
final class PillContext: ObservableObject {
|
final class PillContext: ObservableObject {
|
||||||
struct PillViewState: Equatable {
|
@Published var viewState: PillViewState = .undefined
|
||||||
let isOwnMention: Bool
|
|
||||||
let displayText: String
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published private(set) var viewState: PillViewState
|
let data: PillTextAttachmentData
|
||||||
|
var cancellable: AnyCancellable?
|
||||||
private var cancellable: AnyCancellable?
|
|
||||||
|
|
||||||
init(timelineContext: TimelineViewModel.Context, data: PillTextAttachmentData) {
|
init(timelineContext: TimelineViewModel.Context, data: PillTextAttachmentData) {
|
||||||
switch data.type {
|
self.data = data
|
||||||
case let .user(id):
|
timelineContext.viewState.pillContextUpdater?(self)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PillContext {
|
extension PillContext {
|
||||||
enum MockType {
|
static func mock(viewState: PillViewState, delay: Duration? = nil) -> PillContext {
|
||||||
case loadUser(isOwn: Bool)
|
// This is just for previews so the internal data doesn't really matter
|
||||||
case loadedUser(isOwn: Bool)
|
let viewModel = PillContext(timelineContext: TimelineViewModel.mock.context, data: PillTextAttachmentData(type: .allUsers, font: .preferredFont(forTextStyle: .body)))
|
||||||
case allUsers
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func mock(type: MockType) -> PillContext {
|
enum PillViewState: Equatable {
|
||||||
let testID = "@test:test.com"
|
enum PillImage: Equatable {
|
||||||
let pillType: PillType
|
case link
|
||||||
switch type {
|
case roomAvatar(RoomAvatar)
|
||||||
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):
|
case mention(isOwnMention: Bool, displayText: String)
|
||||||
pillType = .user(userID: "@test:test.com")
|
case reference(avatar: PillImage, displayText: String)
|
||||||
let viewModel = PillContext(timelineContext: TimelineViewModel.mock.context, data: PillTextAttachmentData(type: pillType, font: .preferredFont(forTextStyle: .body)))
|
case undefined
|
||||||
viewModel.viewState = PillViewState(isOwnMention: isOwn, displayText: "@Very Very Long Test Display Text")
|
|
||||||
return viewModel
|
var isOwnMention: Bool {
|
||||||
case .allUsers:
|
switch self {
|
||||||
pillType = .allUsers
|
case .mention(let isOwnMention, _):
|
||||||
return PillContext(timelineContext: TimelineViewModel.mock.context, data: PillTextAttachmentData(type: pillType, font: .preferredFont(forTextStyle: .body)))
|
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
|
import UIKit
|
||||||
|
|
||||||
enum PillType: Codable, Equatable {
|
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
|
/// A pill that mentions a user
|
||||||
case user(userID: String)
|
case user(userID: String)
|
||||||
/// A pill that mentions all users in a room
|
/// A pill that mentions all users in a room
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
// Please see LICENSE files in the repository root for full details.
|
// Please see LICENSE files in the repository root for full details.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Compound
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct PillView: View {
|
struct PillView: View {
|
||||||
@ -22,16 +23,40 @@ struct PillView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
mainContent
|
||||||
|
.onChange(of: context.viewState.displayText) {
|
||||||
|
didChangeText()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var mainContent: some View {
|
||||||
|
HStack(spacing: 4) {
|
||||||
|
image
|
||||||
Text(context.viewState.displayText)
|
Text(context.viewState.displayText)
|
||||||
.font(.compound.bodyLGSemibold)
|
.font(.compound.bodyLGSemibold)
|
||||||
.foregroundColor(textColor)
|
.foregroundColor(textColor)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
|
}
|
||||||
.padding(.leading, 4)
|
.padding(.leading, 4)
|
||||||
.padding(.trailing, 6)
|
.padding(.trailing, 6)
|
||||||
.padding(.vertical, 1)
|
.padding(.vertical, 1)
|
||||||
.background { Capsule().foregroundColor(backgroundColor) }
|
.background { Capsule().foregroundColor(backgroundColor) }
|
||||||
.onChange(of: context.viewState.displayText) {
|
}
|
||||||
didChangeText()
|
|
||||||
|
@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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -41,24 +66,27 @@ struct PillView_Previews: PreviewProvider, TestablePreview {
|
|||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
PillView(mediaProvider: mockMediaProvider,
|
PillView(mediaProvider: mockMediaProvider,
|
||||||
context: PillContext.mock(type: .loadUser(isOwn: false))) { }
|
context: PillContext.mock(viewState: .mention(isOwnMention: false,
|
||||||
|
displayText: "@Alice"))) { }
|
||||||
.frame(maxWidth: PillConstants.mockMaxWidth)
|
.frame(maxWidth: PillConstants.mockMaxWidth)
|
||||||
.previewDisplayName("Loading")
|
.previewDisplayName("User")
|
||||||
PillView(mediaProvider: mockMediaProvider,
|
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)
|
.frame(maxWidth: PillConstants.mockMaxWidth)
|
||||||
.previewDisplayName("Loading Own")
|
.previewDisplayName("User with a long name")
|
||||||
PillView(mediaProvider: mockMediaProvider,
|
PillView(mediaProvider: mockMediaProvider,
|
||||||
context: PillContext.mock(type: .loadedUser(isOwn: false))) { }
|
context: PillContext.mock(viewState: .mention(isOwnMention: true,
|
||||||
|
displayText: "@Alice"))) { }
|
||||||
.frame(maxWidth: PillConstants.mockMaxWidth)
|
.frame(maxWidth: PillConstants.mockMaxWidth)
|
||||||
.previewDisplayName("Loaded Long")
|
.previewDisplayName("Own user")
|
||||||
PillView(mediaProvider: mockMediaProvider,
|
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)
|
.frame(maxWidth: PillConstants.mockMaxWidth)
|
||||||
.previewDisplayName("Loaded Long Own")
|
.previewDisplayName("Room")
|
||||||
PillView(mediaProvider: mockMediaProvider,
|
PillView(mediaProvider: mockMediaProvider,
|
||||||
context: PillContext.mock(type: .allUsers)) { }
|
context: PillContext.mock(viewState: .reference(avatar: .link, displayText: L10n.screenRoomEventPill("Room")))) { }
|
||||||
.frame(maxWidth: PillConstants.mockMaxWidth)
|
.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.
|
// In the future we might use this to do some customisation in what is plain text used to represent mentions.
|
||||||
struct PlainMentionBuilder: MentionBuilderProtocol {
|
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 handleAllUsersMention(for attributedString: NSMutableAttributedString, in range: NSRange) { }
|
||||||
|
|
||||||
func handleUserMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, userID: String, userDisplayName: String?) {
|
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)
|
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,
|
unreadNotificationsCount: 0,
|
||||||
notificationMode: nil,
|
notificationMode: nil,
|
||||||
canonicalAlias: "#footest:somewhere.org",
|
canonicalAlias: "#footest:somewhere.org",
|
||||||
|
alternativeAliases: [],
|
||||||
hasOngoingCall: false,
|
hasOngoingCall: false,
|
||||||
isMarkedUnread: false,
|
isMarkedUnread: false,
|
||||||
isFavourite: false)
|
isFavourite: false)
|
||||||
@ -220,6 +221,7 @@ private extension HomeScreenRoom {
|
|||||||
unreadNotificationsCount: 0,
|
unreadNotificationsCount: 0,
|
||||||
notificationMode: nil,
|
notificationMode: nil,
|
||||||
canonicalAlias: alias,
|
canonicalAlias: alias,
|
||||||
|
alternativeAliases: [],
|
||||||
hasOngoingCall: false,
|
hasOngoingCall: false,
|
||||||
isMarkedUnread: false,
|
isMarkedUnread: false,
|
||||||
isFavourite: false)
|
isFavourite: false)
|
||||||
|
@ -157,6 +157,7 @@ private extension HomeScreenRoom {
|
|||||||
unreadNotificationsCount: 0,
|
unreadNotificationsCount: 0,
|
||||||
notificationMode: nil,
|
notificationMode: nil,
|
||||||
canonicalAlias: "#footest:somewhere.org",
|
canonicalAlias: "#footest:somewhere.org",
|
||||||
|
alternativeAliases: [],
|
||||||
hasOngoingCall: false,
|
hasOngoingCall: false,
|
||||||
isMarkedUnread: false,
|
isMarkedUnread: false,
|
||||||
isFavourite: false)
|
isFavourite: false)
|
||||||
@ -184,6 +185,7 @@ private extension HomeScreenRoom {
|
|||||||
unreadNotificationsCount: 0,
|
unreadNotificationsCount: 0,
|
||||||
notificationMode: nil,
|
notificationMode: nil,
|
||||||
canonicalAlias: alias,
|
canonicalAlias: alias,
|
||||||
|
alternativeAliases: [],
|
||||||
hasOngoingCall: false,
|
hasOngoingCall: false,
|
||||||
isMarkedUnread: false,
|
isMarkedUnread: false,
|
||||||
isFavourite: false)
|
isFavourite: false)
|
||||||
|
@ -19,6 +19,7 @@ struct MediaEventsTimelineScreenCoordinatorParameters {
|
|||||||
let emojiProvider: EmojiProviderProtocol
|
let emojiProvider: EmojiProviderProtocol
|
||||||
let userIndicatorController: UserIndicatorControllerProtocol
|
let userIndicatorController: UserIndicatorControllerProtocol
|
||||||
let timelineControllerFactory: TimelineControllerFactoryProtocol
|
let timelineControllerFactory: TimelineControllerFactoryProtocol
|
||||||
|
let clientProxy: ClientProxyProtocol
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MediaEventsTimelineScreenCoordinatorAction {
|
enum MediaEventsTimelineScreenCoordinatorAction {
|
||||||
@ -49,7 +50,8 @@ final class MediaEventsTimelineScreenCoordinator: CoordinatorProtocol {
|
|||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
emojiProvider: parameters.emojiProvider,
|
emojiProvider: parameters.emojiProvider,
|
||||||
timelineControllerFactory: parameters.timelineControllerFactory)
|
timelineControllerFactory: parameters.timelineControllerFactory,
|
||||||
|
clientProxy: parameters.clientProxy)
|
||||||
|
|
||||||
let filesTimelineViewModel = TimelineViewModel(roomProxy: parameters.roomProxy,
|
let filesTimelineViewModel = TimelineViewModel(roomProxy: parameters.roomProxy,
|
||||||
timelineController: parameters.filesTimelineController,
|
timelineController: parameters.filesTimelineController,
|
||||||
@ -61,7 +63,8 @@ final class MediaEventsTimelineScreenCoordinator: CoordinatorProtocol {
|
|||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
emojiProvider: parameters.emojiProvider,
|
emojiProvider: parameters.emojiProvider,
|
||||||
timelineControllerFactory: parameters.timelineControllerFactory)
|
timelineControllerFactory: parameters.timelineControllerFactory,
|
||||||
|
clientProxy: parameters.clientProxy)
|
||||||
|
|
||||||
viewModel = MediaEventsTimelineScreenViewModel(mediaTimelineViewModel: mediaTimelineViewModel,
|
viewModel = MediaEventsTimelineScreenViewModel(mediaTimelineViewModel: mediaTimelineViewModel,
|
||||||
filesTimelineViewModel: filesTimelineViewModel,
|
filesTimelineViewModel: filesTimelineViewModel,
|
||||||
|
@ -274,6 +274,7 @@ struct MediaEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
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 appMediator: AppMediatorProtocol
|
||||||
let emojiProvider: EmojiProviderProtocol
|
let emojiProvider: EmojiProviderProtocol
|
||||||
let timelineControllerFactory: TimelineControllerFactoryProtocol
|
let timelineControllerFactory: TimelineControllerFactoryProtocol
|
||||||
|
let clientProxy: ClientProxyProtocol
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PinnedEventsTimelineScreenCoordinatorAction {
|
enum PinnedEventsTimelineScreenCoordinatorAction {
|
||||||
@ -53,7 +54,8 @@ final class PinnedEventsTimelineScreenCoordinator: CoordinatorProtocol {
|
|||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
emojiProvider: parameters.emojiProvider,
|
emojiProvider: parameters.emojiProvider,
|
||||||
timelineControllerFactory: parameters.timelineControllerFactory)
|
timelineControllerFactory: parameters.timelineControllerFactory,
|
||||||
|
clientProxy: parameters.clientProxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
func start() {
|
func start() {
|
||||||
|
@ -99,7 +99,8 @@ struct PinnedEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||||
|
clientProxy: ClientProxyMock(.init()))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
|
@ -86,7 +86,8 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
|||||||
appSettings: parameters.appSettings,
|
appSettings: parameters.appSettings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
emojiProvider: parameters.emojiProvider,
|
emojiProvider: parameters.emojiProvider,
|
||||||
timelineControllerFactory: parameters.timelineControllerFactory)
|
timelineControllerFactory: parameters.timelineControllerFactory,
|
||||||
|
clientProxy: parameters.clientProxy)
|
||||||
|
|
||||||
wysiwygViewModel = WysiwygComposerViewModel(minHeight: ComposerConstant.minHeight,
|
wysiwygViewModel = WysiwygComposerViewModel(minHeight: ComposerConstant.minHeight,
|
||||||
maxCompressedHeight: ComposerConstant.maxHeight,
|
maxCompressedHeight: ComposerConstant.maxHeight,
|
||||||
|
@ -266,7 +266,8 @@ struct RoomScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||||
|
clientProxy: ClientProxyMock(.init()))
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
|
@ -41,6 +41,7 @@ class TimelineInteractionHandler {
|
|||||||
private let emojiProvider: EmojiProviderProtocol
|
private let emojiProvider: EmojiProviderProtocol
|
||||||
private let timelineControllerFactory: TimelineControllerFactoryProtocol
|
private let timelineControllerFactory: TimelineControllerFactoryProtocol
|
||||||
private let pollInteractionHandler: PollInteractionHandlerProtocol
|
private let pollInteractionHandler: PollInteractionHandlerProtocol
|
||||||
|
private let clientProxy: ClientProxyProtocol
|
||||||
|
|
||||||
private let actionsSubject: PassthroughSubject<TimelineInteractionHandlerAction, Never> = .init()
|
private let actionsSubject: PassthroughSubject<TimelineInteractionHandlerAction, Never> = .init()
|
||||||
var actions: AnyPublisher<TimelineInteractionHandlerAction, Never> {
|
var actions: AnyPublisher<TimelineInteractionHandlerAction, Never> {
|
||||||
@ -66,7 +67,8 @@ class TimelineInteractionHandler {
|
|||||||
appSettings: AppSettings,
|
appSettings: AppSettings,
|
||||||
analyticsService: AnalyticsService,
|
analyticsService: AnalyticsService,
|
||||||
emojiProvider: EmojiProviderProtocol,
|
emojiProvider: EmojiProviderProtocol,
|
||||||
timelineControllerFactory: TimelineControllerFactoryProtocol) {
|
timelineControllerFactory: TimelineControllerFactoryProtocol,
|
||||||
|
clientProxy: ClientProxyProtocol) {
|
||||||
self.roomProxy = roomProxy
|
self.roomProxy = roomProxy
|
||||||
self.timelineController = timelineController
|
self.timelineController = timelineController
|
||||||
self.mediaProvider = mediaProvider
|
self.mediaProvider = mediaProvider
|
||||||
@ -79,6 +81,7 @@ class TimelineInteractionHandler {
|
|||||||
self.analyticsService = analyticsService
|
self.analyticsService = analyticsService
|
||||||
self.emojiProvider = emojiProvider
|
self.emojiProvider = emojiProvider
|
||||||
self.timelineControllerFactory = timelineControllerFactory
|
self.timelineControllerFactory = timelineControllerFactory
|
||||||
|
self.clientProxy = clientProxy
|
||||||
pollInteractionHandler = PollInteractionHandler(analyticsService: analyticsService, roomProxy: roomProxy)
|
pollInteractionHandler = PollInteractionHandler(analyticsService: analyticsService, roomProxy: roomProxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -580,7 +583,8 @@ class TimelineInteractionHandler {
|
|||||||
appSettings: appSettings,
|
appSettings: appSettings,
|
||||||
analyticsService: analyticsService,
|
analyticsService: analyticsService,
|
||||||
emojiProvider: emojiProvider,
|
emojiProvider: emojiProvider,
|
||||||
timelineControllerFactory: timelineControllerFactory)
|
timelineControllerFactory: timelineControllerFactory,
|
||||||
|
clientProxy: clientProxy)
|
||||||
|
|
||||||
return .displayMediaPreview(item: item, timelineViewModel: .new(timelineViewModel))
|
return .displayMediaPreview(item: item, timelineViewModel: .new(timelineViewModel))
|
||||||
} else {
|
} else {
|
||||||
|
@ -114,6 +114,9 @@ struct TimelineViewState: BindableState {
|
|||||||
/// A closure providing the associated audio player state for an item in the timeline.
|
/// A closure providing the associated audio player state for an item in the timeline.
|
||||||
var audioPlayerStateProvider: (@MainActor (_ itemId: TimelineItemIdentifier) -> AudioPlayerState?)?
|
var audioPlayerStateProvider: (@MainActor (_ itemId: TimelineItemIdentifier) -> AudioPlayerState?)?
|
||||||
|
|
||||||
|
/// A closure that updates the associated pill context
|
||||||
|
var pillContextUpdater: (@MainActor (PillContext) -> Void)?
|
||||||
|
|
||||||
var emojiProvider: EmojiProviderProtocol
|
var emojiProvider: EmojiProviderProtocol
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
private let analyticsService: AnalyticsService
|
private let analyticsService: AnalyticsService
|
||||||
private let emojiProvider: EmojiProviderProtocol
|
private let emojiProvider: EmojiProviderProtocol
|
||||||
private let timelineControllerFactory: TimelineControllerFactoryProtocol
|
private let timelineControllerFactory: TimelineControllerFactoryProtocol
|
||||||
|
private let clientProxy: ClientProxyProtocol
|
||||||
|
|
||||||
private let timelineInteractionHandler: TimelineInteractionHandler
|
private let timelineInteractionHandler: TimelineInteractionHandler
|
||||||
|
|
||||||
@ -55,7 +56,8 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
appSettings: AppSettings,
|
appSettings: AppSettings,
|
||||||
analyticsService: AnalyticsService,
|
analyticsService: AnalyticsService,
|
||||||
emojiProvider: EmojiProviderProtocol,
|
emojiProvider: EmojiProviderProtocol,
|
||||||
timelineControllerFactory: TimelineControllerFactoryProtocol) {
|
timelineControllerFactory: TimelineControllerFactoryProtocol,
|
||||||
|
clientProxy: ClientProxyProtocol) {
|
||||||
self.timelineController = timelineController
|
self.timelineController = timelineController
|
||||||
self.mediaProvider = mediaProvider
|
self.mediaProvider = mediaProvider
|
||||||
self.mediaPlayerProvider = mediaPlayerProvider
|
self.mediaPlayerProvider = mediaPlayerProvider
|
||||||
@ -66,6 +68,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
self.appMediator = appMediator
|
self.appMediator = appMediator
|
||||||
self.emojiProvider = emojiProvider
|
self.emojiProvider = emojiProvider
|
||||||
self.timelineControllerFactory = timelineControllerFactory
|
self.timelineControllerFactory = timelineControllerFactory
|
||||||
|
self.clientProxy = clientProxy
|
||||||
|
|
||||||
let voiceMessageRecorder = VoiceMessageRecorder(audioRecorder: AudioRecorder(), mediaPlayerProvider: mediaPlayerProvider)
|
let voiceMessageRecorder = VoiceMessageRecorder(audioRecorder: AudioRecorder(), mediaPlayerProvider: mediaPlayerProvider)
|
||||||
|
|
||||||
@ -80,7 +83,8 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
appSettings: appSettings,
|
appSettings: appSettings,
|
||||||
analyticsService: analyticsService,
|
analyticsService: analyticsService,
|
||||||
emojiProvider: emojiProvider,
|
emojiProvider: emojiProvider,
|
||||||
timelineControllerFactory: timelineControllerFactory)
|
timelineControllerFactory: timelineControllerFactory,
|
||||||
|
clientProxy: clientProxy)
|
||||||
|
|
||||||
super.init(initialViewState: TimelineViewState(timelineKind: timelineController.timelineKind,
|
super.init(initialViewState: TimelineViewState(timelineKind: timelineController.timelineKind,
|
||||||
roomID: roomProxy.id,
|
roomID: roomProxy.id,
|
||||||
@ -113,6 +117,10 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
return self.timelineInteractionHandler.audioPlayerState(for: itemID)
|
return self.timelineInteractionHandler.audioPlayerState(for: itemID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.pillContextUpdater = { [weak self] pillContext in
|
||||||
|
self?.pillContextUpdater(pillContext)
|
||||||
|
}
|
||||||
|
|
||||||
state.timelineState.paginationState = timelineController.paginationState
|
state.timelineState.paginationState = timelineController.paginationState
|
||||||
buildTimelineViews(timelineItems: timelineController.timelineItems)
|
buildTimelineViews(timelineItems: timelineController.timelineItems)
|
||||||
|
|
||||||
@ -826,6 +834,72 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
actionsSubject.send(.displayMessageForwarding(forwardingItem: .init(id: itemID, roomID: roomProxy.id, content: content)))
|
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
|
// MARK: - User Indicators
|
||||||
|
|
||||||
private func showFocusLoadingIndicator() {
|
private func showFocusLoadingIndicator() {
|
||||||
@ -896,7 +970,8 @@ extension TimelineViewModel {
|
|||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
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,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||||
|
clientProxy: ClientProxyMock(.init()))
|
||||||
return mock
|
return mock
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -339,7 +339,8 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
|
|||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||||
|
clientProxy: ClientProxyMock(.init()))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
|
@ -88,7 +88,8 @@ struct TimelineReadReceiptsView_Previews: PreviewProvider, TestablePreview {
|
|||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
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 singleReceipt = [ReadReceipt(userID: RoomMemberProxyMock.mockAlice.userID, formattedTimestamp: "Now")]
|
||||||
static let doubleReceipt = [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 = [
|
let htmlStrings = [
|
||||||
"""
|
"""
|
||||||
Plain text\n
|
Plain text\n
|
||||||
@bob:matrix.org\n
|
|
||||||
#room:matrix.org\n
|
|
||||||
!room:matrix.org\n
|
!room:matrix.org\n
|
||||||
https://www.matrix.org\n
|
https://www.matrix.org\n
|
||||||
www.matrix.org\n
|
www.matrix.org\n
|
||||||
|
@ -98,7 +98,8 @@ struct HighlightedTimelineItemTimeline_Previews: PreviewProvider {
|
|||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||||
|
clientProxy: ClientProxyMock(.init()))
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
|
@ -91,7 +91,8 @@ struct TimelineView_Previews: PreviewProvider, TestablePreview {
|
|||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||||
|
clientProxy: ClientProxyMock(.init()))
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
NavigationStack {
|
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> {
|
func loadUserDisplayName() async -> Result<Void, ClientProxyError> {
|
||||||
do {
|
do {
|
||||||
let displayName = try await client.displayName()
|
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 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>
|
@discardableResult func loadUserDisplayName() async -> Result<Void, ClientProxyError>
|
||||||
|
|
||||||
func setUserDisplayName(_ name: String) async -> Result<Void, ClientProxyError>
|
func setUserDisplayName(_ name: String) async -> Result<Void, ClientProxyError>
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
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
|
// sourcery: AutoMockable
|
||||||
protocol RoomPreviewProxyProtocol {
|
protocol RoomPreviewProxyProtocol {
|
||||||
var info: RoomPreviewInfoProxy { get }
|
var info: RoomPreviewInfoProxy { get }
|
||||||
|
@ -18,6 +18,7 @@ enum RoomProxyError: Error {
|
|||||||
case missingTransactionID
|
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 {
|
enum RoomProxyType {
|
||||||
case joined(JoinedRoomProxyProtocol)
|
case joined(JoinedRoomProxyProtocol)
|
||||||
case invited(InvitedRoomProxyProtocol)
|
case invited(InvitedRoomProxyProtocol)
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import MatrixRustSDK
|
import MatrixRustSDK
|
||||||
|
|
||||||
|
/// A quick summary of a Room, useful to describe and give quick informations for the room list
|
||||||
struct RoomSummary {
|
struct RoomSummary {
|
||||||
enum JoinRequestType {
|
enum JoinRequestType {
|
||||||
case invite(inviter: RoomMemberProxyProtocol?)
|
case invite(inviter: RoomMemberProxyProtocol?)
|
||||||
@ -45,6 +46,7 @@ struct RoomSummary {
|
|||||||
let unreadNotificationsCount: UInt
|
let unreadNotificationsCount: UInt
|
||||||
let notificationMode: RoomNotificationModeProxy?
|
let notificationMode: RoomNotificationModeProxy?
|
||||||
let canonicalAlias: String?
|
let canonicalAlias: String?
|
||||||
|
let alternativeAliases: Set<String>
|
||||||
|
|
||||||
let hasOngoingCall: Bool
|
let hasOngoingCall: Bool
|
||||||
|
|
||||||
@ -99,6 +101,7 @@ extension RoomSummary {
|
|||||||
unreadNotificationsCount = hasUnreadNotifications ? 1 : 0
|
unreadNotificationsCount = hasUnreadNotifications ? 1 : 0
|
||||||
notificationMode = settingsMode
|
notificationMode = settingsMode
|
||||||
canonicalAlias = nil
|
canonicalAlias = nil
|
||||||
|
alternativeAliases = []
|
||||||
hasOngoingCall = false
|
hasOngoingCall = false
|
||||||
|
|
||||||
joinRequestType = nil
|
joinRequestType = nil
|
||||||
|
@ -275,6 +275,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
|||||||
unreadNotificationsCount: UInt(roomInfo.numUnreadNotifications),
|
unreadNotificationsCount: UInt(roomInfo.numUnreadNotifications),
|
||||||
notificationMode: notificationMode,
|
notificationMode: notificationMode,
|
||||||
canonicalAlias: roomInfo.canonicalAlias,
|
canonicalAlias: roomInfo.canonicalAlias,
|
||||||
|
alternativeAliases: .init(roomInfo.alternativeAliases),
|
||||||
hasOngoingCall: roomInfo.hasRoomCall,
|
hasOngoingCall: roomInfo.hasRoomCall,
|
||||||
isMarkedUnread: roomInfo.isMarkedUnread,
|
isMarkedUnread: roomInfo.isMarkedUnread,
|
||||||
isFavourite: roomInfo.isFavourite)
|
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")
|
XCTAssertEqual(numberOfBlockquotes, 3, "Couldn't find all the blockquotes")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testUserMentionAtachment() {
|
func testUserPermalinkMentionAtachment() {
|
||||||
let string = "https://matrix.to/#/@test:matrix.org"
|
let string = "https://matrix.to/#/@test:matrix.org"
|
||||||
let attributedStringFromHTML = attributedStringBuilder.fromHTML(string)
|
let attributedStringFromHTML = attributedStringBuilder.fromHTML(string)
|
||||||
XCTAssertNotNil(attributedStringFromHTML?.attachment)
|
XCTAssertNotNil(attributedStringFromHTML?.attachment)
|
||||||
XCTAssertNotNil(attributedStringFromHTML?.link)
|
XCTAssertEqual(attributedStringFromHTML?.userID, "@test:matrix.org")
|
||||||
|
XCTAssertEqual(attributedStringFromHTML?.link?.absoluteString, string)
|
||||||
let attributedStringFromPlain = attributedStringBuilder.fromPlain(string)
|
let attributedStringFromPlain = attributedStringBuilder.fromPlain(string)
|
||||||
XCTAssertNotNil(attributedStringFromPlain?.attachment)
|
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() {
|
func testUserMentionAtachmentInBlockQuotes() {
|
||||||
|
@ -34,6 +34,7 @@ class HomeScreenRoomTests: XCTestCase {
|
|||||||
unreadNotificationsCount: unreadNotificationsCount,
|
unreadNotificationsCount: unreadNotificationsCount,
|
||||||
notificationMode: notificationMode,
|
notificationMode: notificationMode,
|
||||||
canonicalAlias: nil,
|
canonicalAlias: nil,
|
||||||
|
alternativeAliases: [],
|
||||||
hasOngoingCall: hasOngoingCall,
|
hasOngoingCall: hasOngoingCall,
|
||||||
isMarkedUnread: isMarkedUnread,
|
isMarkedUnread: isMarkedUnread,
|
||||||
isFavourite: false)
|
isFavourite: false)
|
||||||
|
@ -92,6 +92,7 @@ class LoggingTests: XCTestCase {
|
|||||||
unreadNotificationsCount: 0,
|
unreadNotificationsCount: 0,
|
||||||
notificationMode: nil,
|
notificationMode: nil,
|
||||||
canonicalAlias: nil,
|
canonicalAlias: nil,
|
||||||
|
alternativeAliases: [],
|
||||||
hasOngoingCall: false,
|
hasOngoingCall: false,
|
||||||
isMarkedUnread: false,
|
isMarkedUnread: false,
|
||||||
isFavourite: false)
|
isFavourite: false)
|
||||||
|
@ -12,7 +12,7 @@ import XCTest
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
class PillContextTests: XCTestCase {
|
class PillContextTests: XCTestCase {
|
||||||
func testUser() async throws {
|
func testUser() async {
|
||||||
let id = "@test:matrix.org"
|
let id = "@test:matrix.org"
|
||||||
let proxyMock = JoinedRoomProxyMock(.init(name: "Test"))
|
let proxyMock = JoinedRoomProxyMock(.init(name: "Test"))
|
||||||
let subject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([])
|
let subject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([])
|
||||||
@ -27,7 +27,8 @@ class PillContextTests: XCTestCase {
|
|||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
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)))
|
let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .user(userID: id), font: .preferredFont(forTextStyle: .body)))
|
||||||
|
|
||||||
XCTAssertFalse(context.viewState.isOwnMention)
|
XCTAssertFalse(context.viewState.isOwnMention)
|
||||||
@ -39,10 +40,11 @@ class PillContextTests: XCTestCase {
|
|||||||
await Task.yield()
|
await Task.yield()
|
||||||
|
|
||||||
XCTAssertFalse(context.viewState.isOwnMention)
|
XCTAssertFalse(context.viewState.isOwnMention)
|
||||||
|
XCTAssertNil(context.viewState.image)
|
||||||
XCTAssertEqual(context.viewState.displayText, "@\(name)")
|
XCTAssertEqual(context.viewState.displayText, "@\(name)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testOwnUser() async throws {
|
func testOwnUser() {
|
||||||
let id = "@test:matrix.org"
|
let id = "@test:matrix.org"
|
||||||
let proxyMock = JoinedRoomProxyMock(.init(name: "Test", ownUserID: id))
|
let proxyMock = JoinedRoomProxyMock(.init(name: "Test", ownUserID: id))
|
||||||
let subject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([])
|
let subject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([])
|
||||||
@ -57,13 +59,15 @@ class PillContextTests: XCTestCase {
|
|||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
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)))
|
let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .user(userID: id), font: .preferredFont(forTextStyle: .body)))
|
||||||
|
|
||||||
|
XCTAssertNil(context.viewState.image)
|
||||||
XCTAssertTrue(context.viewState.isOwnMention)
|
XCTAssertTrue(context.viewState.isOwnMention)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAllUsers() async throws {
|
func testAllUsers() {
|
||||||
let avatarURL = URL(string: "https://matrix.jpg")
|
let avatarURL = URL(string: "https://matrix.jpg")
|
||||||
let id = "test_room"
|
let id = "test_room"
|
||||||
let displayName = "Test"
|
let displayName = "Test"
|
||||||
@ -80,10 +84,216 @@ class PillContextTests: XCTestCase {
|
|||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
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)))
|
let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .allUsers, font: .preferredFont(forTextStyle: .body)))
|
||||||
|
|
||||||
XCTAssertTrue(context.viewState.isOwnMention)
|
XCTAssertTrue(context.viewState.isOwnMention)
|
||||||
|
XCTAssertNil(context.viewState.image)
|
||||||
XCTAssertEqual(context.viewState.displayText, PillConstants.atRoom)
|
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,
|
unreadNotificationsCount: 0,
|
||||||
notificationMode: nil,
|
notificationMode: nil,
|
||||||
canonicalAlias: nil,
|
canonicalAlias: nil,
|
||||||
|
alternativeAliases: [],
|
||||||
hasOngoingCall: false,
|
hasOngoingCall: false,
|
||||||
isMarkedUnread: false,
|
isMarkedUnread: false,
|
||||||
isFavourite: false)
|
isFavourite: false)
|
||||||
|
@ -312,7 +312,8 @@ class TimelineViewModelTests: XCTestCase {
|
|||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||||
|
clientProxy: ClientProxyMock(.init()))
|
||||||
return (viewModel, roomProxy, timelineProxy, timelineController)
|
return (viewModel, roomProxy, timelineProxy, timelineController)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -338,7 +339,8 @@ class TimelineViewModelTests: XCTestCase {
|
|||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||||
|
clientProxy: ClientProxyMock(.init()))
|
||||||
|
|
||||||
let deferred = deferFulfillment(viewModel.context.$viewState) { value in
|
let deferred = deferFulfillment(viewModel.context.$viewState) { value in
|
||||||
value.bindings.readReceiptsSummaryInfo?.orderedReceipts == receipts
|
value.bindings.readReceiptsSummaryInfo?.orderedReceipts == receipts
|
||||||
@ -367,7 +369,8 @@ class TimelineViewModelTests: XCTestCase {
|
|||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||||
|
clientProxy: ClientProxyMock(.init()))
|
||||||
XCTAssertEqual(configuration.pinnedEventIDs, viewModel.context.viewState.pinnedEventIDs)
|
XCTAssertEqual(configuration.pinnedEventIDs, viewModel.context.viewState.pinnedEventIDs)
|
||||||
|
|
||||||
configuration.pinnedEventIDs = ["test1", "test2"]
|
configuration.pinnedEventIDs = ["test1", "test2"]
|
||||||
@ -394,7 +397,8 @@ class TimelineViewModelTests: XCTestCase {
|
|||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||||
|
clientProxy: ClientProxyMock(.init()))
|
||||||
|
|
||||||
var deferred = deferFulfillment(viewModel.context.$viewState) { value in
|
var deferred = deferFulfillment(viewModel.context.$viewState) { value in
|
||||||
value.canCurrentUserPin
|
value.canCurrentUserPin
|
||||||
@ -425,7 +429,8 @@ class TimelineViewModelTests: XCTestCase {
|
|||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analyticsService: ServiceLocator.shared.analytics,
|
analyticsService: ServiceLocator.shared.analytics,
|
||||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||||
|
clientProxy: ClientProxyMock(.init()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user