session-ios/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m

268 lines
8.4 KiB
Mathematica
Raw Normal View History

2018-06-25 22:20:28 +02:00
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSMessageFooterView.h"
#import "DateUtil.h"
2018-08-09 16:55:38 +02:00
#import "OWSLabel.h"
2018-07-10 19:27:35 +02:00
#import "OWSMessageTimerView.h"
2018-06-25 22:20:28 +02:00
#import "Signal-Swift.h"
2018-06-29 15:43:36 +02:00
#import <QuartzCore/QuartzCore.h>
2018-06-25 22:20:28 +02:00
NS_ASSUME_NONNULL_BEGIN
@interface OWSMessageFooterView ()
@property (nonatomic) UILabel *timestampLabel;
2018-06-28 17:44:39 +02:00
@property (nonatomic) UIImageView *statusIndicatorImageView;
2018-07-10 19:47:50 +02:00
@property (nonatomic) OWSMessageTimerView *messageTimerView;
2018-06-25 22:20:28 +02:00
@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.
OWSAssertDebug(!self.timestampLabel);
2018-06-25 22:20:28 +02:00
self.layoutMargins = UIEdgeInsetsZero;
2018-06-26 16:17:03 +02:00
self.axis = UILayoutConstraintAxisHorizontal;
self.alignment = UIStackViewAlignmentCenter;
2018-07-10 19:54:08 +02:00
self.distribution = UIStackViewDistributionEqualSpacing;
UIStackView *leftStackView = [UIStackView new];
leftStackView.axis = UILayoutConstraintAxisHorizontal;
leftStackView.spacing = self.hSpacing;
leftStackView.alignment = UIStackViewAlignmentCenter;
[self addArrangedSubview:leftStackView];
2018-07-20 22:34:28 +02:00
[leftStackView setContentHuggingHigh];
2018-06-26 16:17:03 +02:00
2018-08-09 16:55:38 +02:00
self.timestampLabel = [OWSLabel new];
2018-07-10 19:54:08 +02:00
[leftStackView addArrangedSubview:self.timestampLabel];
2018-06-26 16:17:03 +02:00
2018-07-10 19:47:50 +02:00
self.messageTimerView = [OWSMessageTimerView new];
2018-07-10 19:54:08 +02:00
[self.messageTimerView setContentHuggingHigh];
[leftStackView addArrangedSubview:self.messageTimerView];
2018-07-10 19:47:50 +02:00
2018-06-28 17:44:39 +02:00
self.statusIndicatorImageView = [UIImageView new];
[self addArrangedSubview:self.statusIndicatorImageView];
2018-07-09 16:15:23 +02:00
self.userInteractionEnabled = NO;
2018-06-25 22:20:28 +02:00
}
- (void)configureFonts
{
2018-06-28 17:44:39 +02:00
self.timestampLabel.font = UIFont.ows_dynamicTypeCaption1Font;
2018-06-25 22:20:28 +02:00
}
2018-06-28 17:44:39 +02:00
- (CGFloat)hSpacing
2018-06-25 22:20:28 +02:00
{
// TODO: Review constant.
2018-06-28 17:44:39 +02:00
return 8.f;
2018-06-25 22:20:28 +02:00
}
2018-06-28 17:44:39 +02:00
- (CGFloat)maxImageWidth
2018-06-25 22:20:28 +02:00
{
2018-06-28 17:44:39 +02:00
return 18.f;
}
- (CGFloat)imageHeight
{
return 12.f;
2018-06-25 22:20:28 +02:00
}
#pragma mark - Load
2018-09-28 00:49:01 +02:00
- (void)configureWithConversationViewItem:(id<ConversationViewItem>)viewItem
2018-07-06 22:18:45 +02:00
isOverlayingMedia:(BOOL)isOverlayingMedia
conversationStyle:(ConversationStyle *)conversationStyle
isIncoming:(BOOL)isIncoming
2018-06-25 22:20:28 +02:00
{
OWSAssertDebug(viewItem);
OWSAssertDebug(conversationStyle);
2018-06-25 22:20:28 +02:00
[self configureLabelsWithConversationViewItem:viewItem];
2018-06-28 17:44:39 +02:00
UIColor *textColor;
2018-06-28 20:59:37 +02:00
if (isOverlayingMedia) {
2018-06-28 17:44:39 +02:00
textColor = [UIColor whiteColor];
} else {
2018-07-09 15:58:02 +02:00
textColor = [conversationStyle bubbleSecondaryTextColorWithIsIncoming:isIncoming];
2018-06-28 17:44:39 +02:00
}
self.timestampLabel.textColor = textColor;
2018-07-11 21:43:25 +02:00
if (viewItem.isExpiringMessage) {
2018-07-10 19:47:50 +02:00
TSMessage *message = (TSMessage *)viewItem.interaction;
uint64_t expirationTimestamp = message.expiresAt;
uint32_t expiresInSeconds = message.expiresInSeconds;
[self.messageTimerView configureWithExpirationTimestamp:expirationTimestamp
initialDurationSeconds:expiresInSeconds
tintColor:textColor];
self.messageTimerView.hidden = NO;
} else {
self.messageTimerView.hidden = YES;
}
2018-06-28 17:44:39 +02:00
if (viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage) {
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)viewItem.interaction;
UIImage *_Nullable statusIndicatorImage = nil;
MessageReceiptStatus messageStatus =
2018-06-29 23:00:22 +02:00
[MessageRecipientStatusUtils recipientStatusWithOutgoingMessage:outgoingMessage];
2018-06-28 17:44:39 +02:00
switch (messageStatus) {
case MessageReceiptStatusUploading:
case MessageReceiptStatusSending:
statusIndicatorImage = [UIImage imageNamed:@"message_status_sending"];
2018-06-29 15:43:36 +02:00
[self animateSpinningIcon];
2018-06-28 17:44:39 +02:00
break;
case MessageReceiptStatusSent:
case MessageReceiptStatusSkipped:
statusIndicatorImage = [UIImage imageNamed:@"message_status_sent"];
break;
case MessageReceiptStatusDelivered:
statusIndicatorImage = [UIImage imageNamed:@"message_status_delivered"];
break;
2018-07-10 16:35:51 +02:00
case MessageReceiptStatusRead:
statusIndicatorImage = [UIImage imageNamed:@"message_status_read"];
break;
2018-06-28 17:44:39 +02:00
case MessageReceiptStatusFailed:
2018-07-05 16:08:42 +02:00
// No status indicator icon.
2018-06-28 17:44:39 +02:00
break;
}
2018-07-05 16:08:42 +02:00
if (statusIndicatorImage) {
2018-07-20 22:34:28 +02:00
[self showStatusIndicatorWithIcon:statusIndicatorImage textColor:textColor];
2018-06-28 17:44:39 +02:00
} else {
2018-07-20 22:34:28 +02:00
[self hideStatusIndicator];
2018-06-28 17:44:39 +02:00
}
} else {
2018-07-20 22:34:28 +02:00
[self hideStatusIndicator];
2018-06-28 17:44:39 +02:00
}
2018-06-25 22:20:28 +02:00
}
2018-07-20 22:34:28 +02:00
- (void)showStatusIndicatorWithIcon:(UIImage *)icon textColor:(UIColor *)textColor
{
OWSAssertDebug(icon.size.width <= self.maxImageWidth);
2018-07-20 22:34:28 +02:00
self.statusIndicatorImageView.image = [icon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
self.statusIndicatorImageView.tintColor = textColor;
[self.statusIndicatorImageView setContentHuggingHigh];
self.spacing = self.hSpacing;
}
- (void)hideStatusIndicator
{
// If there's no status indicator, we want the other
// footer contents to "cling to the leading edge".
// Instead of hiding the status indicator view,
// we clear its contents and let it stretch to fill
// the available space.
self.statusIndicatorImageView.image = nil;
[self.statusIndicatorImageView setContentHuggingLow];
self.spacing = 0;
}
2018-06-29 15:43:36 +02:00
- (void)animateSpinningIcon
{
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.statusIndicatorImageView.layer addAnimation:animation forKey:@"animation"];
}
2018-09-28 00:49:01 +02:00
- (BOOL)isFailedOutgoingMessage:(id<ConversationViewItem>)viewItem
2018-07-05 16:08:42 +02:00
{
OWSAssertDebug(viewItem);
2018-07-05 16:08:42 +02:00
if (viewItem.interaction.interactionType != OWSInteractionType_OutgoingMessage) {
return NO;
}
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)viewItem.interaction;
MessageReceiptStatus messageStatus =
[MessageRecipientStatusUtils recipientStatusWithOutgoingMessage:outgoingMessage];
return messageStatus == MessageReceiptStatusFailed;
}
2018-09-28 00:49:01 +02:00
- (void)configureLabelsWithConversationViewItem:(id<ConversationViewItem>)viewItem
2018-06-25 22:20:28 +02:00
{
OWSAssertDebug(viewItem);
2018-06-25 22:20:28 +02:00
[self configureFonts];
2018-07-10 01:00:49 +02:00
NSString *timestampLabelText;
2018-07-05 16:08:42 +02:00
if ([self isFailedOutgoingMessage:viewItem]) {
2018-07-10 01:00:49 +02:00
timestampLabelText
2018-07-05 16:08:42 +02:00
= NSLocalizedString(@"MESSAGE_STATUS_SEND_FAILED", @"Label indicating that a message failed to send.");
} else {
2018-07-10 01:00:49 +02:00
timestampLabelText = [DateUtil formatMessageTimestamp:viewItem.interaction.timestamp];
2018-07-05 16:08:42 +02:00
}
2018-07-10 01:00:49 +02:00
self.timestampLabel.text = timestampLabelText.localizedUppercaseString;
2018-06-25 22:20:28 +02:00
}
2018-09-28 00:49:01 +02:00
- (CGSize)measureWithConversationViewItem:(id<ConversationViewItem>)viewItem
2018-06-25 22:20:28 +02:00
{
OWSAssertDebug(viewItem);
2018-06-25 22:20:28 +02:00
[self configureLabelsWithConversationViewItem:viewItem];
CGSize result = CGSizeZero;
2018-06-28 17:44:39 +02:00
result.height = MAX(self.timestampLabel.font.lineHeight, self.imageHeight);
2018-07-05 17:56:46 +02:00
// Measure the actual current width, to be safe.
CGFloat timestampLabelWidth = [self.timestampLabel sizeThatFits:CGSizeZero].width;
result.width = timestampLabelWidth;
2018-06-26 16:17:03 +02:00
if (viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage) {
2018-07-05 16:08:42 +02:00
if (![self isFailedOutgoingMessage:viewItem]) {
result.width += (self.maxImageWidth + self.hSpacing);
}
2018-06-26 16:17:03 +02:00
}
2018-07-10 19:27:35 +02:00
2018-07-11 21:43:25 +02:00
if (viewItem.isExpiringMessage) {
2018-07-10 19:27:35 +02:00
result.width += ([OWSMessageTimerView measureSize].width + self.hSpacing);
}
2018-06-25 22:20:28 +02:00
return CGSizeCeil(result);
}
2018-09-28 00:49:01 +02:00
- (nullable NSString *)messageStatusTextForConversationViewItem:(id<ConversationViewItem>)viewItem
2018-06-25 22:20:28 +02:00
{
OWSAssertDebug(viewItem);
2018-06-25 22:20:28 +02:00
if (viewItem.interaction.interactionType != OWSInteractionType_OutgoingMessage) {
return nil;
}
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)viewItem.interaction;
2018-06-29 23:00:22 +02:00
NSString *statusMessage = [MessageRecipientStatusUtils receiptMessageWithOutgoingMessage:outgoingMessage];
2018-06-25 22:20:28 +02:00
return statusMessage;
}
2018-06-29 15:43:36 +02:00
- (void)prepareForReuse
{
[self.statusIndicatorImageView.layer removeAllAnimations];
2018-07-10 19:27:35 +02:00
2018-07-10 19:47:50 +02:00
[self.messageTimerView prepareForReuse];
2018-06-29 15:43:36 +02:00
}
2018-06-25 22:20:28 +02:00
@end
NS_ASSUME_NONNULL_END