diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 5165be02f..ead0131c1 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 56; objects = { /* Begin PBXBuildFile section */ @@ -615,6 +615,7 @@ A74438ED16F8683A4B793E6A /* AnalyticsSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */; }; A7BEE8216B4B12BE4C0F2C3F /* AppLockSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892EF45CCC5D2BF0FD1F770C /* AppLockSettingsScreenViewModel.swift */; }; A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; }; + A7E7D2FF2AE2A42200EAFDBC /* IntentionalMentions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7E7D2FE2AE2A42200EAFDBC /* IntentionalMentions.swift */; }; A7FD7B992E6EE6E5A8429197 /* RoomSummaryDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142808B69851451AC32A2CEA /* RoomSummaryDetails.swift */; }; A816F7087C495D85048AC50E /* RoomMemberDetailsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B6E30BB748F3F480F077969 /* RoomMemberDetailsScreenModels.swift */; }; A851635B3255C6DC07034A12 /* RoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8108C8F0ACF6A7EB72D0117 /* RoomScreenCoordinator.swift */; }; @@ -1048,7 +1049,7 @@ 127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentValuePublisher.swift; sourceTree = ""; }; 12EDAFB64FA5F6812D54F39A /* MigrationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationScreenViewModel.swift; sourceTree = ""; }; 12F1E7F9C2BE8BB751037826 /* WaitlistScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenCoordinator.swift; sourceTree = ""; }; - 1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; path = IntegrationTests.xctestplan; sourceTree = ""; }; + 1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = IntegrationTests.xctestplan; sourceTree = ""; }; 130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNTextInputNotificationResponse+Creator.swift"; sourceTree = ""; }; 13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; 1423AB065857FA546444DB15 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = ""; }; @@ -1454,7 +1455,7 @@ 8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomStateEventStringBuilder.swift; sourceTree = ""; }; 8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; 8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = ""; }; - 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = ""; }; + 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UITests.xctestplan; sourceTree = ""; }; 8E1BBA73B611EDEEA6E20E05 /* InvitesScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenModels.swift; sourceTree = ""; }; 8EC57A32ABC80D774CC663DB /* SettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenUITests.swift; sourceTree = ""; }; 8F21ED7205048668BEB44A38 /* AppActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppActivityView.swift; sourceTree = ""; }; @@ -1530,6 +1531,7 @@ A6C11AD9813045E44F950410 /* ElementCallWidgetDriverProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallWidgetDriverProtocol.swift; sourceTree = ""; }; A73A07BAEDD74C48795A996A /* AsyncSequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncSequence.swift; sourceTree = ""; }; A7C4EA55DA62F9D0F984A2AE /* CollapsibleTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleTimelineItem.swift; sourceTree = ""; }; + A7E7D2FE2AE2A42200EAFDBC /* IntentionalMentions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentionalMentions.swift; sourceTree = ""; }; A861DA5932B128FE1DCB5CE2 /* InviteUsersScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenCoordinator.swift; sourceTree = ""; }; A8903A9F615BBD0E6D7CD133 /* ApplicationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationProtocol.swift; sourceTree = ""; }; A9FAFE1C2149E6AC8156ED2B /* Collection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Collection.swift; sourceTree = ""; }; @@ -1582,7 +1584,7 @@ B4CFE236419E830E8946639C /* Analytics+SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Analytics+SwiftUI.swift"; sourceTree = ""; }; B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = ""; }; B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = ""; }; - B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; path = ConfettiScene.scn; sourceTree = ""; }; + B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = ConfettiScene.scn; sourceTree = ""; }; B6311F21F911E23BE4DF51B4 /* ReadMarkerRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineView.swift; sourceTree = ""; }; B63B69F9A2BC74DD40DC75C8 /* AdvancedSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsScreenViewModel.swift; sourceTree = ""; }; B697816AF93DA06EC58C5D70 /* WaitlistScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenViewModelProtocol.swift; sourceTree = ""; }; @@ -1683,7 +1685,7 @@ CD95B3714F806AC9CF9A557B /* ComposerToolbarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerToolbarViewModel.swift; sourceTree = ""; }; CDB3227C7A74B734924942E9 /* RoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProvider.swift; sourceTree = ""; }; CEE0E6043EFCF6FD2A341861 /* TimelineReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReplyView.swift; sourceTree = ""; }; - CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; path = UnitTests.xctestplan; sourceTree = ""; }; + CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = ""; }; CF48AF076424DBC1615C74AD /* AuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProxy.swift; sourceTree = ""; }; D0140615D2232612C813FD6C /* EncryptedHistoryRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedHistoryRoomTimelineItem.swift; sourceTree = ""; }; D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoverySection.swift; sourceTree = ""; }; @@ -1779,7 +1781,7 @@ ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModel.swift; sourceTree = ""; }; ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = ""; }; ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = ""; }; - ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; path = message.caf; sourceTree = ""; }; + ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = message.caf; sourceTree = ""; }; ED983D4DCA5AFA6E1ED96099 /* StateRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineView.swift; sourceTree = ""; }; EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = ""; }; EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = ""; }; @@ -1793,7 +1795,7 @@ F174A5627CDB3CAF280D1880 /* EmojiPickerScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenModels.swift; sourceTree = ""; }; F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProvider.swift; sourceTree = ""; }; F1B8500C152BC59445647DA8 /* UnsupportedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsupportedRoomTimelineItem.swift; sourceTree = ""; }; - F2D513D2477B57F90E98EEC0 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; path = portrait_test_video.mp4; sourceTree = ""; }; + F2D513D2477B57F90E98EEC0 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = portrait_test_video.mp4; sourceTree = ""; }; F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixEntityRegexTests.swift; sourceTree = ""; }; F348B5F2C12F9D4F4B4D3884 /* VideoRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRoomTimelineItem.swift; sourceTree = ""; }; F36C0A6D59717193F49EA986 /* UserSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionTests.swift; sourceTree = ""; }; @@ -4434,6 +4436,7 @@ 2F2FED77226A43559F009463 /* TimelineController */, 6B0910BCE4F1B02F124E1A09 /* TimelineItemContent */, 95BE1C7CB2C80344FF0BE724 /* TimelineItems */, + A7E7D2FE2AE2A42200EAFDBC /* IntentionalMentions.swift */, ); path = Timeline; sourceTree = ""; @@ -5508,6 +5511,7 @@ 339BC18777912E1989F2F17D /* Section.swift in Sources */, F833D5B5BE6707F961FA88DB /* SecureBackupController.swift in Sources */, 0C88044649BAEE6C49BFC43A /* SecureBackupControllerProtocol.swift in Sources */, + A7E7D2FF2AE2A42200EAFDBC /* IntentionalMentions.swift in Sources */, 21F29351EDD7B2A5534EE862 /* SecureBackupKeyBackupScreen.swift in Sources */, 5BC6C4ADFE7F2A795ECDE130 /* SecureBackupKeyBackupScreenCoordinator.swift in Sources */, B7888FC1E1DEF816D175C8D6 /* SecureBackupKeyBackupScreenModels.swift in Sources */, @@ -6368,7 +6372,7 @@ repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift"; requirement = { kind = exactVersion; - version = 1.1.23; + version = 1.1.24; }; }; 821C67C9A7F8CC3FD41B28B4 /* XCRemoteSwiftPackageReference "emojibase-bindings" */ = { @@ -6384,7 +6388,7 @@ repositoryURL = "https://github.com/matrix-org/matrix-wysiwyg-composer-swift"; requirement = { kind = exactVersion; - version = 2.14.3; + version = 2.14.4; }; }; 96495DD8554E2F39D3954354 /* XCRemoteSwiftPackageReference "posthog-ios" */ = { diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 420ea32c7..8745555d5 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -128,8 +128,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/matrix-org/matrix-rust-components-swift", "state" : { - "revision" : "c5ca416942b4a4af82126ac2bf7f08ef7af5bb19", - "version" : "1.1.23" + "revision" : "9529d89222115f1013f313cff36cec562cec1a69", + "version" : "1.1.24" } }, { @@ -137,8 +137,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/matrix-org/matrix-wysiwyg-composer-swift", "state" : { - "revision" : "a82eece5d3ce6aff87bcd5460f04bedc9e2492cf", - "version" : "2.14.3" + "revision" : "e60d0ffa9fbb1368bd837c94dede2d8d48952c7f", + "version" : "2.14.4" } }, { @@ -261,7 +261,7 @@ { "identity" : "swiftui-introspect", "kind" : "remoteSourceControl", - "location" : "https://github.com/siteline/SwiftUI-Introspect", + "location" : "https://github.com/siteline/SwiftUI-Introspect.git", "state" : { "revision" : "b94da693e57eaf79d16464b8b7c90d09cba4e290", "version" : "0.9.2" diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index 869a3be3e..ad9f55512 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -249,7 +249,9 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate, return } let roomProxy = await userSession.clientProxy.roomForIdentifier(roomID) - switch await roomProxy?.sendMessage(replyText, html: nil) { + switch await roomProxy?.sendMessage(replyText, + html: nil, + intentionalMentions: .empty) { case .success: break default: diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 8b93ef1b9..d02b2e5a3 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -1625,23 +1625,23 @@ class RoomProxyMock: RoomProxyProtocol { } //MARK: - sendMessage - var sendMessageHtmlInReplyToCallsCount = 0 - var sendMessageHtmlInReplyToCalled: Bool { - return sendMessageHtmlInReplyToCallsCount > 0 + var sendMessageHtmlInReplyToIntentionalMentionsCallsCount = 0 + var sendMessageHtmlInReplyToIntentionalMentionsCalled: Bool { + return sendMessageHtmlInReplyToIntentionalMentionsCallsCount > 0 } - var sendMessageHtmlInReplyToReceivedArguments: (message: String, html: String?, eventID: String?)? - var sendMessageHtmlInReplyToReceivedInvocations: [(message: String, html: String?, eventID: String?)] = [] - var sendMessageHtmlInReplyToReturnValue: Result! - var sendMessageHtmlInReplyToClosure: ((String, String?, String?) async -> Result)? + var sendMessageHtmlInReplyToIntentionalMentionsReceivedArguments: (message: String, html: String?, eventID: String?, intentionalMentions: IntentionalMentions)? + var sendMessageHtmlInReplyToIntentionalMentionsReceivedInvocations: [(message: String, html: String?, eventID: String?, intentionalMentions: IntentionalMentions)] = [] + var sendMessageHtmlInReplyToIntentionalMentionsReturnValue: Result! + var sendMessageHtmlInReplyToIntentionalMentionsClosure: ((String, String?, String?, IntentionalMentions) async -> Result)? - func sendMessage(_ message: String, html: String?, inReplyTo eventID: String?) async -> Result { - sendMessageHtmlInReplyToCallsCount += 1 - sendMessageHtmlInReplyToReceivedArguments = (message: message, html: html, eventID: eventID) - sendMessageHtmlInReplyToReceivedInvocations.append((message: message, html: html, eventID: eventID)) - if let sendMessageHtmlInReplyToClosure = sendMessageHtmlInReplyToClosure { - return await sendMessageHtmlInReplyToClosure(message, html, eventID) + func sendMessage(_ message: String, html: String?, inReplyTo eventID: String?, intentionalMentions: IntentionalMentions) async -> Result { + sendMessageHtmlInReplyToIntentionalMentionsCallsCount += 1 + sendMessageHtmlInReplyToIntentionalMentionsReceivedArguments = (message: message, html: html, eventID: eventID, intentionalMentions: intentionalMentions) + sendMessageHtmlInReplyToIntentionalMentionsReceivedInvocations.append((message: message, html: html, eventID: eventID, intentionalMentions: intentionalMentions)) + if let sendMessageHtmlInReplyToIntentionalMentionsClosure = sendMessageHtmlInReplyToIntentionalMentionsClosure { + return await sendMessageHtmlInReplyToIntentionalMentionsClosure(message, html, eventID, intentionalMentions) } else { - return sendMessageHtmlInReplyToReturnValue + return sendMessageHtmlInReplyToIntentionalMentionsReturnValue } } //MARK: - toggleReaction @@ -1788,23 +1788,23 @@ class RoomProxyMock: RoomProxyProtocol { } //MARK: - editMessage - var editMessageHtmlOriginalCallsCount = 0 - var editMessageHtmlOriginalCalled: Bool { - return editMessageHtmlOriginalCallsCount > 0 + var editMessageHtmlOriginalIntentionalMentionsCallsCount = 0 + var editMessageHtmlOriginalIntentionalMentionsCalled: Bool { + return editMessageHtmlOriginalIntentionalMentionsCallsCount > 0 } - var editMessageHtmlOriginalReceivedArguments: (newMessage: String, html: String?, eventID: String)? - var editMessageHtmlOriginalReceivedInvocations: [(newMessage: String, html: String?, eventID: String)] = [] - var editMessageHtmlOriginalReturnValue: Result! - var editMessageHtmlOriginalClosure: ((String, String?, String) async -> Result)? + var editMessageHtmlOriginalIntentionalMentionsReceivedArguments: (newMessage: String, html: String?, eventID: String, intentionalMentions: IntentionalMentions)? + var editMessageHtmlOriginalIntentionalMentionsReceivedInvocations: [(newMessage: String, html: String?, eventID: String, intentionalMentions: IntentionalMentions)] = [] + var editMessageHtmlOriginalIntentionalMentionsReturnValue: Result! + var editMessageHtmlOriginalIntentionalMentionsClosure: ((String, String?, String, IntentionalMentions) async -> Result)? - func editMessage(_ newMessage: String, html: String?, original eventID: String) async -> Result { - editMessageHtmlOriginalCallsCount += 1 - editMessageHtmlOriginalReceivedArguments = (newMessage: newMessage, html: html, eventID: eventID) - editMessageHtmlOriginalReceivedInvocations.append((newMessage: newMessage, html: html, eventID: eventID)) - if let editMessageHtmlOriginalClosure = editMessageHtmlOriginalClosure { - return await editMessageHtmlOriginalClosure(newMessage, html, eventID) + func editMessage(_ newMessage: String, html: String?, original eventID: String, intentionalMentions: IntentionalMentions) async -> Result { + editMessageHtmlOriginalIntentionalMentionsCallsCount += 1 + editMessageHtmlOriginalIntentionalMentionsReceivedArguments = (newMessage: newMessage, html: html, eventID: eventID, intentionalMentions: intentionalMentions) + editMessageHtmlOriginalIntentionalMentionsReceivedInvocations.append((newMessage: newMessage, html: html, eventID: eventID, intentionalMentions: intentionalMentions)) + if let editMessageHtmlOriginalIntentionalMentionsClosure = editMessageHtmlOriginalIntentionalMentionsClosure { + return await editMessageHtmlOriginalIntentionalMentionsClosure(newMessage, html, eventID, intentionalMentions) } else { - return editMessageHtmlOriginalReturnValue + return editMessageHtmlOriginalIntentionalMentionsReturnValue } } //MARK: - redact diff --git a/ElementX/Sources/Screens/ComposerToolbar/ComposerToolbarModels.swift b/ElementX/Sources/Screens/ComposerToolbar/ComposerToolbarModels.swift index 27c3f0179..5815efb3a 100644 --- a/ElementX/Sources/Screens/ComposerToolbar/ComposerToolbarModels.swift +++ b/ElementX/Sources/Screens/ComposerToolbar/ComposerToolbarModels.swift @@ -19,7 +19,7 @@ import UIKit import WysiwygComposer enum ComposerToolbarViewModelAction { - case sendMessage(plain: String, html: String?, mode: RoomScreenComposerMode) + case sendMessage(plain: String, html: String?, mode: RoomScreenComposerMode, intentionalMentions: IntentionalMentions) case displayCameraPicker case displayMediaPicker case displayDocumentPicker diff --git a/ElementX/Sources/Screens/ComposerToolbar/ComposerToolbarViewModel.swift b/ElementX/Sources/Screens/ComposerToolbar/ComposerToolbarViewModel.swift index 333c11037..f42335304 100644 --- a/ElementX/Sources/Screens/ComposerToolbar/ComposerToolbarViewModel.swift +++ b/ElementX/Sources/Screens/ComposerToolbar/ComposerToolbarViewModel.swift @@ -111,7 +111,10 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool let sendHTML = ServiceLocator.shared.settings.richTextEditorEnabled actionsSubject.send(.sendMessage(plain: wysiwygViewModel.content.markdown, html: sendHTML ? wysiwygViewModel.content.html : nil, - mode: state.composerMode)) + mode: state.composerMode, + intentionalMentions: wysiwygViewModel + .getMentionsState() + .toIntentionalMentions())) } case .cancelReply: set(mode: .default) diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index 28be08695..6b2b9a338 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -171,8 +171,13 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol func process(composerAction: ComposerToolbarViewModelAction) { switch composerAction { - case .sendMessage(let message, let html, let mode): - Task { await sendCurrentMessage(message, html: html, mode: mode) } + case .sendMessage(let message, let html, let mode, let intentionalMentions): + Task { + await sendCurrentMessage(message, + html: html, + mode: mode, + intentionalMentions: intentionalMentions) + } case .displayCameraPicker: actionsSubject.send(.displayCameraPicker) case .displayMediaPicker: @@ -468,7 +473,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol return eventTimelineItem.properties.reactions.isEmpty && eventTimelineItem.sender == otherEventTimelineItem.sender } - private func sendCurrentMessage(_ message: String, html: String?, mode: RoomScreenComposerMode) async { + private func sendCurrentMessage(_ message: String, html: String?, mode: RoomScreenComposerMode, intentionalMentions: IntentionalMentions) async { guard !message.isEmpty else { fatalError("This message should never be empty") } @@ -477,11 +482,19 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol switch mode { case .reply(let itemId, _, _): - await timelineController.sendMessage(message, html: html, inReplyTo: itemId) + await timelineController.sendMessage(message, + html: html, + inReplyTo: itemId, + intentionalMentions: intentionalMentions) case .edit(let originalItemId): - await timelineController.editMessage(message, html: html, original: originalItemId) + await timelineController.editMessage(message, + html: html, + original: originalItemId, + intentionalMentions: intentionalMentions) case .default: - await timelineController.sendMessage(message, html: html) + await timelineController.sendMessage(message, + html: html, + intentionalMentions: intentionalMentions) case .recordVoiceMessage, .previewVoiceMessage: fatalError("invalid composer mode.") } diff --git a/ElementX/Sources/Services/ElementCall/ElementCallWidgetDriver.swift b/ElementX/Sources/Services/ElementCall/ElementCallWidgetDriver.swift index 98ca7b636..feac0287f 100644 --- a/ElementX/Sources/Services/ElementCall/ElementCallWidgetDriver.swift +++ b/ElementX/Sources/Services/ElementCall/ElementCallWidgetDriver.swift @@ -37,7 +37,7 @@ private struct ElementCallWidgetMessage: Codable { } } -class ElementCallWidgetDriver: WidgetPermissionsProvider, ElementCallWidgetDriverProtocol { +class ElementCallWidgetDriver: WidgetCapabilitiesProvider, ElementCallWidgetDriverProtocol { private let room: RoomProtocol private var widgetDriver: WidgetDriverAndHandle? @@ -66,7 +66,7 @@ class ElementCallWidgetDriver: WidgetPermissionsProvider, ElementCallWidgetDrive appPrompt: false, skipLobby: true, confineToRoom: true, - fonts: nil, + font: nil, analyticsId: nil)) else { return .failure(.failedBuildingWidgetSettings) } @@ -114,7 +114,7 @@ class ElementCallWidgetDriver: WidgetPermissionsProvider, ElementCallWidgetDrive MXLog.debug("Stopped widget driver") } - await widgetDriver.driver.run(room: room, permissionsProvider: self) + await widgetDriver.driver.run(room: room, capabilitiesProvider: self) } return .success(url) @@ -133,10 +133,10 @@ class ElementCallWidgetDriver: WidgetPermissionsProvider, ElementCallWidgetDrive return .success(result) } - // MARK: - WidgetPermissionsProvider + // MARK: - WidgetCapabilitiesProvider - func acquirePermissions(permissions: MatrixRustSDK.WidgetPermissions) -> MatrixRustSDK.WidgetPermissions { - permissions + func acquireCapabilities(capabilities: WidgetPermissions) -> WidgetPermissions { + capabilities } // MARK: - Private diff --git a/ElementX/Sources/Services/Room/RoomProxy.swift b/ElementX/Sources/Services/Room/RoomProxy.swift index 936249634..20aa6c8c4 100644 --- a/ElementX/Sources/Services/Room/RoomProxy.swift +++ b/ElementX/Sources/Services/Room/RoomProxy.swift @@ -257,13 +257,18 @@ class RoomProxy: RoomProxyProtocol { } } - func sendMessage(_ message: String, html: String?, inReplyTo eventID: String? = nil) async -> Result { + func sendMessage(_ message: String, + html: String?, + inReplyTo eventID: String? = nil, + intentionalMentions: IntentionalMentions) async -> Result { sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) defer { sendMessageBackgroundTask?.stop() } - let messageContent = buildMessageContentFor(message, html: html) + let messageContent = buildMessageContentFor(message, + html: html, + intentionalMentions: intentionalMentions.toRustMentions()) return await Task.dispatch(on: messageSendingDispatchQueue) { do { @@ -435,13 +440,18 @@ class RoomProxy: RoomProxyProtocol { } } - func editMessage(_ message: String, html: String?, original eventID: String) async -> Result { + func editMessage(_ message: String, + html: String?, + original eventID: String, + intentionalMentions: IntentionalMentions) async -> Result { sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) defer { sendMessageBackgroundTask?.stop() } - let messageContent = buildMessageContentFor(message, html: html) + let messageContent = buildMessageContentFor(message, + html: html, + intentionalMentions: intentionalMentions.toRustMentions()) return await Task.dispatch(on: messageSendingDispatchQueue) { do { @@ -718,26 +728,29 @@ class RoomProxy: RoomProxyProtocol { // MARK: - Private - private func buildMessageContentFor(_ message: String, html: String?) -> RoomMessageEventContentWithoutRelation { + private func buildMessageContentFor(_ message: String, + html: String?, + intentionalMentions: Mentions) -> RoomMessageEventContentWithoutRelation { let emoteSlashCommand = "/me " let isEmote: Bool = message.starts(with: emoteSlashCommand) - guard isEmote else { + let content: RoomMessageEventContentWithoutRelation + if isEmote { + let emoteMessage = String(message.dropFirst(emoteSlashCommand.count)) + + var emoteHtml: String? if let html { - return messageEventContentFromHtml(body: message, htmlBody: html) + emoteHtml = String(html.dropFirst(emoteSlashCommand.count)) + } + content = buildEmoteMessageContentFor(emoteMessage, html: emoteHtml) + } else { + if let html { + content = messageEventContentFromHtml(body: message, htmlBody: html) } else { - return messageEventContentFromMarkdown(md: message) + content = messageEventContentFromMarkdown(md: message) } } - - let emoteMessage = String(message.dropFirst(emoteSlashCommand.count)) - - var emoteHtml: String? - if let html { - emoteHtml = String(html.dropFirst(emoteSlashCommand.count)) - } - - return buildEmoteMessageContentFor(emoteMessage, html: emoteHtml) + return content.withMentions(mentions: intentionalMentions) } private func buildEmoteMessageContentFor(_ message: String, html: String?) -> RoomMessageEventContentWithoutRelation { diff --git a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift index 42bf48e94..d1df024a9 100644 --- a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift @@ -96,7 +96,10 @@ protocol RoomProxyProtocol { func sendMessageEventContent(_ messageContent: RoomMessageEventContentWithoutRelation) async -> Result - func sendMessage(_ message: String, html: String?, inReplyTo eventID: String?) async -> Result + func sendMessage(_ message: String, + html: String?, + inReplyTo eventID: String?, + intentionalMentions: IntentionalMentions) async -> Result func toggleReaction(_ reaction: String, to eventID: String) async -> Result @@ -134,7 +137,10 @@ protocol RoomProxyProtocol { /// Cancels a failed message given its transaction ID from the timeline func cancelSend(transactionID: String) async - func editMessage(_ newMessage: String, html: String?, original eventID: String) async -> Result + func editMessage(_ newMessage: String, + html: String?, + original eventID: String, + intentionalMentions: IntentionalMentions) async -> Result func redact(_ eventID: String) async -> Result @@ -197,8 +203,13 @@ extension RoomProxyProtocol { } } - func sendMessage(_ message: String, html: String?) async -> Result { - await sendMessage(message, html: html, inReplyTo: nil) + func sendMessage(_ message: String, + html: String?, + intentionalMentions: IntentionalMentions) async -> Result { + await sendMessage(message, + html: html, + inReplyTo: nil, + intentionalMentions: intentionalMentions) } // Avoids to duplicate the same logic around in the app diff --git a/ElementX/Sources/Services/Timeline/IntentionalMentions.swift b/ElementX/Sources/Services/Timeline/IntentionalMentions.swift new file mode 100644 index 000000000..e923607b2 --- /dev/null +++ b/ElementX/Sources/Services/Timeline/IntentionalMentions.swift @@ -0,0 +1,41 @@ +// +// Copyright 2023 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +import MatrixRustSDK +import WysiwygComposer + +struct IntentionalMentions: Equatable { + let userIDs: Set + let atRoom: Bool + + static var empty: Self { + IntentionalMentions(userIDs: [], atRoom: false) + } +} + +extension IntentionalMentions { + func toRustMentions() -> Mentions { + Mentions(userIds: Array(userIDs), room: atRoom) + } +} + +extension MentionsState { + func toIntentionalMentions() -> IntentionalMentions { + IntentionalMentions(userIDs: Set(userIds), atRoom: hasAtRoomMention) + } +} diff --git a/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineController.swift b/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineController.swift index 1a7ab9c84..3d97e1db2 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineController.swift @@ -66,11 +66,17 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol { func processItemTap(_ itemID: TimelineItemIdentifier) async -> RoomTimelineControllerAction { .none } - func sendMessage(_ message: String, html: String?, inReplyTo itemID: TimelineItemIdentifier?) async { } + func sendMessage(_ message: String, + html: String?, + inReplyTo itemID: TimelineItemIdentifier?, + intentionalMentions: IntentionalMentions) async { } func toggleReaction(_ reaction: String, to itemID: TimelineItemIdentifier) async { } - func editMessage(_ newMessage: String, html: String?, original itemID: TimelineItemIdentifier) async { } + func editMessage(_ newMessage: String, + html: String?, + original itemID: TimelineItemIdentifier, + intentionalMentions: IntentionalMentions) async { } func redact(_ itemID: TimelineItemIdentifier) async { } diff --git a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift index 96cd06ff2..4712fac05 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift @@ -130,7 +130,10 @@ class RoomTimelineController: RoomTimelineControllerProtocol { } } - func sendMessage(_ message: String, html: String?, inReplyTo itemID: TimelineItemIdentifier?) async { + func sendMessage(_ message: String, + html: String?, + inReplyTo itemID: TimelineItemIdentifier?, + intentionalMentions: IntentionalMentions) async { var inReplyTo: String? if itemID == nil { MXLog.info("Send message in \(roomID)") @@ -142,7 +145,10 @@ class RoomTimelineController: RoomTimelineControllerProtocol { return } - switch await roomProxy.sendMessage(message, html: html, inReplyTo: inReplyTo) { + switch await roomProxy.sendMessage(message, + html: html, + inReplyTo: inReplyTo, + intentionalMentions: intentionalMentions) { case .success: MXLog.info("Finished sending message") case .failure(let error): @@ -165,16 +171,22 @@ class RoomTimelineController: RoomTimelineControllerProtocol { } } - func editMessage(_ newMessage: String, html: String?, original itemID: TimelineItemIdentifier) async { + func editMessage(_ newMessage: String, + html: String?, + original itemID: TimelineItemIdentifier, + intentionalMentions: IntentionalMentions) async { MXLog.info("Edit message in \(roomID)") if let timelineItem = timelineItems.firstUsingStableID(itemID), let item = timelineItem as? EventBasedTimelineItemProtocol, item.hasFailedToSend { MXLog.info("Editing a failed echo, will cancel and resend it as a new message") await cancelSend(itemID) - await sendMessage(newMessage, html: html) + await sendMessage(newMessage, html: html, intentionalMentions: intentionalMentions) } else if let eventID = itemID.eventID { - switch await roomProxy.editMessage(newMessage, html: html, original: eventID) { + switch await roomProxy.editMessage(newMessage, + html: html, + original: eventID, + intentionalMentions: intentionalMentions) { case .success: MXLog.info("Finished editing message") case .failure(let error): diff --git a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerProtocol.swift index a979cf12f..a96f39a08 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerProtocol.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerProtocol.swift @@ -51,9 +51,15 @@ protocol RoomTimelineControllerProtocol { func sendReadReceipt(for itemID: TimelineItemIdentifier) async -> Result - func sendMessage(_ message: String, html: String?, inReplyTo itemID: TimelineItemIdentifier?) async + func sendMessage(_ message: String, + html: String?, + inReplyTo itemID: TimelineItemIdentifier?, + intentionalMentions: IntentionalMentions) async - func editMessage(_ newMessage: String, html: String?, original itemID: TimelineItemIdentifier) async + func editMessage(_ newMessage: String, + html: String?, + original itemID: TimelineItemIdentifier, + intentionalMentions: IntentionalMentions) async func toggleReaction(_ reaction: String, to itemID: TimelineItemIdentifier) async @@ -75,7 +81,12 @@ protocol RoomTimelineControllerProtocol { } extension RoomTimelineControllerProtocol { - func sendMessage(_ message: String, html: String?) async { - await sendMessage(message, html: html, inReplyTo: nil) + func sendMessage(_ message: String, + html: String?, + intentionalMentions: IntentionalMentions) async { + await sendMessage(message, + html: html, + inReplyTo: nil, + intentionalMentions: intentionalMentions) } } diff --git a/UnitTests/Sources/ComposerToolbarViewModelTests.swift b/UnitTests/Sources/ComposerToolbarViewModelTests.swift index d20005031..df3fc1698 100644 --- a/UnitTests/Sources/ComposerToolbarViewModelTests.swift +++ b/UnitTests/Sources/ComposerToolbarViewModelTests.swift @@ -160,4 +160,25 @@ class ComposerToolbarViewModelTests: XCTestCase { let attachment = wysiwygViewModel.textView.attributedText.attribute(.attachment, at: 0, effectiveRange: nil) as? PillTextAttachment XCTAssertEqual(attachment?.pillData?.type, .allUsers) } + + func testIntentionalMentions() async throws { + wysiwygViewModel.setHtmlContent( + """ +

