Introduce message cell footer view.

This commit is contained in:
Matthew Chen 2018-06-25 16:20:28 -04:00
parent 538cd4f893
commit cbacda87ca
9 changed files with 213 additions and 234 deletions

View File

@ -203,7 +203,6 @@
34D1F0881F8678AA0066283D /* ConversationViewLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0721F8678AA0066283D /* ConversationViewLayout.m */; };
34D1F0A91F867BFC0066283D /* ConversationViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0971F867BFC0066283D /* ConversationViewCell.m */; };
34D1F0AB1F867BFC0066283D /* OWSContactOffersCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F09B1F867BFC0066283D /* OWSContactOffersCell.m */; };
34D1F0AC1F867BFC0066283D /* OWSExpirationTimerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F09E1F867BFC0066283D /* OWSExpirationTimerView.m */; };
34D1F0AE1F867BFC0066283D /* OWSMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0A21F867BFC0066283D /* OWSMessageCell.m */; };
34D1F0B01F867BFC0066283D /* OWSSystemMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0A61F867BFC0066283D /* OWSSystemMessageCell.m */; };
34D1F0B11F867BFC0066283D /* OWSUnreadIndicatorCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0A81F867BFC0066283D /* OWSUnreadIndicatorCell.m */; };
@ -225,6 +224,7 @@
34D8C0281ED3673300188D7C /* DebugUITableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C0261ED3673300188D7C /* DebugUITableViewController.m */; };
34D8C02B1ED3685800188D7C /* DebugUIContacts.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C02A1ED3685800188D7C /* DebugUIContacts.m */; };
34D920E220DD39EA00D51158 /* ConversationStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D920E120DD39E900D51158 /* ConversationStyle.swift */; };
34D920E720E179C200D51158 /* OWSMessageFooterView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D920E620E179C200D51158 /* OWSMessageFooterView.m */; };
34D99C931F2937CC00D284D6 /* OWSAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D99C911F2937CC00D284D6 /* OWSAnalytics.swift */; };
34DB0BED2011548B007B313F /* OWSDatabaseConverterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 34DB0BEC2011548B007B313F /* OWSDatabaseConverterTest.m */; };
34DBF003206BD5A500025978 /* OWSMessageTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34DBEFFF206BD5A400025978 /* OWSMessageTextView.m */; };
@ -843,9 +843,6 @@
34D1F0971F867BFC0066283D /* ConversationViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ConversationViewCell.m; sourceTree = "<group>"; };
34D1F09A1F867BFC0066283D /* OWSContactOffersCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactOffersCell.h; sourceTree = "<group>"; };
34D1F09B1F867BFC0066283D /* OWSContactOffersCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactOffersCell.m; sourceTree = "<group>"; };
34D1F09C1F867BFC0066283D /* OWSExpirableMessageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSExpirableMessageView.h; sourceTree = "<group>"; };
34D1F09D1F867BFC0066283D /* OWSExpirationTimerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSExpirationTimerView.h; sourceTree = "<group>"; };
34D1F09E1F867BFC0066283D /* OWSExpirationTimerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSExpirationTimerView.m; sourceTree = "<group>"; };
34D1F0A11F867BFC0066283D /* OWSMessageCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageCell.h; sourceTree = "<group>"; };
34D1F0A21F867BFC0066283D /* OWSMessageCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessageCell.m; sourceTree = "<group>"; };
34D1F0A51F867BFC0066283D /* OWSSystemMessageCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSSystemMessageCell.h; sourceTree = "<group>"; };
@ -883,6 +880,8 @@
34D8C02A1ED3685800188D7C /* DebugUIContacts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIContacts.m; sourceTree = "<group>"; };
34D913491F62D4A500722898 /* SignalAttachment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalAttachment.swift; sourceTree = "<group>"; };
34D920E120DD39E900D51158 /* ConversationStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationStyle.swift; sourceTree = "<group>"; };
34D920E520E179C100D51158 /* OWSMessageFooterView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageFooterView.h; sourceTree = "<group>"; };
34D920E620E179C200D51158 /* OWSMessageFooterView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessageFooterView.m; sourceTree = "<group>"; };
34D99C8A1F27B13B00D284D6 /* OWSViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSViewController.h; sourceTree = "<group>"; };
34D99C8B1F27B13B00D284D6 /* OWSViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSViewController.m; sourceTree = "<group>"; };
34D99C911F2937CC00D284D6 /* OWSAnalytics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSAnalytics.swift; sourceTree = "<group>"; };
@ -1751,15 +1750,14 @@
34D1F09B1F867BFC0066283D /* OWSContactOffersCell.m */,
34CA63192097806E00E526A0 /* OWSContactShareView.h */,
34CA631A2097806E00E526A0 /* OWSContactShareView.m */,
34D1F09C1F867BFC0066283D /* OWSExpirableMessageView.h */,
34D1F09D1F867BFC0066283D /* OWSExpirationTimerView.h */,
34D1F09E1F867BFC0066283D /* OWSExpirationTimerView.m */,
34D1F0B51F87F8850066283D /* OWSGenericAttachmentView.h */,
34D1F0B61F87F8850066283D /* OWSGenericAttachmentView.m */,
3496744B2076768600080B5F /* OWSMessageBubbleView.h */,
3496744C2076768700080B5F /* OWSMessageBubbleView.m */,
34D1F0A11F867BFC0066283D /* OWSMessageCell.h */,
34D1F0A21F867BFC0066283D /* OWSMessageCell.m */,
34D920E520E179C100D51158 /* OWSMessageFooterView.h */,
34D920E620E179C200D51158 /* OWSMessageFooterView.m */,
34DBF000206BD5A400025978 /* OWSMessageTextView.h */,
34DBEFFF206BD5A400025978 /* OWSMessageTextView.m */,
34277A5D20751BDC006049F2 /* OWSQuotedMessageView.h */,
@ -3232,7 +3230,6 @@
3496744F2076ACD000080B5F /* LongTextViewController.swift in Sources */,
34FD93701E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m in Sources */,
34B3F8931E8DF1710035BE1A /* SignalsNavigationController.m in Sources */,
34D1F0AC1F867BFC0066283D /* OWSExpirationTimerView.m in Sources */,
76EB063A18170B33006006FC /* FunctionalUtil.m in Sources */,
34F308A21ECB469700BB7697 /* OWSBezierPathView.m in Sources */,
45B27B862037FFB400A539DF /* DebugUIFileBrowser.swift in Sources */,
@ -3271,6 +3268,7 @@
4542DF54208D40AC007B4E76 /* LoadingViewController.swift in Sources */,
34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */,
34D1F0B71F87F8850066283D /* OWSGenericAttachmentView.m in Sources */,
34D920E720E179C200D51158 /* OWSMessageFooterView.m in Sources */,
348BB25D20A0C5530047AEC2 /* ContactShareViewHelper.swift in Sources */,
34B3F8801E8DF1700035BE1A /* InviteFlow.swift in Sources */,
457C87B82032645C008D52D6 /* DebugUINotifications.swift in Sources */,

