Media uploading (#851)

* MediaUploadingPreprocessor - Prevent images without any GPS metadata from being changed

* Add support for sending images

* Add support for sending video, audio and file attachments

* Rename MediaPickerPreview to MediaUploadPreview

* Move media uploading to the MediaUploadPreviewScreen, add waiting indicators and error handling

* Add support for pasting and drag&dropping in media for upload

* Adopt new media picker source UI, remove developer flag

* Set minimum heights for timeline loadable images

* Fix invalid camera picker file names

* Fix flakey MediaUploadingPreprocessor image tests, improve gps metadata stripping

* UITests: Update existing screenshots and add new step for the room attachment picker

* Switch all github action runners to macos-13

* Cleanup enter key and paste message composer handlers
This commit is contained in:
Stefan Ceriu
2023-05-04 16:09:29 +03:00
committed by GitHub
parent e9c07c9f8f
commit 93e3a1a80b
100 changed files with 809 additions and 393 deletions
@@ -39,7 +39,6 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
roomTitle: roomName ?? "Unknown room 💥",
roomAvatarURL: roomAvatarUrl,
timelineStyle: ServiceLocator.shared.settings.timelineStyle,
mediaUploadingFlowEnabled: ServiceLocator.shared.settings.mediaUploadingFlowEnabled,
bindings: .init(composerText: "", composerFocused: false)),
imageProvider: mediaProvider)
@@ -74,11 +73,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
ServiceLocator.shared.settings.$timelineStyle
.weakAssign(to: \.state.timelineStyle, on: self)
.store(in: &cancellables)
ServiceLocator.shared.settings.$mediaUploadingFlowEnabled
.weakAssign(to: \.state.mediaUploadingFlowEnabled, on: self)
.store(in: &cancellables)
buildTimelineViews()
}
@@ -122,6 +117,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
callback?(.displayMediaPicker)
case .displayDocumentPicker:
callback?(.displayDocumentPicker)
case .handlePasteOrDrop(let provider):
handlePasteOrDrop(provider)
}
}
@@ -334,6 +331,60 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
state.composerMode = .default
}
}
// Pasting and dropping
private func handlePasteOrDrop(_ provider: NSItemProvider) {
guard let type = provider.registeredContentTypes.first,
let preferredExtension = type.preferredFilenameExtension else {
MXLog.error("Invalid NSItemProvider: \(provider)")
displayError(.toast(L10n.screenRoomErrorFailedProcessingMedia))
return
}
let providerSuggestedName = provider.suggestedName
let providerDescription = provider.description
_ = provider.loadDataRepresentation(for: type) { data, error in
Task { @MainActor in
let loadingIndicatorIdentifier = UUID().uuidString
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: loadingIndicatorIdentifier, type: .modal, title: L10n.commonLoading, persistent: true))
defer {
ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(loadingIndicatorIdentifier)
}
if let error {
self.displayError(.toast(L10n.screenRoomErrorFailedProcessingMedia))
MXLog.error("Failed processing NSItemProvider: \(providerDescription) with error: \(error)")
return
}
guard let data else {
self.displayError(.toast(L10n.screenRoomErrorFailedProcessingMedia))
MXLog.error("Invalid NSItemProvider data: \(providerDescription)")
return
}
do {
let url = try await Task.detached {
if let filename = providerSuggestedName {
let hasExtension = !(filename as NSString).pathExtension.isEmpty
let filename = hasExtension ? filename : "\(filename).\(preferredExtension)"
return try FileManager.default.writeDataToTemporaryDirectory(data: data, fileName: filename)
} else {
let filename = "\(UUID().uuidString).\(preferredExtension)"
return try FileManager.default.writeDataToTemporaryDirectory(data: data, fileName: filename)
}
}.value
self.callback?(.displayMediaUploadPreviewScreen(url: url))
} catch {
self.displayError(.toast(L10n.screenRoomErrorFailedProcessingMedia))
MXLog.error("Failed storing NSItemProvider data \(providerDescription) with error: \(error)")
}
}
}
}
}
// MARK: - Mocks