Merge branch 'charlesmchen/constantBubbleSizes'

pull/2/head
Matthew Chen 6 years ago
commit 7bb4814224

@ -369,7 +369,6 @@
452C468F1E427E200087B011 /* OutboundCallInitiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452C468E1E427E200087B011 /* OutboundCallInitiator.swift */; }; 452C468F1E427E200087B011 /* OutboundCallInitiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452C468E1E427E200087B011 /* OutboundCallInitiator.swift */; };
452C7CA72037628B003D51A5 /* Weak.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170D51E315310003FC1F2 /* Weak.swift */; }; 452C7CA72037628B003D51A5 /* Weak.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170D51E315310003FC1F2 /* Weak.swift */; };
452D1AF12081059C00A67F7F /* StringAdditionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452D1AF02081059C00A67F7F /* StringAdditionsTest.swift */; }; 452D1AF12081059C00A67F7F /* StringAdditionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452D1AF02081059C00A67F7F /* StringAdditionsTest.swift */; };
452EA09E1EA7ABE00078744B /* AttachmentPointerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452EA09D1EA7ABE00078744B /* AttachmentPointerView.swift */; };
452EC6DF205E9E30000E787C /* MediaGalleryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452EC6DE205E9E30000E787C /* MediaGalleryViewController.swift */; }; 452EC6DF205E9E30000E787C /* MediaGalleryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452EC6DE205E9E30000E787C /* MediaGalleryViewController.swift */; };
452EC6E1205FF5DC000E787C /* Bench.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452EC6E0205FF5DC000E787C /* Bench.swift */; }; 452EC6E1205FF5DC000E787C /* Bench.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452EC6E0205FF5DC000E787C /* Bench.swift */; };
452ECA4D1E087E7200E2F016 /* MessageFetcherJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452ECA4C1E087E7200E2F016 /* MessageFetcherJob.swift */; }; 452ECA4D1E087E7200E2F016 /* MessageFetcherJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452ECA4C1E087E7200E2F016 /* MessageFetcherJob.swift */; };
@ -1093,7 +1092,6 @@
452B998F20A34B6B006F2F9E /* AddContactShareToExistingContactViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactShareToExistingContactViewController.swift; sourceTree = "<group>"; }; 452B998F20A34B6B006F2F9E /* AddContactShareToExistingContactViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactShareToExistingContactViewController.swift; sourceTree = "<group>"; };
452C468E1E427E200087B011 /* OutboundCallInitiator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutboundCallInitiator.swift; sourceTree = "<group>"; }; 452C468E1E427E200087B011 /* OutboundCallInitiator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutboundCallInitiator.swift; sourceTree = "<group>"; };
452D1AF02081059C00A67F7F /* StringAdditionsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringAdditionsTest.swift; sourceTree = "<group>"; }; 452D1AF02081059C00A67F7F /* StringAdditionsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringAdditionsTest.swift; sourceTree = "<group>"; };
452EA09D1EA7ABE00078744B /* AttachmentPointerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentPointerView.swift; sourceTree = "<group>"; };
452EC6DE205E9E30000E787C /* MediaGalleryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaGalleryViewController.swift; sourceTree = "<group>"; }; 452EC6DE205E9E30000E787C /* MediaGalleryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaGalleryViewController.swift; sourceTree = "<group>"; };
452EC6E0205FF5DC000E787C /* Bench.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bench.swift; sourceTree = "<group>"; }; 452EC6E0205FF5DC000E787C /* Bench.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bench.swift; sourceTree = "<group>"; };
452ECA4C1E087E7200E2F016 /* MessageFetcherJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageFetcherJob.swift; sourceTree = "<group>"; }; 452ECA4C1E087E7200E2F016 /* MessageFetcherJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageFetcherJob.swift; sourceTree = "<group>"; };
@ -2419,7 +2417,6 @@
76EB052B18170B33006006FC /* Views */ = { 76EB052B18170B33006006FC /* Views */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
452EA09D1EA7ABE00078744B /* AttachmentPointerView.swift */,
34E3E5671EC4B19400495BAC /* AudioProgressView.swift */, 34E3E5671EC4B19400495BAC /* AudioProgressView.swift */,
4C2F454E214C00E1004871FF /* AvatarTableViewCell.swift */, 4C2F454E214C00E1004871FF /* AvatarTableViewCell.swift */,
4CA46F4B219CCC630038ABDE /* CaptionView.swift */, 4CA46F4B219CCC630038ABDE /* CaptionView.swift */,
@ -3602,7 +3599,6 @@
34B3F87B1E8DF1700035BE1A /* ExperienceUpgradesPageViewController.swift in Sources */, 34B3F87B1E8DF1700035BE1A /* ExperienceUpgradesPageViewController.swift in Sources */,
3448E1622213585C004B052E /* OnboardingBaseViewController.swift in Sources */, 3448E1622213585C004B052E /* OnboardingBaseViewController.swift in Sources */,
34E5DC8220D8050D00C08145 /* RegistrationUtils.m in Sources */, 34E5DC8220D8050D00C08145 /* RegistrationUtils.m in Sources */,
452EA09E1EA7ABE00078744B /* AttachmentPointerView.swift in Sources */,
45638BDC1F3DD0D400128435 /* DebugUICalling.swift in Sources */, 45638BDC1F3DD0D400128435 /* DebugUICalling.swift in Sources */,
34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */, 34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */,
34D1F0521F7E8EA30066283D /* GiphyDownloader.swift in Sources */, 34D1F0521F7E8EA30066283D /* GiphyDownloader.swift in Sources */,

@ -1,13 +1,11 @@
// //
// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// //
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@class TSAttachmentStream; @class TSAttachmentStream;
typedef void (^AttachmentStateBlock)(BOOL isAttachmentReady);
// This entity is used to display upload progress for outgoing // This entity is used to display upload progress for outgoing
// attachments in conversation view cells. // attachments in conversation view cells.
// //
@ -18,8 +16,7 @@ typedef void (^AttachmentStateBlock)(BOOL isAttachmentReady);
// * Disable any media view controls using a callback. // * Disable any media view controls using a callback.
@interface AttachmentUploadView : UIView @interface AttachmentUploadView : UIView
- (instancetype)initWithAttachment:(TSAttachmentStream *)attachment - (instancetype)initWithAttachment:(TSAttachmentStream *)attachment;
attachmentStateCallback:(AttachmentStateBlock _Nullable)attachmentStateCallback;
@end @end

@ -1,5 +1,5 @@
// //
// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// //
#import "AttachmentUploadView.h" #import "AttachmentUploadView.h"
@ -21,8 +21,6 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic) UILabel *progressLabel; @property (nonatomic) UILabel *progressLabel;
@property (nonatomic) AttachmentStateBlock _Nullable attachmentStateCallback;
@property (nonatomic) BOOL isAttachmentReady; @property (nonatomic) BOOL isAttachmentReady;
@property (nonatomic) CGFloat lastProgress; @property (nonatomic) CGFloat lastProgress;
@ -34,7 +32,6 @@ NS_ASSUME_NONNULL_BEGIN
@implementation AttachmentUploadView @implementation AttachmentUploadView
- (instancetype)initWithAttachment:(TSAttachmentStream *)attachment - (instancetype)initWithAttachment:(TSAttachmentStream *)attachment
attachmentStateCallback:(AttachmentStateBlock _Nullable)attachmentStateCallback
{ {
self = [super init]; self = [super init];
@ -42,7 +39,6 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssertDebug(attachment); OWSAssertDebug(attachment);
self.attachment = attachment; self.attachment = attachment;
self.attachmentStateCallback = attachmentStateCallback;
[self createContents]; [self createContents];
@ -54,10 +50,6 @@ NS_ASSUME_NONNULL_BEGIN
_isAttachmentReady = self.attachment.isUploaded; _isAttachmentReady = self.attachment.isUploaded;
[self ensureViewState]; [self ensureViewState];
if (attachmentStateCallback) {
self.attachmentStateCallback(_isAttachmentReady);
}
} }
return self; return self;
} }
@ -110,10 +102,6 @@ NS_ASSUME_NONNULL_BEGIN
_isAttachmentReady = isAttachmentReady; _isAttachmentReady = isAttachmentReady;
[self ensureViewState]; [self ensureViewState];
if (self.attachmentStateCallback) {
self.attachmentStateCallback(isAttachmentReady);
}
} }
- (void)setLastProgress:(CGFloat)lastProgress - (void)setLastProgress:(CGFloat)lastProgress

