Merge branch 'mkirk/sharp-corners'
This commit is contained in:
commit
914b76c363
@ -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 <NSObject>
|
||||
@ -23,10 +31,17 @@ 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:(OWSDirectionalRectCorner)sharpCorners;
|
||||
|
||||
@property (nonatomic, nullable) UIColor *bubbleColor;
|
||||
|
||||
@property (nonatomic) BOOL useSmallCorners_Top;
|
||||
@property (nonatomic) BOOL useSmallCorners_Bottom;
|
||||
@property (nonatomic) OWSDirectionalRectCorner sharpCorners;
|
||||
|
||||
- (UIBezierPath *)maskPath;
|
||||
|
||||
|
||||
@ -3,12 +3,42 @@
|
||||
//
|
||||
|
||||
#import "OWSBubbleView.h"
|
||||
#import "MainAppContext.h"
|
||||
#import <SignalMessaging/UIView+OWS.h>
|
||||
|
||||
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 = 2;
|
||||
const CGFloat kOWSMessageCellCornerRadius_Small = 4;
|
||||
|
||||
@interface OWSBubbleView ()
|
||||
|
||||
@ -101,16 +131,9 @@ const CGFloat kOWSMessageCellCornerRadius_Small = 2;
|
||||
[CATransaction commit];
|
||||
}
|
||||
|
||||
- (void)setUseSmallCorners_Top:(BOOL)useSmallCorners_Top
|
||||
- (void)setSharpCorners:(OWSDirectionalRectCorner)sharpCorners
|
||||
{
|
||||
_useSmallCorners_Top = useSmallCorners_Top;
|
||||
|
||||
[self updateLayers];
|
||||
}
|
||||
|
||||
- (void)setUseSmallCorners_Bottom:(BOOL)useSmallCorners_Bottom
|
||||
{
|
||||
_useSmallCorners_Bottom = useSmallCorners_Bottom;
|
||||
_sharpCorners = sharpCorners;
|
||||
|
||||
[self updateLayers];
|
||||
}
|
||||
@ -139,63 +162,82 @@ 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:(OWSDirectionalRectCorner)sharpCorners
|
||||
{
|
||||
CGRect bounds = CGRectZero;
|
||||
bounds.size = size;
|
||||
|
||||
CGFloat bubbleTop = 0.f;
|
||||
CGFloat bubbleLeft = 0.f;
|
||||
CGFloat bubbleBottom = size.height;
|
||||
CGFloat bubbleRight = size.width;
|
||||
|
||||
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:(OWSDirectionalRectCorner)sharpCorners
|
||||
{
|
||||
UIBezierPath *bezierPath = [UIBezierPath new];
|
||||
|
||||
CGFloat bubbleLeft = 0.f;
|
||||
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);
|
||||
UIRectCorner uiSharpCorners = UIRectCornerForOWSDirectionalRectCorner(sharpCorners);
|
||||
|
||||
const CGFloat topLeftRounding = (uiSharpCorners & UIRectCornerTopLeft) ? sharpCornerRadius : wideCornerRadius;
|
||||
const CGFloat topRightRounding = (uiSharpCorners & UIRectCornerTopRight) ? 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;
|
||||
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];
|
||||
|
||||
return bezierPath;
|
||||
}
|
||||
|
||||
@ -228,11 +270,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
|
||||
|
||||
@ -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,8 +275,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
[OWSQuotedMessageView quotedMessageViewForConversation:self.viewItem.quotedReply
|
||||
displayableQuotedText:displayableQuotedText
|
||||
conversationStyle:self.conversationStyle
|
||||
isOutgoing:isOutgoing
|
||||
sharesTopBorderWithMessageBubble:!self.shouldShowSenderName];
|
||||
isOutgoing:self.isOutgoing
|
||||
sharpCorners:self.sharpCornersForQuotedMessage];
|
||||
quotedMessageView.delegate = self;
|
||||
|
||||
self.quotedMessageView = quotedMessageView;
|
||||
@ -559,12 +558,35 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
return 12.f;
|
||||
}
|
||||
|
||||
- (OWSDirectionalRectCorner)sharpCorners
|
||||
{
|
||||
OWSDirectionalRectCorner sharpCorners = 0;
|
||||
|
||||
if (!self.viewItem.isFirstInCluster) {
|
||||
sharpCorners = sharpCorners
|
||||
| (self.isIncoming ? OWSDirectionalRectCornerTopLeading : OWSDirectionalRectCornerTopTrailing);
|
||||
}
|
||||
|
||||
if (!self.viewItem.isLastInCluster) {
|
||||
sharpCorners = sharpCorners
|
||||
| (self.isIncoming ? OWSDirectionalRectCornerBottomLeading : OWSDirectionalRectCornerBottomTrailing);
|
||||
}
|
||||
|
||||
return sharpCorners;
|
||||
}
|
||||
|
||||
- (OWSDirectionalRectCorner)sharpCornersForQuotedMessage
|
||||
{
|
||||
if (self.viewItem.senderName) {
|
||||
return OWSDirectionalRectCornerAllCorners;
|
||||
} else {
|
||||
return self.sharpCorners | OWSDirectionalRectCornerBottomLeading | OWSDirectionalRectCornerBottomTrailing;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)configureBubbleRounding
|
||||
{
|
||||
self.bubbleView.useSmallCorners_Top
|
||||
= (self.hasBodyMediaWithThumbnail && !self.shouldShowSenderName && !self.isQuotedReply);
|
||||
self.bubbleView.useSmallCorners_Bottom
|
||||
= (self.hasBodyMediaWithThumbnail && !self.hasBodyText && !self.hasBottomFooter);
|
||||
self.bubbleView.sharpCorners = self.sharpCorners;
|
||||
}
|
||||
|
||||
- (void)updateBubbleColor
|
||||
@ -1199,7 +1221,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,8 +1228,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
[OWSQuotedMessageView quotedMessageViewForConversation:self.viewItem.quotedReply
|
||||
displayableQuotedText:displayableQuotedText
|
||||
conversationStyle:self.conversationStyle
|
||||
isOutgoing:isOutgoing
|
||||
sharesTopBorderWithMessageBubble:NO];
|
||||
isOutgoing:self.isOutgoing
|
||||
sharpCorners:self.sharpCornersForQuotedMessage];
|
||||
CGSize result = [quotedMessageView sizeForMaxWidth:self.conversationStyle.maxMessageWidth];
|
||||
return [NSValue valueWithCGSize:CGSizeCeil(result)];
|
||||
}
|
||||
|
||||
@ -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
|
||||
sharesTopBorderWithMessageBubble:(BOOL)sharesTopBorderWithMessageBubble;
|
||||
sharpCorners:(OWSDirectionalRectCorner)sharpCorners;
|
||||
|
||||
// Factory method for "message compose" views.
|
||||
+ (OWSQuotedMessageView *)quotedMessageViewForPreview:(OWSQuotedReplyModel *)quotedMessage
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
#import "OWSQuotedMessageView.h"
|
||||
#import "ConversationViewItem.h"
|
||||
#import "Environment.h"
|
||||
#import "OWSBubbleShapeView.h"
|
||||
#import "OWSBubbleView.h"
|
||||
#import "Signal-Swift.h"
|
||||
#import <SignalMessaging/OWSContactsManager.h>
|
||||
#import <SignalMessaging/SignalMessaging-Swift.h>
|
||||
@ -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) 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
|
||||
sharesTopBorderWithMessageBubble:(BOOL)sharesTopBorderWithMessageBubble
|
||||
sharpCorners:(OWSDirectionalRectCorner)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
|
||||
@ -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
|
||||
sharesTopBorderWithMessageBubble:NO];
|
||||
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
|
||||
sharesTopBorderWithMessageBubble:(BOOL)sharesTopBorderWithMessageBubble
|
||||
sharpCorners:(OWSDirectionalRectCorner)sharpCorners
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
@ -92,7 +93,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
_isForPreview = isForPreview;
|
||||
_conversationStyle = conversationStyle;
|
||||
_isOutgoing = isOutgoing;
|
||||
_sharesTopBorderWithMessageBubble = sharesTopBorderWithMessageBubble;
|
||||
_sharpCorners = sharpCorners;
|
||||
|
||||
_quotedAuthorLabel = [UILabel new];
|
||||
_quotedTextLabel = [UILabel new];
|
||||
@ -151,34 +152,28 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
self.clipsToBounds = YES;
|
||||
|
||||
CAShapeLayer *maskLayer = [CAShapeLayer new];
|
||||
BOOL sharesTopBorderWithMessageBubble = self.sharesTopBorderWithMessageBubble;
|
||||
OWSDirectionalRectCorner sharpCorners = self.sharpCorners;
|
||||
|
||||
OWSLayerView *innerBubbleView = [[OWSLayerView alloc]
|
||||
initWithFrame:CGRectZero
|
||||
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;
|
||||
const CGFloat bubbleBottom = layerFrame.size.height;
|
||||
|
||||
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;
|
||||
const CGFloat sharpCornerRadius = 4;
|
||||
const CGFloat wideCornerRadius = 10;
|
||||
|
||||
[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)];
|
||||
UIBezierPath *bezierPath = [OWSBubbleView roundedBezierRectWithBubbleTop:bubbleTop
|
||||
bubbleLeft:bubbleLeft
|
||||
bubbleBottom:bubbleBottom
|
||||
bubbleRight:bubbleRight
|
||||
sharpCornerRadius:sharpCornerRadius
|
||||
wideCornerRadius:wideCornerRadius
|
||||
sharpCorners:sharpCorners];
|
||||
|
||||
maskLayer.path = bezierPath.CGPath;
|
||||
}];
|
||||
|
||||
@ -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,21 @@ 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 if (nextViewItem.shouldShowDate) {
|
||||
isLastInCluster = YES;
|
||||
} else {
|
||||
isLastInCluster = nextViewItem.interaction.interactionType != OWSInteractionType_OutgoingMessage;
|
||||
}
|
||||
} else if (interactionType == OWSInteractionType_IncomingMessage) {
|
||||
|
||||
TSIncomingMessage *incomingMessage = (TSIncomingMessage *)viewItem.interaction;
|
||||
@ -4909,6 +4926,27 @@ typedef enum : NSUInteger {
|
||||
[NSObject isNullableObject:nextIncomingSenderId equalTo:incomingSenderId];
|
||||
}
|
||||
|
||||
// clustering
|
||||
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) {
|
||||
isLastInCluster = YES;
|
||||
} else if (nextViewItem.interaction.interactionType != OWSInteractionType_IncomingMessage) {
|
||||
isLastInCluster = YES;
|
||||
} else if (nextViewItem.shouldShowDate) {
|
||||
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 +4982,8 @@ typedef enum : NSUInteger {
|
||||
}
|
||||
}
|
||||
|
||||
viewItem.isFirstInCluster = isFirstInCluster;
|
||||
viewItem.isLastInCluster = isLastInCluster;
|
||||
viewItem.shouldShowSenderAvatar = shouldShowSenderAvatar;
|
||||
viewItem.shouldHideFooter = shouldHideFooter;
|
||||
viewItem.senderName = senderName;
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user