Merge branch 'mkirk/long-text'

pull/2/head
Michael Kirk 6 years ago
commit 0bd113e5d7

@ -269,7 +269,7 @@ CHECKOUT OPTIONS:
:commit: 9599b1d9796280c97cb2f786f34984fc98a3b6ef :commit: 9599b1d9796280c97cb2f786f34984fc98a3b6ef
:git: https://github.com/signalapp/Mantle :git: https://github.com/signalapp/Mantle
SignalCoreKit: SignalCoreKit:
:commit: 2bf143469ab82fb35379fe9f5085450edefb2a2a :commit: 061f41321675ffe5af5e547d578bbd2266a46d33
:git: https://github.com/signalapp/SignalCoreKit.git :git: https://github.com/signalapp/SignalCoreKit.git
SignalMetadataKit: SignalMetadataKit:
:commit: 56f28fc3a6e35d548d034ef7d0009f233ca0aa62 :commit: 56f28fc3a6e35d548d034ef7d0009f233ca0aa62

@ -1 +1 @@
Subproject commit 434610837e73668d186716fd2e5b8913c84ef46a Subproject commit 4e153f47ef16d84b3256dd5d3884c708fdf0ab33

@ -325,7 +325,7 @@ private class MockConversationViewItem: NSObject, ConversationViewItem {
var lastAudioMessageView: OWSAudioMessageView? var lastAudioMessageView: OWSAudioMessageView?
var audioDurationSeconds: CGFloat = 0 var audioDurationSeconds: CGFloat = 0
var audioProgressSeconds: CGFloat = 0 var audioProgressSeconds: CGFloat = 0
var messageCellType: OWSMessageCellType = .textMessage var messageCellType: OWSMessageCellType = .textOnlyMessage
var displayableBodyText: DisplayableText? var displayableBodyText: DisplayableText?
var attachmentStream: TSAttachmentStream? var attachmentStream: TSAttachmentStream?
var attachmentPointer: TSAttachmentPointer? var attachmentPointer: TSAttachmentPointer?

@ -217,28 +217,6 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
return self.viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage; return self.viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage;
} }
#pragma mark -
- (BOOL)hasBodyTextContent
{
switch (self.cellType) {
case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextMessage:
case OWSMessageCellType_OversizeTextMessage:
return YES;
case OWSMessageCellType_GenericAttachment:
case OWSMessageCellType_DownloadingAttachment:
case OWSMessageCellType_Audio:
// Is there a caption?
return self.hasBodyText;
case OWSMessageCellType_ContactShare:
return NO;
case OWSMessageCellType_MediaAlbum:
// Is there an album title?
return self.hasBodyText;
}
}
#pragma mark - Load #pragma mark - Load
- (void)configureViews - (void)configureViews
@ -295,8 +273,7 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
UIView *_Nullable bodyMediaView = nil; UIView *_Nullable bodyMediaView = nil;
switch (self.cellType) { switch (self.cellType) {
case OWSMessageCellType_Unknown: case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextMessage: case OWSMessageCellType_TextOnlyMessage:
case OWSMessageCellType_OversizeTextMessage:
break; break;
case OWSMessageCellType_Audio: case OWSMessageCellType_Audio:
OWSAssertDebug(self.viewItem.attachmentStream); OWSAssertDebug(self.viewItem.attachmentStream);
@ -311,7 +288,7 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
case OWSMessageCellType_ContactShare: case OWSMessageCellType_ContactShare:
bodyMediaView = [self loadViewForContactShare]; bodyMediaView = [self loadViewForContactShare];
break; break;
case OWSMessageCellType_MediaAlbum: case OWSMessageCellType_MediaMessage:
bodyMediaView = [self loadViewForMediaAlbum]; bodyMediaView = [self loadViewForMediaAlbum];
break; break;
} }
@ -592,14 +569,13 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
{ {
switch (self.cellType) { switch (self.cellType) {
case OWSMessageCellType_Unknown: case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextMessage: case OWSMessageCellType_TextOnlyMessage:
case OWSMessageCellType_OversizeTextMessage:
case OWSMessageCellType_Audio: case OWSMessageCellType_Audio:
case OWSMessageCellType_GenericAttachment: case OWSMessageCellType_GenericAttachment:
case OWSMessageCellType_DownloadingAttachment: case OWSMessageCellType_DownloadingAttachment:
case OWSMessageCellType_ContactShare: case OWSMessageCellType_ContactShare:
return NO; return NO;
case OWSMessageCellType_MediaAlbum: case OWSMessageCellType_MediaMessage:
return YES; return YES;
} }
} }
@ -607,14 +583,13 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
- (BOOL)hasBodyMediaView { - (BOOL)hasBodyMediaView {
switch (self.cellType) { switch (self.cellType) {
case OWSMessageCellType_Unknown: case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextMessage: case OWSMessageCellType_TextOnlyMessage:
case OWSMessageCellType_OversizeTextMessage:
return NO; return NO;
case OWSMessageCellType_Audio: case OWSMessageCellType_Audio:
case OWSMessageCellType_GenericAttachment: case OWSMessageCellType_GenericAttachment:
case OWSMessageCellType_DownloadingAttachment: case OWSMessageCellType_DownloadingAttachment:
case OWSMessageCellType_ContactShare: case OWSMessageCellType_ContactShare:
case OWSMessageCellType_MediaAlbum: case OWSMessageCellType_MediaMessage:
return YES; return YES;
} }
} }
@ -622,7 +597,7 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
- (BOOL)hasFullWidthMediaView - (BOOL)hasFullWidthMediaView
{ {
return (self.hasBodyMediaWithThumbnail || self.cellType == OWSMessageCellType_ContactShare return (self.hasBodyMediaWithThumbnail || self.cellType == OWSMessageCellType_ContactShare
|| self.cellType == OWSMessageCellType_MediaAlbum); || self.cellType == OWSMessageCellType_MediaMessage);
} }
- (BOOL)canFooterOverlayMedia - (BOOL)canFooterOverlayMedia
@ -1000,8 +975,7 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
CGSize result = CGSizeZero; CGSize result = CGSizeZero;
switch (self.cellType) { switch (self.cellType) {
case OWSMessageCellType_Unknown: case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextMessage: case OWSMessageCellType_TextOnlyMessage: {
case OWSMessageCellType_OversizeTextMessage: {
return nil; return nil;
} }
case OWSMessageCellType_Audio: case OWSMessageCellType_Audio:
@ -1023,7 +997,7 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
result = CGSizeMake(maxMessageWidth, [OWSContactShareView bubbleHeight]); result = CGSizeMake(maxMessageWidth, [OWSContactShareView bubbleHeight]);
break; break;
case OWSMessageCellType_MediaAlbum: case OWSMessageCellType_MediaMessage:
result = [OWSMediaAlbumCellView layoutSizeForMaxMessageWidth:maxMessageWidth result = [OWSMediaAlbumCellView layoutSizeForMaxMessageWidth:maxMessageWidth
items:self.viewItem.mediaAlbumItems]; items:self.viewItem.mediaAlbumItems];
@ -1396,8 +1370,7 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
switch (self.cellType) { switch (self.cellType) {
case OWSMessageCellType_Unknown: case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextMessage: case OWSMessageCellType_TextOnlyMessage:
case OWSMessageCellType_OversizeTextMessage:
break; break;
case OWSMessageCellType_Audio: case OWSMessageCellType_Audio:
OWSAssertDebug(self.viewItem.attachmentStream); OWSAssertDebug(self.viewItem.attachmentStream);
@ -1421,7 +1394,7 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
case OWSMessageCellType_ContactShare: case OWSMessageCellType_ContactShare:
[self.delegate didTapContactShareViewItem:self.viewItem]; [self.delegate didTapContactShareViewItem:self.viewItem];
break; break;
case OWSMessageCellType_MediaAlbum: { case OWSMessageCellType_MediaMessage: {
OWSAssertDebug(self.bodyMediaView); OWSAssertDebug(self.bodyMediaView);
OWSAssertDebug(self.viewItem.mediaAlbumItems.count > 0); OWSAssertDebug(self.viewItem.mediaAlbumItems.count > 0);

@ -3604,10 +3604,16 @@ typedef enum : NSUInteger {
} }
BOOL didAddToProfileWhitelist = [ThreadUtil addThreadToProfileWhitelistIfEmptyContactThread:self.thread]; BOOL didAddToProfileWhitelist = [ThreadUtil addThreadToProfileWhitelistIfEmptyContactThread:self.thread];
TSOutgoingMessage *message = [ThreadUtil enqueueMessageWithAttachments:attachments
messageBody:messageText __block TSOutgoingMessage *message;
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
message = [ThreadUtil enqueueMessageWithText:messageText
mediaAttachments:attachments
inThread:self.thread inThread:self.thread
quotedReplyModel:self.inputToolbar.quotedReply]; quotedReplyModel:self.inputToolbar.quotedReply
linkPreviewDraft:nil
transaction:transaction];
}];
[self messageWasSent:message]; [self messageWasSent:message];
@ -3975,18 +3981,6 @@ typedef enum : NSUInteger {
// which are presented as normal text messages. // which are presented as normal text messages.
BOOL didAddToProfileWhitelist = [ThreadUtil addThreadToProfileWhitelistIfEmptyContactThread:self.thread]; BOOL didAddToProfileWhitelist = [ThreadUtil addThreadToProfileWhitelistIfEmptyContactThread:self.thread];
__block TSOutgoingMessage *message; __block TSOutgoingMessage *message;
if ([text lengthOfBytesUsingEncoding:NSUTF8StringEncoding] >= kOversizeTextMessageSizeThreshold) {
DataSource *_Nullable dataSource = [DataSourceValue dataSourceWithOversizeText:text];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:kOversizeTextAttachmentUTI];
// TODO we should redundantly send the first n chars in the body field so it can be viewed
// on clients that don't support oversized text messgaes, (and potentially generate a preview
// before the attachment is downloaded)
message = [ThreadUtil enqueueMessageWithAttachment:attachment
inThread:self.thread
quotedReplyModel:self.inputToolbar.quotedReply];
} else {
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
message = [ThreadUtil enqueueMessageWithText:text message = [ThreadUtil enqueueMessageWithText:text
inThread:self.thread inThread:self.thread
@ -3994,7 +3988,6 @@ typedef enum : NSUInteger {
linkPreviewDraft:self.inputToolbar.linkPreviewDraft linkPreviewDraft:self.inputToolbar.linkPreviewDraft
transaction:transaction]; transaction:transaction];
}]; }];
}
[self.conversationViewModel appendUnsavedOutgoingTextMessage:message]; [self.conversationViewModel appendUnsavedOutgoingTextMessage:message];
[self messageWasSent:message]; [self messageWasSent:message];