@ -1,17 +1,17 @@
// //
// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// //
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@class ConversationStyle; @class ConversationStyle;
@class TSAttachmentStream; @class TSAttachment;
@protocol ConversationViewItem; @protocol ConversationViewItem;
@interface OWSAudioMessageView : UIStackView @interface OWSAudioMessageView : UIStackView
- (instancetype)initWithAttachment:(TSAttachmentStream *)attachmentStream - (instancetype)initWithAttachment:(TSAttachment *)attachment
isIncoming:(BOOL)isIncoming isIncoming:(BOOL)isIncoming
viewItem:(id<ConversationViewItem>)viewItem viewItem:(id<ConversationViewItem>)viewItem
conversationStyle:(ConversationStyle *)conversationStyle; conversationStyle:(ConversationStyle *)conversationStyle;

@ -1,5 +1,5 @@
// //
// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// //
#import "OWSAudioMessageView.h" #import "OWSAudioMessageView.h"
@ -15,7 +15,8 @@ NS_ASSUME_NONNULL_BEGIN
@interface OWSAudioMessageView () @interface OWSAudioMessageView ()
@property (nonatomic) TSAttachmentStream *attachmentStream; @property (nonatomic) TSAttachment *attachment;
@property (nonatomic, nullable) TSAttachmentStream *attachmentStream;
@property (nonatomic) BOOL isIncoming; @property (nonatomic) BOOL isIncoming;
@property (nonatomic, weak) id<ConversationViewItem> viewItem; @property (nonatomic, weak) id<ConversationViewItem> viewItem;
@property (nonatomic, readonly) ConversationStyle *conversationStyle; @property (nonatomic, readonly) ConversationStyle *conversationStyle;
@ -30,7 +31,7 @@ NS_ASSUME_NONNULL_BEGIN
@implementation OWSAudioMessageView @implementation OWSAudioMessageView
- (instancetype)initWithAttachment:(TSAttachmentStream *)attachmentStream - (instancetype)initWithAttachment:(TSAttachment *)attachment
isIncoming:(BOOL)isIncoming isIncoming:(BOOL)isIncoming
viewItem:(id<ConversationViewItem>)viewItem viewItem:(id<ConversationViewItem>)viewItem
conversationStyle:(ConversationStyle *)conversationStyle conversationStyle:(ConversationStyle *)conversationStyle
@ -38,7 +39,10 @@ NS_ASSUME_NONNULL_BEGIN
self = [super init]; self = [super init];
if (self) { if (self) {
_attachmentStream = attachmentStream; _attachment = attachment;
if ([attachment isKindOfClass:[TSAttachmentStream class]]) {
_attachmentStream = (TSAttachmentStream *)attachment;
}
_isIncoming = isIncoming; _isIncoming = isIncoming;
_viewItem = viewItem; _viewItem = viewItem;
_conversationStyle = conversationStyle; _conversationStyle = conversationStyle;
@ -66,8 +70,6 @@ NS_ASSUME_NONNULL_BEGIN
- (CGFloat)audioDurationSeconds - (CGFloat)audioDurationSeconds
{ {
OWSAssertDebug(self.viewItem.audioDurationSeconds > 0.f);
return self.viewItem.audioDurationSeconds; return self.viewItem.audioDurationSeconds;
} }
@ -174,7 +176,7 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)isVoiceMessage - (BOOL)isVoiceMessage
{ {
return self.attachmentStream.isVoiceMessage; return self.attachment.isVoiceMessage;
} }
- (void)createContents - (void)createContents
@ -190,13 +192,13 @@ NS_ASSUME_NONNULL_BEGIN
[self addArrangedSubview:self.audioPlayPauseButton]; [self addArrangedSubview:self.audioPlayPauseButton];
[self.audioPlayPauseButton setContentHuggingHigh]; [self.audioPlayPauseButton setContentHuggingHigh];
NSString *filename = self.attachmentStream.sourceFilename; NSString *_Nullable filename = self.attachment.sourceFilename;
if (!filename) { if (filename.length < 1) {
filename = [self.attachmentStream.originalFilePath lastPathComponent]; filename = [self.attachmentStream.originalFilePath lastPathComponent];
} }
NSString *topText = [[filename stringByDeletingPathExtension] ows_stripped]; NSString *topText = [[filename stringByDeletingPathExtension] ows_stripped];
if (topText.length < 1) { if (topText.length < 1) {
topText = [MIMETypeUtil fileExtensionForMIMEType:self.attachmentStream.contentType].localizedUppercaseString; topText = [MIMETypeUtil fileExtensionForMIMEType:self.attachment.contentType].localizedUppercaseString;
} }
if (topText.length < 1) { if (topText.length < 1) {
topText = NSLocalizedString(@"GENERIC_ATTACHMENT_LABEL", @"A label for generic attachments."); topText = NSLocalizedString(@"GENERIC_ATTACHMENT_LABEL", @"A label for generic attachments.");

@ -1,15 +1,15 @@
// //
// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// //
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@class ConversationStyle; @class ConversationStyle;
@class TSAttachmentStream; @class TSAttachment;
@interface OWSGenericAttachmentView : UIStackView @interface OWSGenericAttachmentView : UIStackView
- (instancetype)initWithAttachment:(TSAttachmentStream *)attachmentStream isIncoming:(BOOL)isIncoming; - (instancetype)initWithAttachment:(TSAttachment *)attachment isIncoming:(BOOL)isIncoming;
- (void)createContentsWithConversationStyle:(ConversationStyle *)conversationStyle; - (void)createContentsWithConversationStyle:(ConversationStyle *)conversationStyle;

@ -1,5 +1,5 @@
// //
// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// //
#import "OWSGenericAttachmentView.h" #import "OWSGenericAttachmentView.h"
@ -18,7 +18,8 @@ NS_ASSUME_NONNULL_BEGIN
@interface OWSGenericAttachmentView () @interface OWSGenericAttachmentView ()
@property (nonatomic) TSAttachmentStream *attachmentStream; @property (nonatomic) TSAttachment *attachment;
@property (nonatomic, nullable) TSAttachmentStream *attachmentStream;
@property (nonatomic) BOOL isIncoming; @property (nonatomic) BOOL isIncoming;
@property (nonatomic) UILabel *topLabel; @property (nonatomic) UILabel *topLabel;
@property (nonatomic) UILabel *bottomLabel; @property (nonatomic) UILabel *bottomLabel;
@ -29,12 +30,15 @@ NS_ASSUME_NONNULL_BEGIN
@implementation OWSGenericAttachmentView @implementation OWSGenericAttachmentView
- (instancetype)initWithAttachment:(TSAttachmentStream *)attachmentStream isIncoming:(BOOL)isIncoming - (instancetype)initWithAttachment:(TSAttachment *)attachment isIncoming:(BOOL)isIncoming
{ {
self = [super init]; self = [super init];
if (self) { if (self) {
_attachmentStream = attachmentStream; _attachment = attachment;
if ([attachment isKindOfClass:[TSAttachmentStream class]]) {
_attachmentStream = (TSAttachmentStream *)attachment;
}
_isIncoming = isIncoming; _isIncoming = isIncoming;
} }
@ -105,13 +109,13 @@ NS_ASSUME_NONNULL_BEGIN
[self addArrangedSubview:imageView]; [self addArrangedSubview:imageView];
[imageView setContentHuggingHigh]; [imageView setContentHuggingHigh];
NSString *filename = self.attachmentStream.sourceFilename; NSString *_Nullable filename = self.attachment.sourceFilename;
if (!filename) { if (!filename) {
filename = [[self.attachmentStream originalFilePath] lastPathComponent]; filename = [[self.attachmentStream originalFilePath] lastPathComponent];
} }
NSString *fileExtension = filename.pathExtension; NSString *fileExtension = filename.pathExtension;
if (fileExtension.length < 1) { if (fileExtension.length < 1) {
fileExtension = [MIMETypeUtil fileExtensionForMIMEType:self.attachmentStream.contentType]; fileExtension = [MIMETypeUtil fileExtensionForMIMEType:self.attachment.contentType];
} }
UILabel *fileTypeLabel = [UILabel new]; UILabel *fileTypeLabel = [UILabel new];
@ -132,9 +136,9 @@ NS_ASSUME_NONNULL_BEGIN
labelsView.alignment = UIStackViewAlignmentLeading; labelsView.alignment = UIStackViewAlignmentLeading;
[self addArrangedSubview:labelsView]; [self addArrangedSubview:labelsView];
NSString *topText = [self.attachmentStream.sourceFilename ows_stripped]; NSString *topText = [self.attachment.sourceFilename ows_stripped];
if (topText.length < 1) { if (topText.length < 1) {
topText = [MIMETypeUtil fileExtensionForMIMEType:self.attachmentStream.contentType].localizedUppercaseString; topText = [MIMETypeUtil fileExtensionForMIMEType:self.attachment.contentType].localizedUppercaseString;
} }
if (topText.length < 1) { if (topText.length < 1) {
topText = NSLocalizedString(@"GENERIC_ATTACHMENT_LABEL", @"A label for generic attachments."); topText = NSLocalizedString(@"GENERIC_ATTACHMENT_LABEL", @"A label for generic attachments.");
@ -147,12 +151,21 @@ NS_ASSUME_NONNULL_BEGIN
topLabel.font = [OWSGenericAttachmentView topLabelFont]; topLabel.font = [OWSGenericAttachmentView topLabelFont];
[labelsView addArrangedSubview:topLabel]; [labelsView addArrangedSubview:topLabel];
NSError *error; unsigned long long fileSize = 0;
unsigned long long fileSize = if (self.attachmentStream) {
[[NSFileManager defaultManager] attributesOfItemAtPath:[self.attachmentStream originalFilePath] error:&error] NSError *error;
.fileSize; fileSize = [[NSFileManager defaultManager] attributesOfItemAtPath:[self.attachmentStream originalFilePath]
OWSAssertDebug(!error); error:&error]
NSString *bottomText = [OWSFormat formatFileSize:fileSize]; .fileSize;
OWSAssertDebug(!error);
}
// We don't want to show the file size while the attachment is downloading.
// To avoid layout jitter when the download completes, we reserve space in
// the layout using a whitespace string.
NSString *bottomText = @" ";
if (fileSize > 0) {
bottomText = [OWSFormat formatFileSize:fileSize];
}
UILabel *bottomLabel = [UILabel new]; UILabel *bottomLabel = [UILabel new];
self.bottomLabel = bottomLabel; self.bottomLabel = bottomLabel;
bottomLabel.text = bottomText; bottomLabel.text = bottomText;

@ -60,6 +60,15 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
@implementation OWSMessageBubbleView @implementation OWSMessageBubbleView
#pragma mark - Dependencies
- (OWSAttachmentDownloads *)attachmentDownloads
{
return SSKEnvironment.shared.attachmentDownloads;
}
#pragma mark -
- (instancetype)initWithFrame:(CGRect)frame - (instancetype)initWithFrame:(CGRect)frame
{ {
self = [super initWithFrame:frame]; self = [super initWithFrame:frame];
@ -168,22 +177,6 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
return self.viewItem.displayableBodyText; return self.viewItem.displayableBodyText;
} }
- (nullable TSAttachmentStream *)attachmentStream
{
// This should always be valid for the appropriate cell types.
OWSAssertDebug(self.viewItem.attachmentStream);
return self.viewItem.attachmentStream;
}
- (nullable TSAttachmentPointer *)attachmentPointer
{
// This should always be valid for the appropriate cell types.
OWSAssertDebug(self.viewItem.attachmentPointer);
return self.viewItem.attachmentPointer;
}
- (TSMessage *)message - (TSMessage *)message
{ {
OWSAssertDebug([self.viewItem.interaction isKindOfClass:[TSMessage class]]); OWSAssertDebug([self.viewItem.interaction isKindOfClass:[TSMessage class]]);
@ -276,21 +269,20 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
case OWSMessageCellType_TextOnlyMessage: case OWSMessageCellType_TextOnlyMessage:
break; break;
case OWSMessageCellType_Audio: case OWSMessageCellType_Audio:
OWSAssertDebug(self.viewItem.attachmentStream);
bodyMediaView = [self loadViewForAudio]; bodyMediaView = [self loadViewForAudio];
break; break;
case OWSMessageCellType_GenericAttachment: case OWSMessageCellType_GenericAttachment:
bodyMediaView = [self loadViewForGenericAttachment]; bodyMediaView = [self loadViewForGenericAttachment];
break; break;
case OWSMessageCellType_DownloadingAttachment:
bodyMediaView = [self loadViewForDownloadingAttachment];
break;
case OWSMessageCellType_ContactShare: case OWSMessageCellType_ContactShare:
bodyMediaView = [self loadViewForContactShare]; bodyMediaView = [self loadViewForContactShare];
break; break;
case OWSMessageCellType_MediaMessage: case OWSMessageCellType_MediaMessage:
bodyMediaView = [self loadViewForMediaAlbum]; bodyMediaView = [self loadViewForMediaAlbum];
break; break;
case OWSMessageCellType_OversizeTextDownloading:
bodyMediaView = [self loadViewForOversizeTextDownload];
break;
} }
if (bodyMediaView) { if (bodyMediaView) {
@ -572,8 +564,8 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
case OWSMessageCellType_TextOnlyMessage: case OWSMessageCellType_TextOnlyMessage:
case OWSMessageCellType_Audio: case OWSMessageCellType_Audio:
case OWSMessageCellType_GenericAttachment: case OWSMessageCellType_GenericAttachment:
case OWSMessageCellType_DownloadingAttachment:
case OWSMessageCellType_ContactShare: case OWSMessageCellType_ContactShare:
case OWSMessageCellType_OversizeTextDownloading:
return NO; return NO;
case OWSMessageCellType_MediaMessage: case OWSMessageCellType_MediaMessage:
return YES; return YES;
@ -587,9 +579,9 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
return NO; return NO;
case OWSMessageCellType_Audio: case OWSMessageCellType_Audio:
case OWSMessageCellType_GenericAttachment: case OWSMessageCellType_GenericAttachment:
case OWSMessageCellType_DownloadingAttachment:
case OWSMessageCellType_ContactShare: case OWSMessageCellType_ContactShare:
case OWSMessageCellType_MediaMessage: case OWSMessageCellType_MediaMessage:
case OWSMessageCellType_OversizeTextDownloading:
return YES; return YES;
} }
} }
@ -837,16 +829,17 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
- (UIView *)loadViewForAudio - (UIView *)loadViewForAudio
{ {
OWSAssertDebug(self.attachmentStream); TSAttachment *attachment = (self.viewItem.attachmentStream ?: self.viewItem.attachmentPointer);
OWSAssertDebug([self.attachmentStream isAudio]); OWSAssertDebug(attachment);
OWSAssertDebug([attachment isAudio]);
OWSAudioMessageView *audioMessageView = [[OWSAudioMessageView alloc] initWithAttachment:self.attachmentStream OWSAudioMessageView *audioMessageView = [[OWSAudioMessageView alloc] initWithAttachment:attachment
isIncoming:self.isIncoming isIncoming:self.isIncoming
viewItem:self.viewItem viewItem:self.viewItem
conversationStyle:self.conversationStyle]; conversationStyle:self.conversationStyle];
self.viewItem.lastAudioMessageView = audioMessageView; self.viewItem.lastAudioMessageView = audioMessageView;
[audioMessageView createContents]; [audioMessageView createContents];
[self addAttachmentUploadViewIfNecessary]; [self addProgressViewsIfNecessary:audioMessageView];
self.loadCellContentBlock = ^{ self.loadCellContentBlock = ^{
// Do nothing. // Do nothing.
@ -860,11 +853,12 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
- (UIView *)loadViewForGenericAttachment - (UIView *)loadViewForGenericAttachment
{ {
OWSAssertDebug(self.viewItem.attachmentStream); TSAttachment *attachment = (self.viewItem.attachmentStream ?: self.viewItem.attachmentPointer);
OWSAssertDebug(attachment);
OWSGenericAttachmentView *attachmentView = OWSGenericAttachmentView *attachmentView =
[[OWSGenericAttachmentView alloc] initWithAttachment:self.attachmentStream isIncoming:self.isIncoming]; [[OWSGenericAttachmentView alloc] initWithAttachment:attachment isIncoming:self.isIncoming];
[attachmentView createContentsWithConversationStyle:self.conversationStyle]; [attachmentView createContentsWithConversationStyle:self.conversationStyle];
[self addAttachmentUploadViewIfNecessary]; [self addProgressViewsIfNecessary:attachmentView];
self.loadCellContentBlock = ^{ self.loadCellContentBlock = ^{
// Do nothing. // Do nothing.
@ -876,20 +870,15 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
return attachmentView; return attachmentView;
} }
- (UIView *)loadViewForDownloadingAttachment - (UIView *)loadViewForContactShare
{ {
OWSAssertDebug(self.attachmentPointer); OWSAssertDebug(self.viewItem.contactShare);
// TODO: We probably want to do something different for attachments
// being restored from backup.
AttachmentPointerView *downloadView =
[[AttachmentPointerView alloc] initWithAttachmentPointer:self.attachmentPointer
isIncoming:self.isIncoming
conversationStyle:self.conversationStyle];
UIView *wrapper = [UIView new]; OWSContactShareView *contactShareView = [[OWSContactShareView alloc] initWithContactShare:self.viewItem.contactShare
[wrapper addSubview:downloadView]; isIncoming:self.isIncoming
[downloadView autoPinEdgesToSuperviewEdges]; conversationStyle:self.conversationStyle];
[contactShareView createContents];
// TODO: Should we change appearance if contact avatar is uploading?
self.loadCellContentBlock = ^{ self.loadCellContentBlock = ^{
// Do nothing. // Do nothing.
@ -898,18 +887,16 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
// Do nothing. // Do nothing.
}; };
return wrapper; return contactShareView;
} }
- (UIView *)loadViewForContactShare - (UIView *)loadViewForOversizeTextDownload
{ {
OWSAssertDebug(self.viewItem.contactShare); // We can use an empty view. The progress views will display download
// progress or tap-to-retry UI.
UIView *attachmentView = [UIView new];
OWSContactShareView *contactShareView = [[OWSContactShareView alloc] initWithContactShare:self.viewItem.contactShare [self addProgressViewsIfNecessary:attachmentView];
isIncoming:self.isIncoming
conversationStyle:self.conversationStyle];
[contactShareView createContents];
// TODO: Should we change appearance if contact avatar is uploading?
self.loadCellContentBlock = ^{ self.loadCellContentBlock = ^{
// Do nothing. // Do nothing.
@ -918,33 +905,101 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
// Do nothing. // Do nothing.
}; };
return contactShareView; return attachmentView;
} }
- (void)addAttachmentUploadViewIfNecessary - (void)addProgressViewsIfNecessary:(UIView *)bodyMediaView
{ {
[self addAttachmentUploadViewIfNecessaryWithAttachmentStateCallback:nil]; if (self.viewItem.attachmentStream) {
[self addUploadViewIfNecessary:bodyMediaView];
} else if (self.viewItem.attachmentPointer) {
[self addDownloadViewIfNecessary:bodyMediaView];
}
} }
- (void)addAttachmentUploadViewIfNecessaryWithAttachmentStateCallback: - (void)addUploadViewIfNecessary:(UIView *)bodyMediaView
(nullable AttachmentStateBlock)attachmentStateCallback
{ {
OWSAssertDebug(self.attachmentStream); OWSAssertDebug(self.viewItem.attachmentStream);
if (!attachmentStateCallback) { if (!self.isOutgoing) {
attachmentStateCallback = ^(BOOL isAttachmentReady) { return;
}; }
if (self.viewItem.attachmentStream.isUploaded) {
return;
} }
if (self.isOutgoing) { AttachmentUploadView *uploadView = [[AttachmentUploadView alloc] initWithAttachment:self.viewItem.attachmentStream];
if (!self.attachmentStream.isUploaded) { [self.bubbleView addSubview:uploadView];
AttachmentUploadView *attachmentUploadView = [uploadView autoPinEdgesToSuperviewEdges];
[[AttachmentUploadView alloc] initWithAttachment:self.attachmentStream [uploadView setContentHuggingLow];
attachmentStateCallback:attachmentStateCallback]; [uploadView setCompressionResistanceLow];
[self.bubbleView addSubview:attachmentUploadView]; }
[attachmentUploadView ows_autoPinToSuperviewEdges];
} - (void)addDownloadViewIfNecessary:(UIView *)bodyMediaView
{
OWSAssertDebug(self.viewItem.attachmentPointer);
switch (self.viewItem.attachmentPointer.state) {
case TSAttachmentPointerStateFailed:
[self addTapToRetryView:bodyMediaView];
return;
case TSAttachmentPointerStateEnqueued:
case TSAttachmentPointerStateDownloading:
break;
}
switch (self.viewItem.attachmentPointer.pointerType) {
case TSAttachmentPointerTypeRestoring:
// TODO: Show "restoring" indicator and possibly progress.
return;
case TSAttachmentPointerTypeUnknown:
case TSAttachmentPointerTypeIncoming:
break;
}
NSString *_Nullable uniqueId = self.viewItem.attachmentPointer.uniqueId;
if (uniqueId.length < 1) {
OWSFailDebug(@"Missing uniqueId.");
return;
}
if ([self.attachmentDownloads downloadProgressForAttachmentId:uniqueId] == nil) {
OWSFailDebug(@"Missing download progress.");
return;
} }
UIView *overlayView = [UIView new];
overlayView.backgroundColor = [self.bubbleColor colorWithAlphaComponent:0.5];
[bodyMediaView addSubview:overlayView];
[overlayView autoPinEdgesToSuperviewEdges];
[overlayView setContentHuggingLow];
[overlayView setCompressionResistanceLow];
MediaDownloadView *downloadView =
[[MediaDownloadView alloc] initWithAttachmentId:uniqueId radius:self.conversationStyle.maxMessageWidth * 0.1f];
bodyMediaView.layer.opacity = 0.5f;
[self.bubbleView addSubview:downloadView];
[downloadView autoPinEdgesToSuperviewEdges];
[downloadView setContentHuggingLow];
[downloadView setCompressionResistanceLow];
}
- (void)addTapToRetryView:(UIView *)bodyMediaView
{
OWSAssertDebug(self.viewItem.attachmentPointer);
// Hide the body media view, replace with "tap to retry" indicator.
UILabel *label = [UILabel new];
label.text = NSLocalizedString(
@"ATTACHMENT_DOWNLOADING_STATUS_FAILED", @"Status label when an attachment download has failed.");
label.font = UIFont.ows_dynamicTypeBodyFont;
label.textColor = Theme.secondaryColor;
label.numberOfLines = 0;
label.lineBreakMode = NSLineBreakByWordWrapping;
label.textAlignment = NSTextAlignmentCenter;
label.backgroundColor = self.bubbleColor;
[bodyMediaView addSubview:label];
[label autoPinEdgesToSuperviewMargins];
[label setContentHuggingLow];
[label setCompressionResistanceLow];
} }
- (void)showAttachmentErrorViewWithMediaView:(UIView *)mediaView - (void)showAttachmentErrorViewWithMediaView:(UIView *)mediaView
@ -1005,16 +1060,14 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
result = CGSizeMake(maxMessageWidth, OWSAudioMessageView.bubbleHeight); result = CGSizeMake(maxMessageWidth, OWSAudioMessageView.bubbleHeight);
break; break;
case OWSMessageCellType_GenericAttachment: { case OWSMessageCellType_GenericAttachment: {
OWSAssertDebug(self.viewItem.attachmentStream); TSAttachment *attachment = (self.viewItem.attachmentStream ?: self.viewItem.attachmentPointer);
OWSAssertDebug(attachment);
OWSGenericAttachmentView *attachmentView = OWSGenericAttachmentView *attachmentView =
[[OWSGenericAttachmentView alloc] initWithAttachment:self.attachmentStream isIncoming:self.isIncoming]; [[OWSGenericAttachmentView alloc] initWithAttachment:attachment isIncoming:self.isIncoming];
[attachmentView createContentsWithConversationStyle:self.conversationStyle]; [attachmentView createContentsWithConversationStyle:self.conversationStyle];
result = [attachmentView measureSizeWithMaxMessageWidth:maxMessageWidth]; result = [attachmentView measureSizeWithMaxMessageWidth:maxMessageWidth];
break; break;
} }
case OWSMessageCellType_DownloadingAttachment:
result = CGSizeMake(MIN(200, maxMessageWidth), [AttachmentPointerView measureHeight]);
break;
case OWSMessageCellType_ContactShare: case OWSMessageCellType_ContactShare:
OWSAssertDebug(self.viewItem.contactShare); OWSAssertDebug(self.viewItem.contactShare);
@ -1027,8 +1080,7 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
if (self.viewItem.mediaAlbumItems.count == 1) { if (self.viewItem.mediaAlbumItems.count == 1) {
// Honor the content aspect ratio for single media. // Honor the content aspect ratio for single media.
ConversationMediaAlbumItem *mediaAlbumItem = self.viewItem.mediaAlbumItems.firstObject; ConversationMediaAlbumItem *mediaAlbumItem = self.viewItem.mediaAlbumItems.firstObject;
if (mediaAlbumItem.attachmentStream && mediaAlbumItem.mediaSize.width > 0 if (mediaAlbumItem.mediaSize.width > 0 && mediaAlbumItem.mediaSize.height > 0) {
&& mediaAlbumItem.mediaSize.height > 0) {
CGSize mediaSize = mediaAlbumItem.mediaSize; CGSize mediaSize = mediaAlbumItem.mediaSize;
CGFloat contentAspectRatio = mediaSize.width / mediaSize.height; CGFloat contentAspectRatio = mediaSize.width / mediaSize.height;
// Clamp the aspect ratio so that very thin/wide content is presented // Clamp the aspect ratio so that very thin/wide content is presented
@ -1060,6 +1112,11 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
} }
} }
break; break;
case OWSMessageCellType_OversizeTextDownloading:
// There's no way to predict the size of the oversize text,
// so we just use a square bubble.
result = CGSizeMake(maxMessageWidth, maxMessageWidth);
break;
} }
OWSAssertDebug(result.width <= maxMessageWidth); OWSAssertDebug(result.width <= maxMessageWidth);
@ -1392,29 +1449,26 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
{ {
OWSAssertDebug(self.delegate); OWSAssertDebug(self.delegate);
if (self.viewItem.attachmentPointer && self.viewItem.attachmentPointer.state == TSAttachmentPointerStateFailed) {
[self.delegate didTapFailedIncomingAttachment:self.viewItem];
return;
}
switch (self.cellType) { switch (self.cellType) {
case OWSMessageCellType_Unknown: case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextOnlyMessage: case OWSMessageCellType_TextOnlyMessage:
case OWSMessageCellType_OversizeTextDownloading:
break; break;
case OWSMessageCellType_Audio: case OWSMessageCellType_Audio:
OWSAssertDebug(self.viewItem.attachmentStream); if (self.viewItem.attachmentStream) {
[self.delegate didTapAudioViewItem:self.viewItem attachmentStream:self.viewItem.attachmentStream];
[self.delegate didTapAudioViewItem:self.viewItem attachmentStream:self.viewItem.attachmentStream]; }
return; return;
case OWSMessageCellType_GenericAttachment: case OWSMessageCellType_GenericAttachment:
OWSAssertDebug(self.viewItem.attachmentStream); if (self.viewItem.attachmentStream) {
[AttachmentSharing showShareUIForAttachment:self.viewItem.attachmentStream];
[AttachmentSharing showShareUIForAttachment:self.viewItem.attachmentStream];
break;
case OWSMessageCellType_DownloadingAttachment: {
TSAttachmentPointer *_Nullable attachmentPointer = self.viewItem.attachmentPointer;
OWSAssertDebug(attachmentPointer);
if (attachmentPointer.state == TSAttachmentPointerStateFailed) {
[self.delegate didTapFailedIncomingAttachment:self.viewItem];
} }
break; break;
}
case OWSMessageCellType_ContactShare: case OWSMessageCellType_ContactShare:
[self.delegate didTapContactShareViewItem:self.viewItem]; [self.delegate didTapContactShareViewItem:self.viewItem];
break; break;

@ -12,9 +12,9 @@ typedef NS_ENUM(NSInteger, OWSMessageCellType) {
OWSMessageCellType_TextOnlyMessage, OWSMessageCellType_TextOnlyMessage,
OWSMessageCellType_Audio, OWSMessageCellType_Audio,
OWSMessageCellType_GenericAttachment, OWSMessageCellType_GenericAttachment,
OWSMessageCellType_DownloadingAttachment,
OWSMessageCellType_ContactShare, OWSMessageCellType_ContactShare,
OWSMessageCellType_MediaMessage, OWSMessageCellType_MediaMessage,
OWSMessageCellType_OversizeTextDownloading,
}; };
NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType); NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);

@ -26,14 +26,14 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
return @"OWSMessageCellType_Audio"; return @"OWSMessageCellType_Audio";
case OWSMessageCellType_GenericAttachment: case OWSMessageCellType_GenericAttachment:
return @"OWSMessageCellType_GenericAttachment"; return @"OWSMessageCellType_GenericAttachment";
case OWSMessageCellType_DownloadingAttachment:
return @"OWSMessageCellType_DownloadingAttachment";
case OWSMessageCellType_Unknown: case OWSMessageCellType_Unknown:
return @"OWSMessageCellType_Unknown"; return @"OWSMessageCellType_Unknown";
case OWSMessageCellType_ContactShare: case OWSMessageCellType_ContactShare:
return @"OWSMessageCellType_ContactShare"; return @"OWSMessageCellType_ContactShare";
case OWSMessageCellType_MediaMessage: case OWSMessageCellType_MediaMessage:
return @"OWSMessageCellType_MediaMessage"; return @"OWSMessageCellType_MediaMessage";
case OWSMessageCellType_OversizeTextDownloading:
return @"OWSMessageCellType_OversizeTextDownloading";
} }
} }
@ -591,10 +591,16 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
} }
TSAttachment *_Nullable oversizeTextAttachment = [message oversizeTextAttachmentWithTransaction:transaction]; TSAttachment *_Nullable oversizeTextAttachment = [message oversizeTextAttachmentWithTransaction:transaction];
if (oversizeTextAttachment != nil && [oversizeTextAttachment isKindOfClass:[TSAttachmentStream class]]) { if ([oversizeTextAttachment isKindOfClass:[TSAttachmentStream class]]) {
TSAttachmentStream *oversizeTextAttachmentStream = (TSAttachmentStream *)oversizeTextAttachment; TSAttachmentStream *oversizeTextAttachmentStream = (TSAttachmentStream *)oversizeTextAttachment;
self.displayableBodyText = [self displayableBodyTextForOversizeTextAttachment:oversizeTextAttachmentStream self.displayableBodyText = [self displayableBodyTextForOversizeTextAttachment:oversizeTextAttachmentStream
interactionId:message.uniqueId]; 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;
} else { } else {
NSString *_Nullable bodyText = [message bodyTextWithTransaction:transaction]; NSString *_Nullable bodyText = [message bodyTextWithTransaction:transaction];
if (bodyText) { if (bodyText) {
@ -638,7 +644,12 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
self.messageCellType = OWSMessageCellType_GenericAttachment; self.messageCellType = OWSMessageCellType_GenericAttachment;
} }
} else if ([mediaAttachment isKindOfClass:[TSAttachmentPointer class]]) { } else if ([mediaAttachment isKindOfClass:[TSAttachmentPointer class]]) {
self.messageCellType = OWSMessageCellType_DownloadingAttachment; if ([mediaAttachment isAudio]) {
self.audioDurationSeconds = 0;
self.messageCellType = OWSMessageCellType_Audio;
} else {
self.messageCellType = OWSMessageCellType_GenericAttachment;
}
self.attachmentPointer = (TSAttachmentPointer *)mediaAttachment; self.attachmentPointer = (TSAttachmentPointer *)mediaAttachment;
} else { } else {
OWSFailDebug(@"Unknown attachment type"); OWSFailDebug(@"Unknown attachment type");
@ -704,10 +715,15 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
: nil); : nil);
if (![attachment isKindOfClass:[TSAttachmentStream class]]) { if (![attachment isKindOfClass:[TSAttachmentStream class]]) {
TSAttachmentPointer *attachmentPointer = (TSAttachmentPointer *)attachment;
CGSize mediaSize = CGSizeZero;
if (attachmentPointer.mediaSize.width > 0 && attachmentPointer.mediaSize.height > 0) {
mediaSize = attachmentPointer.mediaSize;
}
[mediaAlbumItems addObject:[[ConversationMediaAlbumItem alloc] initWithAttachment:attachment [mediaAlbumItems addObject:[[ConversationMediaAlbumItem alloc] initWithAttachment:attachment
attachmentStream:nil attachmentStream:nil
caption:caption caption:caption
mediaSize:CGSizeZero]]; mediaSize:mediaSize]];
continue; continue;
} }
TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment; TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment;
@ -849,6 +865,11 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
- (void)copyTextAction - (void)copyTextAction
{ {
if (self.attachmentPointer != nil) {
OWSFailDebug(@"Can't copy not-yet-downloaded attachment");
return;
}
switch (self.messageCellType) { switch (self.messageCellType) {
case OWSMessageCellType_TextOnlyMessage: case OWSMessageCellType_TextOnlyMessage:
case OWSMessageCellType_Audio: case OWSMessageCellType_Audio:
@ -858,10 +879,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
[UIPasteboard.generalPasteboard setString:self.displayableBodyText.fullText]; [UIPasteboard.generalPasteboard setString:self.displayableBodyText.fullText];
break; break;
} }
case OWSMessageCellType_DownloadingAttachment: {
OWSFailDebug(@"Can't copy not-yet-downloaded attachment");
break;
}
case OWSMessageCellType_Unknown: { case OWSMessageCellType_Unknown: {
OWSFailDebug(@"No text to copy"); OWSFailDebug(@"No text to copy");
break; break;
@ -871,11 +888,19 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
OWSFailDebug(@"Not implemented yet"); OWSFailDebug(@"Not implemented yet");
break; break;
} }
case OWSMessageCellType_OversizeTextDownloading:
OWSFailDebug(@"Can't copy not-yet-downloaded attachment");
return;
} }
} }
- (void)copyMediaAction - (void)copyMediaAction
{ {
if (self.attachmentPointer != nil) {
OWSFailDebug(@"Can't copy not-yet-downloaded attachment");
return;
}
switch (self.messageCellType) { switch (self.messageCellType) {
case OWSMessageCellType_Unknown: case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextOnlyMessage: case OWSMessageCellType_TextOnlyMessage:
@ -888,10 +913,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
[self copyAttachmentToPasteboard:self.attachmentStream]; [self copyAttachmentToPasteboard:self.attachmentStream];
break; break;
} }
case OWSMessageCellType_DownloadingAttachment: {
OWSFailDebug(@"Can't copy not-yet-downloaded attachment");
break;
}
case OWSMessageCellType_MediaMessage: { case OWSMessageCellType_MediaMessage: {
if (self.mediaAlbumItems.count == 1) { if (self.mediaAlbumItems.count == 1) {
ConversationMediaAlbumItem *mediaAlbumItem = self.mediaAlbumItems.firstObject; ConversationMediaAlbumItem *mediaAlbumItem = self.mediaAlbumItems.firstObject;
@ -904,6 +925,9 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
OWSFailDebug(@"Can't copy media album"); OWSFailDebug(@"Can't copy media album");
break; break;
} }
case OWSMessageCellType_OversizeTextDownloading:
OWSFailDebug(@"Can't copy not-yet-downloaded attachment");
return;
} }
} }
@ -926,6 +950,11 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
- (void)shareMediaAction - (void)shareMediaAction
{ {
if (self.attachmentPointer != nil) {
OWSFailDebug(@"Can't share not-yet-downloaded attachment");
return;
}
switch (self.messageCellType) { switch (self.messageCellType) {
case OWSMessageCellType_Unknown: case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextOnlyMessage: case OWSMessageCellType_TextOnlyMessage:
@ -936,10 +965,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
case OWSMessageCellType_GenericAttachment: case OWSMessageCellType_GenericAttachment:
[AttachmentSharing showShareUIForAttachment:self.attachmentStream]; [AttachmentSharing showShareUIForAttachment:self.attachmentStream];
break; break;
case OWSMessageCellType_DownloadingAttachment: {
OWSFailDebug(@"Can't share not-yet-downloaded attachment");
break;
}
case OWSMessageCellType_MediaMessage: { case OWSMessageCellType_MediaMessage: {
// TODO: We need a "canShareMediaAction" method. // TODO: We need a "canShareMediaAction" method.
OWSAssertDebug(self.mediaAlbumItems); OWSAssertDebug(self.mediaAlbumItems);
@ -956,11 +981,19 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
[AttachmentSharing showShareUIForAttachments:attachmentStreams completion:nil]; [AttachmentSharing showShareUIForAttachments:attachmentStreams completion:nil];
break; break;
} }
case OWSMessageCellType_OversizeTextDownloading:
OWSFailDebug(@"Can't share not-yet-downloaded attachment");
return;
} }
} }
- (BOOL)canCopyMedia - (BOOL)canCopyMedia
{ {
if (self.attachmentPointer != nil) {
// The attachment is still downloading.
return NO;
}
switch (self.messageCellType) { switch (self.messageCellType) {
case OWSMessageCellType_Unknown: case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextOnlyMessage: case OWSMessageCellType_TextOnlyMessage:
@ -969,7 +1002,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
case OWSMessageCellType_Audio: case OWSMessageCellType_Audio:
return NO; return NO;
case OWSMessageCellType_GenericAttachment: case OWSMessageCellType_GenericAttachment:
case OWSMessageCellType_DownloadingAttachment:
case OWSMessageCellType_MediaMessage: { case OWSMessageCellType_MediaMessage: {
if (self.mediaAlbumItems.count == 1) { if (self.mediaAlbumItems.count == 1) {
ConversationMediaAlbumItem *mediaAlbumItem = self.mediaAlbumItems.firstObject; ConversationMediaAlbumItem *mediaAlbumItem = self.mediaAlbumItems.firstObject;
@ -979,11 +1011,18 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
} }
return NO; return NO;
} }
case OWSMessageCellType_OversizeTextDownloading:
return NO;
} }
} }
- (BOOL)canSaveMedia - (BOOL)canSaveMedia
{ {
if (self.attachmentPointer != nil) {
// The attachment is still downloading.
return NO;
}
switch (self.messageCellType) { switch (self.messageCellType) {
case OWSMessageCellType_Unknown: case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextOnlyMessage: case OWSMessageCellType_TextOnlyMessage:
@ -992,7 +1031,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
case OWSMessageCellType_Audio: case OWSMessageCellType_Audio:
return NO; return NO;
case OWSMessageCellType_GenericAttachment: case OWSMessageCellType_GenericAttachment:
case OWSMessageCellType_DownloadingAttachment:
return NO; return NO;
case OWSMessageCellType_MediaMessage: { case OWSMessageCellType_MediaMessage: {
for (ConversationMediaAlbumItem *mediaAlbumItem in self.mediaAlbumItems) { for (ConversationMediaAlbumItem *mediaAlbumItem in self.mediaAlbumItems) {
@ -1014,11 +1052,17 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
} }
return NO; return NO;
} }
case OWSMessageCellType_OversizeTextDownloading:
return NO;
} }
} }
- (void)saveMediaAction - (void)saveMediaAction
{ {
if (self.attachmentPointer != nil) {
OWSFailDebug(@"Can't save not-yet-downloaded attachment");
return;
}
switch (self.messageCellType) { switch (self.messageCellType) {
case OWSMessageCellType_Unknown: case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextOnlyMessage: case OWSMessageCellType_TextOnlyMessage:
@ -1031,14 +1075,13 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
case OWSMessageCellType_GenericAttachment: case OWSMessageCellType_GenericAttachment:
OWSFailDebug(@"Cannot save media data."); OWSFailDebug(@"Cannot save media data.");
break; break;
case OWSMessageCellType_DownloadingAttachment: {
OWSFailDebug(@"Can't save not-yet-downloaded attachment");
break;
}
case OWSMessageCellType_MediaMessage: { case OWSMessageCellType_MediaMessage: {
[self saveMediaAlbumItems]; [self saveMediaAlbumItems];
break; break;
} }
case OWSMessageCellType_OversizeTextDownloading:
OWSFailDebug(@"Can't save not-yet-downloaded attachment");
return;
} }
} }
@ -1101,6 +1144,11 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
- (BOOL)hasMediaActionContent - (BOOL)hasMediaActionContent
{ {
if (self.attachmentPointer != nil) {
// The attachment is still downloading.
return NO;
}
switch (self.messageCellType) { switch (self.messageCellType) {
case OWSMessageCellType_Unknown: case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextOnlyMessage: case OWSMessageCellType_TextOnlyMessage:
@ -1109,11 +1157,10 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
case OWSMessageCellType_Audio: case OWSMessageCellType_Audio:
case OWSMessageCellType_GenericAttachment: case OWSMessageCellType_GenericAttachment:
return self.attachmentStream != nil; return self.attachmentStream != nil;
case OWSMessageCellType_DownloadingAttachment: {
return NO;
}
case OWSMessageCellType_MediaMessage: case OWSMessageCellType_MediaMessage:
return self.firstValidAlbumAttachment != nil; return self.firstValidAlbumAttachment != nil;
case OWSMessageCellType_OversizeTextDownloading:
return NO;
} }
} }

@ -3802,7 +3802,8 @@ typedef OWSContact * (^OWSContactBlock)(YapDatabaseReadWriteTransaction *transac
sourceFilename:@"test.mp3" sourceFilename:@"test.mp3"
caption:nil caption:nil
albumMessageId:nil albumMessageId:nil
attachmentType:TSAttachmentTypeDefault]; attachmentType:TSAttachmentTypeDefault
mediaSize:CGSizeZero];
pointer.state = TSAttachmentPointerStateFailed; pointer.state = TSAttachmentPointerStateFailed;
[pointer saveWithTransaction:transaction]; [pointer saveWithTransaction:transaction];
// MJK - should be safe to remove this senderTimestamp // MJK - should be safe to remove this senderTimestamp
@ -4701,7 +4702,8 @@ typedef OWSContact * (^OWSContactBlock)(YapDatabaseReadWriteTransaction *transac
sourceFilename:fakeAssetLoader.filename sourceFilename:fakeAssetLoader.filename
caption:nil caption:nil
albumMessageId:nil albumMessageId:nil
attachmentType:TSAttachmentTypeDefault]; attachmentType:TSAttachmentTypeDefault
mediaSize:CGSizeZero];
attachmentPointer.state = TSAttachmentPointerStateFailed; attachmentPointer.state = TSAttachmentPointerStateFailed;
[attachmentPointer saveWithTransaction:transaction]; [attachmentPointer saveWithTransaction:transaction];
return attachmentPointer; return attachmentPointer;

@ -1,145 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
import SignalServiceKit
import SignalMessaging
class AttachmentPointerView: UIStackView {
let isIncoming: Bool
let attachmentPointer: TSAttachmentPointer
let conversationStyle: ConversationStyle
let progressView = OWSProgressView()
let nameLabel = UILabel()
let statusLabel = UILabel()
let filename: String
let genericFilename = NSLocalizedString("ATTACHMENT_DEFAULT_FILENAME", comment: "Generic filename for an attachment with no known name")
var progress: CGFloat = 0 {
didSet {
self.progressView.progress = progress
}
}
@objc
required init(attachmentPointer: TSAttachmentPointer, isIncoming: Bool, conversationStyle: ConversationStyle) {
self.attachmentPointer = attachmentPointer
self.isIncoming = isIncoming
self.conversationStyle = conversationStyle
let attachmentPointerFilename = attachmentPointer.sourceFilename
if let filename = attachmentPointerFilename, !filename.isEmpty {
self.filename = filename
} else {
self.filename = genericFilename
}
super.init(frame: CGRect.zero)
createSubviews()
updateViews()
if attachmentPointer.state == .downloading {
NotificationCenter.default.addObserver(self,
selector: #selector(attachmentDownloadProgress(_:)),
name: NSNotification.Name.attachmentDownloadProgress,
object: nil)
}
}
deinit {
NotificationCenter.default.removeObserver(self)
}
@objc internal func attachmentDownloadProgress(_ notification: Notification) {
guard let attachmentId = attachmentPointer.uniqueId else {
owsFailDebug("Missing attachment id.")
return
}
guard let progress = (notification as NSNotification).userInfo?[kAttachmentDownloadProgressKey] as? NSNumber else {
owsFailDebug("Attachment download notification missing progress.")
return
}
guard let notificationAttachmentId = (notification as NSNotification).userInfo?[kAttachmentDownloadAttachmentIDKey] as? String else {
owsFailDebug("Attachment download notification missing attachment id.")
return
}
guard notificationAttachmentId == attachmentId else {
return
}
self.progress = CGFloat(progress.floatValue)
}
@available(*, unavailable, message: "use init(call:) constructor instead.")
required init(coder aDecoder: NSCoder) {
notImplemented()
}
private static var vSpacing: CGFloat = 5
private class func nameFont() -> UIFont { return UIFont.ows_dynamicTypeBody }
private class func statusFont() -> UIFont { return UIFont.ows_dynamicTypeCaption1 }
private static var progressWidth: CGFloat = 80
private static var progressHeight: CGFloat = 6
func createSubviews() {
progressView.autoSetDimension(.width, toSize: AttachmentPointerView.progressWidth)
progressView.autoSetDimension(.height, toSize: AttachmentPointerView.progressHeight)
// truncate middle to be sure we include file extension
nameLabel.lineBreakMode = .byTruncatingMiddle
nameLabel.textAlignment = .center
nameLabel.textColor = self.textColor
nameLabel.font = AttachmentPointerView.nameFont()
statusLabel.textAlignment = .center
statusLabel.adjustsFontSizeToFitWidth = true
statusLabel.numberOfLines = 2
statusLabel.textColor = self.textColor
statusLabel.font = AttachmentPointerView.statusFont()
self.axis = .vertical
self.spacing = AttachmentPointerView.vSpacing
addArrangedSubview(nameLabel)
addArrangedSubview(progressView)
addArrangedSubview(statusLabel)
}
func updateViews() {
let emoji = TSAttachment.emoji(forMimeType: self.attachmentPointer.contentType)
nameLabel.text = "\(emoji) \(self.filename)"
statusLabel.text = {
switch self.attachmentPointer.state {
case .enqueued:
return NSLocalizedString("ATTACHMENT_DOWNLOADING_STATUS_QUEUED", comment: "Status label when an attachment is enqueued, but hasn't yet started downloading")
case .downloading:
return NSLocalizedString("ATTACHMENT_DOWNLOADING_STATUS_IN_PROGRESS", comment: "Status label when an attachment is currently downloading")
case .failed:
return NSLocalizedString("ATTACHMENT_DOWNLOADING_STATUS_FAILED", comment: "Status label when an attachment download has failed.")
}
}()
if attachmentPointer.state == .downloading {
progressView.isHidden = false
progressView.autoSetDimension(.height, toSize: 8)
} else {
progressView.isHidden = true
progressView.autoSetDimension(.height, toSize: 0)
}
}
var textColor: UIColor {
return conversationStyle.bubbleTextColor(isIncoming: isIncoming)
}
@objc
public class func measureHeight() -> CGFloat {
return ceil(nameFont().lineHeight +
statusFont().lineHeight +
progressHeight +
vSpacing * 2)
}
}

@ -1,5 +1,5 @@
// //
// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// //
#import "TSAttachment.h" #import "TSAttachment.h"
@ -36,6 +36,8 @@ typedef NS_ENUM(NSUInteger, TSAttachmentPointerState) {
// messages received from other clients // messages received from other clients
@property (nullable, nonatomic, readonly) NSData *digest; @property (nullable, nonatomic, readonly) NSData *digest;
@property (nonatomic, readonly) CGSize mediaSize;
// Non-nil for attachments which need "lazy backup restore." // Non-nil for attachments which need "lazy backup restore."
- (nullable OWSBackupFragment *)lazyRestoreFragment; - (nullable OWSBackupFragment *)lazyRestoreFragment;
@ -49,7 +51,8 @@ typedef NS_ENUM(NSUInteger, TSAttachmentPointerState) {
sourceFilename:(nullable NSString *)sourceFilename sourceFilename:(nullable NSString *)sourceFilename
caption:(nullable NSString *)caption caption:(nullable NSString *)caption
albumMessageId:(nullable NSString *)albumMessageId albumMessageId:(nullable NSString *)albumMessageId
attachmentType:(TSAttachmentType)attachmentType NS_DESIGNATED_INITIALIZER; attachmentType:(TSAttachmentType)attachmentType
mediaSize:(CGSize)mediaSize NS_DESIGNATED_INITIALIZER;
- (instancetype)initForRestoreWithAttachmentStream:(TSAttachmentStream *)attachmentStream NS_DESIGNATED_INITIALIZER; - (instancetype)initForRestoreWithAttachmentStream:(TSAttachmentStream *)attachmentStream NS_DESIGNATED_INITIALIZER;

@ -1,5 +1,5 @@
// //
// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// //
#import "TSAttachmentPointer.h" #import "TSAttachmentPointer.h"
@ -12,6 +12,15 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@interface TSAttachmentStream (TSAttachmentPointer)
- (CGSize)cachedMediaSize;
@end
#pragma mark -
@interface TSAttachmentPointer () @interface TSAttachmentPointer ()
// Optional property. Only set for attachments which need "lazy backup restore." // Optional property. Only set for attachments which need "lazy backup restore."
@ -53,6 +62,7 @@ NS_ASSUME_NONNULL_BEGIN
caption:(nullable NSString *)caption caption:(nullable NSString *)caption
albumMessageId:(nullable NSString *)albumMessageId albumMessageId:(nullable NSString *)albumMessageId
attachmentType:(TSAttachmentType)attachmentType attachmentType:(TSAttachmentType)attachmentType
mediaSize:(CGSize)mediaSize
{ {
self = [super initWithServerId:serverId self = [super initWithServerId:serverId
encryptionKey:key encryptionKey:key
@ -69,6 +79,7 @@ NS_ASSUME_NONNULL_BEGIN
_state = TSAttachmentPointerStateEnqueued; _state = TSAttachmentPointerStateEnqueued;
self.attachmentType = attachmentType; self.attachmentType = attachmentType;
_pointerType = TSAttachmentPointerTypeIncoming; _pointerType = TSAttachmentPointerTypeIncoming;
_mediaSize = mediaSize;
return self; return self;
} }
@ -89,6 +100,7 @@ NS_ASSUME_NONNULL_BEGIN
_state = TSAttachmentPointerStateEnqueued; _state = TSAttachmentPointerStateEnqueued;
self.attachmentType = attachmentStream.attachmentType; self.attachmentType = attachmentStream.attachmentType;
_pointerType = TSAttachmentPointerTypeRestoring; _pointerType = TSAttachmentPointerTypeRestoring;
_mediaSize = (attachmentStream.shouldHaveImageSize ? attachmentStream.cachedMediaSize : CGSizeZero);
return self; return self;
} }
@ -104,9 +116,19 @@ NS_ASSUME_NONNULL_BEGIN
OWSFailDebug(@"Invalid attachment key."); OWSFailDebug(@"Invalid attachment key.");
return nil; return nil;
} }
if (attachmentProto.contentType.length < 1) { NSString *_Nullable fileName = attachmentProto.fileName;
OWSFailDebug(@"Invalid attachment content type."); NSString *_Nullable contentType = attachmentProto.contentType;
return nil; if (contentType.length < 1) {
// Content type might not set if the sending client can't
// infer a MIME type from the file extension.
OWSLogWarn(@"Invalid attachment content type.");
NSString *_Nullable fileExtension = [fileName pathExtension].lowercaseString;
if (fileExtension.length > 0) {
contentType = [MIMETypeUtil mimeTypeForFileExtension:fileExtension];
}
if (contentType.length < 1) {
contentType = OWSMimeTypeApplicationOctetStream;
}
} }
// digest will be empty for old clients. // digest will be empty for old clients.
@ -129,15 +151,22 @@ NS_ASSUME_NONNULL_BEGIN
albumMessageId = albumMessage.uniqueId; albumMessageId = albumMessage.uniqueId;
} }
CGSize mediaSize = CGSizeZero;
if (attachmentProto.hasWidth && attachmentProto.hasHeight && attachmentProto.width > 0
&& attachmentProto.height > 0) {
mediaSize = CGSizeMake(attachmentProto.width, attachmentProto.height);
}
TSAttachmentPointer *pointer = [[TSAttachmentPointer alloc] initWithServerId:attachmentProto.id TSAttachmentPointer *pointer = [[TSAttachmentPointer alloc] initWithServerId:attachmentProto.id
key:attachmentProto.key key:attachmentProto.key
digest:digest digest:digest
byteCount:attachmentProto.size byteCount:attachmentProto.size
contentType:attachmentProto.contentType contentType:contentType
sourceFilename:attachmentProto.fileName sourceFilename:fileName
caption:caption caption:caption
albumMessageId:albumMessageId albumMessageId:albumMessageId
attachmentType:attachmentType]; attachmentType:attachmentType
mediaSize:mediaSize];
return pointer; return pointer;
} }

@ -526,6 +526,19 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
} }
} }
- (CGSize)cachedMediaSize
{
OWSAssertDebug(self.shouldHaveImageSize);
@synchronized(self) {
if (self.cachedImageWidth && self.cachedImageHeight) {
return CGSizeMake(self.cachedImageWidth.floatValue, self.cachedImageHeight.floatValue);
} else {
return CGSizeZero;
}
}
}
#pragma mark - Update With... #pragma mark - Update With...
- (void)applyChangeAsyncToLatestCopyWithChangeBlock:(void (^)(TSAttachmentStream *))changeBlock - (void)applyChangeAsyncToLatestCopyWithChangeBlock:(void (^)(TSAttachmentStream *))changeBlock

Loading…
Cancel
Save