View File

@ -1,5 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
// TODO:

View File

@ -1,25 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
extern const CGFloat kExpirationTimerViewSize;
@interface OWSExpirationTimerView : UIView
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
- (instancetype)initWithExpiration:(uint64_t)expirationTimestamp
initialDurationSeconds:(uint32_t)initialDurationSeconds NS_DESIGNATED_INITIALIZER;
- (void)ensureAnimations;
- (void)clearAnimations;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,192 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSExpirationTimerView.h"
#import "ConversationViewController.h"
#import "NSDate+OWS.h"
#import "OWSMath.h"
#import "UIColor+OWS.h"
#import "UIView+OWS.h"
#import <QuartzCore/QuartzCore.h>
#import <SignalServiceKit/NSTimer+OWS.h>
NS_ASSUME_NONNULL_BEGIN
const CGFloat kExpirationTimerViewSize = 16.f;
@interface OWSExpirationTimerView ()
@property (nonatomic) uint32_t initialDurationSeconds;
@property (nonatomic) uint64_t expirationTimestamp;
@property (nonatomic, readonly) UIImageView *emptyHourglassImageView;
@property (nonatomic, readonly) UIImageView *fullHourglassImageView;
@property (nonatomic, nullable) CAGradientLayer *maskLayer;
@property (nonatomic, nullable) NSTimer *animationTimer;
@end
#pragma mark -
@implementation OWSExpirationTimerView
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (instancetype)initWithExpiration:(uint64_t)expirationTimestamp initialDurationSeconds:(uint32_t)initialDurationSeconds
{
self = [super initWithFrame:CGRectZero];
if (!self) {
return self;
}
self.expirationTimestamp = expirationTimestamp;
self.initialDurationSeconds = initialDurationSeconds;
[self commonInit];
return self;
}
- (void)commonInit
{
self.clipsToBounds = YES;
UIImage *hourglassEmptyImage = [[UIImage imageNamed:@"ic_hourglass_empty"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
UIImage *hourglassFullImage = [[UIImage imageNamed:@"ic_hourglass_full"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
_emptyHourglassImageView = [[UIImageView alloc] initWithImage:hourglassEmptyImage];
self.emptyHourglassImageView.tintColor = [UIColor lightGrayColor];
[self addSubview:self.emptyHourglassImageView];
_fullHourglassImageView = [[UIImageView alloc] initWithImage:hourglassFullImage];
self.fullHourglassImageView.tintColor = [UIColor lightGrayColor];
[self addSubview:self.fullHourglassImageView];
[self.emptyHourglassImageView autoPinHeightToSuperviewWithMargin:2.f];
[self.emptyHourglassImageView autoHCenterInSuperview];
[self.emptyHourglassImageView autoPinToSquareAspectRatio];
[self.fullHourglassImageView autoPinHeightToSuperviewWithMargin:2.f];
[self.fullHourglassImageView autoHCenterInSuperview];
[self.fullHourglassImageView autoPinToSquareAspectRatio];
[self autoSetDimension:ALDimensionWidth toSize:kExpirationTimerViewSize];
[self autoSetDimension:ALDimensionHeight toSize:kExpirationTimerViewSize];
}
- (void)clearAnimations
{
[self.layer removeAllAnimations];
[self.maskLayer removeAllAnimations];
[self.maskLayer removeFromSuperlayer];
self.maskLayer = nil;
[self.fullHourglassImageView.layer.mask removeFromSuperlayer];
self.fullHourglassImageView.layer.mask = nil;
self.layer.opacity = 1.f;
self.emptyHourglassImageView.hidden = YES;
self.fullHourglassImageView.hidden = YES;
[self.animationTimer invalidate];
self.animationTimer = nil;
}
- (void)setFrame:(CGRect)frame {
BOOL sizeDidChange = CGSizeEqualToSize(self.frame.size, frame.size);
[super setFrame:frame];
if (sizeDidChange) {
[self ensureAnimations];
}
}
- (void)setBounds:(CGRect)bounds {
BOOL sizeDidChange = CGSizeEqualToSize(self.bounds.size, bounds.size);
[super setBounds:bounds];
if (sizeDidChange) {
[self ensureAnimations];
}
}
- (void)ensureAnimations
{
OWSAssertIsOnMainThread();
CGFloat secondsLeft = MAX(0, (self.expirationTimestamp - [NSDate ows_millisecondTimeStamp]) / 1000.f);
[self clearAnimations];
const NSTimeInterval kBlinkAnimationDurationSeconds = 2;
if (self.expirationTimestamp == 0) {
// If message hasn't started expiring yet, just show the full hourglass.
self.fullHourglassImageView.hidden = NO;
return;
} else if (secondsLeft <= kBlinkAnimationDurationSeconds + 0.1f) {
// If message has expired, just show the blinking empty hourglass.
self.emptyHourglassImageView.hidden = NO;
// Flashing animation.
[UIView animateWithDuration:0.5f
delay:0.f
options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat
animations:^{
self.layer.opacity = 0.f;
}
completion:^(BOOL finished) {
self.layer.opacity = 1.f;
}];
return;
}
self.emptyHourglassImageView.hidden = NO;
self.fullHourglassImageView.hidden = NO;
CAGradientLayer *maskLayer = [CAGradientLayer new];
maskLayer.anchorPoint = CGPointZero;
maskLayer.frame = self.fullHourglassImageView.bounds;
self.maskLayer = maskLayer;
self.fullHourglassImageView.layer.mask = maskLayer;
// Blur the top of the mask a bit with gradient
maskLayer.colors = @[ (id)[UIColor clearColor].CGColor, (id)[UIColor blackColor].CGColor ];
maskLayer.startPoint = CGPointMake(0.5f, 0.f);
// Use a mask that is 20% tall to soften the edge of the animation.
const CGFloat kMaskEdgeFraction = 0.2f;
maskLayer.endPoint = CGPointMake(0.5f, kMaskEdgeFraction);
NSTimeInterval timeUntilFlashing = MAX(0, secondsLeft - kBlinkAnimationDurationSeconds);
if (self.initialDurationSeconds == 0) {
OWSFail(@"initialDurationSeconds was unexpectedly 0");
return;
}
CGFloat ratioRemaining = (CGFloat)secondsLeft / (CGFloat)self.initialDurationSeconds;
CGFloat ratioComplete = CGFloatClamp((CGFloat)1.0 - ratioRemaining, 0, 1.0);
CGPoint startPosition = CGPointMake(0, self.fullHourglassImageView.height * ratioComplete);
// We offset the bottom slightly to make sure the duration of the perceived animation is correct.
// We're accounting for:
// - the bottom pixel of the two images is the outline of the hourglass. Because the outline is identical in the full vs empty hourglass this wouldn't be perceptible.
// - the top pixel is not visible due to our softening gradient layer.
CGPoint endPosition = CGPointMake(0, self.fullHourglassImageView.height - 2);
maskLayer.position = startPosition;
[CATransaction begin];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
animation.duration = timeUntilFlashing;
animation.fromValue = [NSValue valueWithCGPoint:startPosition];
animation.toValue = [NSValue valueWithCGPoint:endPosition];
[maskLayer addAnimation:animation forKey:@"slideAnimation"];
maskLayer.position = endPosition; // don't snap back
[CATransaction commit];
self.animationTimer = [NSTimer weakScheduledTimerWithTimeInterval:timeUntilFlashing
target:self
selector:@selector(ensureAnimations)
userInfo:nil
repeats:NO];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -10,6 +10,7 @@
#import "OWSBubbleView.h"
#import "OWSContactShareView.h"
#import "OWSGenericAttachmentView.h"
#import "OWSMessageFooterView.h"
#import "OWSMessageTextView.h"
#import "OWSQuotedMessageView.h"
#import "Signal-Swift.h"
@ -36,6 +37,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, nullable) NSMutableArray<NSLayoutConstraint *> *viewConstraints;
@property (nonatomic) OWSMessageFooterView *footerView;
@end
@implementation OWSMessageBubbleView
@ -73,6 +76,8 @@ NS_ASSUME_NONNULL_BEGIN
self.bodyTextView.dataDetectorTypes
= (UIDataDetectorTypeLink | UIDataDetectorTypeAddress | UIDataDetectorTypeCalendarEvent);
self.bodyTextView.hidden = YES;
self.footerView = [OWSMessageFooterView new];
}
- (OWSMessageTextView *)newTextView
@ -436,6 +441,20 @@ NS_ASSUME_NONNULL_BEGIN
bottomMargin = textInsets.bottom;
}
OWSMessageFooterView *footerView = self.footerView;
[footerView configureWithConversationViewItem:self.viewItem];
if (self.footerView) {
[self.bubbleView addSubview:self.footerView];
[self.viewConstraints addObjectsFromArray:@[
[tapForMoreLabel autoPinLeadingToSuperviewMarginWithInset:textInsets.leading],
[tapForMoreLabel autoPinTrailingToSuperviewMarginWithInset:textInsets.trailing],
[tapForMoreLabel autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:lastSubview],
[tapForMoreLabel autoSetDimension:ALDimensionHeight toSize:self.tapForMoreHeight],
]];
lastSubview = tapForMoreLabel;
bottomMargin = textInsets.bottom;
}
OWSAssert(lastSubview);
[self.viewConstraints addObjectsFromArray:@[
[lastSubview autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:bottomMargin],
@ -1007,11 +1026,23 @@ NS_ASSUME_NONNULL_BEGIN
cellSize.height += self.tapForMoreHeight;
}
if (self.hasFooter) {
CGSize footerSize = [self.footerView measureWithConversationViewItem:self.viewItem];
cellSize.width = MAX(cellSize.width, footerSize.width);
cellSize.height += self.footerVSpacing + footerSize.height;
}
cellSize = CGSizeCeil(cellSize);
return cellSize;
}
- (BOOL)hasFooter
{
// TODO:
return YES;
}
- (UIFont *)tapForMoreFont
{
return UIFont.ows_dynamicTypeCaption1Font;
@ -1022,6 +1053,11 @@ NS_ASSUME_NONNULL_BEGIN
return (CGFloat)ceil([self tapForMoreFont].lineHeight * 1.25);
}
- (CGFloat)footerVSpacing
{
return 10.f;
}
#pragma mark -
- (UIColor *)bodyTextColor
@ -1082,6 +1118,8 @@ NS_ASSUME_NONNULL_BEGIN
[self.quotedMessageView removeFromSuperview];
self.quotedMessageView = nil;
[self.footerView removeFromSuperview];
}
#pragma mark - Gestures

View File

@ -4,7 +4,6 @@
#import "OWSMessageCell.h"
#import "OWSContactAvatarBuilder.h"
#import "OWSExpirationTimerView.h"
#import "OWSMessageBubbleView.h"
#import "Signal-Swift.h"
@ -24,10 +23,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic) OWSMessageBubbleView *messageBubbleView;
@property (nonatomic) UILabel *dateHeaderLabel;
@property (nonatomic) UIView *footerView;
@property (nonatomic) AvatarImageView *avatarView;
@property (nonatomic) UILabel *footerLabel;
@property (nonatomic, nullable) OWSExpirationTimerView *expirationTimerView;
@property (nonatomic) OWSMessageFooterView *footerView2;
@property (nonatomic, nullable) NSMutableArray<NSLayoutConstraint *> *viewConstraints;
@property (nonatomic) BOOL isPresentingMenuController;

