Merge remote-tracking branch 'private/charlesmchen/linkPreviewsFixOmnibus'

pull/1/head
Matthew Chen 6 years ago
commit b614d33a2a

@ -363,6 +363,13 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
}
if (self.viewItem.linkPreview) {
if (self.isQuotedReply) {
UIView *spacerView = [UIView containerView];
[spacerView autoSetDimension:ALDimensionHeight toSize:self.bodyMediaQuotedReplyVSpacing];
[spacerView setCompressionResistanceHigh];
[self.stackView addArrangedSubview:spacerView];
}
self.linkPreviewView.state = self.linkPreviewState;
[self.stackView addArrangedSubview:self.linkPreviewView];
[self.linkPreviewView addBorderViewsWithBubbleView:self.bubbleView];
@ -1175,6 +1182,8 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
if (bodyMediaSize && quotedMessageSize && self.hasFullWidthMediaView) {
cellSize.height += self.bodyMediaQuotedReplyVSpacing;
} else if (quotedMessageSize && self.viewItem.linkPreview) {
cellSize.height += self.bodyMediaQuotedReplyVSpacing;
}
}

@ -130,7 +130,7 @@ const CGFloat kRemotelySourcedContentRowSpacing = 3;
- (CGFloat)bubbleHMargin
{
return 6.f;
return (self.isForPreview ? 0.f : 6.f);
}
- (CGFloat)hSpacing
@ -203,7 +203,12 @@ const CGFloat kRemotelySourcedContentRowSpacing = 3;
hStackView.spacing = self.hSpacing;
UIView *stripeView = [UIView new];
stripeView.backgroundColor = [self.conversationStyle quotedReplyStripeColorWithIsIncoming:!self.isOutgoing];
if (self.isForPreview) {
// TODO:
stripeView.backgroundColor = [self.conversationStyle quotedReplyStripeColorWithIsIncoming:YES];
} else {
stripeView.backgroundColor = [self.conversationStyle quotedReplyStripeColorWithIsIncoming:!self.isOutgoing];
}
[stripeView autoSetDimension:ALDimensionWidth toSize:self.stripeThickness];
[stripeView setContentHuggingHigh];
[stripeView setCompressionResistanceHigh];
@ -214,6 +219,8 @@ const CGFloat kRemotelySourcedContentRowSpacing = 3;
vStackView.layoutMargins = UIEdgeInsetsMake(self.textVMargin, 0, self.textVMargin, 0);
vStackView.layoutMarginsRelativeArrangement = YES;
vStackView.spacing = self.vSpacing;
[vStackView setContentHuggingHorizontalLow];
[vStackView setCompressionResistanceHorizontalLow];
[hStackView addArrangedSubview:vStackView];
UILabel *quotedAuthorLabel = [self configureQuotedAuthorLabel];
@ -275,7 +282,7 @@ const CGFloat kRemotelySourcedContentRowSpacing = 3;
quotedAttachmentView = wrapper;
}
[quotedAttachmentView autoSetDimension:ALDimensionWidth toSize:self.quotedAttachmentSize];
[quotedAttachmentView autoPinToSquareAspectRatio];
[quotedAttachmentView setContentHuggingHigh];
[quotedAttachmentView setCompressionResistanceHigh];
[hStackView addArrangedSubview:quotedAttachmentView];

