diff --git a/Sources/StreamChatSwiftUI/ChatChannel/Composer/ComposerTextInputView.swift b/Sources/StreamChatSwiftUI/ChatChannel/Composer/ComposerTextInputView.swift index 48fea75a0..433612083 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/Composer/ComposerTextInputView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/Composer/ComposerTextInputView.swift @@ -18,6 +18,7 @@ struct ComposerTextInputView: UIViewRepresentable { var editable: Bool var maxMessageLength: Int? var currentHeight: CGFloat + var onImagePasted: ((UIImage) -> Void)? func makeUIView(context: Context) -> InputTextView { let inputTextView: InputTextView @@ -33,6 +34,7 @@ struct ComposerTextInputView: UIViewRepresentable { inputTextView.placeholderLabel.text = placeholder inputTextView.contentInsetAdjustmentBehavior = .never inputTextView.setContentCompressionResistancePriority(.streamLow, for: .horizontal) + inputTextView.onImagePasted = onImagePasted if utils.messageListConfig.becomesFirstResponderOnOpen { inputTextView.becomeFirstResponder() diff --git a/Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerView.swift b/Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerView.swift index 694a84d5a..e3e594c7e 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerView.swift @@ -372,7 +372,8 @@ public struct ComposerInputView: View, KeyboardReadable { placeholder: isInCooldown ? L10n.Composer.Placeholder.slowMode : L10n.Composer.Placeholder.message, editable: !isInCooldown, maxMessageLength: maxMessageLength, - currentHeight: textFieldHeight + currentHeight: textFieldHeight, + onImagePasted: viewModel.imagePasted(_:) ) .accessibilityIdentifier("ComposerTextInputView") .accessibilityElement(children: .contain) diff --git a/Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerViewModel.swift b/Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerViewModel.swift index 3d80d3e13..bf96c7d18 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerViewModel.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerViewModel.swift @@ -510,6 +510,20 @@ open class MessageComposerViewModel: ObservableObject { addedAssets = images } + public func imagePasted(_ image: UIImage) { + guard let imageURL = try? image.temporaryLocalFileUrl() else { + log.error("Failed to write image to local temporary file") + return + } + let addedImage = AddedAsset( + image: image, + id: UUID().uuidString, + url: imageURL, + type: .image + ) + addedAssets.append(addedImage) + } + public func removeAttachment(with id: String) { if id.isURL, let url = URL(string: id) { var urls = [URL]() diff --git a/Sources/StreamChatSwiftUI/DefaultViewFactory.swift b/Sources/StreamChatSwiftUI/DefaultViewFactory.swift index 4f350e9e3..55301e5c5 100644 --- a/Sources/StreamChatSwiftUI/DefaultViewFactory.swift +++ b/Sources/StreamChatSwiftUI/DefaultViewFactory.swift @@ -632,7 +632,8 @@ extension ViewFactory { placeholder: String, editable: Bool, maxMessageLength: Int?, - currentHeight: CGFloat + currentHeight: CGFloat, + onImagePasted: ((UIImage) -> Void)? ) -> some View { ComposerTextInputView( text: text, @@ -641,7 +642,8 @@ extension ViewFactory { placeholder: placeholder, editable: editable, maxMessageLength: maxMessageLength, - currentHeight: currentHeight + currentHeight: currentHeight, + onImagePasted: onImagePasted ) } diff --git a/Sources/StreamChatSwiftUI/Utils/Common/InputTextView.swift b/Sources/StreamChatSwiftUI/Utils/Common/InputTextView.swift index d12a6edb8..daa44079d 100644 --- a/Sources/StreamChatSwiftUI/Utils/Common/InputTextView.swift +++ b/Sources/StreamChatSwiftUI/Utils/Common/InputTextView.swift @@ -52,6 +52,8 @@ class InputTextView: UITextView, AccessibilityView { } } } + + var onImagePasted: ((UIImage) -> Void)? override open func didMoveToSuperview() { super.didMoveToSuperview() @@ -143,9 +145,22 @@ class InputTextView: UITextView, AccessibilityView { override open func paste(_ sender: Any?) { super.paste(sender) + if let pastedImage = UIPasteboard.general.image, + let onImagePasted { + onImagePasted(pastedImage) + return + } handleTextChange() DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in self?.scrollToBottom() } } + + override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { + if action == #selector(paste(_:)) && onImagePasted != nil && UIPasteboard.general.image != nil { + return true + } else { + return super.canPerformAction(action, withSender: sender) + } + } } diff --git a/Sources/StreamChatSwiftUI/ViewFactory.swift b/Sources/StreamChatSwiftUI/ViewFactory.swift index 7f4db11d9..428ecb41c 100644 --- a/Sources/StreamChatSwiftUI/ViewFactory.swift +++ b/Sources/StreamChatSwiftUI/ViewFactory.swift @@ -651,7 +651,8 @@ public protocol ViewFactory: AnyObject { placeholder: String, editable: Bool, maxMessageLength: Int?, - currentHeight: CGFloat + currentHeight: CGFloat, + onImagePasted: ((UIImage) -> Void)? ) -> ComposerTextInputViewType associatedtype TrailingComposerViewType: View diff --git a/StreamChatSwiftUITests/Tests/Utils/ViewFactory_Tests.swift b/StreamChatSwiftUITests/Tests/Utils/ViewFactory_Tests.swift index a2fed546e..34b174baf 100644 --- a/StreamChatSwiftUITests/Tests/Utils/ViewFactory_Tests.swift +++ b/StreamChatSwiftUITests/Tests/Utils/ViewFactory_Tests.swift @@ -832,7 +832,8 @@ class ViewFactory_Tests: StreamChatTestCase { placeholder: "Send a message", editable: true, maxMessageLength: nil, - currentHeight: 40 + currentHeight: 40, + onImagePasted: nil ) // Then