|
|
|
@ -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];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|