From 68c7abcbb957bed5c07b0adaab8d366de5d6399f Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 6 Jul 2018 13:08:56 -0600 Subject: [PATCH 1/7] Sharp corners --- .../ConversationView/Cells/OWSBubbleView.h | 3 +- .../ConversationView/Cells/OWSBubbleView.m | 56 ++++++++----------- .../Cells/OWSMessageBubbleView.m | 21 ++++--- .../ConversationViewController.m | 32 +++++++++++ .../ConversationView/ConversationViewItem.h | 2 + 5 files changed, 72 insertions(+), 42 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.h b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.h index 10799dc95..d5866b206 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.h +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.h @@ -25,8 +25,7 @@ extern const CGFloat kOWSMessageCellCornerRadius_Small; @property (nonatomic, nullable) UIColor *bubbleColor; -@property (nonatomic) BOOL useSmallCorners_Top; -@property (nonatomic) BOOL useSmallCorners_Bottom; +@property (nonatomic) UIRectCorner sharpCorners; - (UIBezierPath *)maskPath; diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m index 2a36a6a78..0b4c94bd2 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m @@ -101,16 +101,9 @@ const CGFloat kOWSMessageCellCornerRadius_Small = 2; [CATransaction commit]; } -- (void)setUseSmallCorners_Top:(BOOL)useSmallCorners_Top +- (void)setSharpCorners:(UIRectCorner)sharpCorners { - _useSmallCorners_Top = useSmallCorners_Top; - - [self updateLayers]; -} - -- (void)setUseSmallCorners_Bottom:(BOOL)useSmallCorners_Bottom -{ - _useSmallCorners_Bottom = useSmallCorners_Bottom; + _sharpCorners = sharpCorners; [self updateLayers]; } @@ -139,14 +132,10 @@ const CGFloat kOWSMessageCellCornerRadius_Small = 2; - (UIBezierPath *)maskPath { - return [self.class maskPathForSize:self.bounds.size - useSmallCorners_Top:self.useSmallCorners_Top - useSmallCorners_Bottom:self.useSmallCorners_Bottom]; + return [self.class maskPathForSize:self.bounds.size sharpCorners:self.sharpCorners]; } -+ (UIBezierPath *)maskPathForSize:(CGSize)size - useSmallCorners_Top:(BOOL)useSmallCorners_Top - useSmallCorners_Bottom:(BOOL)useSmallCorners_Bottom ++ (UIBezierPath *)maskPathForSize:(CGSize)size sharpCorners:(UIRectCorner)sharpCorners { CGRect bounds = CGRectZero; bounds.size = size; @@ -157,41 +146,48 @@ const CGFloat kOWSMessageCellCornerRadius_Small = 2; CGFloat bubbleRight = size.width; CGFloat bubbleTop = 0.f; CGFloat bubbleBottom = size.height; - CGFloat topRounding = (useSmallCorners_Top ? kOWSMessageCellCornerRadius_Small : kOWSMessageCellCornerRadius_Large); - CGFloat bottomRounding - = (useSmallCorners_Bottom ? kOWSMessageCellCornerRadius_Small : kOWSMessageCellCornerRadius_Large); + + CGFloat topLeftRounding + = (sharpCorners & UIRectCornerTopLeft) ? kOWSMessageCellCornerRadius_Small : kOWSMessageCellCornerRadius_Large; + CGFloat topRightRounding + = (sharpCorners & UIRectCornerTopRight) ? kOWSMessageCellCornerRadius_Small : kOWSMessageCellCornerRadius_Large; + CGFloat bottomRightRounding = (sharpCorners & UIRectCornerBottomRight) ? kOWSMessageCellCornerRadius_Small + : kOWSMessageCellCornerRadius_Large; + CGFloat bottomLeftRounding = (sharpCorners & UIRectCornerBottomLeft) ? kOWSMessageCellCornerRadius_Small + : kOWSMessageCellCornerRadius_Large; const CGFloat topAngle = 3.0f * M_PI_2; const CGFloat rightAngle = 0.0f; const CGFloat bottomAngle = M_PI_2; const CGFloat leftAngle = M_PI; - [bezierPath moveToPoint:CGPointMake(bubbleLeft + topRounding, bubbleTop)]; + // starting just to the right of the top left corner and working clockwise + [bezierPath moveToPoint:CGPointMake(bubbleLeft + topLeftRounding, bubbleTop)]; // top right corner - [bezierPath addArcWithCenter:CGPointMake(bubbleRight - topRounding, bubbleTop + topRounding) - radius:topRounding + [bezierPath addArcWithCenter:CGPointMake(bubbleRight - topRightRounding, bubbleTop + topRightRounding) + radius:topRightRounding startAngle:topAngle endAngle:rightAngle clockwise:true]; // bottom right corner - [bezierPath addArcWithCenter:CGPointMake(bubbleRight - bottomRounding, bubbleBottom - bottomRounding) - radius:bottomRounding + [bezierPath addArcWithCenter:CGPointMake(bubbleRight - bottomRightRounding, bubbleBottom - bottomRightRounding) + radius:bottomRightRounding startAngle:rightAngle endAngle:bottomAngle clockwise:true]; // bottom left corner - [bezierPath addArcWithCenter:CGPointMake(bubbleLeft + bottomRounding, bubbleBottom - bottomRounding) - radius:bottomRounding + [bezierPath addArcWithCenter:CGPointMake(bubbleLeft + bottomLeftRounding, bubbleBottom - bottomLeftRounding) + radius:bottomLeftRounding startAngle:bottomAngle endAngle:leftAngle clockwise:true]; // top left corner - [bezierPath addArcWithCenter:CGPointMake(bubbleLeft + topRounding, bubbleTop + topRounding) - radius:topRounding + [bezierPath addArcWithCenter:CGPointMake(bubbleLeft + topLeftRounding, bubbleTop + topLeftRounding) + radius:topLeftRounding startAngle:leftAngle endAngle:topAngle clockwise:true]; @@ -228,11 +224,7 @@ const CGFloat kOWSMessageCellCornerRadius_Small = 2; - (CGFloat)minWidth { - if (self.useSmallCorners_Top && self.useSmallCorners_Bottom) { - return (kOWSMessageCellCornerRadius_Small * 2); - } else { - return (kOWSMessageCellCornerRadius_Large * 2); - } + return (kOWSMessageCellCornerRadius_Large * 2); } @end diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m index d8d04e8c3..1959609b1 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m @@ -268,7 +268,6 @@ NS_ASSUME_NONNULL_BEGIN [self.stackView addArrangedSubview:spacerView]; } - BOOL isOutgoing = [self.viewItem.interaction isKindOfClass:TSOutgoingMessage.class]; DisplayableText *_Nullable displayableQuotedText = (self.viewItem.hasQuotedText ? self.viewItem.displayableQuotedText : nil); @@ -276,7 +275,7 @@ NS_ASSUME_NONNULL_BEGIN [OWSQuotedMessageView quotedMessageViewForConversation:self.viewItem.quotedReply displayableQuotedText:displayableQuotedText conversationStyle:self.conversationStyle - isOutgoing:isOutgoing + isOutgoing:self.isOutgoing sharesTopBorderWithMessageBubble:!self.shouldShowSenderName]; quotedMessageView.delegate = self; @@ -561,10 +560,17 @@ NS_ASSUME_NONNULL_BEGIN - (void)configureBubbleRounding { - self.bubbleView.useSmallCorners_Top - = (self.hasBodyMediaWithThumbnail && !self.shouldShowSenderName && !self.isQuotedReply); - self.bubbleView.useSmallCorners_Bottom - = (self.hasBodyMediaWithThumbnail && !self.hasBodyText && !self.hasBottomFooter); + UIRectCorner sharpCorners = 0; + + if (!self.viewItem.isFirstInCluster) { + sharpCorners = sharpCorners | (self.isIncoming ? UIRectCornerTopLeft : UIRectCornerTopRight); + } + + if (!self.viewItem.isLastInCluster) { + sharpCorners = sharpCorners | (self.isIncoming ? UIRectCornerBottomLeft : UIRectCornerBottomRight); + } + + self.bubbleView.sharpCorners = sharpCorners; } - (void)updateBubbleColor @@ -1199,7 +1205,6 @@ NS_ASSUME_NONNULL_BEGIN return nil; } - BOOL isOutgoing = [self.viewItem.interaction isKindOfClass:TSOutgoingMessage.class]; DisplayableText *_Nullable displayableQuotedText = (self.viewItem.hasQuotedText ? self.viewItem.displayableQuotedText : nil); @@ -1207,7 +1212,7 @@ NS_ASSUME_NONNULL_BEGIN [OWSQuotedMessageView quotedMessageViewForConversation:self.viewItem.quotedReply displayableQuotedText:displayableQuotedText conversationStyle:self.conversationStyle - isOutgoing:isOutgoing + isOutgoing:self.isOutgoing sharesTopBorderWithMessageBubble:NO]; CGSize result = [quotedMessageView sizeForMaxWidth:self.conversationStyle.maxMessageWidth]; return [NSValue valueWithCGSize:CGSizeCeil(result)]; diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 7d637f376..8b985dc8e 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -4865,6 +4865,8 @@ typedef enum : NSUInteger { ConversationViewItem *_Nullable nextViewItem = (i + 1 < viewItems.count ? viewItems[i + 1] : nil); BOOL shouldShowSenderAvatar = NO; BOOL shouldHideFooter = NO; + BOOL isFirstInCluster = YES; + BOOL isLastInCluster = YES; NSAttributedString *_Nullable senderName = nil; OWSInteractionType interactionType = viewItem.interaction.interactionType; @@ -4888,6 +4890,18 @@ typedef enum : NSUInteger { && receiptStatus == nextReceiptStatus && outgoingMessage.messageState != TSOutgoingMessageStateFailed && !nextViewItem.shouldShowDate); } + + // clustering + if (previousViewItem == nil) { + isFirstInCluster = YES; + } else { + isFirstInCluster = previousViewItem.interaction.interactionType != OWSInteractionType_OutgoingMessage; + } + if (nextViewItem == nil) { + isLastInCluster = YES; + } else { + isLastInCluster = nextViewItem.interaction.interactionType != OWSInteractionType_OutgoingMessage; + } } else if (interactionType == OWSInteractionType_IncomingMessage) { TSIncomingMessage *incomingMessage = (TSIncomingMessage *)viewItem.interaction; @@ -4909,6 +4923,22 @@ typedef enum : NSUInteger { [NSObject isNullableObject:nextIncomingSenderId equalTo:incomingSenderId]; } + // clustering + if (previousViewItem == nil + || previousViewItem.interaction.interactionType != OWSInteractionType_IncomingMessage) { + isFirstInCluster = YES; + } else { + TSIncomingMessage *previousIncomingMessage = (TSIncomingMessage *)previousViewItem.interaction; + isFirstInCluster = ![incomingSenderId isEqual:previousIncomingMessage.authorId]; + } + + if (nextViewItem == nil || nextViewItem.interaction.interactionType != OWSInteractionType_IncomingMessage) { + isLastInCluster = YES; + } else { + TSIncomingMessage *nextIncomingMessage = (TSIncomingMessage *)nextViewItem.interaction; + isLastInCluster = ![incomingSenderId isEqual:nextIncomingMessage.authorId]; + } + if (viewItem.isGroupThread) { // Show the sender name for incoming group messages unless // the previous message has the same sender name and @@ -4944,6 +4974,8 @@ typedef enum : NSUInteger { } } + viewItem.isFirstInCluster = isFirstInCluster; + viewItem.isLastInCluster = isLastInCluster; viewItem.shouldShowSenderAvatar = shouldShowSenderAvatar; viewItem.shouldHideFooter = shouldHideFooter; viewItem.senderName = senderName; diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h index 20051dd00..b6cb4cd64 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h @@ -58,6 +58,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType); @property (nonatomic) BOOL shouldShowSenderAvatar; @property (nonatomic, nullable) NSAttributedString *senderName; @property (nonatomic) BOOL shouldHideFooter; +@property (nonatomic) BOOL isFirstInCluster; +@property (nonatomic) BOOL isLastInCluster; @property (nonatomic, readonly) ConversationStyle *conversationStyle; From 287da9c30aa76c71e4cacabf4871d0363bfa5dad Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 6 Jul 2018 14:45:00 -0600 Subject: [PATCH 2/7] fixup quote corners // FREEBIE --- .../ConversationView/Cells/OWSBubbleView.m | 2 +- .../Cells/OWSMessageBubbleView.m | 22 ++++- .../Cells/OWSQuotedMessageView.h | 2 +- .../Cells/OWSQuotedMessageView.m | 99 ++++++++++++++----- 4 files changed, 92 insertions(+), 33 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m index 0b4c94bd2..917c9583f 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m @@ -8,7 +8,7 @@ NS_ASSUME_NONNULL_BEGIN const CGFloat kOWSMessageCellCornerRadius_Large = 18; -const CGFloat kOWSMessageCellCornerRadius_Small = 2; +const CGFloat kOWSMessageCellCornerRadius_Small = 4; @interface OWSBubbleView () diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m index 1959609b1..36d5b851f 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m @@ -276,7 +276,7 @@ NS_ASSUME_NONNULL_BEGIN displayableQuotedText:displayableQuotedText conversationStyle:self.conversationStyle isOutgoing:self.isOutgoing - sharesTopBorderWithMessageBubble:!self.shouldShowSenderName]; + sharpCorners:self.sharpCornersForQuotedMessage]; quotedMessageView.delegate = self; self.quotedMessageView = quotedMessageView; @@ -558,7 +558,7 @@ NS_ASSUME_NONNULL_BEGIN return 12.f; } -- (void)configureBubbleRounding +- (UIRectCorner)sharpCorners { UIRectCorner sharpCorners = 0; @@ -570,7 +570,21 @@ NS_ASSUME_NONNULL_BEGIN sharpCorners = sharpCorners | (self.isIncoming ? UIRectCornerBottomLeft : UIRectCornerBottomRight); } - self.bubbleView.sharpCorners = sharpCorners; + return sharpCorners; +} + +- (UIRectCorner)sharpCornersForQuotedMessage +{ + if (self.viewItem.senderName) { + return UIRectCornerAllCorners; + } else { + return self.sharpCorners & UIRectCornerAllCorners; + } +} + +- (void)configureBubbleRounding +{ + self.bubbleView.sharpCorners = self.sharpCorners; } - (void)updateBubbleColor @@ -1213,7 +1227,7 @@ NS_ASSUME_NONNULL_BEGIN displayableQuotedText:displayableQuotedText conversationStyle:self.conversationStyle isOutgoing:self.isOutgoing - sharesTopBorderWithMessageBubble:NO]; + sharpCorners:self.sharpCornersForQuotedMessage]; CGSize result = [quotedMessageView sizeForMaxWidth:self.conversationStyle.maxMessageWidth]; return [NSValue valueWithCGSize:CGSizeCeil(result)]; } diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.h b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.h index 83fb17150..6f22d4519 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.h +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.h @@ -35,7 +35,7 @@ NS_ASSUME_NONNULL_BEGIN displayableQuotedText:(nullable DisplayableText *)displayableQuotedText conversationStyle:(ConversationStyle *)conversationStyle isOutgoing:(BOOL)isOutgoing - sharesTopBorderWithMessageBubble:(BOOL)sharesTopBorderWithMessageBubble; + sharpCorners:(UIRectCorner)sharpCorners; // Factory method for "message compose" views. + (OWSQuotedMessageView *)quotedMessageViewForPreview:(OWSQuotedReplyModel *)quotedMessage diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m index 304a26534..142656e16 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m @@ -25,7 +25,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) BOOL isForPreview; @property (nonatomic, readonly) BOOL isOutgoing; -@property (nonatomic, readonly) BOOL sharesTopBorderWithMessageBubble; +@property (nonatomic, readonly) UIRectCorner sharpCorners; @property (nonatomic, readonly) UILabel *quotedAuthorLabel; @property (nonatomic, readonly) UILabel *quotedTextLabel; @@ -40,7 +40,7 @@ NS_ASSUME_NONNULL_BEGIN displayableQuotedText:(nullable DisplayableText *)displayableQuotedText conversationStyle:(ConversationStyle *)conversationStyle isOutgoing:(BOOL)isOutgoing - sharesTopBorderWithMessageBubble:(BOOL)sharesTopBorderWithMessageBubble + sharpCorners:(UIRectCorner)sharpCorners { OWSAssert(quotedMessage); @@ -49,7 +49,7 @@ NS_ASSUME_NONNULL_BEGIN conversationStyle:conversationStyle isForPreview:NO isOutgoing:isOutgoing - sharesTopBorderWithMessageBubble:sharesTopBorderWithMessageBubble]; + sharpCorners:sharpCorners]; } + (OWSQuotedMessageView *)quotedMessageViewForPreview:(OWSQuotedReplyModel *)quotedMessage @@ -67,7 +67,7 @@ NS_ASSUME_NONNULL_BEGIN conversationStyle:conversationStyle isForPreview:YES isOutgoing:YES - sharesTopBorderWithMessageBubble:NO]; + sharpCorners:UIRectCornerAllCorners]; [instance createContents]; return instance; } @@ -77,7 +77,7 @@ NS_ASSUME_NONNULL_BEGIN conversationStyle:(ConversationStyle *)conversationStyle isForPreview:(BOOL)isForPreview isOutgoing:(BOOL)isOutgoing - sharesTopBorderWithMessageBubble:(BOOL)sharesTopBorderWithMessageBubble + sharpCorners:(UIRectCorner)sharpCorners { self = [super init]; @@ -92,7 +92,7 @@ NS_ASSUME_NONNULL_BEGIN _isForPreview = isForPreview; _conversationStyle = conversationStyle; _isOutgoing = isOutgoing; - _sharesTopBorderWithMessageBubble = sharesTopBorderWithMessageBubble; + _sharpCorners = sharpCorners; _quotedAuthorLabel = [UILabel new]; _quotedTextLabel = [UILabel new]; @@ -151,7 +151,8 @@ NS_ASSUME_NONNULL_BEGIN self.clipsToBounds = YES; CAShapeLayer *maskLayer = [CAShapeLayer new]; - BOOL sharesTopBorderWithMessageBubble = self.sharesTopBorderWithMessageBubble; + UIRectCorner sharpCorners = self.sharpCorners; + OWSLayerView *innerBubbleView = [[OWSLayerView alloc] initWithFrame:CGRectZero layoutCallback:^(UIView *layerView) { @@ -159,26 +160,70 @@ NS_ASSUME_NONNULL_BEGIN UIBezierPath *bezierPath = [UIBezierPath new]; - CGFloat bubbleLeft = 0.f; - CGFloat bubbleRight = layerFrame.size.width; - CGFloat bubbleTop = 0.f; - CGFloat bubbleBottom = layerFrame.size.height; - CGFloat bubbleTopRounding = (sharesTopBorderWithMessageBubble ? 10.f : 4.f); - CGFloat bubbleBottomRounding = 4.f; - - [bezierPath moveToPoint:CGPointMake(bubbleLeft + bubbleTopRounding, bubbleTop)]; - [bezierPath addLineToPoint:CGPointMake(bubbleRight - bubbleTopRounding, bubbleTop)]; - [bezierPath addQuadCurveToPoint:CGPointMake(bubbleRight, bubbleTop + bubbleTopRounding) - controlPoint:CGPointMake(bubbleRight, bubbleTop)]; - [bezierPath addLineToPoint:CGPointMake(bubbleRight, bubbleBottom - bubbleBottomRounding)]; - [bezierPath addQuadCurveToPoint:CGPointMake(bubbleRight - bubbleBottomRounding, bubbleBottom) - controlPoint:CGPointMake(bubbleRight, bubbleBottom)]; - [bezierPath addLineToPoint:CGPointMake(bubbleLeft + bubbleBottomRounding, bubbleBottom)]; - [bezierPath addQuadCurveToPoint:CGPointMake(bubbleLeft, bubbleBottom - bubbleBottomRounding) - controlPoint:CGPointMake(bubbleLeft, bubbleBottom)]; - [bezierPath addLineToPoint:CGPointMake(bubbleLeft, bubbleTop + bubbleTopRounding)]; - [bezierPath addQuadCurveToPoint:CGPointMake(bubbleLeft + bubbleTopRounding, bubbleTop) - controlPoint:CGPointMake(bubbleLeft, bubbleTop)]; + const CGFloat bubbleLeft = 0.f; + const CGFloat bubbleRight = layerFrame.size.width; + const CGFloat bubbleTop = 0.f; + const CGFloat bubbleBottom = layerFrame.size.height; + + const CGFloat sharpCornerRadius = 4; + const CGFloat wideCornerRadius = 10; + + const CGFloat topLeftRounding = (sharpCorners & UIRectCornerTopLeft) ? sharpCornerRadius : wideCornerRadius; + const CGFloat topRightRounding + = (sharpCorners & UIRectCornerTopRight) ? sharpCornerRadius : wideCornerRadius; + + // bottom corners are always sharp + const CGFloat bottomRightRounding = sharpCornerRadius; + const CGFloat bottomLeftRounding = sharpCornerRadius; + + const CGFloat topAngle = 3.0f * M_PI / 2.0f; + const CGFloat rightAngle = 0.0f; + const CGFloat bottomAngle = M_PI / 2.0f; + const CGFloat leftAngle = M_PI; + + // starting just to the right of the top left corner and working clockwise + [bezierPath moveToPoint:CGPointMake(bubbleLeft + topLeftRounding, bubbleTop)]; + + // top line + [bezierPath addLineToPoint:CGPointMake(bubbleRight - topRightRounding, bubbleTop)]; + + // top right corner + [bezierPath addArcWithCenter:CGPointMake(bubbleRight - topRightRounding, bubbleTop + topRightRounding) + radius:topRightRounding + startAngle:topAngle + endAngle:rightAngle + clockwise:true]; + + // right line + [bezierPath addLineToPoint:CGPointMake(bubbleRight, bubbleBottom - bottomRightRounding)]; + + // bottom right corner + [bezierPath + addArcWithCenter:CGPointMake(bubbleRight - bottomRightRounding, bubbleBottom - bottomRightRounding) + radius:bottomRightRounding + startAngle:rightAngle + endAngle:bottomAngle + clockwise:true]; + + // bottom line + [bezierPath addLineToPoint:CGPointMake(bubbleLeft + bottomLeftRounding, bubbleBottom)]; + + // bottom left corner + [bezierPath addArcWithCenter:CGPointMake(bubbleLeft + bottomLeftRounding, bubbleBottom - bottomLeftRounding) + radius:bottomLeftRounding + startAngle:bottomAngle + endAngle:leftAngle + clockwise:true]; + + // left line + [bezierPath addLineToPoint:CGPointMake(bubbleLeft, bubbleTop + topLeftRounding)]; + + // top left corner + [bezierPath addArcWithCenter:CGPointMake(bubbleLeft + topLeftRounding, bubbleTop + topLeftRounding) + radius:topLeftRounding + startAngle:leftAngle + endAngle:topAngle + clockwise:true]; maskLayer.path = bezierPath.CGPath; }]; From 900abf2367f7137352765b38eb9fe3f6892e5f91 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 6 Jul 2018 15:11:01 -0600 Subject: [PATCH 3/7] CR: simplify --- .../Cells/OWSMessageBubbleView.m | 2 +- .../Cells/OWSQuotedMessageView.m | 22 +++++-------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m index 36d5b851f..033bc4aef 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m @@ -578,7 +578,7 @@ NS_ASSUME_NONNULL_BEGIN if (self.viewItem.senderName) { return UIRectCornerAllCorners; } else { - return self.sharpCorners & UIRectCornerAllCorners; + return self.sharpCorners | UIRectCornerBottomLeft | UIRectCornerBottomRight; } } diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m index 142656e16..cbf552d28 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m @@ -172,20 +172,19 @@ NS_ASSUME_NONNULL_BEGIN const CGFloat topRightRounding = (sharpCorners & UIRectCornerTopRight) ? sharpCornerRadius : wideCornerRadius; - // bottom corners are always sharp - const CGFloat bottomRightRounding = sharpCornerRadius; - const CGFloat bottomLeftRounding = sharpCornerRadius; + const CGFloat bottomRightRounding + = (sharpCorners & UIRectCornerBottomRight) ? sharpCornerRadius : wideCornerRadius; + const CGFloat bottomLeftRounding + = (sharpCorners & UIRectCornerBottomLeft) ? sharpCornerRadius : wideCornerRadius; - const CGFloat topAngle = 3.0f * M_PI / 2.0f; + const CGFloat topAngle = 3.0f * M_PI_2; const CGFloat rightAngle = 0.0f; - const CGFloat bottomAngle = M_PI / 2.0f; + const CGFloat bottomAngle = M_PI_2; const CGFloat leftAngle = M_PI; // starting just to the right of the top left corner and working clockwise [bezierPath moveToPoint:CGPointMake(bubbleLeft + topLeftRounding, bubbleTop)]; - // top line - [bezierPath addLineToPoint:CGPointMake(bubbleRight - topRightRounding, bubbleTop)]; // top right corner [bezierPath addArcWithCenter:CGPointMake(bubbleRight - topRightRounding, bubbleTop + topRightRounding) @@ -194,9 +193,6 @@ NS_ASSUME_NONNULL_BEGIN endAngle:rightAngle clockwise:true]; - // right line - [bezierPath addLineToPoint:CGPointMake(bubbleRight, bubbleBottom - bottomRightRounding)]; - // bottom right corner [bezierPath addArcWithCenter:CGPointMake(bubbleRight - bottomRightRounding, bubbleBottom - bottomRightRounding) @@ -205,9 +201,6 @@ NS_ASSUME_NONNULL_BEGIN endAngle:bottomAngle clockwise:true]; - // bottom line - [bezierPath addLineToPoint:CGPointMake(bubbleLeft + bottomLeftRounding, bubbleBottom)]; - // bottom left corner [bezierPath addArcWithCenter:CGPointMake(bubbleLeft + bottomLeftRounding, bubbleBottom - bottomLeftRounding) radius:bottomLeftRounding @@ -215,9 +208,6 @@ NS_ASSUME_NONNULL_BEGIN endAngle:leftAngle clockwise:true]; - // left line - [bezierPath addLineToPoint:CGPointMake(bubbleLeft, bubbleTop + topLeftRounding)]; - // top left corner [bezierPath addArcWithCenter:CGPointMake(bubbleLeft + topLeftRounding, bubbleTop + topLeftRounding) radius:topLeftRounding From 42da082b0117522e58bf76a68495431d3a742ecf Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 6 Jul 2018 15:25:15 -0600 Subject: [PATCH 4/7] extract rounded bezier builder --- .../Cells/OWSBubbleShapeView.h | 8 +++ .../Cells/OWSBubbleShapeView.m | 54 +++++++++++++++++ .../ConversationView/Cells/OWSBubbleView.m | 58 ++++--------------- .../Cells/OWSQuotedMessageView.m | 55 +++--------------- 4 files changed, 79 insertions(+), 96 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleShapeView.h b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleShapeView.h index f05cc46fe..6f9b879bf 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleShapeView.h +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleShapeView.h @@ -30,6 +30,14 @@ NS_ASSUME_NONNULL_BEGIN + (OWSBubbleShapeView *)bubbleShadowView; + (OWSBubbleShapeView *)bubbleClipView; ++ (UIBezierPath *)roundedBezierRectWithBubbleTop:(CGFloat)bubbleTop + bubbleLeft:(CGFloat)bubbleLeft + bubbleBottom:(CGFloat)bubbleBottom + bubbleRight:(CGFloat)bubbleRight + sharpCornerRadius:(CGFloat)sharpCornerRadius + wideCornerRadius:(CGFloat)wideCornerRadius + sharpCorners:(UIRectCorner)sharpCorners; + @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleShapeView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleShapeView.m index 7d387696d..d6cba0e29 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleShapeView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleShapeView.m @@ -193,6 +193,60 @@ typedef NS_ENUM(NSUInteger, OWSBubbleShapeViewMode) { [CATransaction commit]; } ++ (UIBezierPath *)roundedBezierRectWithBubbleTop:(CGFloat)bubbleTop + bubbleLeft:(CGFloat)bubbleLeft + bubbleBottom:(CGFloat)bubbleBottom + bubbleRight:(CGFloat)bubbleRight + sharpCornerRadius:(CGFloat)sharpCornerRadius + wideCornerRadius:(CGFloat)wideCornerRadius + sharpCorners:(UIRectCorner)sharpCorners +{ + UIBezierPath *bezierPath = [UIBezierPath new]; + + const CGFloat topLeftRounding = (sharpCorners & UIRectCornerTopLeft) ? sharpCornerRadius : wideCornerRadius; + const CGFloat topRightRounding = (sharpCorners & UIRectCornerTopRight) ? sharpCornerRadius : wideCornerRadius; + + const CGFloat bottomRightRounding = (sharpCorners & UIRectCornerBottomRight) ? sharpCornerRadius : wideCornerRadius; + const CGFloat bottomLeftRounding = (sharpCorners & UIRectCornerBottomLeft) ? sharpCornerRadius : wideCornerRadius; + + const CGFloat topAngle = 3.0f * M_PI_2; + const CGFloat rightAngle = 0.0f; + const CGFloat bottomAngle = M_PI_2; + const CGFloat leftAngle = M_PI; + + // starting just to the right of the top left corner and working clockwise + [bezierPath moveToPoint:CGPointMake(bubbleLeft + topLeftRounding, bubbleTop)]; + + // top right corner + [bezierPath addArcWithCenter:CGPointMake(bubbleRight - topRightRounding, bubbleTop + topRightRounding) + radius:topRightRounding + startAngle:topAngle + endAngle:rightAngle + clockwise:true]; + + // bottom right corner + [bezierPath addArcWithCenter:CGPointMake(bubbleRight - bottomRightRounding, bubbleBottom - bottomRightRounding) + radius:bottomRightRounding + startAngle:rightAngle + endAngle:bottomAngle + clockwise:true]; + + // bottom left corner + [bezierPath addArcWithCenter:CGPointMake(bubbleLeft + bottomLeftRounding, bubbleBottom - bottomLeftRounding) + radius:bottomLeftRounding + startAngle:bottomAngle + endAngle:leftAngle + clockwise:true]; + + // top left corner + [bezierPath addArcWithCenter:CGPointMake(bubbleLeft + topLeftRounding, bubbleTop + topLeftRounding) + radius:topLeftRounding + startAngle:leftAngle + endAngle:topAngle + clockwise:true]; + return bezierPath; +} + @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m index 917c9583f..3e74a5acb 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m @@ -3,6 +3,7 @@ // #import "OWSBubbleView.h" +#import "OWSBubbleShapeView.h" #import NS_ASSUME_NONNULL_BEGIN @@ -142,57 +143,18 @@ const CGFloat kOWSMessageCellCornerRadius_Small = 4; UIBezierPath *bezierPath = [UIBezierPath new]; - CGFloat bubbleLeft = 0.f; - CGFloat bubbleRight = size.width; CGFloat bubbleTop = 0.f; + CGFloat bubbleLeft = 0.f; CGFloat bubbleBottom = size.height; + CGFloat bubbleRight = size.width; - CGFloat topLeftRounding - = (sharpCorners & UIRectCornerTopLeft) ? kOWSMessageCellCornerRadius_Small : kOWSMessageCellCornerRadius_Large; - CGFloat topRightRounding - = (sharpCorners & UIRectCornerTopRight) ? kOWSMessageCellCornerRadius_Small : kOWSMessageCellCornerRadius_Large; - CGFloat bottomRightRounding = (sharpCorners & UIRectCornerBottomRight) ? kOWSMessageCellCornerRadius_Small - : kOWSMessageCellCornerRadius_Large; - CGFloat bottomLeftRounding = (sharpCorners & UIRectCornerBottomLeft) ? kOWSMessageCellCornerRadius_Small - : kOWSMessageCellCornerRadius_Large; - - const CGFloat topAngle = 3.0f * M_PI_2; - const CGFloat rightAngle = 0.0f; - const CGFloat bottomAngle = M_PI_2; - const CGFloat leftAngle = M_PI; - - // starting just to the right of the top left corner and working clockwise - [bezierPath moveToPoint:CGPointMake(bubbleLeft + topLeftRounding, bubbleTop)]; - - // top right corner - [bezierPath addArcWithCenter:CGPointMake(bubbleRight - topRightRounding, bubbleTop + topRightRounding) - radius:topRightRounding - startAngle:topAngle - endAngle:rightAngle - clockwise:true]; - - // bottom right corner - [bezierPath addArcWithCenter:CGPointMake(bubbleRight - bottomRightRounding, bubbleBottom - bottomRightRounding) - radius:bottomRightRounding - startAngle:rightAngle - endAngle:bottomAngle - clockwise:true]; - - // bottom left corner - [bezierPath addArcWithCenter:CGPointMake(bubbleLeft + bottomLeftRounding, bubbleBottom - bottomLeftRounding) - radius:bottomLeftRounding - startAngle:bottomAngle - endAngle:leftAngle - clockwise:true]; - - // top left corner - [bezierPath addArcWithCenter:CGPointMake(bubbleLeft + topLeftRounding, bubbleTop + topLeftRounding) - radius:topLeftRounding - startAngle:leftAngle - endAngle:topAngle - clockwise:true]; - - return bezierPath; + return [OWSBubbleShapeView roundedBezierRectWithBubbleTop:bubbleTop + bubbleLeft:bubbleLeft + bubbleBottom:bubbleBottom + bubbleRight:bubbleRight + sharpCornerRadius:kOWSMessageCellCornerRadius_Small + wideCornerRadius:kOWSMessageCellCornerRadius_Large + sharpCorners:sharpCorners]; } #pragma mark - Coordination diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m index cbf552d28..96003d0eb 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m @@ -158,8 +158,6 @@ NS_ASSUME_NONNULL_BEGIN layoutCallback:^(UIView *layerView) { CGRect layerFrame = layerView.bounds; - UIBezierPath *bezierPath = [UIBezierPath new]; - const CGFloat bubbleLeft = 0.f; const CGFloat bubbleRight = layerFrame.size.width; const CGFloat bubbleTop = 0.f; @@ -168,52 +166,13 @@ NS_ASSUME_NONNULL_BEGIN const CGFloat sharpCornerRadius = 4; const CGFloat wideCornerRadius = 10; - const CGFloat topLeftRounding = (sharpCorners & UIRectCornerTopLeft) ? sharpCornerRadius : wideCornerRadius; - const CGFloat topRightRounding - = (sharpCorners & UIRectCornerTopRight) ? sharpCornerRadius : wideCornerRadius; - - const CGFloat bottomRightRounding - = (sharpCorners & UIRectCornerBottomRight) ? sharpCornerRadius : wideCornerRadius; - const CGFloat bottomLeftRounding - = (sharpCorners & UIRectCornerBottomLeft) ? sharpCornerRadius : wideCornerRadius; - - const CGFloat topAngle = 3.0f * M_PI_2; - const CGFloat rightAngle = 0.0f; - const CGFloat bottomAngle = M_PI_2; - const CGFloat leftAngle = M_PI; - - // starting just to the right of the top left corner and working clockwise - [bezierPath moveToPoint:CGPointMake(bubbleLeft + topLeftRounding, bubbleTop)]; - - - // top right corner - [bezierPath addArcWithCenter:CGPointMake(bubbleRight - topRightRounding, bubbleTop + topRightRounding) - radius:topRightRounding - startAngle:topAngle - endAngle:rightAngle - clockwise:true]; - - // bottom right corner - [bezierPath - addArcWithCenter:CGPointMake(bubbleRight - bottomRightRounding, bubbleBottom - bottomRightRounding) - radius:bottomRightRounding - startAngle:rightAngle - endAngle:bottomAngle - clockwise:true]; - - // bottom left corner - [bezierPath addArcWithCenter:CGPointMake(bubbleLeft + bottomLeftRounding, bubbleBottom - bottomLeftRounding) - radius:bottomLeftRounding - startAngle:bottomAngle - endAngle:leftAngle - clockwise:true]; - - // top left corner - [bezierPath addArcWithCenter:CGPointMake(bubbleLeft + topLeftRounding, bubbleTop + topLeftRounding) - radius:topLeftRounding - startAngle:leftAngle - endAngle:topAngle - clockwise:true]; + UIBezierPath *bezierPath = [OWSBubbleShapeView roundedBezierRectWithBubbleTop:bubbleTop + bubbleLeft:bubbleLeft + bubbleBottom:bubbleBottom + bubbleRight:bubbleRight + sharpCornerRadius:sharpCornerRadius + wideCornerRadius:wideCornerRadius + sharpCorners:sharpCorners]; maskLayer.path = bezierPath.CGPath; }]; From 0ecc97d5ff74436c2b86ff0af9ccb68e03a076c8 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 6 Jul 2018 15:28:53 -0600 Subject: [PATCH 5/7] date header should break cluster --- .../ConversationView/ConversationViewController.m | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 8b985dc8e..a4ddb7b0c 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -4897,8 +4897,11 @@ typedef enum : NSUInteger { } else { isFirstInCluster = previousViewItem.interaction.interactionType != OWSInteractionType_OutgoingMessage; } + if (nextViewItem == nil) { isLastInCluster = YES; + } else if (nextViewItem.shouldShowDate) { + isLastInCluster = YES; } else { isLastInCluster = nextViewItem.interaction.interactionType != OWSInteractionType_OutgoingMessage; } @@ -4924,15 +4927,20 @@ typedef enum : NSUInteger { } // clustering - if (previousViewItem == nil - || previousViewItem.interaction.interactionType != OWSInteractionType_IncomingMessage) { + if (previousViewItem == nil) { + isFirstInCluster = YES; + } else if (previousViewItem.interaction.interactionType != OWSInteractionType_IncomingMessage) { isFirstInCluster = YES; } else { TSIncomingMessage *previousIncomingMessage = (TSIncomingMessage *)previousViewItem.interaction; isFirstInCluster = ![incomingSenderId isEqual:previousIncomingMessage.authorId]; } - if (nextViewItem == nil || nextViewItem.interaction.interactionType != OWSInteractionType_IncomingMessage) { + if (nextViewItem == nil) { + isLastInCluster = YES; + } else if (nextViewItem.interaction.interactionType != OWSInteractionType_IncomingMessage) { + isLastInCluster = YES; + } else if (nextViewItem.shouldShowDate) { isLastInCluster = YES; } else { TSIncomingMessage *nextIncomingMessage = (TSIncomingMessage *)nextViewItem.interaction; From fa89a84daf9e580df145358e4328aeecee866ac1 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 6 Jul 2018 16:13:25 -0600 Subject: [PATCH 6/7] CR: move builder to BubbleView --- .../Cells/OWSBubbleShapeView.h | 8 --- .../Cells/OWSBubbleShapeView.m | 54 --------------- .../ConversationView/Cells/OWSBubbleView.h | 8 +++ .../ConversationView/Cells/OWSBubbleView.m | 69 ++++++++++++++++--- .../Cells/OWSQuotedMessageView.m | 16 ++--- 5 files changed, 77 insertions(+), 78 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleShapeView.h b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleShapeView.h index 6f9b879bf..f05cc46fe 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleShapeView.h +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleShapeView.h @@ -30,14 +30,6 @@ NS_ASSUME_NONNULL_BEGIN + (OWSBubbleShapeView *)bubbleShadowView; + (OWSBubbleShapeView *)bubbleClipView; -+ (UIBezierPath *)roundedBezierRectWithBubbleTop:(CGFloat)bubbleTop - bubbleLeft:(CGFloat)bubbleLeft - bubbleBottom:(CGFloat)bubbleBottom - bubbleRight:(CGFloat)bubbleRight - sharpCornerRadius:(CGFloat)sharpCornerRadius - wideCornerRadius:(CGFloat)wideCornerRadius - sharpCorners:(UIRectCorner)sharpCorners; - @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleShapeView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleShapeView.m index d6cba0e29..7d387696d 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleShapeView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleShapeView.m @@ -193,60 +193,6 @@ typedef NS_ENUM(NSUInteger, OWSBubbleShapeViewMode) { [CATransaction commit]; } -+ (UIBezierPath *)roundedBezierRectWithBubbleTop:(CGFloat)bubbleTop - bubbleLeft:(CGFloat)bubbleLeft - bubbleBottom:(CGFloat)bubbleBottom - bubbleRight:(CGFloat)bubbleRight - sharpCornerRadius:(CGFloat)sharpCornerRadius - wideCornerRadius:(CGFloat)wideCornerRadius - sharpCorners:(UIRectCorner)sharpCorners -{ - UIBezierPath *bezierPath = [UIBezierPath new]; - - const CGFloat topLeftRounding = (sharpCorners & UIRectCornerTopLeft) ? sharpCornerRadius : wideCornerRadius; - const CGFloat topRightRounding = (sharpCorners & UIRectCornerTopRight) ? sharpCornerRadius : wideCornerRadius; - - const CGFloat bottomRightRounding = (sharpCorners & UIRectCornerBottomRight) ? sharpCornerRadius : wideCornerRadius; - const CGFloat bottomLeftRounding = (sharpCorners & UIRectCornerBottomLeft) ? sharpCornerRadius : wideCornerRadius; - - const CGFloat topAngle = 3.0f * M_PI_2; - const CGFloat rightAngle = 0.0f; - const CGFloat bottomAngle = M_PI_2; - const CGFloat leftAngle = M_PI; - - // starting just to the right of the top left corner and working clockwise - [bezierPath moveToPoint:CGPointMake(bubbleLeft + topLeftRounding, bubbleTop)]; - - // top right corner - [bezierPath addArcWithCenter:CGPointMake(bubbleRight - topRightRounding, bubbleTop + topRightRounding) - radius:topRightRounding - startAngle:topAngle - endAngle:rightAngle - clockwise:true]; - - // bottom right corner - [bezierPath addArcWithCenter:CGPointMake(bubbleRight - bottomRightRounding, bubbleBottom - bottomRightRounding) - radius:bottomRightRounding - startAngle:rightAngle - endAngle:bottomAngle - clockwise:true]; - - // bottom left corner - [bezierPath addArcWithCenter:CGPointMake(bubbleLeft + bottomLeftRounding, bubbleBottom - bottomLeftRounding) - radius:bottomLeftRounding - startAngle:bottomAngle - endAngle:leftAngle - clockwise:true]; - - // top left corner - [bezierPath addArcWithCenter:CGPointMake(bubbleLeft + topLeftRounding, bubbleTop + topLeftRounding) - radius:topLeftRounding - startAngle:leftAngle - endAngle:topAngle - clockwise:true]; - return bezierPath; -} - @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.h b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.h index d5866b206..bd109daf3 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.h +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.h @@ -23,6 +23,14 @@ extern const CGFloat kOWSMessageCellCornerRadius_Small; @interface OWSBubbleView : UIView ++ (UIBezierPath *)roundedBezierRectWithBubbleTop:(CGFloat)bubbleTop + bubbleLeft:(CGFloat)bubbleLeft + bubbleBottom:(CGFloat)bubbleBottom + bubbleRight:(CGFloat)bubbleRight + sharpCornerRadius:(CGFloat)sharpCornerRadius + wideCornerRadius:(CGFloat)wideCornerRadius + sharpCorners:(UIRectCorner)sharpCorners; + @property (nonatomic, nullable) UIColor *bubbleColor; @property (nonatomic) UIRectCorner sharpCorners; diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m index 3e74a5acb..831ffb4b3 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 "OWSBubbleShapeView.h" #import NS_ASSUME_NONNULL_BEGIN @@ -148,13 +147,67 @@ const CGFloat kOWSMessageCellCornerRadius_Small = 4; CGFloat bubbleBottom = size.height; CGFloat bubbleRight = size.width; - return [OWSBubbleShapeView roundedBezierRectWithBubbleTop:bubbleTop - bubbleLeft:bubbleLeft - bubbleBottom:bubbleBottom - bubbleRight:bubbleRight - sharpCornerRadius:kOWSMessageCellCornerRadius_Small - wideCornerRadius:kOWSMessageCellCornerRadius_Large - sharpCorners:sharpCorners]; + return [OWSBubbleView roundedBezierRectWithBubbleTop:bubbleTop + bubbleLeft:bubbleLeft + bubbleBottom:bubbleBottom + bubbleRight:bubbleRight + sharpCornerRadius:kOWSMessageCellCornerRadius_Small + wideCornerRadius:kOWSMessageCellCornerRadius_Large + sharpCorners:sharpCorners]; +} + ++ (UIBezierPath *)roundedBezierRectWithBubbleTop:(CGFloat)bubbleTop + bubbleLeft:(CGFloat)bubbleLeft + bubbleBottom:(CGFloat)bubbleBottom + bubbleRight:(CGFloat)bubbleRight + sharpCornerRadius:(CGFloat)sharpCornerRadius + wideCornerRadius:(CGFloat)wideCornerRadius + sharpCorners:(UIRectCorner)sharpCorners +{ + UIBezierPath *bezierPath = [UIBezierPath new]; + + const CGFloat topLeftRounding = (sharpCorners & UIRectCornerTopLeft) ? sharpCornerRadius : wideCornerRadius; + const CGFloat topRightRounding = (sharpCorners & UIRectCornerTopRight) ? sharpCornerRadius : wideCornerRadius; + + const CGFloat bottomRightRounding = (sharpCorners & UIRectCornerBottomRight) ? sharpCornerRadius : wideCornerRadius; + const CGFloat bottomLeftRounding = (sharpCorners & UIRectCornerBottomLeft) ? sharpCornerRadius : wideCornerRadius; + + const CGFloat topAngle = 3.0f * M_PI_2; + const CGFloat rightAngle = 0.0f; + const CGFloat bottomAngle = M_PI_2; + const CGFloat leftAngle = M_PI; + + // starting just to the right of the top left corner and working clockwise + [bezierPath moveToPoint:CGPointMake(bubbleLeft + topLeftRounding, bubbleTop)]; + + // top right corner + [bezierPath addArcWithCenter:CGPointMake(bubbleRight - topRightRounding, bubbleTop + topRightRounding) + radius:topRightRounding + startAngle:topAngle + endAngle:rightAngle + clockwise:true]; + + // bottom right corner + [bezierPath addArcWithCenter:CGPointMake(bubbleRight - bottomRightRounding, bubbleBottom - bottomRightRounding) + radius:bottomRightRounding + startAngle:rightAngle + endAngle:bottomAngle + clockwise:true]; + + // bottom left corner + [bezierPath addArcWithCenter:CGPointMake(bubbleLeft + bottomLeftRounding, bubbleBottom - bottomLeftRounding) + radius:bottomLeftRounding + startAngle:bottomAngle + endAngle:leftAngle + clockwise:true]; + + // top left corner + [bezierPath addArcWithCenter:CGPointMake(bubbleLeft + topLeftRounding, bubbleTop + topLeftRounding) + radius:topLeftRounding + startAngle:leftAngle + endAngle:topAngle + clockwise:true]; + return bezierPath; } #pragma mark - Coordination diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m index 96003d0eb..3451e7d99 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m @@ -5,7 +5,7 @@ #import "OWSQuotedMessageView.h" #import "ConversationViewItem.h" #import "Environment.h" -#import "OWSBubbleShapeView.h" +#import "OWSBubbleView.h" #import "Signal-Swift.h" #import #import @@ -166,13 +166,13 @@ NS_ASSUME_NONNULL_BEGIN const CGFloat sharpCornerRadius = 4; const CGFloat wideCornerRadius = 10; - UIBezierPath *bezierPath = [OWSBubbleShapeView roundedBezierRectWithBubbleTop:bubbleTop - bubbleLeft:bubbleLeft - bubbleBottom:bubbleBottom - bubbleRight:bubbleRight - sharpCornerRadius:sharpCornerRadius - wideCornerRadius:wideCornerRadius - sharpCorners:sharpCorners]; + UIBezierPath *bezierPath = [OWSBubbleView roundedBezierRectWithBubbleTop:bubbleTop + bubbleLeft:bubbleLeft + bubbleBottom:bubbleBottom + bubbleRight:bubbleRight + sharpCornerRadius:sharpCornerRadius + wideCornerRadius:wideCornerRadius + sharpCorners:sharpCorners]; maskLayer.path = bezierPath.CGPath; }]; From 37c4a802e41270a968d310f476d2ec49dedf0393 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 6 Jul 2018 16:32:46 -0600 Subject: [PATCH 7/7] sharp corners respect RTL --- .../ConversationView/Cells/OWSBubbleView.h | 12 ++++- .../ConversationView/Cells/OWSBubbleView.m | 49 +++++++++++++++---- .../Cells/OWSMessageBubbleView.m | 16 +++--- .../Cells/OWSQuotedMessageView.h | 4 +- .../Cells/OWSQuotedMessageView.m | 21 ++++---- 5 files changed, 73 insertions(+), 29 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.h b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.h index bd109daf3..fdbc217fa 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.h +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.h @@ -9,6 +9,14 @@ NS_ASSUME_NONNULL_BEGIN extern const CGFloat kOWSMessageCellCornerRadius_Large; extern const CGFloat kOWSMessageCellCornerRadius_Small; +typedef NS_OPTIONS(NSUInteger, OWSDirectionalRectCorner) { + OWSDirectionalRectCornerTopLeading = 1 << 0, + OWSDirectionalRectCornerTopTrailing = 1 << 1, + OWSDirectionalRectCornerBottomLeading = 1 << 2, + OWSDirectionalRectCornerBottomTrailing = 1 << 3, + OWSDirectionalRectCornerAllCorners = ~0UL +}; + @class OWSBubbleView; @protocol OWSBubbleViewPartner @@ -29,11 +37,11 @@ extern const CGFloat kOWSMessageCellCornerRadius_Small; bubbleRight:(CGFloat)bubbleRight sharpCornerRadius:(CGFloat)sharpCornerRadius wideCornerRadius:(CGFloat)wideCornerRadius - sharpCorners:(UIRectCorner)sharpCorners; + sharpCorners:(OWSDirectionalRectCorner)sharpCorners; @property (nonatomic, nullable) UIColor *bubbleColor; -@property (nonatomic) UIRectCorner sharpCorners; +@property (nonatomic) OWSDirectionalRectCorner sharpCorners; - (UIBezierPath *)maskPath; diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m index 831ffb4b3..ab6ab1162 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m @@ -3,10 +3,40 @@ // #import "OWSBubbleView.h" +#import "MainAppContext.h" #import NS_ASSUME_NONNULL_BEGIN +UIRectCorner UIRectCornerForOWSDirectionalRectCorner(OWSDirectionalRectCorner corner); +UIRectCorner UIRectCornerForOWSDirectionalRectCorner(OWSDirectionalRectCorner corner) +{ + if (corner == OWSDirectionalRectCornerAllCorners) { + return UIRectCornerAllCorners; + } + + UIRectCorner rectCorner = 0; + BOOL isRTL = CurrentAppContext().isRTL; + + if (corner & OWSDirectionalRectCornerTopLeading) { + rectCorner = rectCorner | (isRTL ? UIRectCornerTopRight : UIRectCornerTopLeft); + } + + if (corner & OWSDirectionalRectCornerTopTrailing) { + rectCorner = rectCorner | (isRTL ? UIRectCornerTopLeft : UIRectCornerTopRight); + } + + if (corner & OWSDirectionalRectCornerBottomTrailing) { + rectCorner = rectCorner | (isRTL ? UIRectCornerBottomLeft : UIRectCornerBottomRight); + } + + if (corner & OWSDirectionalRectCornerBottomLeading) { + rectCorner = rectCorner | (isRTL ? UIRectCornerBottomRight : UIRectCornerBottomLeft); + } + + return rectCorner; +} + const CGFloat kOWSMessageCellCornerRadius_Large = 18; const CGFloat kOWSMessageCellCornerRadius_Small = 4; @@ -101,7 +131,7 @@ const CGFloat kOWSMessageCellCornerRadius_Small = 4; [CATransaction commit]; } -- (void)setSharpCorners:(UIRectCorner)sharpCorners +- (void)setSharpCorners:(OWSDirectionalRectCorner)sharpCorners { _sharpCorners = sharpCorners; @@ -135,13 +165,11 @@ const CGFloat kOWSMessageCellCornerRadius_Small = 4; return [self.class maskPathForSize:self.bounds.size sharpCorners:self.sharpCorners]; } -+ (UIBezierPath *)maskPathForSize:(CGSize)size sharpCorners:(UIRectCorner)sharpCorners ++ (UIBezierPath *)maskPathForSize:(CGSize)size sharpCorners:(OWSDirectionalRectCorner)sharpCorners { CGRect bounds = CGRectZero; bounds.size = size; - UIBezierPath *bezierPath = [UIBezierPath new]; - CGFloat bubbleTop = 0.f; CGFloat bubbleLeft = 0.f; CGFloat bubbleBottom = size.height; @@ -162,15 +190,18 @@ const CGFloat kOWSMessageCellCornerRadius_Small = 4; bubbleRight:(CGFloat)bubbleRight sharpCornerRadius:(CGFloat)sharpCornerRadius wideCornerRadius:(CGFloat)wideCornerRadius - sharpCorners:(UIRectCorner)sharpCorners + sharpCorners:(OWSDirectionalRectCorner)sharpCorners { UIBezierPath *bezierPath = [UIBezierPath new]; - const CGFloat topLeftRounding = (sharpCorners & UIRectCornerTopLeft) ? sharpCornerRadius : wideCornerRadius; - const CGFloat topRightRounding = (sharpCorners & UIRectCornerTopRight) ? sharpCornerRadius : wideCornerRadius; + UIRectCorner uiSharpCorners = UIRectCornerForOWSDirectionalRectCorner(sharpCorners); + + const CGFloat topLeftRounding = (uiSharpCorners & UIRectCornerTopLeft) ? sharpCornerRadius : wideCornerRadius; + const CGFloat topRightRounding = (uiSharpCorners & UIRectCornerTopRight) ? sharpCornerRadius : wideCornerRadius; - const CGFloat bottomRightRounding = (sharpCorners & UIRectCornerBottomRight) ? sharpCornerRadius : wideCornerRadius; - const CGFloat bottomLeftRounding = (sharpCorners & UIRectCornerBottomLeft) ? sharpCornerRadius : wideCornerRadius; + const CGFloat bottomRightRounding + = (uiSharpCorners & UIRectCornerBottomRight) ? sharpCornerRadius : wideCornerRadius; + const CGFloat bottomLeftRounding = (uiSharpCorners & UIRectCornerBottomLeft) ? sharpCornerRadius : wideCornerRadius; const CGFloat topAngle = 3.0f * M_PI_2; const CGFloat rightAngle = 0.0f; diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m index 033bc4aef..6b612d25f 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m @@ -558,27 +558,29 @@ NS_ASSUME_NONNULL_BEGIN return 12.f; } -- (UIRectCorner)sharpCorners +- (OWSDirectionalRectCorner)sharpCorners { - UIRectCorner sharpCorners = 0; + OWSDirectionalRectCorner sharpCorners = 0; if (!self.viewItem.isFirstInCluster) { - sharpCorners = sharpCorners | (self.isIncoming ? UIRectCornerTopLeft : UIRectCornerTopRight); + sharpCorners = sharpCorners + | (self.isIncoming ? OWSDirectionalRectCornerTopLeading : OWSDirectionalRectCornerTopTrailing); } if (!self.viewItem.isLastInCluster) { - sharpCorners = sharpCorners | (self.isIncoming ? UIRectCornerBottomLeft : UIRectCornerBottomRight); + sharpCorners = sharpCorners + | (self.isIncoming ? OWSDirectionalRectCornerBottomLeading : OWSDirectionalRectCornerBottomTrailing); } return sharpCorners; } -- (UIRectCorner)sharpCornersForQuotedMessage +- (OWSDirectionalRectCorner)sharpCornersForQuotedMessage { if (self.viewItem.senderName) { - return UIRectCornerAllCorners; + return OWSDirectionalRectCornerAllCorners; } else { - return self.sharpCorners | UIRectCornerBottomLeft | UIRectCornerBottomRight; + return self.sharpCorners | OWSDirectionalRectCornerBottomLeading | OWSDirectionalRectCornerBottomTrailing; } } diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.h b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.h index 6f22d4519..83f8e5363 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.h +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.h @@ -2,6 +2,8 @@ // Copyright (c) 2018 Open Whisper Systems. All rights reserved. // +#import "OWSBubbleView.h" + NS_ASSUME_NONNULL_BEGIN @class ConversationStyle; @@ -35,7 +37,7 @@ NS_ASSUME_NONNULL_BEGIN displayableQuotedText:(nullable DisplayableText *)displayableQuotedText conversationStyle:(ConversationStyle *)conversationStyle isOutgoing:(BOOL)isOutgoing - sharpCorners:(UIRectCorner)sharpCorners; + sharpCorners:(OWSDirectionalRectCorner)sharpCorners; // Factory method for "message compose" views. + (OWSQuotedMessageView *)quotedMessageViewForPreview:(OWSQuotedReplyModel *)quotedMessage diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m index 3451e7d99..3bfe39fb8 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m @@ -25,7 +25,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) BOOL isForPreview; @property (nonatomic, readonly) BOOL isOutgoing; -@property (nonatomic, readonly) UIRectCorner sharpCorners; +@property (nonatomic, readonly) OWSDirectionalRectCorner sharpCorners; @property (nonatomic, readonly) UILabel *quotedAuthorLabel; @property (nonatomic, readonly) UILabel *quotedTextLabel; @@ -40,7 +40,7 @@ NS_ASSUME_NONNULL_BEGIN displayableQuotedText:(nullable DisplayableText *)displayableQuotedText conversationStyle:(ConversationStyle *)conversationStyle isOutgoing:(BOOL)isOutgoing - sharpCorners:(UIRectCorner)sharpCorners + sharpCorners:(OWSDirectionalRectCorner)sharpCorners { OWSAssert(quotedMessage); @@ -62,12 +62,13 @@ NS_ASSUME_NONNULL_BEGIN displayableQuotedText = [DisplayableText displayableText:quotedMessage.body]; } - OWSQuotedMessageView *instance = [[OWSQuotedMessageView alloc] initWithQuotedMessage:quotedMessage - displayableQuotedText:displayableQuotedText - conversationStyle:conversationStyle - isForPreview:YES - isOutgoing:YES - sharpCorners:UIRectCornerAllCorners]; + OWSQuotedMessageView *instance = + [[OWSQuotedMessageView alloc] initWithQuotedMessage:quotedMessage + displayableQuotedText:displayableQuotedText + conversationStyle:conversationStyle + isForPreview:YES + isOutgoing:YES + sharpCorners:OWSDirectionalRectCornerAllCorners]; [instance createContents]; return instance; } @@ -77,7 +78,7 @@ NS_ASSUME_NONNULL_BEGIN conversationStyle:(ConversationStyle *)conversationStyle isForPreview:(BOOL)isForPreview isOutgoing:(BOOL)isOutgoing - sharpCorners:(UIRectCorner)sharpCorners + sharpCorners:(OWSDirectionalRectCorner)sharpCorners { self = [super init]; @@ -151,7 +152,7 @@ NS_ASSUME_NONNULL_BEGIN self.clipsToBounds = YES; CAShapeLayer *maskLayer = [CAShapeLayer new]; - UIRectCorner sharpCorners = self.sharpCorners; + OWSDirectionalRectCorner sharpCorners = self.sharpCorners; OWSLayerView *innerBubbleView = [[OWSLayerView alloc] initWithFrame:CGRectZero