@ -9,13 +9,12 @@ NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, OWSMessageCellType) { typedef NS_ENUM(NSInteger, OWSMessageCellType) {
OWSMessageCellType_Unknown, OWSMessageCellType_Unknown,
OWSMessageCellType_TextMessage, OWSMessageCellType_TextOnlyMessage,
OWSMessageCellType_OversizeTextMessage,
OWSMessageCellType_Audio, OWSMessageCellType_Audio,
OWSMessageCellType_GenericAttachment, OWSMessageCellType_GenericAttachment,
OWSMessageCellType_DownloadingAttachment, OWSMessageCellType_DownloadingAttachment,
OWSMessageCellType_ContactShare, OWSMessageCellType_ContactShare,
OWSMessageCellType_MediaAlbum, OWSMessageCellType_MediaMessage,
}; };
NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType); NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);

@ -20,10 +20,8 @@ NS_ASSUME_NONNULL_BEGIN
NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
{ {
switch (cellType) { switch (cellType) {
case OWSMessageCellType_TextMessage: case OWSMessageCellType_TextOnlyMessage:
return @"OWSMessageCellType_TextMessage"; return @"OWSMessageCellType_TextOnlyMessage";
case OWSMessageCellType_OversizeTextMessage:
return @"OWSMessageCellType_OversizeTextMessage";
case OWSMessageCellType_Audio: case OWSMessageCellType_Audio:
return @"OWSMessageCellType_Audio"; return @"OWSMessageCellType_Audio";
case OWSMessageCellType_GenericAttachment: case OWSMessageCellType_GenericAttachment:
@ -34,8 +32,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
return @"OWSMessageCellType_Unknown"; return @"OWSMessageCellType_Unknown";
case OWSMessageCellType_ContactShare: case OWSMessageCellType_ContactShare:
return @"OWSMessageCellType_ContactShare"; return @"OWSMessageCellType_ContactShare";
case OWSMessageCellType_MediaAlbum: case OWSMessageCellType_MediaMessage:
return @"OWSMessageCellType_MediaAlbum"; return @"OWSMessageCellType_MediaMessage";
} }
} }
@ -592,11 +590,21 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
} }
} }
NSArray<TSAttachment *> *attachments = [message attachmentsWithTransaction:transaction]; TSAttachment *_Nullable oversizeTextAttachment = [message oversizeTextAttachmentWithTransaction:transaction];
if ([message isMediaAlbumWithTransaction:transaction]) { if (oversizeTextAttachment != nil && [oversizeTextAttachment isKindOfClass:[TSAttachmentStream class]]) {
OWSAssertDebug(attachments.count > 0); TSAttachmentStream *oversizeTextAttachmentStream = (TSAttachmentStream *)oversizeTextAttachment;
NSArray<ConversationMediaAlbumItem *> *mediaAlbumItems = [self mediaAlbumItemsForAttachments:attachments]; self.displayableBodyText = [self displayableBodyTextForOversizeTextAttachment:oversizeTextAttachmentStream
interactionId:message.uniqueId];
} else {
NSString *_Nullable bodyText = [message bodyTextWithTransaction:transaction];
if (bodyText) {
self.displayableBodyText = [self displayableBodyTextForText:bodyText interactionId:message.uniqueId];
}
}
NSArray<TSAttachment *> *mediaAttachments = [message mediaAttachmentsWithTransaction:transaction];
NSArray<ConversationMediaAlbumItem *> *mediaAlbumItems = [self mediaAlbumItemsForAttachments:mediaAttachments];
if (mediaAlbumItems.count > 0) {
if (mediaAlbumItems.count == 1) { if (mediaAlbumItems.count == 1) {
ConversationMediaAlbumItem *mediaAlbumItem = mediaAlbumItems.firstObject; ConversationMediaAlbumItem *mediaAlbumItem = mediaAlbumItems.firstObject;
if (mediaAlbumItem.attachmentStream && !mediaAlbumItem.attachmentStream.isValidVisualMedia) { if (mediaAlbumItem.attachmentStream && !mediaAlbumItem.attachmentStream.isValidVisualMedia) {
@ -607,31 +615,18 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
} }
self.mediaAlbumItems = mediaAlbumItems; self.mediaAlbumItems = mediaAlbumItems;
self.messageCellType = OWSMessageCellType_MediaAlbum; self.messageCellType = OWSMessageCellType_MediaMessage;
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];
}
return; return;
} }
// Only media galleries should have more than one attachment.
OWSAssertDebug(attachments.count <= 1);
TSAttachment *_Nullable attachment = attachments.firstObject; // Only media galleries should have more than one attachment.
if (attachment) { OWSAssertDebug(mediaAttachments.count <= 1);
if ([attachment isKindOfClass:[TSAttachmentStream class]]) {
self.attachmentStream = (TSAttachmentStream *)attachment;
if ([attachment.contentType isEqualToString:OWSMimeTypeOversizeTextMessage]) { TSAttachment *_Nullable mediaAttachment = mediaAttachments.firstObject;
self.messageCellType = OWSMessageCellType_OversizeTextMessage; if (mediaAttachment) {
self.displayableBodyText = [self displayableBodyTextForOversizeTextAttachment:self.attachmentStream if ([mediaAttachment isKindOfClass:[TSAttachmentStream class]]) {
interactionId:message.uniqueId]; self.attachmentStream = (TSAttachmentStream *)mediaAttachment;
} else if ([self.attachmentStream isAudio]) { if ([self.attachmentStream isAudio]) {
CGFloat audioDurationSeconds = [self.attachmentStream audioDurationSeconds]; CGFloat audioDurationSeconds = [self.attachmentStream audioDurationSeconds];
if (audioDurationSeconds > 0) { if (audioDurationSeconds > 0) {
self.audioDurationSeconds = audioDurationSeconds; self.audioDurationSeconds = audioDurationSeconds;
@ -639,34 +634,28 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
} else { } else {
self.messageCellType = OWSMessageCellType_GenericAttachment; self.messageCellType = OWSMessageCellType_GenericAttachment;
} }
} else { } else if (self.messageCellType == OWSMessageCellType_Unknown) {
self.messageCellType = OWSMessageCellType_GenericAttachment; self.messageCellType = OWSMessageCellType_GenericAttachment;
} }
} else if ([attachment isKindOfClass:[TSAttachmentPointer class]]) { } else if ([mediaAttachment isKindOfClass:[TSAttachmentPointer class]]) {
self.messageCellType = OWSMessageCellType_DownloadingAttachment; self.messageCellType = OWSMessageCellType_DownloadingAttachment;
self.attachmentPointer = (TSAttachmentPointer *)attachment; self.attachmentPointer = (TSAttachmentPointer *)mediaAttachment;
} else { } else {
OWSFailDebug(@"Unknown attachment type"); OWSFailDebug(@"Unknown attachment type");
} }
} }
// Ignore message body for oversize text attachments.
if (message.body.length > 0) {
if (self.hasBodyText) { if (self.hasBodyText) {
OWSFailDebug(@"oversize text message has unexpected caption.");
}
// If we haven't already assigned an attachment type at this point, message.body isn't a caption,
// it's a stand-alone text message.
if (self.messageCellType == OWSMessageCellType_Unknown) { if (self.messageCellType == OWSMessageCellType_Unknown) {
OWSAssertDebug(message.attachmentIds.count == 0); OWSAssertDebug(message.attachmentIds.count == 0
self.messageCellType = OWSMessageCellType_TextMessage; || (message.attachmentIds.count == 1 &&
[message oversizeTextAttachmentWithTransaction:transaction] != nil));
self.messageCellType = OWSMessageCellType_TextOnlyMessage;
} }
self.displayableBodyText = [self displayableBodyTextForText:message.body interactionId:message.uniqueId];
OWSAssertDebug(self.displayableBodyText); OWSAssertDebug(self.displayableBodyText);
} }
if (self.hasBodyText && attachment == nil && message.linkPreview) { if (self.hasBodyText && message.linkPreview) {
self.linkPreview = message.linkPreview; self.linkPreview = message.linkPreview;
if (message.linkPreview.imageAttachmentId.length > 0) { if (message.linkPreview.imageAttachmentId.length > 0) {
TSAttachment *_Nullable linkPreviewAttachment = TSAttachment *_Nullable linkPreviewAttachment =
@ -692,7 +681,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
// Messages of unknown type (including messages with missing attachments) // Messages of unknown type (including messages with missing attachments)
// are rendered like empty text messages, but without any interactivity. // are rendered like empty text messages, but without any interactivity.
OWSLogWarn(@"Treating unknown message as empty text message: %@ %llu", message.class, message.timestamp); OWSLogWarn(@"Treating unknown message as empty text message: %@ %llu", message.class, message.timestamp);
self.messageCellType = OWSMessageCellType_TextMessage; self.messageCellType = OWSMessageCellType_TextOnlyMessage;
self.displayableBodyText = [[DisplayableText alloc] initWithFullText:@"" displayText:@"" isTextTruncated:NO]; self.displayableBodyText = [[DisplayableText alloc] initWithFullText:@"" displayText:@"" isTextTruncated:NO];
} }
} }
@ -700,7 +689,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
- (NSArray<ConversationMediaAlbumItem *> *)mediaAlbumItemsForAttachments:(NSArray<TSAttachment *> *)attachments - (NSArray<ConversationMediaAlbumItem *> *)mediaAlbumItemsForAttachments:(NSArray<TSAttachment *> *)attachments
{ {
OWSAssertIsOnMainThread(); OWSAssertIsOnMainThread();
OWSAssertDebug(attachments.count > 0);
NSMutableArray<ConversationMediaAlbumItem *> *mediaAlbumItems = [NSMutableArray new]; NSMutableArray<ConversationMediaAlbumItem *> *mediaAlbumItems = [NSMutableArray new];
for (TSAttachment *attachment in attachments) { for (TSAttachment *attachment in attachments) {
@ -855,10 +843,9 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
- (void)copyTextAction - (void)copyTextAction
{ {
switch (self.messageCellType) { switch (self.messageCellType) {
case OWSMessageCellType_TextMessage: case OWSMessageCellType_TextOnlyMessage:
case OWSMessageCellType_OversizeTextMessage:
case OWSMessageCellType_Audio: case OWSMessageCellType_Audio:
case OWSMessageCellType_MediaAlbum: case OWSMessageCellType_MediaMessage:
case OWSMessageCellType_GenericAttachment: { case OWSMessageCellType_GenericAttachment: {
OWSAssertDebug(self.displayableBodyText); OWSAssertDebug(self.displayableBodyText);
[UIPasteboard.generalPasteboard setString:self.displayableBodyText.fullText]; [UIPasteboard.generalPasteboard setString:self.displayableBodyText.fullText];
@ -884,8 +871,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
{ {
switch (self.messageCellType) { switch (self.messageCellType) {
case OWSMessageCellType_Unknown: case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextMessage: case OWSMessageCellType_TextOnlyMessage:
case OWSMessageCellType_OversizeTextMessage:
case OWSMessageCellType_ContactShare: { case OWSMessageCellType_ContactShare: {
OWSFailDebug(@"No media to copy"); OWSFailDebug(@"No media to copy");
break; break;
@ -899,7 +885,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
OWSFailDebug(@"Can't copy not-yet-downloaded attachment"); OWSFailDebug(@"Can't copy not-yet-downloaded attachment");
break; break;
} }
case OWSMessageCellType_MediaAlbum: { case OWSMessageCellType_MediaMessage: {
if (self.mediaAlbumItems.count == 1) { if (self.mediaAlbumItems.count == 1) {
ConversationMediaAlbumItem *mediaAlbumItem = self.mediaAlbumItems.firstObject; ConversationMediaAlbumItem *mediaAlbumItem = self.mediaAlbumItems.firstObject;
if (mediaAlbumItem.attachmentStream && mediaAlbumItem.attachmentStream.isValidVisualMedia) { if (mediaAlbumItem.attachmentStream && mediaAlbumItem.attachmentStream.isValidVisualMedia) {
@ -935,8 +921,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
{ {
switch (self.messageCellType) { switch (self.messageCellType) {
case OWSMessageCellType_Unknown: case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextMessage: case OWSMessageCellType_TextOnlyMessage:
case OWSMessageCellType_OversizeTextMessage:
case OWSMessageCellType_ContactShare: case OWSMessageCellType_ContactShare:
OWSFailDebug(@"No media to share."); OWSFailDebug(@"No media to share.");
break; break;
@ -948,7 +933,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
OWSFailDebug(@"Can't share not-yet-downloaded attachment"); OWSFailDebug(@"Can't share not-yet-downloaded attachment");
break; break;
} }
case OWSMessageCellType_MediaAlbum: { case OWSMessageCellType_MediaMessage: {
// TODO: We need a "canShareMediaAction" method. // TODO: We need a "canShareMediaAction" method.
OWSAssertDebug(self.mediaAlbumItems); OWSAssertDebug(self.mediaAlbumItems);
NSMutableArray<TSAttachmentStream *> *attachmentStreams = [NSMutableArray new]; NSMutableArray<TSAttachmentStream *> *attachmentStreams = [NSMutableArray new];
@ -971,15 +956,14 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
{ {
switch (self.messageCellType) { switch (self.messageCellType) {
case OWSMessageCellType_Unknown: case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextMessage: case OWSMessageCellType_TextOnlyMessage:
case OWSMessageCellType_OversizeTextMessage:
case OWSMessageCellType_ContactShare: case OWSMessageCellType_ContactShare:
return NO; return NO;
case OWSMessageCellType_Audio: case OWSMessageCellType_Audio:
return NO; return NO;
case OWSMessageCellType_GenericAttachment: case OWSMessageCellType_GenericAttachment:
case OWSMessageCellType_DownloadingAttachment: case OWSMessageCellType_DownloadingAttachment:
case OWSMessageCellType_MediaAlbum: { case OWSMessageCellType_MediaMessage: {
if (self.mediaAlbumItems.count == 1) { if (self.mediaAlbumItems.count == 1) {
ConversationMediaAlbumItem *mediaAlbumItem = self.mediaAlbumItems.firstObject; ConversationMediaAlbumItem *mediaAlbumItem = self.mediaAlbumItems.firstObject;
if (mediaAlbumItem.attachmentStream && mediaAlbumItem.attachmentStream.isValidVisualMedia) { if (mediaAlbumItem.attachmentStream && mediaAlbumItem.attachmentStream.isValidVisualMedia) {
@ -995,8 +979,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
{ {
switch (self.messageCellType) { switch (self.messageCellType) {
case OWSMessageCellType_Unknown: case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextMessage: case OWSMessageCellType_TextOnlyMessage:
case OWSMessageCellType_OversizeTextMessage:
case OWSMessageCellType_ContactShare: case OWSMessageCellType_ContactShare:
return NO; return NO;
case OWSMessageCellType_Audio: case OWSMessageCellType_Audio:
@ -1004,7 +987,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
case OWSMessageCellType_GenericAttachment: case OWSMessageCellType_GenericAttachment:
case OWSMessageCellType_DownloadingAttachment: case OWSMessageCellType_DownloadingAttachment:
return NO; return NO;
case OWSMessageCellType_MediaAlbum: { case OWSMessageCellType_MediaMessage: {
for (ConversationMediaAlbumItem *mediaAlbumItem in self.mediaAlbumItems) { for (ConversationMediaAlbumItem *mediaAlbumItem in self.mediaAlbumItems) {
if (!mediaAlbumItem.attachmentStream) { if (!mediaAlbumItem.attachmentStream) {
continue; continue;
@ -1031,8 +1014,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
{ {
switch (self.messageCellType) { switch (self.messageCellType) {
case OWSMessageCellType_Unknown: case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextMessage: case OWSMessageCellType_TextOnlyMessage:
case OWSMessageCellType_OversizeTextMessage:
case OWSMessageCellType_ContactShare: case OWSMessageCellType_ContactShare:
OWSFailDebug(@"Cannot save text data."); OWSFailDebug(@"Cannot save text data.");
break; break;
@ -1046,7 +1028,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
OWSFailDebug(@"Can't save not-yet-downloaded attachment"); OWSFailDebug(@"Can't save not-yet-downloaded attachment");
break; break;
} }
case OWSMessageCellType_MediaAlbum: { case OWSMessageCellType_MediaMessage: {
[self saveMediaAlbumItems]; [self saveMediaAlbumItems];
break; break;
} }
@ -1114,8 +1096,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
{ {
switch (self.messageCellType) { switch (self.messageCellType) {
case OWSMessageCellType_Unknown: case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextMessage: case OWSMessageCellType_TextOnlyMessage:
case OWSMessageCellType_OversizeTextMessage:
case OWSMessageCellType_ContactShare: case OWSMessageCellType_ContactShare:
return NO; return NO;
case OWSMessageCellType_Audio: case OWSMessageCellType_Audio:
@ -1124,14 +1105,14 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
case OWSMessageCellType_DownloadingAttachment: { case OWSMessageCellType_DownloadingAttachment: {
return NO; return NO;
} }
case OWSMessageCellType_MediaAlbum: case OWSMessageCellType_MediaMessage:
return self.firstValidAlbumAttachment != nil; return self.firstValidAlbumAttachment != nil;
} }
} }
- (BOOL)mediaAlbumHasFailedAttachment - (BOOL)mediaAlbumHasFailedAttachment
{ {
OWSAssertDebug(self.messageCellType == OWSMessageCellType_MediaAlbum); OWSAssertDebug(self.messageCellType == OWSMessageCellType_MediaMessage);
OWSAssertDebug(self.mediaAlbumItems.count > 0); OWSAssertDebug(self.mediaAlbumItems.count > 0);
for (ConversationMediaAlbumItem *mediaAlbumItem in self.mediaAlbumItems) { for (ConversationMediaAlbumItem *mediaAlbumItem in self.mediaAlbumItems) {

@ -390,7 +390,7 @@ NS_ASSUME_NONNULL_BEGIN
}]; }];
} }
+ (void)sendAttachment:(NSString *)filePath + (void)sendAttachmentWithFilePath:(NSString *)filePath
thread:(TSThread *)thread thread:(TSThread *)thread
label:(NSString *)label label:(NSString *)label
hasCaption:(BOOL)hasCaption hasCaption:(BOOL)hasCaption
@ -425,7 +425,9 @@ NS_ASSUME_NONNULL_BEGIN
[DDLog flushLog]; [DDLog flushLog];
} }
OWSAssertDebug(![attachment hasError]); OWSAssertDebug(![attachment hasError]);
[ThreadUtil enqueueMessageWithAttachment:attachment inThread:thread quotedReplyModel:nil];
[self sendAttachment:attachment thread:thread messageBody:messageBody];
success(); success();
} }
@ -551,7 +553,7 @@ NS_ASSUME_NONNULL_BEGIN
ActionFailureBlock failure) { ActionFailureBlock failure) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
OWSAssertDebug(fakeAssetLoader.filePath.length > 0); OWSAssertDebug(fakeAssetLoader.filePath.length > 0);
[self sendAttachment:fakeAssetLoader.filePath [self sendAttachmentWithFilePath:fakeAssetLoader.filePath
thread:thread thread:thread
label:label label:label
hasCaption:hasCaption hasCaption:hasCaption
@ -1732,19 +1734,25 @@ NS_ASSUME_NONNULL_BEGIN
return attachment; return attachment;
} }
+ (void)sendAttachment:(NSString *)filePath + (void)sendAttachment:(nullable SignalAttachment *)attachment
thread:(TSThread *)thread thread:(TSThread *)thread
success:(nullable void (^)(void))success messageBody:(nullable NSString *)messageBody
failure:(nullable void (^)(void))failure
{ {
OWSAssertDebug(filePath); [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
OWSAssertDebug(thread); NSArray<SignalAttachment *> *attachments = @[];
if (attachment != nil) {
SignalAttachment *attachment = [self signalAttachmentForFilePath:filePath]; attachments = @[ attachment ];
[ThreadUtil enqueueMessageWithAttachment:attachment inThread:thread quotedReplyModel:nil]; }
success(); [ThreadUtil enqueueMessageWithText:messageBody
mediaAttachments:attachments
inThread:thread
quotedReplyModel:nil
linkPreviewDraft:nil
transaction:transaction];
}];
} }
+ (DebugUIMessagesAction *)fakeIncomingTextMessageAction:(TSThread *)thread text:(NSString *)text + (DebugUIMessagesAction *)fakeIncomingTextMessageAction:(TSThread *)thread text:(NSString *)text
{ {
OWSAssertDebug(thread); OWSAssertDebug(thread);
@ -3342,11 +3350,7 @@ typedef OWSContact * (^OWSContactBlock)(YapDatabaseReadWriteTransaction *transac
+ (void)sendOversizeTextMessage:(TSThread *)thread + (void)sendOversizeTextMessage:(TSThread *)thread
{ {
NSString *message = [self randomOversizeText]; [self sendAttachment:nil thread:thread messageBody:[self randomOversizeText]];
DataSource *_Nullable dataSource = [DataSourceValue dataSourceWithOversizeText:message];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:kOversizeTextAttachmentUTI];
[ThreadUtil enqueueMessageWithAttachment:attachment inThread:thread quotedReplyModel:nil];
} }
+ (NSData *)createRandomNSDataOfSize:(size_t)size + (NSData *)createRandomNSDataOfSize:(size_t)size
@ -3379,7 +3383,7 @@ typedef OWSContact * (^OWSContactBlock)(YapDatabaseReadWriteTransaction *transac
// style them indistinguishably from a separate text message. // style them indistinguishably from a separate text message.
attachment.captionText = [self randomCaptionText]; attachment.captionText = [self randomCaptionText];
} }
[ThreadUtil enqueueMessageWithAttachment:attachment inThread:thread quotedReplyModel:nil]; [self sendAttachment:attachment thread:thread messageBody:nil];
} }
+ (SSKProtoEnvelope *)createEnvelopeForThread:(TSThread *)thread + (SSKProtoEnvelope *)createEnvelopeForThread:(TSThread *)thread
@ -4445,7 +4449,7 @@ typedef OWSContact * (^OWSContactBlock)(YapDatabaseReadWriteTransaction *transac
[DDLog flushLog]; [DDLog flushLog];
} }
OWSAssertDebug(![attachment hasError]); OWSAssertDebug(![attachment hasError]);
[ThreadUtil enqueueMessageWithAttachment:attachment inThread:thread quotedReplyModel:nil]; [self sendAttachment:attachment thread:thread messageBody:nil];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
sendUnsafeFile(); sendUnsafeFile();
@ -4759,12 +4763,14 @@ typedef OWSContact * (^OWSContactBlock)(YapDatabaseReadWriteTransaction *transac
[attachments addObject:attachment]; [attachments addObject:attachment];
} }
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
TSOutgoingMessage *message = [ThreadUtil enqueueMessageWithAttachments:attachments TSOutgoingMessage *message = [ThreadUtil enqueueMessageWithText:messageBody
messageBody:messageBody mediaAttachments:attachments
inThread:thread inThread:thread
quotedReplyModel:nil]; quotedReplyModel:nil
OWSLogError(@"timestamp: %llu.", message.timestamp); linkPreviewDraft:nil
transaction:transaction];
OWSLogDebug(@"timestamp: %llu.", message.timestamp);
}]; }];
} }

@ -33,6 +33,13 @@ NS_ASSUME_NONNULL_BEGIN
@implementation DebugUIMisc @implementation DebugUIMisc
#pragma mark - Dependencies
+ (YapDatabaseConnection *)dbConnection
{
return [OWSPrimaryStorage.sharedManager dbReadWriteConnection];
}
#pragma mark - Factory Methods #pragma mark - Factory Methods
- (NSString *)name - (NSString *)name
@ -252,11 +259,23 @@ NS_ASSUME_NONNULL_BEGIN
SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:utiType]; SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:utiType];
NSData *databasePassword = [OWSPrimaryStorage.sharedManager databasePassword]; NSData *databasePassword = [OWSPrimaryStorage.sharedManager databasePassword];
attachment.captionText = [databasePassword hexadecimalString]; attachment.captionText = [databasePassword hexadecimalString];
[self sendAttachment:attachment thread:thread];
}
+ (void)sendAttachment:(SignalAttachment *)attachment thread:(TSThread *)thread
{
if (!attachment || [attachment hasError]) { if (!attachment || [attachment hasError]) {
OWSFailDebug(@"attachment[%@]: %@", [attachment sourceFilename], [attachment errorName]); OWSFailDebug(@"attachment[%@]: %@", [attachment sourceFilename], [attachment errorName]);
return; return;
} }
[ThreadUtil enqueueMessageWithAttachment:attachment inThread:thread quotedReplyModel:nil]; [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
[ThreadUtil enqueueMessageWithText:nil
mediaAttachments:@[ attachment ]
inThread:thread
quotedReplyModel:nil
linkPreviewDraft:nil
transaction:transaction];
}];
} }
+ (void)sendUnencryptedDatabase:(TSThread *)thread + (void)sendUnencryptedDatabase:(TSThread *)thread
@ -274,11 +293,7 @@ NS_ASSUME_NONNULL_BEGIN
DataSource *_Nullable dataSource = [DataSourcePath dataSourceWithFilePath:filePath shouldDeleteOnDeallocation:YES]; DataSource *_Nullable dataSource = [DataSourcePath dataSourceWithFilePath:filePath shouldDeleteOnDeallocation:YES];
[dataSource setSourceFilename:fileName]; [dataSource setSourceFilename:fileName];
SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:utiType]; SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:utiType];
if (!attachment || [attachment hasError]) { [self sendAttachment:attachment thread:thread];
OWSFailDebug(@"attachment[%@]: %@", [attachment sourceFilename], [attachment errorName]);
return;
}
[ThreadUtil enqueueMessageWithAttachment:attachment inThread:thread quotedReplyModel:nil];
} }
#ifdef DEBUG #ifdef DEBUG

@ -1861,11 +1861,12 @@ class MediaMessageTextToolbar: UIView, UITextViewDelegate {
public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if !FeatureFlags.sendingMediaWithOversizeText {
let existingText: String = textView.text ?? "" let existingText: String = textView.text ?? ""
let proposedText: String = (existingText as NSString).replacingCharacters(in: range, with: text) let proposedText: String = (existingText as NSString).replacingCharacters(in: range, with: text)
// Don't complicate things by mixing media attachments with oversize text attachments // Don't complicate things by mixing media attachments with oversize text attachments
guard proposedText.utf8.count <= kOversizeTextMessageSizeThreshold else { guard proposedText.utf8.count < kOversizeTextMessageSizeThreshold else {
Logger.debug("long text was truncated") Logger.debug("long text was truncated")
self.lengthLimitLabel.isHidden = false self.lengthLimitLabel.isHidden = false
@ -1886,7 +1887,7 @@ class MediaMessageTextToolbar: UIView, UITextViewDelegate {
self.lengthLimitLabel.isHidden = true self.lengthLimitLabel.isHidden = true
// After verifying the byte-length is sufficiently small, verify the character count is within bounds. // After verifying the byte-length is sufficiently small, verify the character count is within bounds.
guard proposedText.count <= kMaxMessageBodyCharacterCount else { guard proposedText.count < kMaxMessageBodyCharacterCount else {
Logger.debug("hit attachment message body character count limit") Logger.debug("hit attachment message body character count limit")
self.lengthLimitLabel.isHidden = false self.lengthLimitLabel.isHidden = false
@ -1903,6 +1904,7 @@ class MediaMessageTextToolbar: UIView, UITextViewDelegate {
return false return false
} }
}
// Though we can wrap the text, we don't want to encourage multline captions, plus a "done" button // Though we can wrap the text, we don't want to encourage multline captions, plus a "done" button
// allows the user to get the keyboard out of the way while in the attachment approval view. // allows the user to get the keyboard out of the way while in the attachment approval view.

@ -1,5 +1,5 @@
// //
// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// //
#import "SharingThreadPickerViewController.h" #import "SharingThreadPickerViewController.h"

@ -44,20 +44,18 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Durable Message Enqueue #pragma mark - Durable Message Enqueue
+ (TSOutgoingMessage *)enqueueMessageWithText:(NSString *)text + (TSOutgoingMessage *)enqueueMessageWithText:(NSString *)fullMessageText
inThread:(TSThread *)thread inThread:(TSThread *)thread
quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel
linkPreviewDraft:(nullable nullable OWSLinkPreviewDraft *)linkPreviewDraft linkPreviewDraft:(nullable nullable OWSLinkPreviewDraft *)linkPreviewDraft
transaction:(YapDatabaseReadTransaction *)transaction; transaction:(YapDatabaseReadTransaction *)transaction;
+ (TSOutgoingMessage *)enqueueMessageWithAttachment:(SignalAttachment *)attachment + (TSOutgoingMessage *)enqueueMessageWithText:(nullable NSString *)fullMessageText
mediaAttachments:(NSArray<SignalAttachment *> *)attachments
inThread:(TSThread *)thread inThread:(TSThread *)thread
quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel; quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel
linkPreviewDraft:(nullable nullable OWSLinkPreviewDraft *)linkPreviewDraft
+ (TSOutgoingMessage *)enqueueMessageWithAttachments:(NSArray<SignalAttachment *> *)attachments transaction:(YapDatabaseReadTransaction *)transaction;
messageBody:(nullable NSString *)messageBody
inThread:(TSThread *)thread
quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel;
+ (TSOutgoingMessage *)enqueueMessageWithContactShare:(OWSContact *)contactShare inThread:(TSThread *)thread; + (TSOutgoingMessage *)enqueueMessageWithContactShare:(OWSContact *)contactShare inThread:(TSThread *)thread;
+ (void)enqueueLeaveGroupMessageInThread:(TSGroupThread *)thread; + (void)enqueueLeaveGroupMessageInThread:(TSGroupThread *)thread;

@ -9,6 +9,7 @@
#import "OWSUnreadIndicator.h" #import "OWSUnreadIndicator.h"
#import "TSUnreadIndicatorInteraction.h" #import "TSUnreadIndicatorInteraction.h"
#import <SignalCoreKit/NSDate+OWS.h> #import <SignalCoreKit/NSDate+OWS.h>
#import <SignalCoreKit/SignalCoreKit-Swift.h>
#import <SignalMessaging/OWSProfileManager.h> #import <SignalMessaging/OWSProfileManager.h>
#import <SignalMessaging/SignalMessaging-Swift.h> #import <SignalMessaging/SignalMessaging-Swift.h>
#import <SignalServiceKit/OWSAddToContactsOfferMessage.h> #import <SignalServiceKit/OWSAddToContactsOfferMessage.h>
@ -65,42 +66,18 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Durable Message Enqueue #pragma mark - Durable Message Enqueue
+ (TSOutgoingMessage *)enqueueMessageWithText:(NSString *)text + (TSOutgoingMessage *)enqueueMessageWithText:(NSString *)fullMessageText
inThread:(TSThread *)thread inThread:(TSThread *)thread
quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel
linkPreviewDraft:(nullable nullable OWSLinkPreviewDraft *)linkPreviewDraft linkPreviewDraft:(nullable nullable OWSLinkPreviewDraft *)linkPreviewDraft
transaction:(YapDatabaseReadTransaction *)transaction transaction:(YapDatabaseReadTransaction *)transaction
{ {
OWSDisappearingMessagesConfiguration *configuration = return [self enqueueMessageWithText:fullMessageText
[OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:thread.uniqueId transaction:transaction]; mediaAttachments:@[]
inThread:thread
uint32_t expiresInSeconds = (configuration.isEnabled ? configuration.durationSeconds : 0); quotedReplyModel:quotedReplyModel
linkPreviewDraft:linkPreviewDraft
TSOutgoingMessage *message = transaction:transaction];
[TSOutgoingMessage outgoingMessageInThread:thread
messageBody:text
attachmentId:nil
expiresInSeconds:expiresInSeconds
quotedMessage:[quotedReplyModel buildQuotedMessageForSending]
linkPreview:nil];
[BenchManager benchAsyncWithTitle:@"Saving outgoing message" block:^(void (^benchmarkCompletion)(void)) {
// To avoid blocking the send flow, we dispatch an async write from within this read transaction
[self.dbConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull writeTransaction) {
[message saveWithTransaction:writeTransaction];
OWSLinkPreview *_Nullable linkPreview =
[self linkPreviewForLinkPreviewDraft:linkPreviewDraft transaction:writeTransaction];
if (linkPreview) {
[message updateWithLinkPreview:linkPreview transaction:writeTransaction];
}
[self.messageSenderJobQueue addMessage:message transaction:writeTransaction];
}
completionBlock:benchmarkCompletion];
}];
return message;
} }
+ (nullable OWSLinkPreview *)linkPreviewForLinkPreviewDraft:(nullable OWSLinkPreviewDraft *)linkPreviewDraft + (nullable OWSLinkPreview *)linkPreviewForLinkPreviewDraft:(nullable OWSLinkPreviewDraft *)linkPreviewDraft
@ -121,40 +98,56 @@ NS_ASSUME_NONNULL_BEGIN
return linkPreview; return linkPreview;
} }
+ (TSOutgoingMessage *)enqueueMessageWithAttachment:(SignalAttachment *)attachment + (TSOutgoingMessage *)enqueueMessageWithText:(nullable NSString *)fullMessageText
inThread:(TSThread *)thread mediaAttachments:(NSArray<SignalAttachment *> *)attachmentsParam
quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel
{
return [self enqueueMessageWithAttachments:@[
attachment,
]
messageBody:attachment.captionText
inThread:thread
quotedReplyModel:quotedReplyModel];
}
+ (TSOutgoingMessage *)enqueueMessageWithAttachments:(NSArray<SignalAttachment *> *)attachments
messageBody:(nullable NSString *)messageBody
inThread:(TSThread *)thread inThread:(TSThread *)thread
quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel
linkPreviewDraft:(nullable nullable OWSLinkPreviewDraft *)linkPreviewDraft
transaction:(YapDatabaseReadTransaction *)transaction
{ {
OWSAssertIsOnMainThread(); OWSAssertIsOnMainThread();
OWSAssertDebug(attachments.count > 0);
OWSAssertDebug(thread); OWSAssertDebug(thread);
for (SignalAttachment *attachment in attachments) {
OWSAssertDebug(!attachment.hasError); NSString *_Nullable truncatedText;
OWSAssertDebug(attachment.mimeType.length > 0); NSArray<SignalAttachment *> *attachments = attachmentsParam;
if ([fullMessageText lengthOfBytesUsingEncoding:NSUTF8StringEncoding] <= kOversizeTextMessageSizeThreshold) {
truncatedText = fullMessageText;
} else {
if (SSKFeatureFlags.sendingMediaWithOversizeText) {
truncatedText = [fullMessageText ows_truncatedToByteCount:kOversizeTextMessageSizeThreshold];
} else {
// Legacy iOS clients already support receiving long text, but they assume _any_ body
// text is the _full_ body text. So until we consider "rollout" complete, we maintain
// the legacy sending behavior, which does not include the truncated text in the
// websocket proto.
truncatedText = nil;
}
DataSource *_Nullable dataSource = [DataSourceValue dataSourceWithOversizeText:fullMessageText];
if (dataSource) {
SignalAttachment *oversizeTextAttachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:kOversizeTextAttachmentUTI];
attachments = [attachmentsParam arrayByAddingObject:oversizeTextAttachment];
} else {
OWSFailDebug(@"dataSource was unexpectedly nil");
}
} }
OWSDisappearingMessagesConfiguration *configuration = OWSDisappearingMessagesConfiguration *configuration =
[OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:thread.uniqueId]; [OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:thread.uniqueId transaction:transaction];
uint32_t expiresInSeconds = (configuration.isEnabled ? configuration.durationSeconds : 0); uint32_t expiresInSeconds = (configuration.isEnabled ? configuration.durationSeconds : 0);
for (SignalAttachment *attachment in attachments) {
OWSAssertDebug(!attachment.hasError);
OWSAssertDebug(attachment.mimeType.length > 0);
}
BOOL isVoiceMessage = (attachments.count == 1 && attachments.lastObject.isVoiceMessage); BOOL isVoiceMessage = (attachments.count == 1 && attachments.lastObject.isVoiceMessage);
TSOutgoingMessage *message = TSOutgoingMessage *message =
[[TSOutgoingMessage alloc] initOutgoingMessageWithTimestamp:[NSDate ows_millisecondTimeStamp] [[TSOutgoingMessage alloc] initOutgoingMessageWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread inThread:thread
messageBody:messageBody messageBody:truncatedText
attachmentIds:[NSMutableArray new] attachmentIds:[NSMutableArray new]
expiresInSeconds:expiresInSeconds expiresInSeconds:expiresInSeconds
expireStartedAt:0 expireStartedAt:0
@ -164,12 +157,40 @@ NS_ASSUME_NONNULL_BEGIN
contactShare:nil contactShare:nil
linkPreview:nil]; linkPreview:nil];
NSMutableArray<OWSOutgoingAttachmentInfo *> *attachmentInfos = [NSMutableArray new]; [BenchManager
benchAsyncWithTitle:@"Saving outgoing message"
block:^(void (^benchmarkCompletion)(void)) {
// To avoid blocking the send flow, we dispatch an async write from within this read
// transaction
[self.dbConnection
asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull writeTransaction) {
[message saveWithTransaction:writeTransaction];
OWSLinkPreview *_Nullable linkPreview =
[self linkPreviewForLinkPreviewDraft:linkPreviewDraft
transaction:writeTransaction];
if (linkPreview) {
[message updateWithLinkPreview:linkPreview transaction:writeTransaction];
}
if (attachments.count == 0) {
[self.messageSenderJobQueue addMessage:message transaction:writeTransaction];
} else {
NSMutableArray<OWSOutgoingAttachmentInfo *> *attachmentInfos =
[NSMutableArray new];
for (SignalAttachment *attachment in attachments) { for (SignalAttachment *attachment in attachments) {
OWSOutgoingAttachmentInfo *attachmentInfo = [attachment buildOutgoingAttachmentInfoWithMessage:message]; OWSOutgoingAttachmentInfo *attachmentInfo =
[attachment buildOutgoingAttachmentInfoWithMessage:message];
[attachmentInfos addObject:attachmentInfo]; [attachmentInfos addObject:attachmentInfo];
} }
[self.messageSenderJobQueue addMediaMessage:message attachmentInfos:attachmentInfos isTemporaryAttachment:NO];
[self.messageSenderJobQueue addMediaMessage:message
attachmentInfos:attachmentInfos
isTemporaryAttachment:NO];
}
}
completionBlock:benchmarkCompletion];
}];
return message; return message;
} }

@ -1,5 +1,5 @@
// //
// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// //
#import "TSYapDatabaseObject.h" #import "TSYapDatabaseObject.h"
@ -93,6 +93,7 @@ typedef NS_ENUM(NSUInteger, TSAttachmentType) {
@property (nonatomic, readonly) BOOL isAudio; @property (nonatomic, readonly) BOOL isAudio;
@property (nonatomic, readonly) BOOL isVoiceMessage; @property (nonatomic, readonly) BOOL isVoiceMessage;
@property (nonatomic, readonly) BOOL isVisualMedia; @property (nonatomic, readonly) BOOL isVisualMedia;
@property (nonatomic, readonly) BOOL isOversizeText;
+ (NSString *)emojiForMimeType:(NSString *)contentType; + (NSString *)emojiForMimeType:(NSString *)contentType;

@ -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"
@ -273,6 +273,11 @@ NSUInteger const TSAttachmentSchemaVersion = 4;
return [MIMETypeUtil isVisualMedia:self.contentType]; return [MIMETypeUtil isVisualMedia:self.contentType];
} }
- (BOOL)isOversizeText
{
return [self.contentType isEqualToString:OWSMimeTypeOversizeTextMessage];
}
- (nullable NSString *)sourceFilename - (nullable NSString *)sourceFilename
{ {
return _sourceFilename.filterFilename; return _sourceFilename.filterFilename;

@ -45,6 +45,9 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)hasAttachments; - (BOOL)hasAttachments;
- (NSArray<TSAttachment *> *)attachmentsWithTransaction:(YapDatabaseReadTransaction *)transaction; - (NSArray<TSAttachment *> *)attachmentsWithTransaction:(YapDatabaseReadTransaction *)transaction;
- (NSArray<TSAttachment *> *)mediaAttachmentsWithTransaction:(YapDatabaseReadTransaction *)transaction;
- (nullable TSAttachment *)oversizeTextAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction;
- (void)removeAttachment:(TSAttachment *)attachment - (void)removeAttachment:(TSAttachment *)attachment
transaction:(YapDatabaseReadWriteTransaction *)transaction NS_SWIFT_NAME(removeAttachment(_:transaction:)); transaction:(YapDatabaseReadWriteTransaction *)transaction NS_SWIFT_NAME(removeAttachment(_:transaction:));
@ -52,8 +55,6 @@ NS_ASSUME_NONNULL_BEGIN
// quoted reply thumbnails, contact share avatars, link preview images, etc. // quoted reply thumbnails, contact share avatars, link preview images, etc.
- (NSArray<NSString *> *)allAttachmentIds; - (NSArray<NSString *> *)allAttachmentIds;
- (BOOL)isMediaAlbumWithTransaction:(YapDatabaseReadTransaction *)transaction;
- (void)setQuotedMessageThumbnailAttachmentStream:(TSAttachmentStream *)attachmentStream; - (void)setQuotedMessageThumbnailAttachmentStream:(TSAttachmentStream *)attachmentStream;
- (nullable NSString *)oversizeTextWithTransaction:(YapDatabaseReadTransaction *)transaction; - (nullable NSString *)oversizeTextWithTransaction:(YapDatabaseReadTransaction *)transaction;

@ -219,6 +219,26 @@ static const NSUInteger OWSMessageSchemaVersion = 4;
return [attachments copy]; return [attachments copy];
} }
- (NSArray<TSAttachment *> *)attachmentsWithTransaction:(YapDatabaseReadTransaction *)transaction
contentType:(NSString *)contentType
{
NSArray<TSAttachment *> *attachments = [self attachmentsWithTransaction:transaction];
return [attachments filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(TSAttachment *evaluatedObject,
NSDictionary<NSString *, id> *_Nullable bindings) {
return [evaluatedObject.contentType isEqualToString:contentType];
}]];
}
- (NSArray<TSAttachment *> *)attachmentsWithTransaction:(YapDatabaseReadTransaction *)transaction
exceptContentType:(NSString *)contentType
{
NSArray<TSAttachment *> *attachments = [self attachmentsWithTransaction:transaction];
return [attachments filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(TSAttachment *evaluatedObject,
NSDictionary<NSString *, id> *_Nullable bindings) {
return ![evaluatedObject.contentType isEqualToString:contentType];
}]];
}
- (void)removeAttachment:(TSAttachment *)attachment transaction:(YapDatabaseReadWriteTransaction *)transaction; - (void)removeAttachment:(TSAttachment *)attachment transaction:(YapDatabaseReadWriteTransaction *)transaction;
{ {
OWSAssertDebug([self.attachmentIds containsObject:attachment.uniqueId]); OWSAssertDebug([self.attachmentIds containsObject:attachment.uniqueId]);
@ -229,20 +249,6 @@ static const NSUInteger OWSMessageSchemaVersion = 4;
[self saveWithTransaction:transaction]; [self saveWithTransaction:transaction];
} }
- (BOOL)isMediaAlbumWithTransaction:(YapDatabaseReadTransaction *)transaction
{
NSArray<TSAttachment *> *attachments = [self attachmentsWithTransaction:transaction];
if (attachments.count < 1) {
return NO;
}
for (TSAttachment *attachment in attachments) {
if (!attachment.isVisualMedia) {
return NO;
}
}
return YES;
}
- (NSString *)debugDescription - (NSString *)debugDescription
{ {
if ([self hasAttachments] && self.body.length > 0) { if ([self hasAttachments] && self.body.length > 0) {
@ -257,15 +263,24 @@ static const NSUInteger OWSMessageSchemaVersion = 4;
} }
} }
- (nullable TSAttachment *)oversizeTextAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction
{
return [self attachmentsWithTransaction:transaction contentType:OWSMimeTypeOversizeTextMessage].firstObject;
}
- (NSArray<TSAttachment *> *)mediaAttachmentsWithTransaction:(YapDatabaseReadTransaction *)transaction
{
return [self attachmentsWithTransaction:transaction exceptContentType:OWSMimeTypeOversizeTextMessage];
}
- (nullable NSString *)oversizeTextWithTransaction:(YapDatabaseReadTransaction *)transaction - (nullable NSString *)oversizeTextWithTransaction:(YapDatabaseReadTransaction *)transaction
{ {
if (self.attachmentIds.count != 1) { TSAttachment *_Nullable attachment = [self oversizeTextAttachmentWithTransaction:transaction];
if (!attachment) {
return nil; return nil;
} }
TSAttachment *_Nullable attachment = [self attachmentsWithTransaction:transaction].firstObject; if (![attachment isKindOfClass:TSAttachmentStream.class]) {
if (![OWSMimeTypeOversizeTextMessage isEqualToString:attachment.contentType]
|| ![attachment isKindOfClass:TSAttachmentStream.class]) {
return nil; return nil;
} }

@ -0,0 +1,22 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
/// By centralizing feature flags here and documenting their rollout plan, it's easier to review
/// which feature flags are in play.
@objc(SSKFeatureFlags)
public class FeatureFlags: NSObject {
/// iOS has long supported sending oversized text as a sidecar attachment. The other clients
/// simply displayed it as a text attachment. As part of the new cross-client long-text feature,
/// we want to be able to display long text with attachments as well. Existing iOS clients
/// won't properly display this, so we'll need to wait a while for rollout.
/// The stakes aren't __too__ high, because legacy clients won't lose data - they just won't
/// see the media attached to a long text message until they update their client.
@objc
public static var sendingMediaWithOversizeText: Bool {
return false
}
}
Loading…
Cancel
Save