session-ios/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m

905 lines
32 KiB
Mathematica
Raw Normal View History

2017-10-10 22:13:54 +02:00
//
2018-01-19 16:42:55 +01:00
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
2017-10-10 22:13:54 +02:00
//
#import "ConversationViewItem.h"
#import "OWSAudioMessageView.h"
#import "OWSContactOffersCell.h"
2017-10-17 06:05:29 +02:00
#import "OWSMessageCell.h"
2018-07-11 20:12:58 +02:00
#import "OWSMessageHeaderView.h"
2017-10-10 22:13:54 +02:00
#import "OWSSystemMessageCell.h"
#import "Signal-Swift.h"
#import <AssetsLibrary/AssetsLibrary.h>
2017-12-19 03:50:51 +01:00
#import <SignalMessaging/NSString+OWS.h>
2018-07-11 20:12:58 +02:00
#import <SignalMessaging/OWSUnreadIndicator.h>
2018-08-31 02:59:26 +02:00
#import <SignalServiceKit/NSData+Image.h>
#import <SignalServiceKit/OWSContact.h>
2017-10-10 22:13:54 +02:00
#import <SignalServiceKit/TSInteraction.h>
NS_ASSUME_NONNULL_BEGIN
2017-10-12 19:48:09 +02:00
NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
{
switch (cellType) {
case OWSMessageCellType_TextMessage:
return @"OWSMessageCellType_TextMessage";
case OWSMessageCellType_OversizeTextMessage:
return @"OWSMessageCellType_OversizeTextMessage";
case OWSMessageCellType_StillImage:
return @"OWSMessageCellType_StillImage";
case OWSMessageCellType_AnimatedImage:
return @"OWSMessageCellType_AnimatedImage";
case OWSMessageCellType_Audio:
return @"OWSMessageCellType_Audio";
case OWSMessageCellType_Video:
return @"OWSMessageCellType_Video";
case OWSMessageCellType_GenericAttachment:
return @"OWSMessageCellType_GenericAttachment";
case OWSMessageCellType_DownloadingAttachment:
return @"OWSMessageCellType_DownloadingAttachment";
case OWSMessageCellType_Unknown:
return @"OWSMessageCellType_Unknown";
2018-05-02 17:01:23 +02:00
case OWSMessageCellType_ContactShare:
return @"OWSMessageCellType_ContactShare";
2017-10-12 19:48:09 +02:00
}
}
#pragma mark -
2017-10-10 22:13:54 +02:00
@interface ConversationViewItem ()
@property (nonatomic, nullable) NSValue *cachedCellSize;
2018-02-23 21:44:46 +01:00
#pragma mark - OWSAudioPlayerDelegate
2017-10-10 22:13:54 +02:00
@property (nonatomic) AudioPlaybackState audioPlaybackState;
@property (nonatomic) CGFloat audioProgressSeconds;
2017-11-20 20:50:43 +01:00
@property (nonatomic) CGFloat audioDurationSeconds;
2017-10-10 22:13:54 +02:00
#pragma mark - View State
@property (nonatomic) BOOL hasViewState;
@property (nonatomic) OWSMessageCellType messageCellType;
2018-03-29 17:25:19 +02:00
@property (nonatomic, nullable) DisplayableText *displayableBodyText;
@property (nonatomic, nullable) DisplayableText *displayableQuotedText;
@property (nonatomic, nullable) OWSQuotedReplyModel *quotedReply;
@property (nonatomic, readonly, nullable) NSString *quotedAttachmentMimetype;
@property (nonatomic, readonly, nullable) NSString *quotedRecipientId;
2017-10-10 22:13:54 +02:00
@property (nonatomic, nullable) TSAttachmentStream *attachmentStream;
@property (nonatomic, nullable) TSAttachmentPointer *attachmentPointer;
@property (nonatomic, nullable) ContactShareViewModel *contactShare;
@property (nonatomic) CGSize mediaSize;
2017-10-10 22:13:54 +02:00
@end
#pragma mark -
@implementation ConversationViewItem
- (instancetype)initWithInteraction:(TSInteraction *)interaction
isGroupThread:(BOOL)isGroupThread
transaction:(YapDatabaseReadTransaction *)transaction
2018-06-25 21:20:17 +02:00
conversationStyle:(ConversationStyle *)conversationStyle
2017-10-10 22:13:54 +02:00
{
2018-06-22 19:48:23 +02:00
OWSAssert(interaction);
OWSAssert(transaction);
2018-06-25 21:20:17 +02:00
OWSAssert(conversationStyle);
2018-06-22 19:48:23 +02:00
2017-10-10 22:13:54 +02:00
self = [super init];
if (!self) {
return self;
}
_interaction = interaction;
_isGroupThread = isGroupThread;
2018-06-25 21:20:17 +02:00
_conversationStyle = conversationStyle;
2017-10-10 22:13:54 +02:00
[self ensureViewState:transaction];
2017-10-10 22:13:54 +02:00
return self;
}
- (void)replaceInteraction:(TSInteraction *)interaction transaction:(YapDatabaseReadTransaction *)transaction
2017-10-10 22:13:54 +02:00
{
OWSAssert(interaction);
_interaction = interaction;
2017-10-12 19:48:09 +02:00
self.hasViewState = NO;
self.messageCellType = OWSMessageCellType_Unknown;
2018-03-29 17:25:19 +02:00
self.displayableBodyText = nil;
2017-10-12 19:48:09 +02:00
self.attachmentStream = nil;
self.attachmentPointer = nil;
self.mediaSize = CGSizeZero;
self.displayableQuotedText = nil;
self.quotedReply = nil;
2018-08-09 16:47:43 +02:00
self.systemMessageText = nil;
2017-10-10 22:13:54 +02:00
[self clearCachedLayoutState];
[self ensureViewState:transaction];
2017-11-17 16:49:34 +01:00
}
2018-03-29 17:25:19 +02:00
- (BOOL)hasBodyText
2017-11-17 16:49:34 +01:00
{
2018-03-29 17:25:19 +02:00
return _displayableBodyText != nil;
2017-10-10 22:13:54 +02:00
}
- (BOOL)hasQuotedText
{
return _displayableQuotedText != nil;
}
- (BOOL)hasQuotedAttachment
{
return self.quotedAttachmentMimetype.length > 0;
}
- (BOOL)isQuotedReply
{
return self.hasQuotedAttachment || self.hasQuotedText;
}
2018-07-11 21:43:25 +02:00
- (BOOL)isExpiringMessage
{
if (self.interaction.interactionType != OWSInteractionType_OutgoingMessage
&& self.interaction.interactionType != OWSInteractionType_IncomingMessage) {
return NO;
}
TSMessage *message = (TSMessage *)self.interaction;
return message.isExpiringMessage;
}
2018-07-11 20:12:58 +02:00
- (BOOL)hasCellHeader
{
return self.shouldShowDate || self.unreadIndicator;
}
2017-10-10 22:13:54 +02:00
- (void)setShouldShowDate:(BOOL)shouldShowDate
{
if (_shouldShowDate == shouldShowDate) {
return;
}
_shouldShowDate = shouldShowDate;
[self clearCachedLayoutState];
}
2018-06-26 22:04:09 +02:00
- (void)setShouldShowSenderAvatar:(BOOL)shouldShowSenderAvatar
{
2018-06-26 22:04:09 +02:00
if (_shouldShowSenderAvatar == shouldShowSenderAvatar) {
return;
}
2018-06-26 22:04:09 +02:00
_shouldShowSenderAvatar = shouldShowSenderAvatar;
[self clearCachedLayoutState];
}
2018-07-02 15:42:48 +02:00
- (void)setSenderName:(nullable NSAttributedString *)senderName
2018-03-29 17:49:02 +02:00
{
2018-06-26 22:04:09 +02:00
if ([NSObject isNullableObject:senderName equalTo:_senderName]) {
2018-03-29 17:49:02 +02:00
return;
}
2018-06-26 22:04:09 +02:00
_senderName = senderName;
[self clearCachedLayoutState];
}
- (void)setShouldHideFooter:(BOOL)shouldHideFooter
{
if (_shouldHideFooter == shouldHideFooter) {
return;
}
_shouldHideFooter = shouldHideFooter;
2018-03-29 17:49:02 +02:00
[self clearCachedLayoutState];
}
2018-07-11 20:12:58 +02:00
- (void)setUnreadIndicator:(nullable OWSUnreadIndicator *)unreadIndicator
{
if ([NSObject isNullableObject:_unreadIndicator equalTo:unreadIndicator]) {
return;
}
_unreadIndicator = unreadIndicator;
[self clearCachedLayoutState];
}
2017-10-10 22:13:54 +02:00
- (void)clearCachedLayoutState
{
self.cachedCellSize = nil;
}
2018-08-09 16:47:43 +02:00
- (CGSize)cellSize
2017-10-10 22:13:54 +02:00
{
2017-12-19 17:38:25 +01:00
OWSAssertIsOnMainThread();
2018-06-25 21:20:17 +02:00
OWSAssert(self.conversationStyle);
2017-10-10 22:13:54 +02:00
if (!self.cachedCellSize) {
ConversationViewCell *_Nullable measurementCell = [self measurementCell];
measurementCell.viewItem = self;
2018-06-25 21:20:17 +02:00
measurementCell.conversationStyle = self.conversationStyle;
2018-08-09 16:47:43 +02:00
CGSize cellSize = [measurementCell cellSize];
2017-10-10 22:13:54 +02:00
self.cachedCellSize = [NSValue valueWithCGSize:cellSize];
[measurementCell prepareForReuse];
}
2017-10-12 22:19:07 +02:00
return [self.cachedCellSize CGSizeValue];
2017-10-10 22:13:54 +02:00
}
- (nullable ConversationViewCell *)measurementCell
{
2017-12-19 17:38:25 +01:00
OWSAssertIsOnMainThread();
2017-10-10 22:13:54 +02:00
OWSAssert(self.interaction);
// For performance reasons, we cache one instance of each kind of
// cell and uses these cells for measurement.
static NSMutableDictionary<NSNumber *, ConversationViewCell *> *measurementCellCache = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
measurementCellCache = [NSMutableDictionary new];
});
NSNumber *cellCacheKey = @(self.interaction.interactionType);
ConversationViewCell *_Nullable measurementCell = measurementCellCache[cellCacheKey];
if (!measurementCell) {
switch (self.interaction.interactionType) {
case OWSInteractionType_Unknown:
OWSFailDebug(@"Unknown interaction type.");
2017-10-10 22:13:54 +02:00
return nil;
case OWSInteractionType_IncomingMessage:
case OWSInteractionType_OutgoingMessage:
2017-10-17 06:05:29 +02:00
measurementCell = [OWSMessageCell new];
2017-10-10 22:13:54 +02:00
break;
case OWSInteractionType_Error:
case OWSInteractionType_Info:
2018-07-06 21:31:38 +02:00
case OWSInteractionType_Call:
measurementCell = [OWSSystemMessageCell new];
2018-07-06 21:31:38 +02:00
break;
2017-10-10 22:13:54 +02:00
case OWSInteractionType_Offer:
measurementCell = [OWSContactOffersCell new];
break;
}
OWSAssert(measurementCell);
measurementCellCache[cellCacheKey] = measurementCell;
}
return measurementCell;
}
2018-06-22 23:36:42 +02:00
- (CGFloat)vSpacingWithPreviousLayoutItem:(ConversationViewItem *)previousLayoutItem
2018-06-22 19:48:23 +02:00
{
2018-06-25 21:00:45 +02:00
OWSAssert(previousLayoutItem);
2018-06-22 23:36:42 +02:00
2018-07-11 20:12:58 +02:00
if (self.hasCellHeader) {
return OWSMessageHeaderViewDateHeaderVMargin;
2018-07-09 21:43:07 +02:00
}
2018-06-26 17:02:30 +02:00
// "Bubble Collapse". Adjacent messages with the same author should be close together.
if (self.interaction.interactionType == OWSInteractionType_IncomingMessage
&& previousLayoutItem.interaction.interactionType == OWSInteractionType_IncomingMessage) {
TSIncomingMessage *incomingMessage = (TSIncomingMessage *)self.interaction;
TSIncomingMessage *previousIncomingMessage = (TSIncomingMessage *)previousLayoutItem.interaction;
if ([incomingMessage.authorId isEqualToString:previousIncomingMessage.authorId]) {
return 2.f;
}
} else if (self.interaction.interactionType == OWSInteractionType_OutgoingMessage
&& previousLayoutItem.interaction.interactionType == OWSInteractionType_OutgoingMessage) {
return 2.f;
}
2018-07-02 18:41:38 +02:00
return 12.f;
2018-06-22 19:48:23 +02:00
}
2017-10-10 22:13:54 +02:00
- (ConversationViewCell *)dequeueCellForCollectionView:(UICollectionView *)collectionView
indexPath:(NSIndexPath *)indexPath
{
2017-12-19 17:38:25 +01:00
OWSAssertIsOnMainThread();
2017-10-10 22:13:54 +02:00
OWSAssert(collectionView);
OWSAssert(indexPath);
OWSAssert(self.interaction);
switch (self.interaction.interactionType) {
case OWSInteractionType_Unknown:
OWSFailDebug(@"Unknown interaction type.");
2017-10-10 22:13:54 +02:00
return nil;
case OWSInteractionType_IncomingMessage:
case OWSInteractionType_OutgoingMessage:
2017-10-17 06:05:29 +02:00
return [collectionView dequeueReusableCellWithReuseIdentifier:[OWSMessageCell cellReuseIdentifier]
2017-10-10 22:13:54 +02:00
forIndexPath:indexPath];
case OWSInteractionType_Error:
case OWSInteractionType_Info:
2018-07-06 21:31:38 +02:00
case OWSInteractionType_Call:
return [collectionView dequeueReusableCellWithReuseIdentifier:[OWSSystemMessageCell cellReuseIdentifier]
2018-07-06 21:31:38 +02:00
forIndexPath:indexPath];
2017-10-10 22:13:54 +02:00
case OWSInteractionType_Offer:
return [collectionView dequeueReusableCellWithReuseIdentifier:[OWSContactOffersCell cellReuseIdentifier]
forIndexPath:indexPath];
}
}
2018-02-23 21:44:46 +01:00
#pragma mark - OWSAudioPlayerDelegate
2017-10-10 22:13:54 +02:00
- (void)setAudioPlaybackState:(AudioPlaybackState)audioPlaybackState
{
_audioPlaybackState = audioPlaybackState;
[self.lastAudioMessageView updateContents];
}
- (void)setAudioProgress:(CGFloat)progress duration:(CGFloat)duration
{
2017-12-19 17:38:25 +01:00
OWSAssertIsOnMainThread();
2017-10-10 22:13:54 +02:00
self.audioProgressSeconds = progress;
[self.lastAudioMessageView updateContents];
}
#pragma mark - Displayable Text
2017-10-10 22:13:54 +02:00
2017-10-11 15:58:20 +02:00
// TODO: Now that we're caching the displayable text on the view items,
// I don't think we need this cache any more.
- (NSCache *)displayableTextCache
2017-10-10 22:13:54 +02:00
{
static NSCache *cache = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cache = [NSCache new];
// Cache the results for up to 1,000 messages.
cache.countLimit = 1000;
});
return cache;
}
2018-03-29 17:25:19 +02:00
- (DisplayableText *)displayableBodyTextForText:(NSString *)text interactionId:(NSString *)interactionId
2017-10-10 22:13:54 +02:00
{
OWSAssert(text);
OWSAssert(interactionId.length > 0);
NSString *displayableTextCacheKey = [@"body-" stringByAppendingString:interactionId];
return [self displayableTextForCacheKey:displayableTextCacheKey
textBlock:^{
return text;
}];
}
2018-03-29 17:25:19 +02:00
- (DisplayableText *)displayableBodyTextForOversizeTextAttachment:(TSAttachmentStream *)attachmentStream
interactionId:(NSString *)interactionId
{
OWSAssert(attachmentStream);
OWSAssert(interactionId.length > 0);
NSString *displayableTextCacheKey = [@"oversize-body-" stringByAppendingString:interactionId];
return [self displayableTextForCacheKey:displayableTextCacheKey
textBlock:^{
NSData *textData = [NSData dataWithContentsOfURL:attachmentStream.mediaURL];
NSString *text =
[[NSString alloc] initWithData:textData encoding:NSUTF8StringEncoding];
return text;
}];
}
- (DisplayableText *)displayableQuotedTextForText:(NSString *)text interactionId:(NSString *)interactionId
{
OWSAssert(text);
OWSAssert(interactionId.length > 0);
NSString *displayableTextCacheKey = [@"quoted-" stringByAppendingString:interactionId];
return [self displayableTextForCacheKey:displayableTextCacheKey
textBlock:^{
return text;
}];
}
- (DisplayableText *)displayableTextForCacheKey:(NSString *)displayableTextCacheKey
textBlock:(NSString * (^_Nonnull)(void))textBlock
{
OWSAssert(displayableTextCacheKey.length > 0);
DisplayableText *_Nullable displayableText = [[self displayableTextCache] objectForKey:displayableTextCacheKey];
if (!displayableText) {
NSString *text = textBlock();
displayableText = [DisplayableText displayableText:text];
[[self displayableTextCache] setObject:displayableText forKey:displayableTextCacheKey];
2017-10-10 22:13:54 +02:00
}
return displayableText;
2017-10-10 22:13:54 +02:00
}
#pragma mark - View State
- (nullable TSAttachment *)firstAttachmentIfAnyOfMessage:(TSMessage *)message
transaction:(YapDatabaseReadTransaction *)transaction
{
OWSAssert(transaction);
if (message.attachmentIds.count == 0) {
return nil;
}
NSString *_Nullable attachmentId = message.attachmentIds.firstObject;
if (attachmentId.length == 0) {
return nil;
}
return [TSAttachment fetchObjectWithUniqueID:attachmentId transaction:transaction];
}
- (void)ensureViewState:(YapDatabaseReadTransaction *)transaction
2017-10-10 22:13:54 +02:00
{
2017-12-19 17:38:25 +01:00
OWSAssertIsOnMainThread();
OWSAssert(transaction);
OWSAssert(!self.hasViewState);
2018-08-09 16:47:43 +02:00
switch (self.interaction.interactionType) {
case OWSInteractionType_Unknown:
case OWSInteractionType_Offer:
return;
case OWSInteractionType_Error:
case OWSInteractionType_Info:
case OWSInteractionType_Call:
self.systemMessageText = [self systemMessageTextWithTransaction:transaction];
OWSAssert(self.systemMessageText.length > 0);
return;
case OWSInteractionType_IncomingMessage:
case OWSInteractionType_OutgoingMessage:
break;
default:
OWSFailDebug(@"Unknown interaction type.");
2018-08-09 16:47:43 +02:00
return;
}
2017-10-10 22:13:54 +02:00
2018-08-09 16:47:43 +02:00
OWSAssert([self.interaction isKindOfClass:[TSOutgoingMessage class]] ||
[self.interaction isKindOfClass:[TSIncomingMessage class]]);
2017-10-10 22:13:54 +02:00
self.hasViewState = YES;
TSMessage *message = (TSMessage *)self.interaction;
if (message.contactShare) {
self.contactShare =
[[ContactShareViewModel alloc] initWithContactShareRecord:message.contactShare transaction:transaction];
2018-05-02 17:01:23 +02:00
self.messageCellType = OWSMessageCellType_ContactShare;
return;
}
TSAttachment *_Nullable attachment = [self firstAttachmentIfAnyOfMessage:message transaction:transaction];
if (attachment) {
if ([attachment isKindOfClass:[TSAttachmentStream class]]) {
self.attachmentStream = (TSAttachmentStream *)attachment;
if ([attachment.contentType isEqualToString:OWSMimeTypeOversizeTextMessage]) {
self.messageCellType = OWSMessageCellType_OversizeTextMessage;
2018-03-29 17:25:19 +02:00
self.displayableBodyText = [self displayableBodyTextForOversizeTextAttachment:self.attachmentStream
interactionId:message.uniqueId];
} else if ([self.attachmentStream isAnimated] || [self.attachmentStream isImage] ||
[self.attachmentStream isVideo]) {
if ([self.attachmentStream isAnimated]) {
2018-08-31 04:49:31 +02:00
if (![self.attachmentStream isValidImage]) {
OWSLogWarn(@"Treating invalid image as generic attachment.");
2018-08-31 04:49:31 +02:00
self.messageCellType = OWSMessageCellType_GenericAttachment;
return;
}
self.messageCellType = OWSMessageCellType_AnimatedImage;
} else if ([self.attachmentStream isImage]) {
2018-08-31 02:59:26 +02:00
if (![self.attachmentStream isValidImage]) {
OWSLogWarn(@"Treating invalid image as generic attachment.");
2018-08-31 02:59:26 +02:00
self.messageCellType = OWSMessageCellType_GenericAttachment;
return;
}
self.messageCellType = OWSMessageCellType_StillImage;
} else if ([self.attachmentStream isVideo]) {
2018-08-31 02:59:26 +02:00
if (![self.attachmentStream isValidVideo]) {
OWSLogWarn(@"Treating invalid video as generic attachment.");
2018-08-31 02:59:26 +02:00
self.messageCellType = OWSMessageCellType_GenericAttachment;
return;
}
self.messageCellType = OWSMessageCellType_Video;
2017-10-10 22:13:54 +02:00
} else {
OWSFailDebug(@"unexpected attachment type.");
2017-10-10 22:13:54 +02:00
self.messageCellType = OWSMessageCellType_GenericAttachment;
return;
}
self.mediaSize = [self.attachmentStream imageSize];
if (self.mediaSize.width <= 0 || self.mediaSize.height <= 0) {
self.messageCellType = OWSMessageCellType_GenericAttachment;
}
} else if ([self.attachmentStream isAudio]) {
CGFloat audioDurationSeconds = [self.attachmentStream audioDurationSeconds];
if (audioDurationSeconds > 0) {
2017-11-20 20:50:43 +01:00
self.audioDurationSeconds = audioDurationSeconds;
self.messageCellType = OWSMessageCellType_Audio;
} else {
self.messageCellType = OWSMessageCellType_GenericAttachment;
}
} else {
self.messageCellType = OWSMessageCellType_GenericAttachment;
2017-10-10 22:13:54 +02:00
}
} else if ([attachment isKindOfClass:[TSAttachmentPointer class]]) {
self.messageCellType = OWSMessageCellType_DownloadingAttachment;
self.attachmentPointer = (TSAttachmentPointer *)attachment;
} else {
OWSFailDebug(@"Unknown attachment type");
2017-10-10 22:13:54 +02:00
}
}
2017-11-17 16:49:34 +01:00
// Ignore message body for oversize text attachments.
if (message.body.length > 0) {
2018-03-29 17:25:19 +02:00
if (self.hasBodyText) {
OWSFailDebug(@"oversize text message has unexpected caption.");
2017-11-17 16:49:34 +01:00
}
// If we haven't already assigned an attachment type at this point, message.body isn't a caption,
// it's a stand-alone text message.
if (self.messageCellType == OWSMessageCellType_Unknown) {
OWSAssert(message.attachmentIds.count == 0);
self.messageCellType = OWSMessageCellType_TextMessage;
}
2018-03-29 17:25:19 +02:00
self.displayableBodyText = [self displayableBodyTextForText:message.body interactionId:message.uniqueId];
OWSAssert(self.displayableBodyText);
2017-10-10 22:13:54 +02:00
}
if (self.messageCellType == OWSMessageCellType_Unknown) {
// Messages of unknown type (including messages with missing attachments)
// are rendered like empty text messages, but without any interactivity.
OWSLogWarn(@"Treating unknown message as empty text message: %@ %llu", message.class, message.timestamp);
2018-01-19 16:42:55 +01:00
self.messageCellType = OWSMessageCellType_TextMessage;
2018-03-29 17:25:19 +02:00
self.displayableBodyText = [[DisplayableText alloc] initWithFullText:@"" displayText:@"" isTextTruncated:NO];
}
if (message.quotedMessage) {
self.quotedReply =
[OWSQuotedReplyModel quotedReplyWithQuotedMessage:message.quotedMessage transaction:transaction];
if (self.quotedReply.body.length > 0) {
self.displayableQuotedText =
[self displayableQuotedTextForText:self.quotedReply.body interactionId:message.uniqueId];
}
}
2017-10-10 22:13:54 +02:00
}
2018-08-09 16:47:43 +02:00
- (NSString *)systemMessageTextWithTransaction:(YapDatabaseReadTransaction *)transaction
{
OWSAssert(transaction);
switch (self.interaction.interactionType) {
case OWSInteractionType_Error: {
TSErrorMessage *errorMessage = (TSErrorMessage *)self.interaction;
return [errorMessage previewTextWithTransaction:transaction];
}
case OWSInteractionType_Info: {
TSInfoMessage *infoMessage = (TSInfoMessage *)self.interaction;
if ([infoMessage isKindOfClass:[OWSVerificationStateChangeMessage class]]) {
OWSVerificationStateChangeMessage *verificationMessage
= (OWSVerificationStateChangeMessage *)infoMessage;
BOOL isVerified = verificationMessage.verificationState == OWSVerificationStateVerified;
NSString *displayName = [[Environment current].contactsManager
displayNameForPhoneIdentifier:verificationMessage.recipientId];
NSString *titleFormat = (isVerified
? (verificationMessage.isLocalChange
? NSLocalizedString(@"VERIFICATION_STATE_CHANGE_FORMAT_VERIFIED_LOCAL",
@"Format for info message indicating that the verification state was verified "
@"on "
@"this device. Embeds {{user's name or phone number}}.")
: NSLocalizedString(@"VERIFICATION_STATE_CHANGE_FORMAT_VERIFIED_OTHER_DEVICE",
@"Format for info message indicating that the verification state was verified "
@"on "
@"another device. Embeds {{user's name or phone number}}."))
: (verificationMessage.isLocalChange
? NSLocalizedString(@"VERIFICATION_STATE_CHANGE_FORMAT_NOT_VERIFIED_LOCAL",
@"Format for info message indicating that the verification state was "
@"unverified on "
@"this device. Embeds {{user's name or phone number}}.")
: NSLocalizedString(@"VERIFICATION_STATE_CHANGE_FORMAT_NOT_VERIFIED_OTHER_DEVICE",
@"Format for info message indicating that the verification state was "
@"unverified on "
@"another device. Embeds {{user's name or phone number}}.")));
return [NSString stringWithFormat:titleFormat, displayName];
} else {
return [infoMessage previewTextWithTransaction:transaction];
}
}
case OWSInteractionType_Call: {
TSCall *call = (TSCall *)self.interaction;
return [call previewTextWithTransaction:transaction];
}
default:
OWSFailDebug(@"not a system message.");
2018-08-09 16:47:43 +02:00
return nil;
}
}
2017-10-10 22:13:54 +02:00
- (nullable NSString *)quotedAttachmentMimetype
{
return self.quotedReply.contentType;
}
- (nullable NSString *)quotedRecipientId
{
return self.quotedReply.authorId;
}
2017-10-10 22:13:54 +02:00
- (OWSMessageCellType)messageCellType
{
2017-12-19 17:38:25 +01:00
OWSAssertIsOnMainThread();
2017-10-10 22:13:54 +02:00
return _messageCellType;
}
2018-03-29 17:25:19 +02:00
- (nullable DisplayableText *)displayableBodyText
2017-10-10 22:13:54 +02:00
{
2017-12-19 17:38:25 +01:00
OWSAssertIsOnMainThread();
OWSAssert(self.hasViewState);
2017-10-10 22:13:54 +02:00
2018-03-29 17:25:19 +02:00
OWSAssert(_displayableBodyText);
OWSAssert(_displayableBodyText.displayText);
OWSAssert(_displayableBodyText.fullText);
2018-03-29 17:25:19 +02:00
return _displayableBodyText;
2017-10-10 22:13:54 +02:00
}
- (nullable TSAttachmentStream *)attachmentStream
{
2017-12-19 17:38:25 +01:00
OWSAssertIsOnMainThread();
OWSAssert(self.hasViewState);
2017-10-10 22:13:54 +02:00
return _attachmentStream;
}
- (nullable TSAttachmentPointer *)attachmentPointer
{
2017-12-19 17:38:25 +01:00
OWSAssertIsOnMainThread();
OWSAssert(self.hasViewState);
2017-10-10 22:13:54 +02:00
return _attachmentPointer;
}
- (CGSize)mediaSize
2017-10-10 22:13:54 +02:00
{
2017-12-19 17:38:25 +01:00
OWSAssertIsOnMainThread();
OWSAssert(self.hasViewState);
2017-10-10 22:13:54 +02:00
return _mediaSize;
2017-10-10 22:13:54 +02:00
}
- (nullable DisplayableText *)displayableQuotedText
{
OWSAssertIsOnMainThread();
OWSAssert(self.hasViewState);
OWSAssert(_displayableQuotedText);
OWSAssert(_displayableQuotedText.displayText);
OWSAssert(_displayableQuotedText.fullText);
return _displayableQuotedText;
}
- (void)copyTextAction
2017-10-10 22:13:54 +02:00
{
switch (self.messageCellType) {
case OWSMessageCellType_TextMessage:
case OWSMessageCellType_OversizeTextMessage:
case OWSMessageCellType_StillImage:
case OWSMessageCellType_AnimatedImage:
case OWSMessageCellType_Audio:
case OWSMessageCellType_Video:
case OWSMessageCellType_GenericAttachment: {
2018-03-29 17:25:19 +02:00
OWSAssert(self.displayableBodyText);
[UIPasteboard.generalPasteboard setString:self.displayableBodyText.fullText];
2017-10-10 22:13:54 +02:00
break;
}
case OWSMessageCellType_DownloadingAttachment: {
OWSFailDebug(@"Can't copy not-yet-downloaded attachment");
break;
}
case OWSMessageCellType_Unknown: {
OWSFailDebug(@"No text to copy");
break;
}
2018-05-02 17:01:23 +02:00
case OWSMessageCellType_ContactShare: {
// TODO: Implement copy contact.
OWSFailDebug(@"Not implemented yet");
break;
}
}
}
- (void)copyMediaAction
{
switch (self.messageCellType) {
case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextMessage:
case OWSMessageCellType_OversizeTextMessage:
2018-05-02 17:01:23 +02:00
case OWSMessageCellType_ContactShare: {
OWSFailDebug(@"No media to copy");
break;
}
2017-10-10 22:13:54 +02:00
case OWSMessageCellType_StillImage:
case OWSMessageCellType_AnimatedImage:
case OWSMessageCellType_Audio:
case OWSMessageCellType_Video:
case OWSMessageCellType_GenericAttachment: {
NSString *utiType = [MIMETypeUtil utiTypeForMIMEType:self.attachmentStream.contentType];
if (!utiType) {
OWSFailDebug(@"Unknown MIME type: %@", self.attachmentStream.contentType);
2017-10-10 22:13:54 +02:00
utiType = (NSString *)kUTTypeGIF;
}
NSData *data = [NSData dataWithContentsOfURL:[self.attachmentStream mediaURL]];
if (!data) {
OWSFailDebug(@"Could not load attachment data");
2017-10-10 22:13:54 +02:00
return;
}
[UIPasteboard.generalPasteboard setData:data forPasteboardType:utiType];
break;
}
case OWSMessageCellType_DownloadingAttachment: {
OWSFailDebug(@"Can't copy not-yet-downloaded attachment");
2017-10-10 22:13:54 +02:00
break;
}
}
}
- (void)shareTextAction
2017-10-10 22:13:54 +02:00
{
switch (self.messageCellType) {
case OWSMessageCellType_TextMessage:
case OWSMessageCellType_OversizeTextMessage:
case OWSMessageCellType_StillImage:
case OWSMessageCellType_AnimatedImage:
case OWSMessageCellType_Audio:
case OWSMessageCellType_Video:
case OWSMessageCellType_GenericAttachment: {
2018-03-29 17:25:19 +02:00
OWSAssert(self.displayableBodyText);
[AttachmentSharing showShareUIForText:self.displayableBodyText.fullText];
2017-10-10 22:13:54 +02:00
break;
}
case OWSMessageCellType_DownloadingAttachment: {
OWSFailDebug(@"Can't share not-yet-downloaded attachment");
break;
}
case OWSMessageCellType_Unknown: {
OWSFailDebug(@"No text to share");
2018-05-11 16:36:40 +02:00
break;
}
case OWSMessageCellType_ContactShare: {
OWSFailDebug(@"share contact not implemented.");
2018-05-11 16:36:40 +02:00
break;
}
}
}
- (void)shareMediaAction
{
switch (self.messageCellType) {
case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextMessage:
case OWSMessageCellType_OversizeTextMessage:
2018-05-11 16:36:40 +02:00
case OWSMessageCellType_ContactShare:
2018-08-27 16:29:51 +02:00
OWSFailDebug(@"No media to share.");
break;
2017-10-10 22:13:54 +02:00
case OWSMessageCellType_StillImage:
case OWSMessageCellType_AnimatedImage:
case OWSMessageCellType_Audio:
case OWSMessageCellType_Video:
case OWSMessageCellType_GenericAttachment:
[AttachmentSharing showShareUIForAttachment:self.attachmentStream];
break;
case OWSMessageCellType_DownloadingAttachment: {
OWSFailDebug(@"Can't share not-yet-downloaded attachment");
2017-10-10 22:13:54 +02:00
break;
}
}
}
- (BOOL)canSaveMedia
2017-10-10 22:13:54 +02:00
{
switch (self.messageCellType) {
case OWSMessageCellType_Unknown:
2017-10-10 22:13:54 +02:00
case OWSMessageCellType_TextMessage:
case OWSMessageCellType_OversizeTextMessage:
2018-05-02 17:01:23 +02:00
case OWSMessageCellType_ContactShare:
2017-10-10 22:13:54 +02:00
return NO;
case OWSMessageCellType_StillImage:
case OWSMessageCellType_AnimatedImage:
return YES;
case OWSMessageCellType_Audio:
return NO;
case OWSMessageCellType_Video:
return UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(self.attachmentStream.mediaURL.path);
case OWSMessageCellType_GenericAttachment:
return NO;
case OWSMessageCellType_DownloadingAttachment: {
return NO;
}
}
}
- (void)saveMediaAction
2017-10-10 22:13:54 +02:00
{
switch (self.messageCellType) {
case OWSMessageCellType_Unknown:
2017-10-10 22:13:54 +02:00
case OWSMessageCellType_TextMessage:
case OWSMessageCellType_OversizeTextMessage:
2018-05-02 17:01:23 +02:00
case OWSMessageCellType_ContactShare:
OWSFailDebug(@"Cannot save text data.");
2017-10-10 22:13:54 +02:00
break;
case OWSMessageCellType_StillImage:
case OWSMessageCellType_AnimatedImage: {
NSData *data = [NSData dataWithContentsOfURL:[self.attachmentStream mediaURL]];
if (!data) {
OWSFailDebug(@"Could not load image data");
2017-10-10 22:13:54 +02:00
return;
}
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library writeImageDataToSavedPhotosAlbum:data
metadata:nil
completionBlock:^(NSURL *assetURL, NSError *error) {
if (error) {
OWSLogWarn(@"Error Saving image to photo album: %@", error);
2017-10-10 22:13:54 +02:00
}
}];
break;
}
case OWSMessageCellType_Audio:
OWSFailDebug(@"Cannot save media data.");
2017-10-10 22:13:54 +02:00
break;
case OWSMessageCellType_Video:
if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(self.attachmentStream.mediaURL.path)) {
UISaveVideoAtPathToSavedPhotosAlbum(self.attachmentStream.mediaURL.path, self, nil, nil);
} else {
OWSFailDebug(@"Could not save incompatible video data.");
2017-10-10 22:13:54 +02:00
}
break;
case OWSMessageCellType_GenericAttachment:
OWSFailDebug(@"Cannot save media data.");
2017-10-10 22:13:54 +02:00
break;
case OWSMessageCellType_DownloadingAttachment: {
OWSFailDebug(@"Can't save not-yet-downloaded attachment");
2017-10-10 22:13:54 +02:00
break;
}
}
}
- (void)deleteAction
{
[self.interaction remove];
}
2018-03-29 17:25:19 +02:00
- (BOOL)hasBodyTextActionContent
{
2018-03-29 17:25:19 +02:00
return self.hasBodyText && self.displayableBodyText.fullText.length > 0;
}
- (BOOL)hasMediaActionContent
2017-10-10 22:13:54 +02:00
{
switch (self.messageCellType) {
case OWSMessageCellType_Unknown:
2017-10-10 22:13:54 +02:00
case OWSMessageCellType_TextMessage:
case OWSMessageCellType_OversizeTextMessage:
2018-05-02 17:01:23 +02:00
case OWSMessageCellType_ContactShare:
return NO;
2017-10-10 22:13:54 +02:00
case OWSMessageCellType_StillImage:
case OWSMessageCellType_AnimatedImage:
case OWSMessageCellType_Audio:
case OWSMessageCellType_Video:
case OWSMessageCellType_GenericAttachment:
return self.attachmentStream != nil;
case OWSMessageCellType_DownloadingAttachment: {
return NO;
}
}
}
@end
NS_ASSUME_NONNULL_END