Add TimelineProxy (update sdk to 0.0.7-november23) (#2178)

* Refactor RoomProxy

* Refactor paginateBackwards

* Refactor sendReadReceipt

* Refactor messageEventContent APIs

* Refactor sendMessage

* Refactor toggleReaction

* Refactor send attachments

* Refactor sendLocation

* Refactor cancel/retry send

* ⚠️ Fix encryption build errors

* Refactor editMessage

* Refactor retryDecryption

* Refactor fetchDetails

* Refactor polls APIs

* Refactor fetchMembers

* Refactor RoomTimelineProviderProtocol

* Update sdk to 0.0.7-november23

* Fix UTs

* Fix comment

* Delete old workaround

* Move TimelineProxyError

* Delete queue warnings

* Fix key listener

* Add pollHistory timeline property

* Refactor room/timeline subscriptions

* Delete unused code
This commit is contained in:
Alfonso Grillo 2023-11-28 20:01:35 +01:00 committed by GitHub
parent fb2bc1c39a
commit e7494164b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1212 additions and 1085 deletions

View File

@ -176,6 +176,7 @@
2F623DA1122140A987B34D08 /* NotificationSettingsEditScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7BB497B2F539C17E88F6B7 /* NotificationSettingsEditScreenViewModelProtocol.swift */; }; 2F623DA1122140A987B34D08 /* NotificationSettingsEditScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7BB497B2F539C17E88F6B7 /* NotificationSettingsEditScreenViewModelProtocol.swift */; };
2F66701B15657A87B4AC3A0A /* WaitlistScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09CE2B7AD979BDEE09FEDB08 /* WaitlistScreenModels.swift */; }; 2F66701B15657A87B4AC3A0A /* WaitlistScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09CE2B7AD979BDEE09FEDB08 /* WaitlistScreenModels.swift */; };
2F94054F50E312AF30BE07F3 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40B21E611DADDEF00307E7AC /* String.swift */; }; 2F94054F50E312AF30BE07F3 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40B21E611DADDEF00307E7AC /* String.swift */; };
2FEC6652055984389CE1BBEC /* TimelineProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50F03079F6B5EF9CA005F14 /* TimelineProxyProtocol.swift */; };
3042527CB344A9EF1157FC26 /* AudioRecorderStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C55CC239AE12339C565F6C9A /* AudioRecorderStateTests.swift */; }; 3042527CB344A9EF1157FC26 /* AudioRecorderStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C55CC239AE12339C565F6C9A /* AudioRecorderStateTests.swift */; };
308BD9343B95657FAA583FB7 /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 19CD5B074D7DD44AF4C58BB6 /* SwiftState */; }; 308BD9343B95657FAA583FB7 /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 19CD5B074D7DD44AF4C58BB6 /* SwiftState */; };
3097A0A867D2B19CE32DAE58 /* UIKitBackgroundTaskService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF1FFC3336EB23374BBBFCC /* UIKitBackgroundTaskService.swift */; }; 3097A0A867D2B19CE32DAE58 /* UIKitBackgroundTaskService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF1FFC3336EB23374BBBFCC /* UIKitBackgroundTaskService.swift */; };
@ -889,6 +890,7 @@
E78D429F18071545BF661A52 /* RoomDetailsEditScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3E77399BD262D301451BF2 /* RoomDetailsEditScreenCoordinator.swift */; }; E78D429F18071545BF661A52 /* RoomDetailsEditScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3E77399BD262D301451BF2 /* RoomDetailsEditScreenCoordinator.swift */; };
E794AB6ABE1FF5AF0573FEA1 /* BlurHashEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9332DFE9642F0A46ECA0497B /* BlurHashEncode.swift */; }; E794AB6ABE1FF5AF0573FEA1 /* BlurHashEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9332DFE9642F0A46ECA0497B /* BlurHashEncode.swift */; };
E79D79CDAFE8BEBCC3AECA54 /* AppLockScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08283301736A6FE9D558B2CB /* AppLockScreenViewModelProtocol.swift */; }; E79D79CDAFE8BEBCC3AECA54 /* AppLockScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08283301736A6FE9D558B2CB /* AppLockScreenViewModelProtocol.swift */; };
E82E13CC3EB923CCB8F8273C /* TimelineProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E543072DE58E751F028998 /* TimelineProxy.swift */; };
E84ADFE9696936C18C2424B5 /* SecureBackupScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A00BB9CD12CF6AC98D5485 /* SecureBackupScreen.swift */; }; E84ADFE9696936C18C2424B5 /* SecureBackupScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A00BB9CD12CF6AC98D5485 /* SecureBackupScreen.swift */; };
E89536FC8C0E4B79E9842A78 /* RoomTimelineControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C0197EAE9D45A662B8847B6 /* RoomTimelineControllerProtocol.swift */; }; E89536FC8C0E4B79E9842A78 /* RoomTimelineControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C0197EAE9D45A662B8847B6 /* RoomTimelineControllerProtocol.swift */; };
E9347F56CF0683208F4D9249 /* RoomNotificationSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81A9B5225D0881CEFA2CF7C9 /* RoomNotificationSettingsScreenViewModel.swift */; }; E9347F56CF0683208F4D9249 /* RoomNotificationSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81A9B5225D0881CEFA2CF7C9 /* RoomNotificationSettingsScreenViewModel.swift */; };
@ -1658,6 +1660,7 @@
B43456E73F8A2D52B69B9FB9 /* TemplateScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenViewModel.swift; sourceTree = "<group>"; }; B43456E73F8A2D52B69B9FB9 /* TemplateScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenViewModel.swift; sourceTree = "<group>"; };
B48B7AD4908C5C374517B892 /* MapAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = MapAssets.xcassets; sourceTree = "<group>"; }; B48B7AD4908C5C374517B892 /* MapAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = MapAssets.xcassets; sourceTree = "<group>"; };
B4CFE236419E830E8946639C /* Analytics+SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Analytics+SwiftUI.swift"; sourceTree = "<group>"; }; B4CFE236419E830E8946639C /* Analytics+SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Analytics+SwiftUI.swift"; sourceTree = "<group>"; };
B50F03079F6B5EF9CA005F14 /* TimelineProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineProxyProtocol.swift; sourceTree = "<group>"; };
B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = "<group>"; }; B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = "<group>"; };
B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = "<group>"; }; B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = "<group>"; };
B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; path = ConfettiScene.scn; sourceTree = "<group>"; }; B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; path = ConfettiScene.scn; sourceTree = "<group>"; };
@ -1912,6 +1915,7 @@
F899D02CF26EA7675EEBE74C /* UserSessionScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionScreenTests.swift; sourceTree = "<group>"; }; F899D02CF26EA7675EEBE74C /* UserSessionScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionScreenTests.swift; sourceTree = "<group>"; };
F8CCF9A924521DECA44778C4 /* AppLockSetupBiometricsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupBiometricsScreen.swift; sourceTree = "<group>"; }; F8CCF9A924521DECA44778C4 /* AppLockSetupBiometricsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupBiometricsScreen.swift; sourceTree = "<group>"; };
F8CEB4634C0DD7779C4AB504 /* CreateRoomScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomScreenUITests.swift; sourceTree = "<group>"; }; F8CEB4634C0DD7779C4AB504 /* CreateRoomScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomScreenUITests.swift; sourceTree = "<group>"; };
F9E543072DE58E751F028998 /* TimelineProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineProxy.swift; sourceTree = "<group>"; };
F9E785D5137510481733A3E8 /* TextRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineView.swift; sourceTree = "<group>"; }; F9E785D5137510481733A3E8 /* TextRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineView.swift; sourceTree = "<group>"; };
F9ED8E731E21055F728E5FED /* TimelineStartRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStartRoomTimelineView.swift; sourceTree = "<group>"; }; F9ED8E731E21055F728E5FED /* TimelineStartRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStartRoomTimelineView.swift; sourceTree = "<group>"; };
FA7BB497B2F539C17E88F6B7 /* NotificationSettingsEditScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenViewModelProtocol.swift; sourceTree = "<group>"; }; FA7BB497B2F539C17E88F6B7 /* NotificationSettingsEditScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenViewModelProtocol.swift; sourceTree = "<group>"; };
@ -4653,6 +4657,8 @@
095AED4CF56DFF3EB7BB84C8 /* RoomTimelineProviderProtocol.swift */, 095AED4CF56DFF3EB7BB84C8 /* RoomTimelineProviderProtocol.swift */,
2D505843AB66822EB91F0DF0 /* TimelineItemProxy.swift */, 2D505843AB66822EB91F0DF0 /* TimelineItemProxy.swift */,
55AEEF8142DF1B59DB40FB93 /* TimelineItemSender.swift */, 55AEEF8142DF1B59DB40FB93 /* TimelineItemSender.swift */,
F9E543072DE58E751F028998 /* TimelineProxy.swift */,
B50F03079F6B5EF9CA005F14 /* TimelineProxyProtocol.swift */,
3EA31CC7012EA2A5653DAFC9 /* Fixtures */, 3EA31CC7012EA2A5653DAFC9 /* Fixtures */,
2F2FED77226A43559F009463 /* TimelineController */, 2F2FED77226A43559F009463 /* TimelineController */,
6B0910BCE4F1B02F124E1A09 /* TimelineItemContent */, 6B0910BCE4F1B02F124E1A09 /* TimelineItemContent */,
@ -5869,6 +5875,8 @@
1B88BB631F7FC45A213BB554 /* TimelineItemSender.swift in Sources */, 1B88BB631F7FC45A213BB554 /* TimelineItemSender.swift in Sources */,
A680F54935A6ADEA4ED6C38F /* TimelineItemStatusView.swift in Sources */, A680F54935A6ADEA4ED6C38F /* TimelineItemStatusView.swift in Sources */,
562EFB9AB62B38830D9AA778 /* TimelineMediaFrame.swift in Sources */, 562EFB9AB62B38830D9AA778 /* TimelineMediaFrame.swift in Sources */,
E82E13CC3EB923CCB8F8273C /* TimelineProxy.swift in Sources */,
2FEC6652055984389CE1BBEC /* TimelineProxyProtocol.swift in Sources */,
9B582B3EEFEA615D4A6FBF1A /* TimelineReactionsView.swift in Sources */, 9B582B3EEFEA615D4A6FBF1A /* TimelineReactionsView.swift in Sources */,
49814A48470F347426513B07 /* TimelineReadReceiptsView.swift in Sources */, 49814A48470F347426513B07 /* TimelineReadReceiptsView.swift in Sources */,
2A90DD14DE5C891BFA433950 /* TimelineReplyView.swift in Sources */, 2A90DD14DE5C891BFA433950 /* TimelineReplyView.swift in Sources */,
@ -6652,7 +6660,7 @@
repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift"; repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift";
requirement = { requirement = {
kind = exactVersion; kind = exactVersion;
version = "0.0.6-november23"; version = "0.0.7-november23";
}; };
}; };
821C67C9A7F8CC3FD41B28B4 /* XCRemoteSwiftPackageReference "emojibase-bindings" */ = { 821C67C9A7F8CC3FD41B28B4 /* XCRemoteSwiftPackageReference "emojibase-bindings" */ = {

View File

@ -130,8 +130,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/matrix-org/matrix-rust-components-swift", "location" : "https://github.com/matrix-org/matrix-rust-components-swift",
"state" : { "state" : {
"revision" : "42274cc2414e675b246432b037e7fa82b587fd97", "revision" : "aa1dd4fc587d4b4adf603fd7ffef1580c9955d0c",
"version" : "0.0.6-november23" "version" : "0.0.7-november23"
} }
}, },
{ {

View File

@ -266,9 +266,9 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
return return
} }
let roomProxy = await userSession.clientProxy.roomForIdentifier(roomID) let roomProxy = await userSession.clientProxy.roomForIdentifier(roomID)
switch await roomProxy?.sendMessage(replyText, switch await roomProxy?.timeline.sendMessage(replyText,
html: nil, html: nil,
intentionalMentions: .empty) { intentionalMentions: .empty) {
case .success: case .success:
break break
default: default:

View File

@ -595,20 +595,20 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
private func presentMapNavigator(interactionMode: StaticLocationInteractionMode) { private func presentMapNavigator(interactionMode: StaticLocationInteractionMode) {
let locationPickerNavigationStackCoordinator = NavigationStackCoordinator() let locationPickerNavigationStackCoordinator = NavigationStackCoordinator()
let params = StaticLocationScreenCoordinatorParameters(interactionMode: interactionMode) let params = StaticLocationScreenCoordinatorParameters(interactionMode: interactionMode)
let coordinator = StaticLocationScreenCoordinator(parameters: params) let coordinator = StaticLocationScreenCoordinator(parameters: params)
coordinator.actions.sink { [weak self] action in coordinator.actions.sink { [weak self] action in
guard let self else { return } guard let self else { return }
switch action { switch action {
case .selectedLocation(let geoURI, let isUserLocation): case .selectedLocation(let geoURI, let isUserLocation):
Task { Task {
_ = await self.roomProxy?.sendLocation(body: geoURI.bodyMessage, _ = await self.roomProxy?.timeline.sendLocation(body: geoURI.bodyMessage,
geoURI: geoURI, geoURI: geoURI,
description: nil, description: nil,
zoomLevel: 15, zoomLevel: 15,
assetType: isUserLocation ? .sender : .pin) assetType: isUserLocation ? .sender : .pin)
self.navigationSplitCoordinator.setSheetCoordinator(nil) self.navigationSplitCoordinator.setSheetCoordinator(nil)
} }
@ -622,9 +622,9 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
} }
} }
.store(in: &cancellables) .store(in: &cancellables)
locationPickerNavigationStackCoordinator.setRootCoordinator(coordinator) locationPickerNavigationStackCoordinator.setRootCoordinator(coordinator)
navigationStackCoordinator.setSheetCoordinator(locationPickerNavigationStackCoordinator) { [weak self] in navigationStackCoordinator.setSheetCoordinator(locationPickerNavigationStackCoordinator) { [weak self] in
self?.stateMachine.tryEvent(.dismissMapNavigator) self?.stateMachine.tryEvent(.dismissMapNavigator)
} }
@ -671,7 +671,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
return return
} }
let result = await roomProxy.createPoll(question: question, answers: options, pollKind: pollKind) let result = await roomProxy.timeline.createPoll(question: question, answers: options, pollKind: pollKind)
self.analytics.trackComposer(inThread: false, self.analytics.trackComposer(inThread: false,
isEditing: false, isEditing: false,
@ -697,7 +697,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
return return
} }
let result = await roomProxy.editPoll(original: pollStartID, question: question, answers: options, pollKind: pollKind) let result = await roomProxy.timeline.editPoll(original: pollStartID, question: question, answers: options, pollKind: pollKind)
switch result { switch result {
case .success: case .success:
@ -781,7 +781,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
return return
} }
guard let messageEventContent = roomProxy.messageEventContent(for: eventID) else { guard let messageEventContent = roomProxy.timeline.messageEventContent(for: eventID) else {
MXLog.error("Failed retrieving forwarded message event content for eventID: \(eventID)") MXLog.error("Failed retrieving forwarded message event content for eventID: \(eventID)")
userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown)) userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
return return
@ -793,7 +793,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
return return
} }
if case .failure(let error) = await targetRoomProxy.sendMessageEventContent(messageEventContent) { if case .failure(let error) = await targetRoomProxy.timeline.sendMessageEventContent(messageEventContent) {
MXLog.error("Failed forwarding message with error: \(error)") MXLog.error("Failed forwarding message with error: \(error)")
userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown)) userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
return return

View File

