Merge branch 'mkirk/long-text'

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

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

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

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

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

@ -3604,10 +3604,16 @@ typedef enum : NSUInteger {
}
BOOL didAddToProfileWhitelist = [ThreadUtil addThreadToProfileWhitelistIfEmptyContactThread:self.thread];
TSOutgoingMessage *message = [ThreadUtil enqueueMessageWithAttachments:attachments
messageBody:messageText
inThread:self.thread
quotedReplyModel:self.inputToolbar.quotedReply];
__block TSOutgoingMessage *message;
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
message = [ThreadUtil enqueueMessageWithText:messageText
mediaAttachments:attachments
inThread:self.thread
quotedReplyModel:self.inputToolbar.quotedReply
linkPreviewDraft:nil
transaction:transaction];
}];
[self messageWasSent:message];
@ -3975,26 +3981,13 @@ typedef enum : NSUInteger {
// which are presented as normal text messages.
BOOL didAddToProfileWhitelist = [ThreadUtil addThreadToProfileWhitelistIfEmptyContactThread:self.thread];
__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) {
message = [ThreadUtil enqueueMessageWithText:text
inThread:self.thread
quotedReplyModel:self.inputToolbar.quotedReply
linkPreviewDraft:self.inputToolbar.linkPreviewDraft
transaction:transaction];
}];
}
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
message = [ThreadUtil enqueueMessageWithText:text
inThread:self.thread
quotedReplyModel:self.inputToolbar.quotedReply
linkPreviewDraft:self.inputToolbar.linkPreviewDraft
transaction:transaction];
}];
[self.conversationViewModel appendUnsavedOutgoingTextMessage:message];
[self messageWasSent:message];

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

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

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

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