@ -73,6 +73,7 @@ const CGFloat kMaxTextViewHeight = 98;
@property (nonatomic) UIEdgeInsets receivedSafeAreaInsets;
@property (nonatomic, nullable) InputLinkPreview *inputLinkPreview;
@property (nonatomic) BOOL wasLinkPreviewCancelled;
@property (nonatomic, nullable, weak) LinkPreviewView *linkPreviewView;
@end
@ -122,6 +123,7 @@ const CGFloat kMaxTextViewHeight = 98;
_inputTextView = [ConversationInputTextView new];
self.inputTextView.textViewToolbarDelegate = self;
self.inputTextView.font = [UIFont ows_dynamicTypeBodyFont];
self.inputTextView.backgroundColor = Theme.toolbarBackgroundColor;
[self.inputTextView setContentHuggingHorizontalLow];
_textViewHeightConstraint = [self.inputTextView autoSetDimension:ALDimensionHeight toSize:kMinTextViewHeight];
@ -302,9 +304,11 @@ const CGFloat kMaxTextViewHeight = 98;
[quotedMessagePreview setCompressionResistanceHorizontalLow];
self.quotedReplyWrapper.hidden = NO;
self.quotedReplyWrapper.layoutMargins = UIEdgeInsetsMake(self.quotedMessageTopMargin, 0, 0, 0);
self.quotedReplyWrapper.layoutMargins = UIEdgeInsetsZero;
[self.quotedReplyWrapper addSubview:quotedMessagePreview];
[quotedMessagePreview ows_autoPinToSuperviewMargins];
self.linkPreviewView.hasAsymmetricalRounding = !self.quotedReply;
}
- (CGFloat)quotedMessageTopMargin
@ -715,31 +719,27 @@ const CGFloat kMaxTextViewHeight = 98;
OWSAssertIsOnMainThread();
if (self.wasLinkPreviewCancelled) {
self.inputLinkPreview = nil;
[self clearLinkPreviewView];
[self clearLinkPreviewStateAndView];
return;
}
NSString *body =
[[self messageText] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (body.length < 1) {
self.inputLinkPreview = nil;
[self clearLinkPreviewView];
[self clearLinkPreviewStateAndView];
self.wasLinkPreviewCancelled = NO;
return;
}
// Don't include link previews for oversize text messages.
if ([body lengthOfBytesUsingEncoding:NSUTF8StringEncoding] >= kOversizeTextMessageSizeThreshold) {
self.inputLinkPreview = nil;
[self clearLinkPreviewView];
[self clearLinkPreviewStateAndView];
return;
}
NSString *_Nullable previewUrl = [OWSLinkPreview previewUrlForMessageBodyText:body];
if (previewUrl.length < 1) {
self.inputLinkPreview = nil;
[self clearLinkPreviewView];
[self clearLinkPreviewStateAndView];
return;
}
@ -783,12 +783,24 @@ const CGFloat kMaxTextViewHeight = 98;
LinkPreviewView *linkPreviewView = [[LinkPreviewView alloc] initWithDraftDelegate:self];
linkPreviewView.state = state;
linkPreviewView.hasAsymmetricalRounding = !self.quotedReply;
self.linkPreviewView = linkPreviewView;
self.linkPreviewWrapper.hidden = NO;
[self.linkPreviewWrapper addSubview:linkPreviewView];
[linkPreviewView ows_autoPinToSuperviewMargins];
}
- (void)clearLinkPreviewStateAndView
{
OWSAssertIsOnMainThread();
self.inputLinkPreview = nil;
self.linkPreviewView = nil;
[self clearLinkPreviewView];
}
- (void)clearLinkPreviewView
{
OWSAssertIsOnMainThread();
@ -828,8 +840,8 @@ const CGFloat kMaxTextViewHeight = 98;
self.wasLinkPreviewCancelled = YES;
self.self.inputLinkPreview = nil;
[self clearLinkPreviewView];
self.inputLinkPreview = nil;
[self clearLinkPreviewStateAndView];
}
@end

