diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 820cc32e5..f93e4e927 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -7335,7 +7335,7 @@ repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift"; requirement = { kind = exactVersion; - version = 1.0.8; + version = 1.0.9; }; }; 701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = { diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c73084897..aaac073f6 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -139,8 +139,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/element-hq/matrix-rust-components-swift", "state" : { - "revision" : "464227df09ff5ab4d00e432df131f779ba2f7ced", - "version" : "1.0.8" + "revision" : "4f46a00c5a0ab3053f49f449b769237645f00b18", + "version" : "1.0.9" } }, { diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index 46b83a40d..519e53704 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -666,10 +666,12 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg ServiceLocator.shared.networkMonitor .reachabilityPublisher .removeDuplicates() - .sink { reachability in + .sink { [weak self] reachability in MXLog.info("Reachability changed to \(reachability)") if reachability == .reachable { + self?.userSession?.clientProxy.setSendingQueueEnabled(true) + ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(reachabilityNotificationIdentifier) } else { ServiceLocator.shared.userIndicatorController.submitIndicator(.init(id: reachabilityNotificationIdentifier, diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index aa8ca64fa..4b9cb51da 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -3496,6 +3496,45 @@ class ClientProxyMock: ClientProxyProtocol { return resolveRoomAliasReturnValue } } + //MARK: - setSendingQueueEnabled + + var setSendingQueueEnabledUnderlyingCallsCount = 0 + var setSendingQueueEnabledCallsCount: Int { + get { + if Thread.isMainThread { + return setSendingQueueEnabledUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = setSendingQueueEnabledUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + setSendingQueueEnabledUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + setSendingQueueEnabledUnderlyingCallsCount = newValue + } + } + } + } + var setSendingQueueEnabledCalled: Bool { + return setSendingQueueEnabledCallsCount > 0 + } + var setSendingQueueEnabledReceivedEnabled: Bool? + var setSendingQueueEnabledReceivedInvocations: [Bool] = [] + var setSendingQueueEnabledClosure: ((Bool) -> Void)? + + func setSendingQueueEnabled(_ enabled: Bool) { + setSendingQueueEnabledCallsCount += 1 + setSendingQueueEnabledReceivedEnabled = enabled + setSendingQueueEnabledReceivedInvocations.append(enabled) + setSendingQueueEnabledClosure?(enabled) + } //MARK: - ignoreUser var ignoreUserUnderlyingCallsCount = 0 @@ -11057,11 +11096,6 @@ class SessionVerificationControllerProxyMock: SessionVerificationControllerProxy } } class TimelineProxyMock: TimelineProxyProtocol { - var actions: AnyPublisher { - get { return underlyingActions } - set(value) { underlyingActions = value } - } - var underlyingActions: AnyPublisher! var isLive: Bool { get { return underlyingIsLive } set(value) { underlyingIsLive = value } @@ -11108,113 +11142,6 @@ class TimelineProxyMock: TimelineProxyProtocol { subscribeForUpdatesCallsCount += 1 await subscribeForUpdatesClosure?() } - //MARK: - cancelSend - - var cancelSendTransactionIDUnderlyingCallsCount = 0 - var cancelSendTransactionIDCallsCount: Int { - get { - if Thread.isMainThread { - return cancelSendTransactionIDUnderlyingCallsCount - } else { - var returnValue: Int? = nil - DispatchQueue.main.sync { - returnValue = cancelSendTransactionIDUnderlyingCallsCount - } - - return returnValue! - } - } - set { - if Thread.isMainThread { - cancelSendTransactionIDUnderlyingCallsCount = newValue - } else { - DispatchQueue.main.sync { - cancelSendTransactionIDUnderlyingCallsCount = newValue - } - } - } - } - var cancelSendTransactionIDCalled: Bool { - return cancelSendTransactionIDCallsCount > 0 - } - var cancelSendTransactionIDReceivedTransactionID: String? - var cancelSendTransactionIDReceivedInvocations: [String] = [] - var cancelSendTransactionIDClosure: ((String) async -> Void)? - - func cancelSend(transactionID: String) async { - cancelSendTransactionIDCallsCount += 1 - cancelSendTransactionIDReceivedTransactionID = transactionID - cancelSendTransactionIDReceivedInvocations.append(transactionID) - await cancelSendTransactionIDClosure?(transactionID) - } - //MARK: - editMessage - - var editMessageHtmlOriginalIntentionalMentionsUnderlyingCallsCount = 0 - var editMessageHtmlOriginalIntentionalMentionsCallsCount: Int { - get { - if Thread.isMainThread { - return editMessageHtmlOriginalIntentionalMentionsUnderlyingCallsCount - } else { - var returnValue: Int? = nil - DispatchQueue.main.sync { - returnValue = editMessageHtmlOriginalIntentionalMentionsUnderlyingCallsCount - } - - return returnValue! - } - } - set { - if Thread.isMainThread { - editMessageHtmlOriginalIntentionalMentionsUnderlyingCallsCount = newValue - } else { - DispatchQueue.main.sync { - editMessageHtmlOriginalIntentionalMentionsUnderlyingCallsCount = newValue - } - } - } - } - var editMessageHtmlOriginalIntentionalMentionsCalled: Bool { - return editMessageHtmlOriginalIntentionalMentionsCallsCount > 0 - } - var editMessageHtmlOriginalIntentionalMentionsReceivedArguments: (message: String, html: String?, eventID: String, intentionalMentions: IntentionalMentions)? - var editMessageHtmlOriginalIntentionalMentionsReceivedInvocations: [(message: String, html: String?, eventID: String, intentionalMentions: IntentionalMentions)] = [] - - var editMessageHtmlOriginalIntentionalMentionsUnderlyingReturnValue: Result! - var editMessageHtmlOriginalIntentionalMentionsReturnValue: Result! { - get { - if Thread.isMainThread { - return editMessageHtmlOriginalIntentionalMentionsUnderlyingReturnValue - } else { - var returnValue: Result? = nil - DispatchQueue.main.sync { - returnValue = editMessageHtmlOriginalIntentionalMentionsUnderlyingReturnValue - } - - return returnValue! - } - } - set { - if Thread.isMainThread { - editMessageHtmlOriginalIntentionalMentionsUnderlyingReturnValue = newValue - } else { - DispatchQueue.main.sync { - editMessageHtmlOriginalIntentionalMentionsUnderlyingReturnValue = newValue - } - } - } - } - var editMessageHtmlOriginalIntentionalMentionsClosure: ((String, String?, String, IntentionalMentions) async -> Result)? - - func editMessage(_ message: String, html: String?, original eventID: String, intentionalMentions: IntentionalMentions) async -> Result { - editMessageHtmlOriginalIntentionalMentionsCallsCount += 1 - editMessageHtmlOriginalIntentionalMentionsReceivedArguments = (message: message, html: html, eventID: eventID, intentionalMentions: intentionalMentions) - editMessageHtmlOriginalIntentionalMentionsReceivedInvocations.append((message: message, html: html, eventID: eventID, intentionalMentions: intentionalMentions)) - if let editMessageHtmlOriginalIntentionalMentionsClosure = editMessageHtmlOriginalIntentionalMentionsClosure { - return await editMessageHtmlOriginalIntentionalMentionsClosure(message, html, eventID, intentionalMentions) - } else { - return editMessageHtmlOriginalIntentionalMentionsReturnValue - } - } //MARK: - fetchDetails var fetchDetailsForUnderlyingCallsCount = 0 @@ -11283,8 +11210,8 @@ class TimelineProxyMock: TimelineProxyProtocol { var messageEventContentForCalled: Bool { return messageEventContentForCallsCount > 0 } - var messageEventContentForReceivedEventID: String? - var messageEventContentForReceivedInvocations: [String] = [] + var messageEventContentForReceivedTimelineItemID: TimelineItemIdentifier? + var messageEventContentForReceivedInvocations: [TimelineItemIdentifier] = [] var messageEventContentForUnderlyingReturnValue: RoomMessageEventContentWithoutRelation? var messageEventContentForReturnValue: RoomMessageEventContentWithoutRelation? { @@ -11310,14 +11237,14 @@ class TimelineProxyMock: TimelineProxyProtocol { } } } - var messageEventContentForClosure: ((String) async -> RoomMessageEventContentWithoutRelation?)? + var messageEventContentForClosure: ((TimelineItemIdentifier) async -> RoomMessageEventContentWithoutRelation?)? - func messageEventContent(for eventID: String) async -> RoomMessageEventContentWithoutRelation? { + func messageEventContent(for timelineItemID: TimelineItemIdentifier) async -> RoomMessageEventContentWithoutRelation? { messageEventContentForCallsCount += 1 - messageEventContentForReceivedEventID = eventID - messageEventContentForReceivedInvocations.append(eventID) + messageEventContentForReceivedTimelineItemID = timelineItemID + messageEventContentForReceivedInvocations.append(timelineItemID) if let messageEventContentForClosure = messageEventContentForClosure { - return await messageEventContentForClosure(eventID) + return await messageEventContentForClosure(timelineItemID) } else { return messageEventContentForReturnValue } @@ -11361,45 +11288,6 @@ class TimelineProxyMock: TimelineProxyProtocol { retryDecryptionForReceivedInvocations.append(sessionID) await retryDecryptionForClosure?(sessionID) } - //MARK: - retrySend - - var retrySendTransactionIDUnderlyingCallsCount = 0 - var retrySendTransactionIDCallsCount: Int { - get { - if Thread.isMainThread { - return retrySendTransactionIDUnderlyingCallsCount - } else { - var returnValue: Int? = nil - DispatchQueue.main.sync { - returnValue = retrySendTransactionIDUnderlyingCallsCount - } - - return returnValue! - } - } - set { - if Thread.isMainThread { - retrySendTransactionIDUnderlyingCallsCount = newValue - } else { - DispatchQueue.main.sync { - retrySendTransactionIDUnderlyingCallsCount = newValue - } - } - } - } - var retrySendTransactionIDCalled: Bool { - return retrySendTransactionIDCallsCount > 0 - } - var retrySendTransactionIDReceivedTransactionID: String? - var retrySendTransactionIDReceivedInvocations: [String] = [] - var retrySendTransactionIDClosure: ((String) async -> Void)? - - func retrySend(transactionID: String) async { - retrySendTransactionIDCallsCount += 1 - retrySendTransactionIDReceivedTransactionID = transactionID - retrySendTransactionIDReceivedInvocations.append(transactionID) - await retrySendTransactionIDClosure?(transactionID) - } //MARK: - paginateBackwards var paginateBackwardsRequestSizeUnderlyingCallsCount = 0 @@ -11536,6 +11424,142 @@ class TimelineProxyMock: TimelineProxyProtocol { return paginateForwardsRequestSizeReturnValue } } + //MARK: - edit + + var editMessageHtmlIntentionalMentionsUnderlyingCallsCount = 0 + var editMessageHtmlIntentionalMentionsCallsCount: Int { + get { + if Thread.isMainThread { + return editMessageHtmlIntentionalMentionsUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = editMessageHtmlIntentionalMentionsUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + editMessageHtmlIntentionalMentionsUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + editMessageHtmlIntentionalMentionsUnderlyingCallsCount = newValue + } + } + } + } + var editMessageHtmlIntentionalMentionsCalled: Bool { + return editMessageHtmlIntentionalMentionsCallsCount > 0 + } + var editMessageHtmlIntentionalMentionsReceivedArguments: (timelineItemID: TimelineItemIdentifier, message: String, html: String?, intentionalMentions: IntentionalMentions)? + var editMessageHtmlIntentionalMentionsReceivedInvocations: [(timelineItemID: TimelineItemIdentifier, message: String, html: String?, intentionalMentions: IntentionalMentions)] = [] + + var editMessageHtmlIntentionalMentionsUnderlyingReturnValue: Result! + var editMessageHtmlIntentionalMentionsReturnValue: Result! { + get { + if Thread.isMainThread { + return editMessageHtmlIntentionalMentionsUnderlyingReturnValue + } else { + var returnValue: Result? = nil + DispatchQueue.main.sync { + returnValue = editMessageHtmlIntentionalMentionsUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + editMessageHtmlIntentionalMentionsUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + editMessageHtmlIntentionalMentionsUnderlyingReturnValue = newValue + } + } + } + } + var editMessageHtmlIntentionalMentionsClosure: ((TimelineItemIdentifier, String, String?, IntentionalMentions) 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) + editMessageHtmlIntentionalMentionsReceivedInvocations.append((timelineItemID: timelineItemID, message: message, html: html, intentionalMentions: intentionalMentions)) + if let editMessageHtmlIntentionalMentionsClosure = editMessageHtmlIntentionalMentionsClosure { + return await editMessageHtmlIntentionalMentionsClosure(timelineItemID, message, html, intentionalMentions) + } else { + return editMessageHtmlIntentionalMentionsReturnValue + } + } + //MARK: - redact + + var redactReasonUnderlyingCallsCount = 0 + var redactReasonCallsCount: Int { + get { + if Thread.isMainThread { + return redactReasonUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = redactReasonUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + redactReasonUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + redactReasonUnderlyingCallsCount = newValue + } + } + } + } + var redactReasonCalled: Bool { + return redactReasonCallsCount > 0 + } + var redactReasonReceivedArguments: (timelineItemID: TimelineItemIdentifier, reason: String?)? + var redactReasonReceivedInvocations: [(timelineItemID: TimelineItemIdentifier, reason: String?)] = [] + + var redactReasonUnderlyingReturnValue: Result! + var redactReasonReturnValue: Result! { + get { + if Thread.isMainThread { + return redactReasonUnderlyingReturnValue + } else { + var returnValue: Result? = nil + DispatchQueue.main.sync { + returnValue = redactReasonUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + redactReasonUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + redactReasonUnderlyingReturnValue = newValue + } + } + } + } + var redactReasonClosure: ((TimelineItemIdentifier, String?) async -> Result)? + + func redact(_ timelineItemID: TimelineItemIdentifier, reason: String?) async -> Result { + redactReasonCallsCount += 1 + redactReasonReceivedArguments = (timelineItemID: timelineItemID, reason: reason) + redactReasonReceivedInvocations.append((timelineItemID: timelineItemID, reason: reason)) + if let redactReasonClosure = redactReasonClosure { + return await redactReasonClosure(timelineItemID, reason) + } else { + return redactReasonReturnValue + } + } //MARK: - sendAudio var sendAudioUrlAudioInfoProgressSubjectRequestHandleUnderlyingCallsCount = 0 diff --git a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift index b2ed0f39c..ae546acad 100644 --- a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift @@ -4,6 +4,82 @@ // swiftlint:disable all import Foundation import MatrixRustSDK +open class AbortSendHandleSDKMock: MatrixRustSDK.AbortSendHandle { + init() { + super.init(noPointer: .init()) + } + + public required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + fatalError("init(unsafeFromRawPointer:) has not been implemented") + } + + fileprivate var pointer: UnsafeMutableRawPointer! + + //MARK: - abort + + var abortUnderlyingCallsCount = 0 + open var abortCallsCount: Int { + get { + if Thread.isMainThread { + return abortUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = abortUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + abortUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + abortUnderlyingCallsCount = newValue + } + } + } + } + open var abortCalled: Bool { + return abortCallsCount > 0 + } + + var abortUnderlyingReturnValue: Bool! + open var abortReturnValue: Bool! { + get { + if Thread.isMainThread { + return abortUnderlyingReturnValue + } else { + var returnValue: Bool? = nil + DispatchQueue.main.sync { + returnValue = abortUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + abortUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + abortUnderlyingReturnValue = newValue + } + } + } + } + open var abortClosure: (() async -> Bool)? + + open override func abort() async -> Bool { + abortCallsCount += 1 + if let abortClosure = abortClosure { + return await abortClosure() + } else { + return abortReturnValue + } + } +} open class AuthenticationServiceSDKMock: MatrixRustSDK.AuthenticationService { init() { super.init(noPointer: .init()) @@ -889,6 +965,46 @@ open class ClientSDKMock: MatrixRustSDK.Client { } } + //MARK: - enableSendingQueue + + var enableSendingQueueEnableUnderlyingCallsCount = 0 + open var enableSendingQueueEnableCallsCount: Int { + get { + if Thread.isMainThread { + return enableSendingQueueEnableUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = enableSendingQueueEnableUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + enableSendingQueueEnableUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + enableSendingQueueEnableUnderlyingCallsCount = newValue + } + } + } + } + open var enableSendingQueueEnableCalled: Bool { + return enableSendingQueueEnableCallsCount > 0 + } + open var enableSendingQueueEnableReceivedEnable: Bool? + open var enableSendingQueueEnableReceivedInvocations: [Bool] = [] + open var enableSendingQueueEnableClosure: ((Bool) -> Void)? + + open override func enableSendingQueue(enable: Bool) { + enableSendingQueueEnableCallsCount += 1 + enableSendingQueueEnableReceivedEnable = enable + enableSendingQueueEnableReceivedInvocations.append(enable) + enableSendingQueueEnableClosure?(enable) + } + //MARK: - encryption var encryptionUnderlyingCallsCount = 0 @@ -2593,15 +2709,15 @@ open class ClientSDKMock: MatrixRustSDK.Client { } } } - open var sessionClosure: (() async throws -> Session)? + open var sessionClosure: (() throws -> Session)? - open override func session() async throws -> Session { + open override func session() throws -> Session { if let error = sessionThrowableError { throw error } sessionCallsCount += 1 if let sessionClosure = sessionClosure { - return try await sessionClosure() + return try sessionClosure() } else { return sessionReturnValue } @@ -2877,6 +2993,75 @@ open class ClientSDKMock: MatrixRustSDK.Client { } } + //MARK: - subscribeToSendingQueueStatus + + var subscribeToSendingQueueStatusListenerUnderlyingCallsCount = 0 + open var subscribeToSendingQueueStatusListenerCallsCount: Int { + get { + if Thread.isMainThread { + return subscribeToSendingQueueStatusListenerUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = subscribeToSendingQueueStatusListenerUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + subscribeToSendingQueueStatusListenerUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + subscribeToSendingQueueStatusListenerUnderlyingCallsCount = newValue + } + } + } + } + open var subscribeToSendingQueueStatusListenerCalled: Bool { + return subscribeToSendingQueueStatusListenerCallsCount > 0 + } + open var subscribeToSendingQueueStatusListenerReceivedListener: SendingQueueStatusListener? + open var subscribeToSendingQueueStatusListenerReceivedInvocations: [SendingQueueStatusListener] = [] + + var subscribeToSendingQueueStatusListenerUnderlyingReturnValue: TaskHandle! + open var subscribeToSendingQueueStatusListenerReturnValue: TaskHandle! { + get { + if Thread.isMainThread { + return subscribeToSendingQueueStatusListenerUnderlyingReturnValue + } else { + var returnValue: TaskHandle? = nil + DispatchQueue.main.sync { + returnValue = subscribeToSendingQueueStatusListenerUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + subscribeToSendingQueueStatusListenerUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + subscribeToSendingQueueStatusListenerUnderlyingReturnValue = newValue + } + } + } + } + open var subscribeToSendingQueueStatusListenerClosure: ((SendingQueueStatusListener) -> TaskHandle)? + + open override func subscribeToSendingQueueStatus(listener: SendingQueueStatusListener) -> TaskHandle { + subscribeToSendingQueueStatusListenerCallsCount += 1 + subscribeToSendingQueueStatusListenerReceivedListener = listener + subscribeToSendingQueueStatusListenerReceivedInvocations.append(listener) + if let subscribeToSendingQueueStatusListenerClosure = subscribeToSendingQueueStatusListenerClosure { + return subscribeToSendingQueueStatusListenerClosure(listener) + } else { + return subscribeToSendingQueueStatusListenerReturnValue + } + } + //MARK: - syncService var syncServiceUnderlyingCallsCount = 0 @@ -7398,6 +7583,71 @@ open class MessageSDKMock: MatrixRustSDK.Message { } } + //MARK: - content + + var contentUnderlyingCallsCount = 0 + open var contentCallsCount: Int { + get { + if Thread.isMainThread { + return contentUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = contentUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + contentUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + contentUnderlyingCallsCount = newValue + } + } + } + } + open var contentCalled: Bool { + return contentCallsCount > 0 + } + + var contentUnderlyingReturnValue: RoomMessageEventContentWithoutRelation! + open var contentReturnValue: RoomMessageEventContentWithoutRelation! { + get { + if Thread.isMainThread { + return contentUnderlyingReturnValue + } else { + var returnValue: RoomMessageEventContentWithoutRelation? = nil + DispatchQueue.main.sync { + returnValue = contentUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + contentUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + contentUnderlyingReturnValue = newValue + } + } + } + } + open var contentClosure: (() -> RoomMessageEventContentWithoutRelation)? + + open override func content() -> RoomMessageEventContentWithoutRelation { + contentCallsCount += 1 + if let contentClosure = contentClosure { + return contentClosure() + } else { + return contentReturnValue + } + } + //MARK: - inReplyTo var inReplyToUnderlyingCallsCount = 0 @@ -13954,15 +14204,15 @@ open class RoomListItemSDKMock: MatrixRustSDK.RoomListItem { } } } - open var fullRoomClosure: (() async throws -> Room)? + open var fullRoomClosure: (() throws -> Room)? - open override func fullRoom() async throws -> Room { + open override func fullRoom() throws -> Room { if let error = fullRoomThrowableError { throw error } fullRoomCallsCount += 1 if let fullRoomClosure = fullRoomClosure { - return try await fullRoomClosure() + return try fullRoomClosure() } else { return fullRoomReturnValue } @@ -16307,46 +16557,6 @@ open class TimelineSDKMock: MatrixRustSDK.Timeline { } } - //MARK: - cancelSend - - var cancelSendTxnIdUnderlyingCallsCount = 0 - open var cancelSendTxnIdCallsCount: Int { - get { - if Thread.isMainThread { - return cancelSendTxnIdUnderlyingCallsCount - } else { - var returnValue: Int? = nil - DispatchQueue.main.sync { - returnValue = cancelSendTxnIdUnderlyingCallsCount - } - - return returnValue! - } - } - set { - if Thread.isMainThread { - cancelSendTxnIdUnderlyingCallsCount = newValue - } else { - DispatchQueue.main.sync { - cancelSendTxnIdUnderlyingCallsCount = newValue - } - } - } - } - open var cancelSendTxnIdCalled: Bool { - return cancelSendTxnIdCallsCount > 0 - } - open var cancelSendTxnIdReceivedTxnId: String? - open var cancelSendTxnIdReceivedInvocations: [String] = [] - open var cancelSendTxnIdClosure: ((String) -> Void)? - - open override func cancelSend(txnId: String) { - cancelSendTxnIdCallsCount += 1 - cancelSendTxnIdReceivedTxnId = txnId - cancelSendTxnIdReceivedInvocations.append(txnId) - cancelSendTxnIdClosure?(txnId) - } - //MARK: - createPoll open var createPollQuestionAnswersMaxSelectionsPollKindThrowableError: Error? @@ -16379,16 +16589,16 @@ open class TimelineSDKMock: MatrixRustSDK.Timeline { } open var createPollQuestionAnswersMaxSelectionsPollKindReceivedArguments: (question: String, answers: [String], maxSelections: UInt8, pollKind: PollKind)? open var createPollQuestionAnswersMaxSelectionsPollKindReceivedInvocations: [(question: String, answers: [String], maxSelections: UInt8, pollKind: PollKind)] = [] - open var createPollQuestionAnswersMaxSelectionsPollKindClosure: ((String, [String], UInt8, PollKind) throws -> Void)? + open var createPollQuestionAnswersMaxSelectionsPollKindClosure: ((String, [String], UInt8, PollKind) async throws -> Void)? - open override func createPoll(question: String, answers: [String], maxSelections: UInt8, pollKind: PollKind) throws { + open override func createPoll(question: String, answers: [String], maxSelections: UInt8, pollKind: PollKind) async throws { if let error = createPollQuestionAnswersMaxSelectionsPollKindThrowableError { throw error } createPollQuestionAnswersMaxSelectionsPollKindCallsCount += 1 createPollQuestionAnswersMaxSelectionsPollKindReceivedArguments = (question: question, answers: answers, maxSelections: maxSelections, pollKind: pollKind) createPollQuestionAnswersMaxSelectionsPollKindReceivedInvocations.append((question: question, answers: answers, maxSelections: maxSelections, pollKind: pollKind)) - try createPollQuestionAnswersMaxSelectionsPollKindClosure?(question, answers, maxSelections, pollKind) + try await createPollQuestionAnswersMaxSelectionsPollKindClosure?(question, answers, maxSelections, pollKind) } //MARK: - edit @@ -16749,18 +16959,18 @@ open class TimelineSDKMock: MatrixRustSDK.Timeline { } } - //MARK: - getTimelineEventContentByEventId + //MARK: - getEventTimelineItemByTransactionId - open var getTimelineEventContentByEventIdEventIdThrowableError: Error? - var getTimelineEventContentByEventIdEventIdUnderlyingCallsCount = 0 - open var getTimelineEventContentByEventIdEventIdCallsCount: Int { + open var getEventTimelineItemByTransactionIdTransactionIdThrowableError: Error? + var getEventTimelineItemByTransactionIdTransactionIdUnderlyingCallsCount = 0 + open var getEventTimelineItemByTransactionIdTransactionIdCallsCount: Int { get { if Thread.isMainThread { - return getTimelineEventContentByEventIdEventIdUnderlyingCallsCount + return getEventTimelineItemByTransactionIdTransactionIdUnderlyingCallsCount } else { var returnValue: Int? = nil DispatchQueue.main.sync { - returnValue = getTimelineEventContentByEventIdEventIdUnderlyingCallsCount + returnValue = getEventTimelineItemByTransactionIdTransactionIdUnderlyingCallsCount } return returnValue! @@ -16768,29 +16978,29 @@ open class TimelineSDKMock: MatrixRustSDK.Timeline { } set { if Thread.isMainThread { - getTimelineEventContentByEventIdEventIdUnderlyingCallsCount = newValue + getEventTimelineItemByTransactionIdTransactionIdUnderlyingCallsCount = newValue } else { DispatchQueue.main.sync { - getTimelineEventContentByEventIdEventIdUnderlyingCallsCount = newValue + getEventTimelineItemByTransactionIdTransactionIdUnderlyingCallsCount = newValue } } } } - open var getTimelineEventContentByEventIdEventIdCalled: Bool { - return getTimelineEventContentByEventIdEventIdCallsCount > 0 + open var getEventTimelineItemByTransactionIdTransactionIdCalled: Bool { + return getEventTimelineItemByTransactionIdTransactionIdCallsCount > 0 } - open var getTimelineEventContentByEventIdEventIdReceivedEventId: String? - open var getTimelineEventContentByEventIdEventIdReceivedInvocations: [String] = [] + open var getEventTimelineItemByTransactionIdTransactionIdReceivedTransactionId: String? + open var getEventTimelineItemByTransactionIdTransactionIdReceivedInvocations: [String] = [] - var getTimelineEventContentByEventIdEventIdUnderlyingReturnValue: RoomMessageEventContentWithoutRelation! - open var getTimelineEventContentByEventIdEventIdReturnValue: RoomMessageEventContentWithoutRelation! { + var getEventTimelineItemByTransactionIdTransactionIdUnderlyingReturnValue: EventTimelineItem! + open var getEventTimelineItemByTransactionIdTransactionIdReturnValue: EventTimelineItem! { get { if Thread.isMainThread { - return getTimelineEventContentByEventIdEventIdUnderlyingReturnValue + return getEventTimelineItemByTransactionIdTransactionIdUnderlyingReturnValue } else { - var returnValue: RoomMessageEventContentWithoutRelation? = nil + var returnValue: EventTimelineItem? = nil DispatchQueue.main.sync { - returnValue = getTimelineEventContentByEventIdEventIdUnderlyingReturnValue + returnValue = getEventTimelineItemByTransactionIdTransactionIdUnderlyingReturnValue } return returnValue! @@ -16798,27 +17008,27 @@ open class TimelineSDKMock: MatrixRustSDK.Timeline { } set { if Thread.isMainThread { - getTimelineEventContentByEventIdEventIdUnderlyingReturnValue = newValue + getEventTimelineItemByTransactionIdTransactionIdUnderlyingReturnValue = newValue } else { DispatchQueue.main.sync { - getTimelineEventContentByEventIdEventIdUnderlyingReturnValue = newValue + getEventTimelineItemByTransactionIdTransactionIdUnderlyingReturnValue = newValue } } } } - open var getTimelineEventContentByEventIdEventIdClosure: ((String) async throws -> RoomMessageEventContentWithoutRelation)? + open var getEventTimelineItemByTransactionIdTransactionIdClosure: ((String) async throws -> EventTimelineItem)? - open override func getTimelineEventContentByEventId(eventId: String) async throws -> RoomMessageEventContentWithoutRelation { - if let error = getTimelineEventContentByEventIdEventIdThrowableError { + open override func getEventTimelineItemByTransactionId(transactionId: String) async throws -> EventTimelineItem { + if let error = getEventTimelineItemByTransactionIdTransactionIdThrowableError { throw error } - getTimelineEventContentByEventIdEventIdCallsCount += 1 - getTimelineEventContentByEventIdEventIdReceivedEventId = eventId - getTimelineEventContentByEventIdEventIdReceivedInvocations.append(eventId) - if let getTimelineEventContentByEventIdEventIdClosure = getTimelineEventContentByEventIdEventIdClosure { - return try await getTimelineEventContentByEventIdEventIdClosure(eventId) + getEventTimelineItemByTransactionIdTransactionIdCallsCount += 1 + getEventTimelineItemByTransactionIdTransactionIdReceivedTransactionId = transactionId + getEventTimelineItemByTransactionIdTransactionIdReceivedInvocations.append(transactionId) + if let getEventTimelineItemByTransactionIdTransactionIdClosure = getEventTimelineItemByTransactionIdTransactionIdClosure { + return try await getEventTimelineItemByTransactionIdTransactionIdClosure(transactionId) } else { - return getTimelineEventContentByEventIdEventIdReturnValue + return getEventTimelineItemByTransactionIdTransactionIdReturnValue } } @@ -17004,6 +17214,79 @@ open class TimelineSDKMock: MatrixRustSDK.Timeline { } } + //MARK: - redactEvent + + open var redactEventItemReasonThrowableError: Error? + var redactEventItemReasonUnderlyingCallsCount = 0 + open var redactEventItemReasonCallsCount: Int { + get { + if Thread.isMainThread { + return redactEventItemReasonUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = redactEventItemReasonUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + redactEventItemReasonUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + redactEventItemReasonUnderlyingCallsCount = newValue + } + } + } + } + open var redactEventItemReasonCalled: Bool { + return redactEventItemReasonCallsCount > 0 + } + open var redactEventItemReasonReceivedArguments: (item: EventTimelineItem, reason: String?)? + open var redactEventItemReasonReceivedInvocations: [(item: EventTimelineItem, reason: String?)] = [] + + var redactEventItemReasonUnderlyingReturnValue: Bool! + open var redactEventItemReasonReturnValue: Bool! { + get { + if Thread.isMainThread { + return redactEventItemReasonUnderlyingReturnValue + } else { + var returnValue: Bool? = nil + DispatchQueue.main.sync { + returnValue = redactEventItemReasonUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + redactEventItemReasonUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + redactEventItemReasonUnderlyingReturnValue = newValue + } + } + } + } + open var redactEventItemReasonClosure: ((EventTimelineItem, String?) async throws -> Bool)? + + open override func redactEvent(item: EventTimelineItem, reason: String?) async throws -> Bool { + if let error = redactEventItemReasonThrowableError { + throw error + } + redactEventItemReasonCallsCount += 1 + redactEventItemReasonReceivedArguments = (item: item, reason: reason) + redactEventItemReasonReceivedInvocations.append((item: item, reason: reason)) + if let redactEventItemReasonClosure = redactEventItemReasonClosure { + return try await redactEventItemReasonClosure(item, reason) + } else { + return redactEventItemReasonReturnValue + } + } + //MARK: - retryDecryption var retryDecryptionSessionIdsUnderlyingCallsCount = 0 @@ -17044,48 +17327,9 @@ open class TimelineSDKMock: MatrixRustSDK.Timeline { retryDecryptionSessionIdsClosure?(sessionIds) } - //MARK: - retrySend - - var retrySendTxnIdUnderlyingCallsCount = 0 - open var retrySendTxnIdCallsCount: Int { - get { - if Thread.isMainThread { - return retrySendTxnIdUnderlyingCallsCount - } else { - var returnValue: Int? = nil - DispatchQueue.main.sync { - returnValue = retrySendTxnIdUnderlyingCallsCount - } - - return returnValue! - } - } - set { - if Thread.isMainThread { - retrySendTxnIdUnderlyingCallsCount = newValue - } else { - DispatchQueue.main.sync { - retrySendTxnIdUnderlyingCallsCount = newValue - } - } - } - } - open var retrySendTxnIdCalled: Bool { - return retrySendTxnIdCallsCount > 0 - } - open var retrySendTxnIdReceivedTxnId: String? - open var retrySendTxnIdReceivedInvocations: [String] = [] - open var retrySendTxnIdClosure: ((String) -> Void)? - - open override func retrySend(txnId: String) { - retrySendTxnIdCallsCount += 1 - retrySendTxnIdReceivedTxnId = txnId - retrySendTxnIdReceivedInvocations.append(txnId) - retrySendTxnIdClosure?(txnId) - } - //MARK: - send + open var sendMsgThrowableError: Error? var sendMsgUnderlyingCallsCount = 0 open var sendMsgCallsCount: Int { get { @@ -17115,13 +17359,45 @@ open class TimelineSDKMock: MatrixRustSDK.Timeline { } open var sendMsgReceivedMsg: RoomMessageEventContentWithoutRelation? open var sendMsgReceivedInvocations: [RoomMessageEventContentWithoutRelation] = [] - open var sendMsgClosure: ((RoomMessageEventContentWithoutRelation) -> Void)? - open override func send(msg: RoomMessageEventContentWithoutRelation) { + var sendMsgUnderlyingReturnValue: AbortSendHandle! + open var sendMsgReturnValue: AbortSendHandle! { + get { + if Thread.isMainThread { + return sendMsgUnderlyingReturnValue + } else { + var returnValue: AbortSendHandle? = nil + DispatchQueue.main.sync { + returnValue = sendMsgUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + sendMsgUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + sendMsgUnderlyingReturnValue = newValue + } + } + } + } + open var sendMsgClosure: ((RoomMessageEventContentWithoutRelation) async throws -> AbortSendHandle)? + + open override func send(msg: RoomMessageEventContentWithoutRelation) async throws -> AbortSendHandle { + if let error = sendMsgThrowableError { + throw error + } sendMsgCallsCount += 1 sendMsgReceivedMsg = msg sendMsgReceivedInvocations.append(msg) - sendMsgClosure?(msg) + if let sendMsgClosure = sendMsgClosure { + return try await sendMsgClosure(msg) + } else { + return sendMsgReturnValue + } } //MARK: - sendAudio @@ -17362,13 +17638,13 @@ open class TimelineSDKMock: MatrixRustSDK.Timeline { } open var sendLocationBodyGeoUriDescriptionZoomLevelAssetTypeReceivedArguments: (body: String, geoUri: String, description: String?, zoomLevel: UInt8?, assetType: AssetType?)? open var sendLocationBodyGeoUriDescriptionZoomLevelAssetTypeReceivedInvocations: [(body: String, geoUri: String, description: String?, zoomLevel: UInt8?, assetType: AssetType?)] = [] - open var sendLocationBodyGeoUriDescriptionZoomLevelAssetTypeClosure: ((String, String, String?, UInt8?, AssetType?) -> Void)? + open var sendLocationBodyGeoUriDescriptionZoomLevelAssetTypeClosure: ((String, String, String?, UInt8?, AssetType?) async -> Void)? - open override func sendLocation(body: String, geoUri: String, description: String?, zoomLevel: UInt8?, assetType: AssetType?) { + open override func sendLocation(body: String, geoUri: String, description: String?, zoomLevel: UInt8?, assetType: AssetType?) async { sendLocationBodyGeoUriDescriptionZoomLevelAssetTypeCallsCount += 1 sendLocationBodyGeoUriDescriptionZoomLevelAssetTypeReceivedArguments = (body: body, geoUri: geoUri, description: description, zoomLevel: zoomLevel, assetType: assetType) sendLocationBodyGeoUriDescriptionZoomLevelAssetTypeReceivedInvocations.append((body: body, geoUri: geoUri, description: description, zoomLevel: zoomLevel, assetType: assetType)) - sendLocationBodyGeoUriDescriptionZoomLevelAssetTypeClosure?(body, geoUri, description, zoomLevel, assetType) + await sendLocationBodyGeoUriDescriptionZoomLevelAssetTypeClosure?(body, geoUri, description, zoomLevel, assetType) } //MARK: - sendPollResponse @@ -17403,16 +17679,16 @@ open class TimelineSDKMock: MatrixRustSDK.Timeline { } open var sendPollResponsePollStartIdAnswersReceivedArguments: (pollStartId: String, answers: [String])? open var sendPollResponsePollStartIdAnswersReceivedInvocations: [(pollStartId: String, answers: [String])] = [] - open var sendPollResponsePollStartIdAnswersClosure: ((String, [String]) throws -> Void)? + open var sendPollResponsePollStartIdAnswersClosure: ((String, [String]) async throws -> Void)? - open override func sendPollResponse(pollStartId: String, answers: [String]) throws { + open override func sendPollResponse(pollStartId: String, answers: [String]) async throws { if let error = sendPollResponsePollStartIdAnswersThrowableError { throw error } sendPollResponsePollStartIdAnswersCallsCount += 1 sendPollResponsePollStartIdAnswersReceivedArguments = (pollStartId: pollStartId, answers: answers) sendPollResponsePollStartIdAnswersReceivedInvocations.append((pollStartId: pollStartId, answers: answers)) - try sendPollResponsePollStartIdAnswersClosure?(pollStartId, answers) + try await sendPollResponsePollStartIdAnswersClosure?(pollStartId, answers) } //MARK: - sendReadReceipt diff --git a/ElementX/Sources/Mocks/RoomProxyMock.swift b/ElementX/Sources/Mocks/RoomProxyMock.swift index 8bca4d7fc..5ebbc7f48 100644 --- a/ElementX/Sources/Mocks/RoomProxyMock.swift +++ b/ElementX/Sources/Mocks/RoomProxyMock.swift @@ -41,7 +41,6 @@ struct RoomProxyMockConfiguration { func makeTimeline() -> TimelineProxyMock { let timeline = TimelineProxyMock() - timeline.underlyingActions = Empty(completeImmediately: false).eraseToAnyPublisher() timeline.sendMessageEventContentReturnValue = .success(()) let timelineProvider = RoomTimelineProviderMock() diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenInteractionHandler.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenInteractionHandler.swift index c957c952c..7adf4ea13 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenInteractionHandler.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenInteractionHandler.swift @@ -157,7 +157,9 @@ class RoomScreenInteractionHandler { actions.append(.copy) } - actions.append(.copyPermalink) + if item.isRemoteMessage { + actions.append(.copyPermalink) + } if canRedactItem(item), let poll = item.pollIfAvailable, !poll.hasEnded, let eventID = itemID.eventID { actions.append(.endPoll(pollStartID: eventID)) @@ -224,11 +226,7 @@ class RoomScreenInteractionHandler { } case .redact: Task { - if eventTimelineItem.hasFailedToSend { - await timelineController.cancelSending(itemID: itemID) - } else { - await timelineController.redact(itemID) - } + await timelineController.redact(itemID) } case .reply: guard let eventID = eventTimelineItem.id.eventID else { diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift index 651f63ebe..adeacceb7 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift @@ -91,7 +91,6 @@ enum RoomScreenViewAction { case displayRoomMemberDetails(userID: String) case displayReactionSummary(itemID: TimelineItemIdentifier, key: String) case displayEmojiPicker(itemID: TimelineItemIdentifier) - case displayMessageSendingFailureAlert(itemID: TimelineItemIdentifier) case displayReadReceipts(itemID: TimelineItemIdentifier) case displayCall @@ -175,10 +174,6 @@ struct TimelineItemActionMenuInfo: Equatable, Identifiable { } } -struct MessageSendingFailureInfo: Hashable { - let itemID: TimelineItemIdentifier -} - struct ReactionSummaryInfo: Identifiable { let reactions: [AggregatedReaction] let selectedKey: String @@ -196,7 +191,6 @@ struct ReadReceiptSummaryInfo: Identifiable { enum RoomScreenAlertInfoType: Hashable { case audioRecodingPermissionError case pollEndConfirmation(String) - case messageSendingFailure(TimelineItemIdentifier) } struct RoomMemberState { diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index a1e23e15a..2c0f18afd 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -174,8 +174,6 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol roomScreenInteractionHandler.displayEmojiPicker(for: itemID) case .displayReactionSummary(let itemID, let key): displayReactionSummary(for: itemID, selectedKey: key) - case .displayMessageSendingFailureAlert(let itemID): - displayAlert(.messageSendingFailure(itemID)) case .displayReadReceipts(itemID: let itemID): displayReadReceipts(for: itemID) case .displayCall: @@ -387,14 +385,6 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol } .store(in: &cancellables) - roomProxy.timeline.actions - .filter { $0 == .sentMessage } - .receive(on: DispatchQueue.main) - .sink { [weak self] _ in - self?.scrollToBottom() - } - .store(in: &cancellables) - appSettings.$timelineStyle .weakAssign(to: \.state.timelineStyle, on: self) .store(in: &cancellables) @@ -558,10 +548,10 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol inReplyTo: itemId, intentionalMentions: intentionalMentions) case .edit(let originalItemId): - await timelineController.editMessage(message, - html: html, - original: originalItemId, - intentionalMentions: intentionalMentions) + await timelineController.edit(originalItemId, + message: message, + html: html, + intentionalMentions: intentionalMentions) case .default: await timelineController.sendMessage(message, html: html, @@ -569,6 +559,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol case .recordVoiceMessage, .previewVoiceMessage: fatalError("invalid composer mode.") } + + scrollToBottom() } private func trackComposerMode(_ mode: RoomScreenComposerMode) { @@ -770,15 +762,6 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol message: L10n.commonPollEndConfirmation, primaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil), secondaryButton: .init(title: L10n.actionOk, action: { self.roomScreenInteractionHandler.endPoll(pollStartID: pollStartID) })) - case .messageSendingFailure(let itemID): - state.bindings.alertInfo = .init(id: type, - title: L10n.screenRoomRetrySendMenuTitle, - primaryButton: .init(title: L10n.screenRoomRetrySendMenuSendAgainAction) { - Task { await self.timelineController.retrySending(itemID: itemID) } - }, - secondaryButton: .init(title: L10n.actionRemove, role: .destructive) { - Task { await self.timelineController.cancelSending(itemID: itemID) } - }) } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift index 61f54fed1..71e1f296b 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift @@ -110,9 +110,7 @@ struct TimelineItemBubbledStylerView: View { VStack(alignment: alignment, spacing: -3) { messageBubble .timelineItemAccessibility(timelineItem) { - if adjustedDeliveryStatus == .sendingFailed { - context.send(viewAction: .displayMessageSendingFailureAlert(itemID: timelineItem.id)) - } else { + if adjustedDeliveryStatus != .sendingFailed { context.send(viewAction: .displayTimelineItemMenu(itemID: timelineItem.id)) } } @@ -171,22 +169,10 @@ struct TimelineItemBubbledStylerView: View { timelineItem.bubbleSendInfoLayoutType .layout { contentWithReply - interactiveLocalizedSendInfo + layoutedLocalizedSendInfo } } - @ViewBuilder - var interactiveLocalizedSendInfo: some View { - if adjustedDeliveryStatus == .sendingFailed { - layoutedLocalizedSendInfo - .onTapGesture { - context.send(viewAction: .displayMessageSendingFailureAlert(itemID: timelineItem.id)) - } - } else { - layoutedLocalizedSendInfo - } - } - @ViewBuilder var layoutedLocalizedSendInfo: some View { switch timelineItem.bubbleSendInfoLayoutType { diff --git a/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineItemStatusView.swift b/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineItemStatusView.swift index c12dc9e97..59298188e 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineItemStatusView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineItemStatusView.swift @@ -55,9 +55,6 @@ struct TimelineItemStatusView: View { if style == .plain { CompoundIcon(\.error, size: .xSmall, relativeTo: .compound.bodyMD) .foregroundColor(.compound.iconCriticalPrimary) - .onTapGesture { - context.send(viewAction: .displayMessageSendingFailureAlert(itemID: timelineItem.id)) - } .accessibilityLabel(L10n.commonSendingFailed) .accessibilityHint(L10n.actionTapForOptions) } diff --git a/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift b/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift index 382971495..1386c60c3 100644 --- a/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift +++ b/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift @@ -105,7 +105,7 @@ class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol { initialDeviceName: initialDeviceName, deviceId: deviceID) - let refreshToken = try? await client.session().refreshToken + let refreshToken = try? client.session().refreshToken if refreshToken != nil { MXLog.warning("Refresh token found for a non oidc session, can't restore session, logging out") diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index 310c3a198..17d19f28f 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -45,6 +45,9 @@ class ClientProxy: ClientProxyProtocol { // periphery:ignore - required for instance retention in the rust codebase private var verificationStateListenerTaskHandle: TaskHandle? + // periphery:ignore - required for instance retention in the rust codebase + private var sendingQueueListenerTaskHandle: TaskHandle? + private var delegateHandle: TaskHandle? // These following summary providers both operate on the same allRooms() list but @@ -161,6 +164,17 @@ class ClientProxy: ClientProxyProtocol { verificationStateListenerTaskHandle = client.encryption().verificationStateListener(listener: VerificationStateListenerProxy { [weak self] verificationState in self?.updateVerificationState(verificationState) }) + + sendingQueueListenerTaskHandle = client.subscribeToSendingQueueStatus(listener: SendingQueueStatusListenerProxy { [weak self] enabled in + guard let self else { return } + + MXLog.error("Sending queue status changed to enabled: \(enabled)") + + if enabled == false, + networkMonitor.reachabilityPublisher.value == .reachable { + setSendingQueueEnabled(true) + } + }) } private func updateVerificationState(_ verificationState: VerificationState) { @@ -578,6 +592,11 @@ class ClientProxy: ClientProxyProtocol { } } + func setSendingQueueEnabled(_ enabled: Bool) { + MXLog.info("Setting sending queue to enabled: \(enabled)") + client.enableSendingQueue(enable: enabled) + } + // MARK: Ignored users func ignoreUser(_ userID: String) async -> Result { @@ -784,7 +803,7 @@ class ClientProxy: ClientProxyProtocol { if roomListItem?.isTimelineInitialized() == false { try await roomListItem?.initTimeline(eventTypeFilter: eventFilters, internalIdPrefix: nil) } - let fullRoom = try await roomListItem?.fullRoom() + let fullRoom = try roomListItem?.fullRoom() return (roomListItem, fullRoom) } catch { @@ -920,6 +939,18 @@ private class IgnoredUsersListenerProxy: IgnoredUsersListener { } } +private class SendingQueueStatusListenerProxy: SendingQueueStatusListener { + private let onUpdateClosure: (Bool) -> Void + + init(onUpdateClosure: @escaping (Bool) -> Void) { + self.onUpdateClosure = onUpdateClosure + } + + func onUpdate(newValue: Bool) { + onUpdateClosure(newValue) + } +} + private extension RoomPreviewDetails { init(_ roomPreview: RoomPreview) { self = RoomPreviewDetails(roomID: roomPreview.roomId, diff --git a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift index 0f051aa41..cf17af222 100644 --- a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift +++ b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift @@ -160,6 +160,8 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol { func roomDirectorySearchProxy() -> RoomDirectorySearchProxyProtocol func resolveRoomAlias(_ alias: String) async -> Result + + func setSendingQueueEnabled(_ enabled: Bool) // MARK: - Ignored users diff --git a/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineController.swift b/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineController.swift index 589945102..2eeaff01e 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineController.swift @@ -92,11 +92,11 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol { intentionalMentions: IntentionalMentions) async { } func toggleReaction(_ reaction: String, to itemID: TimelineItemIdentifier) async { } - - func editMessage(_ newMessage: String, - html: String?, - original itemID: TimelineItemIdentifier, - intentionalMentions: IntentionalMentions) async { } + + func edit(_ timelineItemID: TimelineItemIdentifier, + message: String, + html: String?, + intentionalMentions: IntentionalMentions) async { } func redact(_ itemID: TimelineItemIdentifier) async { } @@ -109,23 +109,7 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol { } func retryDecryption(for sessionID: String) async { } - - func retrySending(itemID: TimelineItemIdentifier) async { - guard let transactionID = itemID.transactionID else { - return - } - await roomProxy?.timeline.retrySend(transactionID: transactionID) - } - - func cancelSending(itemID: TimelineItemIdentifier) async { - guard let transactionID = itemID.transactionID else { - return - } - - await roomProxy?.timeline.cancelSend(transactionID: transactionID) - } - func eventTimestamp(for itemID: TimelineItemIdentifier) -> Date? { timelineItemsTimestamp[itemID] ?? .now } diff --git a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift index 4d532748a..9322f61ea 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift @@ -131,7 +131,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol { return } - _ = await roomProxy.timeline.sendReadReceipt(for: eventID, type: receiptType) + _ = await activeTimeline.sendReadReceipt(for: eventID, type: receiptType) } } @@ -162,10 +162,10 @@ class RoomTimelineController: RoomTimelineControllerProtocol { return } - switch await roomProxy.timeline.sendMessage(message, - html: html, - inReplyTo: inReplyTo, - intentionalMentions: intentionalMentions) { + switch await activeTimeline.sendMessage(message, + html: html, + inReplyTo: inReplyTo, + intentionalMentions: intentionalMentions) { case .success: MXLog.info("Finished sending message") case .failure(let error): @@ -188,38 +188,27 @@ class RoomTimelineController: RoomTimelineControllerProtocol { } } - func editMessage(_ newMessage: String, - html: String?, - original itemID: TimelineItemIdentifier, - intentionalMentions: IntentionalMentions) async { + func edit(_ timelineItemID: TimelineItemIdentifier, + message: String, + html: String?, + 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 cancelSending(itemID: itemID) - await sendMessage(newMessage, html: html, intentionalMentions: intentionalMentions) - } else if let eventID = itemID.eventID { - switch await activeTimeline.editMessage(newMessage, - html: html, - original: eventID, - intentionalMentions: intentionalMentions) { - case .success: - MXLog.info("Finished editing message") - case .failure(let error): - MXLog.error("Failed editing message with error: \(error)") - } - } else { - MXLog.error("Editing failed: missing identifiers") + + 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)") } } - func redact(_ itemID: TimelineItemIdentifier) async { + func redact(_ timelineItemID: TimelineItemIdentifier) async { MXLog.info("Send redaction in \(roomID)") - guard let eventID = itemID.eventID else { - return - } - switch await roomProxy.redact(eventID) { + + switch await activeTimeline.redact(timelineItemID, reason: nil) { case .success: MXLog.info("Finished redacting message") case .failure(let error): @@ -227,12 +216,8 @@ class RoomTimelineController: RoomTimelineControllerProtocol { } } - func messageEventContent(for itemID: TimelineItemIdentifier) async -> RoomMessageEventContentWithoutRelation? { - guard let eventID = itemID.eventID else { - MXLog.warning("The item doesn't have an event ID.") - return nil - } - return await activeTimeline.messageEventContent(for: eventID) + func messageEventContent(for timelineItemID: TimelineItemIdentifier) async -> RoomMessageEventContentWithoutRelation? { + await activeTimeline.messageEventContent(for: timelineItemID) } // Handle this parallel to the timeline items so we're not forced @@ -256,26 +241,6 @@ class RoomTimelineController: RoomTimelineControllerProtocol { await activeTimeline.retryDecryption(for: sessionID) } - func retrySending(itemID: TimelineItemIdentifier) async { - guard let transactionID = itemID.transactionID else { - MXLog.error("Failed Retry Send: missing transaction ID") - return - } - - MXLog.info("Retry sending in \(roomID)") - await roomProxy.timeline.retrySend(transactionID: transactionID) - } - - func cancelSending(itemID: TimelineItemIdentifier) async { - guard let transactionID = itemID.transactionID else { - MXLog.error("Failed Cancel Send: missing transaction ID") - return - } - - MXLog.info("Cancelling send in \(roomID)") - await roomProxy.timeline.cancelSend(transactionID: transactionID) - } - // MARK: - Private /// The cancellable used to update the timeline items. diff --git a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerProtocol.swift index c063a0ddf..9e2146596 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerProtocol.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineControllerProtocol.swift @@ -59,10 +59,10 @@ protocol RoomTimelineControllerProtocol { inReplyTo itemID: TimelineItemIdentifier?, intentionalMentions: IntentionalMentions) async - func editMessage(_ newMessage: String, - html: String?, - original itemID: TimelineItemIdentifier, - intentionalMentions: IntentionalMentions) async + func edit(_ timelineItemID: TimelineItemIdentifier, + message: String, + html: String?, + intentionalMentions: IntentionalMentions) async func toggleReaction(_ reaction: String, to itemID: TimelineItemIdentifier) async @@ -74,10 +74,6 @@ protocol RoomTimelineControllerProtocol { func retryDecryption(for sessionID: String) async - func retrySending(itemID: TimelineItemIdentifier) async - - func cancelSending(itemID: TimelineItemIdentifier) async - func eventTimestamp(for itemID: TimelineItemIdentifier) -> Date? } diff --git a/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift b/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift index 371030fdf..772dcf78b 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift @@ -78,11 +78,8 @@ class EventTimelineItemProxy { } switch localSendState { - case .notSentYet: + case .notSentYet, .sendingFailed: return .sending - // cancelled is exactly like sendingFailed but does not contain an error - case .sendingFailed, .cancelled: - return .sendingFailed case .sent: return .sent } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift index dcd238a57..ba605cecf 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift @@ -41,9 +41,9 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { return buildEncryptedTimelineItem(eventItemProxy, encryptedMessage, isOutgoing) case .redactedMessage: return buildRedactedTimelineItem(eventItemProxy, isOutgoing) - case .sticker(let body, let imageInfo, let urlString): - guard let url = URL(string: urlString) else { - MXLog.error("Invalid sticker url string: \(urlString)") + case .sticker(let body, let imageInfo, let mediaSource): + guard let url = URL(string: mediaSource.url()) else { + MXLog.error("Invalid sticker url string: \(mediaSource.url())") return buildUnsupportedTimelineItem(eventItemProxy, "m.sticker", "Invalid Sticker URL", isOutgoing) } diff --git a/ElementX/Sources/Services/Timeline/TimelineProxy.swift b/ElementX/Sources/Services/Timeline/TimelineProxy.swift index c8231eea7..36d9c1ec4 100644 --- a/ElementX/Sources/Services/Timeline/TimelineProxy.swift +++ b/ElementX/Sources/Services/Timeline/TimelineProxy.swift @@ -33,11 +33,6 @@ final class TimelineProxy: TimelineProxyProtocol { private let forwardPaginationStatusSubject = CurrentValueSubject(.timelineEndReached) private let timelineUpdatesSubject = PassthroughSubject<[TimelineDiff], Never>() - private let actionsSubject = PassthroughSubject() - var actions: AnyPublisher { - actionsSubject.eraseToAnyPublisher() - } - let isLive: Bool private var innerTimelineProvider: RoomTimelineProviderProtocol! @@ -83,38 +78,6 @@ final class TimelineProxy: TimelineProxyProtocol { paginationStatePublisher: paginationStatePublisher) } - func cancelSend(transactionID: String) async { - MXLog.info("Cancelling sending for transaction ID: \(transactionID)") - - return await Task.dispatch(on: messageSendingDispatchQueue) { - self.timeline.cancelSend(txnId: transactionID) - MXLog.info("Finished cancelling sending for transaction ID: \(transactionID)") - } - } - - func editMessage(_ message: String, - html: String?, - original eventID: String, - intentionalMentions: IntentionalMentions) async -> Result { - MXLog.info("Editing message with original event ID: \(eventID)") - - let messageContent = buildMessageContentFor(message, - html: html, - intentionalMentions: intentionalMentions.toRustMentions()) - - do { - let originalEvent = try await timeline.getEventTimelineItemByEventId(eventId: eventID) - try await timeline.edit(newContent: messageContent, editItem: originalEvent) - - MXLog.info("Finished editing message with original event ID: \(eventID)") - - return .success(()) - } catch { - MXLog.error("Failed editing message with original event ID: \(eventID) with error: \(error)") - return .failure(.failedEditingMessage) - } - } - func fetchDetails(for eventID: String) { Task { do { @@ -127,17 +90,8 @@ final class TimelineProxy: TimelineProxyProtocol { } } - func messageEventContent(for eventID: String) async -> RoomMessageEventContentWithoutRelation? { - MXLog.info("Fetching event content for \(eventID)") - - do { - let result = try await timeline.getTimelineEventContentByEventId(eventId: eventID) - MXLog.info("Finished fetching event content for eventID: \(eventID)") - return result - } catch { - MXLog.error("Failed fetching event content for eventID: \(eventID) with error: \(error)") - return nil - } + func messageEventContent(for timelineItemID: TimelineItemIdentifier) async -> RoomMessageEventContentWithoutRelation? { + await timelineProvider.itemProxies.firstEventTimelineItemUsingID(timelineItemID)?.content().asMessage()?.content() } func paginateBackwards(requestSize: UInt16) async -> Result { @@ -150,7 +104,7 @@ final class TimelineProxy: TimelineProxyProtocol { return .success(()) } catch { MXLog.error("Failed paginating backwards with error: \(error)") - return .failure(.failedPaginatingBackwards) + return .failure(.sdkError(error)) } } @@ -159,7 +113,7 @@ final class TimelineProxy: TimelineProxyProtocol { // We need it to make sure we send a valid status after a failure. guard forwardPaginationStatusSubject.value == .idle else { MXLog.error("Attempting to paginate forwards when already at the end.") - return .failure(.failedPaginatingBackwards) + return .failure(.failedPaginatingEndReached) } MXLog.info("Paginating forwards") @@ -174,7 +128,7 @@ final class TimelineProxy: TimelineProxyProtocol { } catch { MXLog.error("Failed paginating forwards with error: \(error)") forwardPaginationStatusSubject.send(.idle) - return .failure(.failedPaginatingBackwards) + return .failure(.sdkError(error)) } } @@ -187,15 +141,59 @@ final class TimelineProxy: TimelineProxyProtocol { } } - func retrySend(transactionID: String) async { - MXLog.info("Retrying sending for transactionID: \(transactionID)") + func edit(_ timelineItemID: TimelineItemIdentifier, + message: String, html: String?, + intentionalMentions: IntentionalMentions) async -> Result { + MXLog.info("Editing timeline item: \(timelineItemID)") - return await Task.dispatch(on: messageSendingDispatchQueue) { - self.timeline.retrySend(txnId: transactionID) - MXLog.info("Finished retrying sending for transactionID: \(transactionID)") + guard let eventTimelineItem = await timelineProvider.itemProxies.firstEventTimelineItemUsingID(timelineItemID) else { + MXLog.error("Unknown timeline item: \(timelineItemID)") + return .failure(.failedEditing) + } + + let messageContent = buildMessageContentFor(message, + html: html, + intentionalMentions: intentionalMentions.toRustMentions()) + + do { + try await timeline.edit(newContent: messageContent, editItem: eventTimelineItem) + + MXLog.info("Finished editing timeline item: \(timelineItemID)") + + return .success(()) + } catch { + MXLog.error("Failed editing timeline item: \(timelineItemID) with error: \(error)") + return .failure(.sdkError(error)) } } + func redact(_ timelineItemID: TimelineItemIdentifier, reason: String?) async -> Result { + MXLog.info("Redacting timeline item: \(timelineItemID)") + + guard let eventTimelineItem = await timelineProvider.itemProxies.firstEventTimelineItemUsingID(timelineItemID) else { + MXLog.error("Unknown timeline item: \(timelineItemID)") + return .failure(.failedRedacting) + } + + do { + let success = try await timeline.redactEvent(item: eventTimelineItem, reason: reason) + + guard success else { + MXLog.error("Failed redacting timeline item: \(timelineItemID)") + return .failure(.failedRedacting) + } + + MXLog.info("Redacted timeline item: \(timelineItemID)") + + return .success(()) + } catch { + MXLog.error("Failed redacting timeline item: \(timelineItemID) with error: \(error)") + return .failure(.sdkError(error)) + } + } + + // MARK: - Sending + func sendAudio(url: URL, audioInfo: AudioInfo, progressSubject: CurrentValueSubject?, @@ -213,11 +211,9 @@ final class TimelineProxy: TimelineProxyProtocol { MXLog.info("Finished sending audio") } catch { MXLog.error("Failed sending audio with error: \(error)") - return .failure(.failedSendingMedia) + return .failure(.sdkError(error)) } - actionsSubject.send(.sentMessage) - return .success(()) } @@ -238,11 +234,9 @@ final class TimelineProxy: TimelineProxyProtocol { MXLog.info("Finished sending file") } catch { MXLog.error("Failed sending file with error: \(error)") - return .failure(.failedSendingMedia) + return .failure(.sdkError(error)) } - actionsSubject.send(.sentMessage) - return .success(()) } @@ -264,11 +258,9 @@ final class TimelineProxy: TimelineProxyProtocol { MXLog.info("Finished sending image") } catch { MXLog.error("Failed sending image with error: \(error)") - return .failure(.failedSendingMedia) + return .failure(.sdkError(error)) } - actionsSubject.send(.sentMessage) - return .success(()) } @@ -279,19 +271,15 @@ final class TimelineProxy: TimelineProxyProtocol { assetType: AssetType?) async -> Result { MXLog.info("Sending location") - return await Task.dispatch(on: messageSendingDispatchQueue) { - self.timeline.sendLocation(body: body, - geoUri: geoURI.string, - description: description, - zoomLevel: zoomLevel, - assetType: assetType) - - self.actionsSubject.send(.sentMessage) - - MXLog.info("Finished sending location") - - return .success(()) - } + await timeline.sendLocation(body: body, + geoUri: geoURI.string, + description: description, + zoomLevel: zoomLevel, + assetType: assetType) + + MXLog.info("Finished sending location") + + return .success(()) } func sendVideo(url: URL, @@ -312,11 +300,9 @@ final class TimelineProxy: TimelineProxyProtocol { MXLog.info("Finished sending video") } catch { MXLog.error("Failed sending video with error: \(error)") - return .failure(.failedSendingMedia) + return .failure(.sdkError(error)) } - actionsSubject.send(.sentMessage) - return .success(()) } @@ -338,11 +324,9 @@ final class TimelineProxy: TimelineProxyProtocol { MXLog.info("Finished sending voice message") } catch { MXLog.error("Failed sending vocie message with error: \(error)") - return .failure(.failedSendingMedia) + return .failure(.sdkError(error)) } - actionsSubject.send(.sentMessage) - return .success(()) } @@ -366,36 +350,34 @@ final class TimelineProxy: TimelineProxyProtocol { try await timeline.sendReply(msg: messageContent, replyItem: replyItem) MXLog.info("Finished sending reply to eventID: \(eventID)") } else { - timeline.send(msg: messageContent) + _ = try await timeline.send(msg: messageContent) MXLog.info("Finished sending message") } } catch { if let eventID { - MXLog.error("Failed sending reply to eventID: \(eventID)") + MXLog.error("Failed sending reply to eventID: \(eventID) with error: \(error)") } else { - MXLog.error("Failed sending message") + MXLog.error("Failed sending message with error: \(error)") } - return .failure(.failedSendingMessage) + return .failure(.sdkError(error)) } - actionsSubject.send(.sentMessage) - return .success(()) } func sendMessageEventContent(_ messageContent: RoomMessageEventContentWithoutRelation) async -> Result { MXLog.info("Sending message content") - - return await Task.dispatch(on: messageSendingDispatchQueue) { - self.timeline.send(msg: messageContent) - - self.actionsSubject.send(.sentMessage) - - MXLog.info("Finished sending message content") - - return .success(()) + + do { + _ = try await timeline.send(msg: messageContent) + } catch { + MXLog.error("Failed sending message with error: \(error)") } + + MXLog.info("Finished sending message content") + + return .success(()) } func sendReadReceipt(for eventID: String, type: ReceiptType) async -> Result { @@ -407,7 +389,7 @@ final class TimelineProxy: TimelineProxyProtocol { return .success(()) } catch { MXLog.error("Failed sending read receipt for eventID: \(eventID) with error: \(error)") - return .failure(.failedSendingReadReceipt) + return .failure(.sdkError(error)) } } @@ -420,7 +402,7 @@ final class TimelineProxy: TimelineProxyProtocol { return .success(()) } catch { MXLog.error("Failed toggling reaction for eventID: \(eventID)") - return .failure(.failedSendingReaction) + return .failure(.sdkError(error)) } } @@ -429,19 +411,15 @@ final class TimelineProxy: TimelineProxyProtocol { func createPoll(question: String, answers: [String], pollKind: Poll.Kind) async -> Result { MXLog.info("Creating poll") - return await Task.dispatch(on: .global()) { - do { - try self.timeline.createPoll(question: question, answers: answers, maxSelections: 1, pollKind: .init(pollKind: pollKind)) - - self.actionsSubject.send(.sentMessage) - - MXLog.info("Finished creating poll") - - return .success(()) - } catch { - MXLog.error("Failed creating poll with error: \(error)") - return .failure(.failedCreatingPoll) - } + do { + try await timeline.createPoll(question: question, answers: answers, maxSelections: 1, pollKind: .init(pollKind: pollKind)) + + MXLog.info("Finished creating poll") + + return .success(()) + } catch { + MXLog.error("Failed creating poll with error: \(error)") + return .failure(.sdkError(error)) } } @@ -461,7 +439,7 @@ final class TimelineProxy: TimelineProxyProtocol { return .success(()) } catch { MXLog.error("Failed editing poll with eventID: \(eventID) with error: \(error)") - return .failure(.failedEditingPoll) + return .failure(.sdkError(error)) } } @@ -477,7 +455,7 @@ final class TimelineProxy: TimelineProxyProtocol { return .success(()) } catch { MXLog.error("Failed ending poll with eventID: \(pollStartID) with error: \(error)") - return .failure(.failedEndingPoll) + return .failure(.sdkError(error)) } } } @@ -485,17 +463,15 @@ final class TimelineProxy: TimelineProxyProtocol { func sendPollResponse(pollStartID: String, answers: [String]) async -> Result { MXLog.info("Sending response for poll with eventID: \(pollStartID)") - return await Task.dispatch(on: .global()) { - do { - try self.timeline.sendPollResponse(pollStartId: pollStartID, answers: answers) - - MXLog.info("Finished sending response for poll with eventID: \(pollStartID)") - - return .success(()) - } catch { - MXLog.error("Failed sending response for poll with eventID: \(pollStartID) with error: \(error)") - return .failure(.failedSendingPollResponse) - } + do { + try await timeline.sendPollResponse(pollStartId: pollStartID, answers: answers) + + MXLog.info("Finished sending response for poll with eventID: \(pollStartID)") + + return .success(()) + } catch { + MXLog.error("Failed sending response for poll with eventID: \(pollStartID) with error: \(error)") + return .failure(.sdkError(error)) } } @@ -607,3 +583,20 @@ private extension MatrixRustSDK.PollKind { } } } + +extension Array where Element == TimelineItemProxy { + func firstEventTimelineItemUsingID(_ id: TimelineItemIdentifier) -> EventTimelineItem? { + var eventTimelineItemProxy: EventTimelineItemProxy? + + for item in self { + if case let .event(eventTimelineItem) = item { + if eventTimelineItem.id == id { + eventTimelineItemProxy = eventTimelineItem + break + } + } + } + + return eventTimelineItemProxy?.item + } +} diff --git a/ElementX/Sources/Services/Timeline/TimelineProxyProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineProxyProtocol.swift index c58dbeef3..dc3ab9ab5 100644 --- a/ElementX/Sources/Services/Timeline/TimelineProxyProtocol.swift +++ b/ElementX/Sources/Services/Timeline/TimelineProxyProtocol.swift @@ -18,55 +18,41 @@ import Combine import Foundation import MatrixRustSDK -enum TimelineProxyError: Error, Equatable { - case failedEditingMessage - case failedPaginatingBackwards - case failedSendingMessage - case failedSendingReaction - case failedSendingReadReceipt - case failedSendingMedia +enum TimelineProxyError: Error { + case sdkError(Error) - // Polls - case failedCreatingPoll - case failedEditingPoll - case failedEndingPoll - case failedSendingPollResponse -} - -enum TimelineProxyAction { - case sentMessage + case failedEditing + case failedRedacting + case failedPaginatingEndReached } // sourcery: AutoMockable protocol TimelineProxyProtocol { - var actions: AnyPublisher { get } - var isLive: Bool { get } var timelineProvider: RoomTimelineProviderProtocol { get } func subscribeForUpdates() async - /// Cancels a failed message given its transaction ID from the timeline - func cancelSend(transactionID: String) async - - func editMessage(_ message: String, - html: String?, - original eventID: String, - intentionalMentions: IntentionalMentions) async -> Result - func fetchDetails(for eventID: String) - func messageEventContent(for eventID: String) async -> RoomMessageEventContentWithoutRelation? + func messageEventContent(for timelineItemID: TimelineItemIdentifier) async -> RoomMessageEventContentWithoutRelation? func retryDecryption(for sessionID: String) async - /// Retries sending a failed message given its transaction ID - func retrySend(transactionID: String) async - 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 redact(_ timelineItemID: TimelineItemIdentifier, + reason: String?) async -> Result + + // MARK: - Sending + func sendAudio(url: URL, audioInfo: AudioInfo, progressSubject: CurrentValueSubject?, diff --git a/ElementX/Sources/Services/UserSession/UserSessionStore.swift b/ElementX/Sources/Services/UserSession/UserSessionStore.swift index 09be87005..8e4151231 100644 --- a/ElementX/Sources/Services/UserSession/UserSessionStore.swift +++ b/ElementX/Sources/Services/UserSession/UserSessionStore.swift @@ -68,7 +68,7 @@ class UserSessionStore: UserSessionStoreProtocol { func userSession(for client: Client, passphrase: String?) async -> Result { do { - let session = try await client.session() + let session = try client.session() let userID = try client.userId() let clientProxy = await setupProxyForClient(client) diff --git a/Package.resolved b/Package.resolved index bb1cd99a0..3a5ae8a17 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-argument-parser", "state" : { - "revision" : "46989693916f56d1186bd59ac15124caef896560", - "version" : "1.3.1" + "revision" : "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b", + "version" : "1.4.0" } }, { diff --git a/UnitTests/Sources/RoomPollsHistoryScreenViewModelTests.swift b/UnitTests/Sources/RoomPollsHistoryScreenViewModelTests.swift index 1647fd2ad..01cec1c4a 100644 --- a/UnitTests/Sources/RoomPollsHistoryScreenViewModelTests.swift +++ b/UnitTests/Sources/RoomPollsHistoryScreenViewModelTests.swift @@ -128,7 +128,7 @@ class RoomPollsHistoryScreenViewModelTests: XCTestCase { value.bindings.alertInfo != nil } - interactionHandler.endPollPollStartIDReturnValue = .failure(TimelineProxyError.failedEndingPoll) + interactionHandler.endPollPollStartIDReturnValue = .failure(SDKError.generic) viewModel.context.send(viewAction: .end(pollStartID: "somePollID")) try await deferred.fulfill() @@ -155,7 +155,7 @@ class RoomPollsHistoryScreenViewModelTests: XCTestCase { value.bindings.alertInfo != nil } - interactionHandler.sendPollResponsePollStartIDOptionIDReturnValue = .failure(TimelineProxyError.failedSendingPollResponse) + interactionHandler.sendPollResponsePollStartIDOptionIDReturnValue = .failure(SDKError.generic) viewModel.context.send(viewAction: .sendPollResponse(pollStartID: "somePollID", optionID: "someOptionID")) try await deferred.fulfill() @@ -180,3 +180,7 @@ class RoomPollsHistoryScreenViewModelTests: XCTestCase { try await deferred.fulfill() } } + +private enum SDKError: Error { + case generic +} diff --git a/UnitTests/Sources/RoomScreenViewModelTests.swift b/UnitTests/Sources/RoomScreenViewModelTests.swift index 5e7605116..e554bc8f1 100644 --- a/UnitTests/Sources/RoomScreenViewModelTests.swift +++ b/UnitTests/Sources/RoomScreenViewModelTests.swift @@ -336,7 +336,6 @@ class RoomScreenViewModelTests: XCTestCase { let roomProxy = RoomProxyMock(.init(name: "")) let timelineProxy = TimelineProxyMock() - timelineProxy.underlyingActions = Empty(completeImmediately: false).eraseToAnyPublisher() roomProxy.timeline = timelineProxy let timelineController = MockRoomTimelineController() diff --git a/UnitTests/Sources/VoiceMessageRecorderTests.swift b/UnitTests/Sources/VoiceMessageRecorderTests.swift index 256abfc79..e9408bc18 100644 --- a/UnitTests/Sources/VoiceMessageRecorderTests.swift +++ b/UnitTests/Sources/VoiceMessageRecorderTests.swift @@ -239,7 +239,7 @@ class VoiceMessageRecorderTests: XCTestCase { let timelineProxy = TimelineProxyMock() let roomProxy = RoomProxyMock() roomProxy.timeline = timelineProxy - timelineProxy.sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleReturnValue = .failure(.failedSendingMedia) + timelineProxy.sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleReturnValue = .failure(.sdkError(SDKError.generic)) guard case .failure(.failedSendingVoiceMessage) = await voiceMessageRecorder.sendVoiceMessage(inRoom: roomProxy, audioConverter: audioConverter) else { XCTFail("An error is expected") return @@ -260,7 +260,7 @@ class VoiceMessageRecorderTests: XCTestCase { let timelineProxy = TimelineProxyMock() let roomProxy = RoomProxyMock() roomProxy.timeline = timelineProxy - timelineProxy.sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleReturnValue = .failure(.failedSendingMedia) + timelineProxy.sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleReturnValue = .failure(.sdkError(SDKError.generic)) guard case .failure(.failedSendingVoiceMessage) = await voiceMessageRecorder.sendVoiceMessage(inRoom: roomProxy, audioConverter: audioConverter) else { XCTFail("An error is expected") return @@ -283,7 +283,7 @@ class VoiceMessageRecorderTests: XCTestCase { let timelineProxy = TimelineProxyMock() let roomProxy = RoomProxyMock() roomProxy.timeline = timelineProxy - timelineProxy.sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleReturnValue = .failure(.failedSendingMedia) + timelineProxy.sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleReturnValue = .failure(.sdkError(SDKError.generic)) guard case .failure(.failedSendingVoiceMessage) = await voiceMessageRecorder.sendVoiceMessage(inRoom: roomProxy, audioConverter: audioConverter) else { XCTFail("An error is expected") return @@ -389,3 +389,7 @@ class VoiceMessageRecorderTests: XCTestCase { try await deferred.fulfill() } } + +private enum SDKError: Error { + case generic +} diff --git a/changelog.d/2842.feature b/changelog.d/2842.feature new file mode 100644 index 000000000..dd914b0cc --- /dev/null +++ b/changelog.d/2842.feature @@ -0,0 +1 @@ +Adopt new automatically retrying message sending queue \ No newline at end of file diff --git a/project.yml b/project.yml index 00241558f..fe476f75d 100644 --- a/project.yml +++ b/project.yml @@ -49,7 +49,7 @@ packages: # Element/Matrix dependencies MatrixRustSDK: url: https://github.com/element-hq/matrix-rust-components-swift - exactVersion: 1.0.8 + exactVersion: 1.0.9 # path: ../matrix-rust-sdk Compound: url: https://github.com/element-hq/compound-ios