diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 77e277bf9..ca30b3827 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -42,6 +42,7 @@ 340FC8CD20518C77007AEB0F /* OWSBackupJob.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC8CC20518C76007AEB0F /* OWSBackupJob.m */; }; 340FC8D0205BF2FA007AEB0F /* OWSBackupIO.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC8CE205BF2FA007AEB0F /* OWSBackupIO.m */; }; 341F2C0F1F2B8AE700D07D6B /* DebugUIMisc.m in Sources */ = {isa = PBXBuildFile; fileRef = 341F2C0E1F2B8AE700D07D6B /* DebugUIMisc.m */; }; + 34277A5E20751BDC006049F2 /* OWSQuotedMessageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34277A5C20751BDC006049F2 /* OWSQuotedMessageView.m */; }; 3430FE181F7751D4000EC51B /* GiphyAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3430FE171F7751D4000EC51B /* GiphyAPI.swift */; }; 34330A5A1E7875FB00DF2FB9 /* fontawesome-webfont.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A591E7875FB00DF2FB9 /* fontawesome-webfont.ttf */; }; 34330A5C1E787A9800DF2FB9 /* dripicons-v2.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A5B1E787A9800DF2FB9 /* dripicons-v2.ttf */; }; @@ -602,6 +603,8 @@ 341458471FBE11C4005ABCF9 /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = translations/fa.lproj/Localizable.strings; sourceTree = ""; }; 341F2C0D1F2B8AE700D07D6B /* DebugUIMisc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMisc.h; sourceTree = ""; }; 341F2C0E1F2B8AE700D07D6B /* DebugUIMisc.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIMisc.m; sourceTree = ""; }; + 34277A5C20751BDC006049F2 /* OWSQuotedMessageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSQuotedMessageView.m; sourceTree = ""; }; + 34277A5D20751BDC006049F2 /* OWSQuotedMessageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSQuotedMessageView.h; sourceTree = ""; }; 3430FE171F7751D4000EC51B /* GiphyAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GiphyAPI.swift; sourceTree = ""; }; 34330A591E7875FB00DF2FB9 /* fontawesome-webfont.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "fontawesome-webfont.ttf"; sourceTree = ""; }; 34330A5B1E787A9800DF2FB9 /* dripicons-v2.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "dripicons-v2.ttf"; sourceTree = ""; }; @@ -1665,6 +1668,8 @@ 34D1F0A21F867BFC0066283D /* OWSMessageCell.m */, 34DBF000206BD5A400025978 /* OWSMessageTextView.h */, 34DBEFFF206BD5A400025978 /* OWSMessageTextView.m */, + 34277A5D20751BDC006049F2 /* OWSQuotedMessageView.h */, + 34277A5C20751BDC006049F2 /* OWSQuotedMessageView.m */, 34D1F0A51F867BFC0066283D /* OWSSystemMessageCell.h */, 34D1F0A61F867BFC0066283D /* OWSSystemMessageCell.m */, 34D1F0A71F867BFC0066283D /* OWSUnreadIndicatorCell.h */, @@ -3251,6 +3256,7 @@ 34D1F0C01F8EC1760066283D /* MessageRecipientStatusUtils.swift in Sources */, 45F659731E1BD99C00444429 /* CallKitCallUIAdaptee.swift in Sources */, 45BB93381E688E14001E3939 /* UIDevice+featureSupport.swift in Sources */, + 34277A5E20751BDC006049F2 /* OWSQuotedMessageView.m in Sources */, 458DE9D61DEE3FD00071BB03 /* PeerConnectionClient.swift in Sources */, 45F32C242057297A00A300D5 /* MessageDetailViewController.swift in Sources */, 34D1F0841F8678AA0066283D /* ConversationInputToolbar.m in Sources */, diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleStrokeView.h b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleStrokeView.h index c2ddcc92b..c02a4b70e 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleStrokeView.h +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleStrokeView.h @@ -2,19 +2,17 @@ // Copyright (c) 2018 Open Whisper Systems. All rights reserved. // +#import "OWSBubbleView.h" + NS_ASSUME_NONNULL_BEGIN @class OWSBubbleView; -@interface OWSBubbleStrokeView : UIView - -@property (nonatomic, weak) OWSBubbleView *bubbleView; +@interface OWSBubbleStrokeView : UIView @property (nonatomic) UIColor *strokeColor; @property (nonatomic) CGFloat strokeThickness; -- (void)updateLayers; - @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleStrokeView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleStrokeView.m index 368f44fef..425bed494 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleStrokeView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleStrokeView.m @@ -12,6 +12,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) CAShapeLayer *shapeLayer; +@property (nonatomic, weak) OWSBubbleView *bubbleView; + @end #pragma mark - @@ -31,8 +33,6 @@ NS_ASSUME_NONNULL_BEGIN self.shapeLayer = [CAShapeLayer new]; [self.layer addSublayer:self.shapeLayer]; - [self updateLayers]; - return self; } @@ -56,7 +56,7 @@ NS_ASSUME_NONNULL_BEGIN [super setFrame:frame]; - if (didChange || !self.shapeLayer) { + if (didChange) { [self updateLayers]; } } @@ -67,7 +67,7 @@ NS_ASSUME_NONNULL_BEGIN [super setBounds:bounds]; - if (didChange || !self.shapeLayer) { + if (didChange) { [self updateLayers]; } } diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.h b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.h index b7ea6b871..4ef469937 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.h +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.h @@ -2,6 +2,8 @@ // Copyright (c) 2018 Open Whisper Systems. All rights reserved. // +#import "OWSBubbleView.h" + NS_ASSUME_NONNULL_BEGIN extern const CGFloat kOWSMessageCellCornerRadius; @@ -13,11 +15,19 @@ extern const CGFloat kBubbleThornVInset; extern const CGFloat kBubbleTextHInset; extern const CGFloat kBubbleTextVInset; -@class OWSBubbleStrokeView; +@class OWSBubbleView; -@interface OWSBubbleView : UIView +@protocol OWSBubbleViewPartner + +- (void)updateLayers; + +- (void)setBubbleView:(OWSBubbleView *)bubbleView; + +@end -@property (nonatomic, weak, nullable) OWSBubbleStrokeView *bubbleStrokeView; +#pragma mark - + +@interface OWSBubbleView : UIView @property (nonatomic) BOOL isOutgoing; @property (nonatomic) BOOL hideTail; @@ -27,6 +37,14 @@ extern const CGFloat kBubbleTextVInset; - (UIBezierPath *)maskPath; +#pragma mark - Coordination + +- (void)addPartnerView:(id)view; + +- (void)clearPartnerViews; + +- (void)updatePartnerViews; + @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m index c8f3acf69..4cb83dd3f 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m @@ -3,7 +3,6 @@ // #import "OWSBubbleView.h" -#import "OWSBubbleStrokeView.h" #import NS_ASSUME_NONNULL_BEGIN @@ -22,6 +21,8 @@ const CGFloat kBubbleTextVInset = 10.f; @property (nonatomic) CAShapeLayer *maskLayer; @property (nonatomic) CAShapeLayer *shapeLayer; +@property (nonatomic, readonly) NSMutableArray> *partnerViews; + @end #pragma mark - @@ -41,7 +42,7 @@ const CGFloat kBubbleTextVInset = 10.f; self.maskLayer = [CAShapeLayer new]; self.layer.mask = self.maskLayer; - [self updateLayers]; + _partnerViews = [NSMutableArray new]; return self; } @@ -52,7 +53,7 @@ const CGFloat kBubbleTextVInset = 10.f; _isOutgoing = isOutgoing; - if (didChange || !self.shapeLayer) { + if (didChange) { [self updateLayers]; } } @@ -63,7 +64,7 @@ const CGFloat kBubbleTextVInset = 10.f; _hideTail = hideTail; - if (didChange || !self.shapeLayer) { + if (didChange) { [self updateLayers]; } } @@ -74,7 +75,7 @@ const CGFloat kBubbleTextVInset = 10.f; _isTruncated = isTruncated; - if (didChange || !self.shapeLayer) { + if (didChange) { [self updateLayers]; } } @@ -87,13 +88,13 @@ const CGFloat kBubbleTextVInset = 10.f; [super setFrame:frame]; - if (didChangeSize || !self.shapeLayer) { + if (didChangeSize) { [self updateLayers]; } // We always need to inform the "bubble stroke view" (if any) if our // frame/bounds/center changes. Its contents are not in local coordinates. - [self.bubbleStrokeView updateLayers]; + [self updatePartnerViews]; } - (void)setBounds:(CGRect)bounds @@ -104,13 +105,13 @@ const CGFloat kBubbleTextVInset = 10.f; [super setBounds:bounds]; - if (didChangeSize || !self.shapeLayer) { + if (didChangeSize) { [self updateLayers]; } // We always need to inform the "bubble stroke view" (if any) if our // frame/bounds/center changes. Its contents are not in local coordinates. - [self.bubbleStrokeView updateLayers]; + [self updatePartnerViews]; } - (void)setCenter:(CGPoint)center @@ -119,7 +120,7 @@ const CGFloat kBubbleTextVInset = 10.f; // We always need to inform the "bubble stroke view" (if any) if our // frame/bounds/center changes. Its contents are not in local coordinates. - [self.bubbleStrokeView updateLayers]; + [self updatePartnerViews]; } - (void)setBubbleColor:(nullable UIColor *)bubbleColor @@ -211,6 +212,33 @@ const CGFloat kBubbleTextVInset = 10.f; return bezierPath; } +#pragma mark - Coordination + +- (void)addPartnerView:(id)partnerView +{ + OWSAssert(self.partnerViews); + + [partnerView setBubbleView:self]; + + [self.partnerViews addObject:partnerView]; +} + +- (void)clearPartnerViews +{ + OWSAssert(self.partnerViews); + + [self.partnerViews removeAllObjects]; +} + +- (void)updatePartnerViews +{ + [self layoutIfNeeded]; + + for (id partnerView in self.partnerViews) { + [partnerView updateLayers]; + } +} + @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m index 58f5b30c7..4b1178416 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m @@ -13,6 +13,7 @@ #import "OWSExpirationTimerView.h" #import "OWSGenericAttachmentView.h" #import "OWSMessageTextView.h" +#import "OWSQuotedMessageView.h" #import "Signal-Swift.h" #import "UIColor+OWS.h" #import @@ -21,11 +22,6 @@ NS_ASSUME_NONNULL_BEGIN -CG_INLINE CGSize CGSizeCeil(CGSize size) -{ - return CGSizeMake((CGFloat)ceil(size.width), (CGFloat)ceil(size.height)); -} - @interface OWSMessageCell () // The nullable properties are created as needed. @@ -266,23 +262,6 @@ CG_INLINE CGSize CGSizeCeil(CGSize size) return self.viewItem.hasQuotedAttachment; } -- (BOOL)hasQuotedAttachmentThumbnail -{ - // This should always be valid for the appropriate cell types. - OWSAssert(self.viewItem); - - return (self.viewItem.hasQuotedAttachment && - [TSAttachmentStream hasThumbnailForMimeType:self.viewItem.quotedAttachmentMimetype]); -} - -- (nullable DisplayableText *)displayableQuotedText -{ - // This should always be valid for the appropriate cell types. - OWSAssert(self.viewItem.displayableQuotedText); - - return self.viewItem.displayableQuotedText; -} - - (TSMessage *)message { OWSAssert([self.viewItem.interaction isKindOfClass:[TSMessage class]]); @@ -382,34 +361,30 @@ CG_INLINE CGSize CGSizeCeil(CGSize size) if (self.isQuotedReply) { OWSAssert(!lastSubview); - UIView *quotedMessageView = [self createQuotedMessageView]; - + TSMessage *message = (TSMessage *)self.viewItem.interaction; + OWSQuotedMessageView *quotedMessageView = [OWSQuotedMessageView + quotedMessageViewForConversation:message.quotedMessage + displayableQuotedText:(self.viewItem.hasQuotedText ? self.viewItem.displayableQuotedText : nil)]; + [quotedMessageView createContents]; [self.bubbleView addSubview:quotedMessageView]; - CGFloat leadingMargin = self.quotedBubbleLeadingMargin; - CGFloat trailingMargin = self.quotedBubbleTrailingMargin; - + CGFloat bubbleLeadingMargin = (self.isIncoming ? kBubbleThornSideInset : 0.f); + CGFloat bubbleTrailingMargin = (self.isIncoming ? 0.f : kBubbleThornSideInset); [self.viewConstraints addObjectsFromArray:@[ - [quotedMessageView autoPinLeadingToSuperviewMarginWithInset:leadingMargin], - [quotedMessageView autoPinTrailingToSuperviewMarginWithInset:trailingMargin], + [quotedMessageView autoPinLeadingToSuperviewMarginWithInset:bubbleLeadingMargin], + [quotedMessageView autoPinTrailingToSuperviewMarginWithInset:bubbleTrailingMargin], ]]; if (lastSubview) { - [self.viewConstraints addObject:[quotedMessageView autoPinEdge:ALEdgeTop - toEdge:ALEdgeBottom - ofView:lastSubview - withOffset:self.quotedMessageTopInset]]; + [self.viewConstraints + addObject:[quotedMessageView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:lastSubview]]; } else { - [self.viewConstraints addObject:[quotedMessageView autoPinEdgeToSuperviewEdge:ALEdgeTop - withInset:self.quotedMessageTopInset]]; + [self.viewConstraints addObject:[quotedMessageView autoPinEdgeToSuperviewEdge:ALEdgeTop]]; } lastSubview = quotedMessageView; bottomMargin = 0; - [self.bubbleView logFrameLaterWithLabel:@"bubbleView"]; - [quotedMessageView logFrameLaterWithLabel:@"quotedMessageView"]; - - // TODO: Consider stroking the quoted thumbnail. + [self.bubbleView addPartnerView:quotedMessageView.boundsStrokeView]; } UIView *_Nullable bodyMediaView = nil; @@ -493,15 +468,14 @@ CG_INLINE CGSize CGSizeCeil(CGSize size) OWSBubbleStrokeView *bubbleStrokeView = [OWSBubbleStrokeView new]; bubbleStrokeView.strokeThickness = 1.f; bubbleStrokeView.strokeColor = [UIColor colorWithWhite:0.f alpha:0.1f]; - bubbleStrokeView.bubbleView = self.bubbleView; [self.bubbleView addSubview:bubbleStrokeView]; [bubbleStrokeView autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:bodyMediaView]; [bubbleStrokeView autoPinEdge:ALEdgeBottom toEdge:ALEdgeBottom ofView:bodyMediaView]; [bubbleStrokeView autoPinEdge:ALEdgeLeft toEdge:ALEdgeLeft ofView:bodyMediaView]; [bubbleStrokeView autoPinEdge:ALEdgeRight toEdge:ALEdgeRight ofView:bodyMediaView]; - self.bubbleView.bubbleStrokeView = bubbleStrokeView; - OWSAssert(self.bubbleView.bubbleStrokeView); + + [self.bubbleView addPartnerView:bubbleStrokeView]; } } @@ -786,19 +760,6 @@ CG_INLINE CGSize CGSizeCeil(CGSize size) return [UIFont systemFontOfSize:12.0f]; } -- (UILabel *)createQuotedTextLabel -{ - UILabel *quotedTextLabel = [UILabel new]; - quotedTextLabel.numberOfLines = 3; - quotedTextLabel.lineBreakMode = NSLineBreakByWordWrapping; - quotedTextLabel.text = self.displayableQuotedText.displayText; - quotedTextLabel.textColor = self.quotedTextColor; - - // Honor dynamic type in the message bodies. - quotedTextLabel.font = self.textMessageFont; - return quotedTextLabel; -} - - (OWSMessageTextView *)configureBodyTextView { OWSAssert(self.hasBodyText); @@ -864,78 +825,6 @@ CG_INLINE CGSize CGSizeCeil(CGSize size) return tapForMoreLabel; } -- (UIView *)createQuotedMessageView -{ - OWSAssert(self.isQuotedReply); - - UIView *quotedMessageView = [UIView containerView]; - quotedMessageView.userInteractionEnabled = NO; - quotedMessageView.clipsToBounds = YES; - // TODO: - quotedMessageView.layer.cornerRadius = 3.f; - quotedMessageView.backgroundColor = [UIColor colorWithRGBHex:0xe2f7fa]; - - UIView *quoteStripView = [UIView containerView]; - quoteStripView.backgroundColor = (self.isIncoming ? [UIColor whiteColor] : [UIColor colorWithRGBHex:0x007884]); - quoteStripView.userInteractionEnabled = NO; - [quotedMessageView addSubview:quoteStripView]; - [quoteStripView autoPinHeightToSuperview]; - [quoteStripView autoPinLeadingToSuperviewMargin]; - [quoteStripView autoSetDimension:ALDimensionWidth toSize:self.quotedReplyStripeThickness]; - - UIView *_Nullable quotedThumbnailView = nil; - if (self.hasQuotedAttachmentThumbnail) { - // TODO: - quotedThumbnailView = [UIView containerView]; - quotedThumbnailView.userInteractionEnabled = NO; - quotedThumbnailView.backgroundColor = [UIColor redColor]; - [quotedMessageView addSubview:quotedThumbnailView]; - [quotedThumbnailView autoPinTopToSuperviewMargin]; - [quotedThumbnailView autoPinTrailingToSuperviewMargin]; - [quotedThumbnailView autoSetDimension:ALDimensionWidth toSize:self.quotedThumbnailSize]; - [quotedThumbnailView autoSetDimension:ALDimensionHeight toSize:self.quotedThumbnailSize]; - } - - OWSContactsManager *contactsManager = Environment.current.contactsManager; - NSString *quotedAuthor = [contactsManager displayNameForPhoneIdentifier:self.viewItem.quotedRecipientId]; - - UILabel *quotedAuthorLabel = [UILabel new]; - quotedAuthorLabel.text = quotedAuthor; - quotedAuthorLabel.font = self.quotedAuthorFont; - quotedAuthorLabel.textColor - = (self.isIncoming ? [UIColor colorWithRGBHex:0xd84315] : [UIColor colorWithRGBHex:0x007884]); - quotedAuthorLabel.numberOfLines = 1; - quotedAuthorLabel.lineBreakMode = NSLineBreakByTruncatingTail; - [quotedMessageView addSubview:quotedAuthorLabel]; - [quotedAuthorLabel autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:self.quotedContentTopInset]; - [quotedAuthorLabel autoPinLeadingToTrailingEdgeOfView:quoteStripView offset:self.quotedReplyStripeHSpacing]; - if (quotedThumbnailView) { - [quotedAuthorLabel autoPinTrailingToEdgeOfView:quotedThumbnailView offset:self.quotedThumbnailHSpacing]; - } else { - [quotedAuthorLabel autoPinTrailingToSuperviewMarginWithInset:self.quotedContentTrailingMargin]; - } - - if (self.hasQuotedText) { - UILabel *quotedTextLabel = [self createQuotedTextLabel]; - - [quotedMessageView addSubview:quotedTextLabel]; - [quotedTextLabel autoPinEdge:ALEdgeTop - toEdge:ALEdgeBottom - ofView:quotedAuthorLabel - withOffset:self.quotedAuthorBottomSpacing]; - [quotedTextLabel autoPinLeadingToTrailingEdgeOfView:quoteStripView offset:self.quotedReplyStripeHSpacing]; - if (quotedThumbnailView) { - [quotedTextLabel autoPinLeadingToTrailingEdgeOfView:quotedThumbnailView - offset:self.quotedThumbnailHSpacing]; - } else { - [quotedTextLabel autoPinTrailingToSuperviewMarginWithInset:self.quotedContentTrailingMargin]; - } - [quotedTextLabel autoPinBottomToSuperviewMarginWithInset:self.quotedContentBottomInset]; - } - - return quotedMessageView; -} - - (UIView *)loadViewForStillImage { OWSAssert(self.attachmentStream); @@ -1284,73 +1173,17 @@ CG_INLINE CGSize CGSizeCeil(CGSize size) OWSAssert(self.viewItem); OWSAssert([self.viewItem.interaction isKindOfClass:[TSMessage class]]); - CGSize result = CGSizeZero; - if (!self.isQuotedReply) { - return result; - } - - result.width += self.quotedMessageHInset; - result.width += self.quotedReplyStripeThickness; - result.width += self.quotedReplyStripeHSpacing; - - result.height += self.quotedMessageTopInset; - - CGFloat thumbnailHeight = 0.f; - if (self.hasQuotedAttachmentThumbnail) { - result.width += self.quotedThumbnailHSpacing; - result.width += self.quotedThumbnailSize; - - thumbnailHeight = self.quotedThumbnailSize; - } else { - result.width += self.quotedContentTrailingMargin; + return CGSizeZero; } - result.width += self.quotedMessageHInset; - - // Once we've determined everything _except_ the size of the text - // content (i.e. the quoted author and the quoted text (if any)), - // we can determine the size of the text content. + TSMessage *message = (TSMessage *)self.viewItem.interaction; + OWSQuotedMessageView *quotedMessageView = [OWSQuotedMessageView + quotedMessageViewForConversation:message.quotedMessage + displayableQuotedText:(self.hasQuotedText ? self.viewItem.displayableQuotedText : nil)]; const int maxMessageWidth = [self maxMessageWidthForContentWidth:contentWidth]; - CGFloat maxTextWidth = (maxMessageWidth - (self.textTrailingMargin + self.textLeadingMargin + result.width)); - CGFloat textWidth = 0.f; - - // Author - { - OWSContactsManager *contactsManager = Environment.current.contactsManager; - NSString *quotedAuthor = [contactsManager displayNameForPhoneIdentifier:self.viewItem.quotedRecipientId]; - - UILabel *quotedAuthorLabel = [UILabel new]; - quotedAuthorLabel.text = quotedAuthor; - quotedAuthorLabel.font = self.quotedAuthorFont; - quotedAuthorLabel.lineBreakMode = NSLineBreakByTruncatingTail; - quotedAuthorLabel.numberOfLines = 1; - - CGSize quotedAuthorSize = CGSizeCeil([quotedAuthorLabel sizeThatFits:CGSizeMake(maxTextWidth, CGFLOAT_MAX)]); - - textWidth = MAX(textWidth, quotedAuthorSize.width); - result.height += self.quotedContentTopInset; - result.height += self.quotedAuthorHeight; - } - - if (self.hasQuotedText) { - UILabel *quotedTextLabel = [self createQuotedTextLabel]; - - CGSize textSize = CGSizeCeil([quotedTextLabel sizeThatFits:CGSizeMake(maxTextWidth, CGFLOAT_MAX)]); - - textWidth = MAX(textWidth, textSize.width); - result.height += self.quotedAuthorBottomSpacing; - result.height += textSize.height; - } - - result.width += textWidth; - result.height += self.quotedContentBottomInset; - - result.height = MAX(result.height, thumbnailHeight); - - if (includeMargins) { - result.width += kBubbleThornSideInset; - } + CGSize result = [quotedMessageView sizeForMaxWidth:maxMessageWidth - kBubbleThornSideInset]; + result.width += kBubbleThornSideInset; return result; } @@ -1412,82 +1245,6 @@ CG_INLINE CGSize CGSizeCeil(CGSize size) return (CGFloat)ceil([self tapForMoreFont].lineHeight * 1.25); } -// TODO: -- (UIFont *)quotedAuthorFont -{ - return [UIFont ows_regularFontWithSize:10.f]; -} - -// TODO: -- (CGFloat)quotedAuthorHeight -{ - return (CGFloat)ceil([self quotedAuthorFont].lineHeight * 1.25); -} - -// TODO: -- (CGFloat)quotedAuthorBottomSpacing -{ - return 2.f; -} - -// TODO: -- (CGFloat)quotedContentTopInset -{ - return 3.f; -} - -// TODO: -- (CGFloat)quotedContentBottomInset -{ - return 3.f; -} - -// Distance from top edge of "quoted message" bubble to top of message bubble. -// TODO: -- (CGFloat)quotedMessageTopInset -{ - return 3.f; -} - -// Distance from side of "quoted message" bubble to side of message bubble. -// TODO: -- (CGFloat)quotedMessageHInset -{ - return 3.f; -} - -// TODO: -- (CGFloat)quotedReplyStripeThickness -{ - return 3.f; -} - -// The spacing between the vertical "quoted reply stripe" -// and the quoted message content. -// TODO: -- (CGFloat)quotedReplyStripeHSpacing -{ - return 10.f; -} - -// TODO: -- (CGFloat)quotedThumbnailSize -{ - return 30.f; -} - -// TODO: -- (CGFloat)quotedThumbnailHSpacing -{ - return 10.f; -} - -// TODO: -- (CGFloat)quotedContentTrailingMargin -{ - return 10.f; -} - #pragma mark - - (BOOL)isIncoming @@ -1518,24 +1275,6 @@ CG_INLINE CGSize CGSizeCeil(CGSize size) return result; } -- (CGFloat)quotedBubbleLeadingMargin -{ - CGFloat result = self.quotedMessageHInset; - if (self.isIncoming) { - result += kBubbleThornSideInset; - } - return result; -} - -- (CGFloat)quotedBubbleTrailingMargin -{ - CGFloat result = self.quotedMessageHInset; - if (!self.isIncoming) { - result += kBubbleThornSideInset; - } - return result; -} - - (CGFloat)textTopMargin { return kBubbleTextVInset; @@ -1551,11 +1290,6 @@ CG_INLINE CGSize CGSizeCeil(CGSize size) return self.isIncoming ? [UIColor blackColor] : [UIColor whiteColor]; } -- (UIColor *)quotedTextColor -{ - return [UIColor blackColor]; -} - - (BOOL)isMediaBeingSent { if (self.isIncoming) { @@ -1595,7 +1329,7 @@ CG_INLINE CGSize CGSizeCeil(CGSize size) self.bubbleView.hidden = YES; self.bubbleView.bubbleColor = nil; - self.bubbleView.bubbleStrokeView = nil; + [self.bubbleView clearPartnerViews]; for (UIView *subview in self.bubbleView.subviews) { [subview removeFromSuperview]; diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.h b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.h new file mode 100644 index 000000000..09cfcd274 --- /dev/null +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.h @@ -0,0 +1,32 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +NS_ASSUME_NONNULL_BEGIN + +@class DisplayableText; +@class OWSBubbleStrokeView; +@class TSQuotedMessage; + +@interface OWSQuotedMessageView : UIView + +@property (nonatomic, nullable, readonly) OWSBubbleStrokeView *boundsStrokeView; + +- (instancetype)init NS_UNAVAILABLE; + +// Only needs to be called if we're going to render this instance. +- (void)createContents; + +// Measurement +- (CGSize)sizeForMaxWidth:(CGFloat)maxWidth; + +// Factory method for "message bubble" views. ++ (OWSQuotedMessageView *)quotedMessageViewForConversation:(TSQuotedMessage *)quotedMessage + displayableQuotedText:(nullable DisplayableText *)displayableQuotedText; + +// Factory method for "message compose" views. ++ (OWSQuotedMessageView *)quotedMessageViewForPreview:(TSQuotedMessage *)quotedMessage; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m new file mode 100644 index 000000000..835091e36 --- /dev/null +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m @@ -0,0 +1,383 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "OWSQuotedMessageView.h" +#import "ConversationViewItem.h" +#import "Environment.h" +#import "OWSBubbleStrokeView.h" +#import "Signal-Swift.h" +#import +#import +#import +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface OWSQuotedMessageView () + +@property (nonatomic, readonly) TSQuotedMessage *quotedMessage; +@property (nonatomic, nullable, readonly) DisplayableText *displayableQuotedText; + +@property (nonatomic, readonly) UIFont *textMessageFont; + +@property (nonatomic, nullable) OWSBubbleStrokeView *boundsStrokeView; + +@end + +#pragma mark - + +@implementation OWSQuotedMessageView + ++ (OWSQuotedMessageView *)quotedMessageViewForConversation:(TSQuotedMessage *)quotedMessage + displayableQuotedText:(nullable DisplayableText *)displayableQuotedText +{ + OWSAssert(quotedMessage); + + return + [[OWSQuotedMessageView alloc] initWithQuotedMessage:quotedMessage displayableQuotedText:displayableQuotedText]; +} + ++ (OWSQuotedMessageView *)quotedMessageViewForPreview:(TSQuotedMessage *)quotedMessage +{ + OWSAssert(quotedMessage); + + DisplayableText *_Nullable displayableQuotedText = nil; + if (quotedMessage.body.length > 0) { + displayableQuotedText = [DisplayableText displayableText:quotedMessage.body]; + } + + return + [[OWSQuotedMessageView alloc] initWithQuotedMessage:quotedMessage displayableQuotedText:displayableQuotedText]; +} + +- (instancetype)initWithQuotedMessage:(TSQuotedMessage *)quotedMessage + displayableQuotedText:(nullable DisplayableText *)displayableQuotedText +{ + self = [super init]; + + if (!self) { + return self; + } + + OWSAssert(quotedMessage); + + _quotedMessage = quotedMessage; + _displayableQuotedText = displayableQuotedText; + _textMessageFont = [UIFont ows_dynamicTypeBodyFont]; + + return self; +} + +- (BOOL)hasQuotedAttachment +{ + return (self.quotedMessage.contentType.length > 0 + && ![OWSMimeTypeOversizeTextMessage isEqualToString:self.quotedMessage.contentType]); +} + +- (BOOL)hasQuotedAttachmentThumbnail +{ + return (self.quotedMessage.contentType.length > 0 + && ![OWSMimeTypeOversizeTextMessage isEqualToString:self.quotedMessage.contentType] && + [TSAttachmentStream hasThumbnailForMimeType:self.quotedMessage.contentType]); +} + +- (NSString *)quotedSnippet +{ + if (self.displayableQuotedText.displayText.length > 0) { + return self.displayableQuotedText.displayText; + } else { + NSString *mimeType = self.quotedMessage.contentType; + + if (mimeType.length > 0) { + return [TSAttachment emojiForMimeType:mimeType]; + } + } + + return @""; +} + +#pragma mark - + +- (void)createContents +{ + self.backgroundColor = [UIColor whiteColor]; + self.userInteractionEnabled = NO; + self.layoutMargins = UIEdgeInsetsZero; + self.clipsToBounds = YES; + + self.boundsStrokeView = [OWSBubbleStrokeView new]; + self.boundsStrokeView.strokeColor = OWSMessagesBubbleImageFactory.bubbleColorIncoming; + self.boundsStrokeView.strokeThickness = 1.f; + [self addSubview:self.boundsStrokeView]; + [self.boundsStrokeView autoPinToSuperviewEdges]; + [self.boundsStrokeView setContentHuggingLow]; + [self.boundsStrokeView setCompressionResistanceLow]; + + UIView *_Nullable quotedAttachmentView = nil; + // TODO: + if (self.hasQuotedAttachment) { + // TODO: + quotedAttachmentView = [UIView containerView]; + quotedAttachmentView.userInteractionEnabled = NO; + quotedAttachmentView.backgroundColor = [UIColor redColor]; + [self addSubview:quotedAttachmentView]; + [quotedAttachmentView autoPinTrailingToSuperviewMarginWithInset:self.quotedContentHInset]; + [quotedAttachmentView autoVCenterInSuperview]; + [quotedAttachmentView autoSetDimension:ALDimensionWidth toSize:self.quotedAttachmentSize]; + [quotedAttachmentView autoSetDimension:ALDimensionHeight toSize:self.quotedAttachmentSize]; + [quotedAttachmentView setContentHuggingHigh]; + [quotedAttachmentView setCompressionResistanceHigh]; + + if (quotedAttachmentView) { + quotedAttachmentView.layer.borderColor = [UIColor colorWithWhite:0.f alpha:0.1f].CGColor; + quotedAttachmentView.layer.borderWidth = 1.f; + quotedAttachmentView.layer.cornerRadius = 2.f; + } + } + + OWSContactsManager *contactsManager = Environment.current.contactsManager; + NSString *quotedAuthor = [contactsManager displayNameForPhoneIdentifier:self.quotedMessage.authorId]; + + UILabel *quotedAuthorLabel = [UILabel new]; + { + quotedAuthorLabel.text = quotedAuthor; + quotedAuthorLabel.font = self.quotedAuthorFont; + // TODO: + quotedAuthorLabel.textColor = [UIColor ows_darkGrayColor]; + quotedAuthorLabel.numberOfLines = 1; + quotedAuthorLabel.lineBreakMode = NSLineBreakByTruncatingTail; + [self addSubview:quotedAuthorLabel]; + [quotedAuthorLabel autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:self.quotedAuthorTopInset]; + [quotedAuthorLabel autoPinLeadingToSuperviewMarginWithInset:self.quotedContentHInset]; + if (quotedAttachmentView) { + [quotedAuthorLabel autoPinTrailingToLeadingEdgeOfView:quotedAttachmentView + offset:self.quotedAttachmentHSpacing]; + } else { + [quotedAuthorLabel autoPinTrailingToSuperviewMarginWithInset:self.quotedContentHInset]; + } + [quotedAuthorLabel autoSetDimension:ALDimensionHeight toSize:self.quotedAuthorHeight]; + [quotedAuthorLabel setContentHuggingLow]; + [quotedAuthorLabel setCompressionResistanceLow]; + } + + { + // Stripe and text container. + UIView *stripeAndTextContainer = [UIView containerView]; + [self addSubview:stripeAndTextContainer]; + [stripeAndTextContainer autoPinEdge:ALEdgeTop + toEdge:ALEdgeBottom + ofView:quotedAuthorLabel + withOffset:self.quotedAuthorBottomSpacing]; + [stripeAndTextContainer autoPinLeadingToSuperviewMarginWithInset:self.quotedContentHInset]; + if (quotedAttachmentView) { + [stripeAndTextContainer autoPinTrailingToLeadingEdgeOfView:quotedAttachmentView + offset:self.quotedAttachmentHSpacing]; + } else { + [stripeAndTextContainer autoPinTrailingToSuperviewMarginWithInset:self.quotedContentHInset]; + } + [stripeAndTextContainer autoPinBottomToSuperviewMarginWithInset:self.quotedTextBottomInset]; + [stripeAndTextContainer setContentHuggingLow]; + [stripeAndTextContainer setCompressionResistanceLow]; + + // Stripe. + BOOL isIncomingQuote + = ![NSObject isNullableObject:self.quotedMessage.authorId equalTo:TSAccountManager.localNumber]; + UIColor *stripeColor = (isIncomingQuote ? OWSMessagesBubbleImageFactory.bubbleColorIncoming + : OWSMessagesBubbleImageFactory.bubbleColorOutgoingSent); + UIView *quoteStripView = [UIView containerView]; + quoteStripView.backgroundColor = stripeColor; + quoteStripView.userInteractionEnabled = NO; + [stripeAndTextContainer addSubview:quoteStripView]; + [quoteStripView autoPinHeightToSuperview]; + [quoteStripView autoPinLeadingToSuperviewMargin]; + [quoteStripView autoSetDimension:ALDimensionWidth toSize:self.quotedReplyStripeThickness]; + [quoteStripView setContentHuggingVerticalLow]; + [quoteStripView setContentHuggingHorizontalHigh]; + [quoteStripView setCompressionResistanceHigh]; + + // Text. + UILabel *quotedTextLabel = [self createQuotedTextLabel]; + [stripeAndTextContainer addSubview:quotedTextLabel]; + [quotedTextLabel autoPinTopToSuperviewMarginWithInset:self.quotedReplyStripeVExtension]; + [quotedTextLabel autoPinBottomToSuperviewMarginWithInset:self.quotedReplyStripeVExtension]; + [quotedTextLabel autoPinLeadingToTrailingEdgeOfView:quoteStripView offset:self.quotedReplyStripeHSpacing]; + [quotedTextLabel autoPinTrailingToSuperviewMargin]; + [quotedTextLabel setContentHuggingLow]; + [quotedTextLabel setCompressionResistanceLow]; + } +} + +#pragma mark - Measurement + +- (CGSize)sizeForMaxWidth:(CGFloat)maxWidth +{ + CGSize result = CGSizeZero; + + result.width += self.quotedContentHInset; + + CGFloat thumbnailHeight = 0.f; + if (self.hasQuotedAttachment) { + result.width += self.quotedAttachmentHSpacing; + result.width += self.quotedAttachmentSize; + + thumbnailHeight += self.quotedAttachmentMinVInset; + thumbnailHeight += self.quotedAttachmentSize; + thumbnailHeight += self.quotedAttachmentMinVInset; + } + + result.width += self.quotedContentHInset; + + // Quoted Author + CGFloat quotedAuthorWidth = 0.f; + { + CGFloat maxQuotedAuthorWidth = maxWidth - result.width; + + OWSContactsManager *contactsManager = Environment.current.contactsManager; + NSString *quotedAuthor = [contactsManager displayNameForPhoneIdentifier:self.quotedMessage.authorId]; + + UILabel *quotedAuthorLabel = [UILabel new]; + quotedAuthorLabel.text = quotedAuthor; + quotedAuthorLabel.font = self.quotedAuthorFont; + quotedAuthorLabel.lineBreakMode = NSLineBreakByTruncatingTail; + quotedAuthorLabel.numberOfLines = 1; + + CGSize quotedAuthorSize + = CGSizeCeil([quotedAuthorLabel sizeThatFits:CGSizeMake(maxQuotedAuthorWidth, CGFLOAT_MAX)]); + + quotedAuthorWidth = quotedAuthorSize.width; + + result.height += self.quotedAuthorTopInset; + result.height += self.quotedAuthorHeight; + result.height += self.quotedAuthorBottomSpacing; + } + + CGFloat quotedTextWidth = 0.f; + { + CGFloat maxQuotedTextWidth + = (maxWidth - (result.width + self.quotedReplyStripeThickness + self.quotedReplyStripeHSpacing)); + + UILabel *quotedTextLabel = [self createQuotedTextLabel]; + + CGSize textSize = CGSizeCeil([quotedTextLabel sizeThatFits:CGSizeMake(maxQuotedTextWidth, CGFLOAT_MAX)]); + + quotedTextWidth = textSize.width + self.quotedReplyStripeThickness + self.quotedReplyStripeHSpacing; + result.height += textSize.height + self.quotedReplyStripeVExtension * 2; + } + + CGFloat textWidth = MAX(quotedAuthorWidth, quotedTextWidth); + result.width += textWidth; + + result.height += self.quotedTextBottomInset; + result.height = MAX(result.height, thumbnailHeight); + + return result; +} + +- (UILabel *)createQuotedTextLabel +{ + UILabel *quotedTextLabel = [UILabel new]; + quotedTextLabel.numberOfLines = 3; + quotedTextLabel.lineBreakMode = NSLineBreakByWordWrapping; + quotedTextLabel.text = self.quotedSnippet; + quotedTextLabel.textColor = self.quotedTextColor; + + // Honor dynamic type in the message bodies. + quotedTextLabel.font = self.textMessageFont; + return quotedTextLabel; +} + +- (UIColor *)quotedTextColor +{ + return [UIColor blackColor]; +} + +// TODO: +- (UIFont *)quotedAuthorFont +{ + return [UIFont ows_regularFontWithSize:10.f]; +} + +// TODO: +- (CGFloat)quotedAuthorHeight +{ + return (CGFloat)ceil([self quotedAuthorFont].lineHeight * 1.f); +} + +// TODO: +- (CGFloat)quotedAuthorTopInset +{ + return 4.f; +} + +// TODO: +- (CGFloat)quotedAuthorBottomSpacing +{ + return 2.f; +} + +// TODO: +- (CGFloat)quotedTextBottomInset +{ + return 5.f; +} + +// TODO: +- (CGFloat)quotedReplyStripeThickness +{ + return 2.f; +} + +// TODO: +- (CGFloat)quotedReplyStripeVExtension +{ + return 5.f; +} + +// The spacing between the vertical "quoted reply stripe" +// and the quoted message content. +// TODO: +- (CGFloat)quotedReplyStripeHSpacing +{ + return 8.f; +} + +// Distance from top edge of "quoted message" bubble to top of message bubble. +// TODO: +- (CGFloat)quotedAttachmentMinVInset +{ + return 10.f; +} + +// TODO: +- (CGFloat)quotedAttachmentSize +{ + return 30.f; +} + +// TODO: +- (CGFloat)quotedAttachmentHSpacing +{ + return 10.f; +} + +// Distance from sides of the quoted content to the sides of the message bubble. +// TODO: +- (CGFloat)quotedContentHInset +{ + return 8.f; +} + +#pragma mark - + +- (CGSize)sizeThatFits:(CGSize)size +{ + return [self sizeForMaxWidth:CGFLOAT_MAX]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/HomeViewController.m b/Signal/src/ViewControllers/HomeViewController.m index 0c4af11c7..344c90819 100644 --- a/Signal/src/ViewControllers/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeViewController.m @@ -284,15 +284,6 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState }; } [self updateBarButtonItems]; - - dispatch_async(dispatch_get_main_queue(), ^{ - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0]; - TSThread *thread = [self threadForIndexPath:indexPath]; - if (!thread) { - return; - } - [self presentThread:thread keyboardOnViewAppearing:NO callOnViewAppearing:NO]; - }); } - (void)viewDidAppear:(BOOL)animated diff --git a/SignalMessaging/categories/UIFont+OWS.h b/SignalMessaging/categories/UIFont+OWS.h index 2bb7cb392..715f5bf52 100644 --- a/SignalMessaging/categories/UIFont+OWS.h +++ b/SignalMessaging/categories/UIFont+OWS.h @@ -18,6 +18,8 @@ NS_ASSUME_NONNULL_BEGIN + (UIFont *)ows_boldFontWithSize:(CGFloat)size; ++ (UIFont *)ows_dynamicTypeBodyFont:(CGFloat)size; + #pragma mark - Icon Fonts + (UIFont *)ows_fontAwesomeFont:(CGFloat)size; diff --git a/SignalMessaging/categories/UIFont+OWS.m b/SignalMessaging/categories/UIFont+OWS.m index 1275111f8..87532213b 100644 --- a/SignalMessaging/categories/UIFont+OWS.m +++ b/SignalMessaging/categories/UIFont+OWS.m @@ -33,6 +33,11 @@ NS_ASSUME_NONNULL_BEGIN return [UIFont boldSystemFontOfSize:size]; } ++ (UIFont *)ows_dynamicTypeBodyFont:(CGFloat)size +{ + return [UIFont ows_dynamicTypeBodyFont]; +} + #pragma mark - Icon Fonts + (UIFont *)ows_fontAwesomeFont:(CGFloat)size diff --git a/SignalMessaging/categories/UIView+OWS.h b/SignalMessaging/categories/UIView+OWS.h index 2b4b114c8..043a63fc3 100644 --- a/SignalMessaging/categories/UIView+OWS.h +++ b/SignalMessaging/categories/UIView+OWS.h @@ -125,4 +125,11 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value); @end +#pragma mark - Macros + +CG_INLINE CGSize CGSizeCeil(CGSize size) +{ + return CGSizeMake((CGFloat)ceil(size.width), (CGFloat)ceil(size.height)); +} + NS_ASSUME_NONNULL_END diff --git a/SignalMessaging/utils/OWSMessagesBubbleImageFactory.swift b/SignalMessaging/utils/OWSMessagesBubbleImageFactory.swift index 7f9c65d38..83582cf4b 100644 --- a/SignalMessaging/utils/OWSMessagesBubbleImageFactory.swift +++ b/SignalMessaging/utils/OWSMessagesBubbleImageFactory.swift @@ -9,7 +9,8 @@ import SignalServiceKit @objc public class OWSMessagesBubbleImageFactory: NSObject { - static let shared = OWSMessagesBubbleImageFactory() + @objc + public static let shared = OWSMessagesBubbleImageFactory() private let jsqFactory = JSQMessagesBubbleImageFactory()! @@ -57,12 +58,16 @@ public class OWSMessagesBubbleImageFactory: NSObject { } } + @objc public static let bubbleColorIncoming = UIColor.jsq_messageBubbleLightGray()! + @objc public static let bubbleColorOutgoingUnsent = UIColor.gray + @objc public static let bubbleColorOutgoingSending = UIColor.ows_fadedBlue + @objc public static let bubbleColorOutgoingSent = UIColor.ows_materialBlue public func bubbleColor(message: TSMessage) -> UIColor {