@ -669,10 +669,21 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
if (self.hasBodyText && attachment == nil && message.linkPreview) {
self.linkPreview = message.linkPreview;
if (message.linkPreview.imageAttachmentId.length > 0) {
self.linkPreviewAttachment =
TSAttachment *_Nullable linkPreviewAttachment =
[TSAttachment fetchObjectWithUniqueID:message.linkPreview.imageAttachmentId transaction:transaction];
if (!self.linkPreviewAttachment) {
if (!linkPreviewAttachment) {
OWSFailDebug(@"Could not load link preview image attachment.");
} else if (!linkPreviewAttachment.isImage) {
OWSFailDebug(@"Link preview attachment isn't an image.");
} else if ([linkPreviewAttachment isKindOfClass:[TSAttachmentStream class]]) {
TSAttachmentStream *attachmentStream = (TSAttachmentStream *)linkPreviewAttachment;
if (!attachmentStream.isValidImage) {
OWSFailDebug(@"Link preview image attachment isn't valid.");
} else {
self.linkPreviewAttachment = linkPreviewAttachment;
}
} else {
self.linkPreviewAttachment = linkPreviewAttachment;
}
}
}

@ -188,7 +188,8 @@ public class LinkPreviewSent: NSObject, LinkPreviewState {
guard let attachmentStream = imageAttachment as? TSAttachmentStream else {
return .loading
}
guard attachmentStream.isValidImage else {
guard attachmentStream.isImage,
attachmentStream.isValidImage else {
return .invalid
}
return .loaded
@ -201,7 +202,8 @@ public class LinkPreviewSent: NSObject, LinkPreviewState {
owsFailDebug("Could not load image.")
return nil
}
guard attachmentStream.isValidImage else {
guard attachmentStream.isImage,
attachmentStream.isValidImage else {
return nil
}
guard let imageFilepath = attachmentStream.originalFilePath else {
@ -229,14 +231,20 @@ public protocol LinkPreviewViewDraftDelegate {
public class LinkPreviewImageView: UIImageView {
private let maskLayer = CAShapeLayer()
private let hasAsymmetricalRounding: Bool
@objc
public init() {
public init(hasAsymmetricalRounding: Bool) {
self.hasAsymmetricalRounding = hasAsymmetricalRounding
super.init(frame: .zero)
self.layer.mask = maskLayer
}
public required init?(coder aDecoder: NSCoder) {
self.hasAsymmetricalRounding = false
super.init(coder: aDecoder)
}
@ -271,8 +279,15 @@ public class LinkPreviewImageView: UIImageView {
let bigRounding: CGFloat = 14
let smallRounding: CGFloat = 4
let upperLeftRounding = CurrentAppContext().isRTL ? smallRounding : bigRounding
let upperRightRounding = CurrentAppContext().isRTL ? bigRounding : smallRounding
let upperLeftRounding: CGFloat
let upperRightRounding: CGFloat
if hasAsymmetricalRounding {
upperLeftRounding = CurrentAppContext().isRTL ? smallRounding : bigRounding
upperRightRounding = CurrentAppContext().isRTL ? bigRounding : smallRounding
} else {
upperLeftRounding = smallRounding
upperRightRounding = smallRounding
}
let lowerRightRounding = smallRounding
let lowerLeftRounding = smallRounding
@ -323,6 +338,17 @@ public class LinkPreviewView: UIStackView {
}
}
@objc
public var hasAsymmetricalRounding: Bool = false {
didSet {
AssertIsOnMainThread()
if hasAsymmetricalRounding != oldValue {
updateContents()
}
}
}
@available(*, unavailable, message:"use other constructor instead.")
required init(coder aDecoder: NSCoder) {
notImplemented()
@ -670,7 +696,7 @@ public class LinkPreviewView: UIStackView {
owsFailDebug("Could not load image.")
return nil
}
let imageView = LinkPreviewImageView()
let imageView = LinkPreviewImageView(hasAsymmetricalRounding: self.hasAsymmetricalRounding)
imageView.image = image
return imageView
}

@ -10,7 +10,7 @@ protocol QuotedReplyPreviewDelegate: class {
}
@objc
class QuotedReplyPreview: UIView {
class QuotedReplyPreview: UIStackView {
@objc
public weak var delegate: QuotedReplyPreviewDelegate?
@ -19,8 +19,13 @@ class QuotedReplyPreview: UIView {
private var quotedMessageView: OWSQuotedMessageView?
private var heightConstraint: NSLayoutConstraint!
@objc
required init?(coder aDecoder: NSCoder) {
@available(*, unavailable, message:"use other constructor instead.")
required init(coder aDecoder: NSCoder) {
notImplemented()
}
@available(*, unavailable, message:"use other constructor instead.")
override init(frame: CGRect) {
notImplemented()
}
@ -38,6 +43,8 @@ class QuotedReplyPreview: UIView {
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: .UIContentSizeCategoryDidChange, object: nil)
}
private let draftMarginTop: CGFloat = 6
func updateContents() {
subviews.forEach { $0.removeFromSuperview() }
@ -53,21 +60,35 @@ class QuotedReplyPreview: UIView {
let cancelButton: UIButton = UIButton(type: .custom)
let buttonImage: UIImage = #imageLiteral(resourceName: "quoted-message-cancel").withRenderingMode(.alwaysTemplate)
cancelButton.setImage(buttonImage, for: .normal)
let cancelImage = UIImage(named: "compose-cancel")?.withRenderingMode(.alwaysTemplate)
cancelButton.setImage(cancelImage, for: .normal)
cancelButton.imageView?.tintColor = Theme.secondaryColor
cancelButton.addTarget(self, action: #selector(didTapCancel), for: .touchUpInside)
if let cancelSize = cancelImage?.size {
cancelButton.autoSetDimensions(to: cancelSize)
}
self.layoutMargins = .zero
self.addSubview(quotedMessageView)
self.addSubview(cancelButton)
quotedMessageView.autoPinEdges(toSuperviewMarginsExcludingEdge: .trailing)
cancelButton.autoPinEdges(toSuperviewMarginsExcludingEdge: .leading)
cancelButton.autoPinEdge(.leading, to: .trailing, of: quotedMessageView)
cancelButton.autoSetDimensions(to: CGSize(width: 40, height: 40))
self.axis = .horizontal
self.alignment = .fill
self.distribution = .fill
self.spacing = 8
self.isLayoutMarginsRelativeArrangement = true
let hMarginLeading: CGFloat = 6
let hMarginTrailing: CGFloat = 12
self.layoutMargins = UIEdgeInsets(top: draftMarginTop,
left: CurrentAppContext().isRTL ? hMarginTrailing : hMarginLeading,
bottom: 0,
right: CurrentAppContext().isRTL ? hMarginLeading : hMarginTrailing)
self.addArrangedSubview(quotedMessageView)
let cancelStack = UIStackView()
cancelStack.axis = .horizontal
cancelStack.alignment = .top
cancelStack.setContentHuggingHigh()
cancelStack.setCompressionResistanceHigh()
cancelStack.addArrangedSubview(cancelButton)
self.addArrangedSubview(cancelStack)
updateHeight()
}

@ -567,7 +567,8 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value)
UIView *borderView = [UIView new];
borderView.userInteractionEnabled = NO;
borderView.backgroundColor = nil;
borderView.backgroundColor = UIColor.clearColor;
borderView.opaque = NO;
borderView.layer.borderColor = color.CGColor;
borderView.layer.borderWidth = strokeWidth;
borderView.layer.cornerRadius = cornerRadius;

@ -453,8 +453,6 @@ public class OWSLinkPreview: MTLModel {
}
class func allPreviewUrls(forMessageBodyText body: String?) -> [String] {
AssertIsOnMainThread()
guard OWSLinkPreview.featureEnabled else {
return []
}
@ -650,16 +648,21 @@ public class OWSLinkPreview: MTLModel {
}
let data = try Data(contentsOf: URL(fileURLWithPath: asset.filePath))
guard let srcImage = UIImage(data: data) else {
Logger.error("Could not parse image.")
return Promise(error: LinkPreviewError.invalidContent)
}
let maxImageSize: CGFloat = 1024
let shouldResize = imageSize.width > maxImageSize || imageSize.height > maxImageSize
guard shouldResize else {
return Promise.value(data)
guard let dstData = UIImageJPEGRepresentation(srcImage, 0.8) else {
Logger.error("Could not write resized image.")
return Promise(error: LinkPreviewError.invalidContent)
}
return Promise.value(dstData)
}
guard let srcImage = UIImage(data: data) else {
Logger.error("Could not parse image.")
return Promise(error: LinkPreviewError.invalidContent)
}
guard let dstImage = srcImage.resized(withMaxDimensionPoints: maxImageSize) else {
Logger.error("Could not resize image.")
return Promise(error: LinkPreviewError.invalidContent)
@ -710,23 +713,14 @@ public class OWSLinkPreview: MTLModel {
return downloadImage(url: imageUrl, imageMimeType: imageMimeType)
.then(on: DispatchQueue.global()) { (imageData: Data) -> Promise<OWSLinkPreviewDraft> in
let imageFilePath = OWSFileSystem.temporaryFilePath(withFileExtension: imageFileExtension)
// We always recompress images to Jpeg.
let imageFilePath = OWSFileSystem.temporaryFilePath(withFileExtension: "jpg")
do {
try imageData.write(to: NSURL.fileURL(withPath: imageFilePath), options: .atomicWrite)
} catch let error as NSError {
owsFailDebug("file write failed: \(imageFilePath), \(error)")
return Promise(error: LinkPreviewError.assertionFailure)
}
// NOTE: imageSize(forFilePath:...) will call ows_isValidImage(...).
let imageSize = NSData.imageSize(forFilePath: imageFilePath, mimeType: imageMimeType)
let kMaxImageSize: CGFloat = 2048
guard imageSize.width > 0,
imageSize.height > 0,
imageSize.width < kMaxImageSize,
imageSize.height < kMaxImageSize else {
Logger.error("Image has invalid size: \(imageSize).")
return Promise(error: LinkPreviewError.assertionFailure)
}
let linkPreviewDraft = OWSLinkPreviewDraft(urlString: linkUrlString, title: title, imageFilePath: imageFilePath)
return Promise.value(linkPreviewDraft)

@ -643,6 +643,8 @@ open class ProxiedContentDownloader: NSObject, URLSessionTaskDelegate, URLSessio
var request = URLRequest(url: assetRequest.assetDescription.url as URL)
request.httpMethod = "HEAD"
request.httpShouldUsePipelining = true
// Some services like Reddit will severely rate-limit requests without a user agent.
request.addValue("Signal", forHTTPHeaderField: "User-Agent")
let task = downloadSession.dataTask(with: request, completionHandler: { data, response, error -> Void in
if let data = data, data.count > 0 {

Loading…
Cancel
Save