diff --git a/Signal/src/ViewControllers/ColorPickerViewController.swift b/Signal/src/ViewControllers/ColorPickerViewController.swift index c18fdd48f..beaff2faf 100644 --- a/Signal/src/ViewControllers/ColorPickerViewController.swift +++ b/Signal/src/ViewControllers/ColorPickerViewController.swift @@ -433,6 +433,11 @@ private class MockConversationViewItem: NSObject, ConversationViewItem { owsFailDebug("unexpected invocation") return nil } + + func mediaAlbumHasFailedAttachment() -> Bool { + owsFailDebug("unexpected invocation") + return false + } } private class MockIncomingMessage: TSIncomingMessage { diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m index fd4eff6b6..7fdf6f72a 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m @@ -187,14 +187,6 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes return (TSMessage *)self.viewItem.interaction; } -- (CGSize)mediaSize -{ - // This should always be valid for the appropriate cell types. - OWSAssertDebug(self.viewItem.mediaSize.width > 0 && self.viewItem.mediaSize.height > 0); - - return self.viewItem.mediaSize; -} - - (BOOL)isQuotedReply { // This should always be valid for the appropriate cell types. @@ -232,10 +224,7 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes return YES; case OWSMessageCellType_GenericAttachment: case OWSMessageCellType_DownloadingAttachment: - case OWSMessageCellType_StillImage: - case OWSMessageCellType_AnimatedImage: case OWSMessageCellType_Audio: - case OWSMessageCellType_Video: // Is there a caption? return self.hasBodyText; case OWSMessageCellType_ContactShare: @@ -305,12 +294,6 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes case OWSMessageCellType_TextMessage: case OWSMessageCellType_OversizeTextMessage: break; - case OWSMessageCellType_StillImage: - case OWSMessageCellType_AnimatedImage: - case OWSMessageCellType_Video: - OWSAssertDebug(self.viewItem.attachmentStream); - bodyMediaView = [self loadViewForMedia]; - break; case OWSMessageCellType_Audio: OWSAssertDebug(self.viewItem.attachmentStream); bodyMediaView = [self loadViewForAudio]; @@ -595,10 +578,6 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes case OWSMessageCellType_TextMessage: case OWSMessageCellType_OversizeTextMessage: return NO; - case OWSMessageCellType_StillImage: - case OWSMessageCellType_AnimatedImage: - case OWSMessageCellType_Video: - return YES; case OWSMessageCellType_Audio: case OWSMessageCellType_GenericAttachment: case OWSMessageCellType_DownloadingAttachment: @@ -796,38 +775,21 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes [albumView unloadMedia]; }; + // Only apply "inner shadow" for single media, not albums. + if (albumView.itemViews.count == 1) { + UIView *itemView = albumView.itemViews.firstObject; + OWSBubbleShapeView *innerShadowView = [[OWSBubbleShapeView alloc] + initInnerShadowWithColor:(Theme.isDarkThemeEnabled ? UIColor.ows_whiteColor : UIColor.ows_blackColor) + radius:0.5f + opacity:0.15f]; + [itemView addSubview:innerShadowView]; + [self.bubbleView addPartnerView:innerShadowView]; + [self.viewConstraints addObjectsFromArray:[innerShadowView ows_autoPinToSuperviewEdges]]; + } + return albumView; } -- (UIView *)loadViewForMedia -{ - OWSAssertDebug(self.attachmentStream); - OWSAssertDebug([self.attachmentStream isVisualMedia]); - - ConversationMediaView *mediaView = - [[ConversationMediaView alloc] initWithMediaCache:self.cellMediaCache - attachment:self.attachmentStream - isOutgoing:self.isOutgoing - maxMessageWidth:self.conversationStyle.maxMessageWidth]; - self.loadCellContentBlock = ^{ - [mediaView loadMedia]; - }; - self.unloadCellContentBlock = ^{ - [mediaView unloadMedia]; - }; - [self addAttachmentUploadViewIfNecessary]; - - OWSBubbleShapeView *innerShadowView = [[OWSBubbleShapeView alloc] - initInnerShadowWithColor:(Theme.isDarkThemeEnabled ? UIColor.ows_whiteColor : UIColor.ows_blackColor) - radius:0.5f - opacity:0.15f]; - [mediaView addSubview:innerShadowView]; - [self.bubbleView addPartnerView:innerShadowView]; - [self.viewConstraints addObjectsFromArray:[innerShadowView ows_autoPinToSuperviewEdges]]; - - return mediaView; -} - - (UIView *)loadViewForAudio { OWSAssertDebug(self.attachmentStream); @@ -990,43 +952,6 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes case OWSMessageCellType_OversizeTextMessage: { return nil; } - case OWSMessageCellType_StillImage: - case OWSMessageCellType_AnimatedImage: - case OWSMessageCellType_Video: { - OWSAssertDebug(self.mediaSize.width > 0); - OWSAssertDebug(self.mediaSize.height > 0); - - // TODO: Adjust this behavior. - - CGFloat contentAspectRatio = self.mediaSize.width / self.mediaSize.height; - // Clamp the aspect ratio so that very thin/wide content is presented - // in a reasonable way. - const CGFloat minAspectRatio = 0.35f; - const CGFloat maxAspectRatio = 1 / minAspectRatio; - contentAspectRatio = MAX(minAspectRatio, MIN(maxAspectRatio, contentAspectRatio)); - - const CGFloat maxMediaWidth = maxMessageWidth; - const CGFloat maxMediaHeight = maxMessageWidth; - CGFloat mediaWidth = maxMediaHeight * contentAspectRatio; - CGFloat mediaHeight = maxMediaHeight; - if (mediaWidth > maxMediaWidth) { - mediaWidth = maxMediaWidth; - mediaHeight = maxMediaWidth / contentAspectRatio; - } - - // We don't want to blow up small images unnecessarily. - const CGFloat kMinimumSize = 150.f; - CGFloat shortSrcDimension = MIN(self.mediaSize.width, self.mediaSize.height); - CGFloat shortDstDimension = MIN(mediaWidth, mediaHeight); - if (shortDstDimension > kMinimumSize && shortDstDimension > shortSrcDimension) { - CGFloat factor = kMinimumSize / shortDstDimension; - mediaWidth *= factor; - mediaHeight *= factor; - } - - result = CGSizeRound(CGSizeMake(mediaWidth, mediaHeight)); - break; - } case OWSMessageCellType_Audio: result = CGSizeMake(maxMessageWidth, OWSAudioMessageView.bubbleHeight); break; @@ -1049,6 +974,42 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes case OWSMessageCellType_MediaAlbum: result = [OWSMediaAlbumCellView layoutSizeForMaxMessageWidth:maxMessageWidth items:self.viewItem.mediaAlbumItems]; + + if (self.viewItem.mediaAlbumItems.count == 1) { + // Honor the content aspect ratio for single media. + ConversationMediaAlbumItem *mediaAlbumItem = self.viewItem.mediaAlbumItems.firstObject; + if (mediaAlbumItem.attachmentStream && mediaAlbumItem.mediaSize.width > 0 + && mediaAlbumItem.mediaSize.height > 0) { + CGSize mediaSize = mediaAlbumItem.mediaSize; + CGFloat contentAspectRatio = mediaSize.width / mediaSize.height; + // Clamp the aspect ratio so that very thin/wide content is presented + // in a reasonable way. + const CGFloat minAspectRatio = 0.35f; + const CGFloat maxAspectRatio = 1 / minAspectRatio; + contentAspectRatio = MAX(minAspectRatio, MIN(maxAspectRatio, contentAspectRatio)); + + const CGFloat maxMediaWidth = maxMessageWidth; + const CGFloat maxMediaHeight = maxMessageWidth; + CGFloat mediaWidth = maxMediaHeight * contentAspectRatio; + CGFloat mediaHeight = maxMediaHeight; + if (mediaWidth > maxMediaWidth) { + mediaWidth = maxMediaWidth; + mediaHeight = maxMediaWidth / contentAspectRatio; + } + + // We don't want to blow up small images unnecessarily. + const CGFloat kMinimumSize = 150.f; + CGFloat shortSrcDimension = MIN(mediaSize.width, mediaSize.height); + CGFloat shortDstDimension = MIN(mediaWidth, mediaHeight); + if (shortDstDimension > kMinimumSize && shortDstDimension > shortSrcDimension) { + CGFloat factor = kMinimumSize / shortDstDimension; + mediaWidth *= factor; + mediaHeight *= factor; + } + + result = CGSizeRound(CGSizeMake(mediaWidth, mediaHeight)); + } + } break; } @@ -1367,35 +1328,11 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes case OWSMessageCellType_TextMessage: case OWSMessageCellType_OversizeTextMessage: break; - case OWSMessageCellType_StillImage: - OWSAssertDebug(self.bodyMediaView); - OWSAssertDebug(self.viewItem.attachmentStream); - - [self.delegate didTapImageViewItem:self.viewItem - attachmentStream:self.viewItem.attachmentStream - imageView:self.bodyMediaView]; - break; - case OWSMessageCellType_AnimatedImage: - OWSAssertDebug(self.bodyMediaView); - OWSAssertDebug(self.viewItem.attachmentStream); - - [self.delegate didTapImageViewItem:self.viewItem - attachmentStream:self.viewItem.attachmentStream - imageView:self.bodyMediaView]; - break; case OWSMessageCellType_Audio: OWSAssertDebug(self.viewItem.attachmentStream); [self.delegate didTapAudioViewItem:self.viewItem attachmentStream:self.viewItem.attachmentStream]; return; - case OWSMessageCellType_Video: - OWSAssertDebug(self.bodyMediaView); - OWSAssertDebug(self.viewItem.attachmentStream); - - [self.delegate didTapVideoViewItem:self.viewItem - attachmentStream:self.viewItem.attachmentStream - imageView:self.bodyMediaView]; - return; case OWSMessageCellType_GenericAttachment: OWSAssertDebug(self.viewItem.attachmentStream); @@ -1413,10 +1350,33 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes case OWSMessageCellType_ContactShare: [self.delegate didTapContactShareViewItem:self.viewItem]; break; - case OWSMessageCellType_MediaAlbum: + case OWSMessageCellType_MediaAlbum: { OWSAssertDebug(self.bodyMediaView); OWSAssertDebug(self.viewItem.mediaAlbumItems.count > 0); + if (self.viewItem.mediaAlbumHasFailedAttachment) { + [self.delegate didTapFailedIncomingAttachment:self.viewItem]; + return; + } + + // TODO: We might be able to get rid of this. + if (self.viewItem.mediaAlbumItems.count == 1) { + ConversationMediaAlbumItem *mediaAlbumItem = self.viewItem.mediaAlbumItems.firstObject; + if (!mediaAlbumItem.attachmentStream.isValidVisualMedia) { + // Do nothing. + } else if (mediaAlbumItem.attachmentStream.isAnimated || mediaAlbumItem.attachmentStream.isImage) { + [self.delegate didTapImageViewItem:self.viewItem + attachmentStream:mediaAlbumItem.attachmentStream + imageView:self.bodyMediaView]; + return; + } else if (mediaAlbumItem.attachmentStream.isVideo) { + [self.delegate didTapVideoViewItem:self.viewItem + attachmentStream:mediaAlbumItem.attachmentStream + imageView:self.bodyMediaView]; + return; + } + } + // For now, use first valid attachment. TSAttachmentStream *_Nullable attachmentStream = self.viewItem.firstValidAlbumAttachment; if (!attachmentStream) { @@ -1428,6 +1388,7 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes attachmentStream:attachmentStream imageView:self.bodyMediaView]; break; + } } } diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h index 1004ee1bc..048c191a1 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h @@ -11,10 +11,7 @@ typedef NS_ENUM(NSInteger, OWSMessageCellType) { OWSMessageCellType_Unknown, OWSMessageCellType_TextMessage, OWSMessageCellType_OversizeTextMessage, - OWSMessageCellType_StillImage, - OWSMessageCellType_AnimatedImage, OWSMessageCellType_Audio, - OWSMessageCellType_Video, OWSMessageCellType_GenericAttachment, OWSMessageCellType_DownloadingAttachment, OWSMessageCellType_ContactShare, @@ -108,7 +105,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType); @property (nonatomic, readonly, nullable) DisplayableText *displayableBodyText; @property (nonatomic, readonly, nullable) TSAttachmentStream *attachmentStream; @property (nonatomic, readonly, nullable) TSAttachmentPointer *attachmentPointer; -@property (nonatomic, readonly) CGSize mediaSize; @property (nonatomic, readonly, nullable) NSArray *mediaAlbumItems; @property (nonatomic, readonly, nullable) DisplayableText *displayableQuotedText; @@ -146,6 +142,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType); - (nullable TSAttachmentStream *)firstValidAlbumAttachment; +- (BOOL)mediaAlbumHasFailedAttachment; + @end #pragma mark - diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m index a1bcefc85..b5f4c3863 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m @@ -25,14 +25,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) 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: @@ -94,7 +88,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) @property (nonatomic, nullable) TSAttachmentStream *attachmentStream; @property (nonatomic, nullable) TSAttachmentPointer *attachmentPointer; @property (nonatomic, nullable) ContactShareViewModel *contactShare; -@property (nonatomic) CGSize mediaSize; @property (nonatomic, nullable) NSArray *mediaAlbumItems; @property (nonatomic, nullable) NSString *systemMessageText; @property (nonatomic, nullable) TSThread *incomingMessageAuthorThread; @@ -156,7 +149,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) self.displayableBodyText = nil; self.attachmentStream = nil; self.attachmentPointer = nil; - self.mediaSize = CGSizeZero; self.displayableQuotedText = nil; self.quotedReply = nil; self.systemMessageText = nil; @@ -573,9 +565,24 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) if ([message isMediaAlbumWithTransaction:transaction]) { OWSAssertDebug(attachments.count > 0); NSArray *mediaAlbumItems = [self mediaAlbumItemsForAttachments:attachments]; + + 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; + } + } + self.mediaAlbumItems = mediaAlbumItems; self.messageCellType = OWSMessageCellType_MediaAlbum; NSString *_Nullable albumTitle = [message bodyTextWithTransaction:transaction]; + if (!albumTitle && mediaAlbumItems.count == 1) { + // If the album contains only one option, use its caption as the + // the album title. + albumTitle = mediaAlbumItems.firstObject.caption; + } if (albumTitle) { self.displayableBodyText = [self displayableBodyTextForText:albumTitle interactionId:message.uniqueId]; } @@ -593,41 +600,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) self.messageCellType = OWSMessageCellType_OversizeTextMessage; self.displayableBodyText = [self displayableBodyTextForOversizeTextAttachment:self.attachmentStream interactionId:message.uniqueId]; - } else if ([self.attachmentStream isAnimated] || [self.attachmentStream isImage] || - [self.attachmentStream isVideo]) { - if ([self.attachmentStream isAnimated]) { - if (![self.attachmentStream isValidImage]) { - OWSLogWarn(@"Treating invalid image as generic attachment."); - self.messageCellType = OWSMessageCellType_GenericAttachment; - return; - } - - self.messageCellType = OWSMessageCellType_AnimatedImage; - } else if ([self.attachmentStream isImage]) { - if (![self.attachmentStream isValidImage]) { - OWSLogWarn(@"Treating invalid image as generic attachment."); - self.messageCellType = OWSMessageCellType_GenericAttachment; - return; - } - - self.messageCellType = OWSMessageCellType_StillImage; - } else if ([self.attachmentStream isVideo]) { - if (![self.attachmentStream isValidVideo]) { - OWSLogWarn(@"Treating invalid video as generic attachment."); - self.messageCellType = OWSMessageCellType_GenericAttachment; - return; - } - - self.messageCellType = OWSMessageCellType_Video; - } else { - OWSFailDebug(@"unexpected attachment type."); - 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) { @@ -825,14 +797,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) return _attachmentPointer; } -- (CGSize)mediaSize -{ - OWSAssertIsOnMainThread(); - OWSAssertDebug(self.hasViewState); - - return _mediaSize; -} - - (nullable DisplayableText *)displayableQuotedText { OWSAssertIsOnMainThread(); @@ -850,10 +814,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) switch (self.messageCellType) { case OWSMessageCellType_TextMessage: case OWSMessageCellType_OversizeTextMessage: - case OWSMessageCellType_StillImage: - case OWSMessageCellType_AnimatedImage: case OWSMessageCellType_Audio: - case OWSMessageCellType_Video: case OWSMessageCellType_MediaAlbum: case OWSMessageCellType_GenericAttachment: { OWSAssertDebug(self.displayableBodyText); @@ -886,10 +847,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) OWSFailDebug(@"No media to copy"); break; } - case OWSMessageCellType_StillImage: - case OWSMessageCellType_AnimatedImage: case OWSMessageCellType_Audio: - case OWSMessageCellType_Video: case OWSMessageCellType_GenericAttachment: { [self copyAttachmentToPasteboard:self.attachmentStream]; break; @@ -899,6 +857,14 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) break; } case OWSMessageCellType_MediaAlbum: { + if (self.mediaAlbumItems.count == 1) { + ConversationMediaAlbumItem *mediaAlbumItem = self.mediaAlbumItems.firstObject; + if (mediaAlbumItem.attachmentStream && mediaAlbumItem.attachmentStream.isValidVisualMedia) { + [self copyAttachmentToPasteboard:mediaAlbumItem.attachmentStream]; + return; + } + } + OWSFailDebug(@"Can't copy media album"); break; } @@ -931,10 +897,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) case OWSMessageCellType_ContactShare: OWSFailDebug(@"No media to share."); break; - case OWSMessageCellType_StillImage: - case OWSMessageCellType_AnimatedImage: case OWSMessageCellType_Audio: - case OWSMessageCellType_Video: case OWSMessageCellType_GenericAttachment: [AttachmentSharing showShareUIForAttachment:self.attachmentStream]; break; @@ -947,7 +910,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) OWSAssertDebug(self.mediaAlbumItems); NSMutableArray *attachmentStreams = [NSMutableArray new]; for (ConversationMediaAlbumItem *mediaAlbumItem in self.mediaAlbumItems) { - if (mediaAlbumItem.attachmentStream) { + if (mediaAlbumItem.attachmentStream && mediaAlbumItem.attachmentStream.isValidVisualMedia) { [attachmentStreams addObject:mediaAlbumItem.attachmentStream]; } } @@ -969,17 +932,19 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) case OWSMessageCellType_OversizeTextMessage: case OWSMessageCellType_ContactShare: return NO; - case OWSMessageCellType_StillImage: - case OWSMessageCellType_AnimatedImage: - return YES; case OWSMessageCellType_Audio: return NO; - case OWSMessageCellType_Video: - return UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(self.attachmentStream.originalFilePath); case OWSMessageCellType_GenericAttachment: case OWSMessageCellType_DownloadingAttachment: - case OWSMessageCellType_MediaAlbum: + case OWSMessageCellType_MediaAlbum: { + if (self.mediaAlbumItems.count == 1) { + ConversationMediaAlbumItem *mediaAlbumItem = self.mediaAlbumItems.firstObject; + if (mediaAlbumItem.attachmentStream && mediaAlbumItem.attachmentStream.isValidVisualMedia) { + return YES; + } + } return NO; + } } } @@ -991,13 +956,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) case OWSMessageCellType_OversizeTextMessage: case OWSMessageCellType_ContactShare: return NO; - case OWSMessageCellType_StillImage: - case OWSMessageCellType_AnimatedImage: - return YES; case OWSMessageCellType_Audio: return NO; - case OWSMessageCellType_Video: - return UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(self.attachmentStream.originalFilePath); case OWSMessageCellType_GenericAttachment: case OWSMessageCellType_DownloadingAttachment: return NO; @@ -1006,6 +966,9 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) if (!mediaAlbumItem.attachmentStream) { continue; } + if (!mediaAlbumItem.attachmentStream.isValidVisualMedia) { + continue; + } if (mediaAlbumItem.attachmentStream.isImage || mediaAlbumItem.attachmentStream.isAnimated) { return YES; } @@ -1030,33 +993,9 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) case OWSMessageCellType_ContactShare: OWSFailDebug(@"Cannot save text data."); break; - case OWSMessageCellType_StillImage: - case OWSMessageCellType_AnimatedImage: { - NSData *data = [NSData dataWithContentsOfURL:[self.attachmentStream originalMediaURL]]; - if (!data) { - OWSFailDebug(@"Could not load image data"); - 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); - } - }]; - break; - } case OWSMessageCellType_Audio: OWSFailDebug(@"Cannot save media data."); break; - case OWSMessageCellType_Video: - if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(self.attachmentStream.originalFilePath)) { - UISaveVideoAtPathToSavedPhotosAlbum(self.attachmentStream.originalFilePath, self, nil, nil); - } else { - OWSFailDebug(@"Could not save incompatible video data."); - } - break; case OWSMessageCellType_GenericAttachment: OWSFailDebug(@"Cannot save media data."); break; @@ -1071,6 +1010,9 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) if (!mediaAlbumItem.attachmentStream) { continue; } + if (!mediaAlbumItem.attachmentStream.isValidVisualMedia) { + continue; + } if (mediaAlbumItem.attachmentStream.isImage || mediaAlbumItem.attachmentStream.isAnimated) { NSData *data = [NSData dataWithContentsOfURL:[mediaAlbumItem.attachmentStream originalMediaURL]]; if (!data) { @@ -1115,21 +1057,33 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) case OWSMessageCellType_OversizeTextMessage: case OWSMessageCellType_ContactShare: return NO; - case OWSMessageCellType_StillImage: - case OWSMessageCellType_AnimatedImage: case OWSMessageCellType_Audio: - case OWSMessageCellType_Video: case OWSMessageCellType_GenericAttachment: return self.attachmentStream != nil; case OWSMessageCellType_DownloadingAttachment: { return NO; } case OWSMessageCellType_MediaAlbum: - // TODO: I suspect we need separate "can save media", "can share media", etc. methods. return self.firstValidAlbumAttachment != nil; } } +- (BOOL)mediaAlbumHasFailedAttachment +{ + OWSAssertDebug(self.messageCellType == OWSMessageCellType_MediaAlbum); + OWSAssertDebug(self.mediaAlbumItems.count > 0); + + for (ConversationMediaAlbumItem *mediaAlbumItem in self.mediaAlbumItems) { + if ([mediaAlbumItem.attachment isKindOfClass:[TSAttachmentPointer class]]) { + TSAttachmentPointer *attachmentPointer = (TSAttachmentPointer *)mediaAlbumItem.attachment; + if (attachmentPointer.state == TSAttachmentPointerStateFailed) { + return YES; + } + } + } + return NO; +} + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/TSMessage.m b/SignalServiceKit/src/Messages/Interactions/TSMessage.m index bb67f3699..546c9b795 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSMessage.m @@ -225,7 +225,7 @@ static const NSUInteger OWSMessageSchemaVersion = 4; - (BOOL)isMediaAlbumWithTransaction:(YapDatabaseReadTransaction *)transaction { NSArray *attachments = [self attachmentsWithTransaction:transaction]; - if (attachments.count < 2) { + if (attachments.count < 1) { return NO; } for (TSAttachment *attachment in attachments) {