Merge branch 'charlesmchen/tweakHomeView'

pull/1/head
Matthew Chen 7 years ago
commit a04065a52c

@ -18,13 +18,15 @@ 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;
@ -75,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];
// We pin the payloadView traillingEdge later, as part of the "Unread Badge" logic.
self.nameLabel = [UILabel new];
self.nameLabel.lineBreakMode = NSLineBreakByTruncatingTail;
self.nameLabel.font = self.nameFont;
@ -95,35 +87,69 @@ 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.payloadView addArrangedSubview:self.topRowView];
self.topRowView.spacing = 6.f;
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];
UIStackView *vStackView = [[UIStackView alloc] initWithArrangedSubviews:@[
self.topRowView,
self.snippetLabel,
]];
vStackView.axis = UILayoutConstraintAxisVertical;
self.unreadLabel = [UILabel new];
self.unreadLabel.textColor = [UIColor whiteColor];
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.unreadLabel setContentHuggingHigh];
[self.unreadLabel setCompressionResistanceHigh];
[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];
self.payloadView.userInteractionEnabled = NO;
hStackView.userInteractionEnabled = NO;
}
- (void)dealloc
@ -180,9 +206,6 @@ NS_ASSUME_NONNULL_BEGIN
[self updateNameLabel];
[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.
self.snippetLabel.font = [self snippetFont];
@ -197,19 +220,26 @@ 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_blackColor];
self.dateTimeLabel.font = self.unreadFont.ows_mediumWeight;
textColor = [UIColor ows_light90Color];
self.dateTimeLabel.font = self.dateTimeFont.ows_mediumWeight;
} else {
self.dateTimeLabel.textColor = [UIColor lightGrayColor];
self.dateTimeLabel.font = self.unreadFont;
self.dateTimeLabel.font = self.dateTimeFont;
}
self.dateTimeLabel.textColor = textColor;
NSUInteger unreadCount = thread.unreadCount;
if (unreadCount == 0 || overrideSnippet != nil) {
[self.viewConstraints addObject:[self.payloadView autoPinTrailingToSuperviewMargin]];
} else {
[self.contentView addSubview:self.unreadBadge];
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;
@ -236,28 +266,54 @@ NS_ASSUME_NONNULL_BEGIN
[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]
]];
} else {
UIImage *_Nullable statusIndicatorImage = nil;
UIColor *messageStatusViewTintColor = textColor;
BOOL shouldAnimateStatusIcon = NO;
if ([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;
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];
}
}
}
@ -303,7 +359,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 +369,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 +381,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 +409,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 +430,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;
@ -395,12 +440,6 @@ NS_ASSUME_NONNULL_BEGIN
return 12.f;
}
// Using an NSUInteger precludes us from negating this value
- (CGFloat)topRowHSpacing
{
return ceil([HomeViewCell scaleValueWithDynamicType:5]);
}
#pragma mark - Reuse
- (void)prepareForReuse
@ -414,8 +453,6 @@ NS_ASSUME_NONNULL_BEGIN
self.contactsManager = nil;
self.avatarView.image = nil;
[self.unreadBadge removeFromSuperview];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

@ -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()

@ -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.
*

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

Loading…
Cancel
Save