From a2242c63e26c33234c24ce80adf64711e127b023 Mon Sep 17 00:00:00 2001 From: Mauro <34335419+Velin92@users.noreply.github.com> Date: Tue, 25 Feb 2025 14:46:01 +0100 Subject: [PATCH] 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 --- .../MediaEventsTimelineFlowCoordinator.swift | 3 +- .../PinnedEventsTimelineFlowCoordinator.swift | 3 +- .../Mocks/Generated/GeneratedMocks.swift | 140 +++++++++++ .../Mocks/RoomSummaryProviderMock.swift | 36 ++- .../HTMLParsing/AttributedStringBuilder.swift | 15 +- .../Sources/Other/Pills/MentionBuilder.swift | 172 ++++++++++++-- .../Pills/PillAttachmentViewProvider.swift | 2 +- .../Sources/Other/Pills/PillContext.swift | 120 +++++----- .../Other/Pills/PillTextAttachmentData.swift | 15 ++ ElementX/Sources/Other/Pills/PillView.swift | 64 +++-- .../Other/Pills/PlainMentionBuilder.swift | 8 + .../View/HomeScreenInviteCell.swift | 2 + .../View/HomeScreenKnockedCell.swift | 2 + ...MediaEventsTimelineScreenCoordinator.swift | 7 +- .../View/MediaEventsTimelineScreen.swift | 3 +- ...innedEventsTimelineScreenCoordinator.swift | 4 +- .../View/PinnedEventsTimelineScreen.swift | 3 +- .../RoomScreen/RoomScreenCoordinator.swift | 3 +- .../Screens/RoomScreen/View/RoomScreen.swift | 3 +- .../Timeline/TimelineInteractionHandler.swift | 8 +- .../Screens/Timeline/TimelineModels.swift | 3 + .../Screens/Timeline/TimelineViewModel.swift | 81 ++++++- .../ReadReceiptsSummaryView.swift | 3 +- .../Style/TimelineItemBubbledStylerView.swift | 3 +- .../TimelineReadReceiptsView.swift | 5 +- .../TimelineItemViews/FormattedBodyText.swift | 2 - .../HighlightedTimelineItemModifier.swift | 3 +- .../Screens/Timeline/View/TimelineView.swift | 3 +- .../Sources/Services/Client/ClientProxy.swift | 12 + .../Services/Client/ClientProxyProtocol.swift | 4 + .../RoomPreviewProxyProtocol.swift | 1 + .../Services/Room/RoomProxyProtocol.swift | 1 + .../Room/RoomSummary/RoomSummary.swift | 3 + .../RoomSummary/RoomSummaryProvider.swift | 1 + .../test_formattedBodyText-iPad-en-GB.1.png | 4 +- .../test_formattedBodyText-iPad-pseudo.1.png | 4 +- ...st_formattedBodyText-iPhone-16-en-GB.1.png | 4 +- ...t_formattedBodyText-iPhone-16-pseudo.1.png | 4 +- .../test_globalSearchScreen-iPad-en-GB.1.png | 4 +- .../test_globalSearchScreen-iPad-pseudo.1.png | 4 +- ...t_globalSearchScreen-iPhone-16-en-GB.1.png | 4 +- ..._globalSearchScreen-iPhone-16-pseudo.1.png | 4 +- ...t_messageForwardingScreen-iPad-en-GB.1.png | 4 +- ..._messageForwardingScreen-iPad-pseudo.1.png | 4 +- ...sageForwardingScreen-iPhone-16-en-GB.1.png | 4 +- ...ageForwardingScreen-iPhone-16-pseudo.1.png | 4 +- ...ssageText-iPad-en-GB.Custom-Attachment.png | 4 +- ...sageText-iPad-pseudo.Custom-Attachment.png | 4 +- ...Text-iPhone-16-en-GB.Custom-Attachment.png | 4 +- ...ext-iPhone-16-pseudo.Custom-Attachment.png | 4 +- .../test_pillView-iPad-en-GB.All-Users.png | 3 - ...st_pillView-iPad-en-GB.Loaded-Long-Own.png | 3 - .../test_pillView-iPad-en-GB.Loaded-Long.png | 3 - .../test_pillView-iPad-en-GB.Loading-Own.png | 3 - .../test_pillView-iPad-en-GB.Loading.png | 3 - .../test_pillView-iPad-en-GB.Message-link.png | 3 + .../test_pillView-iPad-en-GB.Own-user.png | 3 + .../test_pillView-iPad-en-GB.Room.png | 3 + ...lView-iPad-en-GB.User-with-a-long-name.png | 3 + .../test_pillView-iPad-en-GB.User.png | 3 + .../test_pillView-iPad-pseudo.All-Users.png | 3 - ...t_pillView-iPad-pseudo.Loaded-Long-Own.png | 3 - .../test_pillView-iPad-pseudo.Loaded-Long.png | 3 - .../test_pillView-iPad-pseudo.Loading-Own.png | 3 - .../test_pillView-iPad-pseudo.Loading.png | 3 - ...test_pillView-iPad-pseudo.Message-link.png | 3 + .../test_pillView-iPad-pseudo.Own-user.png | 3 + .../test_pillView-iPad-pseudo.Room.png | 3 + ...View-iPad-pseudo.User-with-a-long-name.png | 3 + .../test_pillView-iPad-pseudo.User.png | 3 + ...est_pillView-iPhone-16-en-GB.All-Users.png | 3 - ...llView-iPhone-16-en-GB.Loaded-Long-Own.png | 3 - ...t_pillView-iPhone-16-en-GB.Loaded-Long.png | 3 - ...t_pillView-iPhone-16-en-GB.Loading-Own.png | 3 - .../test_pillView-iPhone-16-en-GB.Loading.png | 3 - ..._pillView-iPhone-16-en-GB.Message-link.png | 3 + ...test_pillView-iPhone-16-en-GB.Own-user.png | 3 + .../test_pillView-iPhone-16-en-GB.Room.png | 3 + ...-iPhone-16-en-GB.User-with-a-long-name.png | 3 + .../test_pillView-iPhone-16-en-GB.User.png | 3 + ...st_pillView-iPhone-16-pseudo.All-Users.png | 3 - ...lView-iPhone-16-pseudo.Loaded-Long-Own.png | 3 - ..._pillView-iPhone-16-pseudo.Loaded-Long.png | 3 - ..._pillView-iPhone-16-pseudo.Loading-Own.png | 3 - ...test_pillView-iPhone-16-pseudo.Loading.png | 3 - ...pillView-iPhone-16-pseudo.Message-link.png | 3 + ...est_pillView-iPhone-16-pseudo.Own-user.png | 3 + .../test_pillView-iPhone-16-pseudo.Room.png | 3 + ...iPhone-16-pseudo.User-with-a-long-name.png | 3 + .../test_pillView-iPhone-16-pseudo.User.png | 3 + .../test_roomSelectionScreen-iPad-en-GB.1.png | 4 +- ...test_roomSelectionScreen-iPad-pseudo.1.png | 4 +- ..._roomSelectionScreen-iPhone-16-en-GB.1.png | 4 +- ...roomSelectionScreen-iPhone-16-pseudo.1.png | 4 +- .../AttributedStringBuilderTests.swift | 82 ++++++- UnitTests/Sources/HomeScreenRoomTests.swift | 1 + UnitTests/Sources/LoggingTests.swift | 1 + UnitTests/Sources/PillContextTests.swift | 222 +++++++++++++++++- UnitTests/Sources/RoomSummaryTests.swift | 1 + .../Sources/TimelineViewModelTests.swift | 15 +- 100 files changed, 1015 insertions(+), 245 deletions(-) delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.All-Users.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Loaded-Long-Own.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Loaded-Long.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Loading-Own.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Loading.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Message-link.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Own-user.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Room.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.User-with-a-long-name.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.User.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.All-Users.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Loaded-Long-Own.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Loaded-Long.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Loading-Own.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Loading.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Message-link.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Own-user.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Room.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.User-with-a-long-name.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.User.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.All-Users.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Loaded-Long-Own.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Loaded-Long.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Loading-Own.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Loading.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Message-link.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Own-user.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Room.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.User-with-a-long-name.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.User.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.All-Users.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Loaded-Long-Own.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Loaded-Long.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Loading-Own.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Loading.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Message-link.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Own-user.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Room.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.User-with-a-long-name.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.User.png diff --git a/ElementX/Sources/FlowCoordinators/MediaEventsTimelineFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/MediaEventsTimelineFlowCoordinator.swift index 38754db2ce..ae184039fa 100644 --- a/ElementX/Sources/FlowCoordinators/MediaEventsTimelineFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/MediaEventsTimelineFlowCoordinator.swift @@ -93,7 +93,8 @@ class MediaEventsTimelineFlowCoordinator: FlowCoordinatorProtocol { appMediator: appMediator, emojiProvider: emojiProvider, userIndicatorController: userIndicatorController, - timelineControllerFactory: timelineControllerFactory) + timelineControllerFactory: timelineControllerFactory, + clientProxy: userSession.clientProxy) let coordinator = MediaEventsTimelineScreenCoordinator(parameters: parameters) diff --git a/ElementX/Sources/FlowCoordinators/PinnedEventsTimelineFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/PinnedEventsTimelineFlowCoordinator.swift index 608b77e926..bcd0e1300d 100644 --- a/ElementX/Sources/FlowCoordinators/PinnedEventsTimelineFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/PinnedEventsTimelineFlowCoordinator.swift @@ -78,7 +78,8 @@ class PinnedEventsTimelineFlowCoordinator: FlowCoordinatorProtocol { voiceMessageMediaManager: userSession.voiceMessageMediaManager, appMediator: appMediator, emojiProvider: emojiProvider, - timelineControllerFactory: timelineControllerFactory)) + timelineControllerFactory: timelineControllerFactory, + clientProxy: userSession.clientProxy)) coordinator.actions .sink { [weak self] action in diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 969e54dfce..b73c4b2ec4 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -3225,6 +3225,146 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable { return roomPreviewForIdentifierViaReturnValue } } + //MARK: - roomSummaryForIdentifier + + var roomSummaryForIdentifierUnderlyingCallsCount = 0 + var roomSummaryForIdentifierCallsCount: Int { + get { + if Thread.isMainThread { + return roomSummaryForIdentifierUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = roomSummaryForIdentifierUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + roomSummaryForIdentifierUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + roomSummaryForIdentifierUnderlyingCallsCount = newValue + } + } + } + } + var roomSummaryForIdentifierCalled: Bool { + return roomSummaryForIdentifierCallsCount > 0 + } + var roomSummaryForIdentifierReceivedIdentifier: String? + var roomSummaryForIdentifierReceivedInvocations: [String] = [] + + var roomSummaryForIdentifierUnderlyingReturnValue: RoomSummary? + var roomSummaryForIdentifierReturnValue: RoomSummary? { + get { + if Thread.isMainThread { + return roomSummaryForIdentifierUnderlyingReturnValue + } else { + var returnValue: RoomSummary?? = nil + DispatchQueue.main.sync { + returnValue = roomSummaryForIdentifierUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + roomSummaryForIdentifierUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + roomSummaryForIdentifierUnderlyingReturnValue = newValue + } + } + } + } + var roomSummaryForIdentifierClosure: ((String) -> RoomSummary?)? + + func roomSummaryForIdentifier(_ identifier: String) -> RoomSummary? { + roomSummaryForIdentifierCallsCount += 1 + roomSummaryForIdentifierReceivedIdentifier = identifier + DispatchQueue.main.async { + self.roomSummaryForIdentifierReceivedInvocations.append(identifier) + } + if let roomSummaryForIdentifierClosure = roomSummaryForIdentifierClosure { + return roomSummaryForIdentifierClosure(identifier) + } else { + return roomSummaryForIdentifierReturnValue + } + } + //MARK: - roomSummaryForAlias + + var roomSummaryForAliasUnderlyingCallsCount = 0 + var roomSummaryForAliasCallsCount: Int { + get { + if Thread.isMainThread { + return roomSummaryForAliasUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = roomSummaryForAliasUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + roomSummaryForAliasUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + roomSummaryForAliasUnderlyingCallsCount = newValue + } + } + } + } + var roomSummaryForAliasCalled: Bool { + return roomSummaryForAliasCallsCount > 0 + } + var roomSummaryForAliasReceivedAlias: String? + var roomSummaryForAliasReceivedInvocations: [String] = [] + + var roomSummaryForAliasUnderlyingReturnValue: RoomSummary? + var roomSummaryForAliasReturnValue: RoomSummary? { + get { + if Thread.isMainThread { + return roomSummaryForAliasUnderlyingReturnValue + } else { + var returnValue: RoomSummary?? = nil + DispatchQueue.main.sync { + returnValue = roomSummaryForAliasUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + roomSummaryForAliasUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + roomSummaryForAliasUnderlyingReturnValue = newValue + } + } + } + } + var roomSummaryForAliasClosure: ((String) -> RoomSummary?)? + + func roomSummaryForAlias(_ alias: String) -> RoomSummary? { + roomSummaryForAliasCallsCount += 1 + roomSummaryForAliasReceivedAlias = alias + DispatchQueue.main.async { + self.roomSummaryForAliasReceivedInvocations.append(alias) + } + if let roomSummaryForAliasClosure = roomSummaryForAliasClosure { + return roomSummaryForAliasClosure(alias) + } else { + return roomSummaryForAliasReturnValue + } + } //MARK: - loadUserDisplayName var loadUserDisplayNameUnderlyingCallsCount = 0 diff --git a/ElementX/Sources/Mocks/RoomSummaryProviderMock.swift b/ElementX/Sources/Mocks/RoomSummaryProviderMock.swift index 1c32989dce..b65612b570 100644 --- a/ElementX/Sources/Mocks/RoomSummaryProviderMock.swift +++ b/ElementX/Sources/Mocks/RoomSummaryProviderMock.swift @@ -67,6 +67,31 @@ extension RoomSummaryProviderMock { } } +extension RoomSummary { + static func mock(id: String, + name: String, + canonicalAlias: String? = nil) -> RoomSummary { + RoomSummary(roomListItem: RoomListItemSDKMock(), + id: id, + joinRequestType: nil, + name: name, + isDirect: false, + avatarURL: nil, + heroes: [], + lastMessage: AttributedString("I do not wish to take the trouble to understand mysticism"), + lastMessageFormattedTimestamp: "14:56", + unreadMessagesCount: 0, + unreadMentionsCount: 0, + unreadNotificationsCount: 0, + notificationMode: .allMessages, + canonicalAlias: canonicalAlias, + alternativeAliases: [], + hasOngoingCall: false, + isMarkedUnread: false, + isFavourite: false) + } +} + extension Array where Element == RoomSummary { static let mockRooms: [Element] = [ RoomSummary(roomListItem: RoomListItemSDKMock(), @@ -83,6 +108,7 @@ extension Array where Element == RoomSummary { unreadNotificationsCount: 0, notificationMode: .allMessages, canonicalAlias: nil, + alternativeAliases: [], hasOngoingCall: false, isMarkedUnread: false, isFavourite: false), @@ -99,7 +125,8 @@ extension Array where Element == RoomSummary { unreadMentionsCount: 0, unreadNotificationsCount: 2, notificationMode: .mute, - canonicalAlias: nil, + canonicalAlias: "#foundation-and-empire:matrix.org", + alternativeAliases: [], hasOngoingCall: false, isMarkedUnread: false, isFavourite: false), @@ -117,6 +144,7 @@ extension Array where Element == RoomSummary { unreadNotificationsCount: 0, notificationMode: .mentionsAndKeywordsOnly, canonicalAlias: nil, + alternativeAliases: [], hasOngoingCall: false, isMarkedUnread: false, isFavourite: false), @@ -134,6 +162,7 @@ extension Array where Element == RoomSummary { unreadNotificationsCount: 2, notificationMode: .allMessages, canonicalAlias: nil, + alternativeAliases: [], hasOngoingCall: false, isMarkedUnread: false, isFavourite: false), @@ -151,6 +180,7 @@ extension Array where Element == RoomSummary { unreadNotificationsCount: 1, notificationMode: .allMessages, canonicalAlias: nil, + alternativeAliases: [], hasOngoingCall: true, isMarkedUnread: false, isFavourite: false), @@ -168,6 +198,7 @@ extension Array where Element == RoomSummary { unreadNotificationsCount: 0, notificationMode: .mute, canonicalAlias: nil, + alternativeAliases: [], hasOngoingCall: true, isMarkedUnread: false, isFavourite: false), @@ -185,6 +216,7 @@ extension Array where Element == RoomSummary { unreadNotificationsCount: 0, notificationMode: nil, canonicalAlias: nil, + alternativeAliases: [], hasOngoingCall: false, isMarkedUnread: false, isFavourite: false) @@ -235,6 +267,7 @@ extension Array where Element == RoomSummary { unreadNotificationsCount: 0, notificationMode: nil, canonicalAlias: "#footest:somewhere.org", + alternativeAliases: [], hasOngoingCall: false, isMarkedUnread: false, isFavourite: false), @@ -252,6 +285,7 @@ extension Array where Element == RoomSummary { unreadNotificationsCount: 0, notificationMode: nil, canonicalAlias: nil, + alternativeAliases: [], hasOngoingCall: false, isMarkedUnread: false, isFavourite: false) diff --git a/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilder.swift b/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilder.swift index ff4f056354..2eb9ef85fe 100644 --- a/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilder.swift +++ b/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilder.swift @@ -224,7 +224,8 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol { case .atRoom: attributedString.addAttribute(.MatrixAllUsersMention, value: true, range: match.range) case .roomAlias(let alias): - if let url = try? matrixToRoomAliasPermalink(roomAlias: alias) { + if let urlString = try? matrixToRoomAliasPermalink(roomAlias: alias), + let url = URL(string: urlString) { attributedString.addAttribute(.link, value: url, range: match.range) } case .matrixURI(let uri): @@ -282,13 +283,13 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol { case .user(let userID): mentionBuilder.handleUserMention(for: attributedString, in: range, url: url, userID: userID, userDisplayName: nil) case .room(let roomID): - attributedString.addAttributes([.MatrixRoomID: roomID], range: range) + mentionBuilder.handleRoomIDMention(for: attributedString, in: range, url: url, roomID: roomID) case .roomAlias(let alias): - attributedString.addAttributes([.MatrixRoomAlias: alias], range: range) + mentionBuilder.handleRoomAliasMention(for: attributedString, in: range, url: url, roomAlias: alias) case .eventOnRoomId(let roomID, let eventID): - attributedString.addAttributes([.MatrixEventOnRoomID: EventOnRoomIDAttribute.Value(roomID: roomID, eventID: eventID)], range: range) + mentionBuilder.handleEventOnRoomIDMention(for: attributedString, in: range, url: url, eventID: eventID, roomID: roomID) case .eventOnRoomAlias(let alias, let eventID): - attributedString.addAttributes([.MatrixEventOnRoomAlias: EventOnRoomAliasAttribute.Value(alias: alias, eventID: eventID)], range: range) + mentionBuilder.handleEventOnRoomAliasMention(for: attributedString, in: range, url: url, eventID: eventID, roomAlias: alias) } } } @@ -364,6 +365,10 @@ extension NSAttributedString.Key { protocol MentionBuilderProtocol { func handleUserMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, userID: String, userDisplayName: String?) + func handleRoomIDMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, roomID: String) + func handleRoomAliasMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, roomAlias: String) + func handleEventOnRoomAliasMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, eventID: String, roomAlias: String) + func handleEventOnRoomIDMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, eventID: String, roomID: String) func handleAllUsersMention(for attributedString: NSMutableAttributedString, in range: NSRange) } diff --git a/ElementX/Sources/Other/Pills/MentionBuilder.swift b/ElementX/Sources/Other/Pills/MentionBuilder.swift index fb1eee91cb..d140c026fc 100644 --- a/ElementX/Sources/Other/Pills/MentionBuilder.swift +++ b/ElementX/Sources/Other/Pills/MentionBuilder.swift @@ -9,13 +9,20 @@ import Foundation import UIKit struct MentionBuilder: MentionBuilderProtocol { - func handleUserMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, userID: String, userDisplayName: String?) { - let attributes = attributedString.attributes(at: 0, longestEffectiveRange: nil, in: range) - let font = attributes[.font] as? UIFont ?? .preferredFont(forTextStyle: .body) - let blockquote = attributes[.MatrixBlockquote] - let foregroundColor = attributes[.foregroundColor] as? UIColor ?? .compound.textPrimary + struct AttributesToRestore { + let font: UIFont + let blockquote: Bool? + let foregroundColor: UIColor + } + + func handleUserMention(for attributedString: NSMutableAttributedString, + in range: NSRange, + url: URL, + userID: String, + userDisplayName: String?) { + let attributesToRestore = getAttributesToRestore(for: attributedString, in: range) - let attachmentData = PillTextAttachmentData(type: .user(userID: userID), font: font) + let attachmentData = PillTextAttachmentData(type: .user(userID: userID), font: attributesToRestore.font) guard let attachment = PillTextAttachment(attachmentData: attachmentData) else { attributedString.addAttribute(.MatrixUserID, value: userID, range: range) @@ -26,34 +33,151 @@ struct MentionBuilder: MentionBuilderProtocol { return } - var attachmentAttributes: [NSAttributedString.Key: Any] = [.link: url, .MatrixUserID: userID, .font: font, .foregroundColor: foregroundColor] - if let blockquote { - // mentions can be in blockquotes, so if the replaced string was in one, we keep the attribute - attachmentAttributes[.MatrixBlockquote] = blockquote - } - let attachmentString = NSMutableAttributedString(attachment: attachment) - attachmentString.addAttributes(attachmentAttributes, range: NSRange(location: 0, length: attachmentString.length)) - attributedString.replaceCharacters(in: range, with: attachmentString) + var attachmentAttributes: [NSAttributedString.Key: Any] = [.link: url, + .MatrixUserID: userID, + .font: attributesToRestore.font, + .foregroundColor: attributesToRestore.foregroundColor] + attachmentAttributes.addBlockquoteIfNeeded(attributesToRestore.blockquote) + attachmentAttributes.addMatrixUsernameIfNeeded(userDisplayName) + + setPillAttachment(attachment: attachment, + attributedString: attributedString, + in: range, + with: attachmentAttributes) } func handleAllUsersMention(for attributedString: NSMutableAttributedString, in range: NSRange) { - let attributes = attributedString.attributes(at: 0, longestEffectiveRange: nil, in: range) - let font = attributes[.font] as? UIFont ?? .preferredFont(forTextStyle: .body) - let blockquote = attributes[.MatrixBlockquote] - let foregroundColor = attributes[.foregroundColor] as? UIColor ?? .compound.textPrimary + let attributesToRestore = getAttributesToRestore(for: attributedString, in: range) + + let attachmentData = PillTextAttachmentData(type: .allUsers, font: attributesToRestore.font) + guard let attachment = PillTextAttachment(attachmentData: attachmentData) else { + return + } + + var attachmentAttributes: [NSAttributedString.Key: Any] = [.font: attributesToRestore.font, + .MatrixAllUsersMention: true, + .foregroundColor: attributesToRestore.foregroundColor] + attachmentAttributes.addBlockquoteIfNeeded(attributesToRestore.blockquote) + + setPillAttachment(attachment: attachment, + attributedString: attributedString, + in: range, + with: attachmentAttributes) + } + + func handleRoomIDMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, roomID: String) { + let attributesToRestore = getAttributesToRestore(for: attributedString, in: range) - let attachmentData = PillTextAttachmentData(type: .allUsers, font: font) + 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] = [.font: font, .MatrixAllUsersMention: true, .foregroundColor: foregroundColor] - if let blockquote { - // mentions can be in blockquotes, so if the replaced string was in one, we keep the attribute - attachmentAttributes[.MatrixBlockquote] = blockquote + var attachmentAttributes: [NSAttributedString.Key: Any] = [.link: url, + .MatrixRoomID: roomID, + .font: attributesToRestore.font, + .foregroundColor: attributesToRestore.foregroundColor] + attachmentAttributes.addBlockquoteIfNeeded(attributesToRestore.blockquote) + + setPillAttachment(attachment: attachment, + attributedString: attributedString, + in: range, + with: attachmentAttributes) + } + + func handleRoomAliasMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, roomAlias: String) { + let attributesToRestore = getAttributesToRestore(for: attributedString, in: range) + + let attachmentData = PillTextAttachmentData(type: .roomAlias(roomAlias), font: attributesToRestore.font) + guard let attachment = PillTextAttachment(attachmentData: attachmentData) else { + attributedString.addAttribute(.MatrixRoomAlias, value: roomAlias, range: range) + return } + + var attachmentAttributes: [NSAttributedString.Key: Any] = [.link: url, + .MatrixRoomAlias: roomAlias, + .font: attributesToRestore.font, + .foregroundColor: attributesToRestore.foregroundColor] + attachmentAttributes.addBlockquoteIfNeeded(attributesToRestore.blockquote) + + setPillAttachment(attachment: attachment, + attributedString: attributedString, + in: range, + with: attachmentAttributes) + } + + func handleEventOnRoomAliasMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, eventID: String, roomAlias: String) { + let attributesToRestore = getAttributesToRestore(for: attributedString, in: range) + + let attachmentData = PillTextAttachmentData(type: .event(room: .roomAlias(roomAlias)), font: attributesToRestore.font) + guard let attachment = PillTextAttachment(attachmentData: attachmentData) else { + attributedString.addAttribute(.MatrixEventOnRoomAlias, value: EventOnRoomAliasAttribute.Value(alias: roomAlias, eventID: eventID), range: range) + return + } + + var attachmentAttributes: [NSAttributedString.Key: Any] = [.link: url, + .MatrixEventOnRoomAlias: EventOnRoomAliasAttribute.Value(alias: roomAlias, eventID: eventID), + .font: attributesToRestore.font, + .foregroundColor: attributesToRestore.foregroundColor] + attachmentAttributes.addBlockquoteIfNeeded(attributesToRestore.blockquote) + + setPillAttachment(attachment: attachment, + attributedString: attributedString, + in: range, + with: attachmentAttributes) + } + + func handleEventOnRoomIDMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, eventID: String, roomID: String) { + let attributesToRestore = getAttributesToRestore(for: attributedString, in: range) + + let attachmentData = PillTextAttachmentData(type: .event(room: .roomID(roomID)), font: attributesToRestore.font) + guard let attachment = PillTextAttachment(attachmentData: attachmentData) else { + attributedString.addAttribute(.MatrixEventOnRoomID, value: EventOnRoomIDAttribute.Value(roomID: roomID, eventID: eventID), range: range) + return + } + + var attachmentAttributes: [NSAttributedString.Key: Any] = [.link: url, + .MatrixEventOnRoomID: EventOnRoomIDAttribute.Value(roomID: roomID, eventID: eventID), + .font: attributesToRestore.font, + .foregroundColor: attributesToRestore.foregroundColor] + attachmentAttributes.addBlockquoteIfNeeded(attributesToRestore.blockquote) + + setPillAttachment(attachment: attachment, + attributedString: attributedString, + in: range, + with: attachmentAttributes) + } + + private func getAttributesToRestore(for attributedString: NSMutableAttributedString, in range: NSRange) -> AttributesToRestore { + let attributes = attributedString.attributes(at: 0, longestEffectiveRange: nil, in: range) + let font = attributes[.font] as? UIFont ?? .preferredFont(forTextStyle: .body) + let blockquote = attributes[.MatrixBlockquote] as? Bool + let foregroundColor = attributes[.foregroundColor] as? UIColor ?? .compound.textPrimary + + return AttributesToRestore(font: font, blockquote: blockquote, foregroundColor: foregroundColor) + } + + private func setPillAttachment(attachment: PillTextAttachment, + attributedString: NSMutableAttributedString, + in range: NSRange, + with attributes: [NSAttributedString.Key: Any]) { let attachmentString = NSMutableAttributedString(attachment: attachment) - attachmentString.addAttributes(attachmentAttributes, range: NSRange(location: 0, length: attachmentString.length)) + attachmentString.addAttributes(attributes, range: NSRange(location: 0, length: attachmentString.length)) attributedString.replaceCharacters(in: range, with: attachmentString) } } + +private extension Dictionary where Key == NSAttributedString.Key, Value == Any { + mutating func addBlockquoteIfNeeded(_ value: Bool?) { + if let value { + self[.MatrixBlockquote] = value + } + } + + mutating func addMatrixUsernameIfNeeded(_ value: String?) { + if let value { + self[.MatrixUserDisplayName] = value + } + } +} diff --git a/ElementX/Sources/Other/Pills/PillAttachmentViewProvider.swift b/ElementX/Sources/Other/Pills/PillAttachmentViewProvider.swift index 2f8125f6a7..662076bd30 100644 --- a/ElementX/Sources/Other/Pills/PillAttachmentViewProvider.swift +++ b/ElementX/Sources/Other/Pills/PillAttachmentViewProvider.swift @@ -45,7 +45,7 @@ final class PillAttachmentViewProvider: NSTextAttachmentViewProvider, NSSecureCo let mediaProvider: MediaProviderProtocol? if ProcessInfo.isXcodePreview || ProcessInfo.isRunningTests { // The mock viewModel simulates the loading logic for testing purposes - context = PillContext.mock(type: .loadUser(isOwn: false)) + context = PillContext.mock(viewState: .mention(isOwnMention: false, displayText: "Alice"), delay: .seconds(2)) mediaProvider = MediaProviderMock(configuration: .init()) } else if let timelineContext = delegate?.timelineContext { context = PillContext(timelineContext: timelineContext, data: pillData) diff --git a/ElementX/Sources/Other/Pills/PillContext.swift b/ElementX/Sources/Other/Pills/PillContext.swift index cd5a99affc..135657126b 100644 --- a/ElementX/Sources/Other/Pills/PillContext.swift +++ b/ElementX/Sources/Other/Pills/PillContext.swift @@ -10,75 +10,77 @@ import Foundation @MainActor final class PillContext: ObservableObject { - struct PillViewState: Equatable { - let isOwnMention: Bool - let displayText: String - } - - @Published private(set) var viewState: PillViewState + @Published var viewState: PillViewState = .undefined - private var cancellable: AnyCancellable? + let data: PillTextAttachmentData + var cancellable: AnyCancellable? init(timelineContext: TimelineViewModel.Context, data: PillTextAttachmentData) { - switch data.type { - case let .user(id): - let isOwnMention = id == timelineContext.viewState.ownUserID - if let profile = timelineContext.viewState.members[id] { - var name = id - if let displayName = profile.displayName { - name = "@\(displayName)" - } - viewState = PillViewState(isOwnMention: isOwnMention, displayText: name) - } else { - viewState = PillViewState(isOwnMention: isOwnMention, displayText: id) - cancellable = timelineContext.$viewState.sink { [weak self] viewState in - guard let self else { - return - } - if let profile = viewState.members[id] { - var name = id - if let displayName = profile.displayName { - name = "@\(displayName)" - } - self.viewState = PillViewState(isOwnMention: isOwnMention, displayText: name) - cancellable = nil - } - } + self.data = data + timelineContext.viewState.pillContextUpdater?(self) + } +} + +extension PillContext { + static func mock(viewState: PillViewState, delay: Duration? = nil) -> PillContext { + // This is just for previews so the internal data doesn't really matter + let viewModel = PillContext(timelineContext: TimelineViewModel.mock.context, data: PillTextAttachmentData(type: .allUsers, font: .preferredFont(forTextStyle: .body))) + if let delay { + viewModel.viewState = .mention(isOwnMention: false, displayText: "placeholder") + Task { + try? await Task.sleep(for: delay) + viewModel.viewState = viewState } - case .allUsers: - viewState = PillViewState(isOwnMention: true, displayText: PillConstants.atRoom) + } else { + viewModel.viewState = viewState } + return viewModel } } -extension PillContext { - enum MockType { - case loadUser(isOwn: Bool) - case loadedUser(isOwn: Bool) - case allUsers +enum PillViewState: Equatable { + enum PillImage: Equatable { + case link + case roomAvatar(RoomAvatar) } - static func mock(type: MockType) -> PillContext { - let testID = "@test:test.com" - let pillType: PillType - switch type { - case .loadUser(let isOwn): - pillType = .user(userID: testID) - let viewModel = PillContext(timelineContext: TimelineViewModel.mock.context, data: PillTextAttachmentData(type: pillType, font: .preferredFont(forTextStyle: .body))) - viewModel.viewState = PillViewState(isOwnMention: isOwn, displayText: testID) - Task { - try? await Task.sleep(for: .seconds(2)) - viewModel.viewState = PillViewState(isOwnMention: isOwn, displayText: "@Test Long Display Text") - } - return viewModel - case .loadedUser(let isOwn): - pillType = .user(userID: "@test:test.com") - let viewModel = PillContext(timelineContext: TimelineViewModel.mock.context, data: PillTextAttachmentData(type: pillType, font: .preferredFont(forTextStyle: .body))) - viewModel.viewState = PillViewState(isOwnMention: isOwn, displayText: "@Very Very Long Test Display Text") - return viewModel - case .allUsers: - pillType = .allUsers - return PillContext(timelineContext: TimelineViewModel.mock.context, data: PillTextAttachmentData(type: pillType, font: .preferredFont(forTextStyle: .body))) + case mention(isOwnMention: Bool, displayText: String) + case reference(avatar: PillImage, displayText: String) + case undefined + + var isOwnMention: Bool { + switch self { + case .mention(let isOwnMention, _): + return isOwnMention + default: + return false + } + } + + var displayText: String { + switch self { + case .mention(_, let displayText), .reference(_, let displayText): + return displayText + case .undefined: + return "" + } + } + + var isUndefined: Bool { + switch self { + case .undefined: + return true + default: + return false + } + } + + var image: PillImage? { + switch self { + case .reference(let avatar, _): + return avatar + default: + return nil } } } diff --git a/ElementX/Sources/Other/Pills/PillTextAttachmentData.swift b/ElementX/Sources/Other/Pills/PillTextAttachmentData.swift index bad906b3db..25d84a7608 100644 --- a/ElementX/Sources/Other/Pills/PillTextAttachmentData.swift +++ b/ElementX/Sources/Other/Pills/PillTextAttachmentData.swift @@ -9,6 +9,21 @@ import Foundation import UIKit enum PillType: Codable, Equatable { + enum EventRoom: Codable, Equatable { + case roomAlias(String) + case roomID(String) + + var value: String { + switch self { + case .roomAlias(let value), .roomID(let value): + return value + } + } + } + + case event(room: EventRoom) + case roomAlias(String) + case roomID(String) /// A pill that mentions a user case user(userID: String) /// A pill that mentions all users in a room diff --git a/ElementX/Sources/Other/Pills/PillView.swift b/ElementX/Sources/Other/Pills/PillView.swift index 1c89f5239d..8e1093b5a5 100644 --- a/ElementX/Sources/Other/Pills/PillView.swift +++ b/ElementX/Sources/Other/Pills/PillView.swift @@ -5,6 +5,7 @@ // Please see LICENSE files in the repository root for full details. // +import Compound import SwiftUI struct PillView: View { @@ -22,18 +23,42 @@ struct PillView: View { } var body: some View { - Text(context.viewState.displayText) - .font(.compound.bodyLGSemibold) - .foregroundColor(textColor) - .lineLimit(1) - .padding(.leading, 4) - .padding(.trailing, 6) - .padding(.vertical, 1) - .background { Capsule().foregroundColor(backgroundColor) } + mainContent .onChange(of: context.viewState.displayText) { didChangeText() } } + + @ViewBuilder + private var mainContent: some View { + HStack(spacing: 4) { + image + Text(context.viewState.displayText) + .font(.compound.bodyLGSemibold) + .foregroundColor(textColor) + .lineLimit(1) + } + .padding(.leading, 4) + .padding(.trailing, 6) + .padding(.vertical, 1) + .background { Capsule().foregroundColor(backgroundColor) } + } + + @ViewBuilder + private var image: some View { + if let image = context.viewState.image { + switch image { + case .link: + CompoundIcon(\.link, size: .custom(12), relativeTo: .compound.bodyLGSemibold) + .padding(2) + .foregroundStyle(.compound.bgCanvasDefault) + .background(.compound.textLinkExternal) + .clipShape(Circle()) + case .roomAvatar(let avatar): + RoomAvatarImage(avatar: avatar, avatarSize: .custom(16), mediaProvider: mediaProvider) + } + } + } } struct PillView_Previews: PreviewProvider, TestablePreview { @@ -41,24 +66,27 @@ struct PillView_Previews: PreviewProvider, TestablePreview { static var previews: some View { PillView(mediaProvider: mockMediaProvider, - context: PillContext.mock(type: .loadUser(isOwn: false))) { } + context: PillContext.mock(viewState: .mention(isOwnMention: false, + displayText: "@Alice"))) { } .frame(maxWidth: PillConstants.mockMaxWidth) - .previewDisplayName("Loading") + .previewDisplayName("User") PillView(mediaProvider: mockMediaProvider, - context: PillContext.mock(type: .loadUser(isOwn: true))) { } + context: PillContext.mock(viewState: .mention(isOwnMention: false, + displayText: "@Alice but with a very very long name"))) { } .frame(maxWidth: PillConstants.mockMaxWidth) - .previewDisplayName("Loading Own") + .previewDisplayName("User with a long name") PillView(mediaProvider: mockMediaProvider, - context: PillContext.mock(type: .loadedUser(isOwn: false))) { } + context: PillContext.mock(viewState: .mention(isOwnMention: true, + displayText: "@Alice"))) { } .frame(maxWidth: PillConstants.mockMaxWidth) - .previewDisplayName("Loaded Long") + .previewDisplayName("Own user") PillView(mediaProvider: mockMediaProvider, - context: PillContext.mock(type: .loadedUser(isOwn: true))) { } + context: PillContext.mock(viewState: .reference(avatar: .roomAvatar(.room(id: "roomID", name: "Room", avatarURL: nil)), displayText: "Room"))) { } .frame(maxWidth: PillConstants.mockMaxWidth) - .previewDisplayName("Loaded Long Own") + .previewDisplayName("Room") PillView(mediaProvider: mockMediaProvider, - context: PillContext.mock(type: .allUsers)) { } + context: PillContext.mock(viewState: .reference(avatar: .link, displayText: L10n.screenRoomEventPill("Room")))) { } .frame(maxWidth: PillConstants.mockMaxWidth) - .previewDisplayName("All Users") + .previewDisplayName("Message link") } } diff --git a/ElementX/Sources/Other/Pills/PlainMentionBuilder.swift b/ElementX/Sources/Other/Pills/PlainMentionBuilder.swift index cf7310ce72..2ad6f1db17 100644 --- a/ElementX/Sources/Other/Pills/PlainMentionBuilder.swift +++ b/ElementX/Sources/Other/Pills/PlainMentionBuilder.swift @@ -9,6 +9,12 @@ import Foundation // In the future we might use this to do some customisation in what is plain text used to represent mentions. struct PlainMentionBuilder: MentionBuilderProtocol { + func handleEventOnRoomAliasMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, eventID: String, roomAlias: String) { } + + func handleEventOnRoomIDMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, eventID: String, roomID: String) { } + + func handleRoomAliasMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, roomAlias: String) { } + func handleAllUsersMention(for attributedString: NSMutableAttributedString, in range: NSRange) { } func handleUserMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, userID: String, userDisplayName: String?) { @@ -17,4 +23,6 @@ struct PlainMentionBuilder: MentionBuilderProtocol { } attributedString.insert(NSAttributedString(string: "@"), at: range.location) } + + func handleRoomIDMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, roomID: String) { } } diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenInviteCell.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenInviteCell.swift index dd548d2356..2d61d8bc2e 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenInviteCell.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenInviteCell.swift @@ -193,6 +193,7 @@ private extension HomeScreenRoom { unreadNotificationsCount: 0, notificationMode: nil, canonicalAlias: "#footest:somewhere.org", + alternativeAliases: [], hasOngoingCall: false, isMarkedUnread: false, isFavourite: false) @@ -220,6 +221,7 @@ private extension HomeScreenRoom { unreadNotificationsCount: 0, notificationMode: nil, canonicalAlias: alias, + alternativeAliases: [], hasOngoingCall: false, isMarkedUnread: false, isFavourite: false) diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenKnockedCell.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenKnockedCell.swift index b3914d1a55..cf7292dc01 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenKnockedCell.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenKnockedCell.swift @@ -157,6 +157,7 @@ private extension HomeScreenRoom { unreadNotificationsCount: 0, notificationMode: nil, canonicalAlias: "#footest:somewhere.org", + alternativeAliases: [], hasOngoingCall: false, isMarkedUnread: false, isFavourite: false) @@ -184,6 +185,7 @@ private extension HomeScreenRoom { unreadNotificationsCount: 0, notificationMode: nil, canonicalAlias: alias, + alternativeAliases: [], hasOngoingCall: false, isMarkedUnread: false, isFavourite: false) diff --git a/ElementX/Sources/Screens/MediaEventsTimelineScreen/MediaEventsTimelineScreenCoordinator.swift b/ElementX/Sources/Screens/MediaEventsTimelineScreen/MediaEventsTimelineScreenCoordinator.swift index ed7e47744f..2bef55beb1 100644 --- a/ElementX/Sources/Screens/MediaEventsTimelineScreen/MediaEventsTimelineScreenCoordinator.swift +++ b/ElementX/Sources/Screens/MediaEventsTimelineScreen/MediaEventsTimelineScreenCoordinator.swift @@ -19,6 +19,7 @@ struct MediaEventsTimelineScreenCoordinatorParameters { let emojiProvider: EmojiProviderProtocol let userIndicatorController: UserIndicatorControllerProtocol let timelineControllerFactory: TimelineControllerFactoryProtocol + let clientProxy: ClientProxyProtocol } enum MediaEventsTimelineScreenCoordinatorAction { @@ -49,7 +50,8 @@ final class MediaEventsTimelineScreenCoordinator: CoordinatorProtocol { appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics, emojiProvider: parameters.emojiProvider, - timelineControllerFactory: parameters.timelineControllerFactory) + timelineControllerFactory: parameters.timelineControllerFactory, + clientProxy: parameters.clientProxy) let filesTimelineViewModel = TimelineViewModel(roomProxy: parameters.roomProxy, timelineController: parameters.filesTimelineController, @@ -61,7 +63,8 @@ final class MediaEventsTimelineScreenCoordinator: CoordinatorProtocol { appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics, emojiProvider: parameters.emojiProvider, - timelineControllerFactory: parameters.timelineControllerFactory) + timelineControllerFactory: parameters.timelineControllerFactory, + clientProxy: parameters.clientProxy) viewModel = MediaEventsTimelineScreenViewModel(mediaTimelineViewModel: mediaTimelineViewModel, filesTimelineViewModel: filesTimelineViewModel, diff --git a/ElementX/Sources/Screens/MediaEventsTimelineScreen/View/MediaEventsTimelineScreen.swift b/ElementX/Sources/Screens/MediaEventsTimelineScreen/View/MediaEventsTimelineScreen.swift index 1b6acc7ce1..bbe1842d7f 100644 --- a/ElementX/Sources/Screens/MediaEventsTimelineScreen/View/MediaEventsTimelineScreen.swift +++ b/ElementX/Sources/Screens/MediaEventsTimelineScreen/View/MediaEventsTimelineScreen.swift @@ -274,6 +274,7 @@ struct MediaEventsTimelineScreen_Previews: PreviewProvider, TestablePreview { appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics, emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), - timelineControllerFactory: TimelineControllerFactoryMock(.init())) + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + clientProxy: ClientProxyMock(.init())) } } diff --git a/ElementX/Sources/Screens/PinnedEventsTimelineScreen/PinnedEventsTimelineScreenCoordinator.swift b/ElementX/Sources/Screens/PinnedEventsTimelineScreen/PinnedEventsTimelineScreenCoordinator.swift index c7926c73b6..133d783689 100644 --- a/ElementX/Sources/Screens/PinnedEventsTimelineScreen/PinnedEventsTimelineScreenCoordinator.swift +++ b/ElementX/Sources/Screens/PinnedEventsTimelineScreen/PinnedEventsTimelineScreenCoordinator.swift @@ -17,6 +17,7 @@ struct PinnedEventsTimelineScreenCoordinatorParameters { let appMediator: AppMediatorProtocol let emojiProvider: EmojiProviderProtocol let timelineControllerFactory: TimelineControllerFactoryProtocol + let clientProxy: ClientProxyProtocol } enum PinnedEventsTimelineScreenCoordinatorAction { @@ -53,7 +54,8 @@ final class PinnedEventsTimelineScreenCoordinator: CoordinatorProtocol { appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics, emojiProvider: parameters.emojiProvider, - timelineControllerFactory: parameters.timelineControllerFactory) + timelineControllerFactory: parameters.timelineControllerFactory, + clientProxy: parameters.clientProxy) } func start() { diff --git a/ElementX/Sources/Screens/PinnedEventsTimelineScreen/View/PinnedEventsTimelineScreen.swift b/ElementX/Sources/Screens/PinnedEventsTimelineScreen/View/PinnedEventsTimelineScreen.swift index 4037b940f5..185c85be09 100644 --- a/ElementX/Sources/Screens/PinnedEventsTimelineScreen/View/PinnedEventsTimelineScreen.swift +++ b/ElementX/Sources/Screens/PinnedEventsTimelineScreen/View/PinnedEventsTimelineScreen.swift @@ -99,7 +99,8 @@ struct PinnedEventsTimelineScreen_Previews: PreviewProvider, TestablePreview { appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics, emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), - timelineControllerFactory: TimelineControllerFactoryMock(.init())) + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + clientProxy: ClientProxyMock(.init())) }() static var previews: some View { diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift index 7af774f779..344e722443 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift @@ -86,7 +86,8 @@ final class RoomScreenCoordinator: CoordinatorProtocol { appSettings: parameters.appSettings, analyticsService: ServiceLocator.shared.analytics, emojiProvider: parameters.emojiProvider, - timelineControllerFactory: parameters.timelineControllerFactory) + timelineControllerFactory: parameters.timelineControllerFactory, + clientProxy: parameters.clientProxy) wysiwygViewModel = WysiwygComposerViewModel(minHeight: ComposerConstant.minHeight, maxCompressedHeight: ComposerConstant.maxHeight, diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift index 157bb2aa15..7ebcc7f4fc 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift @@ -266,7 +266,8 @@ struct RoomScreen_Previews: PreviewProvider, TestablePreview { appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics, emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), - timelineControllerFactory: TimelineControllerFactoryMock(.init())) + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + clientProxy: ClientProxyMock(.init())) static var previews: some View { NavigationStack { diff --git a/ElementX/Sources/Screens/Timeline/TimelineInteractionHandler.swift b/ElementX/Sources/Screens/Timeline/TimelineInteractionHandler.swift index 37700868b7..818d941e7b 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineInteractionHandler.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineInteractionHandler.swift @@ -41,6 +41,7 @@ class TimelineInteractionHandler { private let emojiProvider: EmojiProviderProtocol private let timelineControllerFactory: TimelineControllerFactoryProtocol private let pollInteractionHandler: PollInteractionHandlerProtocol + private let clientProxy: ClientProxyProtocol private let actionsSubject: PassthroughSubject = .init() var actions: AnyPublisher { @@ -66,7 +67,8 @@ class TimelineInteractionHandler { appSettings: AppSettings, analyticsService: AnalyticsService, emojiProvider: EmojiProviderProtocol, - timelineControllerFactory: TimelineControllerFactoryProtocol) { + timelineControllerFactory: TimelineControllerFactoryProtocol, + clientProxy: ClientProxyProtocol) { self.roomProxy = roomProxy self.timelineController = timelineController self.mediaProvider = mediaProvider @@ -79,6 +81,7 @@ class TimelineInteractionHandler { self.analyticsService = analyticsService self.emojiProvider = emojiProvider self.timelineControllerFactory = timelineControllerFactory + self.clientProxy = clientProxy pollInteractionHandler = PollInteractionHandler(analyticsService: analyticsService, roomProxy: roomProxy) } @@ -580,7 +583,8 @@ class TimelineInteractionHandler { appSettings: appSettings, analyticsService: analyticsService, emojiProvider: emojiProvider, - timelineControllerFactory: timelineControllerFactory) + timelineControllerFactory: timelineControllerFactory, + clientProxy: clientProxy) return .displayMediaPreview(item: item, timelineViewModel: .new(timelineViewModel)) } else { diff --git a/ElementX/Sources/Screens/Timeline/TimelineModels.swift b/ElementX/Sources/Screens/Timeline/TimelineModels.swift index 74a6bc2660..4b623f9cf7 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineModels.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineModels.swift @@ -114,6 +114,9 @@ struct TimelineViewState: BindableState { /// A closure providing the associated audio player state for an item in the timeline. var audioPlayerStateProvider: (@MainActor (_ itemId: TimelineItemIdentifier) -> AudioPlayerState?)? + /// A closure that updates the associated pill context + var pillContextUpdater: (@MainActor (PillContext) -> Void)? + var emojiProvider: EmojiProviderProtocol } diff --git a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift index 2072b64d90..6fd76358d8 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift @@ -31,6 +31,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { private let analyticsService: AnalyticsService private let emojiProvider: EmojiProviderProtocol private let timelineControllerFactory: TimelineControllerFactoryProtocol + private let clientProxy: ClientProxyProtocol private let timelineInteractionHandler: TimelineInteractionHandler @@ -55,7 +56,8 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { appSettings: AppSettings, analyticsService: AnalyticsService, emojiProvider: EmojiProviderProtocol, - timelineControllerFactory: TimelineControllerFactoryProtocol) { + timelineControllerFactory: TimelineControllerFactoryProtocol, + clientProxy: ClientProxyProtocol) { self.timelineController = timelineController self.mediaProvider = mediaProvider self.mediaPlayerProvider = mediaPlayerProvider @@ -66,6 +68,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { self.appMediator = appMediator self.emojiProvider = emojiProvider self.timelineControllerFactory = timelineControllerFactory + self.clientProxy = clientProxy let voiceMessageRecorder = VoiceMessageRecorder(audioRecorder: AudioRecorder(), mediaPlayerProvider: mediaPlayerProvider) @@ -80,7 +83,8 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { appSettings: appSettings, analyticsService: analyticsService, emojiProvider: emojiProvider, - timelineControllerFactory: timelineControllerFactory) + timelineControllerFactory: timelineControllerFactory, + clientProxy: clientProxy) super.init(initialViewState: TimelineViewState(timelineKind: timelineController.timelineKind, roomID: roomProxy.id, @@ -113,6 +117,10 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { return self.timelineInteractionHandler.audioPlayerState(for: itemID) } + state.pillContextUpdater = { [weak self] pillContext in + self?.pillContextUpdater(pillContext) + } + state.timelineState.paginationState = timelineController.paginationState buildTimelineViews(timelineItems: timelineController.timelineItems) @@ -826,6 +834,72 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { actionsSubject.send(.displayMessageForwarding(forwardingItem: .init(id: itemID, roomID: roomProxy.id, content: content))) } + // MARK: Pills + + private func pillContextUpdater(_ pillContext: PillContext) { + switch pillContext.data.type { + case let .user(id): + let isOwnMention = id == state.ownUserID + if let profile = state.members[id] { + var name = id + if let displayName = profile.displayName { + name = "@\(displayName)" + } + pillContext.viewState = .mention(isOwnMention: isOwnMention, displayText: name) + } else { + pillContext.viewState = .mention(isOwnMention: isOwnMention, displayText: id) + pillContext.cancellable = context.$viewState + .compactMap { $0.members[id] } + .sink { [weak pillContext] profile in + guard let pillContext else { + return + } + + var name = id + if let displayName = profile.displayName { + name = "@\(displayName)" + } + pillContext.viewState = .mention(isOwnMention: isOwnMention, displayText: name) + pillContext.cancellable = nil + } + } + case .allUsers: + pillContext.viewState = .mention(isOwnMention: true, displayText: PillConstants.atRoom) + case .event(let room): + var pillViewState: PillViewState = .reference(avatar: .link, displayText: L10n.screenRoomEventPill(room.value)) + defer { + pillContext.viewState = pillViewState + } + + switch room { + case .roomAlias(let alias): + guard let roomSummary = clientProxy.roomSummaryForAlias(alias) else { + return + } + // We always show the link image for event permalinks + pillViewState = .reference(avatar: .link, displayText: L10n.screenRoomEventPill(roomSummary.name)) + case .roomID(let id): + guard let roomSummary = clientProxy.roomSummaryForIdentifier(id) else { + return + } + // We always show the link image for event permalinks + pillViewState = .reference(avatar: .link, displayText: L10n.screenRoomEventPill(roomSummary.name)) + } + case .roomAlias(let alias): + guard let roomSummary = clientProxy.roomSummaryForAlias(alias) else { + pillContext.viewState = .reference(avatar: .link, displayText: alias) + return + } + pillContext.viewState = .reference(avatar: .roomAvatar(roomSummary.avatar), displayText: roomSummary.name) + case .roomID(let id): + guard let roomSummary = clientProxy.roomSummaryForIdentifier(id) else { + pillContext.viewState = .reference(avatar: .link, displayText: id) + return + } + pillContext.viewState = .reference(avatar: .roomAvatar(roomSummary.avatar), displayText: roomSummary.name) + } + } + // MARK: - User Indicators private func showFocusLoadingIndicator() { @@ -896,7 +970,8 @@ extension TimelineViewModel { appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics, emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), - timelineControllerFactory: TimelineControllerFactoryMock(.init())) + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + clientProxy: ClientProxyMock(.init())) } } diff --git a/ElementX/Sources/Screens/Timeline/View/ReadReceipts/ReadReceiptsSummaryView.swift b/ElementX/Sources/Screens/Timeline/View/ReadReceipts/ReadReceiptsSummaryView.swift index 3699d6db49..317926be75 100644 --- a/ElementX/Sources/Screens/Timeline/View/ReadReceipts/ReadReceiptsSummaryView.swift +++ b/ElementX/Sources/Screens/Timeline/View/ReadReceipts/ReadReceiptsSummaryView.swift @@ -53,7 +53,8 @@ struct ReadReceiptsSummaryView_Previews: PreviewProvider, TestablePreview { appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics, emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), - timelineControllerFactory: TimelineControllerFactoryMock(.init())) + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + clientProxy: ClientProxyMock(.init())) return mock }() diff --git a/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift b/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift index f87dc1e6d9..86b570836b 100644 --- a/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift +++ b/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift @@ -339,7 +339,8 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics, emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), - timelineControllerFactory: TimelineControllerFactoryMock(.init())) + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + clientProxy: ClientProxyMock(.init())) }() static var previews: some View { diff --git a/ElementX/Sources/Screens/Timeline/View/Supplementary/TimelineReadReceiptsView.swift b/ElementX/Sources/Screens/Timeline/View/Supplementary/TimelineReadReceiptsView.swift index f8638a16e7..ae8be59545 100644 --- a/ElementX/Sources/Screens/Timeline/View/Supplementary/TimelineReadReceiptsView.swift +++ b/ElementX/Sources/Screens/Timeline/View/Supplementary/TimelineReadReceiptsView.swift @@ -88,8 +88,9 @@ struct TimelineReadReceiptsView_Previews: PreviewProvider, TestablePreview { appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics, emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), - timelineControllerFactory: TimelineControllerFactoryMock(.init())) - + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + clientProxy: ClientProxyMock(.init())) + static let singleReceipt = [ReadReceipt(userID: RoomMemberProxyMock.mockAlice.userID, formattedTimestamp: "Now")] static let doubleReceipt = [ReadReceipt(userID: RoomMemberProxyMock.mockAlice.userID, formattedTimestamp: "Now"), ReadReceipt(userID: RoomMemberProxyMock.mockBob.userID, formattedTimestamp: "Before")] diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/FormattedBodyText.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/FormattedBodyText.swift index 458ae7f611..11b86e624d 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/FormattedBodyText.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/FormattedBodyText.swift @@ -142,8 +142,6 @@ struct FormattedBodyText_Previews: PreviewProvider, TestablePreview { let htmlStrings = [ """ Plain text\n - @bob:matrix.org\n - #room:matrix.org\n !room:matrix.org\n https://www.matrix.org\n www.matrix.org\n diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/HighlightedTimelineItemModifier.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/HighlightedTimelineItemModifier.swift index 7cb63aee6c..2ecd7780f8 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/HighlightedTimelineItemModifier.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/HighlightedTimelineItemModifier.swift @@ -98,7 +98,8 @@ struct HighlightedTimelineItemTimeline_Previews: PreviewProvider { appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics, emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), - timelineControllerFactory: TimelineControllerFactoryMock(.init())) + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + clientProxy: ClientProxyMock(.init())) static var previews: some View { NavigationStack { diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineView.swift index f482a4698d..072cae9e6b 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineView.swift @@ -91,7 +91,8 @@ struct TimelineView_Previews: PreviewProvider, TestablePreview { appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics, emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), - timelineControllerFactory: TimelineControllerFactoryMock(.init())) + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + clientProxy: ClientProxyMock(.init())) static var previews: some View { NavigationStack { diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index 5a3de15fea..cfb7fafa18 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -515,6 +515,18 @@ class ClientProxy: ClientProxyProtocol { return .failure(.sdkError(error)) } } + + 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 { do { diff --git a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift index f5f28776ce..a59c13655f 100644 --- a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift +++ b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift @@ -142,6 +142,10 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol { func roomPreviewForIdentifier(_ identifier: String, via: [String]) async -> Result + func roomSummaryForIdentifier(_ identifier: String) -> RoomSummary? + + func roomSummaryForAlias(_ alias: String) -> RoomSummary? + @discardableResult func loadUserDisplayName() async -> Result func setUserDisplayName(_ name: String) async -> Result diff --git a/ElementX/Sources/Services/Room/RoomPreview/RoomPreviewProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomPreview/RoomPreviewProxyProtocol.swift index 72a3386ecb..88f0193cb2 100644 --- a/ElementX/Sources/Services/Room/RoomPreview/RoomPreviewProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomPreview/RoomPreviewProxyProtocol.swift @@ -7,6 +7,7 @@ import Foundation +/// A preview object for the Room. useful to get all the possible info for rooms to which the user is not invited to // sourcery: AutoMockable protocol RoomPreviewProxyProtocol { var info: RoomPreviewInfoProxy { get } diff --git a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift index 4650fbc60b..47221db0bd 100644 --- a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift @@ -18,6 +18,7 @@ enum RoomProxyError: Error { case missingTransactionID } +/// An enum that describes the relationship between the current user and the room, and contains a reference to the specific implementation of the `RoomProxy`. enum RoomProxyType { case joined(JoinedRoomProxyProtocol) case invited(InvitedRoomProxyProtocol) diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift index 7748395d1a..e4453b0e12 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift @@ -8,6 +8,7 @@ import Foundation import MatrixRustSDK +/// A quick summary of a Room, useful to describe and give quick informations for the room list struct RoomSummary { enum JoinRequestType { case invite(inviter: RoomMemberProxyProtocol?) @@ -45,6 +46,7 @@ struct RoomSummary { let unreadNotificationsCount: UInt let notificationMode: RoomNotificationModeProxy? let canonicalAlias: String? + let alternativeAliases: Set let hasOngoingCall: Bool @@ -99,6 +101,7 @@ extension RoomSummary { unreadNotificationsCount = hasUnreadNotifications ? 1 : 0 notificationMode = settingsMode canonicalAlias = nil + alternativeAliases = [] hasOngoingCall = false joinRequestType = nil diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift index ff8597f8d5..d56f4b1d5e 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift @@ -275,6 +275,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { unreadNotificationsCount: UInt(roomInfo.numUnreadNotifications), notificationMode: notificationMode, canonicalAlias: roomInfo.canonicalAlias, + alternativeAliases: .init(roomInfo.alternativeAliases), hasOngoingCall: roomInfo.hasRoomCall, isMarkedUnread: roomInfo.isMarkedUnread, isFavourite: roomInfo.isFavourite) diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_formattedBodyText-iPad-en-GB.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_formattedBodyText-iPad-en-GB.1.png index aae27a6caa..d62e13f252 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_formattedBodyText-iPad-en-GB.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_formattedBodyText-iPad-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a53cfbfc88026e695e6fffe2b651a8d68bec0a3fee91875ce2c651d8a4d61de5 -size 321468 +oid sha256:0a5300140d0d125b42c74b95e136ca19e809d0ca441f63dcf3e0eff2c38c0522 +size 319205 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_formattedBodyText-iPad-pseudo.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_formattedBodyText-iPad-pseudo.1.png index aae27a6caa..d62e13f252 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_formattedBodyText-iPad-pseudo.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_formattedBodyText-iPad-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a53cfbfc88026e695e6fffe2b651a8d68bec0a3fee91875ce2c651d8a4d61de5 -size 321468 +oid sha256:0a5300140d0d125b42c74b95e136ca19e809d0ca441f63dcf3e0eff2c38c0522 +size 319205 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_formattedBodyText-iPhone-16-en-GB.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_formattedBodyText-iPhone-16-en-GB.1.png index e32799e565..58191e1c47 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_formattedBodyText-iPhone-16-en-GB.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_formattedBodyText-iPhone-16-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e52762fc68301b3b7a95b6702528d2c1ca6fd7ddbe83bbf730162dd23e59d92 -size 250960 +oid sha256:974272940990f0fd78e1f403c74ba9714fe8662d79399e91f63307bc35421fdd +size 247463 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_formattedBodyText-iPhone-16-pseudo.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_formattedBodyText-iPhone-16-pseudo.1.png index e32799e565..58191e1c47 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_formattedBodyText-iPhone-16-pseudo.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_formattedBodyText-iPhone-16-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e52762fc68301b3b7a95b6702528d2c1ca6fd7ddbe83bbf730162dd23e59d92 -size 250960 +oid sha256:974272940990f0fd78e1f403c74ba9714fe8662d79399e91f63307bc35421fdd +size 247463 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_globalSearchScreen-iPad-en-GB.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_globalSearchScreen-iPad-en-GB.1.png index 656153c329..40f302627c 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_globalSearchScreen-iPad-en-GB.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_globalSearchScreen-iPad-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9d0a07817576872813714096d839d0d0b17553304b86721312e67a269e82d409 -size 152891 +oid sha256:61ebfb0d15c41532a779c91b2ff490a7eb2daaeed9cbc8d9b804535e6d721758 +size 158570 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_globalSearchScreen-iPad-pseudo.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_globalSearchScreen-iPad-pseudo.1.png index 568fb515cb..89a6b35f0a 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_globalSearchScreen-iPad-pseudo.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_globalSearchScreen-iPad-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:af30ec72f2713ce2776ad0ecf874c9ef2922a50927e715063a13a814b6ef1225 -size 153269 +oid sha256:5bf11205a434cc0ff90b275e4475e86134ed15e861f21fb37f96a9fc3b25e262 +size 158954 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_globalSearchScreen-iPhone-16-en-GB.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_globalSearchScreen-iPhone-16-en-GB.1.png index 6e59b5c7ef..9d042a8815 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_globalSearchScreen-iPhone-16-en-GB.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_globalSearchScreen-iPhone-16-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c5fb4b5505c4a01815a357752f4aa43ad1ed86ad29f85bb3f59510c0aadb61e1 -size 89808 +oid sha256:b0088c448b0a3e1106a869a747c7dc85c882c3a3553e529eb3e502ccbbb22e25 +size 94620 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_globalSearchScreen-iPhone-16-pseudo.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_globalSearchScreen-iPhone-16-pseudo.1.png index 9729e58a5c..fea211012f 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_globalSearchScreen-iPhone-16-pseudo.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_globalSearchScreen-iPhone-16-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:374174cf42d57f21db62597d20ba29d3a8bde957bf06e1124bf3f27feff33fb7 -size 90120 +oid sha256:ba7e2d7a550a145a8f2872986b8eb6d437306390feaa2019329192c16398f642 +size 94955 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageForwardingScreen-iPad-en-GB.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageForwardingScreen-iPad-en-GB.1.png index 98046592ff..5c4683c8f0 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageForwardingScreen-iPad-en-GB.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageForwardingScreen-iPad-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:81be3bd1ac46f06143f2fa4bd73a5168f4dee02e5071f21fc1e96449bef17377 -size 153595 +oid sha256:9c92c6e353e7759adf0f75cf9a21297656c5c6daad3cfdbbf39301440b5bf8a0 +size 159902 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageForwardingScreen-iPad-pseudo.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageForwardingScreen-iPad-pseudo.1.png index 4563bcc11e..9e7764fc1b 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageForwardingScreen-iPad-pseudo.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageForwardingScreen-iPad-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:999e8810095d712fd3e1d9aec7d1c2e83a51fe0c6f805816a0abf65ffae35948 -size 155531 +oid sha256:dae65098eaf100bb2b762b5a25b4caa3a366e2780690e9a41a976d0bce707a74 +size 161798 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageForwardingScreen-iPhone-16-en-GB.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageForwardingScreen-iPhone-16-en-GB.1.png index c5e5179616..b55e66f7af 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageForwardingScreen-iPhone-16-en-GB.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageForwardingScreen-iPhone-16-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c811e030816eeff12ec117631f093c98ea467fe76403d3eb156ef10aa295ac79 -size 103086 +oid sha256:bac9d7d51106483c23a118e85a567aafa466ba0a118eae797bd27e8f69d97417 +size 108070 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageForwardingScreen-iPhone-16-pseudo.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageForwardingScreen-iPhone-16-pseudo.1.png index 6f09fa3b73..a15cc2952d 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageForwardingScreen-iPhone-16-pseudo.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageForwardingScreen-iPhone-16-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5c34f0d2af50da052122137c3220336af0214f08320f669d83baa019606534d7 -size 102908 +oid sha256:94508db7f079602a32529ba110fd027d9660c75c1d81f38a2e28a9448f12d5cc +size 107929 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageText-iPad-en-GB.Custom-Attachment.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageText-iPad-en-GB.Custom-Attachment.png index ee3043108e..6cd9fc29e2 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageText-iPad-en-GB.Custom-Attachment.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageText-iPad-en-GB.Custom-Attachment.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ab0efa7ad7ced4f257adf46064a78cba4b07b61e07610b94372aea205803d85d -size 72247 +oid sha256:efe3a163270b708fada60d439d66ae82792fe59ef2a2e6bbaa19f264968e56f1 +size 72035 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageText-iPad-pseudo.Custom-Attachment.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageText-iPad-pseudo.Custom-Attachment.png index ee3043108e..6cd9fc29e2 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageText-iPad-pseudo.Custom-Attachment.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageText-iPad-pseudo.Custom-Attachment.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ab0efa7ad7ced4f257adf46064a78cba4b07b61e07610b94372aea205803d85d -size 72247 +oid sha256:efe3a163270b708fada60d439d66ae82792fe59ef2a2e6bbaa19f264968e56f1 +size 72035 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageText-iPhone-16-en-GB.Custom-Attachment.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageText-iPhone-16-en-GB.Custom-Attachment.png index c7dd35a923..643c0f0e97 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageText-iPhone-16-en-GB.Custom-Attachment.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageText-iPhone-16-en-GB.Custom-Attachment.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e8df10b2bbfcf7242bff019ca058bbeb0ed948b830e9db0aeb9516d03201431e -size 31481 +oid sha256:b8a1b232998033c6b57f309ae3270216a528a9c6b40243cd00500f867b7d3b38 +size 31526 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageText-iPhone-16-pseudo.Custom-Attachment.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageText-iPhone-16-pseudo.Custom-Attachment.png index c7dd35a923..643c0f0e97 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageText-iPhone-16-pseudo.Custom-Attachment.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageText-iPhone-16-pseudo.Custom-Attachment.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e8df10b2bbfcf7242bff019ca058bbeb0ed948b830e9db0aeb9516d03201431e -size 31481 +oid sha256:b8a1b232998033c6b57f309ae3270216a528a9c6b40243cd00500f867b7d3b38 +size 31526 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.All-Users.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.All-Users.png deleted file mode 100644 index f0d1c59141..0000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.All-Users.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4aadf5d83f4c585cf4698b95d1b836c516a27ca89f879fb2a54466f4476f8d0b -size 70117 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Loaded-Long-Own.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Loaded-Long-Own.png deleted file mode 100644 index 50bfa92304..0000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Loaded-Long-Own.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6788496d70fa922b8b67611210e3834bf83e44f9ee57d7f9dd4cc24d5c9afda2 -size 73527 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Loaded-Long.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Loaded-Long.png deleted file mode 100644 index 4030f97d36..0000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Loaded-Long.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1da3837356af612e584c218fe66756cfbe8e5471b9aa6c0058c37b322d726da5 -size 73438 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Loading-Own.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Loading-Own.png deleted file mode 100644 index ef5e434d9a..0000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Loading-Own.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cec492d1d00cd401bf65f4fef497e7ed3265a2c3afbebca7e0ee0ffc63a704ea -size 72143 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Loading.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Loading.png deleted file mode 100644 index 05378bd3fb..0000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Loading.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b6e72ed5b84224c7368c31ff8e039026039148471106c72a26a7e907eaa8b9b9 -size 72074 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Message-link.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Message-link.png new file mode 100644 index 0000000000..15c5f8c913 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Message-link.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d6e55f90134a794fcc1483ff6b0134dfd54285a787622ece157e469ec9019541 +size 73593 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Own-user.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Own-user.png new file mode 100644 index 0000000000..5341ca7ab3 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Own-user.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a3ce3dbcb983f608d38f510dc4ee16e9bc65c9e246aebb3aee8fc46bcaf476e +size 69720 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Room.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Room.png new file mode 100644 index 0000000000..0e072fb2f2 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.Room.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3866839ecf0aab97249e8bb9af2a65e621afab2b249e71794e4146c2f5d920c8 +size 69868 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.User-with-a-long-name.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.User-with-a-long-name.png new file mode 100644 index 0000000000..9a6d427bb4 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.User-with-a-long-name.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fcc37d47cfc3f0b4b9fb715c6d7f01ef9971f822b9c476f131a5dd1a3214b635 +size 74787 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.User.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.User.png new file mode 100644 index 0000000000..07cc0c4df6 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-en-GB.User.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b95c2fb2064fcf29b6e618b8b314c574b8868078b600d1b2ca4d8b81a1771c6 +size 69625 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.All-Users.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.All-Users.png deleted file mode 100644 index f0d1c59141..0000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.All-Users.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4aadf5d83f4c585cf4698b95d1b836c516a27ca89f879fb2a54466f4476f8d0b -size 70117 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Loaded-Long-Own.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Loaded-Long-Own.png deleted file mode 100644 index 50bfa92304..0000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Loaded-Long-Own.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6788496d70fa922b8b67611210e3834bf83e44f9ee57d7f9dd4cc24d5c9afda2 -size 73527 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Loaded-Long.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Loaded-Long.png deleted file mode 100644 index 4030f97d36..0000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Loaded-Long.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1da3837356af612e584c218fe66756cfbe8e5471b9aa6c0058c37b322d726da5 -size 73438 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Loading-Own.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Loading-Own.png deleted file mode 100644 index ef5e434d9a..0000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Loading-Own.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cec492d1d00cd401bf65f4fef497e7ed3265a2c3afbebca7e0ee0ffc63a704ea -size 72143 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Loading.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Loading.png deleted file mode 100644 index 05378bd3fb..0000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Loading.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b6e72ed5b84224c7368c31ff8e039026039148471106c72a26a7e907eaa8b9b9 -size 72074 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Message-link.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Message-link.png new file mode 100644 index 0000000000..818a7878ce --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Message-link.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aded9619b7456485ef3ee0fbfa25d57675ad5e2293cc17430d9cc4a88999f18e +size 74763 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Own-user.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Own-user.png new file mode 100644 index 0000000000..5341ca7ab3 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Own-user.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a3ce3dbcb983f608d38f510dc4ee16e9bc65c9e246aebb3aee8fc46bcaf476e +size 69720 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Room.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Room.png new file mode 100644 index 0000000000..0e072fb2f2 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.Room.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3866839ecf0aab97249e8bb9af2a65e621afab2b249e71794e4146c2f5d920c8 +size 69868 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.User-with-a-long-name.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.User-with-a-long-name.png new file mode 100644 index 0000000000..9a6d427bb4 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.User-with-a-long-name.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fcc37d47cfc3f0b4b9fb715c6d7f01ef9971f822b9c476f131a5dd1a3214b635 +size 74787 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.User.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.User.png new file mode 100644 index 0000000000..07cc0c4df6 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPad-pseudo.User.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b95c2fb2064fcf29b6e618b8b314c574b8868078b600d1b2ca4d8b81a1771c6 +size 69625 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.All-Users.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.All-Users.png deleted file mode 100644 index 7994e12d49..0000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.All-Users.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:83cd25f6384b130d705b3a6f4ef800786a8b4ea81bf301075e0eba25bf2f677c -size 29689 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Loaded-Long-Own.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Loaded-Long-Own.png deleted file mode 100644 index d595ef6696..0000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Loaded-Long-Own.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d1e5543ec91df74db7bd467cfbd85a509ba125e4336b921ad51fa85cd618b916 -size 34006 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Loaded-Long.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Loaded-Long.png deleted file mode 100644 index f2874b7117..0000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Loaded-Long.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:62e1b513e728b9f20a2f57cff5fec125327b87104d7bbd2abac93e053826eb82 -size 33950 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Loading-Own.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Loading-Own.png deleted file mode 100644 index 8b16883750..0000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Loading-Own.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0c580cbb9fe8cef3c10b88fbe9d8cac6a17135a74dacbb1186eba92184115854 -size 31838 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Loading.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Loading.png deleted file mode 100644 index d702648077..0000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Loading.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:006b5e597406f1da118e3a9704346b64c62b47751002f06f69330c844c7582da -size 31764 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Message-link.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Message-link.png new file mode 100644 index 0000000000..8e62542846 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Message-link.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59a671e69b145e85e484c45212ac982531bb9f11bddf148a0a1841989d77981a +size 33375 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Own-user.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Own-user.png new file mode 100644 index 0000000000..0c93e63511 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Own-user.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79b26613033be55f6d5bac6ea368aa8dcaf346f7799af773bf1599984ffd6bb3 +size 30224 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Room.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Room.png new file mode 100644 index 0000000000..fb7435f132 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.Room.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5917c9d41ef154277d1b79ddb3d8a875bdf36c020fd53166a286908e0fdf3fa +size 29609 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.User-with-a-long-name.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.User-with-a-long-name.png new file mode 100644 index 0000000000..2920991ea9 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.User-with-a-long-name.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f38c8984a2f15bb1785edd0b3d508796a91b668f48503bb31b1f71863b6b9ad +size 34390 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.User.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.User.png new file mode 100644 index 0000000000..95d99d1db6 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-en-GB.User.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a0d7e0566bfc51fdbeae5f6f6e19120a5d57b333f23c0a96a3d66958ed13330 +size 30143 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.All-Users.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.All-Users.png deleted file mode 100644 index 7994e12d49..0000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.All-Users.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:83cd25f6384b130d705b3a6f4ef800786a8b4ea81bf301075e0eba25bf2f677c -size 29689 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Loaded-Long-Own.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Loaded-Long-Own.png deleted file mode 100644 index d595ef6696..0000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Loaded-Long-Own.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d1e5543ec91df74db7bd467cfbd85a509ba125e4336b921ad51fa85cd618b916 -size 34006 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Loaded-Long.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Loaded-Long.png deleted file mode 100644 index f2874b7117..0000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Loaded-Long.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:62e1b513e728b9f20a2f57cff5fec125327b87104d7bbd2abac93e053826eb82 -size 33950 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Loading-Own.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Loading-Own.png deleted file mode 100644 index 8b16883750..0000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Loading-Own.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0c580cbb9fe8cef3c10b88fbe9d8cac6a17135a74dacbb1186eba92184115854 -size 31838 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Loading.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Loading.png deleted file mode 100644 index d702648077..0000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Loading.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:006b5e597406f1da118e3a9704346b64c62b47751002f06f69330c844c7582da -size 31764 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Message-link.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Message-link.png new file mode 100644 index 0000000000..37165d0fe5 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Message-link.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d180ee391dc1f9f1f7de6e6f2247f9bfb5d816fa00d8945158a078e452fcac38 +size 34860 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Own-user.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Own-user.png new file mode 100644 index 0000000000..0c93e63511 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Own-user.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79b26613033be55f6d5bac6ea368aa8dcaf346f7799af773bf1599984ffd6bb3 +size 30224 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Room.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Room.png new file mode 100644 index 0000000000..fb7435f132 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.Room.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5917c9d41ef154277d1b79ddb3d8a875bdf36c020fd53166a286908e0fdf3fa +size 29609 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.User-with-a-long-name.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.User-with-a-long-name.png new file mode 100644 index 0000000000..2920991ea9 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.User-with-a-long-name.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f38c8984a2f15bb1785edd0b3d508796a91b668f48503bb31b1f71863b6b9ad +size 34390 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.User.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.User.png new file mode 100644 index 0000000000..95d99d1db6 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_pillView-iPhone-16-pseudo.User.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a0d7e0566bfc51fdbeae5f6f6e19120a5d57b333f23c0a96a3d66958ed13330 +size 30143 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_roomSelectionScreen-iPad-en-GB.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_roomSelectionScreen-iPad-en-GB.1.png index 10aaec705d..e48d445e50 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_roomSelectionScreen-iPad-en-GB.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_roomSelectionScreen-iPad-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:81fc617254014ad63c2325d5832a2b758b4d62c4c3e399d2c9b9352fb9d0e0be -size 150682 +oid sha256:29ecf5613971aad7c7890ed563d8b69d3c13ad1b2d9189c6cb3a80af53c6568b +size 156966 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_roomSelectionScreen-iPad-pseudo.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_roomSelectionScreen-iPad-pseudo.1.png index cdd6c514ec..7f4f3b8d6f 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_roomSelectionScreen-iPad-pseudo.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_roomSelectionScreen-iPad-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:29222a03a1d9dab355a9d976e4c116b240e33f1c1dc2d4492ea3a1faa2fefe48 -size 152165 +oid sha256:270c52cdad79a7495bded4560a8500f4b9266e0af8d3ec0742a42a2fd7c79888 +size 158449 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_roomSelectionScreen-iPhone-16-en-GB.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_roomSelectionScreen-iPhone-16-en-GB.1.png index a75016a899..c6b0b25203 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_roomSelectionScreen-iPhone-16-en-GB.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_roomSelectionScreen-iPhone-16-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:391e0f9ec34cef26c6d2df7cbdd4e30b1ea71f4c00edc0bbd96550d98430c391 -size 100964 +oid sha256:429ace579d36361e16639266d2b14b6a515da57b14a25205c5e2460a1a8fe85e +size 105969 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_roomSelectionScreen-iPhone-16-pseudo.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_roomSelectionScreen-iPhone-16-pseudo.1.png index 152a7212ec..0f7ac0010a 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_roomSelectionScreen-iPhone-16-pseudo.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_roomSelectionScreen-iPhone-16-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:abd4ee010b07107658b3e3aa4e2947fa3c8d55f4b79466f77461819881c2df8b -size 102214 +oid sha256:f29a6ad8b2d81b1e3ffe1d9d8b526488d7c021bf17b04e74d8d3db38f2955944 +size 107269 diff --git a/UnitTests/Sources/AttributedStringBuilderTests.swift b/UnitTests/Sources/AttributedStringBuilderTests.swift index d10711d465..903c0cb22e 100644 --- a/UnitTests/Sources/AttributedStringBuilderTests.swift +++ b/UnitTests/Sources/AttributedStringBuilderTests.swift @@ -425,14 +425,88 @@ class AttributedStringBuilderTests: XCTestCase { XCTAssertEqual(numberOfBlockquotes, 3, "Couldn't find all the blockquotes") } - func testUserMentionAtachment() { + func testUserPermalinkMentionAtachment() { let string = "https://matrix.to/#/@test:matrix.org" let attributedStringFromHTML = attributedStringBuilder.fromHTML(string) XCTAssertNotNil(attributedStringFromHTML?.attachment) - XCTAssertNotNil(attributedStringFromHTML?.link) + XCTAssertEqual(attributedStringFromHTML?.userID, "@test:matrix.org") + XCTAssertEqual(attributedStringFromHTML?.link?.absoluteString, string) let attributedStringFromPlain = attributedStringBuilder.fromPlain(string) XCTAssertNotNil(attributedStringFromPlain?.attachment) - XCTAssertNotNil(attributedStringFromHTML?.link) + XCTAssertEqual(attributedStringFromPlain?.userID, "@test:matrix.org") + XCTAssertEqual(attributedStringFromPlain?.link?.absoluteString, string) + } + + func testUserIDMentionAtachment() { + let string = "@test:matrix.org" + let attributedStringFromHTML = attributedStringBuilder.fromHTML(string) + XCTAssertNotNil(attributedStringFromHTML?.attachment) + XCTAssertEqual(attributedStringFromHTML?.userID, "@test:matrix.org") + XCTAssertEqual(attributedStringFromHTML?.link?.absoluteString, "https://matrix.to/#/@test:matrix.org") + let attributedStringFromPlain = attributedStringBuilder.fromPlain(string) + XCTAssertNotNil(attributedStringFromPlain?.attachment) + XCTAssertEqual(attributedStringFromPlain?.userID, "@test:matrix.org") + XCTAssertEqual(attributedStringFromPlain?.link?.absoluteString, "https://matrix.to/#/@test:matrix.org") + } + + func testRoomIDPermalinkMentionAttachment() { + let string = "https://matrix.to/#/!test:matrix.org" + let attributedStringFromHTML = attributedStringBuilder.fromHTML(string) + XCTAssertNotNil(attributedStringFromHTML?.attachment) + XCTAssertEqual(attributedStringFromHTML?.roomID, "!test:matrix.org") + XCTAssertEqual(attributedStringFromHTML?.link?.absoluteString, string) + let attributedStringFromPlain = attributedStringBuilder.fromPlain(string) + XCTAssertNotNil(attributedStringFromPlain?.attachment) + XCTAssertEqual(attributedStringFromHTML?.roomID, "!test:matrix.org") + XCTAssertEqual(attributedStringFromPlain?.link?.absoluteString, string) + } + + func testRoomAliasPermalinkMentionAttachment() { + let string = "https://matrix.to/#/#test:matrix.org" + let attributedStringFromHTML = attributedStringBuilder.fromHTML(string) + XCTAssertNotNil(attributedStringFromHTML?.attachment) + XCTAssertEqual(attributedStringFromHTML?.roomAlias, "#test:matrix.org") + XCTAssertEqual(attributedStringFromHTML?.link?.absoluteString, "https://matrix.to/#/%23test:matrix.org") + let attributedStringFromPlain = attributedStringBuilder.fromPlain(string) + XCTAssertNotNil(attributedStringFromPlain?.attachment) + XCTAssertEqual(attributedStringFromHTML?.roomAlias, "#test:matrix.org") + XCTAssertEqual(attributedStringFromPlain?.link?.absoluteString, "https://matrix.to/#/%23test:matrix.org") + } + + func testRoomAliasMentionAttachment() { + let string = "#test:matrix.org" + let attributedStringFromHTML = attributedStringBuilder.fromHTML(string) + XCTAssertNotNil(attributedStringFromHTML?.attachment) + XCTAssertEqual(attributedStringFromHTML?.roomAlias, "#test:matrix.org") + XCTAssertEqual(attributedStringFromHTML?.link?.absoluteString, "https://matrix.to/#/%23test:matrix.org") + let attributedStringFromPlain = attributedStringBuilder.fromPlain(string) + XCTAssertNotNil(attributedStringFromPlain?.attachment) + XCTAssertEqual(attributedStringFromHTML?.roomAlias, "#test:matrix.org") + XCTAssertEqual(attributedStringFromPlain?.link?.absoluteString, "https://matrix.to/#/%23test:matrix.org") + } + + func testEventRoomIDPermalinkMentionAttachment() { + let string = "https://matrix.to/#/!test:matrix.org/$test" + let attributedStringFromHTML = attributedStringBuilder.fromHTML(string) + XCTAssertNotNil(attributedStringFromHTML?.attachment) + XCTAssertEqual(attributedStringFromHTML?.eventOnRoomID, .some(.init(roomID: "!test:matrix.org", eventID: "$test"))) + XCTAssertEqual(attributedStringFromHTML?.link?.absoluteString, string) + let attributedStringFromPlain = attributedStringBuilder.fromPlain(string) + XCTAssertNotNil(attributedStringFromPlain?.attachment) + XCTAssertEqual(attributedStringFromPlain?.eventOnRoomID, .some(.init(roomID: "!test:matrix.org", eventID: "$test"))) + XCTAssertEqual(attributedStringFromPlain?.link?.absoluteString, string) + } + + func testEventRoomAliasPermalinkMentionAttachment() { + let string = "https://matrix.to/#/#test:matrix.org/$test" + let attributedStringFromHTML = attributedStringBuilder.fromHTML(string) + XCTAssertNotNil(attributedStringFromHTML?.attachment) + XCTAssertEqual(attributedStringFromHTML?.eventOnRoomAlias, .some(.init(alias: "#test:matrix.org", eventID: "$test"))) + XCTAssertEqual(attributedStringFromHTML?.link?.absoluteString, "https://matrix.to/#/%23test:matrix.org/$test") + let attributedStringFromPlain = attributedStringBuilder.fromPlain(string) + XCTAssertNotNil(attributedStringFromPlain?.attachment) + XCTAssertEqual(attributedStringFromPlain?.eventOnRoomAlias, .some(.init(alias: "#test:matrix.org", eventID: "$test"))) + XCTAssertEqual(attributedStringFromPlain?.link?.absoluteString, "https://matrix.to/#/%23test:matrix.org/$test") } func testUserMentionAtachmentInBlockQuotes() { @@ -631,7 +705,7 @@ class AttributedStringBuilderTests: XCTestCase { XCTAssertEqual(foundLink, url) XCTAssertEqual(foundAttachments, 2) } - + // MARK: - Private private func checkLinkIn(attributedString: AttributedString?, expectedLink: String, expectedRuns: Int) { diff --git a/UnitTests/Sources/HomeScreenRoomTests.swift b/UnitTests/Sources/HomeScreenRoomTests.swift index 83fd45aefc..d711f456f6 100644 --- a/UnitTests/Sources/HomeScreenRoomTests.swift +++ b/UnitTests/Sources/HomeScreenRoomTests.swift @@ -34,6 +34,7 @@ class HomeScreenRoomTests: XCTestCase { unreadNotificationsCount: unreadNotificationsCount, notificationMode: notificationMode, canonicalAlias: nil, + alternativeAliases: [], hasOngoingCall: hasOngoingCall, isMarkedUnread: isMarkedUnread, isFavourite: false) diff --git a/UnitTests/Sources/LoggingTests.swift b/UnitTests/Sources/LoggingTests.swift index 9551e62cde..bcf5cced74 100644 --- a/UnitTests/Sources/LoggingTests.swift +++ b/UnitTests/Sources/LoggingTests.swift @@ -92,6 +92,7 @@ class LoggingTests: XCTestCase { unreadNotificationsCount: 0, notificationMode: nil, canonicalAlias: nil, + alternativeAliases: [], hasOngoingCall: false, isMarkedUnread: false, isFavourite: false) diff --git a/UnitTests/Sources/PillContextTests.swift b/UnitTests/Sources/PillContextTests.swift index ec3cb3a9e9..bb925cc18f 100644 --- a/UnitTests/Sources/PillContextTests.swift +++ b/UnitTests/Sources/PillContextTests.swift @@ -12,7 +12,7 @@ import XCTest @MainActor class PillContextTests: XCTestCase { - func testUser() async throws { + func testUser() async { let id = "@test:matrix.org" let proxyMock = JoinedRoomProxyMock(.init(name: "Test")) let subject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([]) @@ -27,7 +27,8 @@ class PillContextTests: XCTestCase { appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics, emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), - timelineControllerFactory: TimelineControllerFactoryMock(.init())) + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + clientProxy: ClientProxyMock(.init())) let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .user(userID: id), font: .preferredFont(forTextStyle: .body))) XCTAssertFalse(context.viewState.isOwnMention) @@ -39,10 +40,11 @@ class PillContextTests: XCTestCase { await Task.yield() XCTAssertFalse(context.viewState.isOwnMention) + XCTAssertNil(context.viewState.image) XCTAssertEqual(context.viewState.displayText, "@\(name)") } - func testOwnUser() async throws { + func testOwnUser() { let id = "@test:matrix.org" let proxyMock = JoinedRoomProxyMock(.init(name: "Test", ownUserID: id)) let subject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([]) @@ -57,13 +59,15 @@ class PillContextTests: XCTestCase { appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics, emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), - timelineControllerFactory: TimelineControllerFactoryMock(.init())) + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + clientProxy: ClientProxyMock(.init())) let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .user(userID: id), font: .preferredFont(forTextStyle: .body))) + XCTAssertNil(context.viewState.image) XCTAssertTrue(context.viewState.isOwnMention) } - func testAllUsers() async throws { + func testAllUsers() { let avatarURL = URL(string: "https://matrix.jpg") let id = "test_room" let displayName = "Test" @@ -80,10 +84,216 @@ class PillContextTests: XCTestCase { appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics, emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), - timelineControllerFactory: TimelineControllerFactoryMock(.init())) + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + clientProxy: ClientProxyMock(.init())) let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .allUsers, font: .preferredFont(forTextStyle: .body))) XCTAssertTrue(context.viewState.isOwnMention) + XCTAssertNil(context.viewState.image) XCTAssertEqual(context.viewState.displayText, PillConstants.atRoom) } + + func testRoomIDMention() { + let proxyMock = JoinedRoomProxyMock(.init()) + let mockController = MockTimelineController() + let clientMock = ClientProxyMock(.init()) + clientMock.roomSummaryForIdentifierReturnValue = .mock(id: "1", name: "Foundation 🔭🪐🌌") + mockController.roomProxy = proxyMock + let mock = TimelineViewModel(roomProxy: proxyMock, + timelineController: mockController, + mediaProvider: MediaProviderMock(configuration: .init()), + mediaPlayerProvider: MediaPlayerProviderMock(), + voiceMessageMediaManager: VoiceMessageMediaManagerMock(), + userIndicatorController: ServiceLocator.shared.userIndicatorController, + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings, + analyticsService: ServiceLocator.shared.analytics, + emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + clientProxy: clientMock) + let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .roomID("1"), font: .preferredFont(forTextStyle: .body))) + + XCTAssertFalse(context.viewState.isOwnMention) + XCTAssertFalse(context.viewState.isUndefined) + XCTAssertEqual(context.viewState.image, .roomAvatar(.room(id: "1", name: "Foundation 🔭🪐🌌", avatarURL: nil))) + XCTAssertEqual(context.viewState.displayText, "Foundation 🔭🪐🌌") + } + + func testRoomIDMentionMissingRoom() { + let proxyMock = JoinedRoomProxyMock(.init()) + let mockController = MockTimelineController() + mockController.roomProxy = proxyMock + let mock = TimelineViewModel(roomProxy: proxyMock, + timelineController: mockController, + mediaProvider: MediaProviderMock(configuration: .init()), + mediaPlayerProvider: MediaPlayerProviderMock(), + voiceMessageMediaManager: VoiceMessageMediaManagerMock(), + userIndicatorController: ServiceLocator.shared.userIndicatorController, + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings, + analyticsService: ServiceLocator.shared.analytics, + emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + clientProxy: ClientProxyMock(.init())) + let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .roomID("1"), font: .preferredFont(forTextStyle: .body))) + + XCTAssertFalse(context.viewState.isOwnMention) + XCTAssertFalse(context.viewState.isUndefined) + XCTAssertEqual(context.viewState.image, .link) + XCTAssertEqual(context.viewState.displayText, "1") + } + + func testRoomAliasMention() { + let proxyMock = JoinedRoomProxyMock(.init()) + let mockController = MockTimelineController() + mockController.roomProxy = proxyMock + let clientMock = ClientProxyMock(.init()) + clientMock.roomSummaryForAliasReturnValue = .mock(id: "2", + name: "Foundation and Empire", + canonicalAlias: "#foundation-and-empire:matrix.org") + let mock = TimelineViewModel(roomProxy: proxyMock, + timelineController: mockController, + mediaProvider: MediaProviderMock(configuration: .init()), + mediaPlayerProvider: MediaPlayerProviderMock(), + voiceMessageMediaManager: VoiceMessageMediaManagerMock(), + userIndicatorController: ServiceLocator.shared.userIndicatorController, + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings, + analyticsService: ServiceLocator.shared.analytics, + emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + clientProxy: clientMock) + let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .roomAlias("#foundation-and-empire:matrix.org"), font: .preferredFont(forTextStyle: .body))) + + XCTAssertFalse(context.viewState.isOwnMention) + XCTAssertFalse(context.viewState.isUndefined) + XCTAssertEqual(context.viewState.image, .roomAvatar(.room(id: "2", name: "Foundation and Empire", avatarURL: nil))) + XCTAssertEqual(context.viewState.displayText, "Foundation and Empire") + } + + func testRoomAliasMentionMissingRoom() { + let proxyMock = JoinedRoomProxyMock(.init()) + let mockController = MockTimelineController() + mockController.roomProxy = proxyMock + let mock = TimelineViewModel(roomProxy: proxyMock, + timelineController: mockController, + mediaProvider: MediaProviderMock(configuration: .init()), + mediaPlayerProvider: MediaPlayerProviderMock(), + voiceMessageMediaManager: VoiceMessageMediaManagerMock(), + userIndicatorController: ServiceLocator.shared.userIndicatorController, + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings, + analyticsService: ServiceLocator.shared.analytics, + emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + clientProxy: ClientProxyMock(.init())) + let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .roomAlias("#foundation-and-empire:matrix.org"), font: .preferredFont(forTextStyle: .body))) + + XCTAssertFalse(context.viewState.isOwnMention) + XCTAssertFalse(context.viewState.isUndefined) + XCTAssertEqual(context.viewState.image, .link) + XCTAssertEqual(context.viewState.displayText, "#foundation-and-empire:matrix.org") + } + + func testEventOnRoomIDMention() { + let proxyMock = JoinedRoomProxyMock(.init()) + let mockController = MockTimelineController() + mockController.roomProxy = proxyMock + let clientMock = ClientProxyMock(.init()) + clientMock.roomSummaryForIdentifierReturnValue = .mock(id: "1", name: "Foundation 🔭🪐🌌") + let mock = TimelineViewModel(roomProxy: proxyMock, + timelineController: mockController, + mediaProvider: MediaProviderMock(configuration: .init()), + mediaPlayerProvider: MediaPlayerProviderMock(), + voiceMessageMediaManager: VoiceMessageMediaManagerMock(), + userIndicatorController: ServiceLocator.shared.userIndicatorController, + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings, + analyticsService: ServiceLocator.shared.analytics, + emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + clientProxy: clientMock) + let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .event(room: .roomID("1")), font: .preferredFont(forTextStyle: .body))) + + XCTAssertFalse(context.viewState.isOwnMention) + XCTAssertFalse(context.viewState.isUndefined) + XCTAssertEqual(context.viewState.image, .link) + XCTAssertEqual(context.viewState.displayText, L10n.screenRoomEventPill("Foundation 🔭🪐🌌")) + } + + func testEventOnRoomIDMentionMissingRoom() { + let proxyMock = JoinedRoomProxyMock(.init()) + let mockController = MockTimelineController() + mockController.roomProxy = proxyMock + let mock = TimelineViewModel(roomProxy: proxyMock, + timelineController: mockController, + mediaProvider: MediaProviderMock(configuration: .init()), + mediaPlayerProvider: MediaPlayerProviderMock(), + voiceMessageMediaManager: VoiceMessageMediaManagerMock(), + userIndicatorController: ServiceLocator.shared.userIndicatorController, + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings, + analyticsService: ServiceLocator.shared.analytics, + emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + clientProxy: ClientProxyMock(.init())) + let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .event(room: .roomID("1")), font: .preferredFont(forTextStyle: .body))) + + XCTAssertFalse(context.viewState.isOwnMention) + XCTAssertFalse(context.viewState.isUndefined) + XCTAssertEqual(context.viewState.image, .link) + XCTAssertEqual(context.viewState.displayText, L10n.screenRoomEventPill("1")) + } + + func testEventOnRoomAliasMention() async throws { + let proxyMock = JoinedRoomProxyMock(.init()) + let mockController = MockTimelineController() + mockController.roomProxy = proxyMock + let clientMock = ClientProxyMock(.init()) + clientMock.roomSummaryForAliasReturnValue = .mock(id: "2", + name: "Foundation and Empire", + canonicalAlias: "#foundation-and-empire:matrix.org") + let mock = TimelineViewModel(roomProxy: proxyMock, + timelineController: mockController, + mediaProvider: MediaProviderMock(configuration: .init()), + mediaPlayerProvider: MediaPlayerProviderMock(), + voiceMessageMediaManager: VoiceMessageMediaManagerMock(), + userIndicatorController: ServiceLocator.shared.userIndicatorController, + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings, + analyticsService: ServiceLocator.shared.analytics, + emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + clientProxy: clientMock) + let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .event(room: .roomAlias("#foundation-and-empire:matrix.org")), font: .preferredFont(forTextStyle: .body))) + + XCTAssertFalse(context.viewState.isOwnMention) + XCTAssertFalse(context.viewState.isUndefined) + XCTAssertEqual(context.viewState.image, .link) + XCTAssertEqual(context.viewState.displayText, L10n.screenRoomEventPill("Foundation and Empire")) + } + + func testEventOnRoomAliasMentionMissingRoom() async throws { + let proxyMock = JoinedRoomProxyMock(.init()) + let mockController = MockTimelineController() + mockController.roomProxy = proxyMock + let mock = TimelineViewModel(roomProxy: proxyMock, + timelineController: mockController, + mediaProvider: MediaProviderMock(configuration: .init()), + mediaPlayerProvider: MediaPlayerProviderMock(), + voiceMessageMediaManager: VoiceMessageMediaManagerMock(), + userIndicatorController: ServiceLocator.shared.userIndicatorController, + appMediator: AppMediatorMock.default, + appSettings: ServiceLocator.shared.settings, + analyticsService: ServiceLocator.shared.analytics, + emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + clientProxy: ClientProxyMock(.init())) + let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .event(room: .roomAlias("#foundation-and-empire:matrix.org")), font: .preferredFont(forTextStyle: .body))) + + XCTAssertFalse(context.viewState.isOwnMention) + XCTAssertFalse(context.viewState.isUndefined) + XCTAssertEqual(context.viewState.image, .link) + XCTAssertEqual(context.viewState.displayText, L10n.screenRoomEventPill("#foundation-and-empire:matrix.org")) + } } diff --git a/UnitTests/Sources/RoomSummaryTests.swift b/UnitTests/Sources/RoomSummaryTests.swift index 017d175672..74d8e1b05d 100644 --- a/UnitTests/Sources/RoomSummaryTests.swift +++ b/UnitTests/Sources/RoomSummaryTests.swift @@ -68,6 +68,7 @@ class RoomSummaryTests: XCTestCase { unreadNotificationsCount: 0, notificationMode: nil, canonicalAlias: nil, + alternativeAliases: [], hasOngoingCall: false, isMarkedUnread: false, isFavourite: false) diff --git a/UnitTests/Sources/TimelineViewModelTests.swift b/UnitTests/Sources/TimelineViewModelTests.swift index 31bedc14fe..8d1c38a3b8 100644 --- a/UnitTests/Sources/TimelineViewModelTests.swift +++ b/UnitTests/Sources/TimelineViewModelTests.swift @@ -312,7 +312,8 @@ class TimelineViewModelTests: XCTestCase { appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics, emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), - timelineControllerFactory: TimelineControllerFactoryMock(.init())) + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + clientProxy: ClientProxyMock(.init())) return (viewModel, roomProxy, timelineProxy, timelineController) } @@ -338,7 +339,8 @@ class TimelineViewModelTests: XCTestCase { appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics, emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), - timelineControllerFactory: TimelineControllerFactoryMock(.init())) + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + clientProxy: ClientProxyMock(.init())) let deferred = deferFulfillment(viewModel.context.$viewState) { value in value.bindings.readReceiptsSummaryInfo?.orderedReceipts == receipts @@ -367,7 +369,8 @@ class TimelineViewModelTests: XCTestCase { appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics, emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), - timelineControllerFactory: TimelineControllerFactoryMock(.init())) + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + clientProxy: ClientProxyMock(.init())) XCTAssertEqual(configuration.pinnedEventIDs, viewModel.context.viewState.pinnedEventIDs) configuration.pinnedEventIDs = ["test1", "test2"] @@ -394,7 +397,8 @@ class TimelineViewModelTests: XCTestCase { appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics, emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), - timelineControllerFactory: TimelineControllerFactoryMock(.init())) + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + clientProxy: ClientProxyMock(.init())) var deferred = deferFulfillment(viewModel.context.$viewState) { value in value.canCurrentUserPin @@ -425,7 +429,8 @@ class TimelineViewModelTests: XCTestCase { appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics, emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings), - timelineControllerFactory: TimelineControllerFactoryMock(.init())) + timelineControllerFactory: TimelineControllerFactoryMock(.init()), + clientProxy: ClientProxyMock(.init())) } }