From ff33c07596e7620512b22253317b96e7767e0bfe Mon Sep 17 00:00:00 2001 From: Mauro <34335419+Velin92@users.noreply.github.com> Date: Mon, 22 Jul 2024 13:15:57 +0200 Subject: [PATCH] Fix editing items not in the timeline failing (#3075) --- ElementX.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../Mocks/Generated/GeneratedMocks.swift | 188 +++++++++++++++--- .../Mocks/Generated/SDKGeneratedMocks.swift | 76 +++++++ .../Sources/Services/Room/RoomProxy.swift | 10 + .../Services/Room/RoomProxyProtocol.swift | 2 + .../RoomTimelineController.swift | 44 +++- .../Services/Timeline/TimelineProxy.swift | 42 +--- .../Timeline/TimelineProxyProtocol.swift | 9 +- project.yml | 2 +- 10 files changed, 307 insertions(+), 72 deletions(-) diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index d395541ae..3989e7415 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -7471,7 +7471,7 @@ repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift"; requirement = { kind = exactVersion; - version = 1.0.26; + version = 1.0.27; }; }; 701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = { diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f224fe980..c506ff5bf 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -149,8 +149,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/element-hq/matrix-rust-components-swift", "state" : { - "revision" : "29a19a07df68a5fe97431d08c944ced27e791ae3", - "version" : "1.0.26" + "revision" : "3a1f56a8dc2b14c93e562ece82fbf780d2f79704", + "version" : "1.0.27" } }, { diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index fbf14a2a1..f86fcb299 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -9297,6 +9297,76 @@ class RoomProxyMock: RoomProxyProtocol { return markAsReadReceiptTypeReturnValue } } + //MARK: - edit + + var editEventIDNewContentUnderlyingCallsCount = 0 + var editEventIDNewContentCallsCount: Int { + get { + if Thread.isMainThread { + return editEventIDNewContentUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = editEventIDNewContentUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + editEventIDNewContentUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + editEventIDNewContentUnderlyingCallsCount = newValue + } + } + } + } + var editEventIDNewContentCalled: Bool { + return editEventIDNewContentCallsCount > 0 + } + var editEventIDNewContentReceivedArguments: (eventID: String, newContent: RoomMessageEventContentWithoutRelation)? + var editEventIDNewContentReceivedInvocations: [(eventID: String, newContent: RoomMessageEventContentWithoutRelation)] = [] + + var editEventIDNewContentUnderlyingReturnValue: Result! + var editEventIDNewContentReturnValue: Result! { + get { + if Thread.isMainThread { + return editEventIDNewContentUnderlyingReturnValue + } else { + var returnValue: Result? = nil + DispatchQueue.main.sync { + returnValue = editEventIDNewContentUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + editEventIDNewContentUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + editEventIDNewContentUnderlyingReturnValue = newValue + } + } + } + } + var editEventIDNewContentClosure: ((String, RoomMessageEventContentWithoutRelation) async -> Result)? + + func edit(eventID: String, newContent: RoomMessageEventContentWithoutRelation) async -> Result { + editEventIDNewContentCallsCount += 1 + editEventIDNewContentReceivedArguments = (eventID: eventID, newContent: newContent) + DispatchQueue.main.async { + self.editEventIDNewContentReceivedInvocations.append((eventID: eventID, newContent: newContent)) + } + if let editEventIDNewContentClosure = editEventIDNewContentClosure { + return await editEventIDNewContentClosure(eventID, newContent) + } else { + return editEventIDNewContentReturnValue + } + } //MARK: - sendTypingNotification var sendTypingNotificationIsTypingUnderlyingCallsCount = 0 @@ -12319,15 +12389,15 @@ class TimelineProxyMock: TimelineProxyProtocol { } //MARK: - edit - var editMessageHtmlIntentionalMentionsUnderlyingCallsCount = 0 - var editMessageHtmlIntentionalMentionsCallsCount: Int { + var editNewContentUnderlyingCallsCount = 0 + var editNewContentCallsCount: Int { get { if Thread.isMainThread { - return editMessageHtmlIntentionalMentionsUnderlyingCallsCount + return editNewContentUnderlyingCallsCount } else { var returnValue: Int? = nil DispatchQueue.main.sync { - returnValue = editMessageHtmlIntentionalMentionsUnderlyingCallsCount + returnValue = editNewContentUnderlyingCallsCount } return returnValue! @@ -12335,29 +12405,29 @@ class TimelineProxyMock: TimelineProxyProtocol { } set { if Thread.isMainThread { - editMessageHtmlIntentionalMentionsUnderlyingCallsCount = newValue + editNewContentUnderlyingCallsCount = newValue } else { DispatchQueue.main.sync { - editMessageHtmlIntentionalMentionsUnderlyingCallsCount = newValue + editNewContentUnderlyingCallsCount = newValue } } } } - var editMessageHtmlIntentionalMentionsCalled: Bool { - return editMessageHtmlIntentionalMentionsCallsCount > 0 + var editNewContentCalled: Bool { + return editNewContentCallsCount > 0 } - var editMessageHtmlIntentionalMentionsReceivedArguments: (timelineItemID: TimelineItemIdentifier, message: String, html: String?, intentionalMentions: IntentionalMentions)? - var editMessageHtmlIntentionalMentionsReceivedInvocations: [(timelineItemID: TimelineItemIdentifier, message: String, html: String?, intentionalMentions: IntentionalMentions)] = [] + var editNewContentReceivedArguments: (timelineItem: EventTimelineItem, newContent: RoomMessageEventContentWithoutRelation)? + var editNewContentReceivedInvocations: [(timelineItem: EventTimelineItem, newContent: RoomMessageEventContentWithoutRelation)] = [] - var editMessageHtmlIntentionalMentionsUnderlyingReturnValue: Result! - var editMessageHtmlIntentionalMentionsReturnValue: Result! { + var editNewContentUnderlyingReturnValue: Result! + var editNewContentReturnValue: Result! { get { if Thread.isMainThread { - return editMessageHtmlIntentionalMentionsUnderlyingReturnValue + return editNewContentUnderlyingReturnValue } else { var returnValue: Result? = nil DispatchQueue.main.sync { - returnValue = editMessageHtmlIntentionalMentionsUnderlyingReturnValue + returnValue = editNewContentUnderlyingReturnValue } return returnValue! @@ -12365,26 +12435,26 @@ class TimelineProxyMock: TimelineProxyProtocol { } set { if Thread.isMainThread { - editMessageHtmlIntentionalMentionsUnderlyingReturnValue = newValue + editNewContentUnderlyingReturnValue = newValue } else { DispatchQueue.main.sync { - editMessageHtmlIntentionalMentionsUnderlyingReturnValue = newValue + editNewContentUnderlyingReturnValue = newValue } } } } - var editMessageHtmlIntentionalMentionsClosure: ((TimelineItemIdentifier, String, String?, IntentionalMentions) async -> Result)? + var editNewContentClosure: ((EventTimelineItem, RoomMessageEventContentWithoutRelation) async -> Result)? - func edit(_ timelineItemID: TimelineItemIdentifier, message: String, html: String?, intentionalMentions: IntentionalMentions) async -> Result { - editMessageHtmlIntentionalMentionsCallsCount += 1 - editMessageHtmlIntentionalMentionsReceivedArguments = (timelineItemID: timelineItemID, message: message, html: html, intentionalMentions: intentionalMentions) + func edit(_ timelineItem: EventTimelineItem, newContent: RoomMessageEventContentWithoutRelation) async -> Result { + editNewContentCallsCount += 1 + editNewContentReceivedArguments = (timelineItem: timelineItem, newContent: newContent) DispatchQueue.main.async { - self.editMessageHtmlIntentionalMentionsReceivedInvocations.append((timelineItemID: timelineItemID, message: message, html: html, intentionalMentions: intentionalMentions)) + self.editNewContentReceivedInvocations.append((timelineItem: timelineItem, newContent: newContent)) } - if let editMessageHtmlIntentionalMentionsClosure = editMessageHtmlIntentionalMentionsClosure { - return await editMessageHtmlIntentionalMentionsClosure(timelineItemID, message, html, intentionalMentions) + if let editNewContentClosure = editNewContentClosure { + return await editNewContentClosure(timelineItem, newContent) } else { - return editMessageHtmlIntentionalMentionsReturnValue + return editNewContentReturnValue } } //MARK: - redact @@ -13477,6 +13547,76 @@ class TimelineProxyMock: TimelineProxyProtocol { return getLoadedReplyDetailsEventIDReturnValue } } + //MARK: - buildMessageContentFor + + var buildMessageContentForHtmlIntentionalMentionsUnderlyingCallsCount = 0 + var buildMessageContentForHtmlIntentionalMentionsCallsCount: Int { + get { + if Thread.isMainThread { + return buildMessageContentForHtmlIntentionalMentionsUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = buildMessageContentForHtmlIntentionalMentionsUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + buildMessageContentForHtmlIntentionalMentionsUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + buildMessageContentForHtmlIntentionalMentionsUnderlyingCallsCount = newValue + } + } + } + } + var buildMessageContentForHtmlIntentionalMentionsCalled: Bool { + return buildMessageContentForHtmlIntentionalMentionsCallsCount > 0 + } + var buildMessageContentForHtmlIntentionalMentionsReceivedArguments: (message: String, html: String?, intentionalMentions: Mentions)? + var buildMessageContentForHtmlIntentionalMentionsReceivedInvocations: [(message: String, html: String?, intentionalMentions: Mentions)] = [] + + var buildMessageContentForHtmlIntentionalMentionsUnderlyingReturnValue: RoomMessageEventContentWithoutRelation! + var buildMessageContentForHtmlIntentionalMentionsReturnValue: RoomMessageEventContentWithoutRelation! { + get { + if Thread.isMainThread { + return buildMessageContentForHtmlIntentionalMentionsUnderlyingReturnValue + } else { + var returnValue: RoomMessageEventContentWithoutRelation? = nil + DispatchQueue.main.sync { + returnValue = buildMessageContentForHtmlIntentionalMentionsUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + buildMessageContentForHtmlIntentionalMentionsUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + buildMessageContentForHtmlIntentionalMentionsUnderlyingReturnValue = newValue + } + } + } + } + var buildMessageContentForHtmlIntentionalMentionsClosure: ((String, String?, Mentions) -> RoomMessageEventContentWithoutRelation)? + + func buildMessageContentFor(_ message: String, html: String?, intentionalMentions: Mentions) -> RoomMessageEventContentWithoutRelation { + buildMessageContentForHtmlIntentionalMentionsCallsCount += 1 + buildMessageContentForHtmlIntentionalMentionsReceivedArguments = (message: message, html: html, intentionalMentions: intentionalMentions) + DispatchQueue.main.async { + self.buildMessageContentForHtmlIntentionalMentionsReceivedInvocations.append((message: message, html: html, intentionalMentions: intentionalMentions)) + } + if let buildMessageContentForHtmlIntentionalMentionsClosure = buildMessageContentForHtmlIntentionalMentionsClosure { + return buildMessageContentForHtmlIntentionalMentionsClosure(message, html, intentionalMentions) + } else { + return buildMessageContentForHtmlIntentionalMentionsReturnValue + } + } } class UserDiscoveryServiceMock: UserDiscoveryServiceProtocol { diff --git a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift index 192bf60a2..c96019a8b 100644 --- a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift @@ -9565,6 +9565,82 @@ open class NotificationSettingsSDKMock: MatrixRustSDK.NotificationSettings { try await unmuteRoomRoomIdIsEncryptedIsOneToOneClosure?(roomId, isEncrypted, isOneToOne) } } +open class OidcAuthorizationDataSDKMock: MatrixRustSDK.OidcAuthorizationData { + init() { + super.init(noPointer: .init()) + } + + public required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + fatalError("init(unsafeFromRawPointer:) has not been implemented") + } + + fileprivate var pointer: UnsafeMutableRawPointer! + + //MARK: - loginUrl + + var loginUrlUnderlyingCallsCount = 0 + open var loginUrlCallsCount: Int { + get { + if Thread.isMainThread { + return loginUrlUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = loginUrlUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + loginUrlUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + loginUrlUnderlyingCallsCount = newValue + } + } + } + } + open var loginUrlCalled: Bool { + return loginUrlCallsCount > 0 + } + + var loginUrlUnderlyingReturnValue: String! + open var loginUrlReturnValue: String! { + get { + if Thread.isMainThread { + return loginUrlUnderlyingReturnValue + } else { + var returnValue: String? = nil + DispatchQueue.main.sync { + returnValue = loginUrlUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + loginUrlUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + loginUrlUnderlyingReturnValue = newValue + } + } + } + } + open var loginUrlClosure: (() -> String)? + + open override func loginUrl() -> String { + loginUrlCallsCount += 1 + if let loginUrlClosure = loginUrlClosure { + return loginUrlClosure() + } else { + return loginUrlReturnValue + } + } +} open class QrCodeDataSDKMock: MatrixRustSDK.QrCodeData { init() { super.init(noPointer: .init()) diff --git a/ElementX/Sources/Services/Room/RoomProxy.swift b/ElementX/Sources/Services/Room/RoomProxy.swift index 3c9224e69..9a32bc181 100644 --- a/ElementX/Sources/Services/Room/RoomProxy.swift +++ b/ElementX/Sources/Services/Room/RoomProxy.swift @@ -336,6 +336,16 @@ class RoomProxy: RoomProxyProtocol { } } + func edit(eventID: String, newContent: RoomMessageEventContentWithoutRelation) async -> Result { + do { + try await room.edit(eventId: eventID, newContent: newContent) + return .success(()) + } catch { + MXLog.error("Failed editing event id \(eventID), in room \(id) with error: \(error)") + return .failure(.sdkError(error)) + } + } + func sendTypingNotification(isTyping: Bool) async -> Result { MXLog.info("Sending typing notification isTyping: \(isTyping)") diff --git a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift index 7223711c5..6916f63cd 100644 --- a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift @@ -96,6 +96,8 @@ protocol RoomProxyProtocol { func markAsRead(receiptType: ReceiptType) async -> Result + func edit(eventID: String, newContent: RoomMessageEventContentWithoutRelation) async -> Result + /// https://spec.matrix.org/v1.9/client-server-api/#typing-notifications @discardableResult func sendTypingNotification(isTyping: Bool) async -> Result diff --git a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift index 97e0b3a23..d63345a24 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift @@ -191,15 +191,38 @@ class RoomTimelineController: RoomTimelineControllerProtocol { html: String?, intentionalMentions: IntentionalMentions) async { MXLog.info("Edit message in \(roomID)") + MXLog.info("Editing timeline item: \(timelineItemID)") - switch await activeTimeline.edit(timelineItemID, - message: message, - html: html, - intentionalMentions: intentionalMentions) { - case .success: - MXLog.info("Finished editing message") - case .failure(let error): - MXLog.error("Failed editing message with error: \(error)") + let editMode: EditMode + if !timelineItemID.timelineID.isEmpty, + let timelineItem = liveTimelineProvider.itemProxies.firstEventTimelineItemUsingStableID(timelineItemID) { + editMode = .byEvent(timelineItem) + } else if let eventID = timelineItemID.eventID { + editMode = .byID(eventID) + } else { + MXLog.error("Unknown timeline item: \(timelineItemID)") + return + } + + let messageContent = activeTimeline.buildMessageContentFor(message, + html: html, + intentionalMentions: intentionalMentions.toRustMentions()) + + switch editMode { + case let .byEvent(item): + switch await activeTimeline.edit(item, newContent: messageContent) { + case .success: + MXLog.info("Finished editing message by event") + case let .failure(error): + MXLog.error("Failed editing message by event with error: \(error)") + } + case let .byID(eventID): + switch await roomProxy.edit(eventID: eventID, newContent: messageContent) { + case .success: + MXLog.info("Finished editing message by event ID") + case let .failure(error): + MXLog.error("Failed editing message by event ID with error: \(error)") + } } } @@ -408,3 +431,8 @@ class RoomTimelineController: RoomTimelineControllerProtocol { return nil } } + +private enum EditMode { + case byEvent(EventTimelineItem) + case byID(String) +} diff --git a/ElementX/Sources/Services/Timeline/TimelineProxy.swift b/ElementX/Sources/Services/Timeline/TimelineProxy.swift index 904c62bcb..eda53d0f5 100644 --- a/ElementX/Sources/Services/Timeline/TimelineProxy.swift +++ b/ElementX/Sources/Services/Timeline/TimelineProxy.swift @@ -153,39 +153,17 @@ final class TimelineProxy: TimelineProxyProtocol { } } - func edit(_ timelineItemID: TimelineItemIdentifier, - message: String, html: String?, - intentionalMentions: IntentionalMentions) async -> Result { - MXLog.info("Editing timeline item: \(timelineItemID)") - - let timelineItem: EventTimelineItem? = if !timelineItemID.timelineID.isEmpty, - let timelineItem = await timelineProvider.itemProxies.firstEventTimelineItemUsingStableID(timelineItemID) { - timelineItem - } else if let eventID = timelineItemID.eventID { - nil // We need to edit by event ID which was removed. - } else { - nil - } - - guard let timelineItem else { - MXLog.error("Unknown timeline item: \(timelineItemID)") - return .failure(.failedEditing) - } - - let messageContent = buildMessageContentFor(message, - html: html, - intentionalMentions: intentionalMentions.toRustMentions()) - + func edit(_ timelineItem: EventTimelineItem, newContent: RoomMessageEventContentWithoutRelation) async -> Result { do { - guard try await timeline.edit(item: timelineItem, newContent: messageContent) == true else { + guard try await timeline.edit(item: timelineItem, newContent: newContent) == true else { return .failure(.failedEditing) } - MXLog.info("Finished editing timeline item: \(timelineItemID)") + MXLog.info("Finished editing timeline item: \(timelineItem.eventId() ?? timelineItem.transactionId() ?? "unknown")") return .success(()) } catch { - MXLog.error("Failed editing timeline item: \(timelineItemID) with error: \(error)") + MXLog.error("Failed editing timeline item: \(timelineItem.eventId() ?? timelineItem.transactionId() ?? "unknown") with error: \(error)") return .failure(.sdkError(error)) } } @@ -505,12 +483,10 @@ final class TimelineProxy: TimelineProxyProtocol { return .failure(.sdkError(error)) } } - - // MARK: - Private - - private func buildMessageContentFor(_ message: String, - html: String?, - intentionalMentions: Mentions) -> RoomMessageEventContentWithoutRelation { + + func buildMessageContentFor(_ message: String, + html: String?, + intentionalMentions: Mentions) -> RoomMessageEventContentWithoutRelation { let emoteSlashCommand = "/me " let isEmote: Bool = message.starts(with: emoteSlashCommand) @@ -533,6 +509,8 @@ final class TimelineProxy: TimelineProxyProtocol { return content.withMentions(mentions: intentionalMentions) } + // MARK: - Private + private func buildEmoteMessageContentFor(_ message: String, html: String?) -> RoomMessageEventContentWithoutRelation { if let html { return messageEventContentFromHtmlAsEmote(body: message, htmlBody: html) diff --git a/ElementX/Sources/Services/Timeline/TimelineProxyProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineProxyProtocol.swift index 8199c67be..646024c19 100644 --- a/ElementX/Sources/Services/Timeline/TimelineProxyProtocol.swift +++ b/ElementX/Sources/Services/Timeline/TimelineProxyProtocol.swift @@ -41,10 +41,7 @@ protocol TimelineProxyProtocol { func paginateBackwards(requestSize: UInt16) async -> Result func paginateForwards(requestSize: UInt16) async -> Result - func edit(_ timelineItemID: TimelineItemIdentifier, - message: String, - html: String?, - intentionalMentions: IntentionalMentions) async -> Result + func edit(_ timelineItem: EventTimelineItem, newContent: RoomMessageEventContentWithoutRelation) async -> Result func redact(_ timelineItemID: TimelineItemIdentifier, reason: String?) async -> Result @@ -109,6 +106,10 @@ protocol TimelineProxyProtocol { func sendPollResponse(pollStartID: String, answers: [String]) async -> Result func getLoadedReplyDetails(eventID: String) async -> Result + + func buildMessageContentFor(_ message: String, + html: String?, + intentionalMentions: Mentions) -> RoomMessageEventContentWithoutRelation } extension TimelineProxyProtocol { diff --git a/project.yml b/project.yml index ce274ff98..319ac0fcb 100644 --- a/project.yml +++ b/project.yml @@ -60,7 +60,7 @@ packages: # Element/Matrix dependencies MatrixRustSDK: url: https://github.com/element-hq/matrix-rust-components-swift - exactVersion: 1.0.26 + exactVersion: 1.0.27 # path: ../matrix-rust-sdk Compound: url: https://github.com/element-hq/compound-ios