diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index adc90144b..7c66f1bb0 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -663,7 +663,6 @@ F508683B76EF7B23BB2CBD6D /* TimelineItemPlainStylerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94BCC8A9C73C1F838122C645 /* TimelineItemPlainStylerView.swift */; }; F519DE17A3A0F760307B2E6D /* InviteUsersScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D155E09BF961BBA8F85263 /* InviteUsersScreenViewModel.swift */; }; F54E2D6CAD96E1AC15BC526F /* MessageForwardingScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E60332509665C00179ACF6 /* MessageForwardingScreenViewModel.swift */; }; - F587A9AF25A262DE5A7B0369 /* ProgressTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = F28551E81CE3700E5F1EC9B5 /* ProgressTracker.swift */; }; F5D2270B5021D521C0D22E11 /* FlowCoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B9FCA1CFD07B8CF9BD21266 /* FlowCoordinatorProtocol.swift */; }; F656F92A63D3DC1978D79427 /* Compound in Frameworks */ = {isa = PBXBuildFile; productRef = 07FEEEDB11543A7DED420F04 /* Compound */; }; F6F49E37272AD7397CD29A01 /* HomeScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */; }; @@ -1337,7 +1336,6 @@ F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProvider.swift; sourceTree = ""; }; F1964EE08550BEDBD0B0F5FD /* FilePreviewScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewScreenViewModel.swift; sourceTree = ""; }; F1B8500C152BC59445647DA8 /* UnsupportedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsupportedRoomTimelineItem.swift; sourceTree = ""; }; - F28551E81CE3700E5F1EC9B5 /* ProgressTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressTracker.swift; sourceTree = ""; }; F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixEntityRegexTests.swift; sourceTree = ""; }; F348B5F2C12F9D4F4B4D3884 /* VideoRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRoomTimelineItem.swift; sourceTree = ""; }; F36C0A6D59717193F49EA986 /* UserSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionTests.swift; sourceTree = ""; }; @@ -2983,7 +2981,6 @@ 6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */, C789E7BFC066CF39B8AE0974 /* NetworkMonitor.swift */, F754E66A8970963B15B2A41E /* PermalinkBuilder.swift */, - F28551E81CE3700E5F1EC9B5 /* ProgressTracker.swift */, 53482ECA4B6633961EC224F5 /* ScrollViewAdapter.swift */, DBA8DC95C079805B0B56E8A9 /* SharedUserDefaultsKeys.swift */, BB3073CCD77D906B330BC1D6 /* Tests.swift */, @@ -4153,7 +4150,6 @@ 962A4F8AD6312804E2C6BB6E /* PhotoLibraryPicker.swift in Sources */, 9D79B94493FB32249F7E472F /* PlaceholderAvatarImage.swift in Sources */, DF504B10A4918F971A57BEF2 /* PostHogAnalyticsClient.swift in Sources */, - F587A9AF25A262DE5A7B0369 /* ProgressTracker.swift in Sources */, 2835FD52F3F618D07F799B3D /* Publisher.swift in Sources */, 743790BF6A5B0577EA74AF14 /* ReadMarkerRoomTimelineItem.swift in Sources */, 8EF63DDDC1B54F122070B04D /* ReadMarkerRoomTimelineView.swift in Sources */, diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index a69ee8b94..76c4b5052 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -177,12 +177,12 @@ class BugReportServiceMock: BugReportServiceProtocol { var submitBugReportProgressListenerCalled: Bool { return submitBugReportProgressListenerCallsCount > 0 } - var submitBugReportProgressListenerReceivedArguments: (bugReport: BugReport, progressListener: ProgressListener?)? - var submitBugReportProgressListenerReceivedInvocations: [(bugReport: BugReport, progressListener: ProgressListener?)] = [] + var submitBugReportProgressListenerReceivedArguments: (bugReport: BugReport, progressListener: CurrentValueSubject)? + var submitBugReportProgressListenerReceivedInvocations: [(bugReport: BugReport, progressListener: CurrentValueSubject)] = [] var submitBugReportProgressListenerReturnValue: Result! - var submitBugReportProgressListenerClosure: ((BugReport, ProgressListener?) async -> Result)? + var submitBugReportProgressListenerClosure: ((BugReport, CurrentValueSubject) async -> Result)? - func submitBugReport(_ bugReport: BugReport, progressListener: ProgressListener?) async -> Result { + func submitBugReport(_ bugReport: BugReport, progressListener: CurrentValueSubject) async -> Result { submitBugReportProgressListenerCallsCount += 1 submitBugReportProgressListenerReceivedArguments = (bugReport: bugReport, progressListener: progressListener) submitBugReportProgressListenerReceivedInvocations.append((bugReport: bugReport, progressListener: progressListener)) @@ -633,86 +633,86 @@ class RoomProxyMock: RoomProxyProtocol { } //MARK: - sendImage - var sendImageUrlThumbnailURLImageInfoCallsCount = 0 - var sendImageUrlThumbnailURLImageInfoCalled: Bool { - return sendImageUrlThumbnailURLImageInfoCallsCount > 0 + var sendImageUrlThumbnailURLImageInfoProgressSubjectCallsCount = 0 + var sendImageUrlThumbnailURLImageInfoProgressSubjectCalled: Bool { + return sendImageUrlThumbnailURLImageInfoProgressSubjectCallsCount > 0 } - var sendImageUrlThumbnailURLImageInfoReceivedArguments: (url: URL, thumbnailURL: URL, imageInfo: ImageInfo)? - var sendImageUrlThumbnailURLImageInfoReceivedInvocations: [(url: URL, thumbnailURL: URL, imageInfo: ImageInfo)] = [] - var sendImageUrlThumbnailURLImageInfoReturnValue: Result! - var sendImageUrlThumbnailURLImageInfoClosure: ((URL, URL, ImageInfo) async -> Result)? + var sendImageUrlThumbnailURLImageInfoProgressSubjectReceivedArguments: (url: URL, thumbnailURL: URL, imageInfo: ImageInfo, progressSubject: CurrentValueSubject?)? + var sendImageUrlThumbnailURLImageInfoProgressSubjectReceivedInvocations: [(url: URL, thumbnailURL: URL, imageInfo: ImageInfo, progressSubject: CurrentValueSubject?)] = [] + var sendImageUrlThumbnailURLImageInfoProgressSubjectReturnValue: Result! + var sendImageUrlThumbnailURLImageInfoProgressSubjectClosure: ((URL, URL, ImageInfo, CurrentValueSubject?) async -> Result)? - func sendImage(url: URL, thumbnailURL: URL, imageInfo: ImageInfo) async -> Result { - sendImageUrlThumbnailURLImageInfoCallsCount += 1 - sendImageUrlThumbnailURLImageInfoReceivedArguments = (url: url, thumbnailURL: thumbnailURL, imageInfo: imageInfo) - sendImageUrlThumbnailURLImageInfoReceivedInvocations.append((url: url, thumbnailURL: thumbnailURL, imageInfo: imageInfo)) - if let sendImageUrlThumbnailURLImageInfoClosure = sendImageUrlThumbnailURLImageInfoClosure { - return await sendImageUrlThumbnailURLImageInfoClosure(url, thumbnailURL, imageInfo) + func sendImage(url: URL, thumbnailURL: URL, imageInfo: ImageInfo, progressSubject: CurrentValueSubject?) async -> Result { + sendImageUrlThumbnailURLImageInfoProgressSubjectCallsCount += 1 + sendImageUrlThumbnailURLImageInfoProgressSubjectReceivedArguments = (url: url, thumbnailURL: thumbnailURL, imageInfo: imageInfo, progressSubject: progressSubject) + sendImageUrlThumbnailURLImageInfoProgressSubjectReceivedInvocations.append((url: url, thumbnailURL: thumbnailURL, imageInfo: imageInfo, progressSubject: progressSubject)) + if let sendImageUrlThumbnailURLImageInfoProgressSubjectClosure = sendImageUrlThumbnailURLImageInfoProgressSubjectClosure { + return await sendImageUrlThumbnailURLImageInfoProgressSubjectClosure(url, thumbnailURL, imageInfo, progressSubject) } else { - return sendImageUrlThumbnailURLImageInfoReturnValue + return sendImageUrlThumbnailURLImageInfoProgressSubjectReturnValue } } //MARK: - sendVideo - var sendVideoUrlThumbnailURLVideoInfoCallsCount = 0 - var sendVideoUrlThumbnailURLVideoInfoCalled: Bool { - return sendVideoUrlThumbnailURLVideoInfoCallsCount > 0 + var sendVideoUrlThumbnailURLVideoInfoProgressSubjectCallsCount = 0 + var sendVideoUrlThumbnailURLVideoInfoProgressSubjectCalled: Bool { + return sendVideoUrlThumbnailURLVideoInfoProgressSubjectCallsCount > 0 } - var sendVideoUrlThumbnailURLVideoInfoReceivedArguments: (url: URL, thumbnailURL: URL, videoInfo: VideoInfo)? - var sendVideoUrlThumbnailURLVideoInfoReceivedInvocations: [(url: URL, thumbnailURL: URL, videoInfo: VideoInfo)] = [] - var sendVideoUrlThumbnailURLVideoInfoReturnValue: Result! - var sendVideoUrlThumbnailURLVideoInfoClosure: ((URL, URL, VideoInfo) async -> Result)? + var sendVideoUrlThumbnailURLVideoInfoProgressSubjectReceivedArguments: (url: URL, thumbnailURL: URL, videoInfo: VideoInfo, progressSubject: CurrentValueSubject?)? + var sendVideoUrlThumbnailURLVideoInfoProgressSubjectReceivedInvocations: [(url: URL, thumbnailURL: URL, videoInfo: VideoInfo, progressSubject: CurrentValueSubject?)] = [] + var sendVideoUrlThumbnailURLVideoInfoProgressSubjectReturnValue: Result! + var sendVideoUrlThumbnailURLVideoInfoProgressSubjectClosure: ((URL, URL, VideoInfo, CurrentValueSubject?) async -> Result)? - func sendVideo(url: URL, thumbnailURL: URL, videoInfo: VideoInfo) async -> Result { - sendVideoUrlThumbnailURLVideoInfoCallsCount += 1 - sendVideoUrlThumbnailURLVideoInfoReceivedArguments = (url: url, thumbnailURL: thumbnailURL, videoInfo: videoInfo) - sendVideoUrlThumbnailURLVideoInfoReceivedInvocations.append((url: url, thumbnailURL: thumbnailURL, videoInfo: videoInfo)) - if let sendVideoUrlThumbnailURLVideoInfoClosure = sendVideoUrlThumbnailURLVideoInfoClosure { - return await sendVideoUrlThumbnailURLVideoInfoClosure(url, thumbnailURL, videoInfo) + func sendVideo(url: URL, thumbnailURL: URL, videoInfo: VideoInfo, progressSubject: CurrentValueSubject?) async -> Result { + sendVideoUrlThumbnailURLVideoInfoProgressSubjectCallsCount += 1 + sendVideoUrlThumbnailURLVideoInfoProgressSubjectReceivedArguments = (url: url, thumbnailURL: thumbnailURL, videoInfo: videoInfo, progressSubject: progressSubject) + sendVideoUrlThumbnailURLVideoInfoProgressSubjectReceivedInvocations.append((url: url, thumbnailURL: thumbnailURL, videoInfo: videoInfo, progressSubject: progressSubject)) + if let sendVideoUrlThumbnailURLVideoInfoProgressSubjectClosure = sendVideoUrlThumbnailURLVideoInfoProgressSubjectClosure { + return await sendVideoUrlThumbnailURLVideoInfoProgressSubjectClosure(url, thumbnailURL, videoInfo, progressSubject) } else { - return sendVideoUrlThumbnailURLVideoInfoReturnValue + return sendVideoUrlThumbnailURLVideoInfoProgressSubjectReturnValue } } //MARK: - sendAudio - var sendAudioUrlAudioInfoCallsCount = 0 - var sendAudioUrlAudioInfoCalled: Bool { - return sendAudioUrlAudioInfoCallsCount > 0 + var sendAudioUrlAudioInfoProgressSubjectCallsCount = 0 + var sendAudioUrlAudioInfoProgressSubjectCalled: Bool { + return sendAudioUrlAudioInfoProgressSubjectCallsCount > 0 } - var sendAudioUrlAudioInfoReceivedArguments: (url: URL, audioInfo: AudioInfo)? - var sendAudioUrlAudioInfoReceivedInvocations: [(url: URL, audioInfo: AudioInfo)] = [] - var sendAudioUrlAudioInfoReturnValue: Result! - var sendAudioUrlAudioInfoClosure: ((URL, AudioInfo) async -> Result)? + var sendAudioUrlAudioInfoProgressSubjectReceivedArguments: (url: URL, audioInfo: AudioInfo, progressSubject: CurrentValueSubject?)? + var sendAudioUrlAudioInfoProgressSubjectReceivedInvocations: [(url: URL, audioInfo: AudioInfo, progressSubject: CurrentValueSubject?)] = [] + var sendAudioUrlAudioInfoProgressSubjectReturnValue: Result! + var sendAudioUrlAudioInfoProgressSubjectClosure: ((URL, AudioInfo, CurrentValueSubject?) async -> Result)? - func sendAudio(url: URL, audioInfo: AudioInfo) async -> Result { - sendAudioUrlAudioInfoCallsCount += 1 - sendAudioUrlAudioInfoReceivedArguments = (url: url, audioInfo: audioInfo) - sendAudioUrlAudioInfoReceivedInvocations.append((url: url, audioInfo: audioInfo)) - if let sendAudioUrlAudioInfoClosure = sendAudioUrlAudioInfoClosure { - return await sendAudioUrlAudioInfoClosure(url, audioInfo) + func sendAudio(url: URL, audioInfo: AudioInfo, progressSubject: CurrentValueSubject?) async -> Result { + sendAudioUrlAudioInfoProgressSubjectCallsCount += 1 + sendAudioUrlAudioInfoProgressSubjectReceivedArguments = (url: url, audioInfo: audioInfo, progressSubject: progressSubject) + sendAudioUrlAudioInfoProgressSubjectReceivedInvocations.append((url: url, audioInfo: audioInfo, progressSubject: progressSubject)) + if let sendAudioUrlAudioInfoProgressSubjectClosure = sendAudioUrlAudioInfoProgressSubjectClosure { + return await sendAudioUrlAudioInfoProgressSubjectClosure(url, audioInfo, progressSubject) } else { - return sendAudioUrlAudioInfoReturnValue + return sendAudioUrlAudioInfoProgressSubjectReturnValue } } //MARK: - sendFile - var sendFileUrlFileInfoCallsCount = 0 - var sendFileUrlFileInfoCalled: Bool { - return sendFileUrlFileInfoCallsCount > 0 + var sendFileUrlFileInfoProgressSubjectCallsCount = 0 + var sendFileUrlFileInfoProgressSubjectCalled: Bool { + return sendFileUrlFileInfoProgressSubjectCallsCount > 0 } - var sendFileUrlFileInfoReceivedArguments: (url: URL, fileInfo: FileInfo)? - var sendFileUrlFileInfoReceivedInvocations: [(url: URL, fileInfo: FileInfo)] = [] - var sendFileUrlFileInfoReturnValue: Result! - var sendFileUrlFileInfoClosure: ((URL, FileInfo) async -> Result)? + var sendFileUrlFileInfoProgressSubjectReceivedArguments: (url: URL, fileInfo: FileInfo, progressSubject: CurrentValueSubject?)? + var sendFileUrlFileInfoProgressSubjectReceivedInvocations: [(url: URL, fileInfo: FileInfo, progressSubject: CurrentValueSubject?)] = [] + var sendFileUrlFileInfoProgressSubjectReturnValue: Result! + var sendFileUrlFileInfoProgressSubjectClosure: ((URL, FileInfo, CurrentValueSubject?) async -> Result)? - func sendFile(url: URL, fileInfo: FileInfo) async -> Result { - sendFileUrlFileInfoCallsCount += 1 - sendFileUrlFileInfoReceivedArguments = (url: url, fileInfo: fileInfo) - sendFileUrlFileInfoReceivedInvocations.append((url: url, fileInfo: fileInfo)) - if let sendFileUrlFileInfoClosure = sendFileUrlFileInfoClosure { - return await sendFileUrlFileInfoClosure(url, fileInfo) + func sendFile(url: URL, fileInfo: FileInfo, progressSubject: CurrentValueSubject?) async -> Result { + sendFileUrlFileInfoProgressSubjectCallsCount += 1 + sendFileUrlFileInfoProgressSubjectReceivedArguments = (url: url, fileInfo: fileInfo, progressSubject: progressSubject) + sendFileUrlFileInfoProgressSubjectReceivedInvocations.append((url: url, fileInfo: fileInfo, progressSubject: progressSubject)) + if let sendFileUrlFileInfoProgressSubjectClosure = sendFileUrlFileInfoProgressSubjectClosure { + return await sendFileUrlFileInfoProgressSubjectClosure(url, fileInfo, progressSubject) } else { - return sendFileUrlFileInfoReturnValue + return sendFileUrlFileInfoProgressSubjectReturnValue } } //MARK: - retrySend diff --git a/ElementX/Sources/Other/ProgressTracker.swift b/ElementX/Sources/Other/ProgressTracker.swift deleted file mode 100644 index d653c833c..000000000 --- a/ElementX/Sources/Other/ProgressTracker.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// 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 - -protocol ProgressListener { - var progressSubject: CurrentValueSubject { get } -} - -protocol ProgressPublisher: AnyObject { - var publisher: AnyPublisher { get } -} - -final class ProgressTracker: ProgressListener, ProgressPublisher { - let progressSubject: CurrentValueSubject - - var publisher: AnyPublisher { - progressSubject - .eraseToAnyPublisher() - } - - init(initialValue: Double = 0.0) { - progressSubject = .init(initialValue) - } -} diff --git a/ElementX/Sources/Other/UserIndicator/UserIndicator.swift b/ElementX/Sources/Other/UserIndicator/UserIndicator.swift index 8a6200f7b..3a66f2e4e 100644 --- a/ElementX/Sources/Other/UserIndicator/UserIndicator.swift +++ b/ElementX/Sources/Other/UserIndicator/UserIndicator.swift @@ -30,13 +30,13 @@ struct UserIndicator: Equatable, Identifiable { static func == (lhs: UserIndicator.Progress, rhs: UserIndicator.Progress) -> Bool { switch (lhs, rhs) { case (.indeterminate, .indeterminate): return true - case (.published(let lhsPublisher), .published(let rhsPublisher)): return lhsPublisher === rhsPublisher + case (.published(let lhsPublisher), .published(let rhsPublisher)): return lhsPublisher.value == rhsPublisher.value default: return false } } case indeterminate - case published(ProgressPublisher) + case published(CurrentValuePublisher) } var id: String = UUID().uuidString @@ -54,14 +54,14 @@ struct UserIndicator: Equatable, Identifiable { } } - var progressPublisher: AnyPublisher { + var progressPublisher: CurrentValuePublisher { switch type { case .toast(let progress), .modal(let progress, _): switch progress { case .none, .indeterminate: - return Empty().eraseToAnyPublisher() - case .some(.published(let progress)): - return progress.publisher.eraseToAnyPublisher() + return CurrentValueSubject(0.0).asCurrentValuePublisher() + case .some(.published(let publisher)): + return publisher } } } diff --git a/ElementX/Sources/Other/UserIndicator/UserIndicatorModalView.swift b/ElementX/Sources/Other/UserIndicator/UserIndicatorModalView.swift index fad43470e..2b9c6afa8 100644 --- a/ElementX/Sources/Other/UserIndicator/UserIndicatorModalView.swift +++ b/ElementX/Sources/Other/UserIndicator/UserIndicatorModalView.swift @@ -69,7 +69,7 @@ struct UserIndicatorModalView_Previews: PreviewProvider { ) .previewDisplayName("Spinner") - UserIndicatorModalView(indicator: UserIndicator(type: .modal(progress: .published(ProgressTracker(initialValue: 0.5)), + UserIndicatorModalView(indicator: UserIndicator(type: .modal(progress: .published(CurrentValueSubject(0.5).asCurrentValuePublisher()), interactiveDismissDisabled: false), title: "Successfully logged in", iconName: "checkmark") diff --git a/ElementX/Sources/Screens/BugReportScreen/BugReportScreenCoordinator.swift b/ElementX/Sources/Screens/BugReportScreen/BugReportScreenCoordinator.swift index 33d324944..8deed5a50 100644 --- a/ElementX/Sources/Screens/BugReportScreen/BugReportScreenCoordinator.swift +++ b/ElementX/Sources/Screens/BugReportScreen/BugReportScreenCoordinator.swift @@ -60,8 +60,8 @@ final class BugReportScreenCoordinator: CoordinatorProtocol { switch result { case .cancel: self.completion?(.cancel) - case let .submitStarted(progressTracker): - self.startLoading(label: L10n.commonSending, progressPublisher: progressTracker) + case let .submitStarted(progressPublisher): + self.startLoading(label: L10n.commonSending, progressPublisher: progressPublisher) case .submitFinished: self.stopLoading() self.completion?(.finish) @@ -85,7 +85,7 @@ final class BugReportScreenCoordinator: CoordinatorProtocol { private static let loadingIndicatorIdentifier = "BugReportLoading" - private func startLoading(label: String = L10n.commonLoading, progressPublisher: ProgressPublisher) { + private func startLoading(label: String = L10n.commonLoading, progressPublisher: CurrentValuePublisher) { parameters.userIndicatorController?.submitIndicator( UserIndicator(id: Self.loadingIndicatorIdentifier, type: .modal(progress: .published(progressPublisher), interactiveDismissDisabled: false), diff --git a/ElementX/Sources/Screens/BugReportScreen/BugReportScreenModels.swift b/ElementX/Sources/Screens/BugReportScreen/BugReportScreenModels.swift index 0ad0b17ee..e07975838 100644 --- a/ElementX/Sources/Screens/BugReportScreen/BugReportScreenModels.swift +++ b/ElementX/Sources/Screens/BugReportScreen/BugReportScreenModels.swift @@ -19,7 +19,7 @@ import UIKit enum BugReportScreenViewModelAction { case cancel - case submitStarted(progressTracker: ProgressTracker) + case submitStarted(progressPublisher: CurrentValuePublisher) case submitFinished case submitFailed(error: Error) } diff --git a/ElementX/Sources/Screens/BugReportScreen/BugReportScreenViewModel.swift b/ElementX/Sources/Screens/BugReportScreen/BugReportScreenViewModel.swift index cc07ae77b..59f7e0fba 100644 --- a/ElementX/Sources/Screens/BugReportScreen/BugReportScreenViewModel.swift +++ b/ElementX/Sources/Screens/BugReportScreen/BugReportScreenViewModel.swift @@ -62,8 +62,9 @@ class BugReportScreenViewModel: BugReportScreenViewModelType, BugReportScreenVie // MARK: Private private func submitBugReport() async { - let progressTracker = ProgressTracker() - actionsSubject.send(.submitStarted(progressTracker: progressTracker)) + let progressSubject = CurrentValueSubject(0.0) + + actionsSubject.send(.submitStarted(progressPublisher: progressSubject.asCurrentValuePublisher())) var files: [URL] = [] if let screenshot = context.viewState.screenshot, @@ -87,7 +88,7 @@ class BugReportScreenViewModel: BugReportScreenViewModelType, BugReportScreenVie files: files) switch await bugReportService.submitBugReport(bugReport, - progressListener: progressTracker) { + progressListener: progressSubject) { case .success(let response): MXLog.info("Submission uploaded to: \(response.reportUrl)") actionsSubject.send(.submitFinished) diff --git a/ElementX/Sources/Screens/MediaUploadPreviewScreen/MediaUploadPreviewScreenViewModel.swift b/ElementX/Sources/Screens/MediaUploadPreviewScreen/MediaUploadPreviewScreenViewModel.swift index cfab9b6b4..2c2e7cf01 100644 --- a/ElementX/Sources/Screens/MediaUploadPreviewScreen/MediaUploadPreviewScreenViewModel.swift +++ b/ElementX/Sources/Screens/MediaUploadPreviewScreen/MediaUploadPreviewScreenViewModel.swift @@ -14,6 +14,7 @@ // limitations under the License. // +import Combine import SwiftUI typealias MediaUploadPreviewScreenViewModelType = StateStoreViewModel @@ -43,11 +44,13 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType, switch viewAction { case .send: Task { - startLoading() + let progressSubject = CurrentValueSubject(0.0) + + startLoading(progressPublisher: progressSubject.asCurrentValuePublisher()) switch await mediaUploadingPreprocessor.processMedia(at: url) { case .success(let mediaInfo): - switch await sendAttachment(mediaInfo: mediaInfo) { + switch await sendAttachment(mediaInfo: mediaInfo, progressSubject: progressSubject) { case .success: callback?(.dismiss) case .failure(let error): @@ -70,26 +73,26 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType, // MARK: - Private - private func sendAttachment(mediaInfo: MediaInfo) async -> Result { + private func sendAttachment(mediaInfo: MediaInfo, progressSubject: CurrentValueSubject) async -> Result { switch mediaInfo { case let .image(imageURL, thumbnailURL, imageInfo): - return await roomProxy.sendImage(url: imageURL, thumbnailURL: thumbnailURL, imageInfo: imageInfo) + return await roomProxy.sendImage(url: imageURL, thumbnailURL: thumbnailURL, imageInfo: imageInfo, progressSubject: progressSubject) case let .video(videoURL, thumbnailURL, videoInfo): - return await roomProxy.sendVideo(url: videoURL, thumbnailURL: thumbnailURL, videoInfo: videoInfo) + return await roomProxy.sendVideo(url: videoURL, thumbnailURL: thumbnailURL, videoInfo: videoInfo, progressSubject: progressSubject) case let .audio(audioURL, audioInfo): - return await roomProxy.sendAudio(url: audioURL, audioInfo: audioInfo) + return await roomProxy.sendAudio(url: audioURL, audioInfo: audioInfo, progressSubject: progressSubject) case let .file(fileURL, fileInfo): - return await roomProxy.sendFile(url: fileURL, fileInfo: fileInfo) + return await roomProxy.sendFile(url: fileURL, fileInfo: fileInfo, progressSubject: progressSubject) } } private static let loadingIndicatorIdentifier = "MediaUploadPreviewLoading" - private func startLoading() { + private func startLoading(progressPublisher: CurrentValuePublisher) { userIndicatorController?.submitIndicator( UserIndicator(id: Self.loadingIndicatorIdentifier, - type: .modal, - title: L10n.commonLoading, + type: .modal(progress: .published(progressPublisher), interactiveDismissDisabled: false), + title: L10n.commonSending, persistent: true) ) } diff --git a/ElementX/Sources/Services/BugReport/BugReportService.swift b/ElementX/Sources/Services/BugReport/BugReportService.swift index 07d553d95..9312ea995 100644 --- a/ElementX/Sources/Services/BugReport/BugReportService.swift +++ b/ElementX/Sources/Services/BugReport/BugReportService.swift @@ -100,7 +100,7 @@ class BugReportService: NSObject, BugReportServiceProtocol { // swiftlint:disable:next function_body_length cyclomatic_complexity func submitBugReport(_ bugReport: BugReport, - progressListener: ProgressListener?) async -> Result { + progressListener: CurrentValueSubject) async -> Result { var params = [ MultipartFormData(key: "user_id", type: .text(value: bugReport.userID)), MultipartFormData(key: "text", type: .text(value: bugReport.text)) @@ -148,17 +148,13 @@ class BugReportService: NSObject, BugReportServiceProtocol { request.httpMethod = "POST" request.httpBody = body as Data - var delegate: URLSessionTaskDelegate? - if let progressListener { - progressSubject - .receive(on: DispatchQueue.main) - .weakAssign(to: \.value, on: progressListener.progressSubject) - .store(in: &cancellables) - delegate = self - } + progressSubject + .receive(on: DispatchQueue.main) + .weakAssign(to: \.value, on: progressListener) + .store(in: &cancellables) do { - let (data, response) = try await session.dataWithRetry(for: request, delegate: delegate) + let (data, response) = try await session.dataWithRetry(for: request, delegate: self) guard let httpResponse = response as? HTTPURLResponse else { let errorDescription = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "Unknown" diff --git a/ElementX/Sources/Services/BugReport/BugReportServiceProtocol.swift b/ElementX/Sources/Services/BugReport/BugReportServiceProtocol.swift index 7b43c2a3a..d48e56b76 100644 --- a/ElementX/Sources/Services/BugReport/BugReportServiceProtocol.swift +++ b/ElementX/Sources/Services/BugReport/BugReportServiceProtocol.swift @@ -14,6 +14,7 @@ // limitations under the License. // +import Combine import Foundation import UIKit @@ -63,5 +64,5 @@ protocol BugReportServiceProtocol { func crash() func submitBugReport(_ bugReport: BugReport, - progressListener: ProgressListener?) async -> Result + progressListener: CurrentValueSubject) async -> Result } diff --git a/ElementX/Sources/Services/Room/RoomProxy.swift b/ElementX/Sources/Services/Room/RoomProxy.swift index 672b8322c..e2cc3de52 100644 --- a/ElementX/Sources/Services/Room/RoomProxy.swift +++ b/ElementX/Sources/Services/Room/RoomProxy.swift @@ -276,7 +276,7 @@ class RoomProxy: RoomProxyProtocol { } } - func sendImage(url: URL, thumbnailURL: URL, imageInfo: ImageInfo) async -> Result { + func sendImage(url: URL, thumbnailURL: URL, imageInfo: ImageInfo, progressSubject: CurrentValueSubject?) async -> Result { sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) defer { sendMessageBackgroundTask?.stop() @@ -284,7 +284,9 @@ class RoomProxy: RoomProxyProtocol { return await Task.dispatch(on: userInitiatedDispatchQueue) { do { - try self.room.sendImage(url: url.path(), thumbnailUrl: thumbnailURL.path(), imageInfo: imageInfo, progressWatcher: nil) + try self.room.sendImage(url: url.path(), thumbnailUrl: thumbnailURL.path(), imageInfo: imageInfo, progressWatcher: UploadProgressListener { progress in + progressSubject?.send(progress) + }) return .success(()) } catch { return .failure(.failedSendingMedia) @@ -292,7 +294,7 @@ class RoomProxy: RoomProxyProtocol { } } - func sendVideo(url: URL, thumbnailURL: URL, videoInfo: VideoInfo) async -> Result { + func sendVideo(url: URL, thumbnailURL: URL, videoInfo: VideoInfo, progressSubject: CurrentValueSubject?) async -> Result { sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) defer { sendMessageBackgroundTask?.stop() @@ -300,7 +302,9 @@ class RoomProxy: RoomProxyProtocol { return await Task.dispatch(on: userInitiatedDispatchQueue) { do { - try self.room.sendVideo(url: url.path(), thumbnailUrl: thumbnailURL.path(), videoInfo: videoInfo, progressWatcher: nil) + try self.room.sendVideo(url: url.path(), thumbnailUrl: thumbnailURL.path(), videoInfo: videoInfo, progressWatcher: UploadProgressListener { progress in + progressSubject?.send(progress) + }) return .success(()) } catch { return .failure(.failedSendingMedia) @@ -308,7 +312,7 @@ class RoomProxy: RoomProxyProtocol { } } - func sendAudio(url: URL, audioInfo: AudioInfo) async -> Result { + func sendAudio(url: URL, audioInfo: AudioInfo, progressSubject: CurrentValueSubject?) async -> Result { sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) defer { sendMessageBackgroundTask?.stop() @@ -316,7 +320,9 @@ class RoomProxy: RoomProxyProtocol { return await Task.dispatch(on: userInitiatedDispatchQueue) { do { - try self.room.sendAudio(url: url.path(), audioInfo: audioInfo, progressWatcher: nil) + try self.room.sendAudio(url: url.path(), audioInfo: audioInfo, progressWatcher: UploadProgressListener { progress in + progressSubject?.send(progress) + }) return .success(()) } catch { return .failure(.failedSendingMedia) @@ -324,7 +330,7 @@ class RoomProxy: RoomProxyProtocol { } } - func sendFile(url: URL, fileInfo: FileInfo) async -> Result { + func sendFile(url: URL, fileInfo: FileInfo, progressSubject: CurrentValueSubject?) async -> Result { sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) defer { sendMessageBackgroundTask?.stop() @@ -332,7 +338,9 @@ class RoomProxy: RoomProxyProtocol { return await Task.dispatch(on: userInitiatedDispatchQueue) { do { - try self.room.sendFile(url: url.path(), fileInfo: fileInfo, progressWatcher: nil) + try self.room.sendFile(url: url.path(), fileInfo: fileInfo, progressWatcher: UploadProgressListener { progress in + progressSubject?.send(progress) + }) return .success(()) } catch { return .failure(.failedSendingMedia) @@ -611,3 +619,17 @@ private class RoomTimelineListener: TimelineListener { onUpdateClosure(diff) } } + +private 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)) + } + } +} diff --git a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift index 639824812..cc5566692 100644 --- a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift @@ -93,13 +93,13 @@ protocol RoomProxyProtocol { func sendReaction(_ reaction: String, to eventID: String) async -> Result - func sendImage(url: URL, thumbnailURL: URL, imageInfo: ImageInfo) async -> Result + func sendImage(url: URL, thumbnailURL: URL, imageInfo: ImageInfo, progressSubject: CurrentValueSubject?) async -> Result - func sendVideo(url: URL, thumbnailURL: URL, videoInfo: VideoInfo) async -> Result + func sendVideo(url: URL, thumbnailURL: URL, videoInfo: VideoInfo, progressSubject: CurrentValueSubject?) async -> Result - func sendAudio(url: URL, audioInfo: AudioInfo) async -> Result + func sendAudio(url: URL, audioInfo: AudioInfo, progressSubject: CurrentValueSubject?) async -> Result - func sendFile(url: URL, fileInfo: FileInfo) async -> Result + func sendFile(url: URL, fileInfo: FileInfo, progressSubject: CurrentValueSubject?) async -> Result /// Retries sending a failed message given its transaction ID func retrySend(transactionID: String) async