diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 3b04b00aa..be25dca9b 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -7364,7 +7364,7 @@ repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift"; requirement = { kind = exactVersion; - version = 1.0.11; + version = 1.0.13; }; }; 701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = { diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 6a783958c..b3d9db86a 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -148,8 +148,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/element-hq/matrix-rust-components-swift", "state" : { - "revision" : "e1aa005300f7c5d38ac8203a1668df99fff21bd4", - "version" : "1.0.11" + "revision" : "94373ae0568cf1681c39b1a348601e733d76a7ec", + "version" : "1.0.13" } }, { diff --git a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift index 858974785..307ac64cd 100644 --- a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift @@ -10248,6 +10248,46 @@ open class RoomSDKMock: MatrixRustSDK.Room { } } + //MARK: - clearComposerDraft + + open var clearComposerDraftThrowableError: Error? + var clearComposerDraftUnderlyingCallsCount = 0 + open var clearComposerDraftCallsCount: Int { + get { + if Thread.isMainThread { + return clearComposerDraftUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = clearComposerDraftUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + clearComposerDraftUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + clearComposerDraftUnderlyingCallsCount = newValue + } + } + } + } + open var clearComposerDraftCalled: Bool { + return clearComposerDraftCallsCount > 0 + } + open var clearComposerDraftClosure: (() async throws -> Void)? + + open override func clearComposerDraft() async throws { + if let error = clearComposerDraftThrowableError { + throw error + } + clearComposerDraftCallsCount += 1 + try await clearComposerDraftClosure?() + } + //MARK: - discardRoomKey open var discardRoomKeyThrowableError: Error? @@ -11393,6 +11433,75 @@ open class RoomSDKMock: MatrixRustSDK.Room { try await leaveClosure?() } + //MARK: - loadComposerDraft + + open var loadComposerDraftThrowableError: Error? + var loadComposerDraftUnderlyingCallsCount = 0 + open var loadComposerDraftCallsCount: Int { + get { + if Thread.isMainThread { + return loadComposerDraftUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = loadComposerDraftUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + loadComposerDraftUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + loadComposerDraftUnderlyingCallsCount = newValue + } + } + } + } + open var loadComposerDraftCalled: Bool { + return loadComposerDraftCallsCount > 0 + } + + var loadComposerDraftUnderlyingReturnValue: ComposerDraft? + open var loadComposerDraftReturnValue: ComposerDraft? { + get { + if Thread.isMainThread { + return loadComposerDraftUnderlyingReturnValue + } else { + var returnValue: ComposerDraft?? = nil + DispatchQueue.main.sync { + returnValue = loadComposerDraftUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + loadComposerDraftUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + loadComposerDraftUnderlyingReturnValue = newValue + } + } + } + } + open var loadComposerDraftClosure: (() async throws -> ComposerDraft?)? + + open override func loadComposerDraft() async throws -> ComposerDraft? { + if let error = loadComposerDraftThrowableError { + throw error + } + loadComposerDraftCallsCount += 1 + if let loadComposerDraftClosure = loadComposerDraftClosure { + return try await loadComposerDraftClosure() + } else { + return loadComposerDraftReturnValue + } + } + //MARK: - markAsRead open var markAsReadReceiptTypeThrowableError: Error? @@ -12397,6 +12506,50 @@ open class RoomSDKMock: MatrixRustSDK.Room { } } + //MARK: - saveComposerDraft + + open var saveComposerDraftDraftThrowableError: Error? + var saveComposerDraftDraftUnderlyingCallsCount = 0 + open var saveComposerDraftDraftCallsCount: Int { + get { + if Thread.isMainThread { + return saveComposerDraftDraftUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = saveComposerDraftDraftUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + saveComposerDraftDraftUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + saveComposerDraftDraftUnderlyingCallsCount = newValue + } + } + } + } + open var saveComposerDraftDraftCalled: Bool { + return saveComposerDraftDraftCallsCount > 0 + } + open var saveComposerDraftDraftReceivedDraft: ComposerDraft? + open var saveComposerDraftDraftReceivedInvocations: [ComposerDraft] = [] + open var saveComposerDraftDraftClosure: ((ComposerDraft) async throws -> Void)? + + open override func saveComposerDraft(draft: ComposerDraft) async throws { + if let error = saveComposerDraftDraftThrowableError { + throw error + } + saveComposerDraftDraftCallsCount += 1 + saveComposerDraftDraftReceivedDraft = draft + saveComposerDraftDraftReceivedInvocations.append(draft) + try await saveComposerDraftDraftClosure?(draft) + } + //MARK: - sendCallNotification open var sendCallNotificationCallIdApplicationNotifyTypeMentionsThrowableError: Error? @@ -16621,13 +16774,13 @@ open class TimelineSDKMock: MatrixRustSDK.Timeline { open var addListenerListenerReceivedListener: TimelineListener? open var addListenerListenerReceivedInvocations: [TimelineListener] = [] - var addListenerListenerUnderlyingReturnValue: RoomTimelineListenerResult! - open var addListenerListenerReturnValue: RoomTimelineListenerResult! { + var addListenerListenerUnderlyingReturnValue: TaskHandle! + open var addListenerListenerReturnValue: TaskHandle! { get { if Thread.isMainThread { return addListenerListenerUnderlyingReturnValue } else { - var returnValue: RoomTimelineListenerResult? = nil + var returnValue: TaskHandle? = nil DispatchQueue.main.sync { returnValue = addListenerListenerUnderlyingReturnValue } @@ -16645,9 +16798,9 @@ open class TimelineSDKMock: MatrixRustSDK.Timeline { } } } - open var addListenerListenerClosure: ((TimelineListener) async -> RoomTimelineListenerResult)? + open var addListenerListenerClosure: ((TimelineListener) async -> TaskHandle)? - open override func addListener(listener: TimelineListener) async -> RoomTimelineListenerResult { + open override func addListener(listener: TimelineListener) async -> TaskHandle { addListenerListenerCallsCount += 1 addListenerListenerReceivedListener = listener addListenerListenerReceivedInvocations.append(listener) @@ -17198,6 +17351,79 @@ open class TimelineSDKMock: MatrixRustSDK.Timeline { } } + //MARK: - loadReplyDetails + + open var loadReplyDetailsEventIdStrThrowableError: Error? + var loadReplyDetailsEventIdStrUnderlyingCallsCount = 0 + open var loadReplyDetailsEventIdStrCallsCount: Int { + get { + if Thread.isMainThread { + return loadReplyDetailsEventIdStrUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = loadReplyDetailsEventIdStrUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + loadReplyDetailsEventIdStrUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + loadReplyDetailsEventIdStrUnderlyingCallsCount = newValue + } + } + } + } + open var loadReplyDetailsEventIdStrCalled: Bool { + return loadReplyDetailsEventIdStrCallsCount > 0 + } + open var loadReplyDetailsEventIdStrReceivedEventIdStr: String? + open var loadReplyDetailsEventIdStrReceivedInvocations: [String] = [] + + var loadReplyDetailsEventIdStrUnderlyingReturnValue: InReplyToDetails! + open var loadReplyDetailsEventIdStrReturnValue: InReplyToDetails! { + get { + if Thread.isMainThread { + return loadReplyDetailsEventIdStrUnderlyingReturnValue + } else { + var returnValue: InReplyToDetails? = nil + DispatchQueue.main.sync { + returnValue = loadReplyDetailsEventIdStrUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + loadReplyDetailsEventIdStrUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + loadReplyDetailsEventIdStrUnderlyingReturnValue = newValue + } + } + } + } + open var loadReplyDetailsEventIdStrClosure: ((String) async throws -> InReplyToDetails)? + + open override func loadReplyDetails(eventIdStr: String) async throws -> InReplyToDetails { + if let error = loadReplyDetailsEventIdStrThrowableError { + throw error + } + loadReplyDetailsEventIdStrCallsCount += 1 + loadReplyDetailsEventIdStrReceivedEventIdStr = eventIdStr + loadReplyDetailsEventIdStrReceivedInvocations.append(eventIdStr) + if let loadReplyDetailsEventIdStrClosure = loadReplyDetailsEventIdStrClosure { + return try await loadReplyDetailsEventIdStrClosure(eventIdStr) + } else { + return loadReplyDetailsEventIdStrReturnValue + } + } + //MARK: - markAsRead open var markAsReadReceiptTypeThrowableError: Error? diff --git a/ElementX/Sources/Mocks/RoomTimelineProviderMock.swift b/ElementX/Sources/Mocks/RoomTimelineProviderMock.swift index ac6772f2e..1a13ce597 100644 --- a/ElementX/Sources/Mocks/RoomTimelineProviderMock.swift +++ b/ElementX/Sources/Mocks/RoomTimelineProviderMock.swift @@ -20,20 +20,25 @@ import MatrixRustSDK @MainActor class AutoUpdatingRoomTimelineProviderMock: RoomTimelineProvider { - private let innerUpdatePublisher: PassthroughSubject<[TimelineDiff], Never> + static var timelineListener: TimelineListener? + private let innerPaginationStatePublisher: PassthroughSubject - private let innerItems: [TimelineItemProxy] = [] init() { - innerUpdatePublisher = .init() innerPaginationStatePublisher = .init() - super.init(currentItems: [], + let timelineMock = TimelineSDKMock() + + timelineMock.addListenerListenerClosure = { listener in + Self.timelineListener = listener + return TaskHandleSDKMock() + } + + super.init(timeline: timelineMock, isLive: true, - updatePublisher: innerUpdatePublisher.eraseToAnyPublisher(), paginationStatePublisher: innerPaginationStatePublisher.eraseToAnyPublisher()) - Task.detached { [weak self] in + Task.detached { for _ in 0...100 { try? await Task.sleep(for: .seconds(1)) @@ -41,7 +46,7 @@ class AutoUpdatingRoomTimelineProviderMock: RoomTimelineProvider { diff.changeReturnValue = .append diff.appendReturnValue = [TimelineItemFixtures.messageTimelineItem] - self?.innerUpdatePublisher.send([diff]) + await Self.timelineListener?.onUpdate(diff: [diff]) } } } diff --git a/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift b/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift index fd53d277f..f27776f72 100644 --- a/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift +++ b/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift @@ -21,6 +21,8 @@ import MatrixRustSDK class RoomTimelineProvider: RoomTimelineProviderProtocol { private var cancellables = Set() private let serialDispatchQueue: DispatchQueue + + private var roomTimelineObservationToken: TaskHandle? private let paginationStateSubject = CurrentValueSubject(.default) var paginationState: PaginationState { @@ -28,8 +30,10 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol { } private let itemProxiesSubject: CurrentValueSubject<[TimelineItemProxy], Never> - var itemProxies: [TimelineItemProxy] { - itemProxiesSubject.value + private(set) var itemProxies: [TimelineItemProxy] = [] { + didSet { + itemProxiesSubject.send(itemProxies) + } } var updatePublisher: AnyPublisher<([TimelineItemProxy], PaginationState), Never> { @@ -45,28 +49,29 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol { membershipChangeSubject .eraseToAnyPublisher() } + + deinit { + roomTimelineObservationToken?.cancel() + } - init(currentItems: [TimelineItem], - isLive: Bool, - updatePublisher: AnyPublisher<[TimelineDiff], Never>, - paginationStatePublisher: AnyPublisher) { + init(timeline: Timeline, isLive: Bool, paginationStatePublisher: AnyPublisher) { serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomtimelineprovider", qos: .utility) - itemProxiesSubject = CurrentValueSubject<[TimelineItemProxy], Never>(currentItems.map(TimelineItemProxy.init)) + itemProxiesSubject = CurrentValueSubject<[TimelineItemProxy], Never>([]) self.isLive = isLive - // Manually call it here as the didSet doesn't work from constructors - itemProxiesSubject.send(itemProxies) - - updatePublisher - .receive(on: serialDispatchQueue) - .sink { [weak self] in self?.updateItemsWithDiffs($0) } - .store(in: &cancellables) - paginationStatePublisher .sink { [weak self] in self?.paginationStateSubject.send($0) } .store(in: &cancellables) + + Task { + roomTimelineObservationToken = await timeline.addListener(listener: RoomTimelineListener { [weak self] timelineDiffs in + self?.serialDispatchQueue.sync { + self?.updateItemsWithDiffs(timelineDiffs) + } + }) + } } // MARK: - Private @@ -80,22 +85,19 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol { MXLog.verbose("Received timeline diff") - let items = diffs - .reduce(itemProxies) { currentItems, diff in - guard let collectionDiff = buildDiff(from: diff, on: currentItems) else { - MXLog.error("Failed building CollectionDifference from \(diff)") - return currentItems - } - - guard let updatedItems = currentItems.applying(collectionDiff) else { - MXLog.error("Failed applying diff: \(collectionDiff)") - return currentItems - } - - return updatedItems + itemProxies = diffs.reduce(itemProxies) { currentItems, diff in + guard let collectionDiff = buildDiff(from: diff, on: currentItems) else { + MXLog.error("Failed building CollectionDifference from \(diff)") + return currentItems } - - itemProxiesSubject.send(items) + + guard let updatedItems = currentItems.applying(collectionDiff) else { + MXLog.error("Failed applying diff: \(collectionDiff)") + return currentItems + } + + return updatedItems + } MXLog.verbose("Finished applying diffs, current items (\(itemProxies.count)) : \(itemProxies.map(\.debugIdentifier))") } @@ -251,3 +253,15 @@ enum DebugIdentifier { case virtual(timelineID: String, dscription: String) case unknown(timelineID: String) } + +private final class RoomTimelineListener: TimelineListener { + private let onUpdateClosure: ([TimelineDiff]) -> Void + + init(_ onUpdateClosure: @escaping ([TimelineDiff]) -> Void) { + self.onUpdateClosure = onUpdateClosure + } + + func onUpdate(diff: [TimelineDiff]) { + onUpdateClosure(diff) + } +} diff --git a/ElementX/Sources/Services/Timeline/TimelineProxy.swift b/ElementX/Sources/Services/Timeline/TimelineProxy.swift index 00594d94e..8f6dc570c 100644 --- a/ElementX/Sources/Services/Timeline/TimelineProxy.swift +++ b/ElementX/Sources/Services/Timeline/TimelineProxy.swift @@ -22,14 +22,9 @@ final class TimelineProxy: TimelineProxyProtocol { private let timeline: Timeline private var backPaginationStatusObservationToken: TaskHandle? - private var roomTimelineObservationToken: TaskHandle? - - // periphery:ignore - retaining purpose - private var timelineListener: RoomTimelineListener? private let backPaginationSubscriptionSubject = CurrentValueSubject(.idle) private let forwardPaginationStatusSubject = CurrentValueSubject(.timelineEndReached) - private let timelineUpdatesSubject = PassthroughSubject<[TimelineDiff], Never>() let isLive: Bool @@ -40,7 +35,6 @@ final class TimelineProxy: TimelineProxyProtocol { deinit { backPaginationStatusObservationToken?.cancel() - roomTimelineObservationToken?.cancel() } init(timeline: Timeline, isLive: Bool) { @@ -54,15 +48,6 @@ final class TimelineProxy: TimelineProxyProtocol { return } - let timelineListener = RoomTimelineListener { [weak self] timelineDiffs in - self?.timelineUpdatesSubject.send(timelineDiffs) - } - - self.timelineListener = timelineListener - - let result = await timeline.addListener(listener: timelineListener) - roomTimelineObservationToken = result.itemsStream - let paginationStatePublisher = backPaginationSubscriptionSubject .combineLatest(forwardPaginationStatusSubject) .map { PaginationState(backward: $0.0, forward: $0.1) } @@ -70,10 +55,7 @@ final class TimelineProxy: TimelineProxyProtocol { await subscribeToPagination() - innerTimelineProvider = await RoomTimelineProvider(currentItems: result.items, - isLive: isLive, - updatePublisher: timelineUpdatesSubject.eraseToAnyPublisher(), - paginationStatePublisher: paginationStatePublisher) + innerTimelineProvider = await RoomTimelineProvider(timeline: timeline, isLive: isLive, paginationStatePublisher: paginationStatePublisher) } func fetchDetails(for eventID: String) { @@ -533,18 +515,6 @@ final class TimelineProxy: TimelineProxyProtocol { } } -private final class RoomTimelineListener: TimelineListener { - private let onUpdateClosure: ([TimelineDiff]) -> Void - - init(_ onUpdateClosure: @escaping ([TimelineDiff]) -> Void) { - self.onUpdateClosure = onUpdateClosure - } - - func onUpdate(diff: [TimelineDiff]) { - onUpdateClosure(diff) - } -} - private final class RoomPaginationStatusListener: PaginationStatusListener { private let onUpdateClosure: (LiveBackPaginationStatus) -> Void diff --git a/project.yml b/project.yml index 428506bae..ac8c15676 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.11 + exactVersion: 1.0.13 # path: ../matrix-rust-sdk Compound: url: https://github.com/element-hq/compound-ios