@ -1934,11 +1934,16 @@ class RoomProxyMock: RoomProxyProtocol {
set(value) { underlyingStateUpdatesPublisher = value } set(value) { underlyingStateUpdatesPublisher = value }
} }
var underlyingStateUpdatesPublisher: AnyPublisher<Void, Never>! var underlyingStateUpdatesPublisher: AnyPublisher<Void, Never>!
var timelineProvider: RoomTimelineProviderProtocol { var timeline: TimelineProxyProtocol {
get { return underlyingTimelineProvider } get { return underlyingTimeline }
set(value) { underlyingTimelineProvider = value } set(value) { underlyingTimeline = value }
} }
var underlyingTimelineProvider: RoomTimelineProviderProtocol! var underlyingTimeline: TimelineProxyProtocol!
var pollHistoryTimeline: TimelineProxyProtocol {
get { return underlyingPollHistoryTimeline }
set(value) { underlyingPollHistoryTimeline = value }
}
var underlyingPollHistoryTimeline: TimelineProxyProtocol!
//MARK: - subscribeForUpdates //MARK: - subscribeForUpdates
@ -1994,291 +1999,6 @@ class RoomProxyMock: RoomProxyProtocol {
return loadDisplayNameForUserIdReturnValue return loadDisplayNameForUserIdReturnValue
} }
} }
//MARK: - paginateBackwards
var paginateBackwardsRequestSizeUntilNumberOfItemsCallsCount = 0
var paginateBackwardsRequestSizeUntilNumberOfItemsCalled: Bool {
return paginateBackwardsRequestSizeUntilNumberOfItemsCallsCount > 0
}
var paginateBackwardsRequestSizeUntilNumberOfItemsReceivedArguments: (requestSize: UInt, untilNumberOfItems: UInt)?
var paginateBackwardsRequestSizeUntilNumberOfItemsReceivedInvocations: [(requestSize: UInt, untilNumberOfItems: UInt)] = []
var paginateBackwardsRequestSizeUntilNumberOfItemsReturnValue: Result<Void, RoomProxyError>!
var paginateBackwardsRequestSizeUntilNumberOfItemsClosure: ((UInt, UInt) async -> Result<Void, RoomProxyError>)?
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomProxyError> {
paginateBackwardsRequestSizeUntilNumberOfItemsCallsCount += 1
paginateBackwardsRequestSizeUntilNumberOfItemsReceivedArguments = (requestSize: requestSize, untilNumberOfItems: untilNumberOfItems)
paginateBackwardsRequestSizeUntilNumberOfItemsReceivedInvocations.append((requestSize: requestSize, untilNumberOfItems: untilNumberOfItems))
if let paginateBackwardsRequestSizeUntilNumberOfItemsClosure = paginateBackwardsRequestSizeUntilNumberOfItemsClosure {
return await paginateBackwardsRequestSizeUntilNumberOfItemsClosure(requestSize, untilNumberOfItems)
} else {
return paginateBackwardsRequestSizeUntilNumberOfItemsReturnValue
}
}
//MARK: - sendReadReceipt
var sendReadReceiptForCallsCount = 0
var sendReadReceiptForCalled: Bool {
return sendReadReceiptForCallsCount > 0
}
var sendReadReceiptForReceivedEventID: String?
var sendReadReceiptForReceivedInvocations: [String] = []
var sendReadReceiptForReturnValue: Result<Void, RoomProxyError>!
var sendReadReceiptForClosure: ((String) async -> Result<Void, RoomProxyError>)?
func sendReadReceipt(for eventID: String) async -> Result<Void, RoomProxyError> {
sendReadReceiptForCallsCount += 1
sendReadReceiptForReceivedEventID = eventID
sendReadReceiptForReceivedInvocations.append(eventID)
if let sendReadReceiptForClosure = sendReadReceiptForClosure {
return await sendReadReceiptForClosure(eventID)
} else {
return sendReadReceiptForReturnValue
}
}
//MARK: - messageEventContent
var messageEventContentForCallsCount = 0
var messageEventContentForCalled: Bool {
return messageEventContentForCallsCount > 0
}
var messageEventContentForReceivedEventID: String?
var messageEventContentForReceivedInvocations: [String] = []
var messageEventContentForReturnValue: RoomMessageEventContentWithoutRelation?
var messageEventContentForClosure: ((String) -> RoomMessageEventContentWithoutRelation?)?
func messageEventContent(for eventID: String) -> RoomMessageEventContentWithoutRelation? {
messageEventContentForCallsCount += 1
messageEventContentForReceivedEventID = eventID
messageEventContentForReceivedInvocations.append(eventID)
if let messageEventContentForClosure = messageEventContentForClosure {
return messageEventContentForClosure(eventID)
} else {
return messageEventContentForReturnValue
}
}
//MARK: - sendMessageEventContent
var sendMessageEventContentCallsCount = 0
var sendMessageEventContentCalled: Bool {
return sendMessageEventContentCallsCount > 0
}
var sendMessageEventContentReceivedMessageContent: RoomMessageEventContentWithoutRelation?
var sendMessageEventContentReceivedInvocations: [RoomMessageEventContentWithoutRelation] = []
var sendMessageEventContentReturnValue: Result<Void, RoomProxyError>!
var sendMessageEventContentClosure: ((RoomMessageEventContentWithoutRelation) async -> Result<Void, RoomProxyError>)?
func sendMessageEventContent(_ messageContent: RoomMessageEventContentWithoutRelation) async -> Result<Void, RoomProxyError> {
sendMessageEventContentCallsCount += 1
sendMessageEventContentReceivedMessageContent = messageContent
sendMessageEventContentReceivedInvocations.append(messageContent)
if let sendMessageEventContentClosure = sendMessageEventContentClosure {
return await sendMessageEventContentClosure(messageContent)
} else {
return sendMessageEventContentReturnValue
}
}
//MARK: - sendMessage
var sendMessageHtmlInReplyToIntentionalMentionsCallsCount = 0
var sendMessageHtmlInReplyToIntentionalMentionsCalled: Bool {
return sendMessageHtmlInReplyToIntentionalMentionsCallsCount > 0
}
var sendMessageHtmlInReplyToIntentionalMentionsReceivedArguments: (message: String, html: String?, eventID: String?, intentionalMentions: IntentionalMentions)?
var sendMessageHtmlInReplyToIntentionalMentionsReceivedInvocations: [(message: String, html: String?, eventID: String?, intentionalMentions: IntentionalMentions)] = []
var sendMessageHtmlInReplyToIntentionalMentionsReturnValue: Result<Void, RoomProxyError>!
var sendMessageHtmlInReplyToIntentionalMentionsClosure: ((String, String?, String?, IntentionalMentions) async -> Result<Void, RoomProxyError>)?
func sendMessage(_ message: String, html: String?, inReplyTo eventID: String?, intentionalMentions: IntentionalMentions) async -> Result<Void, RoomProxyError> {
sendMessageHtmlInReplyToIntentionalMentionsCallsCount += 1
sendMessageHtmlInReplyToIntentionalMentionsReceivedArguments = (message: message, html: html, eventID: eventID, intentionalMentions: intentionalMentions)
sendMessageHtmlInReplyToIntentionalMentionsReceivedInvocations.append((message: message, html: html, eventID: eventID, intentionalMentions: intentionalMentions))
if let sendMessageHtmlInReplyToIntentionalMentionsClosure = sendMessageHtmlInReplyToIntentionalMentionsClosure {
return await sendMessageHtmlInReplyToIntentionalMentionsClosure(message, html, eventID, intentionalMentions)
} else {
return sendMessageHtmlInReplyToIntentionalMentionsReturnValue
}
}
//MARK: - toggleReaction
var toggleReactionToCallsCount = 0
var toggleReactionToCalled: Bool {
return toggleReactionToCallsCount > 0
}
var toggleReactionToReceivedArguments: (reaction: String, eventID: String)?
var toggleReactionToReceivedInvocations: [(reaction: String, eventID: String)] = []
var toggleReactionToReturnValue: Result<Void, RoomProxyError>!
var toggleReactionToClosure: ((String, String) async -> Result<Void, RoomProxyError>)?
func toggleReaction(_ reaction: String, to eventID: String) async -> Result<Void, RoomProxyError> {
toggleReactionToCallsCount += 1
toggleReactionToReceivedArguments = (reaction: reaction, eventID: eventID)
toggleReactionToReceivedInvocations.append((reaction: reaction, eventID: eventID))
if let toggleReactionToClosure = toggleReactionToClosure {
return await toggleReactionToClosure(reaction, eventID)
} else {
return toggleReactionToReturnValue
}
}
//MARK: - sendImage
var sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleCallsCount = 0
var sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleCalled: Bool {
return sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleCallsCount > 0
}
var sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleReturnValue: Result<Void, RoomProxyError>!
var sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleClosure: ((URL, URL, ImageInfo, CurrentValueSubject<Double, Never>?, @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, RoomProxyError>)?
func sendImage(url: URL, thumbnailURL: URL, imageInfo: ImageInfo, progressSubject: CurrentValueSubject<Double, Never>?, requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, RoomProxyError> {
sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleCallsCount += 1
if let sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleClosure = sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleClosure {
return await sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleClosure(url, thumbnailURL, imageInfo, progressSubject, requestHandle)
} else {
return sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleReturnValue
}
}
//MARK: - sendVideo
var sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleCallsCount = 0
var sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleCalled: Bool {
return sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleCallsCount > 0
}
var sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleReturnValue: Result<Void, RoomProxyError>!
var sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleClosure: ((URL, URL, VideoInfo, CurrentValueSubject<Double, Never>?, @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, RoomProxyError>)?
func sendVideo(url: URL, thumbnailURL: URL, videoInfo: VideoInfo, progressSubject: CurrentValueSubject<Double, Never>?, requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, RoomProxyError> {
sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleCallsCount += 1
if let sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleClosure = sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleClosure {
return await sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleClosure(url, thumbnailURL, videoInfo, progressSubject, requestHandle)
} else {
return sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleReturnValue
}
}
//MARK: - sendAudio
var sendAudioUrlAudioInfoProgressSubjectRequestHandleCallsCount = 0
var sendAudioUrlAudioInfoProgressSubjectRequestHandleCalled: Bool {
return sendAudioUrlAudioInfoProgressSubjectRequestHandleCallsCount > 0
}
var sendAudioUrlAudioInfoProgressSubjectRequestHandleReturnValue: Result<Void, RoomProxyError>!
var sendAudioUrlAudioInfoProgressSubjectRequestHandleClosure: ((URL, AudioInfo, CurrentValueSubject<Double, Never>?, @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, RoomProxyError>)?
func sendAudio(url: URL, audioInfo: AudioInfo, progressSubject: CurrentValueSubject<Double, Never>?, requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, RoomProxyError> {
sendAudioUrlAudioInfoProgressSubjectRequestHandleCallsCount += 1
if let sendAudioUrlAudioInfoProgressSubjectRequestHandleClosure = sendAudioUrlAudioInfoProgressSubjectRequestHandleClosure {
return await sendAudioUrlAudioInfoProgressSubjectRequestHandleClosure(url, audioInfo, progressSubject, requestHandle)
} else {
return sendAudioUrlAudioInfoProgressSubjectRequestHandleReturnValue
}
}
//MARK: - sendFile
var sendFileUrlFileInfoProgressSubjectRequestHandleCallsCount = 0
var sendFileUrlFileInfoProgressSubjectRequestHandleCalled: Bool {
return sendFileUrlFileInfoProgressSubjectRequestHandleCallsCount > 0
}
var sendFileUrlFileInfoProgressSubjectRequestHandleReturnValue: Result<Void, RoomProxyError>!
var sendFileUrlFileInfoProgressSubjectRequestHandleClosure: ((URL, FileInfo, CurrentValueSubject<Double, Never>?, @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, RoomProxyError>)?
func sendFile(url: URL, fileInfo: FileInfo, progressSubject: CurrentValueSubject<Double, Never>?, requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, RoomProxyError> {
sendFileUrlFileInfoProgressSubjectRequestHandleCallsCount += 1
if let sendFileUrlFileInfoProgressSubjectRequestHandleClosure = sendFileUrlFileInfoProgressSubjectRequestHandleClosure {
return await sendFileUrlFileInfoProgressSubjectRequestHandleClosure(url, fileInfo, progressSubject, requestHandle)
} else {
return sendFileUrlFileInfoProgressSubjectRequestHandleReturnValue
}
}
//MARK: - sendLocation
var sendLocationBodyGeoURIDescriptionZoomLevelAssetTypeCallsCount = 0
var sendLocationBodyGeoURIDescriptionZoomLevelAssetTypeCalled: Bool {
return sendLocationBodyGeoURIDescriptionZoomLevelAssetTypeCallsCount > 0
}
var sendLocationBodyGeoURIDescriptionZoomLevelAssetTypeReceivedArguments: (body: String, geoURI: GeoURI, description: String?, zoomLevel: UInt8?, assetType: AssetType?)?
var sendLocationBodyGeoURIDescriptionZoomLevelAssetTypeReceivedInvocations: [(body: String, geoURI: GeoURI, description: String?, zoomLevel: UInt8?, assetType: AssetType?)] = []
var sendLocationBodyGeoURIDescriptionZoomLevelAssetTypeReturnValue: Result<Void, RoomProxyError>!
var sendLocationBodyGeoURIDescriptionZoomLevelAssetTypeClosure: ((String, GeoURI, String?, UInt8?, AssetType?) async -> Result<Void, RoomProxyError>)?
func sendLocation(body: String, geoURI: GeoURI, description: String?, zoomLevel: UInt8?, assetType: AssetType?) async -> Result<Void, RoomProxyError> {
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))
if let sendLocationBodyGeoURIDescriptionZoomLevelAssetTypeClosure = sendLocationBodyGeoURIDescriptionZoomLevelAssetTypeClosure {
return await sendLocationBodyGeoURIDescriptionZoomLevelAssetTypeClosure(body, geoURI, description, zoomLevel, assetType)
} else {
return sendLocationBodyGeoURIDescriptionZoomLevelAssetTypeReturnValue
}
}
//MARK: - sendVoiceMessage
var sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleCallsCount = 0
var sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleCalled: Bool {
return sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleCallsCount > 0
}
var sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleReturnValue: Result<Void, RoomProxyError>!
var sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleClosure: ((URL, AudioInfo, [UInt16], CurrentValueSubject<Double, Never>?, @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, RoomProxyError>)?
func sendVoiceMessage(url: URL, audioInfo: AudioInfo, waveform: [UInt16], progressSubject: CurrentValueSubject<Double, Never>?, requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, RoomProxyError> {
sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleCallsCount += 1
if let sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleClosure = sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleClosure {
return await sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleClosure(url, audioInfo, waveform, progressSubject, requestHandle)
} else {
return sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleReturnValue
}
}
//MARK: - retrySend
var retrySendTransactionIDCallsCount = 0
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: - cancelSend
var cancelSendTransactionIDCallsCount = 0
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 editMessageHtmlOriginalIntentionalMentionsCallsCount = 0
var editMessageHtmlOriginalIntentionalMentionsCalled: Bool {
return editMessageHtmlOriginalIntentionalMentionsCallsCount > 0
}
var editMessageHtmlOriginalIntentionalMentionsReceivedArguments: (newMessage: String, html: String?, eventID: String, intentionalMentions: IntentionalMentions)?
var editMessageHtmlOriginalIntentionalMentionsReceivedInvocations: [(newMessage: String, html: String?, eventID: String, intentionalMentions: IntentionalMentions)] = []
var editMessageHtmlOriginalIntentionalMentionsReturnValue: Result<Void, RoomProxyError>!
var editMessageHtmlOriginalIntentionalMentionsClosure: ((String, String?, String, IntentionalMentions) async -> Result<Void, RoomProxyError>)?
func editMessage(_ newMessage: String, html: String?, original eventID: String, intentionalMentions: IntentionalMentions) async -> Result<Void, RoomProxyError> {
editMessageHtmlOriginalIntentionalMentionsCallsCount += 1
editMessageHtmlOriginalIntentionalMentionsReceivedArguments = (newMessage: newMessage, html: html, eventID: eventID, intentionalMentions: intentionalMentions)
editMessageHtmlOriginalIntentionalMentionsReceivedInvocations.append((newMessage: newMessage, html: html, eventID: eventID, intentionalMentions: intentionalMentions))
if let editMessageHtmlOriginalIntentionalMentionsClosure = editMessageHtmlOriginalIntentionalMentionsClosure {
return await editMessageHtmlOriginalIntentionalMentionsClosure(newMessage, html, eventID, intentionalMentions)
} else {
return editMessageHtmlOriginalIntentionalMentionsReturnValue
}
}
//MARK: - redact //MARK: - redact
var redactCallsCount = 0 var redactCallsCount = 0
@ -2342,22 +2062,6 @@ class RoomProxyMock: RoomProxyProtocol {
return ignoreUserReturnValue return ignoreUserReturnValue
} }
} }
//MARK: - retryDecryption
var retryDecryptionForCallsCount = 0
var retryDecryptionForCalled: Bool {
return retryDecryptionForCallsCount > 0
}
var retryDecryptionForReceivedSessionID: String?
var retryDecryptionForReceivedInvocations: [String] = []
var retryDecryptionForClosure: ((String) async -> Void)?
func retryDecryption(for sessionID: String) async {
retryDecryptionForCallsCount += 1
retryDecryptionForReceivedSessionID = sessionID
retryDecryptionForReceivedInvocations.append(sessionID)
await retryDecryptionForClosure?(sessionID)
}
//MARK: - leaveRoom //MARK: - leaveRoom
var leaveRoomCallsCount = 0 var leaveRoomCallsCount = 0
@ -2459,22 +2163,6 @@ class RoomProxyMock: RoomProxyProtocol {
return acceptInvitationReturnValue return acceptInvitationReturnValue
} }
} }
//MARK: - fetchDetails
var fetchDetailsForCallsCount = 0
var fetchDetailsForCalled: Bool {
return fetchDetailsForCallsCount > 0
}
var fetchDetailsForReceivedEventID: String?
var fetchDetailsForReceivedInvocations: [String] = []
var fetchDetailsForClosure: ((String) -> Void)?
func fetchDetails(for eventID: String) {
fetchDetailsForCallsCount += 1
fetchDetailsForReceivedEventID = eventID
fetchDetailsForReceivedInvocations.append(eventID)
fetchDetailsForClosure?(eventID)
}
//MARK: - invite //MARK: - invite
var inviteUserIDCallsCount = 0 var inviteUserIDCallsCount = 0
@ -2618,90 +2306,6 @@ class RoomProxyMock: RoomProxyProtocol {
return canUserTriggerRoomNotificationUserIDReturnValue return canUserTriggerRoomNotificationUserIDReturnValue
} }
} }
//MARK: - createPoll
var createPollQuestionAnswersPollKindCallsCount = 0
var createPollQuestionAnswersPollKindCalled: Bool {
return createPollQuestionAnswersPollKindCallsCount > 0
}
var createPollQuestionAnswersPollKindReceivedArguments: (question: String, answers: [String], pollKind: Poll.Kind)?
var createPollQuestionAnswersPollKindReceivedInvocations: [(question: String, answers: [String], pollKind: Poll.Kind)] = []
var createPollQuestionAnswersPollKindReturnValue: Result<Void, RoomProxyError>!
var createPollQuestionAnswersPollKindClosure: ((String, [String], Poll.Kind) async -> Result<Void, RoomProxyError>)?
func createPoll(question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, RoomProxyError> {
createPollQuestionAnswersPollKindCallsCount += 1
createPollQuestionAnswersPollKindReceivedArguments = (question: question, answers: answers, pollKind: pollKind)
createPollQuestionAnswersPollKindReceivedInvocations.append((question: question, answers: answers, pollKind: pollKind))
if let createPollQuestionAnswersPollKindClosure = createPollQuestionAnswersPollKindClosure {
return await createPollQuestionAnswersPollKindClosure(question, answers, pollKind)
} else {
return createPollQuestionAnswersPollKindReturnValue
}
}
//MARK: - editPoll
var editPollOriginalQuestionAnswersPollKindCallsCount = 0
var editPollOriginalQuestionAnswersPollKindCalled: Bool {
return editPollOriginalQuestionAnswersPollKindCallsCount > 0
}
var editPollOriginalQuestionAnswersPollKindReceivedArguments: (eventID: String, question: String, answers: [String], pollKind: Poll.Kind)?
var editPollOriginalQuestionAnswersPollKindReceivedInvocations: [(eventID: String, question: String, answers: [String], pollKind: Poll.Kind)] = []
var editPollOriginalQuestionAnswersPollKindReturnValue: Result<Void, RoomProxyError>!
var editPollOriginalQuestionAnswersPollKindClosure: ((String, String, [String], Poll.Kind) async -> Result<Void, RoomProxyError>)?
func editPoll(original eventID: String, question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, RoomProxyError> {
editPollOriginalQuestionAnswersPollKindCallsCount += 1
editPollOriginalQuestionAnswersPollKindReceivedArguments = (eventID: eventID, question: question, answers: answers, pollKind: pollKind)
editPollOriginalQuestionAnswersPollKindReceivedInvocations.append((eventID: eventID, question: question, answers: answers, pollKind: pollKind))
if let editPollOriginalQuestionAnswersPollKindClosure = editPollOriginalQuestionAnswersPollKindClosure {
return await editPollOriginalQuestionAnswersPollKindClosure(eventID, question, answers, pollKind)
} else {
return editPollOriginalQuestionAnswersPollKindReturnValue
}
}
//MARK: - sendPollResponse
var sendPollResponsePollStartIDAnswersCallsCount = 0
var sendPollResponsePollStartIDAnswersCalled: Bool {
return sendPollResponsePollStartIDAnswersCallsCount > 0
}
var sendPollResponsePollStartIDAnswersReceivedArguments: (pollStartID: String, answers: [String])?
var sendPollResponsePollStartIDAnswersReceivedInvocations: [(pollStartID: String, answers: [String])] = []
var sendPollResponsePollStartIDAnswersReturnValue: Result<Void, RoomProxyError>!
var sendPollResponsePollStartIDAnswersClosure: ((String, [String]) async -> Result<Void, RoomProxyError>)?
func sendPollResponse(pollStartID: String, answers: [String]) async -> Result<Void, RoomProxyError> {
sendPollResponsePollStartIDAnswersCallsCount += 1
sendPollResponsePollStartIDAnswersReceivedArguments = (pollStartID: pollStartID, answers: answers)
sendPollResponsePollStartIDAnswersReceivedInvocations.append((pollStartID: pollStartID, answers: answers))
if let sendPollResponsePollStartIDAnswersClosure = sendPollResponsePollStartIDAnswersClosure {
return await sendPollResponsePollStartIDAnswersClosure(pollStartID, answers)
} else {
return sendPollResponsePollStartIDAnswersReturnValue
}
}
//MARK: - endPoll
var endPollPollStartIDTextCallsCount = 0
var endPollPollStartIDTextCalled: Bool {
return endPollPollStartIDTextCallsCount > 0
}
var endPollPollStartIDTextReceivedArguments: (pollStartID: String, text: String)?
var endPollPollStartIDTextReceivedInvocations: [(pollStartID: String, text: String)] = []
var endPollPollStartIDTextReturnValue: Result<Void, RoomProxyError>!
var endPollPollStartIDTextClosure: ((String, String) async -> Result<Void, RoomProxyError>)?
func endPoll(pollStartID: String, text: String) async -> Result<Void, RoomProxyError> {
endPollPollStartIDTextCallsCount += 1
endPollPollStartIDTextReceivedArguments = (pollStartID: pollStartID, text: text)
endPollPollStartIDTextReceivedInvocations.append((pollStartID: pollStartID, text: text))
if let endPollPollStartIDTextClosure = endPollPollStartIDTextClosure {
return await endPollPollStartIDTextClosure(pollStartID, text)
} else {
return endPollPollStartIDTextReturnValue
}
}
//MARK: - elementCallWidgetDriver //MARK: - elementCallWidgetDriver
var elementCallWidgetDriverCallsCount = 0 var elementCallWidgetDriverCallsCount = 0
@ -2963,6 +2567,427 @@ class SessionVerificationControllerProxyMock: SessionVerificationControllerProxy
} }
} }
} }
class TimelineProxyMock: TimelineProxyProtocol {
var timelineProvider: RoomTimelineProviderProtocol {
get { return underlyingTimelineProvider }
set(value) { underlyingTimelineProvider = value }
}
var underlyingTimelineProvider: RoomTimelineProviderProtocol!
//MARK: - subscribeForUpdates
var subscribeForUpdatesCallsCount = 0
var subscribeForUpdatesCalled: Bool {
return subscribeForUpdatesCallsCount > 0
}
var subscribeForUpdatesClosure: (() async -> Void)?
func subscribeForUpdates() async {
subscribeForUpdatesCallsCount += 1
await subscribeForUpdatesClosure?()
}
//MARK: - cancelSend
var cancelSendTransactionIDCallsCount = 0
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 editMessageHtmlOriginalIntentionalMentionsCallsCount = 0
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 editMessageHtmlOriginalIntentionalMentionsReturnValue: Result<Void, TimelineProxyError>!
var editMessageHtmlOriginalIntentionalMentionsClosure: ((String, String?, String, IntentionalMentions) async -> Result<Void, TimelineProxyError>)?
func editMessage(_ message: String, html: String?, original eventID: String, intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError> {
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 fetchDetailsForCallsCount = 0
var fetchDetailsForCalled: Bool {
return fetchDetailsForCallsCount > 0
}
var fetchDetailsForReceivedEventID: String?
var fetchDetailsForReceivedInvocations: [String] = []
var fetchDetailsForClosure: ((String) -> Void)?
func fetchDetails(for eventID: String) {
fetchDetailsForCallsCount += 1
fetchDetailsForReceivedEventID = eventID
fetchDetailsForReceivedInvocations.append(eventID)
fetchDetailsForClosure?(eventID)
}
//MARK: - messageEventContent
var messageEventContentForCallsCount = 0
var messageEventContentForCalled: Bool {
return messageEventContentForCallsCount > 0
}
var messageEventContentForReceivedEventID: String?
var messageEventContentForReceivedInvocations: [String] = []
var messageEventContentForReturnValue: RoomMessageEventContentWithoutRelation?
var messageEventContentForClosure: ((String) -> RoomMessageEventContentWithoutRelation?)?
func messageEventContent(for eventID: String) -> RoomMessageEventContentWithoutRelation? {
messageEventContentForCallsCount += 1
messageEventContentForReceivedEventID = eventID
messageEventContentForReceivedInvocations.append(eventID)
if let messageEventContentForClosure = messageEventContentForClosure {
return messageEventContentForClosure(eventID)
} else {
return messageEventContentForReturnValue
}
}
//MARK: - retryDecryption
var retryDecryptionForCallsCount = 0
var retryDecryptionForCalled: Bool {
return retryDecryptionForCallsCount > 0
}
var retryDecryptionForReceivedSessionID: String?
var retryDecryptionForReceivedInvocations: [String] = []
var retryDecryptionForClosure: ((String) async -> Void)?
func retryDecryption(for sessionID: String) async {
retryDecryptionForCallsCount += 1
retryDecryptionForReceivedSessionID = sessionID
retryDecryptionForReceivedInvocations.append(sessionID)
await retryDecryptionForClosure?(sessionID)
}
//MARK: - retrySend
var retrySendTransactionIDCallsCount = 0
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 paginateBackwardsRequestSizeUntilNumberOfItemsCallsCount = 0
var paginateBackwardsRequestSizeUntilNumberOfItemsCalled: Bool {
return paginateBackwardsRequestSizeUntilNumberOfItemsCallsCount > 0
}
var paginateBackwardsRequestSizeUntilNumberOfItemsReceivedArguments: (requestSize: UInt, untilNumberOfItems: UInt)?
var paginateBackwardsRequestSizeUntilNumberOfItemsReceivedInvocations: [(requestSize: UInt, untilNumberOfItems: UInt)] = []
var paginateBackwardsRequestSizeUntilNumberOfItemsReturnValue: Result<Void, TimelineProxyError>!
var paginateBackwardsRequestSizeUntilNumberOfItemsClosure: ((UInt, UInt) async -> Result<Void, TimelineProxyError>)?
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, TimelineProxyError> {
paginateBackwardsRequestSizeUntilNumberOfItemsCallsCount += 1
paginateBackwardsRequestSizeUntilNumberOfItemsReceivedArguments = (requestSize: requestSize, untilNumberOfItems: untilNumberOfItems)
paginateBackwardsRequestSizeUntilNumberOfItemsReceivedInvocations.append((requestSize: requestSize, untilNumberOfItems: untilNumberOfItems))
if let paginateBackwardsRequestSizeUntilNumberOfItemsClosure = paginateBackwardsRequestSizeUntilNumberOfItemsClosure {
return await paginateBackwardsRequestSizeUntilNumberOfItemsClosure(requestSize, untilNumberOfItems)
} else {
return paginateBackwardsRequestSizeUntilNumberOfItemsReturnValue
}
}
//MARK: - sendAudio
var sendAudioUrlAudioInfoProgressSubjectRequestHandleCallsCount = 0
var sendAudioUrlAudioInfoProgressSubjectRequestHandleCalled: Bool {
return sendAudioUrlAudioInfoProgressSubjectRequestHandleCallsCount > 0
}
var sendAudioUrlAudioInfoProgressSubjectRequestHandleReturnValue: Result<Void, TimelineProxyError>!
var sendAudioUrlAudioInfoProgressSubjectRequestHandleClosure: ((URL, AudioInfo, CurrentValueSubject<Double, Never>?, @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError>)?
func sendAudio(url: URL, audioInfo: AudioInfo, progressSubject: CurrentValueSubject<Double, Never>?, requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError> {
sendAudioUrlAudioInfoProgressSubjectRequestHandleCallsCount += 1
if let sendAudioUrlAudioInfoProgressSubjectRequestHandleClosure = sendAudioUrlAudioInfoProgressSubjectRequestHandleClosure {
return await sendAudioUrlAudioInfoProgressSubjectRequestHandleClosure(url, audioInfo, progressSubject, requestHandle)
} else {
return sendAudioUrlAudioInfoProgressSubjectRequestHandleReturnValue
}
}
//MARK: - sendFile
var sendFileUrlFileInfoProgressSubjectRequestHandleCallsCount = 0
var sendFileUrlFileInfoProgressSubjectRequestHandleCalled: Bool {
return sendFileUrlFileInfoProgressSubjectRequestHandleCallsCount > 0
}
var sendFileUrlFileInfoProgressSubjectRequestHandleReturnValue: Result<Void, TimelineProxyError>!
var sendFileUrlFileInfoProgressSubjectRequestHandleClosure: ((URL, FileInfo, CurrentValueSubject<Double, Never>?, @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError>)?
func sendFile(url: URL, fileInfo: FileInfo, progressSubject: CurrentValueSubject<Double, Never>?, requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError> {
sendFileUrlFileInfoProgressSubjectRequestHandleCallsCount += 1
if let sendFileUrlFileInfoProgressSubjectRequestHandleClosure = sendFileUrlFileInfoProgressSubjectRequestHandleClosure {
return await sendFileUrlFileInfoProgressSubjectRequestHandleClosure(url, fileInfo, progressSubject, requestHandle)
} else {
return sendFileUrlFileInfoProgressSubjectRequestHandleReturnValue
}
}
//MARK: - sendImage
var sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleCallsCount = 0
var sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleCalled: Bool {
return sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleCallsCount > 0
}
var sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleReturnValue: Result<Void, TimelineProxyError>!
var sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleClosure: ((URL, URL, ImageInfo, CurrentValueSubject<Double, Never>?, @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError>)?
func sendImage(url: URL, thumbnailURL: URL, imageInfo: ImageInfo, progressSubject: CurrentValueSubject<Double, Never>?, requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError> {
sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleCallsCount += 1
if let sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleClosure = sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleClosure {
return await sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleClosure(url, thumbnailURL, imageInfo, progressSubject, requestHandle)
} else {
return sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleReturnValue
}
}
//MARK: - sendLocation
var sendLocationBodyGeoURIDescriptionZoomLevelAssetTypeCallsCount = 0
var sendLocationBodyGeoURIDescriptionZoomLevelAssetTypeCalled: Bool {
return sendLocationBodyGeoURIDescriptionZoomLevelAssetTypeCallsCount > 0
}
var sendLocationBodyGeoURIDescriptionZoomLevelAssetTypeReceivedArguments: (body: String, geoURI: GeoURI, description: String?, zoomLevel: UInt8?, assetType: AssetType?)?
var sendLocationBodyGeoURIDescriptionZoomLevelAssetTypeReceivedInvocations: [(body: String, geoURI: GeoURI, description: String?, zoomLevel: UInt8?, assetType: AssetType?)] = []
var sendLocationBodyGeoURIDescriptionZoomLevelAssetTypeReturnValue: Result<Void, TimelineProxyError>!
var sendLocationBodyGeoURIDescriptionZoomLevelAssetTypeClosure: ((String, GeoURI, String?, UInt8?, AssetType?) async -> Result<Void, TimelineProxyError>)?
func sendLocation(body: String, geoURI: GeoURI, description: String?, zoomLevel: UInt8?, assetType: AssetType?) async -> Result<Void, TimelineProxyError> {
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))
if let sendLocationBodyGeoURIDescriptionZoomLevelAssetTypeClosure = sendLocationBodyGeoURIDescriptionZoomLevelAssetTypeClosure {
return await sendLocationBodyGeoURIDescriptionZoomLevelAssetTypeClosure(body, geoURI, description, zoomLevel, assetType)
} else {
return sendLocationBodyGeoURIDescriptionZoomLevelAssetTypeReturnValue
}
}
//MARK: - sendVideo
var sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleCallsCount = 0
var sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleCalled: Bool {
return sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleCallsCount > 0
}
var sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleReturnValue: Result<Void, TimelineProxyError>!
var sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleClosure: ((URL, URL, VideoInfo, CurrentValueSubject<Double, Never>?, @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError>)?
func sendVideo(url: URL, thumbnailURL: URL, videoInfo: VideoInfo, progressSubject: CurrentValueSubject<Double, Never>?, requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError> {
sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleCallsCount += 1
if let sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleClosure = sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleClosure {
return await sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleClosure(url, thumbnailURL, videoInfo, progressSubject, requestHandle)
} else {
return sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleReturnValue
}
}
//MARK: - sendVoiceMessage
var sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleCallsCount = 0
var sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleCalled: Bool {
return sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleCallsCount > 0
}
var sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleReturnValue: Result<Void, TimelineProxyError>!
var sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleClosure: ((URL, AudioInfo, [UInt16], CurrentValueSubject<Double, Never>?, @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError>)?
func sendVoiceMessage(url: URL, audioInfo: AudioInfo, waveform: [UInt16], progressSubject: CurrentValueSubject<Double, Never>?, requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError> {
sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleCallsCount += 1
if let sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleClosure = sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleClosure {
return await sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleClosure(url, audioInfo, waveform, progressSubject, requestHandle)
} else {
return sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleReturnValue
}
}
//MARK: - sendReadReceipt
var sendReadReceiptForCallsCount = 0
var sendReadReceiptForCalled: Bool {
return sendReadReceiptForCallsCount > 0
}
var sendReadReceiptForReceivedEventID: String?
var sendReadReceiptForReceivedInvocations: [String] = []
var sendReadReceiptForReturnValue: Result<Void, TimelineProxyError>!
var sendReadReceiptForClosure: ((String) async -> Result<Void, TimelineProxyError>)?
func sendReadReceipt(for eventID: String) async -> Result<Void, TimelineProxyError> {
sendReadReceiptForCallsCount += 1
sendReadReceiptForReceivedEventID = eventID
sendReadReceiptForReceivedInvocations.append(eventID)
if let sendReadReceiptForClosure = sendReadReceiptForClosure {
return await sendReadReceiptForClosure(eventID)
} else {
return sendReadReceiptForReturnValue
}
}
//MARK: - sendMessageEventContent
var sendMessageEventContentCallsCount = 0
var sendMessageEventContentCalled: Bool {
return sendMessageEventContentCallsCount > 0
}
var sendMessageEventContentReceivedMessageContent: RoomMessageEventContentWithoutRelation?
var sendMessageEventContentReceivedInvocations: [RoomMessageEventContentWithoutRelation] = []
var sendMessageEventContentReturnValue: Result<Void, TimelineProxyError>!
var sendMessageEventContentClosure: ((RoomMessageEventContentWithoutRelation) async -> Result<Void, TimelineProxyError>)?
func sendMessageEventContent(_ messageContent: RoomMessageEventContentWithoutRelation) async -> Result<Void, TimelineProxyError> {
sendMessageEventContentCallsCount += 1
sendMessageEventContentReceivedMessageContent = messageContent
sendMessageEventContentReceivedInvocations.append(messageContent)
if let sendMessageEventContentClosure = sendMessageEventContentClosure {
return await sendMessageEventContentClosure(messageContent)
} else {
return sendMessageEventContentReturnValue
}
}
//MARK: - sendMessage
var sendMessageHtmlInReplyToIntentionalMentionsCallsCount = 0
var sendMessageHtmlInReplyToIntentionalMentionsCalled: Bool {
return sendMessageHtmlInReplyToIntentionalMentionsCallsCount > 0
}
var sendMessageHtmlInReplyToIntentionalMentionsReceivedArguments: (message: String, html: String?, eventID: String?, intentionalMentions: IntentionalMentions)?
var sendMessageHtmlInReplyToIntentionalMentionsReceivedInvocations: [(message: String, html: String?, eventID: String?, intentionalMentions: IntentionalMentions)] = []
var sendMessageHtmlInReplyToIntentionalMentionsReturnValue: Result<Void, TimelineProxyError>!
var sendMessageHtmlInReplyToIntentionalMentionsClosure: ((String, String?, String?, IntentionalMentions) async -> Result<Void, TimelineProxyError>)?
func sendMessage(_ message: String, html: String?, inReplyTo eventID: String?, intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError> {
sendMessageHtmlInReplyToIntentionalMentionsCallsCount += 1
sendMessageHtmlInReplyToIntentionalMentionsReceivedArguments = (message: message, html: html, eventID: eventID, intentionalMentions: intentionalMentions)
sendMessageHtmlInReplyToIntentionalMentionsReceivedInvocations.append((message: message, html: html, eventID: eventID, intentionalMentions: intentionalMentions))
if let sendMessageHtmlInReplyToIntentionalMentionsClosure = sendMessageHtmlInReplyToIntentionalMentionsClosure {
return await sendMessageHtmlInReplyToIntentionalMentionsClosure(message, html, eventID, intentionalMentions)
} else {
return sendMessageHtmlInReplyToIntentionalMentionsReturnValue
}
}
//MARK: - toggleReaction
var toggleReactionToCallsCount = 0
var toggleReactionToCalled: Bool {
return toggleReactionToCallsCount > 0
}
var toggleReactionToReceivedArguments: (reaction: String, eventID: String)?
var toggleReactionToReceivedInvocations: [(reaction: String, eventID: String)] = []
var toggleReactionToReturnValue: Result<Void, TimelineProxyError>!
var toggleReactionToClosure: ((String, String) async -> Result<Void, TimelineProxyError>)?
func toggleReaction(_ reaction: String, to eventID: String) async -> Result<Void, TimelineProxyError> {
toggleReactionToCallsCount += 1
toggleReactionToReceivedArguments = (reaction: reaction, eventID: eventID)
toggleReactionToReceivedInvocations.append((reaction: reaction, eventID: eventID))
if let toggleReactionToClosure = toggleReactionToClosure {
return await toggleReactionToClosure(reaction, eventID)
} else {
return toggleReactionToReturnValue
}
}
//MARK: - createPoll
var createPollQuestionAnswersPollKindCallsCount = 0
var createPollQuestionAnswersPollKindCalled: Bool {
return createPollQuestionAnswersPollKindCallsCount > 0
}
var createPollQuestionAnswersPollKindReceivedArguments: (question: String, answers: [String], pollKind: Poll.Kind)?
var createPollQuestionAnswersPollKindReceivedInvocations: [(question: String, answers: [String], pollKind: Poll.Kind)] = []
var createPollQuestionAnswersPollKindReturnValue: Result<Void, TimelineProxyError>!
var createPollQuestionAnswersPollKindClosure: ((String, [String], Poll.Kind) async -> Result<Void, TimelineProxyError>)?
func createPoll(question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, TimelineProxyError> {
createPollQuestionAnswersPollKindCallsCount += 1
createPollQuestionAnswersPollKindReceivedArguments = (question: question, answers: answers, pollKind: pollKind)
createPollQuestionAnswersPollKindReceivedInvocations.append((question: question, answers: answers, pollKind: pollKind))
if let createPollQuestionAnswersPollKindClosure = createPollQuestionAnswersPollKindClosure {
return await createPollQuestionAnswersPollKindClosure(question, answers, pollKind)
} else {
return createPollQuestionAnswersPollKindReturnValue
}
}
//MARK: - editPoll
var editPollOriginalQuestionAnswersPollKindCallsCount = 0
var editPollOriginalQuestionAnswersPollKindCalled: Bool {
return editPollOriginalQuestionAnswersPollKindCallsCount > 0
}
var editPollOriginalQuestionAnswersPollKindReceivedArguments: (eventID: String, question: String, answers: [String], pollKind: Poll.Kind)?
var editPollOriginalQuestionAnswersPollKindReceivedInvocations: [(eventID: String, question: String, answers: [String], pollKind: Poll.Kind)] = []
var editPollOriginalQuestionAnswersPollKindReturnValue: Result<Void, TimelineProxyError>!
var editPollOriginalQuestionAnswersPollKindClosure: ((String, String, [String], Poll.Kind) async -> Result<Void, TimelineProxyError>)?
func editPoll(original eventID: String, question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, TimelineProxyError> {
editPollOriginalQuestionAnswersPollKindCallsCount += 1
editPollOriginalQuestionAnswersPollKindReceivedArguments = (eventID: eventID, question: question, answers: answers, pollKind: pollKind)
editPollOriginalQuestionAnswersPollKindReceivedInvocations.append((eventID: eventID, question: question, answers: answers, pollKind: pollKind))
if let editPollOriginalQuestionAnswersPollKindClosure = editPollOriginalQuestionAnswersPollKindClosure {
return await editPollOriginalQuestionAnswersPollKindClosure(eventID, question, answers, pollKind)
} else {
return editPollOriginalQuestionAnswersPollKindReturnValue
}
}
//MARK: - endPoll
var endPollPollStartIDTextCallsCount = 0
var endPollPollStartIDTextCalled: Bool {
return endPollPollStartIDTextCallsCount > 0
}
var endPollPollStartIDTextReceivedArguments: (pollStartID: String, text: String)?
var endPollPollStartIDTextReceivedInvocations: [(pollStartID: String, text: String)] = []
var endPollPollStartIDTextReturnValue: Result<Void, TimelineProxyError>!
var endPollPollStartIDTextClosure: ((String, String) async -> Result<Void, TimelineProxyError>)?
func endPoll(pollStartID: String, text: String) async -> Result<Void, TimelineProxyError> {
endPollPollStartIDTextCallsCount += 1
endPollPollStartIDTextReceivedArguments = (pollStartID: pollStartID, text: text)
endPollPollStartIDTextReceivedInvocations.append((pollStartID: pollStartID, text: text))
if let endPollPollStartIDTextClosure = endPollPollStartIDTextClosure {
return await endPollPollStartIDTextClosure(pollStartID, text)
} else {
return endPollPollStartIDTextReturnValue
}
}
//MARK: - sendPollResponse
var sendPollResponsePollStartIDAnswersCallsCount = 0
var sendPollResponsePollStartIDAnswersCalled: Bool {
return sendPollResponsePollStartIDAnswersCallsCount > 0
}
var sendPollResponsePollStartIDAnswersReceivedArguments: (pollStartID: String, answers: [String])?
var sendPollResponsePollStartIDAnswersReceivedInvocations: [(pollStartID: String, answers: [String])] = []
var sendPollResponsePollStartIDAnswersReturnValue: Result<Void, TimelineProxyError>!
var sendPollResponsePollStartIDAnswersClosure: ((String, [String]) async -> Result<Void, TimelineProxyError>)?
func sendPollResponse(pollStartID: String, answers: [String]) async -> Result<Void, TimelineProxyError> {
sendPollResponsePollStartIDAnswersCallsCount += 1
sendPollResponsePollStartIDAnswersReceivedArguments = (pollStartID: pollStartID, answers: answers)
sendPollResponsePollStartIDAnswersReceivedInvocations.append((pollStartID: pollStartID, answers: answers))
if let sendPollResponsePollStartIDAnswersClosure = sendPollResponsePollStartIDAnswersClosure {
return await sendPollResponsePollStartIDAnswersClosure(pollStartID, answers)
} else {
return sendPollResponsePollStartIDAnswersReturnValue
}
}
}
class UserDiscoveryServiceMock: UserDiscoveryServiceProtocol { class UserDiscoveryServiceMock: UserDiscoveryServiceProtocol {
//MARK: - searchProfiles //MARK: - searchProfiles

View File

@ -84,7 +84,7 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType,
// MARK: - Private // MARK: - Private
private func sendAttachment(mediaInfo: MediaInfo, progressSubject: CurrentValueSubject<Double, Never>) async -> Result<Void, RoomProxyError> { private func sendAttachment(mediaInfo: MediaInfo, progressSubject: CurrentValueSubject<Double, Never>) async -> Result<Void, TimelineProxyError> {
let requestHandle: ((SendAttachmentJoinHandleProtocol) -> Void) = { [weak self] handle in let requestHandle: ((SendAttachmentJoinHandleProtocol) -> Void) = { [weak self] handle in
self?.requestHandle?.cancel() self?.requestHandle?.cancel()
self?.requestHandle = handle self?.requestHandle = handle
@ -92,13 +92,13 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType,
switch mediaInfo { switch mediaInfo {
case let .image(imageURL, thumbnailURL, imageInfo): case let .image(imageURL, thumbnailURL, imageInfo):
return await roomProxy.sendImage(url: imageURL, thumbnailURL: thumbnailURL, imageInfo: imageInfo, progressSubject: progressSubject, requestHandle: requestHandle) return await roomProxy.timeline.sendImage(url: imageURL, thumbnailURL: thumbnailURL, imageInfo: imageInfo, progressSubject: progressSubject, requestHandle: requestHandle)
case let .video(videoURL, thumbnailURL, videoInfo): case let .video(videoURL, thumbnailURL, videoInfo):
return await roomProxy.sendVideo(url: videoURL, thumbnailURL: thumbnailURL, videoInfo: videoInfo, progressSubject: progressSubject, requestHandle: requestHandle) return await roomProxy.timeline.sendVideo(url: videoURL, thumbnailURL: thumbnailURL, videoInfo: videoInfo, progressSubject: progressSubject, requestHandle: requestHandle)
case let .audio(audioURL, audioInfo): case let .audio(audioURL, audioInfo):
return await roomProxy.sendAudio(url: audioURL, audioInfo: audioInfo, progressSubject: progressSubject, requestHandle: requestHandle) return await roomProxy.timeline.sendAudio(url: audioURL, audioInfo: audioInfo, progressSubject: progressSubject, requestHandle: requestHandle)
case let .file(fileURL, fileInfo): case let .file(fileURL, fileInfo):
return await roomProxy.sendFile(url: fileURL, fileInfo: fileInfo, progressSubject: progressSubject, requestHandle: requestHandle) return await roomProxy.timeline.sendFile(url: fileURL, fileInfo: fileInfo, progressSubject: progressSubject, requestHandle: requestHandle)
} }
} }

View File

@ -274,7 +274,7 @@ class RoomScreenInteractionHandler {
func sendPollResponse(pollStartID: String, optionID: String) { func sendPollResponse(pollStartID: String, optionID: String) {
Task { Task {
let sendPollResponseResult = await roomProxy.sendPollResponse(pollStartID: pollStartID, answers: [optionID]) let sendPollResponseResult = await roomProxy.timeline.sendPollResponse(pollStartID: pollStartID, answers: [optionID])
analyticsService.trackPollVote() analyticsService.trackPollVote()
switch sendPollResponseResult { switch sendPollResponseResult {
@ -288,8 +288,8 @@ class RoomScreenInteractionHandler {
func endPoll(pollStartID: String) { func endPoll(pollStartID: String) {
Task { Task {
let endPollResult = await roomProxy.endPoll(pollStartID: pollStartID, let endPollResult = await roomProxy.timeline.endPoll(pollStartID: pollStartID,
text: "The poll with event id: \(pollStartID) has ended") text: "The poll with event id: \(pollStartID) has ended")
analyticsService.trackPollEnd() analyticsService.trackPollEnd()
switch endPollResult { switch endPollResult {
case .success: case .success:

View File

@ -23,48 +23,35 @@ import MatrixRustSDK
class RoomProxy: RoomProxyProtocol { class RoomProxy: RoomProxyProtocol {
private let roomListItem: RoomListItemProtocol private let roomListItem: RoomListItemProtocol
private let room: RoomProtocol private let room: RoomProtocol
let timeline: TimelineProxyProtocol
let pollHistoryTimeline: TimelineProxyProtocol
private let backgroundTaskService: BackgroundTaskServiceProtocol private let backgroundTaskService: BackgroundTaskServiceProtocol
private let backgroundTaskName = "SendRoomEvent" private let backgroundTaskName = "SendRoomEvent"
private let messageSendingDispatchQueue = DispatchQueue(label: "io.element.elementx.roomproxy.message_sending", qos: .userInitiated)
private let userInitiatedDispatchQueue = DispatchQueue(label: "io.element.elementx.roomproxy.user_initiated", qos: .userInitiated) private let userInitiatedDispatchQueue = DispatchQueue(label: "io.element.elementx.roomproxy.user_initiated", qos: .userInitiated)
private let lowPriorityDispatchQueue = DispatchQueue(label: "io.element.elementx.roomproxy.low_priority", qos: .utility) private let lowPriorityDispatchQueue = DispatchQueue(label: "io.element.elementx.roomproxy.low_priority", qos: .utility)
private var sendMessageBackgroundTask: BackgroundTaskProtocol? private var sendMessageBackgroundTask: BackgroundTaskProtocol?
private(set) var displayName: String? private(set) var displayName: String?
private var roomTimelineObservationToken: TaskHandle?
private var backPaginationStateObservationToken: TaskHandle?
private var roomInfoObservationToken: TaskHandle? private var roomInfoObservationToken: TaskHandle?
private var subscribedForUpdates = false
private let backPaginationStateSubject = PassthroughSubject<BackPaginationStatus, Never>()
private let membersSubject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([]) private let membersSubject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([])
var members: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> { var members: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> {
membersSubject.asCurrentValuePublisher() membersSubject.asCurrentValuePublisher()
} }
private var timelineListener: RoomTimelineListener?
private let timelineUpdatesSubject = PassthroughSubject<[TimelineDiff], Never>()
private let stateUpdatesSubject = PassthroughSubject<Void, Never>() private let stateUpdatesSubject = PassthroughSubject<Void, Never>()
var stateUpdatesPublisher: AnyPublisher<Void, Never> { var stateUpdatesPublisher: AnyPublisher<Void, Never> {
stateUpdatesSubject.eraseToAnyPublisher() stateUpdatesSubject.eraseToAnyPublisher()
} }
var innerTimelineProvider: RoomTimelineProviderProtocol!
var timelineProvider: RoomTimelineProviderProtocol {
innerTimelineProvider
}
var ownUserID: String { var ownUserID: String {
room.ownUserId() room.ownUserId()
} }
deinit { deinit {
roomTimelineObservationToken?.cancel()
backPaginationStateObservationToken?.cancel()
roomListItem.unsubscribe() roomListItem.unsubscribe()
} }
@ -74,19 +61,25 @@ class RoomProxy: RoomProxyProtocol {
self.roomListItem = roomListItem self.roomListItem = roomListItem
self.room = room self.room = room
self.backgroundTaskService = backgroundTaskService self.backgroundTaskService = backgroundTaskService
timeline = await TimelineProxy(timeline: room.timeline(), backgroundTaskService: backgroundTaskService)
pollHistoryTimeline = await TimelineProxy(timeline: room.pollHistory(), backgroundTaskService: backgroundTaskService)
Task { Task {
await fetchMembers() // Force the timeline to load member details so it can populate sender profiles whenever we add a timeline listener
// This should become automatic on the RustSDK side at some point
await room.timeline().fetchMembers()
await updateMembers() await updateMembers()
} }
} }
func subscribeForUpdates() async { func subscribeForUpdates() async {
guard innerTimelineProvider == nil else { guard !subscribedForUpdates else {
MXLog.warning("Room already subscribed for updates") MXLog.warning("Room already subscribed for updates")
return return
} }
subscribedForUpdates = true
let settings = RoomSubscription(requiredState: [RequiredState(key: "m.room.name", value: ""), let settings = RoomSubscription(requiredState: [RequiredState(key: "m.room.name", value: ""),
RequiredState(key: "m.room.topic", value: ""), RequiredState(key: "m.room.topic", value: ""),
RequiredState(key: "m.room.avatar", value: ""), RequiredState(key: "m.room.avatar", value: ""),
@ -95,26 +88,10 @@ class RoomProxy: RoomProxyProtocol {
timelineLimit: UInt32(SlidingSyncConstants.defaultTimelineLimit)) timelineLimit: UInt32(SlidingSyncConstants.defaultTimelineLimit))
roomListItem.subscribe(settings: settings) roomListItem.subscribe(settings: settings)
let timelineListener = RoomTimelineListener { [weak self] timelineDiffs in await timeline.subscribeForUpdates()
self?.timelineUpdatesSubject.send(timelineDiffs) await pollHistoryTimeline.subscribeForUpdates()
// Workaround for subscribeToRoomStateUpdates creating problems in the timeline
// https://github.com/matrix-org/matrix-rust-sdk/issues/2488
self?.stateUpdatesSubject.send()
}
self.timelineListener = timelineListener subscribeToRoomStateUpdates()
let result = await room.addTimelineListener(listener: timelineListener)
roomTimelineObservationToken = result.itemsStream
subscribeToBackpagination()
// subscribeToRoomStateUpdates()
innerTimelineProvider = await RoomTimelineProvider(currentItems: result.items,
updatePublisher: timelineUpdatesSubject.eraseToAnyPublisher(),
backPaginationStatePublisher: backPaginationStateSubject.eraseToAnyPublisher())
} }
lazy var id: String = room.id() lazy var id: String = room.id()
@ -216,282 +193,6 @@ class RoomProxy: RoomProxyProtocol {
return .failure(.failedRetrievingMemberDisplayName) return .failure(.failedRetrievingMemberDisplayName)
} }
} }
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomProxyError> {
do {
try await Task.dispatch(on: .global()) {
try self.room.paginateBackwards(opts: .untilNumItems(eventLimit: UInt16(requestSize), items: UInt16(untilNumberOfItems), waitForToken: true))
}
return .success(())
} catch {
return .failure(.failedPaginatingBackwards)
}
}
func sendReadReceipt(for eventID: String) async -> Result<Void, RoomProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
return await Task.dispatch(on: lowPriorityDispatchQueue) {
do {
try self.room.sendReadReceipt(eventId: eventID)
return .success(())
} catch {
return .failure(.failedSendingReadReceipt)
}
}
}
func messageEventContent(for eventID: String) -> RoomMessageEventContentWithoutRelation? {
try? room.getTimelineEventContentByEventId(eventId: eventID)
}
func sendMessageEventContent(_ messageContent: RoomMessageEventContentWithoutRelation) async -> Result<Void, RoomProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
return await Task.dispatch(on: messageSendingDispatchQueue) {
self.room.send(msg: messageContent)
return .success(())
}
}
func sendMessage(_ message: String,
html: String?,
inReplyTo eventID: String? = nil,
intentionalMentions: IntentionalMentions) async -> Result<Void, RoomProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
let messageContent = buildMessageContentFor(message,
html: html,
intentionalMentions: intentionalMentions.toRustMentions())
return await Task.dispatch(on: messageSendingDispatchQueue) {
do {
if let eventID {
let replyItem = try self.room.getEventTimelineItemByEventId(eventId: eventID)
try self.room.sendReply(msg: messageContent, replyItem: replyItem)
} else {
self.room.send(msg: messageContent)
}
} catch {
return .failure(.failedSendingMessage)
}
return .success(())
}
}
func toggleReaction(_ reaction: String, to eventID: String) async -> Result<Void, RoomProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
return await Task.dispatch(on: userInitiatedDispatchQueue) {
do {
try self.room.toggleReaction(eventId: eventID, key: reaction)
return .success(())
} catch {
return .failure(.failedSendingReaction)
}
}
}
func sendImage(url: URL,
thumbnailURL: URL,
imageInfo: ImageInfo,
progressSubject: CurrentValueSubject<Double, Never>?,
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, RoomProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
let handle = room.sendImage(url: url.path(percentEncoded: false), thumbnailUrl: thumbnailURL.path(percentEncoded: false), imageInfo: imageInfo, progressWatcher: UploadProgressListener { progress in
progressSubject?.send(progress)
})
await requestHandle(handle)
do {
try await handle.join()
} catch {
return .failure(.failedSendingMedia)
}
return .success(())
}
func sendVideo(url: URL,
thumbnailURL: URL,
videoInfo: VideoInfo,
progressSubject: CurrentValueSubject<Double, Never>?,
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, RoomProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
let handle = room.sendVideo(url: url.path(percentEncoded: false), thumbnailUrl: thumbnailURL.path(percentEncoded: false), videoInfo: videoInfo, progressWatcher: UploadProgressListener { progress in
progressSubject?.send(progress)
})
await requestHandle(handle)
do {
try await handle.join()
} catch {
return .failure(.failedSendingMedia)
}
return .success(())
}
func sendAudio(url: URL,
audioInfo: AudioInfo,
progressSubject: CurrentValueSubject<Double, Never>?,
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, RoomProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
let handle = room.sendAudio(url: url.path(percentEncoded: false), audioInfo: audioInfo, progressWatcher: UploadProgressListener { progress in
progressSubject?.send(progress)
})
await requestHandle(handle)
do {
try await handle.join()
} catch {
return .failure(.failedSendingMedia)
}
return .success(())
}
func sendVoiceMessage(url: URL,
audioInfo: AudioInfo,
waveform: [UInt16],
progressSubject: CurrentValueSubject<Double, Never>?,
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, RoomProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
let handle = room.sendVoiceMessage(url: url.path(percentEncoded: false), audioInfo: audioInfo, waveform: waveform, progressWatcher: UploadProgressListener { progress in
progressSubject?.send(progress)
})
await requestHandle(handle)
do {
try await handle.join()
} catch {
return .failure(.failedSendingMedia)
}
return .success(())
}
func sendFile(url: URL,
fileInfo: FileInfo,
progressSubject: CurrentValueSubject<Double, Never>?,
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, RoomProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
let handle = room.sendFile(url: url.path(percentEncoded: false), fileInfo: fileInfo, progressWatcher: UploadProgressListener { progress in
progressSubject?.send(progress)
})
await requestHandle(handle)
do {
try await handle.join()
} catch {
return .failure(.failedSendingMedia)
}
return .success(())
}
func sendLocation(body: String,
geoURI: GeoURI,
description: String?,
zoomLevel: UInt8?,
assetType: AssetType?) async -> Result<Void, RoomProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
return await Task.dispatch(on: messageSendingDispatchQueue) {
.success(self.room.sendLocation(body: body,
geoUri: geoURI.string,
description: description,
zoomLevel: zoomLevel,
assetType: assetType))
}
}
func retrySend(transactionID: String) async {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
return await Task.dispatch(on: messageSendingDispatchQueue) {
self.room.retrySend(txnId: transactionID)
}
}
func cancelSend(transactionID: String) async {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
return await Task.dispatch(on: messageSendingDispatchQueue) {
self.room.cancelSend(txnId: transactionID)
}
}
func editMessage(_ message: String,
html: String?,
original eventID: String,
intentionalMentions: IntentionalMentions) async -> Result<Void, RoomProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
let messageContent = buildMessageContentFor(message,
html: html,
intentionalMentions: intentionalMentions.toRustMentions())
return await Task.dispatch(on: messageSendingDispatchQueue) {
do {
let originalEvent = try self.room.getEventTimelineItemByEventId(eventId: eventID)
try self.room.edit(newContent: messageContent, editItem: originalEvent)
return .success(())
} catch {
return .failure(.failedEditingMessage)
}
}
}
func redact(_ eventID: String) async -> Result<Void, RoomProxyError> { func redact(_ eventID: String) async -> Result<Void, RoomProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
@ -572,12 +273,6 @@ class RoomProxy: RoomProxyProtocol {
} }
} }
func retryDecryption(for sessionID: String) async {
await Task.dispatch(on: .global()) { [weak self] in
self?.room.retryDecryption(sessionIds: [sessionID])
}
}
func leaveRoom() async -> Result<Void, RoomProxyError> { func leaveRoom() async -> Result<Void, RoomProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer { defer {
@ -626,19 +321,6 @@ class RoomProxy: RoomProxyProtocol {
} }
} }
func fetchDetails(for eventID: String) {
Task {
await Task.dispatch(on: .global()) {
do {
MXLog.info("Fetching event details for \(eventID)")
try self.room.fetchDetailsForEvent(eventId: eventID)
} catch {
MXLog.error("Failed fetching event details for \(eventID) with error: \(error)")
}
}
}
}
func invite(userID: String) async -> Result<Void, RoomProxyError> { func invite(userID: String) async -> Result<Void, RoomProxyError> {
await Task.dispatch(on: .global()) { await Task.dispatch(on: .global()) {
do { do {
@ -713,56 +395,6 @@ class RoomProxy: RoomProxyProtocol {
return .failure(.failedCheckingPermission) return .failure(.failedCheckingPermission)
} }
} }
// MARK: - Polls
func createPoll(question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, RoomProxyError> {
await Task.dispatch(on: .global()) {
do {
return try .success(self.room.createPoll(question: question, answers: answers, maxSelections: 1, pollKind: .init(pollKind: pollKind)))
} catch {
MXLog.error("Failed creating a poll: \(error)")
return .failure(.failedCreatingPoll)
}
}
}
func editPoll(original eventID: String,
question: String,
answers: [String],
pollKind: Poll.Kind) async -> Result<Void, RoomProxyError> {
await Task.dispatch(on: .global()) {
do {
let originalEvent = try self.room.getEventTimelineItemByEventId(eventId: eventID)
return try .success(self.room.editPoll(question: question, answers: answers, maxSelections: 1, pollKind: .init(pollKind: pollKind), editItem: originalEvent))
} catch {
MXLog.error("Failed editing the poll: \(error), eventID: \(eventID)")
return .failure(.failedEditingPoll)
}
}
}
func sendPollResponse(pollStartID: String, answers: [String]) async -> Result<Void, RoomProxyError> {
await Task.dispatch(on: .global()) {
do {
return try .success(self.room.sendPollResponse(pollStartId: pollStartID, answers: answers))
} catch {
MXLog.error("Failed sending a poll vote: \(error), pollStartID: \(pollStartID)")
return .failure(.failedSendingPollResponse)
}
}
}
func endPoll(pollStartID: String, text: String) async -> Result<Void, RoomProxyError> {
await Task.dispatch(on: .global()) {
do {
return try .success(self.room.endPoll(pollStartId: pollStartID, text: text))
} catch {
MXLog.error("Failed ending a poll: \(error), pollStartID: \(pollStartID)")
return .failure(.failedEndingPoll)
}
}
}
// MARK: - Element Call // MARK: - Element Call
@ -771,64 +403,6 @@ class RoomProxy: RoomProxyProtocol {
} }
// MARK: - Private // MARK: - Private
private func buildMessageContentFor(_ message: String,
html: String?,
intentionalMentions: Mentions) -> RoomMessageEventContentWithoutRelation {
let emoteSlashCommand = "/me "
let isEmote: Bool = message.starts(with: emoteSlashCommand)
let content: RoomMessageEventContentWithoutRelation
if isEmote {
let emoteMessage = String(message.dropFirst(emoteSlashCommand.count))
var emoteHtml: String?
if let html {
emoteHtml = String(html.dropFirst(emoteSlashCommand.count))
}
content = buildEmoteMessageContentFor(emoteMessage, html: emoteHtml)
} else {
if let html {
content = messageEventContentFromHtml(body: message, htmlBody: html)
} else {
content = messageEventContentFromMarkdown(md: message)
}
}
return content.withMentions(mentions: intentionalMentions)
}
private func buildEmoteMessageContentFor(_ message: String, html: String?) -> RoomMessageEventContentWithoutRelation {
if let html {
return messageEventContentFromHtmlAsEmote(body: message, htmlBody: html)
} else {
return messageEventContentFromMarkdownAsEmote(md: message)
}
}
/// Force the timeline to load member details so it can populate sender profiles whenever we add a timeline listener
/// This should become automatic on the RustSDK side at some point
private func fetchMembers() async {
do {
try await room.fetchMembers()
} catch {
MXLog.error("Failed fetching members: \(error)")
}
}
private func update(displayName: String) {
self.displayName = displayName
}
private func subscribeToBackpagination() {
let listener = RoomBackpaginationStatusListener { [weak self] status in
self?.backPaginationStateSubject.send(status)
}
do {
backPaginationStateObservationToken = try room.subscribeToBackPaginationStatus(listener: listener)
} catch {
MXLog.error("Failed to subscribe to back pagination state with error: \(error)")
}
}
private func subscribeToRoomStateUpdates() { private func subscribeToRoomStateUpdates() {
roomInfoObservationToken = room.subscribeToRoomInfoUpdates(listener: RoomInfoUpdateListener { [weak self] in roomInfoObservationToken = room.subscribeToRoomInfoUpdates(listener: RoomInfoUpdateListener { [weak self] in
@ -838,44 +412,6 @@ class RoomProxy: RoomProxyProtocol {
} }
} }
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 UploadProgressListener: ProgressWatcher {
private let onUpdateClosure: (Double) -> Void
init(_ onUpdateClosure: @escaping (Double) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func transmissionProgress(progress: TransmissionProgress) {
DispatchQueue.main.async { [weak self] in
self?.onUpdateClosure(Double(progress.current) / Double(progress.total))
}
}
}
private final class RoomBackpaginationStatusListener: BackPaginationStatusListener {
private let onUpdateClosure: (BackPaginationStatus) -> Void
init(_ onUpdateClosure: @escaping (BackPaginationStatus) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func onUpdate(status: BackPaginationStatus) {
onUpdateClosure(status)
}
}
private final class RoomInfoUpdateListener: RoomInfoListener { private final class RoomInfoUpdateListener: RoomInfoListener {
private let onUpdateClosure: () -> Void private let onUpdateClosure: () -> Void
@ -887,14 +423,3 @@ private final class RoomInfoUpdateListener: RoomInfoListener {
onUpdateClosure() onUpdateClosure()
} }
} }
private extension MatrixRustSDK.PollKind {
init(pollKind: Poll.Kind) {
switch pollKind {
case .disclosed:
self = .disclosed
case .undisclosed:
self = .undisclosed
}
}
}

View File

@ -19,19 +19,10 @@ import Foundation
import MatrixRustSDK import MatrixRustSDK
enum RoomProxyError: Error, Equatable { enum RoomProxyError: Error, Equatable {
case noMoreMessagesToBackPaginate
case failedPaginatingBackwards
case failedRetrievingMemberAvatarURL case failedRetrievingMemberAvatarURL
case failedRetrievingMemberDisplayName case failedRetrievingMemberDisplayName
case failedSendingReadReceipt
case failedSendingMessage
case failedSendingReaction
case failedSendingMedia
case failedEditingMessage
case failedRedactingEvent case failedRedactingEvent
case failedReportingContent case failedReportingContent
case failedAddingTimelineListener
case failedRetrievingMembers
case failedRetrievingMember case failedRetrievingMember
case failedLeavingRoom case failedLeavingRoom
case failedAcceptingInvite case failedAcceptingInvite
@ -42,10 +33,6 @@ enum RoomProxyError: Error, Equatable {
case failedRemovingAvatar case failedRemovingAvatar
case failedUploadingAvatar case failedUploadingAvatar
case failedCheckingPermission case failedCheckingPermission
case failedCreatingPoll
case failedSendingPollResponse
case failedEndingPoll
case failedEditingPoll
} }
// sourcery: AutoMockable // sourcery: AutoMockable
@ -82,7 +69,10 @@ protocol RoomProxyProtocol {
/// The thread on which this publisher sends the output isn't defined. /// The thread on which this publisher sends the output isn't defined.
var stateUpdatesPublisher: AnyPublisher<Void, Never> { get } var stateUpdatesPublisher: AnyPublisher<Void, Never> { get }
var timelineProvider: RoomTimelineProviderProtocol { get } var timeline: TimelineProxyProtocol { get }
/// A timeline providing just polls related events
var pollHistoryTimeline: TimelineProxyProtocol { get }
func subscribeForUpdates() async func subscribeForUpdates() async
@ -90,73 +80,11 @@ protocol RoomProxyProtocol {
func loadDisplayNameForUserId(_ userId: String) async -> Result<String?, RoomProxyError> func loadDisplayNameForUserId(_ userId: String) async -> Result<String?, RoomProxyError>
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomProxyError>
func sendReadReceipt(for eventID: String) async -> Result<Void, RoomProxyError>
func messageEventContent(for eventID: String) -> RoomMessageEventContentWithoutRelation?
func sendMessageEventContent(_ messageContent: RoomMessageEventContentWithoutRelation) async -> Result<Void, RoomProxyError>
func sendMessage(_ message: String,
html: String?,
inReplyTo eventID: String?,
intentionalMentions: IntentionalMentions) async -> Result<Void, RoomProxyError>
func toggleReaction(_ reaction: String, to eventID: String) async -> Result<Void, RoomProxyError>
func sendImage(url: URL,
thumbnailURL: URL,
imageInfo: ImageInfo,
progressSubject: CurrentValueSubject<Double, Never>?,
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, RoomProxyError>
func sendVideo(url: URL,
thumbnailURL: URL,
videoInfo: VideoInfo,
progressSubject: CurrentValueSubject<Double, Never>?,
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, RoomProxyError>
func sendAudio(url: URL,
audioInfo: AudioInfo,
progressSubject: CurrentValueSubject<Double, Never>?,
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, RoomProxyError>
func sendFile(url: URL,
fileInfo: FileInfo,
progressSubject: CurrentValueSubject<Double, Never>?,
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, RoomProxyError>
func sendLocation(body: String,
geoURI: GeoURI,
description: String?,
zoomLevel: UInt8?,
assetType: AssetType?) async -> Result<Void, RoomProxyError>
func sendVoiceMessage(url: URL,
audioInfo: AudioInfo,
waveform: [UInt16],
progressSubject: CurrentValueSubject<Double, Never>?,
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, RoomProxyError>
/// Retries sending a failed message given its transaction ID
func retrySend(transactionID: String) async
/// Cancels a failed message given its transaction ID from the timeline
func cancelSend(transactionID: String) async
func editMessage(_ newMessage: String,
html: String?,
original eventID: String,
intentionalMentions: IntentionalMentions) async -> Result<Void, RoomProxyError>
func redact(_ eventID: String) async -> Result<Void, RoomProxyError> func redact(_ eventID: String) async -> Result<Void, RoomProxyError>
func reportContent(_ eventID: String, reason: String?) async -> Result<Void, RoomProxyError> func reportContent(_ eventID: String, reason: String?) async -> Result<Void, RoomProxyError>
func ignoreUser(_ userID: String) async -> Result<Void, RoomProxyError> func ignoreUser(_ userID: String) async -> Result<Void, RoomProxyError>
func retryDecryption(for sessionID: String) async
func leaveRoom() async -> Result<Void, RoomProxyError> func leaveRoom() async -> Result<Void, RoomProxyError>
@ -170,8 +98,6 @@ protocol RoomProxyProtocol {
func acceptInvitation() async -> Result<Void, RoomProxyError> func acceptInvitation() async -> Result<Void, RoomProxyError>
func fetchDetails(for eventID: String)
func invite(userID: String) async -> Result<Void, RoomProxyError> func invite(userID: String) async -> Result<Void, RoomProxyError>
func setName(_ name: String) async -> Result<Void, RoomProxyError> func setName(_ name: String) async -> Result<Void, RoomProxyError>
@ -185,14 +111,6 @@ protocol RoomProxyProtocol {
func canUserRedact(userID: String) async -> Result<Bool, RoomProxyError> func canUserRedact(userID: String) async -> Result<Bool, RoomProxyError>
func canUserTriggerRoomNotification(userID: String) async -> Result<Bool, RoomProxyError> func canUserTriggerRoomNotification(userID: String) async -> Result<Bool, RoomProxyError>
func createPoll(question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, RoomProxyError>
func editPoll(original eventID: String, question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, RoomProxyError>
func sendPollResponse(pollStartID: String, answers: [String]) async -> Result<Void, RoomProxyError>
func endPoll(pollStartID: String, text: String) async -> Result<Void, RoomProxyError>
// MARK: - Element Call // MARK: - Element Call
@ -213,15 +131,6 @@ extension RoomProxyProtocol {
} }
} }
func sendMessage(_ message: String,
html: String?,
intentionalMentions: IntentionalMentions) async -> Result<Void, RoomProxyError> {
await sendMessage(message,
html: html,
inReplyTo: nil,
intentionalMentions: intentionalMentions)
}
// Avoids to duplicate the same logic around in the app // Avoids to duplicate the same logic around in the app
// Probably this should be done in rust. // Probably this should be done in rust.
var roomTitle: String { var roomTitle: String {

View File

@ -96,22 +96,21 @@ class SecureBackupController: SecureBackupControllerProtocol {
return .success(key) return .success(key)
} }
var keyUploadErrored = false
let recoveryKey = try await encryption.enableRecovery(waitForBackupsToUpload: false, progressListener: SecureBackupEnableRecoveryProgressListener { [weak self] state in let recoveryKey = try await encryption.enableRecovery(waitForBackupsToUpload: false, progressListener: SecureBackupEnableRecoveryProgressListener { [weak self] state in
guard let self else { return } guard let self else { return }
switch state { switch state {
case .creatingBackup: case .starting, .creatingBackup, .creatingRecoveryKey, .backingUp:
recoveryKeyStateSubject.send(.settingUp)
case .creatingRecoveryKey:
recoveryKeyStateSubject.send(.settingUp)
case .backingUp:
recoveryKeyStateSubject.send(.settingUp) recoveryKeyStateSubject.send(.settingUp)
case .done: case .done:
recoveryKeyStateSubject.send(.enabled) recoveryKeyStateSubject.send(.enabled)
case .roomKeyUploadError:
keyUploadErrored = true
} }
}) })
return .success(recoveryKey) return keyUploadErrored ? .failure(.failedGeneratingRecoveryKey) : .success(recoveryKey)
} catch { } catch {
return .failure(.failedGeneratingRecoveryKey) return .failure(.failedGeneratingRecoveryKey)
} }
@ -119,7 +118,7 @@ class SecureBackupController: SecureBackupControllerProtocol {
func confirmRecoveryKey(_ key: String) async -> Result<Void, SecureBackupControllerError> { func confirmRecoveryKey(_ key: String) async -> Result<Void, SecureBackupControllerError> {
do { do {
try await encryption.fixRecoveryIssues(recoveryKey: key) try await encryption.recover(recoveryKey: key)
return .success(()) return .success(())
} catch { } catch {
return .failure(.failedConfirmingRecoveryKey) return .failure(.failedConfirmingRecoveryKey)
@ -143,7 +142,7 @@ class SecureBackupController: SecureBackupControllerProtocol {
case .BackupDisabled: case .BackupDisabled:
MXLog.error("Key backup disabled, continuing logout.") MXLog.error("Key backup disabled, continuing logout.")
return .success(()) return .success(())
case .Connection, .Laged: case .Connection, .Lagged:
MXLog.error("Key backup upload failure: \(error)") MXLog.error("Key backup upload failure: \(error)")
return .failure(.failedUploadingForBackup) return .failure(.failedUploadingForBackup)
} }

View File

@ -52,7 +52,7 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
func sendReadReceipt(for itemID: TimelineItemIdentifier) async -> Result<Void, RoomTimelineControllerError> { func sendReadReceipt(for itemID: TimelineItemIdentifier) async -> Result<Void, RoomTimelineControllerError> {
guard let roomProxy, let eventID = itemID.eventID else { return .failure(.generic) } guard let roomProxy, let eventID = itemID.eventID else { return .failure(.generic) }
switch await roomProxy.sendReadReceipt(for: eventID) { switch await roomProxy.timeline.sendReadReceipt(for: eventID) {
case .success: case .success:
return .success(()) return .success(())
case .failure: case .failure:
@ -89,7 +89,7 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
return return
} }
await roomProxy?.retrySend(transactionID: transactionID) await roomProxy?.timeline.retrySend(transactionID: transactionID)
} }
func cancelSending(itemID: TimelineItemIdentifier) async { func cancelSending(itemID: TimelineItemIdentifier) async {
@ -97,7 +97,7 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
return return
} }
await roomProxy?.cancelSend(transactionID: transactionID) await roomProxy?.timeline.cancelSend(transactionID: transactionID)
} }
// MARK: - UI Test signalling // MARK: - UI Test signalling

View File

@ -46,7 +46,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
appSettings: AppSettings, appSettings: AppSettings,
secureBackupController: SecureBackupControllerProtocol) { secureBackupController: SecureBackupControllerProtocol) {
self.roomProxy = roomProxy self.roomProxy = roomProxy
timelineProvider = roomProxy.timelineProvider timelineProvider = roomProxy.timeline.timelineProvider
self.timelineItemFactory = timelineItemFactory self.timelineItemFactory = timelineItemFactory
self.appSettings = appSettings self.appSettings = appSettings
self.secureBackupController = secureBackupController self.secureBackupController = secureBackupController
@ -71,13 +71,10 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomTimelineControllerError> { func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomTimelineControllerError> {
MXLog.info("Started back pagination request") MXLog.info("Started back pagination request")
switch await roomProxy.paginateBackwards(requestSize: requestSize, untilNumberOfItems: untilNumberOfItems) { switch await roomProxy.timeline.paginateBackwards(requestSize: requestSize, untilNumberOfItems: untilNumberOfItems) {
case .success: case .success:
MXLog.info("Finished back pagination request") MXLog.info("Finished back pagination request")
return .success(()) return .success(())
case .failure(.noMoreMessagesToBackPaginate):
MXLog.warning("Back pagination requested when all messages have been loaded.")
return .success(())
case .failure(let error): case .failure(let error):
MXLog.error("Failed back pagination request with error: \(error)") MXLog.error("Failed back pagination request with error: \(error)")
return .failure(.generic) return .failure(.generic)
@ -89,7 +86,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
let eventID = itemID.eventID let eventID = itemID.eventID
else { return .success(()) } else { return .success(()) }
switch await roomProxy.sendReadReceipt(for: eventID) { switch await roomProxy.timeline.sendReadReceipt(for: eventID) {
case .success: case .success:
return .success(()) return .success(())
case .failure: case .failure:
@ -123,11 +120,11 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
MXLog.error("Send reply in \(roomID) failed: missing event ID") MXLog.error("Send reply in \(roomID) failed: missing event ID")
return return
} }
switch await roomProxy.sendMessage(message, switch await roomProxy.timeline.sendMessage(message,
html: html, html: html,
inReplyTo: inReplyTo, inReplyTo: inReplyTo,
intentionalMentions: intentionalMentions) { intentionalMentions: intentionalMentions) {
case .success: case .success:
MXLog.info("Finished sending message") MXLog.info("Finished sending message")
case .failure(let error): case .failure(let error):
@ -142,7 +139,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
return return
} }
switch await roomProxy.toggleReaction(reaction, to: eventID) { switch await roomProxy.timeline.toggleReaction(reaction, to: eventID) {
case .success: case .success:
MXLog.info("Finished toggling reaction") MXLog.info("Finished toggling reaction")
case .failure(let error): case .failure(let error):
@ -162,10 +159,10 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
await cancelSending(itemID: itemID) await cancelSending(itemID: itemID)
await sendMessage(newMessage, html: html, intentionalMentions: intentionalMentions) await sendMessage(newMessage, html: html, intentionalMentions: intentionalMentions)
} else if let eventID = itemID.eventID { } else if let eventID = itemID.eventID {
switch await roomProxy.editMessage(newMessage, switch await roomProxy.timeline.editMessage(newMessage,
html: html, html: html,
original: eventID, original: eventID,
intentionalMentions: intentionalMentions) { intentionalMentions: intentionalMentions) {
case .success: case .success:
MXLog.info("Finished editing message") MXLog.info("Finished editing message")
case .failure(let error): case .failure(let error):
@ -207,7 +204,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
} }
func retryDecryption(for sessionID: String) async { func retryDecryption(for sessionID: String) async {
await roomProxy.retryDecryption(for: sessionID) await roomProxy.timeline.retryDecryption(for: sessionID)
} }
func retrySending(itemID: TimelineItemIdentifier) async { func retrySending(itemID: TimelineItemIdentifier) async {
@ -217,7 +214,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
} }
MXLog.info("Retry sending in \(roomID)") MXLog.info("Retry sending in \(roomID)")
await roomProxy.retrySend(transactionID: transactionID) await roomProxy.timeline.retrySend(transactionID: transactionID)
} }
func cancelSending(itemID: TimelineItemIdentifier) async { func cancelSending(itemID: TimelineItemIdentifier) async {
@ -227,7 +224,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
} }
MXLog.info("Cancelling send in \(roomID)") MXLog.info("Cancelling send in \(roomID)")
await roomProxy.cancelSend(transactionID: transactionID) await roomProxy.timeline.cancelSend(transactionID: transactionID)
} }
// MARK: - Private // MARK: - Private
@ -378,10 +375,10 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
switch timelineItem.replyDetails { switch timelineItem.replyDetails {
case .notLoaded: case .notLoaded:
roomProxy.fetchDetails(for: eventID) roomProxy.timeline.fetchDetails(for: eventID)
case .error: case .error:
if refetchOnError { if refetchOnError {
roomProxy.fetchDetails(for: eventID) roomProxy.timeline.fetchDetails(for: eventID)
} }
default: default:
break break

View File

@ -0,0 +1,518 @@
//
// Copyright 2023 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
import Foundation
import MatrixRustSDK
final class TimelineProxy: TimelineProxyProtocol {
private let timeline: Timeline
private var sendMessageBackgroundTask: BackgroundTaskProtocol?
private let backgroundTaskService: BackgroundTaskServiceProtocol
private let backgroundTaskName = "SendRoomEvent"
private let lowPriorityDispatchQueue = DispatchQueue(label: "io.element.elementx.roomproxy.low_priority", qos: .utility)
private let messageSendingDispatchQueue = DispatchQueue(label: "io.element.elementx.roomproxy.message_sending", qos: .userInitiated)
private let userInitiatedDispatchQueue = DispatchQueue(label: "io.element.elementx.roomproxy.user_initiated", qos: .userInitiated)
init(timeline: Timeline, backgroundTaskService: BackgroundTaskServiceProtocol) {
self.timeline = timeline
self.backgroundTaskService = backgroundTaskService
}
private var backPaginationStateObservationToken: TaskHandle?
private var roomTimelineObservationToken: TaskHandle?
private var timelineListener: RoomTimelineListener?
private let backPaginationStateSubject = PassthroughSubject<BackPaginationStatus, Never>()
private let timelineUpdatesSubject = PassthroughSubject<[TimelineDiff], Never>()
private var innerTimelineProvider: RoomTimelineProviderProtocol!
var timelineProvider: RoomTimelineProviderProtocol {
innerTimelineProvider
}
var hasPendingUpdatesSubscription: Bool {
innerTimelineProvider != nil
}
deinit {
backPaginationStateObservationToken?.cancel()
roomTimelineObservationToken?.cancel()
}
func subscribeForUpdates() async {
guard innerTimelineProvider == nil else {
MXLog.warning("Timeline already subscribed for updates")
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
subscribeToBackpagination()
innerTimelineProvider = await RoomTimelineProvider(currentItems: result.items,
updatePublisher: timelineUpdatesSubject.eraseToAnyPublisher(),
backPaginationStatePublisher: backPaginationStateSubject.eraseToAnyPublisher())
}
func cancelSend(transactionID: String) async {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
return await Task.dispatch(on: messageSendingDispatchQueue) {
self.timeline.cancelSend(txnId: transactionID)
}
}
func editMessage(_ message: String,
html: String?,
original eventID: String,
intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
let messageContent = buildMessageContentFor(message,
html: html,
intentionalMentions: intentionalMentions.toRustMentions())
return await Task.dispatch(on: messageSendingDispatchQueue) {
do {
let originalEvent = try self.timeline.getEventTimelineItemByEventId(eventId: eventID)
try self.timeline.edit(newContent: messageContent, editItem: originalEvent)
return .success(())
} catch {
return .failure(.failedEditingMessage)
}
}
}
func fetchDetails(for eventID: String) {
Task {
await Task.dispatch(on: .global()) {
do {
MXLog.info("Fetching event details for \(eventID)")
try self.timeline.fetchDetailsForEvent(eventId: eventID)
} catch {
MXLog.error("Failed fetching event details for \(eventID) with error: \(error)")
}
}
}
}
func messageEventContent(for eventID: String) -> RoomMessageEventContentWithoutRelation? {
try? timeline.getTimelineEventContentByEventId(eventId: eventID)
}
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, TimelineProxyError> {
do {
try await Task.dispatch(on: .global()) {
try self.timeline.paginateBackwards(opts: .untilNumItems(eventLimit: UInt16(requestSize), items: UInt16(untilNumberOfItems), waitForToken: true))
}
return .success(())
} catch {
return .failure(.failedPaginatingBackwards)
}
}
func retryDecryption(for sessionID: String) async {
await Task.dispatch(on: .global()) { [weak self] in
self?.timeline.retryDecryption(sessionIds: [sessionID])
}
}
func retrySend(transactionID: String) async {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
return await Task.dispatch(on: messageSendingDispatchQueue) {
self.timeline.retrySend(txnId: transactionID)
}
}
func sendAudio(url: URL,
audioInfo: AudioInfo,
progressSubject: CurrentValueSubject<Double, Never>?,
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
let handle = timeline.sendAudio(url: url.path(percentEncoded: false), audioInfo: audioInfo, progressWatcher: UploadProgressListener { progress in
progressSubject?.send(progress)
})
await requestHandle(handle)
do {
try await handle.join()
} catch {
return .failure(.failedSendingMedia)
}
return .success(())
}
func sendFile(url: URL,
fileInfo: FileInfo,
progressSubject: CurrentValueSubject<Double, Never>?,
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
let handle = timeline.sendFile(url: url.path(percentEncoded: false), fileInfo: fileInfo, progressWatcher: UploadProgressListener { progress in
progressSubject?.send(progress)
})
await requestHandle(handle)
do {
try await handle.join()
} catch {
return .failure(.failedSendingMedia)
}
return .success(())
}
func sendImage(url: URL,
thumbnailURL: URL,
imageInfo: ImageInfo,
progressSubject: CurrentValueSubject<Double, Never>?,
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
let handle = timeline.sendImage(url: url.path(percentEncoded: false), thumbnailUrl: thumbnailURL.path(percentEncoded: false), imageInfo: imageInfo, progressWatcher: UploadProgressListener { progress in
progressSubject?.send(progress)
})
await requestHandle(handle)
do {
try await handle.join()
} catch {
return .failure(.failedSendingMedia)
}
return .success(())
}
func sendLocation(body: String,
geoURI: GeoURI,
description: String?,
zoomLevel: UInt8?,
assetType: AssetType?) async -> Result<Void, TimelineProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
return await Task.dispatch(on: messageSendingDispatchQueue) {
.success(self.timeline.sendLocation(body: body,
geoUri: geoURI.string,
description: description,
zoomLevel: zoomLevel,
assetType: assetType))
}
}
func sendVideo(url: URL,
thumbnailURL: URL,
videoInfo: VideoInfo,
progressSubject: CurrentValueSubject<Double, Never>?,
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
let handle = timeline.sendVideo(url: url.path(percentEncoded: false), thumbnailUrl: thumbnailURL.path(percentEncoded: false), videoInfo: videoInfo, progressWatcher: UploadProgressListener { progress in
progressSubject?.send(progress)
})
await requestHandle(handle)
do {
try await handle.join()
} catch {
return .failure(.failedSendingMedia)
}
return .success(())
}
func sendVoiceMessage(url: URL,
audioInfo: AudioInfo,
waveform: [UInt16],
progressSubject: CurrentValueSubject<Double, Never>?,
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
let handle = timeline.sendVoiceMessage(url: url.path(percentEncoded: false), audioInfo: audioInfo, waveform: waveform, progressWatcher: UploadProgressListener { progress in
progressSubject?.send(progress)
})
await requestHandle(handle)
do {
try await handle.join()
} catch {
return .failure(.failedSendingMedia)
}
return .success(())
}
func sendMessage(_ message: String,
html: String?,
inReplyTo eventID: String? = nil,
intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
let messageContent = buildMessageContentFor(message,
html: html,
intentionalMentions: intentionalMentions.toRustMentions())
return await Task.dispatch(on: messageSendingDispatchQueue) {
do {
if let eventID {
let replyItem = try self.timeline.getEventTimelineItemByEventId(eventId: eventID)
try self.timeline.sendReply(msg: messageContent, replyItem: replyItem)
} else {
self.timeline.send(msg: messageContent)
}
} catch {
return .failure(.failedSendingMessage)
}
return .success(())
}
}
func sendMessageEventContent(_ messageContent: RoomMessageEventContentWithoutRelation) async -> Result<Void, TimelineProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
return await Task.dispatch(on: messageSendingDispatchQueue) {
self.timeline.send(msg: messageContent)
return .success(())
}
}
func sendReadReceipt(for eventID: String) async -> Result<Void, TimelineProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
return await Task.dispatch(on: lowPriorityDispatchQueue) {
do {
try self.timeline.sendReadReceipt(eventId: eventID)
return .success(())
} catch {
return .failure(.failedSendingReadReceipt)
}
}
}
func toggleReaction(_ reaction: String, to eventID: String) async -> Result<Void, TimelineProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
return await Task.dispatch(on: userInitiatedDispatchQueue) {
do {
try self.timeline.toggleReaction(eventId: eventID, key: reaction)
return .success(())
} catch {
return .failure(.failedSendingReaction)
}
}
}
// MARK: - Polls
func createPoll(question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, TimelineProxyError> {
await Task.dispatch(on: .global()) {
do {
return try .success(self.timeline.createPoll(question: question, answers: answers, maxSelections: 1, pollKind: .init(pollKind: pollKind)))
} catch {
MXLog.error("Failed creating a poll: \(error)")
return .failure(.failedCreatingPoll)
}
}
}
func editPoll(original eventID: String,
question: String,
answers: [String],
pollKind: Poll.Kind) async -> Result<Void, TimelineProxyError> {
do {
let originalEvent = try await Task.dispatch(on: .global()) {
try self.timeline.getEventTimelineItemByEventId(eventId: eventID)
}
return try await .success(timeline.editPoll(question: question, answers: answers, maxSelections: 1, pollKind: .init(pollKind: pollKind), editItem: originalEvent))
} catch {
MXLog.error("Failed editing the poll: \(error), eventID: \(eventID)")
return .failure(.failedEditingPoll)
}
}
func endPoll(pollStartID: String, text: String) async -> Result<Void, TimelineProxyError> {
await Task.dispatch(on: .global()) {
do {
return try .success(self.timeline.endPoll(pollStartId: pollStartID, text: text))
} catch {
MXLog.error("Failed ending a poll: \(error), pollStartID: \(pollStartID)")
return .failure(.failedEndingPoll)
}
}
}
func sendPollResponse(pollStartID: String, answers: [String]) async -> Result<Void, TimelineProxyError> {
await Task.dispatch(on: .global()) {
do {
return try .success(self.timeline.sendPollResponse(pollStartId: pollStartID, answers: answers))
} catch {
MXLog.error("Failed sending a poll vote: \(error), pollStartID: \(pollStartID)")
return .failure(.failedSendingPollResponse)
}
}
}
// MARK: - Private
private func buildMessageContentFor(_ message: String,
html: String?,
intentionalMentions: Mentions) -> RoomMessageEventContentWithoutRelation {
let emoteSlashCommand = "/me "
let isEmote: Bool = message.starts(with: emoteSlashCommand)
let content: RoomMessageEventContentWithoutRelation
if isEmote {
let emoteMessage = String(message.dropFirst(emoteSlashCommand.count))
var emoteHtml: String?
if let html {
emoteHtml = String(html.dropFirst(emoteSlashCommand.count))
}
content = buildEmoteMessageContentFor(emoteMessage, html: emoteHtml)
} else {
if let html {
content = messageEventContentFromHtml(body: message, htmlBody: html)
} else {
content = messageEventContentFromMarkdown(md: message)
}
}
return content.withMentions(mentions: intentionalMentions)
}
private func buildEmoteMessageContentFor(_ message: String, html: String?) -> RoomMessageEventContentWithoutRelation {
if let html {
return messageEventContentFromHtmlAsEmote(body: message, htmlBody: html)
} else {
return messageEventContentFromMarkdownAsEmote(md: message)
}
}
private func subscribeToBackpagination() {
let listener = RoomBackpaginationStatusListener { [weak self] status in
self?.backPaginationStateSubject.send(status)
}
do {
backPaginationStateObservationToken = try timeline.subscribeToBackPaginationStatus(listener: listener)
} catch {
MXLog.error("Failed to subscribe to back pagination state with error: \(error)")
}
}
}
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 RoomBackpaginationStatusListener: BackPaginationStatusListener {
private let onUpdateClosure: (BackPaginationStatus) -> Void
init(_ onUpdateClosure: @escaping (BackPaginationStatus) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func onUpdate(status: BackPaginationStatus) {
onUpdateClosure(status)
}
}
private final class UploadProgressListener: ProgressWatcher {
private let onUpdateClosure: (Double) -> Void
init(_ onUpdateClosure: @escaping (Double) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func transmissionProgress(progress: TransmissionProgress) {
DispatchQueue.main.async { [weak self] in
self?.onUpdateClosure(Double(progress.current) / Double(progress.total))
}
}
}
private extension MatrixRustSDK.PollKind {
init(pollKind: Poll.Kind) {
switch pollKind {
case .disclosed:
self = .disclosed
case .undisclosed:
self = .undisclosed
}
}
}

View File

@ -0,0 +1,127 @@
//
// Copyright 2023 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
import Foundation
import MatrixRustSDK
enum TimelineProxyError: Error, Equatable {
case failedEditingMessage
case failedPaginatingBackwards
case failedSendingMessage
case failedSendingReaction
case failedSendingReadReceipt
case failedSendingMedia
// Polls
case failedCreatingPoll
case failedEditingPoll
case failedEndingPoll
case failedSendingPollResponse
}
// sourcery: AutoMockable
protocol TimelineProxyProtocol {
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<Void, TimelineProxyError>
func fetchDetails(for eventID: String)
func messageEventContent(for eventID: String) -> RoomMessageEventContentWithoutRelation?
func retryDecryption(for sessionID: String) async
/// Retries sending a failed message given its transaction ID
func retrySend(transactionID: String) async
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, TimelineProxyError>
func sendAudio(url: URL,
audioInfo: AudioInfo,
progressSubject: CurrentValueSubject<Double, Never>?,
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError>
func sendFile(url: URL,
fileInfo: FileInfo,
progressSubject: CurrentValueSubject<Double, Never>?,
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError>
func sendImage(url: URL,
thumbnailURL: URL,
imageInfo: ImageInfo,
progressSubject: CurrentValueSubject<Double, Never>?,
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError>
func sendLocation(body: String,
geoURI: GeoURI,
description: String?,
zoomLevel: UInt8?,
assetType: AssetType?) async -> Result<Void, TimelineProxyError>
func sendVideo(url: URL,
thumbnailURL: URL,
videoInfo: VideoInfo,
progressSubject: CurrentValueSubject<Double, Never>?,
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError>
func sendVoiceMessage(url: URL,
audioInfo: AudioInfo,
waveform: [UInt16],
progressSubject: CurrentValueSubject<Double, Never>?,
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError>
func sendReadReceipt(for eventID: String) async -> Result<Void, TimelineProxyError>
func sendMessageEventContent(_ messageContent: RoomMessageEventContentWithoutRelation) async -> Result<Void, TimelineProxyError>
func sendMessage(_ message: String,
html: String?,
inReplyTo eventID: String?,
intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError>
func toggleReaction(_ reaction: String, to eventID: String) async -> Result<Void, TimelineProxyError>
// Polls
func createPoll(question: String, answers: [String], pollKind: Poll.Kind) async -> Result<Void, TimelineProxyError>
func editPoll(original eventID: String,
question: String,
answers: [String],
pollKind: Poll.Kind) async -> Result<Void, TimelineProxyError>
func endPoll(pollStartID: String, text: String) async -> Result<Void, TimelineProxyError>
func sendPollResponse(pollStartID: String, answers: [String]) async -> Result<Void, TimelineProxyError>
}
extension TimelineProxyProtocol {
func sendMessage(_ message: String,
html: String?,
intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError> {
await sendMessage(message,
html: html,
inReplyTo: nil,
intentionalMentions: intentionalMentions)
}
}

View File

@ -190,10 +190,10 @@ class VoiceMessageRecorder: VoiceMessageRecorderProtocol {
return .failure(.failedSendingVoiceMessage) return .failure(.failedSendingVoiceMessage)
} }
let result = await roomProxy.sendVoiceMessage(url: oggFile, let result = await roomProxy.timeline.sendVoiceMessage(url: oggFile,
audioInfo: audioInfo, audioInfo: audioInfo,
waveform: waveform, waveform: waveform,
progressSubject: nil) { _ in } progressSubject: nil) { _ in }
if case .failure(let error) = result { if case .failure(let error) = result {
MXLog.error("Failed to send the voice message. \(error)") MXLog.error("Failed to send the voice message. \(error)")

View File

@ -332,6 +332,8 @@ class RoomScreenViewModelTests: XCTestCase {
func testRetrySend() async throws { func testRetrySend() async throws {
let timelineController = MockRoomTimelineController() let timelineController = MockRoomTimelineController()
let roomProxyMock = RoomProxyMock(with: .init(displayName: "")) let roomProxyMock = RoomProxyMock(with: .init(displayName: ""))
let timelineProxy = TimelineProxyMock()
roomProxyMock.timeline = timelineProxy
timelineController.roomProxy = roomProxyMock timelineController.roomProxy = roomProxyMock
let viewModel = RoomScreenViewModel(roomProxy: roomProxyMock, let viewModel = RoomScreenViewModel(roomProxy: roomProxyMock,
@ -349,13 +351,15 @@ class RoomScreenViewModelTests: XCTestCase {
try? await Task.sleep(for: .milliseconds(100)) try? await Task.sleep(for: .milliseconds(100))
XCTAssert(roomProxyMock.retrySendTransactionIDCallsCount == 1) XCTAssert(timelineProxy.retrySendTransactionIDCallsCount == 1)
XCTAssert(roomProxyMock.retrySendTransactionIDReceivedInvocations == ["test retry send id"]) XCTAssert(timelineProxy.retrySendTransactionIDReceivedInvocations == ["test retry send id"])
} }
func testRetrySendNoTransactionID() async { func testRetrySendNoTransactionID() async {
let timelineController = MockRoomTimelineController() let timelineController = MockRoomTimelineController()
let roomProxyMock = RoomProxyMock(with: .init(displayName: "")) let roomProxyMock = RoomProxyMock(with: .init(displayName: ""))
let timelineProxy = TimelineProxyMock()
roomProxyMock.timeline = timelineProxy
let viewModel = RoomScreenViewModel(roomProxy: roomProxyMock, let viewModel = RoomScreenViewModel(roomProxy: roomProxyMock,
timelineController: timelineController, timelineController: timelineController,
@ -372,12 +376,14 @@ class RoomScreenViewModelTests: XCTestCase {
try? await Task.sleep(for: .milliseconds(100)) try? await Task.sleep(for: .milliseconds(100))
XCTAssert(roomProxyMock.retrySendTransactionIDCallsCount == 0) XCTAssert(timelineProxy.retrySendTransactionIDCallsCount == 0)
} }
func testCancelSend() async { func testCancelSend() async {
let timelineController = MockRoomTimelineController() let timelineController = MockRoomTimelineController()
let roomProxyMock = RoomProxyMock(with: .init(displayName: "")) let roomProxyMock = RoomProxyMock(with: .init(displayName: ""))
let timelineProxy = TimelineProxyMock()
roomProxyMock.timeline = timelineProxy
timelineController.roomProxy = roomProxyMock timelineController.roomProxy = roomProxyMock
let viewModel = RoomScreenViewModel(roomProxy: roomProxyMock, let viewModel = RoomScreenViewModel(roomProxy: roomProxyMock,
@ -395,13 +401,15 @@ class RoomScreenViewModelTests: XCTestCase {
try? await Task.sleep(for: .milliseconds(100)) try? await Task.sleep(for: .milliseconds(100))
XCTAssert(roomProxyMock.cancelSendTransactionIDCallsCount == 1) XCTAssert(timelineProxy.cancelSendTransactionIDCallsCount == 1)
XCTAssert(roomProxyMock.cancelSendTransactionIDReceivedInvocations == ["test cancel send id"]) XCTAssert(timelineProxy.cancelSendTransactionIDReceivedInvocations == ["test cancel send id"])
} }
func testCancelSendNoTransactionID() async { func testCancelSendNoTransactionID() async {
let timelineController = MockRoomTimelineController() let timelineController = MockRoomTimelineController()
let roomProxyMock = RoomProxyMock(with: .init(displayName: "")) let roomProxyMock = RoomProxyMock(with: .init(displayName: ""))
let timelineProxy = TimelineProxyMock()
roomProxyMock.timeline = timelineProxy
let viewModel = RoomScreenViewModel(roomProxy: roomProxyMock, let viewModel = RoomScreenViewModel(roomProxy: roomProxyMock,
timelineController: timelineController, timelineController: timelineController,
@ -418,7 +426,7 @@ class RoomScreenViewModelTests: XCTestCase {
try? await Task.sleep(for: .milliseconds(100)) try? await Task.sleep(for: .milliseconds(100))
XCTAssert(roomProxyMock.cancelSendTransactionIDCallsCount == 0) XCTAssert(timelineProxy.cancelSendTransactionIDCallsCount == 0)
} }
// MARK: - Read Receipts // MARK: - Read Receipts
@ -429,15 +437,15 @@ class RoomScreenViewModelTests: XCTestCase {
let items = [TextRoomTimelineItem(eventID: "t1"), let items = [TextRoomTimelineItem(eventID: "t1"),
TextRoomTimelineItem(eventID: "t2"), TextRoomTimelineItem(eventID: "t2"),
TextRoomTimelineItem(eventID: "t3")] TextRoomTimelineItem(eventID: "t3")]
let (viewModel, roomProxy, _, notificationCenter) = readReceiptsConfiguration(with: items) let (viewModel, roomProxy, timelineProxy, _, notificationCenter) = readReceiptsConfiguration(with: items)
// When sending a read receipt for the last item. // When sending a read receipt for the last item.
viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.last!.id)) viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.last!.id))
try await Task.sleep(for: .milliseconds(100)) try await Task.sleep(for: .milliseconds(100))
// Then the receipt should be sent. // Then the receipt should be sent.
XCTAssertEqual(roomProxy.sendReadReceiptForCalled, true) XCTAssertEqual(timelineProxy.sendReadReceiptForCalled, true)
XCTAssertEqual(roomProxy.sendReadReceiptForReceivedEventID, "t3") XCTAssertEqual(timelineProxy.sendReadReceiptForReceivedEventID, "t3")
// And the notifications should be cleared. // And the notifications should be cleared.
XCTAssertEqual(notificationCenter.postNameObjectReceivedArguments?.aName, .roomMarkedAsRead) XCTAssertEqual(notificationCenter.postNameObjectReceivedArguments?.aName, .roomMarkedAsRead)
@ -450,19 +458,19 @@ class RoomScreenViewModelTests: XCTestCase {
let items = [TextRoomTimelineItem(eventID: "t1"), let items = [TextRoomTimelineItem(eventID: "t1"),
TextRoomTimelineItem(eventID: "t2"), TextRoomTimelineItem(eventID: "t2"),
TextRoomTimelineItem(eventID: "t3")] TextRoomTimelineItem(eventID: "t3")]
let (viewModel, roomProxy, timelineController, _) = readReceiptsConfiguration(with: items) let (viewModel, _, timelineProxy, timelineController, _) = readReceiptsConfiguration(with: items)
viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.last!.id)) viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.last!.id))
try await Task.sleep(for: .milliseconds(100)) try await Task.sleep(for: .milliseconds(100))
XCTAssertEqual(roomProxy.sendReadReceiptForCallsCount, 1) XCTAssertEqual(timelineProxy.sendReadReceiptForCallsCount, 1)
XCTAssertEqual(roomProxy.sendReadReceiptForReceivedEventID, "t3") XCTAssertEqual(timelineProxy.sendReadReceiptForReceivedEventID, "t3")
// When sending a receipt for the first item in the timeline. // When sending a receipt for the first item in the timeline.
viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.first!.id)) viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.first!.id))
try await Task.sleep(for: .milliseconds(100)) try await Task.sleep(for: .milliseconds(100))
// Then the request should be ignored. // Then the request should be ignored.
XCTAssertEqual(roomProxy.sendReadReceiptForCallsCount, 1) XCTAssertEqual(timelineProxy.sendReadReceiptForCallsCount, 1)
XCTAssertEqual(roomProxy.sendReadReceiptForReceivedEventID, "t3") XCTAssertEqual(timelineProxy.sendReadReceiptForReceivedEventID, "t3")
// When a new message is received and marked as read. // When a new message is received and marked as read.
let newMessage = TextRoomTimelineItem(eventID: "t4") let newMessage = TextRoomTimelineItem(eventID: "t4")
@ -474,8 +482,8 @@ class RoomScreenViewModelTests: XCTestCase {
try await Task.sleep(for: .milliseconds(100)) try await Task.sleep(for: .milliseconds(100))
// Then the request should be made. // Then the request should be made.
XCTAssertEqual(roomProxy.sendReadReceiptForCallsCount, 2) XCTAssertEqual(timelineProxy.sendReadReceiptForCallsCount, 2)
XCTAssertEqual(roomProxy.sendReadReceiptForReceivedEventID, "t4") XCTAssertEqual(timelineProxy.sendReadReceiptForReceivedEventID, "t4")
} }
func testSendReadReceiptWithoutEvents() async throws { func testSendReadReceiptWithoutEvents() async throws {
@ -483,14 +491,14 @@ class RoomScreenViewModelTests: XCTestCase {
let items = [SeparatorRoomTimelineItem(timelineID: "v1"), let items = [SeparatorRoomTimelineItem(timelineID: "v1"),
SeparatorRoomTimelineItem(timelineID: "v2"), SeparatorRoomTimelineItem(timelineID: "v2"),
SeparatorRoomTimelineItem(timelineID: "v3")] SeparatorRoomTimelineItem(timelineID: "v3")]
let (viewModel, roomProxy, _, _) = readReceiptsConfiguration(with: items) let (viewModel, _, timelineProxy, _, _) = readReceiptsConfiguration(with: items)
// When sending a read receipt for the last item. // When sending a read receipt for the last item.
viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.last!.id)) viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.last!.id))
try await Task.sleep(for: .milliseconds(100)) try await Task.sleep(for: .milliseconds(100))
// Then nothing should be sent. // Then nothing should be sent.
XCTAssertEqual(roomProxy.sendReadReceiptForCalled, false) XCTAssertEqual(timelineProxy.sendReadReceiptForCalled, false)
} }
func testSendReadReceiptVirtualLast() async throws { func testSendReadReceiptVirtualLast() async throws {
@ -498,15 +506,15 @@ class RoomScreenViewModelTests: XCTestCase {
let items: [RoomTimelineItemProtocol] = [TextRoomTimelineItem(eventID: "t1"), let items: [RoomTimelineItemProtocol] = [TextRoomTimelineItem(eventID: "t1"),
TextRoomTimelineItem(eventID: "t2"), TextRoomTimelineItem(eventID: "t2"),
SeparatorRoomTimelineItem(timelineID: "v3")] SeparatorRoomTimelineItem(timelineID: "v3")]
let (viewModel, roomProxy, _, _) = readReceiptsConfiguration(with: items) let (viewModel, _, timelineProxy, _, _) = readReceiptsConfiguration(with: items)
// When sending a read receipt for the last item. // When sending a read receipt for the last item.
viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.last!.id)) viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.last!.id))
try await Task.sleep(for: .milliseconds(100)) try await Task.sleep(for: .milliseconds(100))
// Then a read receipt should be sent for the item before it. // Then a read receipt should be sent for the item before it.
XCTAssertEqual(roomProxy.sendReadReceiptForCalled, true) XCTAssertEqual(timelineProxy.sendReadReceiptForCalled, true)
XCTAssertEqual(roomProxy.sendReadReceiptForReceivedEventID, "t2") XCTAssertEqual(timelineProxy.sendReadReceiptForReceivedEventID, "t2")
} }
func testSendReadReceiptMultipleRequests() async throws { func testSendReadReceiptMultipleRequests() async throws {
@ -514,31 +522,34 @@ class RoomScreenViewModelTests: XCTestCase {
let items: [RoomTimelineItemProtocol] = [TextRoomTimelineItem(eventID: "t1"), let items: [RoomTimelineItemProtocol] = [TextRoomTimelineItem(eventID: "t1"),
TextRoomTimelineItem(eventID: "t2"), TextRoomTimelineItem(eventID: "t2"),
SeparatorRoomTimelineItem(timelineID: "v3")] SeparatorRoomTimelineItem(timelineID: "v3")]
let (viewModel, roomProxy, _, _) = readReceiptsConfiguration(with: items) let (viewModel, _, timelineProxy, _, _) = readReceiptsConfiguration(with: items)
viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.last!.id)) viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.last!.id))
try await Task.sleep(for: .milliseconds(100)) try await Task.sleep(for: .milliseconds(100))
XCTAssertEqual(roomProxy.sendReadReceiptForCallsCount, 1) XCTAssertEqual(timelineProxy.sendReadReceiptForCallsCount, 1)
XCTAssertEqual(roomProxy.sendReadReceiptForReceivedEventID, "t2") XCTAssertEqual(timelineProxy.sendReadReceiptForReceivedEventID, "t2")
// When sending the same receipt again // When sending the same receipt again
viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.last!.id)) viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.last!.id))
try await Task.sleep(for: .milliseconds(100)) try await Task.sleep(for: .milliseconds(100))
// Then the second call should be ignored. // Then the second call should be ignored.
XCTAssertEqual(roomProxy.sendReadReceiptForCallsCount, 1) XCTAssertEqual(timelineProxy.sendReadReceiptForCallsCount, 1)
} }
// swiftlint:enable force_unwrapping // swiftlint:enable force_unwrapping
// swiftlint:disable:next large_tuple // swiftlint:disable:next large_tuple
private func readReceiptsConfiguration(with items: [RoomTimelineItemProtocol]) -> (RoomScreenViewModel, private func readReceiptsConfiguration(with items: [RoomTimelineItemProtocol]) -> (RoomScreenViewModel,
RoomProxyMock, RoomProxyMock,
TimelineProxyMock,
MockRoomTimelineController, MockRoomTimelineController,
NotificationCenterMock) { NotificationCenterMock) {
let notificationCenter = NotificationCenterMock() let notificationCenter = NotificationCenterMock()
let roomProxy = RoomProxyMock(with: .init(displayName: "")) let roomProxy = RoomProxyMock(with: .init(displayName: ""))
let timelineProxy = TimelineProxyMock()
roomProxy.timeline = timelineProxy
let timelineController = MockRoomTimelineController() let timelineController = MockRoomTimelineController()
roomProxy.sendReadReceiptForReturnValue = .success(()) timelineProxy.sendReadReceiptForReturnValue = .success(())
roomProxy.underlyingHasUnreadNotifications = true roomProxy.underlyingHasUnreadNotifications = true
timelineController.timelineItems = items timelineController.timelineItems = items
timelineController.roomProxy = roomProxy timelineController.roomProxy = roomProxy
@ -554,7 +565,7 @@ class RoomScreenViewModelTests: XCTestCase {
analyticsService: ServiceLocator.shared.analytics, analyticsService: ServiceLocator.shared.analytics,
notificationCenter: notificationCenter) notificationCenter: notificationCenter)
return (viewModel, roomProxy, timelineController, notificationCenter) return (viewModel, roomProxy, timelineProxy, timelineController, notificationCenter)
} }
func testShowReadReceipts() async throws { func testShowReadReceipts() async throws {

View File

@ -237,8 +237,10 @@ class VoiceMessageRecorderTests: XCTestCase {
try? FileManager.default.removeItem(at: destination) try? FileManager.default.removeItem(at: destination)
} }
let timelineProxy = TimelineProxyMock()
let roomProxy = RoomProxyMock() let roomProxy = RoomProxyMock()
roomProxy.sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleReturnValue = .failure(.failedSendingMedia) roomProxy.timeline = timelineProxy
timelineProxy.sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleReturnValue = .failure(.failedSendingMedia)
guard case .failure(.failedSendingVoiceMessage) = await voiceMessageRecorder.sendVoiceMessage(inRoom: roomProxy, audioConverter: audioConverter) else { guard case .failure(.failedSendingVoiceMessage) = await voiceMessageRecorder.sendVoiceMessage(inRoom: roomProxy, audioConverter: audioConverter) else {
XCTFail("An error is expected") XCTFail("An error is expected")
return return
@ -256,8 +258,10 @@ class VoiceMessageRecorderTests: XCTestCase {
try? FileManager.default.copyItem(at: imageFileURL, to: destination) try? FileManager.default.copyItem(at: imageFileURL, to: destination)
} }
let timelineProxy = TimelineProxyMock()
let roomProxy = RoomProxyMock() let roomProxy = RoomProxyMock()
roomProxy.sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleReturnValue = .failure(.failedSendingMedia) roomProxy.timeline = timelineProxy
timelineProxy.sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleReturnValue = .failure(.failedSendingMedia)
guard case .failure(.failedSendingVoiceMessage) = await voiceMessageRecorder.sendVoiceMessage(inRoom: roomProxy, audioConverter: audioConverter) else { guard case .failure(.failedSendingVoiceMessage) = await voiceMessageRecorder.sendVoiceMessage(inRoom: roomProxy, audioConverter: audioConverter) else {
XCTFail("An error is expected") XCTFail("An error is expected")
return return
@ -277,8 +281,10 @@ class VoiceMessageRecorderTests: XCTestCase {
} }
// If the media upload fails // If the media upload fails
let timelineProxy = TimelineProxyMock()
let roomProxy = RoomProxyMock() let roomProxy = RoomProxyMock()
roomProxy.sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleReturnValue = .failure(.failedSendingMedia) roomProxy.timeline = timelineProxy
timelineProxy.sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleReturnValue = .failure(.failedSendingMedia)
guard case .failure(.failedSendingVoiceMessage) = await voiceMessageRecorder.sendVoiceMessage(inRoom: roomProxy, audioConverter: audioConverter) else { guard case .failure(.failedSendingVoiceMessage) = await voiceMessageRecorder.sendVoiceMessage(inRoom: roomProxy, audioConverter: audioConverter) else {
XCTFail("An error is expected") XCTFail("An error is expected")
return return
@ -291,7 +297,9 @@ class VoiceMessageRecorderTests: XCTestCase {
return return
} }
let timelineProxy = TimelineProxyMock()
let roomProxy = RoomProxyMock() let roomProxy = RoomProxyMock()
roomProxy.timeline = timelineProxy
audioRecorder.currentTime = 42 audioRecorder.currentTime = 42
audioRecorder.audioFileURL = imageFileURL audioRecorder.audioFileURL = imageFileURL
_ = await voiceMessageRecorder.startRecording() _ = await voiceMessageRecorder.startRecording()
@ -312,7 +320,7 @@ class VoiceMessageRecorderTests: XCTestCase {
XCTAssertEqual(destination.pathExtension, "ogg") XCTAssertEqual(destination.pathExtension, "ogg")
} }
roomProxy.sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleClosure = { url, audioInfo, waveform, _, _ in timelineProxy.sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleClosure = { url, audioInfo, waveform, _, _ in
XCTAssertEqual(url, convertedFileURL) XCTAssertEqual(url, convertedFileURL)
XCTAssertEqual(audioInfo.duration, self.audioRecorder.currentTime) XCTAssertEqual(audioInfo.duration, self.audioRecorder.currentTime)
XCTAssertEqual(audioInfo.size, convertedFileSize) XCTAssertEqual(audioInfo.size, convertedFileSize)
@ -328,7 +336,7 @@ class VoiceMessageRecorderTests: XCTestCase {
} }
XCTAssert(audioConverter.convertToOpusOggSourceURLDestinationURLCalled) XCTAssert(audioConverter.convertToOpusOggSourceURLDestinationURLCalled)
XCTAssert(roomProxy.sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleCalled) XCTAssert(timelineProxy.sendVoiceMessageUrlAudioInfoWaveformProgressSubjectRequestHandleCalled)
// the converted file must have been deleted // the converted file must have been deleted
if let convertedFileURL { if let convertedFileURL {

View File

@ -45,7 +45,7 @@ packages:
# Element/Matrix dependencies # Element/Matrix dependencies
MatrixRustSDK: MatrixRustSDK:
url: https://github.com/matrix-org/matrix-rust-components-swift url: https://github.com/matrix-org/matrix-rust-components-swift
exactVersion: 0.0.6-november23 exactVersion: 0.0.7-november23
# path: ../matrix-rust-sdk # path: ../matrix-rust-sdk
Compound: Compound:
url: https://github.com/vector-im/compound-ios url: https://github.com/vector-im/compound-ios