diff --git a/Signal/Images.xcassets/message_send_failure.imageset/Contents.json b/Signal/Images.xcassets/message_send_failure.imageset/Contents.json new file mode 100644 index 000000000..c1ca74cf9 --- /dev/null +++ b/Signal/Images.xcassets/message_send_failure.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "message_send_failure@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "message_send_failure@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "message_send_failure@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/message_send_failure.imageset/message_send_failure@1x.png b/Signal/Images.xcassets/message_send_failure.imageset/message_send_failure@1x.png new file mode 100644 index 000000000..faa7e18f0 Binary files /dev/null and b/Signal/Images.xcassets/message_send_failure.imageset/message_send_failure@1x.png differ diff --git a/Signal/Images.xcassets/message_send_failure.imageset/message_send_failure@2x.png b/Signal/Images.xcassets/message_send_failure.imageset/message_send_failure@2x.png new file mode 100644 index 000000000..1e540f14b Binary files /dev/null and b/Signal/Images.xcassets/message_send_failure.imageset/message_send_failure@2x.png differ diff --git a/Signal/Images.xcassets/message_send_failure.imageset/message_send_failure@3x.png b/Signal/Images.xcassets/message_send_failure.imageset/message_send_failure@3x.png new file mode 100644 index 000000000..141311227 Binary files /dev/null and b/Signal/Images.xcassets/message_send_failure.imageset/message_send_failure@3x.png differ diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m index 4901da5ec..bb84be168 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m @@ -73,6 +73,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) BubbleMaskingView *payloadView; @property (nonatomic) UILabel *dateHeaderLabel; @property (nonatomic) UITextView *textView; +@property (nonatomic, nullable) UIImageView *failedSendBadgeView; @property (nonatomic, nullable) UILabel *tapForMoreLabel; @property (nonatomic, nullable) UIImageView *bubbleImageView; @property (nonatomic, nullable) AttachmentUploadView *attachmentUploadView; @@ -85,6 +86,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) UIView *footerView; @property (nonatomic) UILabel *footerLabel; @property (nonatomic, nullable) OWSExpirationTimerView *expirationTimerView; +@property (nonatomic, nullable) NSArray *payloadConstraints; @property (nonatomic, nullable) NSArray *dateHeaderConstraints; @property (nonatomic, nullable) NSArray *contentConstraints; @property (nonatomic, nullable) NSArray *footerConstraints; @@ -108,6 +110,7 @@ NS_ASSUME_NONNULL_BEGIN OWSAssert(!self.textView); self.layoutMargins = UIEdgeInsetsZero; + self.contentView.layoutMargins = UIEdgeInsetsZero; self.payloadView = [BubbleMaskingView new]; self.payloadView.layoutMargins = UIEdgeInsetsZero; @@ -153,7 +156,6 @@ NS_ASSUME_NONNULL_BEGIN self.footerLabel.hidden = YES; [self.payloadView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.dateHeaderLabel]; - [self.payloadView autoPinWidthToSuperview]; [self.footerView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.payloadView]; [self.footerView autoPinEdgeToSuperviewEdge:ALEdgeBottom]; [self.footerView autoPinWidthToSuperview]; @@ -193,6 +195,28 @@ NS_ASSUME_NONNULL_BEGIN return (CGFloat)ceil([self tapForMoreFont].lineHeight * 1.25); } +- (BOOL)hasFailedSendBadge +{ + if (![self.viewItem.interaction isKindOfClass:[TSOutgoingMessage class]]) { + return NO; + } + TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.viewItem.interaction; + return outgoingMessage.messageState == TSOutgoingMessageStateUnsent; +} + +- (UIImage *)failedSendBadge +{ + UIImage *image = [UIImage imageNamed:@"message_send_failure"]; + OWSAssert(image); + OWSAssert(image.size.width == self.failedSendBadgeSize && image.size.height == self.failedSendBadgeSize); + return image; +} + +- (CGFloat)failedSendBadgeSize +{ + return 20.f; +} + - (OWSMessageCellType)cellType { return self.viewItem.messageCellType; @@ -243,6 +267,25 @@ NS_ASSUME_NONNULL_BEGIN OWSAssert(self.viewItem.interaction); OWSAssert([self.viewItem.interaction isKindOfClass:[TSMessage class]]); + if (self.hasFailedSendBadge) { + self.failedSendBadgeView = [UIImageView new]; + self.failedSendBadgeView.image = + [self.failedSendBadge imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + self.failedSendBadgeView.tintColor = [UIColor ows_destructiveRedColor]; + [self.contentView addSubview:self.failedSendBadgeView]; + + self.payloadConstraints = @[ + [self.payloadView autoPinLeadingToSuperview], + [self.failedSendBadgeView autoPinLeadingToTrailingOfView:self.payloadView], + [self.failedSendBadgeView autoPinTrailingToSuperview], + [self.failedSendBadgeView autoAlignAxis:ALAxisHorizontal toSameAxisOfView:self.payloadView], + [self.failedSendBadgeView autoSetDimension:ALDimensionWidth toSize:self.failedSendBadgeSize], + [self.failedSendBadgeView autoSetDimension:ALDimensionHeight toSize:self.failedSendBadgeSize], + ]; + } else { + self.payloadConstraints = [self.payloadView autoPinWidthToSuperview]; + } + JSQMessagesBubbleImage *bubbleImageData; if ([self.viewItem.interaction isKindOfClass:[TSOutgoingMessage class]]) { TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.viewItem.interaction; @@ -856,6 +899,10 @@ NS_ASSUME_NONNULL_BEGIN cellSize.height += self.dateHeaderHeight; cellSize.height += self.footerHeight; + if (self.hasFailedSendBadge) { + cellSize.width += self.failedSendBadgeSize; + } + cellSize.width = ceil(cellSize.width); cellSize.height = ceil(cellSize.height); @@ -928,6 +975,8 @@ NS_ASSUME_NONNULL_BEGIN { [super prepareForReuse]; + [NSLayoutConstraint deactivateConstraints:self.payloadConstraints]; + self.payloadConstraints = nil; [NSLayoutConstraint deactivateConstraints:self.contentConstraints]; self.contentConstraints = nil; [NSLayoutConstraint deactivateConstraints:self.dateHeaderConstraints]; @@ -940,6 +989,8 @@ NS_ASSUME_NONNULL_BEGIN self.textView.text = nil; self.textView.hidden = YES; self.textView.dataDetectorTypes = UIDataDetectorTypeNone; + [self.failedSendBadgeView removeFromSuperview]; + self.failedSendBadgeView = nil; [self.tapForMoreLabel removeFromSuperview]; self.tapForMoreLabel = nil; self.footerLabel.text = nil; diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewLayout.m b/Signal/src/ViewControllers/ConversationView/ConversationViewLayout.m index 072f2c97b..bf87bf3e6 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewLayout.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewLayout.m @@ -105,8 +105,8 @@ NS_ASSUME_NONNULL_BEGIN switch (layoutItem.layoutAlignment) { case ConversationViewLayoutAlignment_Incoming: case ConversationViewLayoutAlignment_Outgoing: { - BOOL isLeft = ((layoutItem.layoutAlignment == ConversationViewLayoutAlignment_Incoming && !isRTL) - || (layoutItem.layoutAlignment == ConversationViewLayoutAlignment_Outgoing && isRTL)); + BOOL isIncoming = layoutItem.layoutAlignment == ConversationViewLayoutAlignment_Incoming; + BOOL isLeft = isIncoming ^ isRTL; if (isLeft) { itemFrame = CGRectMake(hInset, y, layoutSize.width, layoutSize.height); } else {