Bubble collapse.

pull/1/head
Matthew Chen 7 years ago
parent 578f40d791
commit 3ca2c08b06

@ -40,6 +40,17 @@ const CGFloat kBubbleTextVInset = 10.f;
} }
} }
- (void)setHideTail:(BOOL)hideTail
{
BOOL didChange = _hideTail != hideTail;
_hideTail = hideTail;
if (didChange || !self.shapeLayer) {
[self updateLayers];
}
}
- (void)setFrame:(CGRect)frame - (void)setFrame:(CGRect)frame
{ {
BOOL didChange = !CGSizeEqualToSize(self.frame.size, frame.size); BOOL didChange = !CGSizeEqualToSize(self.frame.size, frame.size);
@ -100,24 +111,25 @@ const CGFloat kBubbleTextVInset = 10.f;
UIBezierPath *bezierPath = [self maskPath]; UIBezierPath *bezierPath = [self maskPath];
self.shapeLayer.fillColor = self.bubbleColor.CGColor; self.shapeLayer.fillColor = self.bubbleColor.CGColor;
// self.shapeLayer.bounds = self.bounds;
self.shapeLayer.path = bezierPath.CGPath; self.shapeLayer.path = bezierPath.CGPath;
// self.maskLayer.bounds = self.bounds;
self.maskLayer.path = bezierPath.CGPath; self.maskLayer.path = bezierPath.CGPath;
} }
- (UIBezierPath *)maskPath - (UIBezierPath *)maskPath
{ {
return [self.class maskPathForSize:self.bounds.size isOutgoing:self.isOutgoing isRTL:self.isRTL]; return [self.class maskPathForSize:self.bounds.size
isOutgoing:self.isOutgoing
hideTail:self.hideTail
isRTL:self.isRTL];
} }
+ (UIBezierPath *)maskPathForSize:(CGSize)size isOutgoing:(BOOL)isOutgoing isRTL:(BOOL)isRTL + (UIBezierPath *)maskPathForSize:(CGSize)size isOutgoing:(BOOL)isOutgoing hideTail:(BOOL)hideTail isRTL:(BOOL)isRTL
{ {
UIBezierPath *bezierPath = [UIBezierPath new]; UIBezierPath *bezierPath = [UIBezierPath new];
CGFloat bubbleLeft = 0.f; CGFloat bubbleLeft = 0.f;
CGFloat bubbleRight = size.width - kBubbleThornSideInset; CGFloat bubbleRight = size.width - (hideTail ? 0.f : kBubbleThornSideInset);
CGFloat bubbleTop = 0.f; CGFloat bubbleTop = 0.f;
CGFloat bubbleBottom = size.height - kBubbleThornVInset; CGFloat bubbleBottom = size.height - kBubbleThornVInset;
@ -135,15 +147,17 @@ const CGFloat kBubbleTextVInset = 10.f;
[bezierPath addQuadCurveToPoint:CGPointMake(bubbleLeft + kBubbleHRounding, bubbleTop) [bezierPath addQuadCurveToPoint:CGPointMake(bubbleLeft + kBubbleHRounding, bubbleTop)
controlPoint:CGPointMake(bubbleLeft, bubbleTop)]; controlPoint:CGPointMake(bubbleLeft, bubbleTop)];
// Thorn Tip if (!hideTail) {
CGPoint thornTip = CGPointMake(size.width + 1, size.height); // Thorn Tip
CGPoint thornA = CGPointMake(bubbleRight - kBubbleHRounding * 0.5f, bubbleBottom - kBubbleVRounding); CGPoint thornTip = CGPointMake(size.width + 1, size.height);
CGPoint thornB = CGPointMake(bubbleRight, bubbleBottom - kBubbleVRounding); CGPoint thornA = CGPointMake(bubbleRight - kBubbleHRounding * 0.5f, bubbleBottom - kBubbleVRounding);
[bezierPath moveToPoint:thornTip]; CGPoint thornB = CGPointMake(bubbleRight, bubbleBottom - kBubbleVRounding);
[bezierPath addQuadCurveToPoint:thornA controlPoint:CGPointMake(thornA.x, bubbleBottom)]; [bezierPath moveToPoint:thornTip];
[bezierPath addLineToPoint:thornB]; [bezierPath addQuadCurveToPoint:thornA controlPoint:CGPointMake(thornA.x, bubbleBottom)];
[bezierPath addQuadCurveToPoint:thornTip controlPoint:CGPointMake(thornB.x, bubbleBottom)]; [bezierPath addLineToPoint:thornB];
[bezierPath addLineToPoint:thornTip]; [bezierPath addQuadCurveToPoint:thornTip controlPoint:CGPointMake(thornB.x, bubbleBottom)];
[bezierPath addLineToPoint:thornTip];
}
// Horizontal Flip If Necessary // Horizontal Flip If Necessary
BOOL shouldFlip = isOutgoing == isRTL; BOOL shouldFlip = isOutgoing == isRTL;

@ -21,8 +21,6 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
// TODO: Review all comments.
CG_INLINE CGSize CGSizeCeil(CGSize size) CG_INLINE CGSize CGSizeCeil(CGSize size)
{ {
return CGSizeMake((CGFloat)ceil(size.width), (CGFloat)ceil(size.height)); return CGSizeMake((CGFloat)ceil(size.width), (CGFloat)ceil(size.height));
@ -308,7 +306,7 @@ CG_INLINE CGSize CGSizeCeil(CGSize size)
// TODO: We might not need to hide it. // TODO: We might not need to hide it.
self.bubbleView.hidden = NO; self.bubbleView.hidden = NO;
self.bubbleView.isOutgoing = self.isOutgoing; self.bubbleView.isOutgoing = self.isOutgoing;
// TODO: Hide tails/thorns here? self.bubbleView.hideTail = self.viewItem.shouldHideBubbleTail;
if (self.shouldHaveFailedSendBadge) { if (self.shouldHaveFailedSendBadge) {
self.failedSendBadgeView = [UIImageView new]; self.failedSendBadgeView = [UIImageView new];
@ -344,15 +342,6 @@ CG_INLINE CGSize CGSizeCeil(CGSize size)
[self updateDateHeader]; [self updateDateHeader];
[self updateFooter]; [self updateFooter];
// TODO: Do we need to pin the bubble size?
{
// - (CGSize)cellSizeForViewWidth:(int)viewWidth contentWidth:(int)contentWidth
// CGSize mediaSize = [self bodyMediaSizeForContentWidth:self.contentWidth];
// TODO:
// [self.viewConstraints addObjectsFromArray:[self.mediaMaskingView
// autoSetDimensionsToSize:mediaSize]];
}
UIView *_Nullable lastSubview = nil; UIView *_Nullable lastSubview = nil;
CGFloat bottomMargin = 0; CGFloat bottomMargin = 0;
UIView *_Nullable bodyMediaView = nil; UIView *_Nullable bodyMediaView = nil;
@ -549,8 +538,7 @@ CG_INLINE CGSize CGSizeCeil(CGSize size)
} else { } else {
DDLogError(@"%@ Failed to load cell media: %@", [self logTag], [self.attachmentStream mediaURL]); DDLogError(@"%@ Failed to load cell media: %@", [self logTag], [self.attachmentStream mediaURL]);
self.viewItem.didCellMediaFailToLoad = YES; self.viewItem.didCellMediaFailToLoad = YES;
[mediaView removeFromSuperview]; // TODO: Do we need to hide/remove the media view?
// TODO: We need to hide/remove the media view.
[self showAttachmentErrorView:mediaView]; [self showAttachmentErrorView:mediaView];
} }
return cellMedia; return cellMedia;
@ -560,10 +548,6 @@ CG_INLINE CGSize CGSizeCeil(CGSize size)
// * If cell is not visible, eagerly unload view contents. // * If cell is not visible, eagerly unload view contents.
- (void)ensureMediaLoadState - (void)ensureMediaLoadState
{ {
// CGSize mediaSize = [self bodyMediaSizeForContentWidth:self.contentWidth];
// TODO:
// [self.viewConstraints addObjectsFromArray:[self.mediaMaskingView autoSetDimensionsToSize:mediaSize]];
if (!self.isCellVisible) { if (!self.isCellVisible) {
// Eagerly unload. // Eagerly unload.
if (self.unloadCellContentBlock) { if (self.unloadCellContentBlock) {
@ -1092,8 +1076,6 @@ CG_INLINE CGSize CGSizeCeil(CGSize size)
OWSAssert(self.mediaSize.height > 0); OWSAssert(self.mediaSize.height > 0);
// TODO: Adjust this behavior. // TODO: Adjust this behavior.
// TODO: This behavior is a bit different than the old behavior defined
// in JSQMediaItem+OWS.h. Let's discuss.
CGFloat contentAspectRatio = self.mediaSize.width / self.mediaSize.height; CGFloat contentAspectRatio = self.mediaSize.width / self.mediaSize.height;
// Clamp the aspect ratio so that very thin/wide content is presented // Clamp the aspect ratio so that very thin/wide content is presented
@ -1187,12 +1169,20 @@ CG_INLINE CGSize CGSizeCeil(CGSize size)
- (CGFloat)textLeadingMargin - (CGFloat)textLeadingMargin
{ {
return (self.isIncoming ? kBubbleTextHInset + kBubbleThornSideInset : kBubbleTextHInset); CGFloat result = kBubbleTextHInset;
if (self.isIncoming && !self.viewItem.shouldHideBubbleTail) {
result += kBubbleThornSideInset;
}
return result;
} }
- (CGFloat)textTrailingMargin - (CGFloat)textTrailingMargin
{ {
return (self.isIncoming ? kBubbleTextHInset : kBubbleTextHInset + kBubbleThornSideInset); CGFloat result = kBubbleTextHInset;
if (!self.isIncoming && !self.viewItem.shouldHideBubbleTail) {
result += kBubbleThornSideInset;
}
return result;
} }
- (CGFloat)textTopMargin - (CGFloat)textTopMargin

@ -4463,8 +4463,10 @@ typedef enum : NSUInteger {
// Update the "shouldShowDate" property of the view items. // Update the "shouldShowDate" property of the view items.
OWSInteractionType lastInteractionType = OWSInteractionType_Unknown; OWSInteractionType lastInteractionType = OWSInteractionType_Unknown;
MessageRecipientStatus lastRecipientStatus = MessageRecipientStatusUploading; MessageRecipientStatus lastRecipientStatus = MessageRecipientStatusUploading;
NSString *_Nullable lastIncomingSenderId = nil;
for (ConversationViewItem *viewItem in viewItems.reverseObjectEnumerator) { for (ConversationViewItem *viewItem in viewItems.reverseObjectEnumerator) {
BOOL shouldHideRecipientStatus = NO; BOOL shouldHideRecipientStatus = NO;
BOOL shouldHideBubbleTail = NO;
OWSInteractionType interactionType = viewItem.interaction.interactionType; OWSInteractionType interactionType = viewItem.interaction.interactionType;
if (interactionType == OWSInteractionType_OutgoingMessage) { if (interactionType == OWSInteractionType_OutgoingMessage) {
@ -4473,14 +4475,23 @@ typedef enum : NSUInteger {
[MessageRecipientStatusUtils recipientStatusWithOutgoingMessage:outgoingMessage]; [MessageRecipientStatusUtils recipientStatusWithOutgoingMessage:outgoingMessage];
if (outgoingMessage.messageState == TSOutgoingMessageStateUnsent) { if (outgoingMessage.messageState == TSOutgoingMessageStateUnsent) {
// always sow "failed to send" status // always show "failed to send" status
shouldHideRecipientStatus = NO; shouldHideRecipientStatus = NO;
} else { } else {
shouldHideRecipientStatus shouldHideRecipientStatus
= (interactionType == lastInteractionType && recipientStatus == lastRecipientStatus); = (interactionType == lastInteractionType && recipientStatus == lastRecipientStatus);
} }
shouldHideBubbleTail = (interactionType == lastInteractionType && recipientStatus == lastRecipientStatus);
lastRecipientStatus = recipientStatus; lastRecipientStatus = recipientStatus;
} else if (interactionType == OWSInteractionType_IncomingMessage) {
TSIncomingMessage *incomingMessage = (TSIncomingMessage *)viewItem.interaction;
NSString *incomingSenderId = incomingMessage.authorId;
OWSAssert(incomingSenderId.length > 0);
shouldHideBubbleTail = (interactionType == lastInteractionType &&
[NSObject isNullableObject:lastIncomingSenderId equalTo:incomingSenderId]);
lastIncomingSenderId = incomingSenderId;
} }
lastInteractionType = interactionType; lastInteractionType = interactionType;
@ -4491,6 +4502,7 @@ typedef enum : NSUInteger {
[rowsThatChangedSize addObject:@(viewItem.previousRow)]; [rowsThatChangedSize addObject:@(viewItem.previousRow)];
} }
viewItem.shouldHideRecipientStatus = shouldHideRecipientStatus; viewItem.shouldHideRecipientStatus = shouldHideRecipientStatus;
viewItem.shouldHideBubbleTail = shouldHideBubbleTail;
} }
self.viewItems = viewItems; self.viewItems = viewItems;

@ -46,6 +46,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);
@property (nonatomic, readonly) BOOL hasBodyText; @property (nonatomic, readonly) BOOL hasBodyText;
@property (nonatomic) BOOL shouldShowDate; @property (nonatomic) BOOL shouldShowDate;
@property (nonatomic) BOOL shouldHideRecipientStatus; @property (nonatomic) BOOL shouldHideRecipientStatus;
@property (nonatomic) BOOL shouldHideBubbleTail;
@property (nonatomic) NSInteger row; @property (nonatomic) NSInteger row;
// During updates, we sometimes need the previous row index // During updates, we sometimes need the previous row index

@ -134,6 +134,17 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
[self clearCachedLayoutState]; [self clearCachedLayoutState];
} }
- (void)setShouldHideBubbleTail:(BOOL)shouldHideBubbleTail
{
if (_shouldHideBubbleTail == shouldHideBubbleTail) {
return;
}
_shouldHideBubbleTail = shouldHideBubbleTail;
[self clearCachedLayoutState];
}
- (void)clearCachedLayoutState - (void)clearCachedLayoutState
{ {
self.cachedCellSize = nil; self.cachedCellSize = nil;

Loading…
Cancel
Save