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;