mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 21:39:12 +00:00
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:
parent
fb2bc1c39a
commit
e7494164b2
@ -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" */ = {
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
518
ElementX/Sources/Services/Timeline/TimelineProxy.swift
Normal file
518
ElementX/Sources/Services/Timeline/TimelineProxy.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
127
ElementX/Sources/Services/Timeline/TimelineProxyProtocol.swift
Normal file
127
ElementX/Sources/Services/Timeline/TimelineProxyProtocol.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
@ -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)")
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user