Make LinkPreviewView reusable.

pull/1/head
Matthew Chen 6 years ago
parent c7053aa36d
commit ca8a4b3751

@ -1,5 +1,5 @@
// //
// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// //
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@ -10,6 +10,7 @@ NS_ASSUME_NONNULL_BEGIN
@protocol ConversationViewItem; @protocol ConversationViewItem;
@class OWSContact; @class OWSContact;
@class OWSLinkPreview;
@class OWSQuotedReplyModel; @class OWSQuotedReplyModel;
@class TSAttachmentPointer; @class TSAttachmentPointer;
@class TSAttachmentStream; @class TSAttachmentStream;
@ -21,6 +22,7 @@ typedef NS_ENUM(NSUInteger, OWSMessageGestureLocation) {
OWSMessageGestureLocation_OversizeText, OWSMessageGestureLocation_OversizeText,
OWSMessageGestureLocation_Media, OWSMessageGestureLocation_Media,
OWSMessageGestureLocation_QuotedReply, OWSMessageGestureLocation_QuotedReply,
OWSMessageGestureLocation_LinkPreview,
}; };
extern const UIDataDetectorTypes kOWSAllowedDataDetectorTypes; extern const UIDataDetectorTypes kOWSAllowedDataDetectorTypes;
@ -46,6 +48,8 @@ extern const UIDataDetectorTypes kOWSAllowedDataDetectorTypes;
quotedReply:(OWSQuotedReplyModel *)quotedReply quotedReply:(OWSQuotedReplyModel *)quotedReply
failedThumbnailDownloadAttachmentPointer:(TSAttachmentPointer *)attachmentPointer; failedThumbnailDownloadAttachmentPointer:(TSAttachmentPointer *)attachmentPointer;
- (void)didTapConversationItem:(id<ConversationViewItem>)viewItem linkPreview:(OWSLinkPreview *)linkPreview;
- (void)didTapContactShareViewItem:(id<ConversationViewItem>)viewItem; - (void)didTapContactShareViewItem:(id<ConversationViewItem>)viewItem;
- (void)didTapSendMessageToContactShare:(ContactShareViewModel *)contactShare - (void)didTapSendMessageToContactShare:(ContactShareViewModel *)contactShare

