Use the maxUploadSize in the media upload screen. (#4359)

* Fix a bug where some files sizes were sent as 0.

* Make FileManger.sizeForItem(at:) a UInt.

The docs specifically say that FileAttributeKey.size returns an unsigned long long so I have no idea why we were returning a Double here…

* Add the maxMediaUploadSize property on ClientProxy.

* Use the maxUploadSize to set a target video size and to show a file too big error.

* Refactor media upload parameter order.
This commit is contained in:
Doug
2025-07-29 12:18:53 +01:00
committed by GitHub
parent b91c0c3930
commit 75bc26d20b
23 changed files with 270 additions and 101 deletions
@@ -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() {
@@ -27,6 +27,12 @@ struct MediaUploadPreviewScreenBindings: BindableState {
var selectedRange = NSRange(location: 0, length: 0)
var isPresentingMediaCaptionWarning = false
var alertInfo: AlertInfo<MediaUploadPreviewAlertType>?
}
enum MediaUploadPreviewAlertType: Hashable {
case maxUploadSizeUnknown
case maxUploadSizeExceeded(limit: UInt)
}
enum MediaUploadPreviewScreenViewAction {
@@ -20,6 +20,7 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType,
private var processingTask: Task<Result<MediaInfo, MediaUploadingPreprocessorError>, Never>
private var requestHandle: SendAttachmentJoinHandleProtocol?
private let clientProxy: ClientProxyProtocol
private var actionsSubject: PassthroughSubject<MediaUploadPreviewScreenViewModelAction, Never> = .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<Result<MediaInfo, MediaUploadingPreprocessorError>, 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<Void, TimelineControllerError> {
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 {
@@ -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)