session-ios/Session/Signal/ConversationView/Cells/OWSMessageFooterView.m

248 lines
7.6 KiB
Objective-C

//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSMessageFooterView.h"
#import "DateUtil.h"
#import "OWSLabel.h"
#import "OWSMessageTimerView.h"
#import "Session-Swift.h"
#import <QuartzCore/QuartzCore.h>
NS_ASSUME_NONNULL_BEGIN
@interface OWSMessageFooterView ()
@property (nonatomic) UILabel *timestampLabel;
@property (nonatomic) UIImageView *statusIndicatorImageView;
@property (nonatomic) OWSMessageTimerView *messageTimerView;
@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);
self.layoutMargins = UIEdgeInsetsZero;
self.axis = UILayoutConstraintAxisHorizontal;
self.alignment = UIStackViewAlignmentCenter;
self.distribution = UIStackViewDistributionEqualSpacing;
UIStackView *leftStackView = [UIStackView new];
leftStackView.axis = UILayoutConstraintAxisHorizontal;
leftStackView.spacing = self.hSpacing;
leftStackView.alignment = UIStackViewAlignmentCenter;
[self addArrangedSubview:leftStackView];
[leftStackView setContentHuggingHigh];
self.timestampLabel = [OWSLabel new];
[leftStackView addArrangedSubview:self.timestampLabel];
self.messageTimerView = [OWSMessageTimerView new];
[self.messageTimerView setContentHuggingHigh];
[leftStackView addArrangedSubview:self.messageTimerView];
self.statusIndicatorImageView = [UIImageView new];
self.userInteractionEnabled = NO;
}
- (void)configureFonts
{
self.timestampLabel.font = [UIFont systemFontOfSize:LKValues.verySmallFontSize];
}
- (CGFloat)hSpacing
{
// TODO: Review constant.
return 8.f;
}
- (CGFloat)maxImageWidth
{
return 18.f;
}
- (CGFloat)imageHeight
{
return 12.f;
}
#pragma mark - Load
- (void)configureWithConversationViewItem:(id<ConversationViewItem>)viewItem
isOverlayingMedia:(BOOL)isOverlayingMedia
conversationStyle:(ConversationStyle *)conversationStyle
isIncoming:(BOOL)isIncoming
{
OWSAssertDebug(viewItem);
OWSAssertDebug(conversationStyle);
[self configureLabelsWithConversationViewItem:viewItem];
UIColor *textColor;
if (isOverlayingMedia) {
textColor = UIColor.whiteColor;
} else {
textColor = [conversationStyle bubbleSecondaryTextColorWithIsIncoming:isIncoming];
}
self.timestampLabel.textColor = textColor;
if (viewItem.isExpiringMessage) {
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;
}
if (viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage) {
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)viewItem.interaction;
UIImage *_Nullable statusIndicatorImage = nil;
__block BOOL isNoteToSelf = NO;
[OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
TSContactThread *thread = [outgoingMessage.thread as:TSContactThread.class];
if (thread != nil) {
NSString *userPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
isNoteToSelf = ([thread.contactIdentifier isEqual:userPublicKey]);
}
}];
if (statusIndicatorImage && !isNoteToSelf) {
[self showStatusIndicatorWithIcon:statusIndicatorImage textColor:textColor];
} else {
[self hideStatusIndicator];
}
} else {
[self hideStatusIndicator];
}
}
- (void)showStatusIndicatorWithIcon:(UIImage *)icon textColor:(UIColor *)textColor
{
OWSAssertDebug(icon.size.width <= self.maxImageWidth);
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;
}
- (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"];
}
- (BOOL)isFailedOutgoingMessage:(id<ConversationViewItem>)viewItem
{
OWSAssertDebug(viewItem);
if (viewItem.interaction.interactionType != OWSInteractionType_OutgoingMessage) {
return NO;
}
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)viewItem.interaction;
MessageReceiptStatus messageStatus =
[MessageRecipientStatusUtils recipientStatusWithOutgoingMessage:outgoingMessage];
return messageStatus == MessageReceiptStatusFailed;
}
- (void)configureLabelsWithConversationViewItem:(id<ConversationViewItem>)viewItem
{
OWSAssertDebug(viewItem);
[self configureFonts];
NSString *timestampLabelText;
if ([self isFailedOutgoingMessage:viewItem]) {
timestampLabelText
= NSLocalizedString(@"MESSAGE_STATUS_SEND_FAILED", @"Label indicating that a message failed to send.");
} else {
timestampLabelText = [DateUtil formatMessageTimestamp:viewItem.interaction.timestampForUI];
}
[self.timestampLabel setText:timestampLabelText.localizedUppercaseString];
}
- (CGSize)measureWithConversationViewItem:(id<ConversationViewItem>)viewItem
{
OWSAssertDebug(viewItem);
[self configureLabelsWithConversationViewItem:viewItem];
CGSize result = CGSizeZero;
result.height = MAX(self.timestampLabel.font.lineHeight, self.imageHeight);
// Measure the actual current width, to be safe.
CGFloat timestampLabelWidth = [self.timestampLabel sizeThatFits:CGSizeZero].width;
result.width = timestampLabelWidth;
if (viewItem.isExpiringMessage) {
result.width += ([OWSMessageTimerView measureSize].width + self.hSpacing);
}
return CGSizeCeil(result);
}
- (nullable NSString *)messageStatusTextForConversationViewItem:(id<ConversationViewItem>)viewItem
{
OWSAssertDebug(viewItem);
if (viewItem.interaction.interactionType != OWSInteractionType_OutgoingMessage) {
return nil;
}
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)viewItem.interaction;
NSString *statusMessage = [MessageRecipientStatusUtils receiptMessageWithOutgoingMessage:outgoingMessage];
return statusMessage;
}
- (void)prepareForReuse
{
[self.statusIndicatorImageView.layer removeAllAnimations];
[self.messageTimerView prepareForReuse];
}
@end
NS_ASSUME_NONNULL_END