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

* Refactor RoomProxy

* Refactor paginateBackwards

* Refactor sendReadReceipt

* Refactor messageEventContent APIs

* Refactor sendMessage

* Refactor toggleReaction

* Refactor send attachments

* Refactor sendLocation

* Refactor cancel/retry send

* ⚠️ Fix encryption build errors

* Refactor editMessage

* Refactor retryDecryption

* Refactor fetchDetails

* Refactor polls APIs

* Refactor fetchMembers

* Refactor RoomTimelineProviderProtocol

* Update sdk to 0.0.7-november23

* Fix UTs

* Fix comment

* Delete old workaround

* Move TimelineProxyError

* Delete queue warnings

* Fix key listener

* Add pollHistory timeline property

* Refactor room/timeline subscriptions

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

View File

@ -176,6 +176,7 @@
2F623DA1122140A987B34D08 /* NotificationSettingsEditScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7BB497B2F539C17E88F6B7 /* NotificationSettingsEditScreenViewModelProtocol.swift */; };
2F66701B15657A87B4AC3A0A /* WaitlistScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09CE2B7AD979BDEE09FEDB08 /* WaitlistScreenModels.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 */; };
308BD9343B95657FAA583FB7 /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 19CD5B074D7DD44AF4C58BB6 /* SwiftState */; };
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 */; };
E794AB6ABE1FF5AF0573FEA1 /* BlurHashEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9332DFE9642F0A46ECA0497B /* BlurHashEncode.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 */; };
E89536FC8C0E4B79E9842A78 /* RoomTimelineControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C0197EAE9D45A662B8847B6 /* RoomTimelineControllerProtocol.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>"; };
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>"; };
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>"; };
B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; 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>"; };
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>"; };
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>"; };
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>"; };
@ -4653,6 +4657,8 @@
095AED4CF56DFF3EB7BB84C8 /* RoomTimelineProviderProtocol.swift */,
2D505843AB66822EB91F0DF0 /* TimelineItemProxy.swift */,
55AEEF8142DF1B59DB40FB93 /* TimelineItemSender.swift */,
F9E543072DE58E751F028998 /* TimelineProxy.swift */,
B50F03079F6B5EF9CA005F14 /* TimelineProxyProtocol.swift */,
3EA31CC7012EA2A5653DAFC9 /* Fixtures */,
2F2FED77226A43559F009463 /* TimelineController */,
6B0910BCE4F1B02F124E1A09 /* TimelineItemContent */,
@ -5869,6 +5875,8 @@
1B88BB631F7FC45A213BB554 /* TimelineItemSender.swift in Sources */,
A680F54935A6ADEA4ED6C38F /* TimelineItemStatusView.swift in Sources */,
562EFB9AB62B38830D9AA778 /* TimelineMediaFrame.swift in Sources */,
E82E13CC3EB923CCB8F8273C /* TimelineProxy.swift in Sources */,
2FEC6652055984389CE1BBEC /* TimelineProxyProtocol.swift in Sources */,
9B582B3EEFEA615D4A6FBF1A /* TimelineReactionsView.swift in Sources */,
49814A48470F347426513B07 /* TimelineReadReceiptsView.swift in Sources */,
2A90DD14DE5C891BFA433950 /* TimelineReplyView.swift in Sources */,
@ -6652,7 +6660,7 @@
repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift";
requirement = {
kind = exactVersion;
version = "0.0.6-november23";
version = "0.0.7-november23";
};
};
821C67C9A7F8CC3FD41B28B4 /* XCRemoteSwiftPackageReference "emojibase-bindings" */ = {

View File

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

View File

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

View File

@ -595,20 +595,20 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
private func presentMapNavigator(interactionMode: StaticLocationInteractionMode) {
let locationPickerNavigationStackCoordinator = NavigationStackCoordinator()
let params = StaticLocationScreenCoordinatorParameters(interactionMode: interactionMode)
let coordinator = StaticLocationScreenCoordinator(parameters: params)
coordinator.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .selectedLocation(let geoURI, let isUserLocation):
Task {
_ = await self.roomProxy?.sendLocation(body: geoURI.bodyMessage,
geoURI: geoURI,
description: nil,
zoomLevel: 15,
assetType: isUserLocation ? .sender : .pin)
_ = await self.roomProxy?.timeline.sendLocation(body: geoURI.bodyMessage,
geoURI: geoURI,
description: nil,
zoomLevel: 15,
assetType: isUserLocation ? .sender : .pin)
self.navigationSplitCoordinator.setSheetCoordinator(nil)
}
@ -622,9 +622,9 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
}
}
.store(in: &cancellables)
locationPickerNavigationStackCoordinator.setRootCoordinator(coordinator)
navigationStackCoordinator.setSheetCoordinator(locationPickerNavigationStackCoordinator) { [weak self] in
self?.stateMachine.tryEvent(.dismissMapNavigator)
}
@ -671,7 +671,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
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,
isEditing: false,
@ -697,7 +697,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
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 {
case .success:
@ -781,7 +781,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
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)")
userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
return
@ -793,7 +793,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
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)")
userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
return

