session-ios/Session/Signal/ConversationView/ConversationViewItem.m

1192 lines
42 KiB
Mathematica
Raw Normal View History

2017-10-10 22:13:54 +02:00
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
2017-10-10 22:13:54 +02:00
//
2020-11-12 03:20:28 +01:00
#import <CoreServices/CoreServices.h>
2017-10-10 22:13:54 +02:00
#import "ConversationViewItem.h"
2020-11-16 00:34:47 +01:00
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"
2019-05-02 23:58:48 +02:00
#import "Session-Swift.h"
2019-08-28 08:07:14 +02:00
#import "AnyPromise.h"
2020-11-11 07:45:50 +01:00
#import <SignalUtilitiesKit/OWSUnreadIndicator.h>
2020-11-23 00:24:40 +01:00
#import <SessionUtilitiesKit/NSData+Image.h>
2020-11-26 00:37:56 +01:00
#import <SessionUtilitiesKit/NSString+SSK.h>
2020-11-16 00:34:47 +01:00
2020-11-26 00:37:56 +01:00
#import <SessionMessagingKit/TSInteraction.h>
2020-11-25 06:15:16 +01:00
#import <SessionMessagingKit/SSKEnvironment.h>
2020-11-11 07:45:50 +01:00
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
2017-10-10 22:13:54 +02:00
NS_ASSUME_NONNULL_BEGIN
2017-10-12 19:48:09 +02:00
NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
{
switch (cellType) {
2019-02-25 17:20:06 +01:00
case OWSMessageCellType_TextOnlyMessage:
return @"OWSMessageCellType_TextOnlyMessage";
2017-10-12 19:48:09 +02:00
case OWSMessageCellType_Audio:
return @"OWSMessageCellType_Audio";
case OWSMessageCellType_GenericAttachment:
return @"OWSMessageCellType_GenericAttachment";
case OWSMessageCellType_Unknown:
return @"OWSMessageCellType_Unknown";
2019-02-25 17:20:06 +01:00
case OWSMessageCellType_MediaMessage:
return @"OWSMessageCellType_MediaMessage";
case OWSMessageCellType_OversizeTextDownloading:
return @"OWSMessageCellType_OversizeTextDownloading";
2017-10-12 19:48:09 +02:00
}
}
#pragma mark -
2018-11-07 17:52:34 +01:00
@implementation ConversationMediaAlbumItem
- (instancetype)initWithAttachment:(TSAttachment *)attachment
attachmentStream:(nullable TSAttachmentStream *)attachmentStream
caption:(nullable NSString *)caption
mediaSize:(CGSize)mediaSize
{
OWSAssertDebug(attachment);
self = [super init];
if (!self) {
return self;
}
_attachment = attachment;
_attachmentStream = attachmentStream;
_caption = caption;
_mediaSize = mediaSize;
return self;
}
2019-01-14 22:17:59 +01:00
- (BOOL)isFailedDownload
{
if (![self.attachment isKindOfClass:[TSAttachmentPointer class]]) {
return NO;
}
TSAttachmentPointer *attachmentPointer = (TSAttachmentPointer *)self.attachment;
return attachmentPointer.state == TSAttachmentPointerStateFailed;
}
@end
#pragma mark -
2018-09-28 00:49:01 +02:00
@interface ConversationInteractionViewItem ()
2017-10-10 22:13:54 +02:00
@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;
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, nullable) OWSLinkPreview *linkPreview;
@property (nonatomic, nullable) TSAttachment *linkPreviewAttachment;
2018-11-07 17:52:34 +01:00
@property (nonatomic, nullable) NSArray<ConversationMediaAlbumItem *> *mediaAlbumItems;
2018-09-26 15:19:12 +02:00
@property (nonatomic, nullable) NSString *systemMessageText;
2018-09-25 22:05:53 +02:00
@property (nonatomic, nullable) TSThread *incomingMessageAuthorThread;
2018-09-26 15:19:12 +02:00
@property (nonatomic, nullable) NSString *authorConversationColorName;
2018-09-28 00:49:01 +02:00
@property (nonatomic, nullable) ConversationStyle *conversationStyle;
2017-10-10 22:13:54 +02:00
@end
#pragma mark -
2018-09-28 00:49:01 +02:00
@implementation ConversationInteractionViewItem
@synthesize shouldShowDate = _shouldShowDate;
@synthesize shouldShowSenderAvatar = _shouldShowSenderAvatar;
@synthesize unreadIndicator = _unreadIndicator;
@synthesize didCellMediaFailToLoad = _didCellMediaFailToLoad;
@synthesize interaction = _interaction;
@synthesize isFirstInCluster = _isFirstInCluster;
@synthesize isGroupThread = _isGroupThread;
@synthesize isLastInCluster = _isLastInCluster;
@synthesize lastAudioMessageView = _lastAudioMessageView;
@synthesize senderName = _senderName;
@synthesize shouldHideFooter = _shouldHideFooter;
2017-10-10 22:13:54 +02:00
- (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
{
OWSAssertDebug(interaction);
OWSAssertDebug(transaction);
OWSAssertDebug(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;
2018-09-26 15:19:12 +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
{
OWSAssertDebug(interaction);
2017-10-10 22:13:54 +02:00
_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.mediaAlbumItems = nil;
self.displayableQuotedText = nil;
self.quotedReply = nil;
self.contactShare = nil;
2018-08-09 16:47:43 +02:00
self.systemMessageText = nil;
self.authorConversationColorName = nil;
self.linkPreview = nil;
self.linkPreviewAttachment = nil;
2017-10-10 22:13:54 +02:00
[self clearCachedLayoutState];
[self ensureViewState:transaction];
2017-11-17 16:49:34 +01:00
}
2019-08-29 04:49:06 +02:00
- (OWSPrimaryStorage *)primaryStorage
{
return SSKEnvironment.shared.primaryStorage;
}
2018-10-31 15:05:24 +01:00
- (NSString *)itemId
{
return self.interaction.uniqueId;
}
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];
}
- (void)setIsFirstInCluster:(BOOL)isFirstInCluster
{
if (_isFirstInCluster == isFirstInCluster) {
return;
}
_isFirstInCluster = isFirstInCluster;
// Although this doesn't affect layout size, the view model use
// hasCachedLayoutState to detect which cells needs to be redrawn due to changes.
[self clearCachedLayoutState];
}
- (void)setIsLastInCluster:(BOOL)isLastInCluster
{
if (_isLastInCluster == isLastInCluster) {
return;
}
_isLastInCluster = isLastInCluster;
// Although this doesn't affect layout size, the view model use
// hasCachedLayoutState to detect which cells needs to be redrawn due to changes.
[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-12-10 17:59:00 +01:00
- (BOOL)hasCachedLayoutState {
return 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();
OWSAssertDebug(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();
OWSAssertDebug(self.interaction);
2017-10-10 22:13:54 +02:00
// 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;
case OWSInteractionType_TypingIndicator:
measurementCell = [OWSTypingIndicatorCell new];
break;
2017-10-10 22:13:54 +02:00
}
OWSAssertDebug(measurementCell);
2017-10-10 22:13:54 +02:00
measurementCellCache[cellCacheKey] = measurementCell;
}
return measurementCell;
}
2018-09-28 00:49:01 +02:00
- (CGFloat)vSpacingWithPreviousLayoutItem:(id<ConversationViewItem>)previousLayoutItem
2018-06-22 19:48:23 +02:00
{
OWSAssertDebug(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;
2020-07-20 07:39:21 +02:00
if ([incomingMessage.authorId isEqualToString:previousIncomingMessage.authorId]) {
2018-06-26 17:02:30 +02:00
return 2.f;
}
} else if (self.interaction.interactionType == OWSInteractionType_OutgoingMessage
2020-07-20 07:39:21 +02:00
&& previousLayoutItem.interaction.interactionType == OWSInteractionType_OutgoingMessage) {
2018-06-26 17:02:30 +02:00
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();
OWSAssertDebug(collectionView);
OWSAssertDebug(indexPath);
OWSAssertDebug(self.interaction);
2017-10-10 22:13:54 +02:00
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];
case OWSInteractionType_TypingIndicator:
return [collectionView dequeueReusableCellWithReuseIdentifier:[OWSTypingIndicatorCell cellReuseIdentifier]
forIndexPath:indexPath];
2017-10-10 22:13:54 +02:00
}
}
2018-11-07 18:39:40 +01:00
- (nullable TSAttachmentStream *)firstValidAlbumAttachment
2018-11-06 16:02:13 +01:00
{
2018-11-07 17:52:34 +01:00
OWSAssertDebug(self.mediaAlbumItems.count > 0);
2018-11-06 16:02:13 +01:00
// For now, use first valid attachment.
TSAttachmentStream *_Nullable attachmentStream = nil;
2018-11-07 17:52:34 +01:00
for (ConversationMediaAlbumItem *mediaAlbumItem in self.mediaAlbumItems) {
if (mediaAlbumItem.attachmentStream && mediaAlbumItem.attachmentStream.isValidVisualMedia) {
attachmentStream = mediaAlbumItem.attachmentStream;
2018-11-06 16:02:13 +01:00
break;
}
}
return attachmentStream;
}
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;
2020-10-02 06:44:33 +02:00
BOOL isPlaying = (audioPlaybackState == AudioPlaybackState_Playing);
[self.lastAudioMessageView setIsPlaying:isPlaying];
2017-10-10 22:13:54 +02:00
}
- (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;
2020-10-05 01:51:26 +02:00
[self.lastAudioMessageView setProgress:progress / duration];
2017-10-10 22:13:54 +02:00
}
2020-11-26 00:37:56 +01:00
- (void)showInvalidAudioFileAlert
{
OWSAssertIsOnMainThread();
[OWSAlerts
showErrorAlertWithMessage:NSLocalizedString(@"INVALID_AUDIO_FILE_ALERT_ERROR_MESSAGE",
@"Message for the alert indicating that an audio file is invalid.")];
}
#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
{
OWSAssertDebug(text);
OWSAssertDebug(interactionId.length > 0);
2017-10-10 22:13:54 +02:00
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
{
OWSAssertDebug(attachmentStream);
OWSAssertDebug(interactionId.length > 0);
NSString *displayableTextCacheKey = [@"oversize-body-" stringByAppendingString:interactionId];
return [self displayableTextForCacheKey:displayableTextCacheKey
textBlock:^{
2018-09-04 16:25:42 +02:00
NSData *textData =
[NSData dataWithContentsOfURL:attachmentStream.originalMediaURL];
NSString *text =
[[NSString alloc] initWithData:textData encoding:NSUTF8StringEncoding];
return text;
}];
}
- (DisplayableText *)displayableQuotedTextForText:(NSString *)text interactionId:(NSString *)interactionId
{
OWSAssertDebug(text);
OWSAssertDebug(interactionId.length > 0);
NSString *displayableTextCacheKey = [@"quoted-" stringByAppendingString:interactionId];
return [self displayableTextForCacheKey:displayableTextCacheKey
textBlock:^{
return text;
}];
}
- (DisplayableText *)displayableCaptionForText:(NSString *)text attachmentId:(NSString *)attachmentId
{
OWSAssertDebug(text);
OWSAssertDebug(attachmentId.length > 0);
NSString *displayableTextCacheKey = [@"attachment-caption-" stringByAppendingString:attachmentId];
return [self displayableTextForCacheKey:displayableTextCacheKey
textBlock:^{
return text;
}];
}
- (DisplayableText *)displayableTextForCacheKey:(NSString *)displayableTextCacheKey
textBlock:(NSString * (^_Nonnull)(void))textBlock
{
OWSAssertDebug(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
- (void)ensureViewState:(YapDatabaseReadTransaction *)transaction
2017-10-10 22:13:54 +02:00
{
2017-12-19 17:38:25 +01:00
OWSAssertIsOnMainThread();
OWSAssertDebug(transaction);
OWSAssertDebug(!self.hasViewState);
2018-08-09 16:47:43 +02:00
switch (self.interaction.interactionType) {
case OWSInteractionType_Unknown:
case OWSInteractionType_Offer:
case OWSInteractionType_TypingIndicator:
2018-08-09 16:47:43 +02:00
return;
case OWSInteractionType_Error:
case OWSInteractionType_Info:
case OWSInteractionType_Call:
self.systemMessageText = [self systemMessageTextWithTransaction:transaction];
OWSAssertDebug(self.systemMessageText.length > 0);
2018-08-09 16:47:43 +02:00
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
OWSAssertDebug([self.interaction isKindOfClass:[TSOutgoingMessage class]] ||
2018-08-09 16:47:43 +02:00
[self.interaction isKindOfClass:[TSIncomingMessage class]]);
2017-10-10 22:13:54 +02:00
self.hasViewState = YES;
TSMessage *message = (TSMessage *)self.interaction;
// Check for quoted replies _before_ media album handling,
// since that logic may exit early.
if (message.quotedMessage) {
self.quotedReply =
[OWSQuotedReplyModel quotedReplyWithQuotedMessage:message.quotedMessage threadId:message.uniqueThreadId transaction:transaction];
if (self.quotedReply.body.length > 0) {
self.displayableQuotedText =
[self displayableQuotedTextForText:self.quotedReply.body interactionId:message.uniqueId];
}
}
2019-02-20 18:32:41 +01:00
TSAttachment *_Nullable oversizeTextAttachment = [message oversizeTextAttachmentWithTransaction:transaction];
if ([oversizeTextAttachment isKindOfClass:[TSAttachmentStream class]]) {
2019-02-20 18:32:41 +01:00
TSAttachmentStream *oversizeTextAttachmentStream = (TSAttachmentStream *)oversizeTextAttachment;
self.displayableBodyText = [self displayableBodyTextForOversizeTextAttachment:oversizeTextAttachmentStream
interactionId:message.uniqueId];
} else if ([oversizeTextAttachment isKindOfClass:[TSAttachmentPointer class]]) {
TSAttachmentPointer *oversizeTextAttachmentPointer = (TSAttachmentPointer *)oversizeTextAttachment;
// TODO: Handle backup restore.
self.messageCellType = OWSMessageCellType_OversizeTextDownloading;
self.attachmentPointer = (TSAttachmentPointer *)oversizeTextAttachmentPointer;
return;
2019-02-23 01:37:03 +01:00
} else {
NSString *_Nullable bodyText = [message bodyTextWithTransaction:transaction];
if (bodyText) {
self.displayableBodyText = [self displayableBodyTextForText:bodyText interactionId:message.uniqueId];
}
2019-02-20 18:32:41 +01:00
}
NSArray<TSAttachment *> *mediaAttachments = [message mediaAttachmentsWithTransaction:transaction];
NSArray<ConversationMediaAlbumItem *> *mediaAlbumItems = [self albumItemsForMediaAttachments:mediaAttachments];
2019-02-23 01:37:03 +01:00
if (mediaAlbumItems.count > 0) {
2018-11-08 19:20:14 +01:00
if (mediaAlbumItems.count == 1) {
ConversationMediaAlbumItem *mediaAlbumItem = mediaAlbumItems.firstObject;
if (mediaAlbumItem.attachmentStream && !mediaAlbumItem.attachmentStream.isValidVisualMedia) {
OWSLogWarn(@"Treating invalid media as generic attachment.");
self.messageCellType = OWSMessageCellType_GenericAttachment;
return;
}
}
2018-11-07 17:52:34 +01:00
self.mediaAlbumItems = mediaAlbumItems;
2019-02-25 17:20:06 +01:00
self.messageCellType = OWSMessageCellType_MediaMessage;
return;
}
2019-02-20 18:32:41 +01:00
// Only media galleries should have more than one attachment.
2019-02-20 18:32:41 +01:00
OWSAssertDebug(mediaAttachments.count <= 1);
2019-02-23 01:37:03 +01:00
2019-02-20 18:32:41 +01:00
TSAttachment *_Nullable mediaAttachment = mediaAttachments.firstObject;
if (mediaAttachment) {
if ([mediaAttachment isKindOfClass:[TSAttachmentStream class]]) {
self.attachmentStream = (TSAttachmentStream *)mediaAttachment;
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;
}
2019-02-20 18:32:41 +01:00
} else if (self.messageCellType == OWSMessageCellType_Unknown) {
self.messageCellType = OWSMessageCellType_GenericAttachment;
2017-10-10 22:13:54 +02:00
}
2019-02-20 18:32:41 +01:00
} else if ([mediaAttachment isKindOfClass:[TSAttachmentPointer class]]) {
if ([mediaAttachment isAudio]) {
self.audioDurationSeconds = 0;
self.messageCellType = OWSMessageCellType_Audio;
} else {
self.messageCellType = OWSMessageCellType_GenericAttachment;
}
2019-02-20 18:32:41 +01:00
self.attachmentPointer = (TSAttachmentPointer *)mediaAttachment;
} else {
OWSFailDebug(@"Unknown attachment type");
2017-10-10 22:13:54 +02:00
}
}
2019-02-20 18:32:41 +01:00
if (self.hasBodyText) {
if (self.messageCellType == OWSMessageCellType_Unknown) {
2019-09-17 02:26:22 +02:00
// OWSAssertDebug(message.attachmentIds.count == 0
// || (message.attachmentIds.count == 1 &&
// [message oversizeTextAttachmentWithTransaction:transaction] != nil));
2019-02-25 17:20:06 +01:00
self.messageCellType = OWSMessageCellType_TextOnlyMessage;
}
OWSAssertDebug(self.displayableBodyText);
2017-10-10 22:13:54 +02:00
}
2019-02-20 18:32:41 +01:00
if (self.hasBodyText && message.linkPreview) {
self.linkPreview = message.linkPreview;
2020-08-19 08:41:25 +02:00
if (message.linkPreview.imageAttachmentId && message.linkPreview.imageAttachmentId.length > 0) {
TSAttachment *_Nullable linkPreviewAttachment =
[TSAttachment fetchObjectWithUniqueID:message.linkPreview.imageAttachmentId transaction:transaction];
if (!linkPreviewAttachment) {
OWSFailDebug(@"Could not load link preview image attachment.");
} else if (!linkPreviewAttachment.isImage) {
OWSFailDebug(@"Link preview attachment isn't an image.");
} else if ([linkPreviewAttachment isKindOfClass:[TSAttachmentStream class]]) {
TSAttachmentStream *attachmentStream = (TSAttachmentStream *)linkPreviewAttachment;
if (!attachmentStream.isValidImage) {
OWSFailDebug(@"Link preview image attachment isn't valid.");
} else {
self.linkPreviewAttachment = linkPreviewAttachment;
}
} else {
self.linkPreviewAttachment = linkPreviewAttachment;
}
}
}
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);
2019-02-25 17:20:06 +01:00
self.messageCellType = OWSMessageCellType_TextOnlyMessage;
2018-03-29 17:25:19 +02:00
self.displayableBodyText = [[DisplayableText alloc] initWithFullText:@"" displayText:@"" isTextTruncated:NO];
}
2017-10-10 22:13:54 +02:00
}
2018-08-09 16:47:43 +02:00
- (NSArray<ConversationMediaAlbumItem *> *)albumItemsForMediaAttachments:(NSArray<TSAttachment *> *)attachments
{
OWSAssertIsOnMainThread();
2018-11-07 17:52:34 +01:00
NSMutableArray<ConversationMediaAlbumItem *> *mediaAlbumItems = [NSMutableArray new];
for (TSAttachment *attachment in attachments) {
if (!attachment.isVisualMedia) {
// Well behaving clients should not send a mix of visual media (like JPG) and non-visual media (like PDF's)
// Since we're not coped to handle a mix of media, return @[]
OWSAssertDebug(mediaAlbumItems.count == 0);
return @[];
}
NSString *_Nullable caption = (attachment.caption
? [self displayableCaptionForText:attachment.caption attachmentId:attachment.uniqueId].displayText
: nil);
if (![attachment isKindOfClass:[TSAttachmentStream class]]) {
TSAttachmentPointer *attachmentPointer = (TSAttachmentPointer *)attachment;
CGSize mediaSize = CGSizeZero;
if (attachmentPointer.mediaSize.width > 0 && attachmentPointer.mediaSize.height > 0) {
mediaSize = attachmentPointer.mediaSize;
}
2018-11-07 17:52:34 +01:00
[mediaAlbumItems addObject:[[ConversationMediaAlbumItem alloc] initWithAttachment:attachment
attachmentStream:nil
caption:caption
mediaSize:mediaSize]];
continue;
}
TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment;
if (![attachmentStream isValidVisualMedia]) {
OWSLogWarn(@"Filtering invalid media.");
2018-11-07 17:52:34 +01:00
[mediaAlbumItems addObject:[[ConversationMediaAlbumItem alloc] initWithAttachment:attachment
attachmentStream:nil
caption:caption
mediaSize:CGSizeZero]];
continue;
}
CGSize mediaSize = [attachmentStream imageSize];
if (mediaSize.width <= 0 || mediaSize.height <= 0) {
OWSLogWarn(@"Filtering media with invalid size.");
2018-11-07 17:52:34 +01:00
[mediaAlbumItems addObject:[[ConversationMediaAlbumItem alloc] initWithAttachment:attachment
attachmentStream:nil
caption:caption
mediaSize:CGSizeZero]];
continue;
}
2018-11-07 17:52:34 +01:00
ConversationMediaAlbumItem *mediaAlbumItem =
[[ConversationMediaAlbumItem alloc] initWithAttachment:attachment
attachmentStream:attachmentStream
caption:caption
mediaSize:mediaSize];
[mediaAlbumItems addObject:mediaAlbumItem];
}
2018-11-07 17:52:34 +01:00
return mediaAlbumItems;
}
2018-08-09 16:47:43 +02:00
- (NSString *)systemMessageTextWithTransaction:(YapDatabaseReadTransaction *)transaction
{
OWSAssertDebug(transaction);
2018-08-09 16:47:43 +02:00
switch (self.interaction.interactionType) {
case OWSInteractionType_Error: {
TSErrorMessage *errorMessage = (TSErrorMessage *)self.interaction;
return [errorMessage previewTextWithTransaction:transaction];
}
case OWSInteractionType_Info: {
TSInfoMessage *infoMessage = (TSInfoMessage *)self.interaction;
2020-11-16 00:34:47 +01:00
return [infoMessage previewTextWithTransaction:transaction];
2018-08-09 16:47:43 +02:00
}
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();
OWSAssertDebug(self.hasViewState);
2017-10-10 22:13:54 +02:00
OWSAssertDebug(_displayableBodyText);
OWSAssertDebug(_displayableBodyText.displayText);
OWSAssertDebug(_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();
OWSAssertDebug(self.hasViewState);
2017-10-10 22:13:54 +02:00
return _attachmentStream;
}
- (nullable TSAttachmentPointer *)attachmentPointer
{
2017-12-19 17:38:25 +01:00
OWSAssertIsOnMainThread();
OWSAssertDebug(self.hasViewState);
2017-10-10 22:13:54 +02:00
return _attachmentPointer;
}
- (nullable DisplayableText *)displayableQuotedText
{
OWSAssertIsOnMainThread();
OWSAssertDebug(self.hasViewState);
OWSAssertDebug(_displayableQuotedText);
OWSAssertDebug(_displayableQuotedText.displayText);
OWSAssertDebug(_displayableQuotedText.fullText);
return _displayableQuotedText;
}
- (void)copyTextAction
2017-10-10 22:13:54 +02:00
{
if (self.attachmentPointer != nil) {
OWSFailDebug(@"Can't copy not-yet-downloaded attachment");
return;
}
2017-10-10 22:13:54 +02:00
switch (self.messageCellType) {
2019-02-25 17:20:06 +01:00
case OWSMessageCellType_TextOnlyMessage:
case OWSMessageCellType_Audio:
2019-02-25 17:20:06 +01:00
case OWSMessageCellType_MediaMessage:
case OWSMessageCellType_GenericAttachment: {
OWSAssertDebug(self.displayableBodyText);
2018-03-29 17:25:19 +02:00
[UIPasteboard.generalPasteboard setString:self.displayableBodyText.fullText];
2017-10-10 22:13:54 +02:00
break;
}
case OWSMessageCellType_Unknown: {
OWSFailDebug(@"No text to copy");
break;
}
case OWSMessageCellType_OversizeTextDownloading:
OWSFailDebug(@"Can't copy not-yet-downloaded attachment");
return;
}
}
- (void)copyMediaAction
{
if (self.attachmentPointer != nil) {
OWSFailDebug(@"Can't copy not-yet-downloaded attachment");
return;
}
switch (self.messageCellType) {
case OWSMessageCellType_Unknown:
2019-02-25 17:20:06 +01:00
case OWSMessageCellType_TextOnlyMessage:
2017-10-10 22:13:54 +02:00
case OWSMessageCellType_Audio:
case OWSMessageCellType_GenericAttachment: {
2018-11-06 16:02:13 +01:00
[self copyAttachmentToPasteboard:self.attachmentStream];
2017-10-10 22:13:54 +02:00
break;
}
2019-02-25 17:20:06 +01:00
case OWSMessageCellType_MediaMessage: {
2018-11-08 19:20:14 +01:00
if (self.mediaAlbumItems.count == 1) {
ConversationMediaAlbumItem *mediaAlbumItem = self.mediaAlbumItems.firstObject;
if (mediaAlbumItem.attachmentStream && mediaAlbumItem.attachmentStream.isValidVisualMedia) {
[self copyAttachmentToPasteboard:mediaAlbumItem.attachmentStream];
return;
}
}
2018-11-07 18:39:40 +01:00
OWSFailDebug(@"Can't copy media album");
break;
}
case OWSMessageCellType_OversizeTextDownloading:
OWSFailDebug(@"Can't copy not-yet-downloaded attachment");
return;
2017-10-10 22:13:54 +02:00
}
}
2018-11-06 16:02:13 +01:00
- (void)copyAttachmentToPasteboard:(TSAttachmentStream *)attachment
2017-10-10 22:13:54 +02:00
{
2018-11-06 16:02:13 +01:00
OWSAssertDebug(attachment);
NSString *utiType = [MIMETypeUtil utiTypeForMIMEType:attachment.contentType];
if (!utiType) {
OWSFailDebug(@"Unknown MIME type: %@", attachment.contentType);
utiType = (NSString *)kUTTypeGIF;
}
2018-11-06 16:02:13 +01:00
NSData *data = [NSData dataWithContentsOfURL:[attachment originalMediaURL]];
if (!data) {
OWSFailDebug(@"Could not load attachment data");
return;
}
[UIPasteboard.generalPasteboard setData:data forPasteboardType:utiType];
}
- (void)shareMediaAction
{
if (self.attachmentPointer != nil) {
OWSFailDebug(@"Can't share not-yet-downloaded attachment");
return;
}
switch (self.messageCellType) {
case OWSMessageCellType_Unknown:
2019-02-25 17:20:06 +01:00
case OWSMessageCellType_TextOnlyMessage:
2017-10-10 22:13:54 +02:00
case OWSMessageCellType_Audio:
case OWSMessageCellType_GenericAttachment:
[AttachmentSharing showShareUIForAttachment:self.attachmentStream];
break;
2019-02-25 17:20:06 +01:00
case OWSMessageCellType_MediaMessage: {
// TODO: We need a "canShareMediaAction" method.
2018-11-07 17:52:34 +01:00
OWSAssertDebug(self.mediaAlbumItems);
NSMutableArray<TSAttachmentStream *> *attachmentStreams = [NSMutableArray new];
for (ConversationMediaAlbumItem *mediaAlbumItem in self.mediaAlbumItems) {
if (mediaAlbumItem.attachmentStream && mediaAlbumItem.attachmentStream.isValidVisualMedia) {
[attachmentStreams addObject:mediaAlbumItem.attachmentStream];
}
}
if (attachmentStreams.count < 1) {
2018-11-07 18:39:40 +01:00
OWSFailDebug(@"Can't share media album; no valid items.");
return;
}
[AttachmentSharing showShareUIForAttachments:attachmentStreams completion:nil];
break;
}
case OWSMessageCellType_OversizeTextDownloading:
OWSFailDebug(@"Can't share not-yet-downloaded attachment");
return;
2017-10-10 22:13:54 +02:00
}
}
2018-11-06 16:08:14 +01:00
- (BOOL)canCopyMedia
2017-10-10 22:13:54 +02:00
{
if (self.attachmentPointer != nil) {
2019-03-18 19:24:33 +01:00
// The attachment is still downloading.
return NO;
}
2017-10-10 22:13:54 +02:00
switch (self.messageCellType) {
case OWSMessageCellType_Unknown:
2019-02-25 17:20:06 +01:00
case OWSMessageCellType_TextOnlyMessage:
2017-10-10 22:13:54 +02:00
case OWSMessageCellType_Audio:
return NO;
case OWSMessageCellType_GenericAttachment:
2019-02-25 17:20:06 +01:00
case OWSMessageCellType_MediaMessage: {
2018-11-08 19:20:14 +01:00
if (self.mediaAlbumItems.count == 1) {
ConversationMediaAlbumItem *mediaAlbumItem = self.mediaAlbumItems.firstObject;
if (mediaAlbumItem.attachmentStream && mediaAlbumItem.attachmentStream.isValidVisualMedia) {
return YES;
}
}
2017-10-10 22:13:54 +02:00
return NO;
2018-11-08 19:20:14 +01:00
}
case OWSMessageCellType_OversizeTextDownloading:
return NO;
2018-11-06 16:08:14 +01:00
}
}
- (BOOL)canSaveMedia
{
if (self.attachmentPointer != nil) {
2019-03-18 19:24:33 +01:00
// The attachment is still downloading.
return NO;
}
2018-11-06 16:08:14 +01:00
switch (self.messageCellType) {
case OWSMessageCellType_Unknown:
2019-02-25 17:20:06 +01:00
case OWSMessageCellType_TextOnlyMessage:
2018-11-06 16:08:14 +01:00
case OWSMessageCellType_Audio:
return NO;
case OWSMessageCellType_GenericAttachment:
2017-10-10 22:13:54 +02:00
return NO;
2019-02-25 17:20:06 +01:00
case OWSMessageCellType_MediaMessage: {
2018-11-07 17:52:34 +01:00
for (ConversationMediaAlbumItem *mediaAlbumItem in self.mediaAlbumItems) {
if (!mediaAlbumItem.attachmentStream) {
continue;
}
2018-11-08 19:20:14 +01:00
if (!mediaAlbumItem.attachmentStream.isValidVisualMedia) {
continue;
}
2018-11-07 17:52:34 +01:00
if (mediaAlbumItem.attachmentStream.isImage || mediaAlbumItem.attachmentStream.isAnimated) {
return YES;
}
2018-11-07 17:52:34 +01:00
if (mediaAlbumItem.attachmentStream.isVideo) {
2018-11-06 16:02:13 +01:00
if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(
2018-11-07 17:52:34 +01:00
mediaAlbumItem.attachmentStream.originalFilePath)) {
return YES;
}
}
}
return NO;
}
case OWSMessageCellType_OversizeTextDownloading:
return NO;
2017-10-10 22:13:54 +02:00
}
}
- (void)saveMediaAction
2017-10-10 22:13:54 +02:00
{
if (self.attachmentPointer != nil) {
OWSFailDebug(@"Can't save not-yet-downloaded attachment");
return;
}
2017-10-10 22:13:54 +02:00
switch (self.messageCellType) {
case OWSMessageCellType_Unknown:
2019-02-25 17:20:06 +01:00
case OWSMessageCellType_TextOnlyMessage:
2017-10-10 22:13:54 +02:00
case OWSMessageCellType_Audio:
OWSFailDebug(@"Cannot save media 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;
2019-02-25 17:20:06 +01:00
case OWSMessageCellType_MediaMessage: {
2019-01-22 22:50:49 +01:00
[self saveMediaAlbumItems];
break;
}
case OWSMessageCellType_OversizeTextDownloading:
OWSFailDebug(@"Can't save not-yet-downloaded attachment");
return;
2019-01-22 22:50:49 +01:00
}
}
- (void)saveMediaAlbumItems
{
// We need to do these writes serially to avoid "write busy" errors
// from too many concurrent asset saves.
[self saveMediaAlbumItems:[self.mediaAlbumItems mutableCopy]];
}
- (void)saveMediaAlbumItems:(NSMutableArray<ConversationMediaAlbumItem *> *)mediaAlbumItems
{
if (mediaAlbumItems.count < 1) {
return;
}
ConversationMediaAlbumItem *mediaAlbumItem = mediaAlbumItems.firstObject;
[mediaAlbumItems removeObjectAtIndex:0];
if (!mediaAlbumItem.attachmentStream || !mediaAlbumItem.attachmentStream.isValidVisualMedia) {
// Skip this item.
} else if (mediaAlbumItem.attachmentStream.isImage || mediaAlbumItem.attachmentStream.isAnimated) {
[[PHPhotoLibrary sharedPhotoLibrary]
performChanges:^{
[PHAssetChangeRequest
creationRequestForAssetFromImageAtFileURL:mediaAlbumItem.attachmentStream.originalMediaURL];
}
completionHandler:^(BOOL success, NSError *error) {
if (error || !success) {
OWSFailDebug(@"Image save failed: %@", error);
}
2019-01-22 22:50:49 +01:00
[self saveMediaAlbumItems:mediaAlbumItems];
}];
return;
} else if (mediaAlbumItem.attachmentStream.isVideo) {
[[PHPhotoLibrary sharedPhotoLibrary]
performChanges:^{
[PHAssetChangeRequest
creationRequestForAssetFromVideoAtFileURL:mediaAlbumItem.attachmentStream.originalMediaURL];
}
2019-01-22 22:50:49 +01:00
completionHandler:^(BOOL success, NSError *error) {
if (error || !success) {
OWSFailDebug(@"Video save failed: %@", error);
}
[self saveMediaAlbumItems:mediaAlbumItems];
}];
return;
2017-10-10 22:13:54 +02:00
}
2019-01-22 22:50:49 +01:00
return [self saveMediaAlbumItems:mediaAlbumItems];
2017-10-10 22:13:54 +02:00
}
- (void)deleteAction
{
[LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self.interaction removeWithTransaction:transaction];
if (self.interaction.interactionType == OWSInteractionType_OutgoingMessage) {
[LKStorage.shared cancelPendingMessageSendJobIfNeededForMessage:self.interaction.timestamp using:transaction];
}
}];
2019-08-28 08:07:14 +02:00
if (self.isGroupThread) {
TSGroupThread *groupThread = (TSGroupThread *)self.interaction.thread;
// Only allow deletion on incoming and outgoing messages
OWSInteractionType interationType = self.interaction.interactionType;
2019-09-02 05:27:12 +02:00
if (interationType != OWSInteractionType_IncomingMessage && interationType != OWSInteractionType_OutgoingMessage) return;
// Make sure it's an open group message
TSMessage *message = (TSMessage *)self.interaction;
2020-04-20 08:53:40 +02:00
if (!message.isOpenGroupMessage) return;
// Get the open group
SNOpenGroup *openGroup = [LKStorage.shared getOpenGroupForThreadID:groupThread.uniqueId];
if (openGroup == nil) return;
// If it's an incoming message the user must have moderator status
if (self.interaction.interactionType == OWSInteractionType_IncomingMessage) {
NSString *userPublicKey = [LKStorage.shared getUserPublicKey];
if (![SNOpenGroupAPI isUserModerator:userPublicKey forChannel:openGroup.channel onServer:openGroup.server]) { return; }
}
2019-08-28 08:07:14 +02:00
// Delete the message
BOOL wasSentByUser = (interationType == OWSInteractionType_OutgoingMessage);
[[SNOpenGroupAPI deleteMessageWithID:message.openGroupServerMessageID forGroup:openGroup.channel onServer:openGroup.server isSentByUser:wasSentByUser].catch(^(NSError *error) {
2019-09-02 05:27:12 +02:00
// Roll back
2019-08-28 08:07:14 +02:00
[self.interaction save];
}) retainUntilComplete];
}
2017-10-10 22:13:54 +02:00
}
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
{
if (self.attachmentPointer != nil) {
2019-03-18 19:24:33 +01:00
// The attachment is still downloading.
return NO;
}
2017-10-10 22:13:54 +02:00
switch (self.messageCellType) {
case OWSMessageCellType_Unknown:
2019-02-25 17:20:06 +01:00
case OWSMessageCellType_TextOnlyMessage:
2017-10-10 22:13:54 +02:00
case OWSMessageCellType_Audio:
case OWSMessageCellType_GenericAttachment:
return self.attachmentStream != nil;
2019-02-25 17:20:06 +01:00
case OWSMessageCellType_MediaMessage:
2018-11-07 18:39:40 +01:00
return self.firstValidAlbumAttachment != nil;
case OWSMessageCellType_OversizeTextDownloading:
return NO;
2017-10-10 22:13:54 +02:00
}
}
2019-01-15 22:39:01 +01:00
- (BOOL)mediaAlbumHasFailedAttachment
{
2019-02-25 17:20:06 +01:00
OWSAssertDebug(self.messageCellType == OWSMessageCellType_MediaMessage);
2019-01-15 22:39:01 +01:00
OWSAssertDebug(self.mediaAlbumItems.count > 0);
for (ConversationMediaAlbumItem *mediaAlbumItem in self.mediaAlbumItems) {
if (mediaAlbumItem.isFailedDownload) {
return YES;
}
}
return NO;
}
2019-09-02 05:27:12 +02:00
- (BOOL)userCanDeleteGroupMessage
{
if (!self.isGroupThread) return false;
2019-09-02 05:27:12 +02:00
// Ensure the thread is a public chat and not an RSS feed
TSGroupThread *groupThread = (TSGroupThread *)self.interaction.thread;
// Only allow deletion on incoming and outgoing messages
OWSInteractionType interationType = self.interaction.interactionType;
if (interationType != OWSInteractionType_OutgoingMessage && interationType != OWSInteractionType_IncomingMessage) return false;
// Make sure it's a public chat message
TSMessage *message = (TSMessage *)self.interaction;
2020-06-23 03:45:22 +02:00
if (!message.isOpenGroupMessage) return true;
2019-10-14 05:40:18 +02:00
// Ensure we have the details needed to contact the server
2020-11-19 05:24:09 +01:00
SNOpenGroup *publicChat = [LKStorage.shared getOpenGroupForThreadID:groupThread.uniqueId];
2020-06-23 03:51:26 +02:00
if (publicChat == nil) return true;
2019-08-29 04:49:06 +02:00
if (interationType == OWSInteractionType_IncomingMessage) {
2019-11-14 06:47:02 +01:00
// Only allow deletion on incoming messages if the user has moderation permission
2021-01-11 05:01:59 +01:00
return [SNOpenGroupAPI isUserModerator:[SNGeneralUtilities getUserPublicKey] forChannel:publicChat.channel onServer:publicChat.server];
2020-08-28 05:48:27 +02:00
} else {
return YES;
2019-08-29 04:49:06 +02:00
}
}
2017-10-10 22:13:54 +02:00
@end
NS_ASSUME_NONNULL_END