diff --git a/ElementX/Resources/Localizations/en.lproj/Localizable.strings b/ElementX/Resources/Localizations/en.lproj/Localizable.strings index b198d8734..d674ebdc3 100644 --- a/ElementX/Resources/Localizations/en.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en.lproj/Localizable.strings @@ -240,6 +240,7 @@ "common_poll_summary" = "Poll: %1$@"; "common_poll_total_votes" = "Total votes: %1$@"; "common_poll_undisclosed_text" = "Results will show after the poll has ended"; +"common_preparing" = "Preparing…"; "common_privacy_policy" = "Privacy policy"; "common_reaction" = "Reaction"; "common_reactions" = "Reactions"; diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index e8790da54..cb1474481 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -859,6 +859,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { let stackCoordinator = NavigationStackCoordinator() let roomDetailsEditParameters = RoomDetailsEditScreenCoordinatorParameters(roomProxy: roomProxy, + clientProxy: userSession.clientProxy, mediaProvider: userSession.mediaProvider, mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: appSettings), navigationStackCoordinator: stackCoordinator, @@ -949,13 +950,14 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { animated: Bool) { let stackCoordinator = NavigationStackCoordinator() - let parameters = MediaUploadPreviewScreenCoordinatorParameters(timelineController: timelineController, - userIndicatorController: userIndicatorController, - mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: appSettings), + let parameters = MediaUploadPreviewScreenCoordinatorParameters(url: url, title: url.lastPathComponent, - url: url, + isRoomEncrypted: roomProxy.infoPublisher.value.isEncrypted, shouldShowCaptionWarning: appSettings.shouldShowMediaCaptionWarning, - isRoomEncrypted: roomProxy.infoPublisher.value.isEncrypted) + mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: appSettings), + timelineController: timelineController, + clientProxy: userSession.clientProxy, + userIndicatorController: userIndicatorController) let mediaUploadPreviewScreenCoordinator = MediaUploadPreviewScreenCoordinator(parameters: parameters) diff --git a/ElementX/Sources/Generated/Strings.swift b/ElementX/Sources/Generated/Strings.swift index 4b70c4e81..37f4fb0c0 100644 --- a/ElementX/Sources/Generated/Strings.swift +++ b/ElementX/Sources/Generated/Strings.swift @@ -538,6 +538,8 @@ internal enum L10n { internal static func commonPollVotesCount(_ p1: Int) -> String { return L10n.tr("Localizable", "common_poll_votes_count", p1) } + /// Preparing… + internal static var commonPreparing: String { return L10n.tr("Localizable", "common_preparing") } /// Privacy policy internal static var commonPrivacyPolicy: String { return L10n.tr("Localizable", "common_privacy_policy") } /// Reaction diff --git a/ElementX/Sources/Mocks/ClientProxyMock.swift b/ElementX/Sources/Mocks/ClientProxyMock.swift index 40d9c377b..9410384fe 100644 --- a/ElementX/Sources/Mocks/ClientProxyMock.swift +++ b/ElementX/Sources/Mocks/ClientProxyMock.swift @@ -20,6 +20,8 @@ struct ClientProxyMockConfiguration { var timelineMediaVisibility = TimelineMediaVisibility.always var hideInviteAvatars = false + + var maxMediaUploadSize: UInt = 100 * 1024 * 1024 } enum ClientProxyMockError: Error { @@ -101,5 +103,7 @@ extension ClientProxyMock { underlyingTimelineMediaVisibilityPublisher = CurrentValueSubject(configuration.timelineMediaVisibility).asCurrentValuePublisher() underlyingHideInviteAvatarsPublisher = CurrentValueSubject(configuration.hideInviteAvatars).asCurrentValuePublisher() + + underlyingMaxMediaUploadSize = .success(configuration.maxMediaUploadSize) } } diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 3f09f3e23..4724b2a39 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -2200,6 +2200,23 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable { } var underlyingIsLiveKitRTCSupported: Bool! var isLiveKitRTCSupportedClosure: (() async -> Bool)? + var maxMediaUploadSizeCallsCount = 0 + var maxMediaUploadSizeCalled: Bool { + return maxMediaUploadSizeCallsCount > 0 + } + + var maxMediaUploadSize: Result { + get async { + maxMediaUploadSizeCallsCount += 1 + if let maxMediaUploadSizeClosure = maxMediaUploadSizeClosure { + return await maxMediaUploadSizeClosure() + } else { + return underlyingMaxMediaUploadSize + } + } + } + var underlyingMaxMediaUploadSize: Result! + var maxMediaUploadSizeClosure: (() async -> Result)? //MARK: - isOnlyDeviceLeft diff --git a/ElementX/Sources/Other/Extensions/FileManager.swift b/ElementX/Sources/Other/Extensions/FileManager.swift index 92c62f20e..39795aeb2 100644 --- a/ElementX/Sources/Other/Extensions/FileManager.swift +++ b/ElementX/Sources/Other/Extensions/FileManager.swift @@ -49,10 +49,10 @@ extension FileManager { /// Retrieve a file's disk size /// - Parameter url: the file URL /// - Returns: the size in bytes - func sizeForItem(at url: URL) throws -> Double { - let attributes = try attributesOfItem(atPath: url.path()) + func sizeForItem(at url: URL) throws -> UInt { + let attributes = try attributesOfItem(atPath: url.path(percentEncoded: false)) - guard let size = attributes[FileAttributeKey.size] as? Double else { + guard let size = attributes[FileAttributeKey.size] as? UInt else { throw FileManagerError.invalidFileSize } diff --git a/ElementX/Sources/Screens/BugReportScreen/BugReportScreenViewModel.swift b/ElementX/Sources/Screens/BugReportScreen/BugReportScreenViewModel.swift index c27a88ac4..0ccf16c12 100644 --- a/ElementX/Sources/Screens/BugReportScreen/BugReportScreenViewModel.swift +++ b/ElementX/Sources/Screens/BugReportScreen/BugReportScreenViewModel.swift @@ -72,7 +72,7 @@ class BugReportScreenViewModel: BugReportScreenViewModelType, BugReportScreenVie private static func validate(_ logFiles: [URL]) -> Bool { for fileURL in logFiles { if let size = try? FileManager.default.sizeForItem(at: fileURL), - size > Double(1024 * 1024 * 1024) { // Consider anything over 1GB to be excessive. + size > 1024 * 1024 * 1024 { // Consider anything over 1GB to be excessive. return false } } diff --git a/ElementX/Sources/Screens/FilePreviewScreen/TimelineMediaPreviewDataSource.swift b/ElementX/Sources/Screens/FilePreviewScreen/TimelineMediaPreviewDataSource.swift index 59ba9819d..5d9e638bb 100644 --- a/ElementX/Sources/Screens/FilePreviewScreen/TimelineMediaPreviewDataSource.swift +++ b/ElementX/Sources/Screens/FilePreviewScreen/TimelineMediaPreviewDataSource.swift @@ -244,12 +244,12 @@ enum TimelineMediaPreviewItem: Equatable { } } - var fileSize: Double? { + var fileSize: UInt? { previewItemURL.flatMap { try? FileManager.default.sizeForItem(at: $0) } ?? expectedFileSize } - private var expectedFileSize: Double? { - let fileSize: UInt? = switch timelineItem { + private var expectedFileSize: UInt? { + switch timelineItem { case let audioItem as AudioRoomTimelineItem: audioItem.content.fileSize case let fileItem as FileRoomTimelineItem: @@ -261,8 +261,6 @@ enum TimelineMediaPreviewItem: Equatable { default: nil } - - return fileSize.map(Double.init) } var caption: String? { diff --git a/ElementX/Sources/Screens/MediaUploadPreviewScreen/MediaUploadPreviewScreenCoordinator.swift b/ElementX/Sources/Screens/MediaUploadPreviewScreen/MediaUploadPreviewScreenCoordinator.swift index def1a7134..af3f37166 100644 --- a/ElementX/Sources/Screens/MediaUploadPreviewScreen/MediaUploadPreviewScreenCoordinator.swift +++ b/ElementX/Sources/Screens/MediaUploadPreviewScreen/MediaUploadPreviewScreenCoordinator.swift @@ -9,13 +9,14 @@ import Combine import SwiftUI struct MediaUploadPreviewScreenCoordinatorParameters { - let timelineController: TimelineControllerProtocol - let userIndicatorController: UserIndicatorControllerProtocol - let mediaUploadingPreprocessor: MediaUploadingPreprocessor - let title: String? let url: URL - let shouldShowCaptionWarning: Bool + let title: String? let isRoomEncrypted: Bool + let shouldShowCaptionWarning: Bool + let mediaUploadingPreprocessor: MediaUploadingPreprocessor + let timelineController: TimelineControllerProtocol + let clientProxy: ClientProxyProtocol + let userIndicatorController: UserIndicatorControllerProtocol } enum MediaUploadPreviewScreenCoordinatorAction { @@ -32,13 +33,14 @@ final class MediaUploadPreviewScreenCoordinator: CoordinatorProtocol { } init(parameters: MediaUploadPreviewScreenCoordinatorParameters) { - viewModel = MediaUploadPreviewScreenViewModel(timelineController: parameters.timelineController, - userIndicatorController: parameters.userIndicatorController, - mediaUploadingPreprocessor: parameters.mediaUploadingPreprocessor, + viewModel = MediaUploadPreviewScreenViewModel(url: parameters.url, title: parameters.title, - url: parameters.url, + isRoomEncrypted: parameters.isRoomEncrypted, shouldShowCaptionWarning: parameters.shouldShowCaptionWarning, - isRoomEncrypted: parameters.isRoomEncrypted) + mediaUploadingPreprocessor: parameters.mediaUploadingPreprocessor, + timelineController: parameters.timelineController, + clientProxy: parameters.clientProxy, + userIndicatorController: parameters.userIndicatorController) } func start() { diff --git a/ElementX/Sources/Screens/MediaUploadPreviewScreen/MediaUploadPreviewScreenModels.swift b/ElementX/Sources/Screens/MediaUploadPreviewScreen/MediaUploadPreviewScreenModels.swift index acf242ca6..be6ec26e5 100644 --- a/ElementX/Sources/Screens/MediaUploadPreviewScreen/MediaUploadPreviewScreenModels.swift +++ b/ElementX/Sources/Screens/MediaUploadPreviewScreen/MediaUploadPreviewScreenModels.swift @@ -27,6 +27,12 @@ struct MediaUploadPreviewScreenBindings: BindableState { var selectedRange = NSRange(location: 0, length: 0) var isPresentingMediaCaptionWarning = false + var alertInfo: AlertInfo? +} + +enum MediaUploadPreviewAlertType: Hashable { + case maxUploadSizeUnknown + case maxUploadSizeExceeded(limit: UInt) } enum MediaUploadPreviewScreenViewAction { diff --git a/ElementX/Sources/Screens/MediaUploadPreviewScreen/MediaUploadPreviewScreenViewModel.swift b/ElementX/Sources/Screens/MediaUploadPreviewScreen/MediaUploadPreviewScreenViewModel.swift index a38059aab..79996877f 100644 --- a/ElementX/Sources/Screens/MediaUploadPreviewScreen/MediaUploadPreviewScreenViewModel.swift +++ b/ElementX/Sources/Screens/MediaUploadPreviewScreen/MediaUploadPreviewScreenViewModel.swift @@ -20,6 +20,7 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType, private var processingTask: Task, Never> private var requestHandle: SendAttachmentJoinHandleProtocol? + private let clientProxy: ClientProxyProtocol private var actionsSubject: PassthroughSubject = .init() @@ -27,20 +28,22 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType, actionsSubject.eraseToAnyPublisher() } - init(timelineController: TimelineControllerProtocol, - userIndicatorController: UserIndicatorControllerProtocol, - mediaUploadingPreprocessor: MediaUploadingPreprocessor, + init(url: URL, title: String?, - url: URL, + isRoomEncrypted: Bool, shouldShowCaptionWarning: Bool, - isRoomEncrypted: Bool) { - self.timelineController = timelineController - self.userIndicatorController = userIndicatorController - self.mediaUploadingPreprocessor = mediaUploadingPreprocessor + mediaUploadingPreprocessor: MediaUploadingPreprocessor, + timelineController: TimelineControllerProtocol, + clientProxy: ClientProxyProtocol, + userIndicatorController: UserIndicatorControllerProtocol) { self.url = url + self.mediaUploadingPreprocessor = mediaUploadingPreprocessor + self.timelineController = timelineController + self.clientProxy = clientProxy + self.userIndicatorController = userIndicatorController // Start processing the media whilst the user is reviewing it/adding a caption. - processingTask = Task { await mediaUploadingPreprocessor.processMedia(at: url) } + processingTask = Self.processMedia(at: url, preprocessor: mediaUploadingPreprocessor, clientProxy: clientProxy) super.init(initialViewState: MediaUploadPreviewScreenViewState(url: url, title: title, @@ -57,6 +60,8 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType, startLoading() Task { + defer { stopLoading() } + switch await processingTask.value { case .success(let mediaInfo): switch await sendAttachment(mediaInfo: mediaInfo, @@ -67,12 +72,13 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType, MXLog.error("Failed sending attachment with error: \(error)") showError(label: L10n.screenMediaUploadPreviewErrorFailedSending) } - - stopLoading() + case .failure(.maxUploadSizeUnknown): + showAlert(.maxUploadSizeUnknown) + case .failure(.maxUploadSizeExceeded(let limit)): + showAlert(.maxUploadSizeExceeded(limit: limit)) case .failure(let error): MXLog.error("Failed processing media to upload with error: \(error)") showError(label: L10n.screenMediaUploadPreviewErrorFailedProcessing) - stopLoading() } } @@ -88,6 +94,15 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType, // MARK: - Private + private static func processMedia(at url: URL, + preprocessor: MediaUploadingPreprocessor, + clientProxy: ClientProxyProtocol) -> Task, Never> { + Task { + guard case let .success(maxUploadSize) = await clientProxy.maxMediaUploadSize else { return .failure(.maxUploadSizeUnknown) } + return await preprocessor.processMedia(at: url, maxUploadSize: maxUploadSize) + } + } + private func sendAttachment(mediaInfo: MediaInfo, caption: String?) async -> Result { let requestHandle: ((SendAttachmentJoinHandleProtocol) -> Void) = { [weak self] handle in self?.requestHandle = handle @@ -124,8 +139,9 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType, private func startLoading() { userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier, type: .modal(progress: .indeterminate, interactiveDismissDisabled: false, allowsInteraction: true), - title: L10n.commonSending, - persistent: true)) + title: L10n.commonPreparing, + persistent: true), + delay: .milliseconds(100)) state.shouldDisableInteraction = true } @@ -139,6 +155,26 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType, private func showError(label: String) { userIndicatorController.submitIndicator(UserIndicator(title: label)) } + + private func showAlert(_ alertType: MediaUploadPreviewAlertType) { + switch alertType { + case .maxUploadSizeUnknown: + state.bindings.alertInfo = .init(id: alertType, + title: L10n.commonSomethingWentWrong, + message: L10n.screenMediaUploadPreviewErrorCouldNotBeUploaded, + primaryButton: .init(title: L10n.actionTryAgain) { [weak self] in + guard let self else { return } + processingTask = Self.processMedia(at: url, preprocessor: mediaUploadingPreprocessor, clientProxy: clientProxy) + process(viewAction: .send) + }, + secondaryButton: .init(title: L10n.actionCancel, role: .cancel) { }) + case .maxUploadSizeExceeded(let limit): + state.bindings.alertInfo = .init(id: alertType, + title: L10n.screenMediaUploadPreviewErrorTooLargeTitle, + message: L10n.screenMediaUploadPreviewErrorTooLargeMessage(limit.formatted(.byteCount(style: .file))), + primaryButton: .init(title: L10n.actionCancel, role: .cancel) { }) + } + } } extension NSAttributedString { diff --git a/ElementX/Sources/Screens/MediaUploadPreviewScreen/View/MediaUploadPreviewScreen.swift b/ElementX/Sources/Screens/MediaUploadPreviewScreen/View/MediaUploadPreviewScreen.swift index 1a6883eea..3cd9b916a 100644 --- a/ElementX/Sources/Screens/MediaUploadPreviewScreen/View/MediaUploadPreviewScreen.swift +++ b/ElementX/Sources/Screens/MediaUploadPreviewScreen/View/MediaUploadPreviewScreen.swift @@ -39,6 +39,7 @@ struct MediaUploadPreviewScreen: View { .presentationBackground(.background) // Fix a bug introduced by the caption warning. .preferredColorScheme(colorSchemeOverride) .onAppear(perform: focusComposerIfHardwareKeyboardConnected) + .alert(item: $context.alertInfo) } @ViewBuilder @@ -227,13 +228,14 @@ struct MediaUploadPreviewScreen_Previews: PreviewProvider, TestablePreview { static let snapshotURL = URL.picturesDirectory static let testURL = Bundle.main.url(forResource: "AppIcon60x60@2x", withExtension: "png") - static let viewModel = MediaUploadPreviewScreenViewModel(timelineController: MockTimelineController(), - userIndicatorController: UserIndicatorControllerMock.default, - mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: ServiceLocator.shared.settings), + static let viewModel = MediaUploadPreviewScreenViewModel(url: snapshotURL, title: "App Icon.png", - url: snapshotURL, + isRoomEncrypted: true, shouldShowCaptionWarning: true, - isRoomEncrypted: true) + mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: ServiceLocator.shared.settings), + timelineController: MockTimelineController(), + clientProxy: ClientProxyMock(.init()), + userIndicatorController: UserIndicatorControllerMock.default) static var previews: some View { NavigationStack { MediaUploadPreviewScreen(context: viewModel.context) diff --git a/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenCoordinator.swift b/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenCoordinator.swift index a8aefdb35..63d253b47 100644 --- a/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenCoordinator.swift @@ -10,6 +10,7 @@ import SwiftUI struct RoomDetailsEditScreenCoordinatorParameters { let roomProxy: JoinedRoomProxyProtocol + let clientProxy: ClientProxyProtocol let mediaProvider: MediaProviderProtocol let mediaUploadingPreprocessor: MediaUploadingPreprocessor weak var navigationStackCoordinator: NavigationStackCoordinator? @@ -35,6 +36,7 @@ final class RoomDetailsEditScreenCoordinator: CoordinatorProtocol { self.parameters = parameters viewModel = RoomDetailsEditScreenViewModel(roomProxy: parameters.roomProxy, + clientProxy: parameters.clientProxy, mediaProvider: parameters.mediaProvider, mediaUploadingPreprocessor: parameters.mediaUploadingPreprocessor, userIndicatorController: parameters.userIndicatorController) diff --git a/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenViewModel.swift b/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenViewModel.swift index 5151f3f46..09862d1a6 100644 --- a/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenViewModel.swift @@ -13,6 +13,7 @@ typealias RoomDetailsEditScreenViewModelType = StateStoreViewModel = .init() private let roomProxy: JoinedRoomProxyProtocol + private let clientProxy: ClientProxyProtocol private let userIndicatorController: UserIndicatorControllerProtocol private let mediaUploadingPreprocessor: MediaUploadingPreprocessor @@ -21,10 +22,12 @@ class RoomDetailsEditScreenViewModel: RoomDetailsEditScreenViewModelType, RoomDe } init(roomProxy: JoinedRoomProxyProtocol, + clientProxy: ClientProxyProtocol, mediaProvider: MediaProviderProtocol, mediaUploadingPreprocessor: MediaUploadingPreprocessor, userIndicatorController: UserIndicatorControllerProtocol) { self.roomProxy = roomProxy + self.clientProxy = clientProxy self.mediaUploadingPreprocessor = mediaUploadingPreprocessor self.userIndicatorController = userIndicatorController @@ -72,15 +75,18 @@ class RoomDetailsEditScreenViewModel: RoomDetailsEditScreenViewModelType, RoomDe func didSelectMediaUrl(url: URL) { Task { let userIndicatorID = UUID().uuidString - defer { - userIndicatorController.retractIndicatorWithId(userIndicatorID) - } + defer { userIndicatorController.retractIndicatorWithId(userIndicatorID) } userIndicatorController.submitIndicator(UserIndicator(id: userIndicatorID, type: .modal(progress: .indeterminate, interactiveDismissDisabled: true, allowsInteraction: false), title: L10n.commonLoading, persistent: true)) - let mediaResult = await mediaUploadingPreprocessor.processMedia(at: url) + guard case let .success(maxUploadSize) = await clientProxy.maxMediaUploadSize else { + MXLog.error("Failed to get max upload size") + userIndicatorController.alertInfo = .init(id: .init()) + return + } + let mediaResult = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize) switch mediaResult { case .success(.image): diff --git a/ElementX/Sources/Screens/RoomDetailsEditScreen/View/RoomDetailsEditScreen.swift b/ElementX/Sources/Screens/RoomDetailsEditScreen/View/RoomDetailsEditScreen.swift index 03d0cdb4e..ced843c26 100644 --- a/ElementX/Sources/Screens/RoomDetailsEditScreen/View/RoomDetailsEditScreen.swift +++ b/ElementX/Sources/Screens/RoomDetailsEditScreen/View/RoomDetailsEditScreen.swift @@ -155,6 +155,7 @@ struct RoomDetailsEditScreen_Previews: PreviewProvider, TestablePreview { members: [.mockMeAdmin])) return RoomDetailsEditScreenViewModel(roomProxy: roomProxy, + clientProxy: ClientProxyMock(.init()), mediaProvider: MediaProviderMock(configuration: .init()), mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: ServiceLocator.shared.settings), userIndicatorController: UserIndicatorControllerMock.default) @@ -166,6 +167,7 @@ struct RoomDetailsEditScreen_Previews: PreviewProvider, TestablePreview { members: [.mockAlice])) return RoomDetailsEditScreenViewModel(roomProxy: roomProxy, + clientProxy: ClientProxyMock(.init()), mediaProvider: MediaProviderMock(configuration: .init()), mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: ServiceLocator.shared.settings), userIndicatorController: UserIndicatorControllerMock.default) diff --git a/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/UserDetailsEditScreenViewModel.swift b/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/UserDetailsEditScreenViewModel.swift index 84a1cf4dd..d9089da18 100644 --- a/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/UserDetailsEditScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/UserDetailsEditScreenViewModel.swift @@ -82,21 +82,24 @@ class UserDetailsEditScreenViewModel: UserDetailsEditScreenViewModelType, UserDe func didSelectMediaURL(url: URL) { Task { let userIndicatorID = UUID().uuidString - defer { - userIndicatorController.retractIndicatorWithId(userIndicatorID) - } + defer { userIndicatorController.retractIndicatorWithId(userIndicatorID) } userIndicatorController.submitIndicator(UserIndicator(id: userIndicatorID, type: .modal(progress: .indeterminate, interactiveDismissDisabled: true, allowsInteraction: false), title: L10n.commonLoading, persistent: true)) - let mediaResult = await mediaUploadingPreprocessor.processMedia(at: url) + guard case let .success(maxUploadSize) = await clientProxy.maxMediaUploadSize else { + MXLog.error("Failed to get max upload size") + userIndicatorController.alertInfo = .init(id: .init()) + return + } + let mediaResult = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize) switch mediaResult { case .success(.image): state.localMedia = try? mediaResult.get() case .failure, .success: - userIndicatorController.alertInfo = .init(id: .init(), title: L10n.commonError, message: L10n.errorUnknown) + userIndicatorController.alertInfo = .init(id: .init()) } } } diff --git a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift index 59de3b6ae..b209039af 100644 --- a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift +++ b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift @@ -163,12 +163,17 @@ final class StartChatScreenCoordinator: CoordinatorProtocol { Task { [weak self] in guard let self else { return } do { - let media = try await parameters.mediaUploadingPreprocessor.processMedia(at: url).get() + guard case let .success(maxUploadSize) = await parameters.userSession.clientProxy.maxMediaUploadSize else { + MXLog.error("Failed to get max upload size") + parameters.userIndicatorController.alertInfo = AlertInfo(id: .init()) + return + } + let media = try await parameters.mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize).get() var parameters = createRoomParameters.value parameters.avatarImageMedia = media createRoomParameters.send(parameters) } catch { - parameters.userIndicatorController.alertInfo = AlertInfo(id: .init(), title: L10n.commonError, message: L10n.errorUnknown) + parameters.userIndicatorController.alertInfo = AlertInfo(id: .init()) } hideLoadingIndicator() } diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index 2b298e7f9..5afc7a4ae 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -310,6 +310,17 @@ class ClientProxy: ClientProxyProtocol { } } } + + var maxMediaUploadSize: Result { + get async { + do { + return try await .success(UInt(client.getMaxMediaUploadSize())) + } catch { + MXLog.error("Failed checking the max media upload size with error: \(error)") + return .failure(.sdkError(error)) + } + } + } private(set) lazy var pusherNotificationClientIdentifier: String? = { // NOTE: The result is stored as part of the restoration token. Any changes diff --git a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift index 8fdcb9b31..563c3145e 100644 --- a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift +++ b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift @@ -130,6 +130,8 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol { var isLiveKitRTCSupported: Bool { get async } + var maxMediaUploadSize: Result { get async } + func isOnlyDeviceLeft() async -> Result func startSync() diff --git a/ElementX/Sources/Services/Media/MediaUploadingPreprocessor.swift b/ElementX/Sources/Services/Media/MediaUploadingPreprocessor.swift index b298aca38..4e33a3247 100644 --- a/ElementX/Sources/Services/Media/MediaUploadingPreprocessor.swift +++ b/ElementX/Sources/Services/Media/MediaUploadingPreprocessor.swift @@ -11,6 +11,9 @@ import UIKit import UniformTypeIdentifiers indirect enum MediaUploadingPreprocessorError: Error { + case maxUploadSizeUnknown + case maxUploadSizeExceeded(limit: UInt) + case failedProcessingMedia(Error) case failedProcessingImage(MediaUploadingPreprocessorError) @@ -95,7 +98,7 @@ struct MediaUploadingPreprocessor { /// from images and retrieve associated media information /// - Parameter url: the file URL /// - Returns: a specific type of `MediaInfo` depending on the file type and its associated details - func processMedia(at url: URL) async -> Result { + func processMedia(at url: URL, maxUploadSize: UInt) async -> Result { // Start by copying the file to a unique temporary location in order to avoid conflicts if processing it multiple times // All the other operations will be made relative to it let uniqueFolder = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) @@ -110,17 +113,17 @@ struct MediaUploadingPreprocessor { // Process unknown types as plain files guard let type = UTType(filenameExtension: newURL.pathExtension), let mimeType = type.preferredMIMEType else { - return processFile(at: newURL, mimeType: "application/octet-stream") + return processFile(at: newURL, mimeType: "application/octet-stream", maxUploadSize: maxUploadSize) } if type.conforms(to: .image) { - return processImage(at: &newURL, type: type, mimeType: mimeType) + return processImage(at: &newURL, type: type, mimeType: mimeType, maxUploadSize: maxUploadSize) } else if type.conforms(to: .movie) || type.conforms(to: .video) { - return await processVideo(at: newURL) + return await processVideo(at: newURL, maxUploadSize: maxUploadSize) } else if type.conforms(to: .audio) { - return await processAudio(at: newURL, mimeType: mimeType) + return await processAudio(at: newURL, mimeType: mimeType, maxUploadSize: maxUploadSize) } else { - return processFile(at: newURL, mimeType: mimeType) + return processFile(at: newURL, mimeType: mimeType, maxUploadSize: maxUploadSize) } } @@ -132,7 +135,7 @@ struct MediaUploadingPreprocessor { /// - type: its UTType /// - mimeType: the mimeType extracted from the UTType /// - Returns: Returns a `MediaInfo.image` containing the URLs for the modified image and its thumbnail plus the corresponding `ImageInfo` - private func processImage(at url: inout URL, type: UTType, mimeType: String) -> Result { + private func processImage(at url: inout URL, type: UTType, mimeType: String, maxUploadSize: UInt) -> Result { do { try stripLocationFromImage(at: url, type: type) @@ -161,18 +164,20 @@ struct MediaUploadingPreprocessor { return .failure(.failedProcessingImage(.failedStrippingLocationData)) } - let fileSize = (try? UInt64(FileManager.default.sizeForItem(at: url))) ?? 0 - let thumbnailFileSize = (try? UInt64(FileManager.default.sizeForItem(at: thumbnailResult.url))) ?? 0 + let fileSize = (try? FileManager.default.sizeForItem(at: url)) ?? 0 + let thumbnailFileSize = (try? FileManager.default.sizeForItem(at: thumbnailResult.url)) ?? 0 + + guard fileSize < maxUploadSize, thumbnailFileSize < maxUploadSize else { return .failure(.maxUploadSizeExceeded(limit: maxUploadSize)) } let thumbnailInfo = ThumbnailInfo(height: UInt64(thumbnailResult.height), width: UInt64(thumbnailResult.width), mimetype: thumbnailResult.mimeType, - size: thumbnailFileSize) + size: UInt64(thumbnailFileSize)) let imageInfo = ImageInfo(height: UInt64(imageSize.height), width: UInt64(imageSize.width), mimetype: mimeType, - size: fileSize, + size: UInt64(fileSize), thumbnailInfo: thumbnailInfo, thumbnailSource: nil, blurhash: thumbnailResult.blurhash, @@ -192,24 +197,26 @@ struct MediaUploadingPreprocessor { /// - type: its UTType /// - mimeType: the mimeType extracted from the UTType /// - Returns: Returns a `MediaInfo.video` containing the URLs for the modified video and its thumbnail plus the corresponding `VideoInfo` - private func processVideo(at url: URL) async -> Result { + private func processVideo(at url: URL, maxUploadSize: UInt) async -> Result { do { - let result = try await convertVideoToMP4(url) + let result = try await convertVideoToMP4(url, targetFileSize: UInt(maxUploadSize)) let thumbnailResult = try await generateThumbnailForVideoAt(result.url) - let videoSize = (try? UInt64(FileManager.default.sizeForItem(at: result.url))) ?? 0 - let thumbnailSize = (try? UInt64(FileManager.default.sizeForItem(at: thumbnailResult.url))) ?? 0 + let videoSize = (try? FileManager.default.sizeForItem(at: result.url)) ?? 0 + let thumbnailSize = (try? FileManager.default.sizeForItem(at: thumbnailResult.url)) ?? 0 + + guard videoSize < maxUploadSize, thumbnailSize < maxUploadSize else { return .failure(.maxUploadSizeExceeded(limit: maxUploadSize)) } let thumbnailInfo = ThumbnailInfo(height: UInt64(thumbnailResult.height), width: UInt64(thumbnailResult.width), mimetype: thumbnailResult.mimeType, - size: thumbnailSize) + size: UInt64(thumbnailSize)) let videoInfo = VideoInfo(duration: result.duration, height: UInt64(result.height), width: UInt64(result.width), mimetype: result.mimeType, - size: videoSize, + size: UInt64(videoSize), thumbnailInfo: thumbnailInfo, thumbnailSource: nil, blurhash: thumbnailResult.blurhash) @@ -227,15 +234,17 @@ struct MediaUploadingPreprocessor { /// - url: The audio URL /// - mimeType: the mimeType extracted from the UTType /// - Returns: Returns a `MediaInfo.audio` containing the file URL plus the corresponding `AudioInfo` - private func processAudio(at url: URL, mimeType: String?) async -> Result { - let fileSize = (try? UInt64(FileManager.default.sizeForItem(at: url))) ?? 0 + private func processAudio(at url: URL, mimeType: String?, maxUploadSize: UInt) async -> Result { + let fileSize = (try? FileManager.default.sizeForItem(at: url)) ?? 0 + + guard fileSize < maxUploadSize else { return .failure(.maxUploadSizeExceeded(limit: maxUploadSize)) } let asset = AVURLAsset(url: url) guard let durationInSeconds = try? await asset.load(.duration).seconds else { return .failure(.failedProcessingAudio) } - let audioInfo = AudioInfo(duration: durationInSeconds, size: fileSize, mimetype: mimeType) + let audioInfo = AudioInfo(duration: durationInSeconds, size: UInt64(fileSize), mimetype: mimeType) return .success(.audio(audioURL: url, audioInfo: audioInfo)) } @@ -245,10 +254,12 @@ struct MediaUploadingPreprocessor { /// - type: its UTType /// - mimeType: the mimeType extracted from the UTType /// - Returns: Returns a `MediaInfo.file` containing the file URL plus the corresponding `FileInfo` - private func processFile(at url: URL, mimeType: String?) -> Result { - let fileSize = (try? UInt64(FileManager.default.sizeForItem(at: url))) ?? 0 + private func processFile(at url: URL, mimeType: String?, maxUploadSize: UInt) -> Result { + let fileSize = (try? FileManager.default.sizeForItem(at: url)) ?? 0 - let fileInfo = FileInfo(mimetype: mimeType, size: fileSize, thumbnailInfo: nil, thumbnailSource: nil) + guard fileSize < maxUploadSize else { return .failure(.maxUploadSizeExceeded(limit: maxUploadSize)) } + + let fileInfo = FileInfo(mimetype: mimeType, size: UInt64(fileSize), thumbnailInfo: nil, thumbnailSource: nil) return .success(.file(fileURL: url, fileInfo: fileInfo)) } @@ -388,7 +399,7 @@ struct MediaUploadingPreprocessor { /// - url: the original video URL /// - targetFileSize: the maximum resulting file size. 90% of this will be used /// - Returns: the URL for the resulting video and its media info as a `VideoProcessingResult` - private func convertVideoToMP4(_ url: URL, targetFileSize: UInt = 0) async throws(MediaUploadingPreprocessorError) -> VideoProcessingInfo { + private func convertVideoToMP4(_ url: URL, targetFileSize: UInt) async throws(MediaUploadingPreprocessorError) -> VideoProcessingInfo { let asset = AVURLAsset(url: url) let presetName = appSettings.optimizeMediaUploads ? AVAssetExportPreset1280x720 : AVAssetExportPreset1920x1080 diff --git a/UnitTests/Sources/MediaUploadPreviewScreenViewModelTests.swift b/UnitTests/Sources/MediaUploadPreviewScreenViewModelTests.swift index eaff30b75..353a454b8 100644 --- a/UnitTests/Sources/MediaUploadPreviewScreenViewModelTests.swift +++ b/UnitTests/Sources/MediaUploadPreviewScreenViewModelTests.swift @@ -12,6 +12,7 @@ import XCTest @MainActor class MediaUploadPreviewScreenViewModelTests: XCTestCase { var timelineProxy: TimelineProxyMock! + var clientProxy: ClientProxyMock! var viewModel: MediaUploadPreviewScreenViewModel! var context: MediaUploadPreviewScreenViewModel.Context { viewModel.context } @@ -89,6 +90,54 @@ class MediaUploadPreviewScreenViewModelTests: XCTestCase { try await send() } + func testUploadWithUnknownMaxUploadSize() async throws { + // Given an upload screen that is unable to fetch the max upload size. + setUpViewModel(url: imageURL, expectedCaption: nil, maxUploadSizeResult: .failure(.sdkError(ClientProxyMockError.generic))) + XCTAssertFalse(context.viewState.shouldDisableInteraction) + XCTAssertNil(context.alertInfo) + + // When attempting to send the media. + let deferredAlert = deferFulfillment(context.observe(\.viewState.bindings.alertInfo)) { $0 != nil } + context.send(viewAction: .send) + + XCTAssertTrue(context.viewState.shouldDisableInteraction, "The interaction should be disabled while sending.") + + // Then alert should be shown to tell the user it failed. + try await deferredAlert.fulfill() + + XCTAssertFalse(context.viewState.shouldDisableInteraction) + XCTAssertEqual(context.alertInfo?.id, .maxUploadSizeUnknown) + + // When trying with the max upload size now available. + let deferredDismiss = deferFulfillment(viewModel.actions) { $0 == .dismiss } + clientProxy.underlyingMaxMediaUploadSize = .success(100 * 1024 * 1024) + context.alertInfo?.primaryButton.action?() + + XCTAssertTrue(context.viewState.shouldDisableInteraction, "The interaction should be disabled while retrying.") + + // Then the file should upload successfully. + try await deferredDismiss.fulfill() + } + + func testUploadExceedingMaxUploadSize() async throws { + // Given an upload screen with a really small max upload size. + setUpViewModel(url: imageURL, expectedCaption: nil, maxUploadSizeResult: .success(100)) + XCTAssertFalse(context.viewState.shouldDisableInteraction) + XCTAssertNil(context.alertInfo) + + // When attempting to send an image that is larger the limit. + let deferredAlert = deferFulfillment(context.observe(\.viewState.bindings.alertInfo)) { $0 != nil } + context.send(viewAction: .send) + + XCTAssertTrue(context.viewState.shouldDisableInteraction, "The interaction should be disabled while sending.") + + // Then an alert should be shown to inform the user of the max upload size. + try await deferredAlert.fulfill() + + XCTAssertFalse(context.viewState.shouldDisableInteraction) + XCTAssertEqual(context.alertInfo?.id, .maxUploadSizeExceeded(limit: 100)) + } + // MARK: - Helpers private var audioURL: URL { assertResourceURL(filename: "test_audio.mp3") } @@ -104,7 +153,7 @@ class MediaUploadPreviewScreenViewModelTests: XCTestCase { return url } - private func setUpViewModel(url: URL, expectedCaption: String?) { + private func setUpViewModel(url: URL, expectedCaption: String?, maxUploadSizeResult: Result? = nil) { timelineProxy = TimelineProxyMock(.init()) timelineProxy.sendAudioUrlAudioInfoCaptionRequestHandleClosure = { [weak self] _, _, caption, _ in self?.verifyCaption(caption, expectedCaption: expectedCaption) ?? .failure(.sdkError(TestError.unknown)) @@ -119,13 +168,19 @@ class MediaUploadPreviewScreenViewModelTests: XCTestCase { self?.verifyCaption(caption, expectedCaption: expectedCaption) ?? .failure(.sdkError(TestError.unknown)) } - viewModel = MediaUploadPreviewScreenViewModel(timelineController: MockTimelineController(timelineProxy: timelineProxy), - userIndicatorController: UserIndicatorControllerMock(), - mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: ServiceLocator.shared.settings), + clientProxy = ClientProxyMock(.init()) + if let maxUploadSizeResult { + clientProxy.underlyingMaxMediaUploadSize = maxUploadSizeResult + } + + viewModel = MediaUploadPreviewScreenViewModel(url: url, title: "Some File", - url: url, + isRoomEncrypted: true, shouldShowCaptionWarning: true, - isRoomEncrypted: true) + mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: ServiceLocator.shared.settings), + timelineController: MockTimelineController(timelineProxy: timelineProxy), + clientProxy: clientProxy, + userIndicatorController: UserIndicatorControllerMock()) } private func verifyCaption(_ caption: String?, expectedCaption: String?) -> Result { diff --git a/UnitTests/Sources/MediaUploadingPreprocessorTests.swift b/UnitTests/Sources/MediaUploadingPreprocessorTests.swift index 50ff543ee..168c0e606 100644 --- a/UnitTests/Sources/MediaUploadingPreprocessorTests.swift +++ b/UnitTests/Sources/MediaUploadingPreprocessorTests.swift @@ -11,6 +11,7 @@ import XCTest @testable import ElementX final class MediaUploadingPreprocessorTests: XCTestCase { + let maxUploadSize: UInt = 100 * 1024 * 1024 var appSettings: AppSettings! var mediaUploadingPreprocessor: MediaUploadingPreprocessor! @@ -32,7 +33,7 @@ final class MediaUploadingPreprocessorTests: XCTestCase { return } - guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url), + guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize), case let .audio(audioURL, audioInfo) = result else { XCTFail("Failed processing asset") return @@ -55,7 +56,7 @@ final class MediaUploadingPreprocessorTests: XCTestCase { return } - guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url), + guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize), case let .video(videoURL, thumbnailURL, videoInfo) = result else { XCTFail("Failed processing asset") return @@ -92,7 +93,7 @@ final class MediaUploadingPreprocessorTests: XCTestCase { // Repeat with optimised media setting appSettings.optimizeMediaUploads = true - guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url), + guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize), case let .video(optimizedVideoURL, _, optimizedVideoInfo) = optimizedResult else { XCTFail("Failed processing asset") return @@ -118,7 +119,7 @@ final class MediaUploadingPreprocessorTests: XCTestCase { return } - guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url), + guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize), case let .video(videoURL, thumbnailURL, videoInfo) = result else { XCTFail("Failed processing asset") return @@ -155,7 +156,7 @@ final class MediaUploadingPreprocessorTests: XCTestCase { // Repeat with optimised media setting appSettings.optimizeMediaUploads = true - guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url), + guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize), case let .video(optimizedVideoURL, _, optimizedVideoInfo) = optimizedResult else { XCTFail("Failed processing asset") return @@ -178,7 +179,7 @@ final class MediaUploadingPreprocessorTests: XCTestCase { return } - guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url), + guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize), case let .image(convertedImageURL, thumbnailURL, imageInfo) = result else { XCTFail("Failed processing asset") return @@ -202,7 +203,7 @@ final class MediaUploadingPreprocessorTests: XCTestCase { // Repeat with optimised media setting appSettings.optimizeMediaUploads = true - guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url), + guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize), case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else { XCTFail("Failed processing asset") return @@ -224,7 +225,7 @@ final class MediaUploadingPreprocessorTests: XCTestCase { return } - guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url), + guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize), case let .image(convertedImageURL, thumbnailURL, imageInfo) = result else { XCTFail("Failed processing asset") return @@ -248,7 +249,7 @@ final class MediaUploadingPreprocessorTests: XCTestCase { // Repeat with optimised media setting appSettings.optimizeMediaUploads = true - guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url), + guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize), case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else { XCTFail("Failed processing asset") return @@ -270,7 +271,7 @@ final class MediaUploadingPreprocessorTests: XCTestCase { return } - guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url), + guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize), case let .image(convertedImageURL, _, imageInfo) = result else { XCTFail("Failed processing asset") return @@ -296,7 +297,7 @@ final class MediaUploadingPreprocessorTests: XCTestCase { // Repeat with optimised media setting appSettings.optimizeMediaUploads = true - guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url), + guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize), case let .image(optimizedImageURL, _, optimizedImageInfo) = optimizedResult else { XCTFail("Failed processing asset") return @@ -321,7 +322,7 @@ final class MediaUploadingPreprocessorTests: XCTestCase { return } - guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url), + guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize), case let .image(convertedImageURL, thumbnailURL, imageInfo) = result else { XCTFail("Failed processing asset") return @@ -349,7 +350,7 @@ final class MediaUploadingPreprocessorTests: XCTestCase { // Repeat with optimised media setting appSettings.optimizeMediaUploads = true - guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url), + guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize), case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else { XCTFail("Failed processing asset") return @@ -379,7 +380,7 @@ final class MediaUploadingPreprocessorTests: XCTestCase { return } - guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url), + guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize), case let .image(convertedImageURL, _, imageInfo) = result else { XCTFail("Failed processing asset") return @@ -405,7 +406,7 @@ final class MediaUploadingPreprocessorTests: XCTestCase { // Repeat with optimised media setting appSettings.optimizeMediaUploads = true - guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url), + guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize), case let .image(optimizedImageURL, _, optimizedImageInfo) = optimizedResult else { XCTFail("Failed processing asset") return @@ -429,7 +430,7 @@ final class MediaUploadingPreprocessorTests: XCTestCase { return } - guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url), + guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize), case let .image(convertedImageURL, thumbnailURL, imageInfo) = result else { XCTFail("Failed processing asset") return @@ -449,7 +450,7 @@ final class MediaUploadingPreprocessorTests: XCTestCase { // Repeat with optimised media setting appSettings.optimizeMediaUploads = true - guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url), + guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize), case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else { XCTFail("Failed processing asset") return diff --git a/UnitTests/Sources/RoomDetailsEditScreenViewModelTests.swift b/UnitTests/Sources/RoomDetailsEditScreenViewModelTests.swift index e3bf3a88c..c918074c1 100644 --- a/UnitTests/Sources/RoomDetailsEditScreenViewModelTests.swift +++ b/UnitTests/Sources/RoomDetailsEditScreenViewModelTests.swift @@ -111,6 +111,7 @@ class RoomDetailsEditScreenViewModelTests: XCTestCase { private func setupViewModel(roomProxyConfiguration: JoinedRoomProxyMockConfiguration) { userIndicatorController = UserIndicatorControllerMock.default viewModel = .init(roomProxy: JoinedRoomProxyMock(roomProxyConfiguration), + clientProxy: ClientProxyMock(.init()), mediaProvider: MediaProviderMock(configuration: .init()), mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: ServiceLocator.shared.settings), userIndicatorController: userIndicatorController)