Hello @room \ + and especially hello to Test

+ """ + ) + + let deferred = deferFulfillment(viewModel.actions) { action in + switch action { + case let .sendMessage(_, _, _, intentionalMentions): + return intentionalMentions == IntentionalMentions(userIDs: ["@test:matrix.org"], atRoom: true) + default: + return false + } + } + viewModel.context.send(viewAction: .sendMessage) + + try await deferred.fulfill() + } } diff --git a/project.yml b/project.yml index 9fd46e94f..8f86eae8b 100644 --- a/project.yml +++ b/project.yml @@ -45,7 +45,7 @@ packages: # Element/Matrix dependencies MatrixRustSDK: url: https://github.com/matrix-org/matrix-rust-components-swift - exactVersion: 1.1.23 + exactVersion: 1.1.24 # path: ../matrix-rust-sdk Compound: url: https://github.com/vector-im/compound-ios @@ -112,7 +112,7 @@ packages: minorVersion: 2.0.0 WysiwygComposer: url: https://github.com/matrix-org/matrix-wysiwyg-composer-swift - exactVersion: 2.14.3 + exactVersion: 2.14.4 # path: ../matrix-wysiwyg/platforms/ios/lib/WysiwygComposer SwiftOGG: url: https://github.com/vector-im/swift-ogg