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

687 lines
24 KiB
Mathematica
Raw Normal View History

2017-10-10 22:13:54 +02:00
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "ConversationViewItem.h"
2017-10-18 20:53:31 +02:00
#import "NSString+OWS.h"
2017-10-10 22:13:54 +02:00
#import "OWSAudioMessageView.h"
#import "OWSContactOffersCell.h"
2017-10-17 06:05:29 +02:00
#import "OWSMessageCell.h"
2017-10-10 22:13:54 +02:00
#import "OWSSystemMessageCell.h"
#import "OWSUnreadIndicatorCell.h"
#import "Signal-Swift.h"
#import <AssetsLibrary/AssetsLibrary.h>
#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";
}
}
#pragma mark -
2017-10-10 22:13:54 +02:00
@interface ConversationViewItem ()
@property (nonatomic, nullable) NSValue *cachedCellSize;
#pragma mark - OWSAudioAttachmentPlayerDelegate
@property (nonatomic) AudioPlaybackState audioPlaybackState;
@property (nonatomic) CGFloat audioProgressSeconds;
#pragma mark - View State
@property (nonatomic) BOOL hasViewState;
@property (nonatomic) OWSMessageCellType messageCellType;
@property (nonatomic, nullable) DisplayableText *displayableText;
2017-10-10 22:13:54 +02:00
@property (nonatomic, nullable) TSAttachmentStream *attachmentStream;
@property (nonatomic, nullable) TSAttachmentPointer *attachmentPointer;
@property (nonatomic) CGSize contentSize;
@end
#pragma mark -
@implementation ConversationViewItem
- (instancetype)initWithInteraction:(TSInteraction *)interaction
isGroupThread:(BOOL)isGroupThread
transaction:(YapDatabaseReadTransaction *)transaction
2017-10-10 22:13:54 +02:00
{
self = [super init];
if (!self) {
return self;
}
_interaction = interaction;
_isGroupThread = isGroupThread;
self.row = NSNotFound;
2017-10-17 06:05:29 +02:00
self.previousRow = NSNotFound;
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;
self.displayableText = nil;
2017-10-12 19:48:09 +02:00
self.attachmentStream = nil;
self.attachmentPointer = nil;
self.contentSize = CGSizeZero;
2017-10-10 22:13:54 +02:00
[self clearCachedLayoutState];
[self ensureViewState:transaction];
2017-10-10 22:13:54 +02:00
}
- (void)setShouldShowDate:(BOOL)shouldShowDate
{
if (_shouldShowDate == shouldShowDate) {
return;
}
_shouldShowDate = shouldShowDate;
[self clearCachedLayoutState];
}
- (void)setShouldHideRecipientStatus:(BOOL)shouldHideRecipientStatus
{
if (_shouldHideRecipientStatus == shouldHideRecipientStatus) {
return;
}
_shouldHideRecipientStatus = shouldHideRecipientStatus;
[self clearCachedLayoutState];
}
2017-10-10 22:13:54 +02:00
- (void)clearCachedLayoutState
{
self.cachedCellSize = nil;
}
2017-10-11 15:44:10 +02:00
- (CGSize)cellSizeForViewWidth:(int)viewWidth contentWidth:(int)contentWidth
2017-10-10 22:13:54 +02:00
{
OWSAssert([NSThread isMainThread]);
if (!self.cachedCellSize) {
ConversationViewCell *_Nullable measurementCell = [self measurementCell];
measurementCell.viewItem = self;
2017-10-12 22:19:07 +02:00
CGSize cellSize = [measurementCell cellSizeForViewWidth:viewWidth contentWidth:contentWidth];
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
}
- (ConversationViewLayoutAlignment)layoutAlignment
{
switch (self.interaction.interactionType) {
case OWSInteractionType_Unknown:
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Unknown interaction type: %@", self.logTag, self.interaction.debugDescription);
2017-10-10 22:13:54 +02:00
return ConversationViewLayoutAlignment_Center;
case OWSInteractionType_IncomingMessage:
return ConversationViewLayoutAlignment_Incoming;
case OWSInteractionType_OutgoingMessage:
return ConversationViewLayoutAlignment_Outgoing;
case OWSInteractionType_Error:
case OWSInteractionType_Info:
case OWSInteractionType_Call:
return ConversationViewLayoutAlignment_Center;
case OWSInteractionType_UnreadIndicator:
return ConversationViewLayoutAlignment_FullWidth;
case OWSInteractionType_Offer:
return ConversationViewLayoutAlignment_Center;
}
}
- (nullable ConversationViewCell *)measurementCell
{
OWSAssert([NSThread isMainThread]);
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:
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Unknown interaction type.", self.logTag);
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:
case OWSInteractionType_Call:
measurementCell = [OWSSystemMessageCell new];
break;
case OWSInteractionType_UnreadIndicator:
measurementCell = [OWSUnreadIndicatorCell new];
break;
case OWSInteractionType_Offer:
measurementCell = [OWSContactOffersCell new];
break;
}
OWSAssert(measurementCell);
measurementCellCache[cellCacheKey] = measurementCell;
}
return measurementCell;
}
- (ConversationViewCell *)dequeueCellForCollectionView:(UICollectionView *)collectionView
indexPath:(NSIndexPath *)indexPath
{
OWSAssert([NSThread isMainThread]);
OWSAssert(collectionView);
OWSAssert(indexPath);
OWSAssert(self.interaction);
switch (self.interaction.interactionType) {
case OWSInteractionType_Unknown:
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Unknown interaction type.", self.logTag);
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:
case OWSInteractionType_Call:
return [collectionView dequeueReusableCellWithReuseIdentifier:[OWSSystemMessageCell cellReuseIdentifier]
forIndexPath:indexPath];
case OWSInteractionType_UnreadIndicator:
return [collectionView dequeueReusableCellWithReuseIdentifier:[OWSUnreadIndicatorCell cellReuseIdentifier]
forIndexPath:indexPath];
case OWSInteractionType_Offer:
return [collectionView dequeueReusableCellWithReuseIdentifier:[OWSContactOffersCell cellReuseIdentifier]
forIndexPath:indexPath];
}
}
#pragma mark - OWSAudioAttachmentPlayerDelegate
- (void)setAudioPlaybackState:(AudioPlaybackState)audioPlaybackState
{
_audioPlaybackState = audioPlaybackState;
[self.lastAudioMessageView updateContents];
}
- (void)setAudioProgress:(CGFloat)progress duration:(CGFloat)duration
{
OWSAssert([NSThread isMainThread]);
self.audioProgressSeconds = progress;
if (duration > 0) {
self.audioDurationSeconds = @(duration);
}
[self.lastAudioMessageView updateContents];
}
#pragma mark - View State
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.
2017-10-10 22:13:54 +02:00
- (NSCache *)displayableTextCache
{
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;
}
- (DisplayableText *)displayableTextForText:(NSString *)text interactionId:(NSString *)interactionId
2017-10-10 22:13:54 +02:00
{
OWSAssert(text);
OWSAssert(interactionId.length > 0);
return [self displayableTextForInteractionId:interactionId
textBlock:^{
return text;
}];
}
- (DisplayableText *)displayableTextForAttachmentStream:(TSAttachmentStream *)attachmentStream
interactionId:(NSString *)interactionId
{
OWSAssert(attachmentStream);
OWSAssert(interactionId.length > 0);
return [self displayableTextForInteractionId:interactionId
textBlock:^{
NSData *textData = [NSData dataWithContentsOfURL:attachmentStream.mediaURL];
NSString *text =
[[NSString alloc] initWithData:textData encoding:NSUTF8StringEncoding];
return text;
}];
}
- (DisplayableText *)displayableTextForInteractionId:(NSString *)interactionId
2017-11-08 20:04:51 +01:00
textBlock:(NSString * (^_Nonnull)(void))textBlock
{
OWSAssert(interactionId.length > 0);
DisplayableText *_Nullable displayableText = [[self displayableTextCache] objectForKey:interactionId];
2017-10-10 22:13:54 +02:00
if (!displayableText) {
NSString *text = textBlock();
// Only show up to N characters of text.
2017-11-16 22:59:25 +01:00
const NSUInteger kMaxTextDisplayLength = 1024;
2017-10-26 18:09:36 +02:00
NSString *_Nullable fullText = [DisplayableText displayableText:text];
BOOL isTextTruncated = NO;
if (!fullText) {
2017-10-26 18:09:36 +02:00
fullText = @"";
}
2017-10-26 18:09:36 +02:00
NSString *_Nullable displayText = fullText;
if (displayText.length > kMaxTextDisplayLength) {
2017-10-10 22:13:54 +02:00
// Trim whitespace before _AND_ after slicing the snipper from the string.
NSString *snippet = [[displayText substringWithRange:NSMakeRange(0, kMaxTextDisplayLength)] ows_stripped];
displayText = [NSString stringWithFormat:NSLocalizedString(@"OVERSIZE_TEXT_DISPLAY_FORMAT",
@"A display format for oversize text messages."),
snippet];
2017-10-26 18:09:36 +02:00
isTextTruncated = YES;
2017-10-10 22:13:54 +02:00
}
if (!displayText) {
2017-10-26 18:09:36 +02:00
displayText = @"";
2017-10-10 22:13:54 +02:00
}
2017-10-26 18:09:36 +02:00
displayableText =
[[DisplayableText alloc] initWithFullText:fullText displayText:displayText isTextTruncated:isTextTruncated];
2017-10-10 22:13:54 +02:00
[[self displayableTextCache] setObject:displayableText forKey:interactionId];
}
return displayableText;
}
- (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
{
OWSAssert([NSThread isMainThread]);
OWSAssert(transaction);
OWSAssert(!self.hasViewState);
if (![self.interaction isKindOfClass:[TSOutgoingMessage class]]
&& ![self.interaction isKindOfClass:[TSIncomingMessage class]]) {
// Only text & attachment messages have "view state".
return;
}
2017-10-10 22:13:54 +02:00
self.hasViewState = YES;
TSMessage *message = (TSMessage *)self.interaction;
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;
self.displayableText =
[self displayableTextForAttachmentStream:self.attachmentStream interactionId:message.uniqueId];
return;
} else if ([self.attachmentStream isAnimated] || [self.attachmentStream isImage] ||
[self.attachmentStream isVideo]) {
if ([self.attachmentStream isAnimated]) {
self.messageCellType = OWSMessageCellType_AnimatedImage;
} else if ([self.attachmentStream isImage]) {
self.messageCellType = OWSMessageCellType_StillImage;
} else if ([self.attachmentStream isVideo]) {
self.messageCellType = OWSMessageCellType_Video;
2017-10-10 22:13:54 +02:00
} else {
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ unexpected attachment type.", self.logTag);
2017-10-10 22:13:54 +02:00
self.messageCellType = OWSMessageCellType_GenericAttachment;
return;
}
self.contentSize = [self.attachmentStream imageSizeWithoutTransaction];
if (self.contentSize.width <= 0 || self.contentSize.height <= 0) {
self.messageCellType = OWSMessageCellType_GenericAttachment;
}
return;
} else if ([self.attachmentStream isAudio]) {
self.messageCellType = OWSMessageCellType_Audio;
2017-10-10 22:13:54 +02:00
return;
} else {
self.messageCellType = OWSMessageCellType_GenericAttachment;
return;
2017-10-10 22:13:54 +02:00
}
} else if ([attachment isKindOfClass:[TSAttachmentPointer class]]) {
self.messageCellType = OWSMessageCellType_DownloadingAttachment;
self.attachmentPointer = (TSAttachmentPointer *)attachment;
return;
} else {
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Unknown attachment type", self.logTag);
2017-10-10 22:13:54 +02:00
}
} else if (message.body != nil) {
self.messageCellType = OWSMessageCellType_TextMessage;
self.displayableText = [self displayableTextForText:message.body interactionId:message.uniqueId];
OWSAssert(self.displayableText);
return;
} else {
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Message has neither attachment nor body", self.logTag);
2017-10-10 22:13:54 +02:00
}
2017-11-08 20:04:51 +01:00
DDLogVerbose(@"%@ message: %@", self.logTag, message.description);
OWSFail(@"%@ Unknown cell type", self.logTag);
2017-10-10 22:13:54 +02:00
// Messages of unknown type (including messages with missing attachments)
// are rendered like empty text messages, but without any interactivity.
2017-10-10 22:13:54 +02:00
self.messageCellType = OWSMessageCellType_Unknown;
self.displayableText = [[DisplayableText alloc] initWithFullText:@"" displayText:@"" isTextTruncated:NO];
2017-10-10 22:13:54 +02:00
}
- (OWSMessageCellType)messageCellType
{
OWSAssert([NSThread isMainThread]);
return _messageCellType;
}
- (nullable DisplayableText *)displayableText
2017-10-10 22:13:54 +02:00
{
OWSAssert([NSThread isMainThread]);
OWSAssert(self.hasViewState);
2017-10-10 22:13:54 +02:00
OWSAssert(_displayableText);
OWSAssert(_displayableText.displayText);
OWSAssert(_displayableText.fullText);
return _displayableText;
2017-10-10 22:13:54 +02:00
}
- (nullable TSAttachmentStream *)attachmentStream
{
OWSAssert([NSThread isMainThread]);
OWSAssert(self.hasViewState);
2017-10-10 22:13:54 +02:00
return _attachmentStream;
}
- (nullable TSAttachmentPointer *)attachmentPointer
{
OWSAssert([NSThread isMainThread]);
OWSAssert(self.hasViewState);
2017-10-10 22:13:54 +02:00
return _attachmentPointer;
}
- (CGSize)contentSize
{
OWSAssert([NSThread isMainThread]);
OWSAssert(self.hasViewState);
2017-10-10 22:13:54 +02:00
return _contentSize;
}
#pragma mark - UIMenuController
- (NSArray<UIMenuItem *> *)menuControllerItems
{
return @[
[[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"EDIT_ITEM_SHARE_ACTION",
@"Short name for edit menu item to share contents of media message.")
action:self.shareActionSelector],
[[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"EDIT_ITEM_MESSAGE_METADATA_ACTION",
@"Short name for edit menu item to show message metadata.")
action:self.metadataActionSelector],
[[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"EDIT_ITEM_COPY_ACTION",
@"Short name for edit menu item to copy contents of media message.")
action:self.copyActionSelector],
[[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"EDIT_ITEM_DELETE_ACTION",
@"Short name for edit menu item to delete contents of media message.")
action:self.deleteActionSelector],
// TODO: Do we want a save action?
[[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"EDIT_ITEM_SAVE_ACTION",
@"Short name for edit menu item to save contents of media message.")
action:self.saveActionSelector],
];
}
- (SEL)copyActionSelector
{
return NSSelectorFromString(@"copyAction:");
}
- (SEL)saveActionSelector
{
return NSSelectorFromString(@"saveAction:");
}
- (SEL)shareActionSelector
{
return NSSelectorFromString(@"shareAction:");
}
- (SEL)deleteActionSelector
{
return NSSelectorFromString(@"deleteAction:");
}
- (SEL)metadataActionSelector
{
return NSSelectorFromString(@"metadataAction:");
}
// We only use custom actions in UIMenuController.
- (BOOL)canPerformAction:(SEL)action
{
if (action == self.copyActionSelector) {
return [self hasActionContent];
} else if (action == self.saveActionSelector) {
return [self canSave];
} else if (action == self.shareActionSelector) {
return [self hasActionContent];
} else if (action == self.deleteActionSelector) {
return YES;
} else if (action == self.metadataActionSelector) {
return YES;
} else {
return NO;
}
}
- (void)copyAction
{
switch (self.messageCellType) {
case OWSMessageCellType_TextMessage:
case OWSMessageCellType_OversizeTextMessage:
OWSAssert(self.displayableText);
[UIPasteboard.generalPasteboard setString:self.displayableText.fullText];
2017-10-10 22:13:54 +02:00
break;
case OWSMessageCellType_StillImage:
case OWSMessageCellType_AnimatedImage:
case OWSMessageCellType_Audio:
case OWSMessageCellType_Video:
case OWSMessageCellType_GenericAttachment: {
NSString *utiType = [MIMETypeUtil utiTypeForMIMEType:self.attachmentStream.contentType];
if (!utiType) {
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Unknown MIME type: %@", self.logTag, self.attachmentStream.contentType);
2017-10-10 22:13:54 +02:00
utiType = (NSString *)kUTTypeGIF;
}
NSData *data = [NSData dataWithContentsOfURL:[self.attachmentStream mediaURL]];
if (!data) {
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Could not load attachment data: %@", self.logTag, [self.attachmentStream mediaURL]);
2017-10-10 22:13:54 +02:00
return;
}
[UIPasteboard.generalPasteboard setData:data forPasteboardType:utiType];
break;
}
case OWSMessageCellType_DownloadingAttachment: {
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Can't copy not-yet-downloaded attachment", self.logTag);
2017-10-10 22:13:54 +02:00
break;
}
}
}
- (void)shareAction
{
switch (self.messageCellType) {
case OWSMessageCellType_TextMessage:
case OWSMessageCellType_OversizeTextMessage:
OWSAssert(self.displayableText);
[AttachmentSharing showShareUIForText:self.displayableText.fullText];
2017-10-10 22:13:54 +02:00
break;
case OWSMessageCellType_StillImage:
case OWSMessageCellType_AnimatedImage:
case OWSMessageCellType_Audio:
case OWSMessageCellType_Video:
case OWSMessageCellType_GenericAttachment:
[AttachmentSharing showShareUIForAttachment:self.attachmentStream];
break;
case OWSMessageCellType_DownloadingAttachment: {
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Can't share not-yet-downloaded attachment", self.logTag);
2017-10-10 22:13:54 +02:00
break;
}
}
}
- (BOOL)canSave
{
switch (self.messageCellType) {
case OWSMessageCellType_TextMessage:
case OWSMessageCellType_OversizeTextMessage:
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)saveAction
{
switch (self.messageCellType) {
case OWSMessageCellType_TextMessage:
case OWSMessageCellType_OversizeTextMessage:
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Cannot save text data.", self.logTag);
2017-10-10 22:13:54 +02:00
break;
case OWSMessageCellType_StillImage:
case OWSMessageCellType_AnimatedImage: {
NSData *data = [NSData dataWithContentsOfURL:[self.attachmentStream mediaURL]];
if (!data) {
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Could not load image data: %@", self.logTag, [self.attachmentStream mediaURL]);
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) {
DDLogWarn(@"Error Saving image to photo album: %@", error);
}
}];
break;
}
case OWSMessageCellType_Audio:
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Cannot save media data.", self.logTag);
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 {
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Could not save incompatible video data.", self.logTag);
2017-10-10 22:13:54 +02:00
}
break;
case OWSMessageCellType_GenericAttachment:
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Cannot save media data.", self.logTag);
2017-10-10 22:13:54 +02:00
break;
case OWSMessageCellType_DownloadingAttachment: {
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Can't save not-yet-downloaded attachment", self.logTag);
2017-10-10 22:13:54 +02:00
break;
}
}
}
- (void)deleteAction
{
[self.interaction remove];
}
- (BOOL)hasActionContent
{
switch (self.messageCellType) {
case OWSMessageCellType_TextMessage:
case OWSMessageCellType_OversizeTextMessage:
OWSAssert(self.displayableText);
return self.displayableText.fullText.length > 0;
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