diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 8791637b17..f292eccfbc 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -445,6 +445,7 @@ extension ConversationVC: using: dependencies ) sendMediaNavController.sendMediaNavDelegate = self + sendMediaNavController.sendMediaNavDataSource = self sendMediaNavController.modalPresentationStyle = .fullScreen self?.present(sendMediaNavController, animated: true, completion: nil) } @@ -466,6 +467,7 @@ extension ConversationVC: using: self.viewModel.dependencies ) sendMediaNavController.sendMediaNavDelegate = self + sendMediaNavController.sendMediaNavDataSource = self sendMediaNavController.modalPresentationStyle = .fullScreen present(sendMediaNavController, animated: true, completion: nil) @@ -483,6 +485,7 @@ extension ConversationVC: threadVariant: self.viewModel.threadData.threadVariant, attachments: attachments, approvalDelegate: self, + approvalDataSource: self, disableLinkPreviewImageDownload: (self.viewModel.threadData.threadCanUpload != true), using: self.viewModel.dependencies ) else { return } @@ -718,6 +721,9 @@ extension ConversationVC: return present(modal, animated: true, completion: nil) } + // Preserve the quote before clearing input fields for message sending. + let quoteToUse: QuotedReplyModel? = quoteModel ?? snInputView.quoteDraftInfo?.model + // Clearing this out immediately to make this appear more snappy DispatchQueue.main.async { [weak self] in self?.snInputView.text = "" @@ -735,7 +741,7 @@ extension ConversationVC: sentTimestampMs: sentTimestampMs, attachments: attachments, linkPreviewDraft: linkPreviewDraft, - quoteModel: quoteModel + quoteModel: quoteToUse ) // If this was a message request then approve it @@ -956,6 +962,7 @@ extension ConversationVC: threadVariant: self.viewModel.threadData.threadVariant, attachments: [ attachment ], approvalDelegate: self, + approvalDataSource: self, disableLinkPreviewImageDownload: (self.viewModel.threadData.threadCanUpload != true), using: self.viewModel.dependencies ) else { return } @@ -3483,3 +3490,47 @@ extension ConversationVC: MediaPresentationContextProvider { return self.navigationController?.navigationBar.generateSnapshot(in: coordinateSpace) } } + +// MARK: - Qoute accessory handling +extension ConversationVC: InputViewDataSource, AttachmentApprovalViewControllerDataSource, SendMediaNavDataSource { + func generateQouteAccessoryView(_ deleteHandler: @escaping () -> Void) -> UIView? { + guard let quoteDraftInfo = snInputView.quoteDraftInfo else { + return nil + } + + let quoteView: QuoteView = QuoteView( + for: .draft, + authorId: quoteDraftInfo.model.authorId, + quotedText: quoteDraftInfo.model.body, + threadVariant: self.viewModel.threadData.threadVariant, + currentUserSessionIds: quoteDraftInfo.model.currentUserSessionIds, + direction: (quoteDraftInfo.isOutgoing ? .outgoing : .incoming), + attachment: quoteDraftInfo.model.attachment, + using: viewModel.dependencies + ) { + deleteHandler() + } + + return quoteView + } + + // MARK: - InputViewDataSource + func shouldShowQuoteAccessoryView(withDeleteHandler handler: @escaping () -> Void) -> UIView? { + generateQouteAccessoryView(handler) + } + + // MARK: - AttachmentApprovalViewControllerDataSource + func attachmentApprovalQouteAccessoryView(_ deleteHandler: @escaping () -> Void) -> UIView? { + generateQouteAccessoryView { [weak self] in + self?.snInputView.quoteDraftInfo = nil + deleteHandler() + } + } + + func sendMediaNavQouteAccessoryView(_ deleteHandler: @escaping () -> Void) -> UIView? { + generateQouteAccessoryView { [weak self] in + self?.snInputView.quoteDraftInfo = nil + deleteHandler() + } + } +} diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 7ec260b5b3..9ea6c8f3b2 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -175,6 +175,7 @@ final class ConversationVC: BaseVC, LibSessionRespondingViewController, Conversa lazy var snInputView: InputView = InputView( threadVariant: self.viewModel.initialThreadVariant, delegate: self, + dataSource: self, using: self.viewModel.dependencies ) diff --git a/Session/Conversations/Input View/InputView.swift b/Session/Conversations/Input View/InputView.swift index 9b9b0bc674..3d0da54c51 100644 --- a/Session/Conversations/Input View/InputView.swift +++ b/Session/Conversations/Input View/InputView.swift @@ -16,7 +16,10 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M private var disposables: Set = Set() private let dependencies: Dependencies private let threadVariant: SessionThread.Variant + private weak var delegate: InputViewDelegate? + private weak var dataSource: InputViewDataSource? + private var sessionProState: SessionProManagerType? var quoteDraftInfo: (model: QuotedReplyModel, isOutgoing: Bool)? { didSet { handleQuoteDraftChanged() } } @@ -202,10 +205,16 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M // MARK: - Initialization - init(threadVariant: SessionThread.Variant, delegate: InputViewDelegate, using dependencies: Dependencies) { + init( + threadVariant: SessionThread.Variant, + delegate: InputViewDelegate, + dataSource: InputViewDataSource?, + using dependencies: Dependencies + ) { self.dependencies = dependencies self.threadVariant = threadVariant self.delegate = delegate + self.dataSource = dataSource self.sessionProState = dependencies[singleton: .sessionProState] super.init(frame: CGRect.zero) @@ -343,23 +352,14 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M additionalContentContainer.subviews.forEach { $0.removeFromSuperview() } linkPreviewInfo = nil - guard let quoteDraftInfo = quoteDraftInfo else { return } + let view = dataSource?.shouldShowQuoteAccessoryView { + self.quoteDraftInfo = nil + } + + guard let quoteView = view else { return } let hInset: CGFloat = 6 // Slight visual adjustment - let quoteView: QuoteView = QuoteView( - for: .draft, - authorId: quoteDraftInfo.model.authorId, - quotedText: quoteDraftInfo.model.body, - threadVariant: threadVariant, - currentUserSessionIds: quoteDraftInfo.model.currentUserSessionIds, - direction: (quoteDraftInfo.isOutgoing ? .outgoing : .incoming), - attachment: quoteDraftInfo.model.attachment, - using: dependencies - ) { [weak self] in - self?.quoteDraftInfo = nil - } - additionalContentContainer.addSubview(quoteView) quoteView.pin(.leading, to: .leading, of: additionalContentContainer, withInset: hInset) quoteView.pin(.top, to: .top, of: additionalContentContainer, withInset: 12) @@ -676,3 +676,8 @@ protocol InputViewDelegate: ExpandingAttachmentsButtonDelegate, VoiceMessageReco @MainActor func handleMentionSelected(_ mentionInfo: MentionInfo, from view: MentionSelectionView) @MainActor func didPasteImageFromPasteboard(_ image: UIImage) } + +public protocol InputViewDataSource: AnyObject { + @MainActor + func shouldShowQuoteAccessoryView(withDeleteHandler handler:@escaping () -> Void) -> UIView? +} diff --git a/Session/Media Viewing & Editing/SendMediaNavigationController.swift b/Session/Media Viewing & Editing/SendMediaNavigationController.swift index 5be73955ea..1b53ecdfba 100644 --- a/Session/Media Viewing & Editing/SendMediaNavigationController.swift +++ b/Session/Media Viewing & Editing/SendMediaNavigationController.swift @@ -77,6 +77,7 @@ class SendMediaNavigationController: UINavigationController { // MARK: - public weak var sendMediaNavDelegate: SendMediaNavDelegate? + public weak var sendMediaNavDataSource: SendMediaNavDataSource? public class func showingCameraFirst(threadId: String, threadVariant: SessionThread.Variant, using dependencies: Dependencies) -> SendMediaNavigationController { let navController = SendMediaNavigationController(threadId: threadId, threadVariant: threadVariant, using: dependencies) @@ -245,6 +246,7 @@ class SendMediaNavigationController: UINavigationController { else { return false } approvalViewController.approvalDelegate = self + approvalViewController.approvalDataSource = self approvalViewController.messageText = sendMediaNavDelegate.sendMediaNavInitialMessageText(self) pushViewController(approvalViewController, animated: true) @@ -425,7 +427,8 @@ extension SendMediaNavigationController: ImagePickerGridControllerDelegate { } } -extension SendMediaNavigationController: AttachmentApprovalViewControllerDelegate { +extension SendMediaNavigationController: AttachmentApprovalViewControllerDelegate, AttachmentApprovalViewControllerDataSource { + // MARK: - AttachmentApprovalViewControllerDelegate func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didChangeMessageText newMessageText: String?) { sendMediaNavDelegate?.sendMediaNav(self, didChangeMessageText: newMessageText) } @@ -476,6 +479,11 @@ extension SendMediaNavigationController: AttachmentApprovalViewControllerDelegat popViewController(animated: true) } + + // MARK: - AttachmentApprovalViewControllerDataSource + func attachmentApprovalQouteAccessoryView(_ deleteHandler: @escaping () -> Void) -> UIView? { + sendMediaNavDataSource?.sendMediaNavQouteAccessoryView(deleteHandler) + } } private enum AttachmentDraft { @@ -799,3 +807,7 @@ protocol SendMediaNavDelegate: AnyObject { func sendMediaNavInitialMessageText(_ sendMediaNavigationController: SendMediaNavigationController) -> String? func sendMediaNav(_ sendMediaNavigationController: SendMediaNavigationController, didChangeMessageText newMessageText: String?) } + +protocol SendMediaNavDataSource: AnyObject { + func sendMediaNavQouteAccessoryView(_ deleteHandler: @escaping () -> Void) -> UIView? +} diff --git a/SessionShareExtension/ThreadPickerVC.swift b/SessionShareExtension/ThreadPickerVC.swift index 60295606b9..39809605ad 100644 --- a/SessionShareExtension/ThreadPickerVC.swift +++ b/SessionShareExtension/ThreadPickerVC.swift @@ -219,6 +219,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView threadVariant: strongSelf.viewModel.viewData[indexPath.row].threadVariant, attachments: attachments, approvalDelegate: strongSelf, + approvalDataSource: nil, disableLinkPreviewImageDownload: ( strongSelf.viewModel.viewData[indexPath.row].threadCanUpload != true ), diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalInputAccessoryView.swift b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalInputAccessoryView.swift index 179a49d32d..46a3e5be5e 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalInputAccessoryView.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalInputAccessoryView.swift @@ -9,25 +9,32 @@ protocol AttachmentApprovalInputAccessoryViewDelegate: AnyObject { func attachmentApprovalInputUpdateMediaRail() } +protocol AttachmentApprovalInputAccessoryViewDataSource: AnyObject { + func attachmentApprovalQouteAccessoryView(onDeleteHandler handler: @escaping () -> Void) -> UIView? +} + // MARK: - class AttachmentApprovalInputAccessoryView: UIView { weak var delegate: AttachmentApprovalInputAccessoryViewDelegate? + weak var dataSource: AttachmentApprovalInputAccessoryViewDataSource? let attachmentTextToolbar: AttachmentTextToolbar let galleryRailView: GalleryRailView + private lazy var additionalContentContainer = UIView() var isEditingMediaMessage: Bool { return attachmentTextToolbar.inputView?.isFirstResponder ?? false } private var currentAttachmentItem: SignalAttachmentItem? - + let kGalleryRailViewHeight: CGFloat = 72 - required init(delegate: AttachmentTextToolbarDelegate, using dependencies: Dependencies) { + required init(delegate: AttachmentTextToolbarDelegate, dataSource: AttachmentApprovalInputAccessoryViewDataSource?, using dependencies: Dependencies) { attachmentTextToolbar = AttachmentTextToolbar(delegate: delegate, using: dependencies) + self.dataSource = dataSource galleryRailView = GalleryRailView() galleryRailView.scrollFocusMode = .keepWithinBounds @@ -64,7 +71,11 @@ class AttachmentApprovalInputAccessoryView: UIView { separator.pin(.leading, to: .leading, of: self) separator.pin(.trailing, to: .trailing, of: self) - let stackView = UIStackView(arrangedSubviews: [galleryRailView, attachmentTextToolbar]) + let stackView = UIStackView(arrangedSubviews: [ + galleryRailView, + additionalContentContainer, + attachmentTextToolbar + ]) stackView.axis = .vertical addSubview(stackView) @@ -83,6 +94,8 @@ class AttachmentApprovalInputAccessoryView: UIView { galleryRailBlockingView.pin(.left, to: .left, of: stackView) galleryRailBlockingView.pin(.right, to: .right, of: stackView) galleryRailBlockingView.pin(.bottom, to: .bottom, of: stackView) + + setupQuoteAccessoryViewIfAny() } // MARK: @@ -101,6 +114,25 @@ class AttachmentApprovalInputAccessoryView: UIView { updateFirstResponder() } + + // MARK: - Additional accessory view + private func setupQuoteAccessoryViewIfAny() { + additionalContentContainer.subviews.forEach { $0.removeFromSuperview() } + + let view = dataSource?.attachmentApprovalQouteAccessoryView { [weak self] in + self?.additionalContentContainer.subviews.forEach { $0.removeFromSuperview() } + } + + guard let quoteView = view else { return } + + let hInset: CGFloat = 6 // Slight visual adjustment + + additionalContentContainer.addSubview(quoteView) + quoteView.pin(.leading, to: .leading, of: additionalContentContainer, withInset: hInset) + quoteView.pin(.top, to: .top, of: additionalContentContainer, withInset: 12) + quoteView.pin(.trailing, to: .trailing, of: additionalContentContainer, withInset: -hInset) + quoteView.pin(.bottom, to: .bottom, of: additionalContentContainer, withInset: -6) + } // MARK: diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalViewController.swift b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalViewController.swift index e3d4039eb8..088866c976 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalViewController.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalViewController.swift @@ -42,6 +42,10 @@ public protocol AttachmentApprovalViewControllerDelegate: AnyObject { func attachmentApprovalDidTapAddMore(_ attachmentApproval: AttachmentApprovalViewController) } +public protocol AttachmentApprovalViewControllerDataSource: AnyObject { + func attachmentApprovalQouteAccessoryView(_ deleteHandler: @escaping () -> Void) -> UIView? +} + // MARK: - @objc @@ -77,6 +81,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC private let disableLinkPreviewImageDownload: Bool public weak var approvalDelegate: AttachmentApprovalViewControllerDelegate? + public weak var approvalDataSource: AttachmentApprovalViewControllerDataSource? let attachmentItemCollection: AttachmentItemCollection @@ -183,6 +188,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC threadVariant: SessionThread.Variant, attachments: [SignalAttachment], approvalDelegate: AttachmentApprovalViewControllerDelegate, + approvalDataSource: AttachmentApprovalViewControllerDataSource?, disableLinkPreviewImageDownload: Bool, using dependencies: Dependencies ) -> UINavigationController? { @@ -195,6 +201,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC using: dependencies ) else { return nil } vc.approvalDelegate = approvalDelegate + vc.approvalDataSource = approvalDataSource let navController = StyledNavigationController(rootViewController: vc) @@ -206,7 +213,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC private let kSpacingBetweenItems: CGFloat = 20 private lazy var bottomToolView: AttachmentApprovalInputAccessoryView = { - let bottomToolView = AttachmentApprovalInputAccessoryView(delegate: self, using: dependencies) + let bottomToolView = AttachmentApprovalInputAccessoryView(delegate: self, dataSource: self, using: dependencies) bottomToolView.delegate = self bottomToolView.attachmentTextToolbar.delegate = self bottomToolView.galleryRailView.delegate = self @@ -857,3 +864,9 @@ extension AttachmentApprovalViewController: AttachmentApprovalInputAccessoryView updateMediaRail() } } + +extension AttachmentApprovalViewController: AttachmentApprovalInputAccessoryViewDataSource { + func attachmentApprovalQouteAccessoryView(onDeleteHandler handler: @escaping () -> Void) -> UIView? { + return approvalDataSource?.attachmentApprovalQouteAccessoryView(handler) + } +}