From 6bab56220c5e632ef71fbfaa3444f75e78dd0402 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Thu, 5 Jul 2018 13:20:37 -0400 Subject: [PATCH 1/4] Tweak home view cells. --- .../ViewControllers/HomeView/HomeViewCell.m | 39 +++++++------------ 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/Signal/src/ViewControllers/HomeView/HomeViewCell.m b/Signal/src/ViewControllers/HomeView/HomeViewCell.m index efc8f5bcf..4d77b7dd0 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewCell.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewCell.m @@ -198,11 +198,11 @@ NS_ASSUME_NONNULL_BEGIN = (overrideDate ? [self stringForDate:overrideDate] : [self stringForDate:thread.lastMessageDate]); if (hasUnreadMessages && overrideSnippet == nil) { - self.dateTimeLabel.textColor = [UIColor ows_blackColor]; - self.dateTimeLabel.font = self.unreadFont.ows_mediumWeight; + self.dateTimeLabel.textColor = [UIColor ows_light90Color]; + self.dateTimeLabel.font = self.dateTimeFont.ows_mediumWeight; } else { - self.dateTimeLabel.textColor = [UIColor lightGrayColor]; - self.dateTimeLabel.font = self.unreadFont; + self.dateTimeLabel.textColor = [UIColor ows_light60Color]; + self.dateTimeLabel.font = self.dateTimeFont; } NSUInteger unreadCount = thread.unreadCount; @@ -303,7 +303,7 @@ NS_ASSUME_NONNULL_BEGIN @"A label for conversations with blocked users.") attributes:@{ NSFontAttributeName : self.snippetFont.ows_mediumWeight, - NSForegroundColorAttributeName : [UIColor ows_blackColor], + NSForegroundColorAttributeName : [UIColor ows_light90Color], }]]; } else { if ([thread isMuted]) { @@ -313,7 +313,7 @@ NS_ASSUME_NONNULL_BEGIN NSFontAttributeName : [UIFont ows_elegantIconsFont:9.f], NSForegroundColorAttributeName : (hasUnreadMessages ? [UIColor colorWithWhite:0.1f alpha:1.f] - : [UIColor lightGrayColor]), + : [UIColor ows_light60Color]), }]]; } NSString *displayableText = thread.lastMessageText; @@ -325,8 +325,8 @@ NS_ASSUME_NONNULL_BEGIN (hasUnreadMessages ? self.snippetFont.ows_mediumWeight : self.snippetFont), NSForegroundColorAttributeName : - (hasUnreadMessages ? [UIColor ows_blackColor] - : [UIColor lightGrayColor]), + (hasUnreadMessages ? [UIColor ows_light90Color] + : [UIColor ows_light60Color]), }]]; } } @@ -353,6 +353,11 @@ NS_ASSUME_NONNULL_BEGIN return [UIFont ows_dynamicTypeCaption1Font].ows_mediumWeight; } +- (UIFont *)dateTimeFont +{ + return [UIFont ows_dynamicTypeCaption1Font]; +} + - (UIFont *)snippetFont { return [UIFont ows_dynamicTypeSubheadlineFont]; @@ -369,22 +374,6 @@ NS_ASSUME_NONNULL_BEGIN return [UIFont ows_dynamicTypeFootnoteFont]; } -// A simple function to scale dimensions to reflect dynamic type. Given a value -// we lerp it larger linearly to reflect size of dynamic type relative to a -// reference value for default dynamic type sizes. -// -// * We _NEVER_ scale values down. -// * We cap scaling. -+ (CGFloat)scaleValueWithDynamicType:(CGFloat)minValue -{ - // The default size of dynamic "body" type. - const NSUInteger kReferenceFontSizeMin = 17.f; - - CGFloat referenceFontSize = UIFont.ows_dynamicTypeBodyFont.pointSize; - CGFloat alpha = CGFloatClamp(referenceFontSize / kReferenceFontSizeMin, 1.f, 1.3f); - return minValue * alpha; -} - - (NSUInteger)avatarSize { return 48.f; @@ -398,7 +387,7 @@ NS_ASSUME_NONNULL_BEGIN // Using an NSUInteger precludes us from negating this value - (CGFloat)topRowHSpacing { - return ceil([HomeViewCell scaleValueWithDynamicType:5]); + return 6.f; } #pragma mark - Reuse From 03d393553ad8b36790e68d7a1de3ebafb7fa58e5 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Thu, 5 Jul 2018 13:54:10 -0400 Subject: [PATCH 2/4] Tweak home view cells. --- .../ViewControllers/HomeView/HomeViewCell.m | 142 +++++++----------- .../ViewModels/ThreadViewModel.swift | 3 + SignalServiceKit/src/Contacts/TSThread.h | 3 + SignalServiceKit/src/Contacts/TSThread.m | 4 +- 4 files changed, 66 insertions(+), 86 deletions(-) diff --git a/Signal/src/ViewControllers/HomeView/HomeViewCell.m b/Signal/src/ViewControllers/HomeView/HomeViewCell.m index 4d77b7dd0..b040cd74f 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewCell.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewCell.m @@ -23,8 +23,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) UILabel *nameLabel; @property (nonatomic) UILabel *snippetLabel; @property (nonatomic) UILabel *dateTimeLabel; -@property (nonatomic) UIView *unreadBadge; -@property (nonatomic) UILabel *unreadLabel; +@property (nonatomic) UIImageView *messageStatusView; @property (nonatomic, nullable) ThreadViewModel *thread; @property (nonatomic, nullable) OWSContactsManager *contactsManager; @@ -83,7 +82,7 @@ NS_ASSUME_NONNULL_BEGIN // Ensure that the cell's contents never overflow the cell bounds. [self.payloadView autoPinEdgeToSuperviewMargin:ALEdgeTop relation:NSLayoutRelationGreaterThanOrEqual]; [self.payloadView autoPinEdgeToSuperviewMargin:ALEdgeBottom relation:NSLayoutRelationGreaterThanOrEqual]; - // We pin the payloadView traillingEdge later, as part of the "Unread Badge" logic. + [self.payloadView autoPinTrailingToSuperviewMargin]; self.nameLabel = [UILabel new]; self.nameLabel.lineBreakMode = NSLineBreakByTruncatingTail; @@ -95,12 +94,18 @@ NS_ASSUME_NONNULL_BEGIN [self.dateTimeLabel setContentHuggingHorizontalHigh]; [self.dateTimeLabel setCompressionResistanceHorizontalHigh]; + self.messageStatusView = [UIImageView new]; + [self.messageStatusView setContentHuggingHorizontalHigh]; + [self.messageStatusView setCompressionResistanceHorizontalHigh]; + self.topRowView = [[UIStackView alloc] initWithArrangedSubviews:@[ self.nameLabel, self.dateTimeLabel, + self.messageStatusView, ]]; self.topRowView.axis = UILayoutConstraintAxisHorizontal; - self.topRowView.alignment = UIStackViewAlignmentLastBaseline; + self.topRowView.alignment = UIStackViewAlignmentCenter; + self.topRowView.spacing = 6.f; [self.payloadView addArrangedSubview:self.topRowView]; self.snippetLabel = [UILabel new]; @@ -111,18 +116,6 @@ NS_ASSUME_NONNULL_BEGIN [self.snippetLabel setContentHuggingHorizontalLow]; [self.snippetLabel setCompressionResistanceHorizontalLow]; - self.unreadLabel = [UILabel new]; - self.unreadLabel.textColor = [UIColor whiteColor]; - self.unreadLabel.lineBreakMode = NSLineBreakByTruncatingTail; - self.unreadLabel.textAlignment = NSTextAlignmentCenter; - - self.unreadBadge = [NeverClearView new]; - self.unreadBadge.backgroundColor = [UIColor ows_materialBlueColor]; - [self.unreadBadge addSubview:self.unreadLabel]; - [self.unreadLabel autoCenterInSuperview]; - [self.unreadLabel setContentHuggingHigh]; - [self.unreadLabel setCompressionResistanceHigh]; - self.payloadView.userInteractionEnabled = NO; } @@ -181,7 +174,6 @@ NS_ASSUME_NONNULL_BEGIN [self updateAvatarView]; self.payloadView.spacing = 0.f; - self.topRowView.spacing = self.topRowHSpacing; // We update the fonts every time this cell is configured to ensure that // changes to the dynamic type settings are reflected. @@ -197,67 +189,60 @@ NS_ASSUME_NONNULL_BEGIN self.dateTimeLabel.text = (overrideDate ? [self stringForDate:overrideDate] : [self stringForDate:thread.lastMessageDate]); + UIColor *textColor = [UIColor ows_light60Color]; if (hasUnreadMessages && overrideSnippet == nil) { - self.dateTimeLabel.textColor = [UIColor ows_light90Color]; + textColor = [UIColor ows_light90Color]; self.dateTimeLabel.font = self.dateTimeFont.ows_mediumWeight; } else { - self.dateTimeLabel.textColor = [UIColor ows_light60Color]; self.dateTimeLabel.font = self.dateTimeFont; } - - NSUInteger unreadCount = thread.unreadCount; - if (unreadCount == 0 || overrideSnippet != nil) { - [self.viewConstraints addObject:[self.payloadView autoPinTrailingToSuperviewMargin]]; + self.dateTimeLabel.textColor = textColor; + + UIImage *_Nullable statusIndicatorImage = nil; + UIColor *messageStatusViewTintColor = textColor; + BOOL shouldAnimateStatusIcon = NO; + if (overrideSnippet == nil && [self.thread.lastMessageForInbox isKindOfClass:[TSOutgoingMessage class]]) { + TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.thread.lastMessageForInbox; + + MessageReceiptStatus messageStatus = + [MessageRecipientStatusUtils recipientStatusWithOutgoingMessage:outgoingMessage]; + switch (messageStatus) { + case MessageReceiptStatusUploading: + case MessageReceiptStatusSending: + statusIndicatorImage = [UIImage imageNamed:@"message_status_sending"]; + shouldAnimateStatusIcon = YES; + break; + case MessageReceiptStatusSent: + case MessageReceiptStatusSkipped: + statusIndicatorImage = [UIImage imageNamed:@"message_status_sent"]; + break; + case MessageReceiptStatusDelivered: + case MessageReceiptStatusRead: + statusIndicatorImage = [UIImage imageNamed:@"message_status_delivered"]; + break; + case MessageReceiptStatusFailed: + // TODO: + statusIndicatorImage = [UIImage imageNamed:@"message_status_sending"]; + break; + } + if (messageStatus == MessageReceiptStatusRead) { + messageStatusViewTintColor = [UIColor ows_signalBlueColor]; + } + } + self.messageStatusView.image = [statusIndicatorImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + self.messageStatusView.tintColor = messageStatusViewTintColor; + self.messageStatusView.hidden = statusIndicatorImage == nil; + if (shouldAnimateStatusIcon) { + CABasicAnimation *animation; + animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; + animation.toValue = @(M_PI * 2.0); + const CGFloat kPeriodSeconds = 1.f; + animation.duration = kPeriodSeconds; + animation.cumulative = YES; + animation.repeatCount = HUGE_VALF; + [self.messageStatusView.layer addAnimation:animation forKey:@"animation"]; } else { - [self.contentView addSubview:self.unreadBadge]; - - self.unreadLabel.text = [OWSFormat formatInt:(int)unreadCount]; - self.unreadLabel.font = self.unreadFont; - const int unreadBadgeHeight = (int)ceil(self.unreadLabel.font.lineHeight * 1.5f); - self.unreadBadge.layer.cornerRadius = unreadBadgeHeight / 2; - - [NSLayoutConstraint autoSetPriority:UILayoutPriorityDefaultHigh - forConstraints:^{ - // This is a bit arbitrary, but it should scale with the size of dynamic text - CGFloat minMargin = CeilEven(unreadBadgeHeight * .5); - - // Spec check. Should be 12pts (6pt on each side) when using default font size. - OWSAssert(UIFont.ows_dynamicTypeBodyFont.pointSize != 17 || minMargin == 12); - - [self.viewConstraints addObjectsFromArray:@[ - [self.unreadBadge autoMatchDimension:ALDimensionWidth - toDimension:ALDimensionWidth - ofView:self.unreadLabel - withOffset:minMargin], - // badge sizing - [self.unreadBadge autoSetDimension:ALDimensionWidth - toSize:unreadBadgeHeight - relation:NSLayoutRelationGreaterThanOrEqual], - [self.unreadBadge autoSetDimension:ALDimensionHeight toSize:unreadBadgeHeight], - ]]; - }]; - - const CGFloat kMinVMargin = 5; - [self.viewConstraints addObjectsFromArray:@[ - // Horizontally, badge is inserted after the tail of the payloadView, pushing back the date *and* snippet - // view - [self.payloadView autoPinEdge:ALEdgeTrailing - toEdge:ALEdgeLeading - ofView:self.unreadBadge - withOffset:-self.topRowHSpacing], - [self.unreadBadge autoPinTrailingToSuperviewMargin], - [self.unreadBadge autoPinEdgeToSuperviewEdge:ALEdgeTop - withInset:kMinVMargin - relation:NSLayoutRelationGreaterThanOrEqual], - [self.unreadBadge autoPinEdgeToSuperviewEdge:ALEdgeBottom - withInset:kMinVMargin - relation:NSLayoutRelationGreaterThanOrEqual], - - // Vertically, badge is positioned vertically by aligning it's label *subview's* baseline. - // This allows us a single visual baseline of text across the top row across [name, dateTime, - // optional(unread count)] - [self.unreadLabel autoAlignAxis:ALAxisBaseline toSameAxisOfView:self.dateTimeLabel] - ]]; + [self.messageStatusView.layer removeAllAnimations]; } } @@ -348,11 +333,6 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Constants -- (UIFont *)unreadFont -{ - return [UIFont ows_dynamicTypeCaption1Font].ows_mediumWeight; -} - - (UIFont *)dateTimeFont { return [UIFont ows_dynamicTypeCaption1Font]; @@ -384,12 +364,6 @@ NS_ASSUME_NONNULL_BEGIN return 12.f; } -// Using an NSUInteger precludes us from negating this value -- (CGFloat)topRowHSpacing -{ - return 6.f; -} - #pragma mark - Reuse - (void)prepareForReuse @@ -403,8 +377,6 @@ NS_ASSUME_NONNULL_BEGIN self.contactsManager = nil; self.avatarView.image = nil; - [self.unreadBadge removeFromSuperview]; - [[NSNotificationCenter defaultCenter] removeObserver:self]; } diff --git a/SignalMessaging/ViewModels/ThreadViewModel.swift b/SignalMessaging/ViewModels/ThreadViewModel.swift index 80267e368..36cbcd0df 100644 --- a/SignalMessaging/ViewModels/ThreadViewModel.swift +++ b/SignalMessaging/ViewModels/ThreadViewModel.swift @@ -14,11 +14,13 @@ public class ThreadViewModel: NSObject { @objc public let contactIdentifier: String? @objc public let name: String @objc public let isMuted: Bool + var isContactThread: Bool { return !isGroupThread } @objc public let lastMessageText: String? + @objc public let lastMessageForInbox: TSInteraction? @objc public init(thread: TSThread, transaction: YapDatabaseReadTransaction) { @@ -28,6 +30,7 @@ public class ThreadViewModel: NSObject { self.name = thread.name() self.isMuted = thread.isMuted self.lastMessageText = thread.lastMessageText(transaction: transaction) + self.lastMessageForInbox = thread.lastInteractionForInbox(transaction: transaction) if let contactThread = thread as? TSContactThread { self.contactIdentifier = contactThread.contactIdentifier() diff --git a/SignalServiceKit/src/Contacts/TSThread.h b/SignalServiceKit/src/Contacts/TSThread.h index e6bf79886..3adefacbf 100644 --- a/SignalServiceKit/src/Contacts/TSThread.h +++ b/SignalServiceKit/src/Contacts/TSThread.h @@ -84,6 +84,9 @@ NS_ASSUME_NONNULL_BEGIN - (NSString *)lastMessageTextWithTransaction:(YapDatabaseReadTransaction *)transaction NS_SWIFT_NAME(lastMessageText(transaction:)); +- (nullable TSInteraction *)lastInteractionForInboxWithTransaction:(YapDatabaseReadTransaction *)transaction + NS_SWIFT_NAME(lastInteractionForInbox(transaction:)); + /** * Updates the thread's caches of the latest interaction. * diff --git a/SignalServiceKit/src/Contacts/TSThread.m b/SignalServiceKit/src/Contacts/TSThread.m index 1c8cf546d..d0ef2ea89 100644 --- a/SignalServiceKit/src/Contacts/TSThread.m +++ b/SignalServiceKit/src/Contacts/TSThread.m @@ -246,8 +246,10 @@ NS_ASSUME_NONNULL_BEGIN OWSAssert([self unseenMessagesWithTransaction:transaction].count < 1); } -- (TSInteraction *)lastInteractionForInboxWithTransaction:(YapDatabaseReadTransaction *)transaction +- (nullable TSInteraction *)lastInteractionForInboxWithTransaction:(YapDatabaseReadTransaction *)transaction { + OWSAssert(transaction); + __block NSUInteger missedCount = 0; __block TSInteraction *last = nil; [[transaction ext:TSMessageDatabaseViewExtensionName] From 159e6d235a7bff0af7164e525f904012f1c3533f Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 9 Jul 2018 11:43:46 -0400 Subject: [PATCH 3/4] Retweak home view unread indicator. --- .../ViewControllers/HomeView/HomeViewCell.m | 194 ++++++++++++------ 1 file changed, 135 insertions(+), 59 deletions(-) diff --git a/Signal/src/ViewControllers/HomeView/HomeViewCell.m b/Signal/src/ViewControllers/HomeView/HomeViewCell.m index b040cd74f..3feeaac6f 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewCell.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewCell.m @@ -18,13 +18,16 @@ NS_ASSUME_NONNULL_BEGIN @interface HomeViewCell () @property (nonatomic) AvatarImageView *avatarView; -@property (nonatomic) UIStackView *payloadView; @property (nonatomic) UIStackView *topRowView; @property (nonatomic) UILabel *nameLabel; @property (nonatomic) UILabel *snippetLabel; @property (nonatomic) UILabel *dateTimeLabel; @property (nonatomic) UIImageView *messageStatusView; +@property (nonatomic) UIView *unreadBadge; +@property (nonatomic) UILabel *unreadLabel; +@property (nonatomic) UIView *unreadBadgeContainer; + @property (nonatomic, nullable) ThreadViewModel *thread; @property (nonatomic, nullable) OWSContactsManager *contactsManager; @@ -74,16 +77,6 @@ NS_ASSUME_NONNULL_BEGIN [self.avatarView autoPinEdgeToSuperviewMargin:ALEdgeTop relation:NSLayoutRelationGreaterThanOrEqual]; [self.avatarView autoPinEdgeToSuperviewMargin:ALEdgeBottom relation:NSLayoutRelationGreaterThanOrEqual]; - self.payloadView = [UIStackView new]; - self.payloadView.axis = UILayoutConstraintAxisVertical; - [self.contentView addSubview:self.payloadView]; - [self.payloadView autoPinLeadingToTrailingEdgeOfView:self.avatarView offset:self.avatarHSpacing]; - [self.payloadView autoVCenterInSuperview]; - // Ensure that the cell's contents never overflow the cell bounds. - [self.payloadView autoPinEdgeToSuperviewMargin:ALEdgeTop relation:NSLayoutRelationGreaterThanOrEqual]; - [self.payloadView autoPinEdgeToSuperviewMargin:ALEdgeBottom relation:NSLayoutRelationGreaterThanOrEqual]; - [self.payloadView autoPinTrailingToSuperviewMargin]; - self.nameLabel = [UILabel new]; self.nameLabel.lineBreakMode = NSLineBreakByTruncatingTail; self.nameLabel.font = self.nameFont; @@ -106,17 +99,57 @@ NS_ASSUME_NONNULL_BEGIN self.topRowView.axis = UILayoutConstraintAxisHorizontal; self.topRowView.alignment = UIStackViewAlignmentCenter; self.topRowView.spacing = 6.f; - [self.payloadView addArrangedSubview:self.topRowView]; self.snippetLabel = [UILabel new]; self.snippetLabel.font = [self snippetFont]; self.snippetLabel.numberOfLines = 1; self.snippetLabel.lineBreakMode = NSLineBreakByTruncatingTail; - [self.payloadView addArrangedSubview:self.snippetLabel]; [self.snippetLabel setContentHuggingHorizontalLow]; [self.snippetLabel setCompressionResistanceHorizontalLow]; - self.payloadView.userInteractionEnabled = NO; + UIStackView *vStackView = [[UIStackView alloc] initWithArrangedSubviews:@[ + self.topRowView, + self.snippetLabel, + ]]; + vStackView.axis = UILayoutConstraintAxisVertical; + + self.unreadLabel = [UILabel new]; + self.unreadLabel.textColor = [UIColor ows_whiteColor]; + self.unreadLabel.lineBreakMode = NSLineBreakByTruncatingTail; + self.unreadLabel.textAlignment = NSTextAlignmentCenter; + [self.unreadLabel setContentHuggingHigh]; + [self.unreadLabel setCompressionResistanceHigh]; + + self.unreadBadge = [NeverClearView new]; + self.unreadBadge.backgroundColor = [UIColor ows_materialBlueColor]; + [self.unreadBadge addSubview:self.unreadLabel]; + [self.unreadLabel autoCenterInSuperview]; + [self.unreadBadge setContentHuggingHigh]; + [self.unreadBadge setCompressionResistanceHigh]; + + self.unreadBadgeContainer = [UIView containerView]; + [self.unreadBadgeContainer addSubview:self.unreadBadge]; + [self.unreadBadge autoPinWidthToSuperview]; + [self.unreadBadgeContainer setContentHuggingHigh]; + [self.unreadBadgeContainer setCompressionResistanceHigh]; + + UIStackView *hStackView = [[UIStackView alloc] initWithArrangedSubviews:@[ + vStackView, + self.unreadBadgeContainer, + ]]; + hStackView.axis = UILayoutConstraintAxisHorizontal; + hStackView.spacing = 6.f; + [self.contentView addSubview:hStackView]; + [hStackView autoPinLeadingToTrailingEdgeOfView:self.avatarView offset:self.avatarHSpacing]; + [hStackView autoVCenterInSuperview]; + // Ensure that the cell's contents never overflow the cell bounds. + [hStackView autoPinEdgeToSuperviewMargin:ALEdgeTop relation:NSLayoutRelationGreaterThanOrEqual]; + [hStackView autoPinEdgeToSuperviewMargin:ALEdgeBottom relation:NSLayoutRelationGreaterThanOrEqual]; + [hStackView autoPinTrailingToSuperviewMargin]; + + [self.unreadBadge autoAlignAxis:ALAxisHorizontal toSameAxisOfView:self.nameLabel]; + + hStackView.userInteractionEnabled = NO; } - (void)dealloc @@ -173,8 +206,6 @@ NS_ASSUME_NONNULL_BEGIN [self updateNameLabel]; [self updateAvatarView]; - self.payloadView.spacing = 0.f; - // We update the fonts every time this cell is configured to ensure that // changes to the dynamic type settings are reflected. self.snippetLabel.font = [self snippetFont]; @@ -198,52 +229,92 @@ NS_ASSUME_NONNULL_BEGIN } self.dateTimeLabel.textColor = textColor; - UIImage *_Nullable statusIndicatorImage = nil; - UIColor *messageStatusViewTintColor = textColor; - BOOL shouldAnimateStatusIcon = NO; - if (overrideSnippet == nil && [self.thread.lastMessageForInbox isKindOfClass:[TSOutgoingMessage class]]) { - TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.thread.lastMessageForInbox; - - MessageReceiptStatus messageStatus = - [MessageRecipientStatusUtils recipientStatusWithOutgoingMessage:outgoingMessage]; - switch (messageStatus) { - case MessageReceiptStatusUploading: - case MessageReceiptStatusSending: - statusIndicatorImage = [UIImage imageNamed:@"message_status_sending"]; - shouldAnimateStatusIcon = YES; - break; - case MessageReceiptStatusSent: - case MessageReceiptStatusSkipped: - statusIndicatorImage = [UIImage imageNamed:@"message_status_sent"]; - break; - case MessageReceiptStatusDelivered: - case MessageReceiptStatusRead: - statusIndicatorImage = [UIImage imageNamed:@"message_status_delivered"]; - break; - case MessageReceiptStatusFailed: - // TODO: - statusIndicatorImage = [UIImage imageNamed:@"message_status_sending"]; - break; + NSUInteger unreadCount = thread.unreadCount; + if (overrideSnippet) { + // If we're using the home view cell to render search results, + // don't show "unread badge" or "message status" indicator. + self.unreadBadgeContainer.hidden = YES; + self.messageStatusView.hidden = YES; + } else if (unreadCount > 0) { + // If there are unread messages, show the "unread badge." + // The "message status" indicators is redundant. + self.unreadBadgeContainer.hidden = NO; + self.messageStatusView.hidden = YES; + + self.unreadLabel.text = [OWSFormat formatInt:(int)unreadCount]; + self.unreadLabel.font = self.unreadFont; + const int unreadBadgeHeight = (int)ceil(self.unreadLabel.font.lineHeight * 1.5f); + self.unreadBadge.layer.cornerRadius = unreadBadgeHeight / 2; + + [NSLayoutConstraint autoSetPriority:UILayoutPriorityDefaultHigh + forConstraints:^{ + // This is a bit arbitrary, but it should scale with the size of dynamic text + CGFloat minMargin = CeilEven(unreadBadgeHeight * .5); + + // Spec check. Should be 12pts (6pt on each side) when using default font size. + OWSAssert(UIFont.ows_dynamicTypeBodyFont.pointSize != 17 || minMargin == 12); + + [self.viewConstraints addObjectsFromArray:@[ + [self.unreadBadge autoMatchDimension:ALDimensionWidth + toDimension:ALDimensionWidth + ofView:self.unreadLabel + withOffset:minMargin], + // badge sizing + [self.unreadBadge autoSetDimension:ALDimensionWidth + toSize:unreadBadgeHeight + relation:NSLayoutRelationGreaterThanOrEqual], + [self.unreadBadge autoSetDimension:ALDimensionHeight toSize:unreadBadgeHeight], + ]]; + }]; + } else { + UIImage *_Nullable statusIndicatorImage = nil; + UIColor *messageStatusViewTintColor = textColor; + BOOL shouldAnimateStatusIcon = NO; + if (overrideSnippet == nil && [self.thread.lastMessageForInbox isKindOfClass:[TSOutgoingMessage class]]) { + TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.thread.lastMessageForInbox; + + MessageReceiptStatus messageStatus = + [MessageRecipientStatusUtils recipientStatusWithOutgoingMessage:outgoingMessage]; + switch (messageStatus) { + case MessageReceiptStatusUploading: + case MessageReceiptStatusSending: + statusIndicatorImage = [UIImage imageNamed:@"message_status_sending"]; + shouldAnimateStatusIcon = YES; + break; + case MessageReceiptStatusSent: + case MessageReceiptStatusSkipped: + statusIndicatorImage = [UIImage imageNamed:@"message_status_sent"]; + break; + case MessageReceiptStatusDelivered: + case MessageReceiptStatusRead: + statusIndicatorImage = [UIImage imageNamed:@"message_status_delivered"]; + break; + case MessageReceiptStatusFailed: + // TODO: + statusIndicatorImage = [UIImage imageNamed:@"message_status_sending"]; + break; + } + if (messageStatus == MessageReceiptStatusRead) { + messageStatusViewTintColor = [UIColor ows_signalBlueColor]; + } } - if (messageStatus == MessageReceiptStatusRead) { - messageStatusViewTintColor = [UIColor ows_signalBlueColor]; + self.messageStatusView.image = [statusIndicatorImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + self.messageStatusView.tintColor = messageStatusViewTintColor; + self.messageStatusView.hidden = statusIndicatorImage == nil; + self.unreadBadgeContainer.hidden = YES; + if (shouldAnimateStatusIcon) { + CABasicAnimation *animation; + animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; + animation.toValue = @(M_PI * 2.0); + const CGFloat kPeriodSeconds = 1.f; + animation.duration = kPeriodSeconds; + animation.cumulative = YES; + animation.repeatCount = HUGE_VALF; + [self.messageStatusView.layer addAnimation:animation forKey:@"animation"]; + } else { + [self.messageStatusView.layer removeAllAnimations]; } } - self.messageStatusView.image = [statusIndicatorImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - self.messageStatusView.tintColor = messageStatusViewTintColor; - self.messageStatusView.hidden = statusIndicatorImage == nil; - if (shouldAnimateStatusIcon) { - CABasicAnimation *animation; - animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; - animation.toValue = @(M_PI * 2.0); - const CGFloat kPeriodSeconds = 1.f; - animation.duration = kPeriodSeconds; - animation.cumulative = YES; - animation.repeatCount = HUGE_VALF; - [self.messageStatusView.layer addAnimation:animation forKey:@"animation"]; - } else { - [self.messageStatusView.layer removeAllAnimations]; - } } - (void)updateAvatarView @@ -333,6 +404,11 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Constants +- (UIFont *)unreadFont +{ + return [UIFont ows_dynamicTypeCaption1Font].ows_mediumWeight; +} + - (UIFont *)dateTimeFont { return [UIFont ows_dynamicTypeCaption1Font]; From aac805a435063241ae0256682c2d60844c6f2d7f Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 9 Jul 2018 17:21:46 -0400 Subject: [PATCH 4/4] Respond to CR. --- Signal/src/ViewControllers/HomeView/HomeViewCell.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Signal/src/ViewControllers/HomeView/HomeViewCell.m b/Signal/src/ViewControllers/HomeView/HomeViewCell.m index 3feeaac6f..ffa39e5b1 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewCell.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewCell.m @@ -97,7 +97,7 @@ NS_ASSUME_NONNULL_BEGIN self.messageStatusView, ]]; self.topRowView.axis = UILayoutConstraintAxisHorizontal; - self.topRowView.alignment = UIStackViewAlignmentCenter; + self.topRowView.alignment = UIStackViewAlignmentLastBaseline; self.topRowView.spacing = 6.f; self.snippetLabel = [UILabel new]; @@ -270,7 +270,7 @@ NS_ASSUME_NONNULL_BEGIN UIImage *_Nullable statusIndicatorImage = nil; UIColor *messageStatusViewTintColor = textColor; BOOL shouldAnimateStatusIcon = NO; - if (overrideSnippet == nil && [self.thread.lastMessageForInbox isKindOfClass:[TSOutgoingMessage class]]) { + if ([self.thread.lastMessageForInbox isKindOfClass:[TSOutgoingMessage class]]) { TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.thread.lastMessageForInbox; MessageReceiptStatus messageStatus =