// // Copyright 2022-2024 New Vector Ltd. // // SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial // Please see LICENSE files in the repository root for full details. // import Combine import MatrixRustSDK import SwiftUI typealias MediaUploadPreviewScreenViewModelType = StateStoreViewModelV2 class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType, MediaUploadPreviewScreenViewModelProtocol { private let userIndicatorController: UserIndicatorControllerProtocol private let roomProxy: JoinedRoomProxyProtocol private let mediaUploadingPreprocessor: MediaUploadingPreprocessor private let url: URL private let threadRootEventID: String? private var processingTask: Task, Never> private var requestHandle: SendAttachmentJoinHandleProtocol? private var actionsSubject: PassthroughSubject = .init() var actions: AnyPublisher { actionsSubject.eraseToAnyPublisher() } init(userIndicatorController: UserIndicatorControllerProtocol, roomProxy: JoinedRoomProxyProtocol, mediaUploadingPreprocessor: MediaUploadingPreprocessor, title: String?, url: URL, threadRootEventID: String?, shouldShowCaptionWarning: Bool) { self.userIndicatorController = userIndicatorController self.roomProxy = roomProxy self.mediaUploadingPreprocessor = mediaUploadingPreprocessor self.url = url self.threadRootEventID = threadRootEventID // Start processing the media whilst the user is reviewing it/adding a caption. processingTask = Task { await mediaUploadingPreprocessor.processMedia(at: url) } super.init(initialViewState: MediaUploadPreviewScreenViewState(url: url, title: title, shouldShowCaptionWarning: shouldShowCaptionWarning, isRoomEncrypted: roomProxy.infoPublisher.value.isEncrypted)) } override func process(viewAction: MediaUploadPreviewScreenViewAction) { // Get the current caption before all the processing starts. let caption = state.bindings.caption.nonBlankString switch viewAction { case .send: startLoading() Task { switch await processingTask.value { case .success(let mediaInfo): switch await sendAttachment(mediaInfo: mediaInfo, caption: caption, threadRootEventID: threadRootEventID) { case .success: actionsSubject.send(.dismiss) case .failure(let error): MXLog.error("Failed sending attachment with error: \(error)") showError(label: L10n.screenMediaUploadPreviewErrorFailedSending) } stopLoading() case .failure(let error): MXLog.error("Failed processing media to upload with error: \(error)") showError(label: L10n.screenMediaUploadPreviewErrorFailedProcessing) stopLoading() } } case .cancel: requestHandle?.cancel() actionsSubject.send(.dismiss) } } func stopProcessing() { processingTask.cancel() } // MARK: - Private private func sendAttachment(mediaInfo: MediaInfo, caption: String?, threadRootEventID: String?) async -> Result { let requestHandle: ((SendAttachmentJoinHandleProtocol) -> Void) = { [weak self] handle in self?.requestHandle = handle } switch mediaInfo { case let .image(imageURL, thumbnailURL, imageInfo): return await roomProxy.timeline.sendImage(url: imageURL, thumbnailURL: thumbnailURL, imageInfo: imageInfo, caption: caption, threadRootEventID: threadRootEventID, requestHandle: requestHandle) case let .video(videoURL, thumbnailURL, videoInfo): return await roomProxy.timeline.sendVideo(url: videoURL, thumbnailURL: thumbnailURL, videoInfo: videoInfo, caption: caption, threadRootEventID: threadRootEventID, requestHandle: requestHandle) case let .audio(audioURL, audioInfo): return await roomProxy.timeline.sendAudio(url: audioURL, audioInfo: audioInfo, caption: caption, threadRootEventID: threadRootEventID, requestHandle: requestHandle) case let .file(fileURL, fileInfo): return await roomProxy.timeline.sendFile(url: fileURL, fileInfo: fileInfo, caption: caption, threadRootEventID: threadRootEventID, requestHandle: requestHandle) } } private static let loadingIndicatorIdentifier = "\(MediaUploadPreviewScreenViewModel.self)-Loading" private func startLoading() { userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier, type: .modal(progress: .indeterminate, interactiveDismissDisabled: false, allowsInteraction: true), title: L10n.commonSending, persistent: true)) state.shouldDisableInteraction = true } private func stopLoading() { userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier) state.shouldDisableInteraction = false requestHandle = nil } private func showError(label: String) { userIndicatorController.submitIndicator(UserIndicator(title: label)) } } extension NSAttributedString { var nonBlankString: String? { guard !string.isBlank else { return nil } return string } }