From 2805742d5de0483478eaeaf58bb72377451f456c Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Mon, 15 Feb 2021 14:45:46 +1100 Subject: [PATCH] Send link previews --- .../ConversationVC+Interaction.swift | 8 +-- .../Input View/InputView.swift | 33 ++++++++++--- .../Content Views/LinkPreviewViewV2.swift | 49 ++++++++++++++++--- .../Message Cells/VisibleMessageCell.swift | 6 ++- 4 files changed, 75 insertions(+), 21 deletions(-) diff --git a/Session/Conversations V2/ConversationVC+Interaction.swift b/Session/Conversations V2/ConversationVC+Interaction.swift index 5e082a1fe..73a70612b 100644 --- a/Session/Conversations V2/ConversationVC+Interaction.swift +++ b/Session/Conversations V2/ConversationVC+Interaction.swift @@ -42,13 +42,13 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc message.sentTimestamp = NSDate.millisecondTimestamp() message.text = text message.quote = VisibleMessage.Quote.from(snInputView.quoteDraftInfo?.model) - // TODO: Link previews + let linkPreviewDraft = snInputView.linkPreviewInfo?.draft let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread) viewModel.appendUnsavedOutgoingTextMessage(tsMessage) - Storage.shared.write(with: { transaction in - // TODO: Link previews + Storage.write(with: { transaction in + message.linkPreview = VisibleMessage.LinkPreview.from(linkPreviewDraft, using: transaction) }, completion: { [weak self] in - // TODO: Link previews + tsMessage.linkPreview = OWSLinkPreview.from(message.linkPreview) Storage.shared.write { transaction in tsMessage.save(with: transaction as! YapDatabaseReadWriteTransaction) } diff --git a/Session/Conversations V2/Input View/InputView.swift b/Session/Conversations V2/Input View/InputView.swift index 8011b7b43..3f0d79d7c 100644 --- a/Session/Conversations V2/Input View/InputView.swift +++ b/Session/Conversations V2/Input View/InputView.swift @@ -1,7 +1,8 @@ -final class InputView : UIView, InputViewButtonDelegate, InputTextViewDelegate, QuoteViewDelegate, BodyTextViewDelegate, UITextViewDelegate { +final class InputView : UIView, InputViewButtonDelegate, InputTextViewDelegate, QuoteViewDelegate, LinkPreviewViewV2Delegate { private let delegate: InputViewDelegate var quoteDraftInfo: (model: OWSQuotedReplyModel, isOutgoing: Bool)? { didSet { handleQuoteDraftChanged() } } + var linkPreviewInfo: (url: String, draft: OWSLinkPreviewDraft?)? private lazy var linkPreviewView: LinkPreviewViewV2 = { let maxWidth = self.additionalContentContainer.bounds.width - InputView.linkPreviewViewInset @@ -103,6 +104,7 @@ final class InputView : UIView, InputViewButtonDelegate, InputTextViewDelegate, private func handleQuoteDraftChanged() { additionalContentContainer.subviews.forEach { $0.removeFromSuperview() } + linkPreviewInfo = nil guard let quoteDraftInfo = quoteDraftInfo else { return } let direction: QuoteView.Direction = quoteDraftInfo.isOutgoing ? .outgoing : .incoming let hInset: CGFloat = 6 @@ -117,7 +119,9 @@ final class InputView : UIView, InputViewButtonDelegate, InputTextViewDelegate, private func doLinkPreviewThingies() { additionalContentContainer.subviews.forEach { $0.removeFromSuperview() } - + quoteDraftInfo = nil + // Suggest that the user enable link previews if they haven't already, and we haven't + // told them about link previews yet let text = inputTextView.text! let userDefaults = UserDefaults.standard if !OWSLinkPreview.allPreviewUrls(forMessageBodyText: text).isEmpty && !SSKPreferences.areLinkPreviewsEnabled @@ -125,25 +129,33 @@ final class InputView : UIView, InputViewButtonDelegate, InputTextViewDelegate, // TODO: Show suggestion userDefaults[.hasSeenLinkPreviewSuggestion] = true } - + // Check that link previews are enabled + guard SSKPreferences.areLinkPreviewsEnabled else { return } + // Check that a valid URL is present guard let linkPreviewURL = OWSLinkPreview.previewUrl(forRawBodyText: text, selectedRange: inputTextView.selectedRange) else { return } - + // Guard against obsolete updates + guard linkPreviewURL != self.linkPreviewInfo?.url else { return } + // Set the state to loading + linkPreviewInfo = (url: linkPreviewURL, draft: nil) linkPreviewView.linkPreviewState = LinkPreviewLoading() + // Add the link preview view additionalContentContainer.addSubview(linkPreviewView) linkPreviewView.pin(.left, to: .left, of: additionalContentContainer, withInset: InputView.linkPreviewViewInset) linkPreviewView.pin(.top, to: .top, of: additionalContentContainer, withInset: 10) linkPreviewView.pin(.right, to: .right, of: additionalContentContainer) linkPreviewView.pin(.bottom, to: .bottom, of: additionalContentContainer, withInset: -4) - + // Build the link preview OWSLinkPreview.tryToBuildPreviewInfo(previewUrl: linkPreviewURL).done { [weak self] draft in guard let self = self else { return } - + guard self.linkPreviewInfo?.url == linkPreviewURL else { return } // Obsolete + self.linkPreviewInfo = (url: linkPreviewURL, draft: draft) self.linkPreviewView.linkPreviewState = LinkPreviewDraft(linkPreviewDraft: draft) - }.catch { _ in - + guard self.linkPreviewInfo?.url == linkPreviewURL else { return } // Obsolete + self.linkPreviewInfo = nil + self.additionalContentContainer.subviews.forEach { $0.removeFromSuperview() } }.retainUntilComplete() } @@ -167,6 +179,11 @@ final class InputView : UIView, InputViewButtonDelegate, InputTextViewDelegate, func handleLongPress() { // Not relevant in this case } + + func handleLinkPreviewCanceled() { + linkPreviewInfo = nil + additionalContentContainer.subviews.forEach { $0.removeFromSuperview() } + } } // MARK: Delegate diff --git a/Session/Conversations V2/Message Cells/Content Views/LinkPreviewViewV2.swift b/Session/Conversations V2/Message Cells/Content Views/LinkPreviewViewV2.swift index fdf2f48b5..a2f900c1e 100644 --- a/Session/Conversations V2/Message Cells/Content Views/LinkPreviewViewV2.swift +++ b/Session/Conversations V2/Message Cells/Content Views/LinkPreviewViewV2.swift @@ -3,7 +3,7 @@ import NVActivityIndicatorView final class LinkPreviewViewV2 : UIView { private let viewItem: ConversationViewItem? private let maxWidth: CGFloat - private let delegate: UITextViewDelegate & BodyTextViewDelegate + private let delegate: LinkPreviewViewV2Delegate var linkPreviewState: LinkPreviewState? { didSet { update() } } private lazy var imageViewContainerWidthConstraint = imageView.set(.width, to: 100) private lazy var imageViewContainerHeightConstraint = imageView.set(.height, to: 100) @@ -45,11 +45,25 @@ final class LinkPreviewViewV2 : UIView { private lazy var hStackViewContainer = UIView() + private lazy var hStackView = UIStackView() + + private lazy var cancelButton: UIButton = { + let result = UIButton(type: .custom) + let tint: UIColor = isLightMode ? .black : .white + result.setImage(UIImage(named: "X")?.withTint(tint), for: UIControl.State.normal) + let cancelButtonSize = LinkPreviewViewV2.cancelButtonSize + result.set(.width, to: cancelButtonSize) + result.set(.height, to: cancelButtonSize) + result.addTarget(self, action: #selector(cancel), for: UIControl.Event.touchUpInside) + return result + }() + // MARK: Settings private static let loaderSize: CGFloat = 24 + private static let cancelButtonSize: CGFloat = 45 // MARK: Lifecycle - init(for viewItem: ConversationViewItem?, maxWidth: CGFloat, delegate: UITextViewDelegate & BodyTextViewDelegate) { + init(for viewItem: ConversationViewItem?, maxWidth: CGFloat, delegate: LinkPreviewViewV2Delegate) { self.viewItem = viewItem self.maxWidth = maxWidth self.delegate = delegate @@ -76,7 +90,8 @@ final class LinkPreviewViewV2 : UIView { titleLabelContainer.addSubview(titleLabel) titleLabel.pin(to: titleLabelContainer, withInset: Values.smallSpacing) // Horizontal stack view - let hStackView = UIStackView(arrangedSubviews: [ imageViewContainer, titleLabelContainer ]) + hStackView.addArrangedSubview(imageViewContainer) + hStackView.addArrangedSubview(titleLabelContainer) hStackView.axis = .horizontal hStackView.alignment = .center hStackViewContainer.addSubview(hStackView) @@ -96,6 +111,7 @@ final class LinkPreviewViewV2 : UIView { // MARK: Updating private func update() { + cancelButton.removeFromSuperview() guard let linkPreviewState = linkPreviewState else { return } // Image view let imageViewContainerSize: CGFloat = (linkPreviewState is LinkPreviewSent) ? 100 : 80 @@ -112,12 +128,15 @@ final class LinkPreviewViewV2 : UIView { loader.alpha = (linkPreviewState.image() != nil) ? 0 : 1 if linkPreviewState.image() != nil { loader.stopAnimating() } else { loader.startAnimating() } // Title - switch linkPreviewState { - case is LinkPreviewSent: titleLabel.textColor = sentLinkPreviewTextColor - default: - let textColor: UIColor = isDarkMode ? .white : .black - titleLabel.textColor = textColor + let isSent = (linkPreviewState is LinkPreviewSent) + let isOutgoing = (viewItem?.interaction.interactionType() == .outgoingMessage) + let textColor: UIColor + if isSent && isOutgoing && isLightMode { + textColor = .white + } else { + textColor = isDarkMode ? .white : .black } + titleLabel.textColor = textColor titleLabel.text = linkPreviewState.title() // Horizontal stack view switch linkPreviewState { @@ -131,5 +150,19 @@ final class LinkPreviewViewV2 : UIView { bodyTextViewContainer.addSubview(bodyTextView) bodyTextView.pin(to: bodyTextViewContainer, withInset: 12) } + if linkPreviewState is LinkPreviewDraft { + hStackView.addArrangedSubview(cancelButton) + } + } + + // MARK: Interaction + @objc private func cancel() { + delegate.handleLinkPreviewCanceled() } } + +// MARK: Delegate +protocol LinkPreviewViewV2Delegate : UITextViewDelegate & BodyTextViewDelegate { + + func handleLinkPreviewCanceled() +} diff --git a/Session/Conversations V2/Message Cells/VisibleMessageCell.swift b/Session/Conversations V2/Message Cells/VisibleMessageCell.swift index d1efc1094..ea2a6dd65 100644 --- a/Session/Conversations V2/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations V2/Message Cells/VisibleMessageCell.swift @@ -1,5 +1,5 @@ -final class VisibleMessageCell : MessageCell, UITextViewDelegate, BodyTextViewDelegate { +final class VisibleMessageCell : MessageCell, LinkPreviewViewV2Delegate { private var unloadContent: (() -> Void)? private var previousX: CGFloat = 0 var albumView: MediaAlbumView? @@ -438,6 +438,10 @@ final class VisibleMessageCell : MessageCell, UITextViewDelegate, BodyTextViewDe resetReply() delegate?.handleReplyButtonTapped(for: viewItem) } + + func handleLinkPreviewCanceled() { + // Not relevant in this case + } // MARK: Convenience private func getCornersToRound() -> UIRectCorner {