View File

@ -1934,11 +1934,16 @@ class RoomProxyMock: RoomProxyProtocol {
set(value) { underlyingStateUpdatesPublisher = value }
}
var underlyingStateUpdatesPublisher: AnyPublisher<Void, Never>!
var timelineProvider: RoomTimelineProviderProtocol {
get { return underlyingTimelineProvider }
set(value) { underlyingTimelineProvider = value }
var timeline: TimelineProxyProtocol {
get { return underlyingTimeline }
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
@ -1994,291 +1999,6 @@ class RoomProxyMock: RoomProxyProtocol {
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
var redactCallsCount = 0
@ -2342,22 +2062,6 @@ class RoomProxyMock: RoomProxyProtocol {
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
var leaveRoomCallsCount = 0
@ -2459,22 +2163,6 @@ class RoomProxyMock: RoomProxyProtocol {
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
var inviteUserIDCallsCount = 0
@ -2618,90 +2306,6 @@ class RoomProxyMock: RoomProxyProtocol {
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
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 {
//MARK: - searchProfiles

View File

@ -84,7 +84,7 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType,
// 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
self?.requestHandle?.cancel()
self?.requestHandle = handle
@ -92,13 +92,13 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType,
switch mediaInfo {
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):
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):
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):
return await roomProxy.sendFile(url: fileURL, fileInfo: fileInfo, progressSubject: progressSubject, requestHandle: requestHandle)
return await roomProxy.timeline.sendFile(url: fileURL, fileInfo: fileInfo, progressSubject: progressSubject, requestHandle: requestHandle)
}
}

View File

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

View File

@ -23,48 +23,35 @@ import MatrixRustSDK
class RoomProxy: RoomProxyProtocol {
private let roomListItem: RoomListItemProtocol
private let room: RoomProtocol
let timeline: TimelineProxyProtocol
let pollHistoryTimeline: TimelineProxyProtocol
private let backgroundTaskService: BackgroundTaskServiceProtocol
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 lowPriorityDispatchQueue = DispatchQueue(label: "io.element.elementx.roomproxy.low_priority", qos: .utility)
private var sendMessageBackgroundTask: BackgroundTaskProtocol?
private(set) var displayName: String?
private var roomTimelineObservationToken: TaskHandle?
private var backPaginationStateObservationToken: TaskHandle?
private var roomInfoObservationToken: TaskHandle?
private var subscribedForUpdates = false
private let backPaginationStateSubject = PassthroughSubject<BackPaginationStatus, Never>()
private let membersSubject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([])
var members: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> {
membersSubject.asCurrentValuePublisher()
}
private var timelineListener: RoomTimelineListener?
private let timelineUpdatesSubject = PassthroughSubject<[TimelineDiff], Never>()
private let stateUpdatesSubject = PassthroughSubject<Void, Never>()
var stateUpdatesPublisher: AnyPublisher<Void, Never> {
stateUpdatesSubject.eraseToAnyPublisher()
}
var innerTimelineProvider: RoomTimelineProviderProtocol!
var timelineProvider: RoomTimelineProviderProtocol {
innerTimelineProvider
}
var ownUserID: String {
room.ownUserId()
}
deinit {
roomTimelineObservationToken?.cancel()
backPaginationStateObservationToken?.cancel()
roomListItem.unsubscribe()
}
@ -74,19 +61,25 @@ class RoomProxy: RoomProxyProtocol {
self.roomListItem = roomListItem
self.room = room
self.backgroundTaskService = backgroundTaskService
timeline = await TimelineProxy(timeline: room.timeline(), backgroundTaskService: backgroundTaskService)
pollHistoryTimeline = await TimelineProxy(timeline: room.pollHistory(), backgroundTaskService: backgroundTaskService)
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()
}
}
func subscribeForUpdates() async {
guard innerTimelineProvider == nil else {
guard !subscribedForUpdates else {
MXLog.warning("Room already subscribed for updates")
return
}
subscribedForUpdates = true
let settings = RoomSubscription(requiredState: [RequiredState(key: "m.room.name", value: ""),
RequiredState(key: "m.room.topic", value: ""),
RequiredState(key: "m.room.avatar", value: ""),
@ -95,26 +88,10 @@ class RoomProxy: RoomProxyProtocol {
timelineLimit: UInt32(SlidingSyncConstants.defaultTimelineLimit))
roomListItem.subscribe(settings: settings)
let timelineListener = RoomTimelineListener { [weak self] timelineDiffs in
self?.timelineUpdatesSubject.send(timelineDiffs)
// Workaround for subscribeToRoomStateUpdates creating problems in the timeline
// https://github.com/matrix-org/matrix-rust-sdk/issues/2488
self?.stateUpdatesSubject.send()
}
await timeline.subscribeForUpdates()
await pollHistoryTimeline.subscribeForUpdates()
self.timelineListener = timelineListener
let result = await room.addTimelineListener(listener: timelineListener)
roomTimelineObservationToken = result.itemsStream
subscribeToBackpagination()
// subscribeToRoomStateUpdates()
innerTimelineProvider = await RoomTimelineProvider(currentItems: result.items,
updatePublisher: timelineUpdatesSubject.eraseToAnyPublisher(),
backPaginationStatePublisher: backPaginationStateSubject.eraseToAnyPublisher())
subscribeToRoomStateUpdates()
}
lazy var id: String = room.id()
@ -216,282 +193,6 @@ class RoomProxy: RoomProxyProtocol {
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> {
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> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
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> {
await Task.dispatch(on: .global()) {
do {
@ -713,56 +395,6 @@ class RoomProxy: RoomProxyProtocol {
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
@ -771,64 +403,6 @@ class RoomProxy: RoomProxyProtocol {
}
// 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() {
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 let onUpdateClosure: () -> Void
@ -887,14 +423,3 @@ private final class RoomInfoUpdateListener: RoomInfoListener {
onUpdateClosure()
}
}
private extension MatrixRustSDK.PollKind {
init(pollKind: Poll.Kind) {
switch pollKind {
case .disclosed:
self = .disclosed
case .undisclosed:
self = .undisclosed
}
}
}

View File

@ -19,19 +19,10 @@ import Foundation
import MatrixRustSDK
enum RoomProxyError: Error, Equatable {
case noMoreMessagesToBackPaginate
case failedPaginatingBackwards
case failedRetrievingMemberAvatarURL
case failedRetrievingMemberDisplayName
case failedSendingReadReceipt
case failedSendingMessage
case failedSendingReaction
case failedSendingMedia
case failedEditingMessage
case failedRedactingEvent
case failedReportingContent
case failedAddingTimelineListener
case failedRetrievingMembers
case failedRetrievingMember
case failedLeavingRoom
case failedAcceptingInvite
@ -42,10 +33,6 @@ enum RoomProxyError: Error, Equatable {
case failedRemovingAvatar
case failedUploadingAvatar
case failedCheckingPermission
case failedCreatingPoll
case failedSendingPollResponse
case failedEndingPoll
case failedEditingPoll
}
// sourcery: AutoMockable
@ -82,7 +69,10 @@ protocol RoomProxyProtocol {
/// The thread on which this publisher sends the output isn't defined.
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
@ -90,73 +80,11 @@ protocol RoomProxyProtocol {
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 reportContent(_ eventID: String, reason: 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>
@ -170,8 +98,6 @@ protocol RoomProxyProtocol {
func acceptInvitation() async -> Result<Void, RoomProxyError>
func fetchDetails(for eventID: String)
func invite(userID: 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 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
@ -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
// Probably this should be done in rust.
var roomTitle: String {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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