From 03d393553ad8b36790e68d7a1de3ebafb7fa58e5 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Thu, 5 Jul 2018 13:54:10 -0400 Subject: [PATCH] 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]