View File

@ -0,0 +1,17 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
@class ConversationViewItem;
NS_ASSUME_NONNULL_BEGIN
@interface OWSMessageFooterView : UIView
- (void)configureWithConversationViewItem:(ConversationViewItem *)viewItem;
- (CGSize)measureWithConversationViewItem:(ConversationViewItem *)viewItem;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,150 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSMessageFooterView.h"
#import "DateUtil.h"
#import "OWSExpirationTimerView.h"
#import "Signal-Swift.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWSMessageFooterView ()
@property (nonatomic) UILabel *timestampLabel;
@property (nonatomic) UILabel *statusLabel;
@property (nonatomic) UIView *statusIndicatorView;
@end
@implementation OWSMessageFooterView
// `[UIView init]` invokes `[self initWithFrame:...]`.
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self commontInit];
}
return self;
}
- (void)commontInit
{
// Ensure only called once.
OWSAssert(!self.timestampLabel);
self.layoutMargins = UIEdgeInsetsZero;
self.timestampLabel = [UILabel new];
// TODO: Color
self.timestampLabel.textColor = [UIColor lightGrayColor];
self.statusLabel = [UILabel new];
// TODO: Color
self.statusLabel.textColor = [UIColor lightGrayColor];
self.statusIndicatorView = [UIView new];
[self.statusIndicatorView autoSetDimension:ALDimensionWidth toSize:self.statusIndicatorSize];
[self.statusIndicatorView autoSetDimension:ALDimensionHeight toSize:self.statusIndicatorSize];
self.statusIndicatorView.layer.cornerRadius = self.statusIndicatorSize * 0.5f;
// TODO: Review constant with Myles.0
UIStackView *statusStackView = [[UIStackView alloc] initWithArrangedSubviews:@[
self.statusLabel,
self.statusIndicatorView,
]];
statusStackView.axis = UILayoutConstraintAxisHorizontal;
statusStackView.spacing = self.hSpacing;
[self.timestampLabel autoPinEdgeToSuperviewEdge:ALEdgeLeading];
[statusStackView autoPinEdgeToSuperviewEdge:ALEdgeTrailing];
[self.timestampLabel autoVCenterInSuperview];
[statusStackView autoVCenterInSuperview];
[self.timestampLabel autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:0 relation:NSLayoutRelationGreaterThanOrEqual];
[self.timestampLabel autoPinEdgeToSuperviewEdge:ALEdgeBottom
withInset:0
relation:NSLayoutRelationGreaterThanOrEqual];
[statusStackView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:0 relation:NSLayoutRelationGreaterThanOrEqual];
[statusStackView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:0 relation:NSLayoutRelationGreaterThanOrEqual];
[statusStackView autoPinEdge:ALEdgeLeading
toEdge:ALEdgeTrailing
ofView:self.timestampLabel
withOffset:self.hSpacing
relation:NSLayoutRelationGreaterThanOrEqual];
}
- (void)configureFonts
{
self.timestampLabel.font = UIFont.ows_dynamicTypeCaption2Font;
self.statusLabel.font = UIFont.ows_dynamicTypeCaption2Font;
}
- (CGFloat)statusIndicatorSize
{
// TODO: Review constant.
return 20.f;
}
- (CGFloat)hSpacing
{
// TODO: Review constant.
return 10.f;
}
#pragma mark - Load
- (void)configureWithConversationViewItem:(ConversationViewItem *)viewItem
{
OWSAssert(viewItem);
[self configureLabelsWithConversationViewItem:viewItem];
;
// TODO:
self.statusIndicatorView.backgroundColor = [UIColor ows_materialBlueColor];
}
- (void)configureLabelsWithConversationViewItem:(ConversationViewItem *)viewItem
{
OWSAssert(viewItem);
[self configureFonts];
// TODO: Correct text.
self.timestampLabel.text =
[DateUtil formatPastTimestampRelativeToNow:viewItem.interaction.timestamp isRTL:CurrentAppContext().isRTL];
self.statusLabel.text = [self messageStatusTextForConversationViewItem:viewItem];
}
- (CGSize)measureWithConversationViewItem:(ConversationViewItem *)viewItem
{
OWSAssert(viewItem);
[self configureLabelsWithConversationViewItem:viewItem];
;
CGSize result = CGSizeZero;
result.height
= MAX(self.timestampLabel.font.lineHeight, MAX(self.statusLabel.font.lineHeight, self.statusIndicatorSize));
result.width = ([self.timestampLabel sizeThatFits:CGSizeZero].width +
[self.statusLabel sizeThatFits:CGSizeZero].width + self.statusIndicatorSize + self.hSpacing * 2.f);
return CGSizeCeil(result);
}
- (nullable NSString *)messageStatusTextForConversationViewItem:(ConversationViewItem *)viewItem
{
OWSAssert(viewItem);
if (viewItem.interaction.interactionType != OWSInteractionType_OutgoingMessage) {
return nil;
}
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)viewItem.interaction;
NSString *statusMessage =
[MessageRecipientStatusUtils receiptMessageWithOutgoingMessage:outgoingMessage referenceView:self];
return statusMessage;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -55,6 +55,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);
@property (nonatomic, readonly) BOOL hasQuotedText;
@property (nonatomic) BOOL shouldShowDate;
// TODO: Consider renaming to shouldHideFooter.
@property (nonatomic) BOOL shouldHideRecipientStatus;
// Used to suppress "group sender" avatars.
@property (nonatomic) BOOL shouldHideAvatar;