mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Multi file uploads (#4358)
* Allow MediaPickerScreen users to select the media selection mode (single or multiple) * Fix cancellation * Add support for multiple media URLs on the MediaUploadPreviewScreen. * Support processing more URLs on the `MediaUploadingPreprocessor` and sending more on the `MediaUploadPreviewScreen` * Add feature flag for `multipleAttachmentUploadEnabled` * Add a label showing the current preview item index in the MediaUploadPreviewScreen * Add support for dragging and dropping or pasting multiple items at the same time. * Support sharing more than one file through the share extension. * Limit the number of items that can be shared in one go to 5. * Fix unit tests * Fix incorrect fatal error when dealing with single selection media pickers. * Document the `multipleAttachmentUploadEnabled` usage in the context of the MediaPicker. * Use a task group for processing selected media in the photo library picker. * Use a task group for processing multiple selected media in the MediaUploadingPreprocessor * Switch the maximum number of items that can be shared to 10. * Allow multiple items to be pasted at the same time.
This commit is contained in:
@@ -27,7 +27,7 @@ enum ComposerToolbarViewModelAction {
|
||||
case editLastMessage
|
||||
case attach(ComposerAttachmentType)
|
||||
|
||||
case handlePasteOrDrop(provider: NSItemProvider)
|
||||
case handlePasteOrDrop(providers: [NSItemProvider])
|
||||
|
||||
case composerModeChanged(mode: ComposerMode)
|
||||
case composerFocusedChanged(isFocused: Bool)
|
||||
@@ -46,7 +46,7 @@ enum ComposerToolbarViewAction {
|
||||
case cancelReply
|
||||
case cancelEdit
|
||||
case attach(ComposerAttachmentType)
|
||||
case handlePasteOrDrop(provider: NSItemProvider)
|
||||
case handlePasteOrDrop(providers: [NSItemProvider])
|
||||
case enableTextFormatting
|
||||
case composerAction(action: ComposerAction)
|
||||
case selectedSuggestion(_ suggestion: SuggestionItem)
|
||||
|
||||
@@ -216,8 +216,8 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
|
||||
case .attach(let attachment):
|
||||
state.bindings.composerFocused = false
|
||||
actionsSubject.send(.attach(attachment))
|
||||
case .handlePasteOrDrop(let provider):
|
||||
actionsSubject.send(.handlePasteOrDrop(provider: provider))
|
||||
case .handlePasteOrDrop(let providers):
|
||||
actionsSubject.send(.handlePasteOrDrop(providers: providers))
|
||||
case .enableTextFormatting:
|
||||
state.bindings.composerFormattingEnabled = true
|
||||
state.bindings.composerFocused = true
|
||||
|
||||
@@ -181,8 +181,8 @@ struct ComposerToolbar: View {
|
||||
sendMessage()
|
||||
} editAction: {
|
||||
context.send(viewAction: .editLastMessage)
|
||||
} pasteAction: { provider in
|
||||
context.send(viewAction: .handlePasteOrDrop(provider: provider))
|
||||
} pasteAction: { providers in
|
||||
context.send(viewAction: .handlePasteOrDrop(providers: providers))
|
||||
} cancellationAction: {
|
||||
switch context.viewState.composerMode {
|
||||
case .edit:
|
||||
@@ -259,7 +259,7 @@ struct ComposerToolbar: View {
|
||||
viewModel: context.viewState.wysiwygViewModel,
|
||||
itemProviderHelper: ItemProviderHelper(),
|
||||
keyCommands: context.viewState.keyCommands) { provider in
|
||||
context.send(viewAction: .handlePasteOrDrop(provider: provider))
|
||||
context.send(viewAction: .handlePasteOrDrop(providers: [provider]))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import SwiftUI
|
||||
import WysiwygComposer
|
||||
|
||||
typealias GenericKeyHandler = (_ key: UIKeyboardHIDUsage) -> Void
|
||||
typealias PasteHandler = (NSItemProvider) -> Void
|
||||
typealias PasteHandler = ([NSItemProvider]) -> Void
|
||||
|
||||
struct MessageComposer: View {
|
||||
@Binding var plainComposerText: NSAttributedString
|
||||
|
||||
+10
-10
@@ -190,8 +190,8 @@ private struct UITextViewWrapper: UIViewRepresentable {
|
||||
textView.insertText("\n")
|
||||
}
|
||||
|
||||
func textView(_ textView: UITextView, didReceivePasteWith provider: NSItemProvider) {
|
||||
pasteHandler(provider)
|
||||
func textView(_ textView: UITextView, didReceivePasteWith providers: [NSItemProvider]) {
|
||||
pasteHandler(providers)
|
||||
}
|
||||
|
||||
func textViewDidChangeSelection(_ textView: UITextView) {
|
||||
@@ -207,7 +207,7 @@ private struct UITextViewWrapper: UIViewRepresentable {
|
||||
private protocol ElementTextViewDelegate: AnyObject {
|
||||
func textViewDidReceiveShiftEnterKeyPress(_ textView: UITextView)
|
||||
func textViewDidReceiveKeyPress(_ textView: UITextView, key: UIKeyboardHIDUsage)
|
||||
func textView(_ textView: UITextView, didReceivePasteWith provider: NSItemProvider)
|
||||
func textView(_ textView: UITextView, didReceivePasteWith providers: [NSItemProvider])
|
||||
}
|
||||
|
||||
private class ElementTextView: UITextView, PillAttachmentViewProviderDelegate {
|
||||
@@ -280,19 +280,19 @@ private class ElementTextView: UITextView, PillAttachmentViewProviderDelegate {
|
||||
return false
|
||||
}
|
||||
|
||||
return UIPasteboard.general.itemProviders.first?.isSupportedForPasteOrDrop ?? false
|
||||
return UIPasteboard.general.itemProviders.filter { !$0.isSupportedForPasteOrDrop }.isEmpty
|
||||
}
|
||||
|
||||
override func paste(_ sender: Any?) {
|
||||
guard let provider = UIPasteboard.general.itemProviders.first,
|
||||
provider.isSupportedForPasteOrDrop else {
|
||||
// If the item is not supported for media upload then
|
||||
// just try pasting its contents into the textfield
|
||||
let providers = UIPasteboard.general.itemProviders
|
||||
|
||||
// Use the default behavior if there are any unsupported providers
|
||||
guard providers.filter({ !$0.isSupportedForPasteOrDrop }).isEmpty else {
|
||||
super.paste(sender)
|
||||
return
|
||||
}
|
||||
|
||||
elementDelegate?.textView(self, didReceivePasteWith: provider)
|
||||
|
||||
elementDelegate?.textView(self, didReceivePasteWith: providers)
|
||||
}
|
||||
|
||||
// MARK: PillAttachmentViewProviderDelegate
|
||||
|
||||
@@ -32,8 +32,8 @@ struct RoomScreenCoordinatorParameters {
|
||||
|
||||
enum RoomScreenCoordinatorAction {
|
||||
case presentReportContent(itemID: TimelineItemIdentifier, senderID: String)
|
||||
case presentMediaUploadPicker(MediaPickerScreenSource)
|
||||
case presentMediaUploadPreviewScreen(URL)
|
||||
case presentMediaUploadPicker(mode: MediaPickerScreenMode)
|
||||
case presentMediaUploadPreviewScreen(mediaURLs: [URL])
|
||||
case presentRoomDetails
|
||||
case presentLocationPicker
|
||||
case presentPollForm(mode: PollFormMode)
|
||||
@@ -121,19 +121,19 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
||||
case .displayReportContent(let itemID, let senderID):
|
||||
actionsSubject.send(.presentReportContent(itemID: itemID, senderID: senderID))
|
||||
case .displayCameraPicker:
|
||||
actionsSubject.send(.presentMediaUploadPicker(.camera))
|
||||
actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .camera, selectionType: .multiple)))
|
||||
case .displayMediaPicker:
|
||||
actionsSubject.send(.presentMediaUploadPicker(.photoLibrary))
|
||||
actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .photoLibrary, selectionType: .multiple)))
|
||||
case .displayDocumentPicker:
|
||||
actionsSubject.send(.presentMediaUploadPicker(.documents))
|
||||
actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .documents, selectionType: .multiple)))
|
||||
case .displayMediaPreview(let mediaPreviewViewModel):
|
||||
roomViewModel.displayMediaPreview(mediaPreviewViewModel)
|
||||
case .displayLocationPicker:
|
||||
actionsSubject.send(.presentLocationPicker)
|
||||
case .displayPollForm(let mode):
|
||||
actionsSubject.send(.presentPollForm(mode: mode))
|
||||
case .displayMediaUploadPreviewScreen(let url):
|
||||
actionsSubject.send(.presentMediaUploadPreviewScreen(url))
|
||||
case .displayMediaUploadPreviewScreen(let mediaURLs):
|
||||
actionsSubject.send(.presentMediaUploadPreviewScreen(mediaURLs: mediaURLs))
|
||||
case .displaySenderDetails(userID: let userID):
|
||||
actionsSubject.send(.presentRoomMemberDetails(userID: userID))
|
||||
case .displayMessageForwarding(let forwardingItem):
|
||||
|
||||
Reference in New Issue
Block a user