mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 10:33:45 +00:00
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:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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? {
|
||||
|
||||
+12
-10
@@ -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 {
|
||||
|
||||
+50
-14
@@ -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 {
|
||||
|
||||
+7
-5
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
+10
-4
@@ -13,6 +13,7 @@ typealias RoomDetailsEditScreenViewModelType = StateStoreViewModel<RoomDetailsEd
|
||||
class RoomDetailsEditScreenViewModel: RoomDetailsEditScreenViewModelType, RoomDetailsEditScreenViewModelProtocol {
|
||||
private let actionsSubject: PassthroughSubject<RoomDetailsEditScreenViewModelAction, Never> = .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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
+8
-5
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user