From 4ac51e95c2acdb106c1a24715ac0536f83f94750 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 11 Jun 2019 16:12:13 -0400 Subject: [PATCH 1/2] Sketch out the "tombstone" state of messages with per-message expiration. --- .../ConversationView/Cells/OWSBubbleView.h | 6 +- .../ConversationView/Cells/OWSBubbleView.m | 47 ++++++++--- .../Cells/OWSMessageBubbleView.m | 6 +- .../Cells/OWSMessageHiddenView.m | 82 ++++++++++++------- .../Cells/TypingIndicatorCell.swift | 4 +- .../ConversationView/ConversationViewItem.m | 10 ++- 6 files changed, 105 insertions(+), 50 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.h b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.h index d6dd8da837..ccbac9400e 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.h +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.h @@ -37,8 +37,10 @@ typedef NS_OPTIONS(NSUInteger, OWSDirectionalRectCorner) { wideCornerRadius:(CGFloat)wideCornerRadius sharpCorners:(OWSDirectionalRectCorner)sharpCorners; -@property (nonatomic, nullable) UIColor *bubbleColor; -@property (nonatomic, nullable) NSArray *bubbleGradientColors; +@property (nonatomic, nullable) UIColor *fillColor; +@property (nonatomic, nullable) NSArray *fillGradientColors; +@property (nonatomic, nullable) UIColor *strokeColor; +@property (nonatomic) CGFloat strokeThickness; @property (nonatomic) OWSDirectionalRectCorner sharpCorners; diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m index f83b80a269..a5f22436fd 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m @@ -124,22 +124,36 @@ const CGFloat kOWSMessageCellCornerRadius_Small = 4; [self updatePartnerViews]; } -- (void)setBubbleColor:(nullable UIColor *)bubbleColor +- (void)setFillColor:(nullable UIColor *)fillColor { OWSAssertIsOnMainThread(); - _bubbleColor = bubbleColor; - _bubbleGradientColors = nil; + _fillColor = fillColor; + _fillGradientColors = nil; [self updateLayers]; } -- (void)setBubbleGradientColors:(nullable NSArray *)bubbleGradientColors +- (void)setFillGradientColors:(nullable NSArray *)fillGradientColors { OWSAssertIsOnMainThread(); - _bubbleColor = nil; - _bubbleGradientColors = bubbleGradientColors; + _fillColor = nil; + _fillGradientColors = fillGradientColors; + + [self updateLayers]; +} + +- (void)setStrokeColor:(nullable UIColor *)strokeColor +{ + _strokeColor = strokeColor; + + [self updateLayers]; +} + +- (void)setStrokeThickness:(CGFloat)strokeThickness +{ + _strokeThickness = strokeThickness; [self updateLayers]; } @@ -169,15 +183,19 @@ const CGFloat kOWSMessageCellCornerRadius_Small = 4; [CATransaction begin]; [CATransaction setDisableActions:YES]; - self.shapeLayer.fillColor = self.bubbleColor.CGColor; - self.shapeLayer.hidden = self.bubbleColor == nil; + self.shapeLayer.fillColor = self.fillColor.CGColor; + self.shapeLayer.strokeColor = self.strokeColor.CGColor; + self.shapeLayer.lineWidth = self.strokeThickness; self.shapeLayer.path = bezierPath.CGPath; + self.shapeLayer.hidden = (self.fillColor == nil) && (self.strokeColor == nil); - NSArray *bubbleGradientCGColors = self.bubbleGradientCGColors; - self.gradientLayer.colors = bubbleGradientCGColors; - self.gradientLayer.hidden = bubbleGradientCGColors.count < 1; + NSArray *_Nullable fillGradientCGColors = self.fillGradientCGColors; + self.gradientLayer.colors = fillGradientCGColors; + self.gradientLayer.hidden = fillGradientCGColors.count < 1; // Currently this view only supports linear (or axial) gradients // from the top-left to bottom-right. + // self.gradientLayer.startPoint = CGPointMake(0, 1); + // self.gradientLayer.endPoint = CGPointMake(1, 0); self.gradientLayer.startPoint = CGPointMake(0, 0); self.gradientLayer.endPoint = CGPointMake(1, 1); self.gradientLayer.frame = self.bounds; @@ -187,10 +205,13 @@ const CGFloat kOWSMessageCellCornerRadius_Small = 4; [CATransaction commit]; } -- (NSArray *)bubbleGradientCGColors +- (nullable NSArray *)fillGradientCGColors { + if (self.fillGradientColors.count < 1) { + return nil; + } NSMutableArray *result = [NSMutableArray new]; - for (UIColor *color in self.bubbleGradientColors) { + for (UIColor *color in self.fillGradientColors) { [result addObject:(id)color.CGColor]; } return result; diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m index 7668dbe5e2..77c834f88d 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m @@ -557,11 +557,11 @@ NS_ASSUME_NONNULL_BEGIN { BOOL hasOnlyBodyMediaView = (self.hasBodyMediaWithThumbnail && self.stackView.subviews.count == 1); if (!hasOnlyBodyMediaView) { - self.bubbleView.bubbleColor = self.bubbleColor; + self.bubbleView.fillColor = self.bubbleColor; } else { // Media-only messages should have no background color; they will fill the bubble's bounds // and we don't want artifacts at the edges. - self.bubbleView.bubbleColor = nil; + self.bubbleView.fillColor = nil; } } @@ -1369,7 +1369,7 @@ NS_ASSUME_NONNULL_BEGIN self.bodyTextView.attributedText = nil; self.bodyTextView.hidden = YES; - self.bubbleView.bubbleColor = nil; + self.bubbleView.fillColor = nil; [self.bubbleView clearPartnerViews]; for (UIView *subview in self.bubbleView.subviews) { diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageHiddenView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageHiddenView.m index 8834ce0249..e0a4042549 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageHiddenView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageHiddenView.m @@ -129,11 +129,6 @@ NS_ASSUME_NONNULL_BEGIN return self.viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage; } -- (BOOL)perMessageExpirationHasExpired -{ - return self.viewItem.perMessageExpirationHasExpired; -} - #pragma mark - Load - (void)configureViews @@ -167,7 +162,7 @@ NS_ASSUME_NONNULL_BEGIN conversationStyle:self.conversationStyle isIncoming:self.isIncoming isOverlayingMedia:NO - isOutsideBubble:NO]; + isOutsideBubble:self.isExpired]; [textViews addObject:self.footerView]; } @@ -181,6 +176,11 @@ NS_ASSUME_NONNULL_BEGIN [self updateBubbleColor]; [self configureBubbleRounding]; + + if (self.isExpired) { + self.bubbleView.strokeColor = self.contentForegroundColor; + self.bubbleView.strokeThickness = 1.f; + } } - (CGFloat)senderNameBottomSpacing @@ -250,15 +250,15 @@ NS_ASSUME_NONNULL_BEGIN - (void)updateBubbleColor { - if (self.perMessageExpirationHasExpired) { - self.bubbleView.bubbleColor = UIColor.ows_gray15Color; + if (self.isExpired) { + self.bubbleView.fillColor = Theme.backgroundColor; } else if (self.isIncoming) { - self.bubbleView.bubbleGradientColors = @[ + self.bubbleView.fillGradientColors = @[ [UIColor.ows_gray05Color blendWithColor:UIColor.ows_blackColor alpha:0.15], UIColor.ows_gray05Color, ]; } else { - self.bubbleView.bubbleGradientColors = @[ + self.bubbleView.fillGradientColors = @[ [UIColor.ows_signalBlueColor blendWithColor:UIColor.ows_blackColor alpha:0.15], UIColor.ows_signalBlueColor, ]; @@ -272,7 +272,7 @@ NS_ASSUME_NONNULL_BEGIN - (UIColor *)contentForegroundColor { - if (self.perMessageExpirationHasExpired) { + if (self.isExpired) { return UIColor.ows_gray60Color; } else if (self.isIncoming) { return UIColor.ows_gray90Color; @@ -330,19 +330,17 @@ NS_ASSUME_NONNULL_BEGIN self.label.textColor = self.contentForegroundColor; self.label.font = UIFont.ows_dynamicTypeSubheadlineFont.ows_mediumWeight; - if (self.isFailedDownload) { + if (self.isExpired) { + self.label.text = NSLocalizedString(@"PER_MESSAGE_EXPIRATION_VIEWED", + @"Label for messages with per-message expiration indicating that " + @"user has viewed the message's contents."); + } else if (self.isFailedDownload) { self.label.text = NSLocalizedString( @"ATTACHMENT_DOWNLOADING_STATUS_FAILED", @"Status label when an attachment download has failed."); } else if (self.isAvailable) { - if (self.perMessageExpirationHasExpired) { - self.label.text = NSLocalizedString(@"PER_MESSAGE_EXPIRATION_VIEWED", - @"Label for messages with per-message expiration indicating that " - @"user has viewed the message's contents."); - } else { - self.label.text = NSLocalizedString(@"PER_MESSAGE_EXPIRATION_TAP_TO_VIEW", - @"Label for messages with per-message expiration indicating that " - @"user can tap to view the message's contents."); - } + self.label.text = NSLocalizedString(@"PER_MESSAGE_EXPIRATION_TAP_TO_VIEW", + @"Label for messages with per-message expiration indicating that " + @"user can tap to view the message's contents."); } else { OWSFailDebug(@"Unexpected view state."); } @@ -354,8 +352,21 @@ NS_ASSUME_NONNULL_BEGIN { OWSAssertDebug(self.iconView); - [self.iconView setTemplateImageName:(self.perMessageExpirationHasExpired ? @"play-outline-24" : @"play-filled-24") - tintColor:self.contentForegroundColor]; + [self.iconView setTemplateImageName:self.iconName tintColor:self.contentForegroundColor]; +} + +- (NSString *)iconName +{ + if (self.isExpired) { + return @"play-outline-24"; + } else if (self.isAvailable) { + return @"play-filled-24"; + } else if (self.isFailedDownload) { + // TODO: Waiting on design. + return @"play-outline-24"; + } else { + return @"play-filled-24"; + } } - (nullable UIView *)createDownloadViewIfNecessary @@ -403,12 +414,20 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)shouldShowIcon { - return self.isAvailable; + // TODO: Pending design. + return YES; +} + +- (BOOL)isExpired +{ + return self.viewItem.perMessageExpirationHasExpired; } - (BOOL)isAvailable { - if (self.viewItem.attachmentStream) { + if (self.viewItem.perMessageExpirationHasExpired) { + return NO; + } else if (self.viewItem.attachmentStream) { return YES; } else if (self.viewItem.attachmentPointer) { return NO; @@ -420,7 +439,9 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)isFailedDownload { - if (self.viewItem.attachmentStream) { + if (self.viewItem.perMessageExpirationHasExpired) { + return NO; + } else if (self.viewItem.attachmentStream) { return NO; } else if (self.viewItem.attachmentPointer) { return self.viewItem.attachmentPointer.state == TSAttachmentPointerStateFailed; @@ -432,7 +453,9 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)isOngoingDownload { - if (self.viewItem.attachmentStream) { + if (self.viewItem.perMessageExpirationHasExpired) { + return NO; + } else if (self.viewItem.attachmentStream) { return NO; } else if (self.viewItem.attachmentPointer) { return self.viewItem.attachmentPointer.state != TSAttachmentPointerStateFailed; @@ -565,7 +588,10 @@ NS_ASSUME_NONNULL_BEGIN self.label.text = nil; self.iconView.image = nil; - self.bubbleView.bubbleColor = nil; + self.bubbleView.fillColor = nil; + self.bubbleView.fillGradientColors = nil; + self.bubbleView.strokeColor = nil; + self.bubbleView.strokeThickness = 0.f; [self.bubbleView clearPartnerViews]; for (UIView *subview in self.bubbleView.subviews) { diff --git a/Signal/src/ViewControllers/ConversationView/Cells/TypingIndicatorCell.swift b/Signal/src/ViewControllers/ConversationView/Cells/TypingIndicatorCell.swift index 1de1235c6a..d324e96f22 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/TypingIndicatorCell.swift +++ b/Signal/src/ViewControllers/ConversationView/Cells/TypingIndicatorCell.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. // import Foundation @@ -50,7 +50,7 @@ public class TypingIndicatorCell: ConversationViewCell { return } - bubbleView.bubbleColor = conversationStyle.bubbleColor(isIncoming: true) + bubbleView.fillColor = conversationStyle.bubbleColor(isIncoming: true) typingIndicatorView.startAnimation() viewConstraints.append(contentsOf: [ diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m index 25cc34ce1c..47cecfcfd0 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m @@ -686,6 +686,12 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) TSMessage *message = (TSMessage *)self.interaction; if (message.hasPerMessageExpiration) { + if (message.perMessageExpirationHasExpired) { + self.messageCellType = OWSMessageCellType_PerMessageExpiration; + self.perMessageExpirationHasExpired = YES; + return; + } + if (transaction.transitional_yapReadTransaction) { NSArray *mediaAttachments = [message mediaAttachmentsWithTransaction:transaction.transitional_yapReadTransaction]; @@ -694,14 +700,14 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) TSAttachment *_Nullable mediaAttachment = mediaAttachments.firstObject; if ([mediaAttachment isKindOfClass:[TSAttachmentPointer class]]) { self.messageCellType = OWSMessageCellType_PerMessageExpiration; - self.perMessageExpirationHasExpired = message.perMessageExpirationHasExpired; + self.perMessageExpirationHasExpired = NO; self.attachmentPointer = (TSAttachmentPointer *)mediaAttachment; return; } else if ([mediaAttachment isKindOfClass:[TSAttachmentStream class]]) { TSAttachmentStream *attachmentStream = (TSAttachmentStream *)mediaAttachment; if (attachmentStream.isValidVisualMedia) { self.messageCellType = OWSMessageCellType_PerMessageExpiration; - self.perMessageExpirationHasExpired = message.perMessageExpirationHasExpired; + self.perMessageExpirationHasExpired = NO; self.attachmentStream = attachmentStream; return; } From 77841451b84bc31ec9fdb6c0bf6e3c21fc00e4e3 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Thu, 13 Jun 2019 17:50:25 -0400 Subject: [PATCH 2/2] Respond to CR. --- .../ConversationView/Cells/OWSBubbleView.m | 2 -- .../ConversationView/Cells/OWSMessageHiddenView.m | 8 ++++---- Signal/translations/en.lproj/Localizable.strings | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m index a5f22436fd..56c0abb0b2 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleView.m @@ -194,8 +194,6 @@ const CGFloat kOWSMessageCellCornerRadius_Small = 4; self.gradientLayer.hidden = fillGradientCGColors.count < 1; // Currently this view only supports linear (or axial) gradients // from the top-left to bottom-right. - // self.gradientLayer.startPoint = CGPointMake(0, 1); - // self.gradientLayer.endPoint = CGPointMake(1, 0); self.gradientLayer.startPoint = CGPointMake(0, 0); self.gradientLayer.endPoint = CGPointMake(1, 1); self.gradientLayer.frame = self.bounds; diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageHiddenView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageHiddenView.m index e0a4042549..ac80c5c5e3 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageHiddenView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageHiddenView.m @@ -333,7 +333,7 @@ NS_ASSUME_NONNULL_BEGIN if (self.isExpired) { self.label.text = NSLocalizedString(@"PER_MESSAGE_EXPIRATION_VIEWED", @"Label for messages with per-message expiration indicating that " - @"user has viewed the message's contents."); + @"the local user has viewed the message's contents."); } else if (self.isFailedDownload) { self.label.text = NSLocalizedString( @"ATTACHMENT_DOWNLOADING_STATUS_FAILED", @"Status label when an attachment download has failed."); @@ -425,7 +425,7 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)isAvailable { - if (self.viewItem.perMessageExpirationHasExpired) { + if (self.isExpired) { return NO; } else if (self.viewItem.attachmentStream) { return YES; @@ -439,7 +439,7 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)isFailedDownload { - if (self.viewItem.perMessageExpirationHasExpired) { + if (self.isExpired) { return NO; } else if (self.viewItem.attachmentStream) { return NO; @@ -453,7 +453,7 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)isOngoingDownload { - if (self.viewItem.perMessageExpirationHasExpired) { + if (self.isExpired) { return NO; } else if (self.viewItem.attachmentStream) { return NO; diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 834ff8ea49..802e61b3a3 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -1640,7 +1640,7 @@ /* Label for messages with per-message expiration indicating that user can tap to view the message's contents. */ "PER_MESSAGE_EXPIRATION_TAP_TO_VIEW" = "Tap to view"; -/* Label for messages with per-message expiration indicating that user has viewed the message's contents. */ +/* Label for messages with per-message expiration indicating that the local user has viewed the message's contents. */ "PER_MESSAGE_EXPIRATION_VIEWED" = "Viewed"; /* A format for a label showing an example phone number. Embeds {{the example phone number}}. */