@ -1861,47 +1861,49 @@ class MediaMessageTextToolbar: UIView, UITextViewDelegate {
public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
let existingText: String = textView.text ?? ""
let proposedText: String = (existingText as NSString).replacingCharacters(in: range, with: text)
// Don't complicate things by mixing media attachments with oversize text attachments
guard proposedText.utf8.count <= kOversizeTextMessageSizeThreshold else {
Logger.debug("long text was truncated")
self.lengthLimitLabel.isHidden = false
// `range` represents the section of the existing text we will replace. We can re-use that space.
// Range is in units of NSStrings's standard UTF-16 characters. Since some of those chars could be
// represented as single bytes in utf-8, while others may be 8 or more, the only way to be sure is
// to just measure the utf8 encoded bytes of the replaced substring.
let bytesAfterDelete: Int = (existingText as NSString).replacingCharacters(in: range, with: "").utf8.count
if !FeatureFlags.sendingMediaWithOversizeText {
let existingText: String = textView.text ?? ""
let proposedText: String = (existingText as NSString).replacingCharacters(in: range, with: text)
// Don't complicate things by mixing media attachments with oversize text attachments
guard proposedText.utf8.count < kOversizeTextMessageSizeThreshold else {
Logger.debug("long text was truncated")
self.lengthLimitLabel.isHidden = false
// `range` represents the section of the existing text we will replace. We can re-use that space.
// Range is in units of NSStrings's standard UTF-16 characters. Since some of those chars could be
// represented as single bytes in utf-8, while others may be 8 or more, the only way to be sure is
// to just measure the utf8 encoded bytes of the replaced substring.
let bytesAfterDelete: Int = (existingText as NSString).replacingCharacters(in: range, with: "").utf8.count
// Accept as much of the input as we can
let byteBudget: Int = Int(kOversizeTextMessageSizeThreshold) - bytesAfterDelete
if byteBudget >= 0, let acceptableNewText = text.truncated(toByteCount: UInt(byteBudget)) {
textView.text = (existingText as NSString).replacingCharacters(in: range, with: acceptableNewText)
}
// Accept as much of the input as we can
let byteBudget: Int = Int(kOversizeTextMessageSizeThreshold) - bytesAfterDelete
if byteBudget >= 0, let acceptableNewText = text.truncated(toByteCount: UInt(byteBudget)) {
textView.text = (existingText as NSString).replacingCharacters(in: range, with: acceptableNewText)
return false
}
self.lengthLimitLabel.isHidden = true
return false
}
self.lengthLimitLabel.isHidden = true
// After verifying the byte-length is sufficiently small, verify the character count is within bounds.
guard proposedText.count < kMaxMessageBodyCharacterCount else {
Logger.debug("hit attachment message body character count limit")
// After verifying the byte-length is sufficiently small, verify the character count is within bounds.
guard proposedText.count <= kMaxMessageBodyCharacterCount else {
Logger.debug("hit attachment message body character count limit")
self.lengthLimitLabel.isHidden = false
self.lengthLimitLabel.isHidden = false
// `range` represents the section of the existing text we will replace. We can re-use that space.
let charsAfterDelete: Int = (existingText as NSString).replacingCharacters(in: range, with: "").count
// `range` represents the section of the existing text we will replace. We can re-use that space.
let charsAfterDelete: Int = (existingText as NSString).replacingCharacters(in: range, with: "").count
// Accept as much of the input as we can
let charBudget: Int = Int(kMaxMessageBodyCharacterCount) - charsAfterDelete
if charBudget >= 0 {
let acceptableNewText = String(text.prefix(charBudget))
textView.text = (existingText as NSString).replacingCharacters(in: range, with: acceptableNewText)
}
// Accept as much of the input as we can
let charBudget: Int = Int(kMaxMessageBodyCharacterCount) - charsAfterDelete
if charBudget >= 0 {
let acceptableNewText = String(text.prefix(charBudget))
textView.text = (existingText as NSString).replacingCharacters(in: range, with: acceptableNewText)
return false
}
return false
}
// Though we can wrap the text, we don't want to encourage multline captions, plus a "done" button

@ -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"

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

@ -9,6 +9,7 @@
#import "OWSUnreadIndicator.h"
#import "TSUnreadIndicatorInteraction.h"
#import <SignalCoreKit/NSDate+OWS.h>
#import <SignalCoreKit/SignalCoreKit-Swift.h>
#import <SignalMessaging/OWSProfileManager.h>
#import <SignalMessaging/SignalMessaging-Swift.h>
#import <SignalServiceKit/OWSAddToContactsOfferMessage.h>
@ -65,42 +66,18 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Durable Message Enqueue
+ (TSOutgoingMessage *)enqueueMessageWithText:(NSString *)text
+ (TSOutgoingMessage *)enqueueMessageWithText:(NSString *)fullMessageText
inThread:(TSThread *)thread
quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel
linkPreviewDraft:(nullable nullable OWSLinkPreviewDraft *)linkPreviewDraft
transaction:(YapDatabaseReadTransaction *)transaction
{
OWSDisappearingMessagesConfiguration *configuration =
[OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:thread.uniqueId transaction:transaction];
uint32_t expiresInSeconds = (configuration.isEnabled ? configuration.durationSeconds : 0);
TSOutgoingMessage *message =
[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;
return [self enqueueMessageWithText:fullMessageText
mediaAttachments:@[]
inThread:thread
quotedReplyModel:quotedReplyModel
linkPreviewDraft:linkPreviewDraft
transaction:transaction];
}
+ (nullable OWSLinkPreview *)linkPreviewForLinkPreviewDraft:(nullable OWSLinkPreviewDraft *)linkPreviewDraft
@ -121,40 +98,56 @@ NS_ASSUME_NONNULL_BEGIN
return linkPreview;
}
+ (TSOutgoingMessage *)enqueueMessageWithAttachment:(SignalAttachment *)attachment
inThread:(TSThread *)thread
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
quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel
+ (TSOutgoingMessage *)enqueueMessageWithText:(nullable NSString *)fullMessageText
mediaAttachments:(NSArray<SignalAttachment *> *)attachmentsParam
inThread:(TSThread *)thread
quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel
linkPreviewDraft:(nullable nullable OWSLinkPreviewDraft *)linkPreviewDraft
transaction:(YapDatabaseReadTransaction *)transaction
{
OWSAssertIsOnMainThread();
OWSAssertDebug(attachments.count > 0);
OWSAssertDebug(thread);
for (SignalAttachment *attachment in attachments) {
OWSAssertDebug(!attachment.hasError);
OWSAssertDebug(attachment.mimeType.length > 0);
NSString *_Nullable truncatedText;
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 fetchObjectWithUniqueID:thread.uniqueId];
[OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:thread.uniqueId transaction:transaction];
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);
TSOutgoingMessage *message =
[[TSOutgoingMessage alloc] initOutgoingMessageWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
messageBody:messageBody
messageBody:truncatedText
attachmentIds:[NSMutableArray new]
expiresInSeconds:expiresInSeconds
expireStartedAt:0
@ -164,12 +157,40 @@ NS_ASSUME_NONNULL_BEGIN
contactShare:nil
linkPreview:nil];
NSMutableArray<OWSOutgoingAttachmentInfo *> *attachmentInfos = [NSMutableArray new];
for (SignalAttachment *attachment in attachments) {
OWSOutgoingAttachmentInfo *attachmentInfo = [attachment buildOutgoingAttachmentInfoWithMessage:message];
[attachmentInfos addObject:attachmentInfo];
}
[self.messageSenderJobQueue addMediaMessage:message attachmentInfos:attachmentInfos isTemporaryAttachment:NO];
[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) {
OWSOutgoingAttachmentInfo *attachmentInfo =
[attachment buildOutgoingAttachmentInfoWithMessage:message];
[attachmentInfos addObject:attachmentInfo];
}
[self.messageSenderJobQueue addMediaMessage:message
attachmentInfos:attachmentInfos
isTemporaryAttachment:NO];
}
}
completionBlock:benchmarkCompletion];
}];
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"
@ -93,6 +93,7 @@ typedef NS_ENUM(NSUInteger, TSAttachmentType) {
@property (nonatomic, readonly) BOOL isAudio;
@property (nonatomic, readonly) BOOL isVoiceMessage;
@property (nonatomic, readonly) BOOL isVisualMedia;
@property (nonatomic, readonly) BOOL isOversizeText;
+ (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"
@ -273,6 +273,11 @@ NSUInteger const TSAttachmentSchemaVersion = 4;
return [MIMETypeUtil isVisualMedia:self.contentType];
}
- (BOOL)isOversizeText
{
return [self.contentType isEqualToString:OWSMimeTypeOversizeTextMessage];
}
- (nullable NSString *)sourceFilename
{
return _sourceFilename.filterFilename;

@ -45,6 +45,9 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)hasAttachments;
- (NSArray<TSAttachment *> *)attachmentsWithTransaction:(YapDatabaseReadTransaction *)transaction;
- (NSArray<TSAttachment *> *)mediaAttachmentsWithTransaction:(YapDatabaseReadTransaction *)transaction;
- (nullable TSAttachment *)oversizeTextAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction;
- (void)removeAttachment:(TSAttachment *)attachment
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.
- (NSArray<NSString *> *)allAttachmentIds;
- (BOOL)isMediaAlbumWithTransaction:(YapDatabaseReadTransaction *)transaction;
- (void)setQuotedMessageThumbnailAttachmentStream:(TSAttachmentStream *)attachmentStream;
- (nullable NSString *)oversizeTextWithTransaction:(YapDatabaseReadTransaction *)transaction;

@ -219,6 +219,26 @@ static const NSUInteger OWSMessageSchemaVersion = 4;
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;
{
OWSAssertDebug([self.attachmentIds containsObject:attachment.uniqueId]);
@ -229,20 +249,6 @@ static const NSUInteger OWSMessageSchemaVersion = 4;
[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
{
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
{
if (self.attachmentIds.count != 1) {
TSAttachment *_Nullable attachment = [self oversizeTextAttachmentWithTransaction:transaction];
if (!attachment) {
return nil;
}
TSAttachment *_Nullable attachment = [self attachmentsWithTransaction:transaction].firstObject;
if (![OWSMimeTypeOversizeTextMessage isEqualToString:attachment.contentType]
|| ![attachment isKindOfClass:TSAttachmentStream.class]) {
if (![attachment isKindOfClass:TSAttachmentStream.class]) {
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