@ -40,6 +40,8 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
@property (nonatomic, nullable) UIView *bodyMediaView; @property (nonatomic, nullable) UIView *bodyMediaView;
@property (nonatomic, nullable) LinkPreviewView *linkPreviewView;
// Should lazy-load expensive view contents (images, etc.). // Should lazy-load expensive view contents (images, etc.).
// Should do nothing if view is already loaded. // Should do nothing if view is already loaded.
@property (nonatomic, nullable) dispatch_block_t loadCellContentBlock; @property (nonatomic, nullable) dispatch_block_t loadCellContentBlock;
@ -1158,6 +1160,13 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
} }
} }
if (self.viewItem.linkPreview) {
CGSize linkPreviewSize = [LinkPreviewView measureWithConversationViewItem:self.viewItem];
linkPreviewSize.width = MIN(linkPreviewSize.width, self.conversationStyle.maxMessageWidth);
cellSize.width = MAX(cellSize.width, linkPreviewSize.width);
cellSize.height += linkPreviewSize.height;
}
NSValue *_Nullable bodyTextSize = [self bodyTextSize]; NSValue *_Nullable bodyTextSize = [self bodyTextSize];
if (bodyTextSize) { if (bodyTextSize) {
[textViewSizes addObject:bodyTextSize]; [textViewSizes addObject:bodyTextSize];
@ -1284,6 +1293,9 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
[self.contactShareButtonsView removeFromSuperview]; [self.contactShareButtonsView removeFromSuperview];
self.contactShareButtonsView = nil; self.contactShareButtonsView = nil;
[self.linkPreviewView removeFromSuperview];
self.linkPreviewView = nil;
} }
#pragma mark - Gestures #pragma mark - Gestures
@ -1338,6 +1350,13 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
OWSFailDebug(@"Missing quoted message."); OWSFailDebug(@"Missing quoted message.");
} }
break; break;
case OWSMessageGestureLocation_LinkPreview:
if (self.viewItem.linkPreview) {
[self.delegate didTapConversationItem:self.viewItem linkPreview:self.viewItem.linkPreview];
} else {
OWSFailDebug(@"Missing link preview.");
}
break;
} }
} }
@ -1426,6 +1445,17 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
} }
} }
if (self.linkPreviewView) {
// Treat this as a "quoted reply" gesture if:
//
// * There is a "quoted reply" view.
// * The gesture occured within or above the "quoted reply" view.
CGPoint location = [self convertPoint:locationInMessageBubble toView:self.linkPreviewView];
if (CGRectContainsPoint(self.linkPreviewView.bounds, location)) {
return OWSMessageGestureLocation_LinkPreview;
}
}
if (self.bodyMediaView) { if (self.bodyMediaView) {
// Treat this as a "body media" gesture if: // Treat this as a "body media" gesture if:
// //

@ -1,5 +1,5 @@
// //
// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// //
#import "OWSMessageCell.h" #import "OWSMessageCell.h"
@ -438,7 +438,8 @@ NS_ASSUME_NONNULL_BEGIN
CGPoint locationInMessageBubble = [sender locationInView:self.messageBubbleView]; CGPoint locationInMessageBubble = [sender locationInView:self.messageBubbleView];
switch ([self.messageBubbleView gestureLocationForLocation:locationInMessageBubble]) { switch ([self.messageBubbleView gestureLocationForLocation:locationInMessageBubble]) {
case OWSMessageGestureLocation_Default: case OWSMessageGestureLocation_Default:
case OWSMessageGestureLocation_OversizeText: { case OWSMessageGestureLocation_OversizeText:
case OWSMessageGestureLocation_LinkPreview: {
[self.delegate conversationCell:self [self.delegate conversationCell:self
shouldAllowReply:shouldAllowReply shouldAllowReply:shouldAllowReply
didLongpressTextViewItem:self.viewItem]; didLongpressTextViewItem:self.viewItem];

@ -765,7 +765,8 @@ const CGFloat kMaxTextViewHeight = 98;
[self clearLinkPreviewView]; [self clearLinkPreviewView];
LinkPreviewView *linkPreviewView = [[LinkPreviewView alloc] initWithState:state delegate:self]; LinkPreviewView *linkPreviewView = [[LinkPreviewView alloc] initWithDelegate:self];
linkPreviewView.state = state;
self.linkPreviewView = linkPreviewView; self.linkPreviewView = linkPreviewView;
// TODO: Revisit once we have a separate quoted reply view. // TODO: Revisit once we have a separate quoted reply view.
[self.contentRows insertArrangedSubview:linkPreviewView atIndex:0]; [self.contentRows insertArrangedSubview:linkPreviewView atIndex:0];

@ -2338,6 +2338,13 @@ typedef enum : NSUInteger {
// TODO: Highlight the quoted message? // TODO: Highlight the quoted message?
} }
- (void)didTapConversationItem:(id<ConversationViewItem>)viewItem linkPreview:(OWSLinkPreview *)linkPreview
{
OWSAssertIsOnMainThread();
// TODO:
}
- (void)showDetailViewForViewItem:(id<ConversationViewItem>)conversationItem - (void)showDetailViewForViewItem:(id<ConversationViewItem>)conversationItem
{ {
OWSAssertIsOnMainThread(); OWSAssertIsOnMainThread();

@ -1,5 +1,5 @@
// //
// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// //
import Foundation import Foundation
@ -710,6 +710,12 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
// no - op // no - op
} }
func didTapConversationItem(_ viewItem: ConversationViewItem, linkPreview: OWSLinkPreview) {
// no - op
// TODO:
}
@objc func didLongPressSent(sender: UIGestureRecognizer) { @objc func didLongPressSent(sender: UIGestureRecognizer) {
guard sender.state == .began else { guard sender.state == .began else {
return return

@ -197,7 +197,16 @@ public protocol LinkPreviewViewDelegate {
@objc @objc
public class LinkPreviewView: UIStackView { public class LinkPreviewView: UIStackView {
private weak var delegate: LinkPreviewViewDelegate? private weak var delegate: LinkPreviewViewDelegate?
private let state: LinkPreviewState
@objc
public var state: LinkPreviewState? {
didSet {
AssertIsOnMainThread()
assert(oldValue == nil)
updateContents()
}
}
@available(*, unavailable, message:"use other constructor instead.") @available(*, unavailable, message:"use other constructor instead.")
required init(coder aDecoder: NSCoder) { required init(coder aDecoder: NSCoder) {
@ -209,30 +218,44 @@ public class LinkPreviewView: UIStackView {
notImplemented() notImplemented()
} }
private let imageView = UIImageView() private var cancelButton: UIButton?
private let titleLabel = UILabel() private var layoutConstraints = [NSLayoutConstraint]()
private let domainLabel = UILabel()
@objc @objc
public init(state: LinkPreviewState, public init(delegate: LinkPreviewViewDelegate?) {
delegate: LinkPreviewViewDelegate?) {
self.state = state
self.delegate = delegate self.delegate = delegate
super.init(frame: .zero) super.init(frame: .zero)
createContents() self.isUserInteractionEnabled = true
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(wasTapped)))
} }
private var isApproval: Bool { private var isApproval: Bool {
return delegate != nil return delegate != nil
} }
private func createContents() { private func resetContents() {
for subview in subviews {
subview.removeFromSuperview()
}
self.axis = .horizontal
self.alignment = .center
self.distribution = .fill
self.spacing = 0
self.isUserInteractionEnabled = true cancelButton = nil
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(wasTapped)))
NSLayoutConstraint.deactivate(layoutConstraints)
layoutConstraints = []
}
private func updateContents() {
resetContents()
guard let state = state else {
return
}
guard state.isLoaded() else { guard state.isLoaded() else {
createLoadingContents() createLoadingContents()
return return
@ -241,7 +264,7 @@ public class LinkPreviewView: UIStackView {
createMessageContents() createMessageContents()
return return
} }
createApprovalContents() createApprovalContents(state: state)
} }
private func createMessageContents() { private func createMessageContents() {
@ -250,9 +273,7 @@ public class LinkPreviewView: UIStackView {
private let approvalHeight: CGFloat = 76 private let approvalHeight: CGFloat = 76
private var cancelButton: UIButton? private func createApprovalContents(state: LinkPreviewState) {
private func createApprovalContents() {
self.axis = .horizontal self.axis = .horizontal
self.alignment = .fill self.alignment = .fill
self.distribution = .equalSpacing self.distribution = .equalSpacing
@ -260,7 +281,7 @@ public class LinkPreviewView: UIStackView {
// Image // Image
if let imageView = createImageView() { if let imageView = createImageView(state: state) {
imageView.contentMode = .scaleAspectFill imageView.contentMode = .scaleAspectFill
imageView.autoPinToSquareAspectRatio() imageView.autoPinToSquareAspectRatio()
let imageSize = approvalHeight let imageSize = approvalHeight
@ -346,7 +367,7 @@ public class LinkPreviewView: UIStackView {
strokeView.autoSetDimension(.height, toSize: CGHairlineWidth()) strokeView.autoSetDimension(.height, toSize: CGHairlineWidth())
} }
private func createImageView() -> UIImageView? { private func createImageView(state: LinkPreviewState) -> UIImageView? {
guard state.isLoaded() else { guard state.isLoaded() else {
owsFailDebug("State not loaded.") owsFailDebug("State not loaded.")
return nil return nil
@ -367,7 +388,7 @@ public class LinkPreviewView: UIStackView {
private func createLoadingContents() { private func createLoadingContents() {
self.axis = .vertical self.axis = .vertical
self.alignment = .center self.alignment = .center
self.autoSetDimension(.height, toSize: approvalHeight) self.layoutConstraints.append(self.autoSetDimension(.height, toSize: approvalHeight))
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray) let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
activityIndicator.startAnimating() activityIndicator.startAnimating()
@ -379,6 +400,9 @@ public class LinkPreviewView: UIStackView {
// MARK: Events // MARK: Events
@objc func wasTapped(sender: UIGestureRecognizer) { @objc func wasTapped(sender: UIGestureRecognizer) {
guard let state = state else {
return
}
guard sender.state == .recognized else { guard sender.state == .recognized else {
return return
} }
@ -392,7 +416,15 @@ public class LinkPreviewView: UIStackView {
return return
} }
} }
self.delegate?.linkPreviewDidTap?(urlString: self.state.urlString()) self.delegate?.linkPreviewDidTap?(urlString: state.urlString())
}
// MARK: Measurement
@objc
public class func measure(withConversationViewItem item: ConversationViewItem) -> CGSize {
// TODO:
return CGSize.zero
} }
@objc func didTapCancel(sender: UIButton) { @objc func didTapCancel(sender: UIButton) {

Loading…
Cancel
Save