diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index f443f7ec0..ab96147ca 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -327,6 +327,8 @@ 459311FC1D75C948008DD4F0 /* OWSDeviceTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 459311FB1D75C948008DD4F0 /* OWSDeviceTableViewCell.m */; }; 4598198E204E2F28009414F2 /* OWS108CallLoggingPreference.h in Headers */ = {isa = PBXBuildFile; fileRef = 4598198C204E2F28009414F2 /* OWS108CallLoggingPreference.h */; }; 4598198F204E2F28009414F2 /* OWS108CallLoggingPreference.m in Sources */ = {isa = PBXBuildFile; fileRef = 4598198D204E2F28009414F2 /* OWS108CallLoggingPreference.m */; }; + 459B775C207BA46C0071D0AB /* OWSQuotedReplyModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 459B775A207BA3A80071D0AB /* OWSQuotedReplyModel.m */; }; + 459B775D207BA4810071D0AB /* OWSQuotedReplyModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 459B7759207BA3A80071D0AB /* OWSQuotedReplyModel.h */; settings = {ATTRIBUTES = (Public, ); }; }; 45A2F005204473A3002E978A /* NewMessage.aifc in Resources */ = {isa = PBXBuildFile; fileRef = 45A2F004204473A3002E978A /* NewMessage.aifc */; }; 45A663C51F92EC760027B59E /* GroupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45A663C41F92EC760027B59E /* GroupTableViewCell.swift */; }; 45A6DAD61EBBF85500893231 /* ReminderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45A6DAD51EBBF85500893231 /* ReminderView.swift */; }; @@ -945,6 +947,8 @@ 4597E94F1D8313CB00040CDE /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = translations/bg.lproj/Localizable.strings; sourceTree = ""; }; 4598198C204E2F28009414F2 /* OWS108CallLoggingPreference.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OWS108CallLoggingPreference.h; sourceTree = ""; }; 4598198D204E2F28009414F2 /* OWS108CallLoggingPreference.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OWS108CallLoggingPreference.m; sourceTree = ""; }; + 459B7759207BA3A80071D0AB /* OWSQuotedReplyModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OWSQuotedReplyModel.h; sourceTree = ""; }; + 459B775A207BA3A80071D0AB /* OWSQuotedReplyModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OWSQuotedReplyModel.m; sourceTree = ""; }; 45A2F004204473A3002E978A /* NewMessage.aifc */ = {isa = PBXFileReference; lastKnownFileType = file; name = NewMessage.aifc; path = Signal/AudioFiles/NewMessage.aifc; sourceTree = SOURCE_ROOT; }; 45A663C41F92EC760027B59E /* GroupTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupTableViewCell.swift; sourceTree = ""; }; 45A6DAD51EBBF85500893231 /* ReminderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReminderView.swift; sourceTree = ""; }; @@ -1763,6 +1767,8 @@ 45194F911FD7214600333B2C /* Models */ = { isa = PBXGroup; children = ( + 459B7759207BA3A80071D0AB /* OWSQuotedReplyModel.h */, + 459B775A207BA3A80071D0AB /* OWSQuotedReplyModel.m */, 34C42D621F4734ED0072EC04 /* OWSContactOffersInteraction.h */, 34C42D631F4734ED0072EC04 /* OWSContactOffersInteraction.m */, 34C42D641F4734ED0072EC04 /* TSUnreadIndicatorInteraction.h */, @@ -2305,6 +2311,7 @@ 346129E71FD5C0C600532771 /* OWSDatabaseMigrationRunner.h in Headers */, 344D6CEA20069E070042AF96 /* SelectRecipientViewController.h in Headers */, 34480B521FD0A7A400BC14EF /* OWSLogger.h in Headers */, + 459B775D207BA4810071D0AB /* OWSQuotedReplyModel.h in Headers */, 34612A001FD5F31400532771 /* OWS105AttachmentFilePaths.h in Headers */, 346129F61FD5F31400532771 /* OWS103EnableVideoCalling.h in Headers */, 344F248A20069F0600CFB4F4 /* ViewControllerUtils.h in Headers */, @@ -3130,6 +3137,7 @@ 451F8A381FD7117E005CB9DA /* OWSViewController.m in Sources */, 346129721FD1D74C00532771 /* SignalKeyingStorage.m in Sources */, 34480B561FD0A7A400BC14EF /* DebugLogger.m in Sources */, + 459B775C207BA46C0071D0AB /* OWSQuotedReplyModel.m in Sources */, 4551DB5A205C562300C8AE75 /* Collection+OWS.swift in Sources */, 3461293C1FD1D46A00532771 /* OWSMath.m in Sources */, 451F8A391FD711D6005CB9DA /* ContactsViewHelper.m in Sources */, diff --git a/Signal/src/Signal-Bridging-Header.h b/Signal/src/Signal-Bridging-Header.h index ea9447f21..ac4265c58 100644 --- a/Signal/src/Signal-Bridging-Header.h +++ b/Signal/src/Signal-Bridging-Header.h @@ -57,6 +57,7 @@ #import #import #import +#import #import #import #import diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 42bb9d371..9204448df 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -2150,7 +2150,7 @@ typedef enum : NSUInteger { __block OWSQuotedReplyModel *quotedReply; [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - quotedReply = [OWSMessageUtils quotedReplyForMessage:message transaction:transaction]; + quotedReply = [OWSQuotedReplyModel quotedReplyForMessage:message transaction:transaction]; }]; if (![quotedReply isKindOfClass:[OWSQuotedReplyModel class]]) { diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m index 247c51962..1ea6fed9b 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m @@ -1969,8 +1969,8 @@ isQuotedMessageAttachmentDownloaded:(BOOL)isQuotedMessageAttachmentDownloaded quotedMessage:nil transaction:transaction]; OWSAssert(messageToQuote); - quotedMessage = - [[OWSMessageUtils quotedReplyForMessage:messageToQuote transaction:transaction] buildQuotedMessage]; + quotedMessage = [[OWSQuotedReplyModel quotedReplyForMessage:messageToQuote transaction:transaction] + buildQuotedMessage]; } else { TSOutgoingMessage *_Nullable messageToQuote = [self createFakeOutgoingMessage:thread messageBody:quotedMessageBodyWIndex @@ -1981,8 +1981,8 @@ isQuotedMessageAttachmentDownloaded:(BOOL)isQuotedMessageAttachmentDownloaded quotedMessage:nil transaction:transaction]; OWSAssert(messageToQuote); - quotedMessage = - [[OWSMessageUtils quotedReplyForMessage:messageToQuote transaction:transaction] buildQuotedMessage]; + quotedMessage = [[OWSQuotedReplyModel quotedReplyForMessage:messageToQuote transaction:transaction] + buildQuotedMessage]; } OWSAssert(quotedMessage); diff --git a/SignalMessaging/Models/OWSQuotedReplyModel.h b/SignalMessaging/Models/OWSQuotedReplyModel.h new file mode 100644 index 000000000..75eb2250f --- /dev/null +++ b/SignalMessaging/Models/OWSQuotedReplyModel.h @@ -0,0 +1,48 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +@class TSAttachmentStream; +@class TSMessage; +@class TSQuotedMessage; +@class YapDatabaseReadTransaction; + +NS_ASSUME_NONNULL_BEGIN + +// View model which has already fetched any attachments. +@interface OWSQuotedReplyModel : NSObject + +@property (nonatomic, readonly) uint64_t timestamp; +@property (nonatomic, readonly) NSString *authorId; +@property (nonatomic, readonly, nullable) TSAttachmentStream *attachmentStream; + +// This property should be set IFF we are quoting a text message +// or attachment with caption. +@property (nullable, nonatomic, readonly) NSString *body; + +#pragma mark - Attachments + +// This is a MIME type. +// +// This property should be set IFF we are quoting an attachment message. +@property (nonatomic, readonly, nullable) NSString *contentType; +@property (nonatomic, readonly, nullable) NSString *sourceFilename; +@property (nonatomic, readonly, nullable) UIImage *thumbnailImage; + +- (instancetype)initWithTimestamp:(uint64_t)timestamp + authorId:(NSString *)authorId + body:(NSString *_Nullable)body + attachmentStream:(nullable TSAttachmentStream *)attachment; + +- (instancetype)initWithQuotedMessage:(TSQuotedMessage *)quotedMessage + transaction:(YapDatabaseReadTransaction *)transaction; + ++ (nullable instancetype)quotedReplyForMessage:(TSMessage *)message + transaction:(YapDatabaseReadTransaction *)transaction; + +- (TSQuotedMessage *)buildQuotedMessage; + + +@end + +NS_ASSUME_NONNULL_END diff --git a/SignalMessaging/Models/OWSQuotedReplyModel.m b/SignalMessaging/Models/OWSQuotedReplyModel.m new file mode 100644 index 000000000..e7fa512d6 --- /dev/null +++ b/SignalMessaging/Models/OWSQuotedReplyModel.m @@ -0,0 +1,197 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "OWSQuotedReplyModel.h" +#import +#import +#import +#import +#import +#import +#import +#import +#import + +// View Model which has already fetched any thumbnail attachment. +@implementation OWSQuotedReplyModel + +- (instancetype)initWithTimestamp:(uint64_t)timestamp + authorId:(NSString *)authorId + body:(NSString *_Nullable)body + attachmentStream:(nullable TSAttachmentStream *)attachmentStream +{ + return [self initWithTimestamp:timestamp + authorId:authorId + body:body + thumbnailImage:attachmentStream.thumbnailImage + contentType:attachmentStream.contentType + sourceFilename:attachmentStream.sourceFilename + attachmentStream:attachmentStream]; +} + + +- (instancetype)initWithTimestamp:(uint64_t)timestamp + authorId:(NSString *)authorId + body:(nullable NSString *)body + thumbnailImage:(nullable UIImage *)thumbnailImage + contentType:(nullable NSString *)contentType + sourceFilename:(nullable NSString *)sourceFilename + attachmentStream:(nullable TSAttachmentStream *)attachmentStream +{ + self = [super init]; + if (!self) { + return self; + } + + _timestamp = timestamp; + _authorId = authorId; + _body = body; + _thumbnailImage = thumbnailImage; + _contentType = contentType; + _sourceFilename = sourceFilename; + + // rename to originalAttachmentStream? + _attachmentStream = attachmentStream; + + return self; +} + +- (instancetype)initWithQuotedMessage:(TSQuotedMessage *)quotedMessage + transaction:(YapDatabaseReadTransaction *)transaction +{ + OWSAssert(quotedMessage.quotedAttachments.count <= 1); + OWSAttachmentInfo *attachmentInfo = quotedMessage.quotedAttachments.firstObject; + + UIImage *_Nullable thumbnailImage; + if (attachmentInfo.thumbnailAttachmentStreamId) { + TSAttachment *attachment = + [TSAttachment fetchObjectWithUniqueID:attachmentInfo.thumbnailAttachmentStreamId transaction:transaction]; + + TSAttachmentStream *attachmentStream; + if ([attachment isKindOfClass:[TSAttachmentStream class]]) { + attachmentStream = (TSAttachmentStream *)attachment; + thumbnailImage = attachmentStream.image; + } + } + + return [self initWithTimestamp:quotedMessage.timestamp + authorId:quotedMessage.authorId + body:quotedMessage.body + thumbnailImage:thumbnailImage + contentType:attachmentInfo.contentType + sourceFilename:attachmentInfo.sourceFilename + attachmentStream:nil]; +} + +- (TSQuotedMessage *)buildQuotedMessage +{ + NSArray *attachments = self.attachmentStream ? @[ self.attachmentStream ] : @[]; + + return [[TSQuotedMessage alloc] initWithTimestamp:self.timestamp + authorId:self.authorId + body:self.body + quotedAttachmentsForSending:attachments]; +} + ++ (nullable OWSQuotedReplyModel *)quotedReplyForMessage:(TSMessage *)message + transaction:(YapDatabaseReadTransaction *)transaction; +{ + OWSAssert(message); + OWSAssert(transaction); + + TSThread *thread = [message threadWithTransaction:transaction]; + + NSString *_Nullable authorId = ^{ + if ([message isKindOfClass:[TSOutgoingMessage class]]) { + return [TSAccountManager localNumber]; + } else if ([message isKindOfClass:[TSIncomingMessage class]]) { + return [(TSIncomingMessage *)message authorId]; + } else { + OWSFail(@"%@ Unexpected message type: %@", self.logTag, message.class); + return (NSString * _Nullable) nil; + } + }(); + OWSAssert(authorId.length > 0); + + return [self quotedReplyForMessage:message authorId:authorId thread:thread transaction:transaction]; +} + ++ (nullable OWSQuotedReplyModel *)quotedReplyForMessage:(TSMessage *)message + authorId:(NSString *)authorId + thread:(TSThread *)thread + transaction:(YapDatabaseReadTransaction *)transaction +{ + OWSAssert(message); + OWSAssert(authorId.length > 0); + OWSAssert(thread); + OWSAssert(transaction); + + uint64_t timestamp = message.timestamp; + NSString *_Nullable quotedText = message.body; + BOOL hasText = quotedText.length > 0; + BOOL hasAttachment = NO; + + TSAttachment *_Nullable attachment = [message attachmentWithTransaction:transaction]; + TSAttachmentStream *quotedAttachment; + if (attachment && [attachment isKindOfClass:[TSAttachmentStream class]]) { + + TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment; + + // If the attachment is "oversize text", try the quote as a reply to text, not as + // a reply to an attachment. + if (!hasText && [OWSMimeTypeOversizeTextMessage isEqualToString:attachment.contentType]) { + hasText = YES; + quotedText = @""; + + NSData *_Nullable oversizeTextData = [NSData dataWithContentsOfFile:attachmentStream.filePath]; + if (oversizeTextData) { + // We don't need to include the entire text body of the message, just + // enough to render a snippet. kOversizeTextMessageSizeThreshold is our + // limit on how long text should be in protos since they'll be stored in + // the database. We apply this constant here for the same reasons. + NSString *_Nullable oversizeText = + [[NSString alloc] initWithData:oversizeTextData encoding:NSUTF8StringEncoding]; + // First, truncate to the rough max characters. + NSString *_Nullable truncatedText = + [oversizeText substringToIndex:kOversizeTextMessageSizeThreshold - 1]; + // But kOversizeTextMessageSizeThreshold is in _bytes_, not characters, + // so we need to continue to trim the string until it fits. + while (truncatedText && truncatedText.length > 0 && + [truncatedText dataUsingEncoding:NSUTF8StringEncoding].length + >= kOversizeTextMessageSizeThreshold) { + // A very coarse binary search by halving is acceptable, since + // kOversizeTextMessageSizeThreshold is much longer than our target + // length of "three short lines of text on any device we might + // display this on. + // + // The search will always converge since in the worst case (namely + // a single character which in utf-8 is >= 1024 bytes) the loop will + // exit when the string is empty. + truncatedText = [truncatedText substringToIndex:oversizeText.length / 2]; + } + if ([truncatedText dataUsingEncoding:NSUTF8StringEncoding].length < kOversizeTextMessageSizeThreshold) { + quotedText = truncatedText; + } else { + OWSFail(@"%@ Missing valid text snippet.", self.logTag); + } + } + } else { + quotedAttachment = attachmentStream; + hasAttachment = YES; + } + } + + if (!hasText && !hasAttachment) { + OWSFail(@"%@ quoted message has neither text nor attachment", self.logTag); + return nil; + } + + return [[OWSQuotedReplyModel alloc] initWithTimestamp:timestamp + authorId:authorId + body:quotedText + attachmentStream:quotedAttachment]; +} + + +@end diff --git a/SignalMessaging/utils/ThreadUtil.m b/SignalMessaging/utils/ThreadUtil.m index c32ca0d05..b5feac58f 100644 --- a/SignalMessaging/utils/ThreadUtil.m +++ b/SignalMessaging/utils/ThreadUtil.m @@ -5,6 +5,7 @@ #import "ThreadUtil.h" #import "OWSContactOffersInteraction.h" #import "OWSContactsManager.h" +#import "OWSQuotedReplyModel.h" #import "TSUnreadIndicatorInteraction.h" #import #import diff --git a/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.h b/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.h index e9242fe68..dfc179173 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.h +++ b/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.h @@ -15,38 +15,6 @@ NS_ASSUME_NONNULL_BEGIN @class TSThread; @class YapDatabaseReadWriteTransaction; -// View model which has already fetched any attachments. -@interface OWSQuotedReplyModel : NSObject - -@property (nonatomic, readonly) uint64_t timestamp; -@property (nonatomic, readonly) NSString *authorId; -@property (nonatomic, readonly, nullable) TSAttachmentStream *attachmentStream; - -// This property should be set IFF we are quoting a text message -// or attachment with caption. -@property (nullable, nonatomic, readonly) NSString *body; - -#pragma mark - Attachments - -// This is a MIME type. -// -// This property should be set IFF we are quoting an attachment message. -@property (nonatomic, readonly, nullable) NSString *contentType; -@property (nonatomic, readonly, nullable) NSString *sourceFilename; -@property (nonatomic, readonly, nullable) UIImage *thumbnailImage; - -- (instancetype)initWithTimestamp:(uint64_t)timestamp - authorId:(NSString *)authorId - body:(NSString *_Nullable)body - attachmentStream:(nullable TSAttachmentStream *)attachment; - -- (instancetype)initWithQuotedMessage:(TSQuotedMessage *)quotedMessage - transaction:(YapDatabaseReadTransaction *)transaction; - -- (TSQuotedMessage *)buildQuotedMessage; - -@end - @interface OWSAttachmentInfo: MTLModel @property (nonatomic, readonly, nullable) NSString *contentType; @@ -56,8 +24,6 @@ NS_ASSUME_NONNULL_BEGIN // to reference the original attachment when generating a thumbnail. // We don't want to do this until the message is saved, when the user sends // the message so as not to end up with an orphaned file. -// -// TODO: rename to pendingAttachmentId or maybe pendingAttachmentStream? @property (nonatomic, readonly, nullable) NSString *attachmentId; // References a yet-to-be downloaded thumbnail file @@ -76,8 +42,6 @@ NS_ASSUME_NONNULL_BEGIN @end -// TODO make this a MantleModel not a YapDatabaseObject. - @interface TSQuotedMessage : MTLModel @property (nonatomic, readonly) uint64_t timestamp; diff --git a/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.m b/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.m index d09ee2447..7b0492913 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.m @@ -46,89 +46,6 @@ NS_ASSUME_NONNULL_BEGIN @end -// View Model which has already fetched any thumbnail attachment. -@implementation OWSQuotedReplyModel - -- (instancetype)initWithTimestamp:(uint64_t)timestamp - authorId:(NSString *)authorId - body:(NSString *_Nullable)body - attachmentStream:(nullable TSAttachmentStream *)attachmentStream -{ - return [self initWithTimestamp:timestamp - authorId:authorId - body:body - thumbnailImage:attachmentStream.thumbnailImage - contentType:attachmentStream.contentType - sourceFilename:attachmentStream.sourceFilename - attachmentStream:attachmentStream]; -} - - -- (instancetype)initWithTimestamp:(uint64_t)timestamp - authorId:(NSString *)authorId - body:(nullable NSString *)body - thumbnailImage:(nullable UIImage *)thumbnailImage - contentType:(nullable NSString *)contentType - sourceFilename:(nullable NSString *)sourceFilename - attachmentStream:(nullable TSAttachmentStream *)attachmentStream -{ - self = [super init]; - if (!self) { - return self; - } - - _timestamp = timestamp; - _authorId = authorId; - _body = body; - _thumbnailImage = thumbnailImage; - _contentType = contentType; - _sourceFilename = sourceFilename; - - // rename to originalAttachmentStream? - _attachmentStream = attachmentStream; - - return self; -} - -- (instancetype)initWithQuotedMessage:(TSQuotedMessage *)quotedMessage - transaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAssert(quotedMessage.quotedAttachments.count <= 1); - OWSAttachmentInfo *attachmentInfo = quotedMessage.quotedAttachments.firstObject; - - UIImage *_Nullable thumbnailImage; - if (attachmentInfo.thumbnailAttachmentStreamId) { - TSAttachment *attachment = - [TSAttachment fetchObjectWithUniqueID:attachmentInfo.thumbnailAttachmentStreamId transaction:transaction]; - - TSAttachmentStream *attachmentStream; - if ([attachment isKindOfClass:[TSAttachmentStream class]]) { - attachmentStream = (TSAttachmentStream *)attachment; - thumbnailImage = attachmentStream.image; - } - } - - return [self initWithTimestamp:quotedMessage.timestamp - authorId:quotedMessage.authorId - body:quotedMessage.body - thumbnailImage:thumbnailImage - contentType:attachmentInfo.contentType - sourceFilename:attachmentInfo.sourceFilename - attachmentStream:nil]; -} - -- (TSQuotedMessage *)buildQuotedMessage -{ - NSArray *attachments = self.attachmentStream ? @[ self.attachmentStream ] : @[]; - - return [[TSQuotedMessage alloc] initWithTimestamp:self.timestamp - authorId:self.authorId - body:self.body - quotedAttachmentsForSending:attachments]; -} - -@end - @interface TSQuotedMessage () @property (atomic) NSArray *quotedAttachments; diff --git a/SignalServiceKit/src/Messages/OWSMessageUtils.h b/SignalServiceKit/src/Messages/OWSMessageUtils.h index b9153e0d2..5c136eebe 100644 --- a/SignalServiceKit/src/Messages/OWSMessageUtils.h +++ b/SignalServiceKit/src/Messages/OWSMessageUtils.h @@ -4,7 +4,6 @@ NS_ASSUME_NONNULL_BEGIN -@class OWSQuotedReplyModel; @class TSMessage; @class TSThread; @class YapDatabaseReadTransaction; @@ -20,9 +19,6 @@ NS_ASSUME_NONNULL_BEGIN - (void)updateApplicationBadgeCount; -+ (nullable OWSQuotedReplyModel *)quotedReplyForMessage:(TSMessage *)message - transaction:(YapDatabaseReadTransaction *)transaction; - @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSMessageUtils.m b/SignalServiceKit/src/Messages/OWSMessageUtils.m index 9c4bff662..79c72df0c 100644 --- a/SignalServiceKit/src/Messages/OWSMessageUtils.m +++ b/SignalServiceKit/src/Messages/OWSMessageUtils.m @@ -104,170 +104,6 @@ NS_ASSUME_NONNULL_BEGIN return numberOfItems; } -+ (nullable OWSQuotedReplyModel *)quotedReplyForMessage:(TSMessage *)message - transaction:(YapDatabaseReadTransaction *)transaction; -{ - OWSAssert(message); - OWSAssert(transaction); - - TSThread *thread = [message threadWithTransaction:transaction]; - - NSString *_Nullable authorId = ^{ - if ([message isKindOfClass:[TSOutgoingMessage class]]) { - return [TSAccountManager localNumber]; - } else if ([message isKindOfClass:[TSIncomingMessage class]]) { - return [(TSIncomingMessage *)message authorId]; - } else { - OWSFail(@"%@ Unexpected message type: %@", self.logTag, message.class); - return (NSString * _Nullable) nil; - } - }(); - OWSAssert(authorId.length > 0); - - return [self quotedReplyForMessage:message authorId:authorId thread:thread transaction:transaction]; -} - -+ (nullable OWSQuotedReplyModel *)quotedReplyForMessage:(TSMessage *)message - authorId:(NSString *)authorId - thread:(TSThread *)thread - transaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAssert(message); - OWSAssert(authorId.length > 0); - OWSAssert(thread); - OWSAssert(transaction); - - uint64_t timestamp = message.timestamp; - NSString *_Nullable quotedText = message.body; - BOOL hasText = quotedText.length > 0; - BOOL hasAttachment = NO; - - TSAttachment *_Nullable attachment = [message attachmentWithTransaction:transaction]; - TSAttachmentStream *quotedAttachment; - if (attachment && [attachment isKindOfClass:[TSAttachmentStream class]]) { - - TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment; - - // If the attachment is "oversize text", try the quote as a reply to text, not as - // a reply to an attachment. - if (!hasText && [OWSMimeTypeOversizeTextMessage isEqualToString:attachment.contentType]) { - hasText = YES; - quotedText = @""; - - NSData *_Nullable oversizeTextData = [NSData dataWithContentsOfFile:attachmentStream.filePath]; - if (oversizeTextData) { - // We don't need to include the entire text body of the message, just - // enough to render a snippet. kOversizeTextMessageSizeThreshold is our - // limit on how long text should be in protos since they'll be stored in - // the database. We apply this constant here for the same reasons. - NSString *_Nullable oversizeText = - [[NSString alloc] initWithData:oversizeTextData encoding:NSUTF8StringEncoding]; - // First, truncate to the rough max characters. - NSString *_Nullable truncatedText = - [oversizeText substringToIndex:kOversizeTextMessageSizeThreshold - 1]; - // But kOversizeTextMessageSizeThreshold is in _bytes_, not characters, - // so we need to continue to trim the string until it fits. - while (truncatedText && truncatedText.length > 0 && - [truncatedText dataUsingEncoding:NSUTF8StringEncoding].length - >= kOversizeTextMessageSizeThreshold) { - // A very coarse binary search by halving is acceptable, since - // kOversizeTextMessageSizeThreshold is much longer than our target - // length of "three short lines of text on any device we might - // display this on. - // - // The search will always converge since in the worst case (namely - // a single character which in utf-8 is >= 1024 bytes) the loop will - // exit when the string is empty. - truncatedText = [truncatedText substringToIndex:oversizeText.length / 2]; - } - if ([truncatedText dataUsingEncoding:NSUTF8StringEncoding].length - < kOversizeTextMessageSizeThreshold) { - quotedText = truncatedText; - } else { - OWSFail(@"%@ Missing valid text snippet.", self.logTag); - } - } - } else { - quotedAttachment = attachmentStream; - hasAttachment = YES; - } - } - - if (!hasText && !hasAttachment) { - OWSFail(@"%@ quoted message has neither text nor attachment", self.logTag); - return nil; - } - - return [[OWSQuotedReplyModel alloc] initWithTimestamp:timestamp - authorId:authorId - body:quotedText - attachmentStream:quotedAttachment]; -} - -+ (nullable NSData *)thumbnailDataForAttachment:(TSAttachment *)attachment -{ - OWSAssert(attachment); - - // Try to generate a thumbnail, if possible. - if (![attachment isKindOfClass:[TSAttachmentStream class]]) { - return nil; - } - - TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment; - UIImage *_Nullable attachmentImage = [attachmentStream image]; - if (!attachmentImage) { - return nil; - } - - CGSize attachmentImageSizePx; - attachmentImageSizePx.width = CGImageGetWidth(attachmentImage.CGImage); - attachmentImageSizePx.height = CGImageGetHeight(attachmentImage.CGImage); - if (attachmentImageSizePx.width <= 0 || attachmentImageSizePx.height <= 0) { - DDLogError(@"%@ attachment thumbnail has invalid size.", self.logTag); - return nil; - } - - // TODO: Revisit this value. - const int kMaxThumbnailSizePx = 100; - - // Try to resize image to thumbnail if necessary. - if (attachmentImageSizePx.width > kMaxThumbnailSizePx || attachmentImageSizePx.height > kMaxThumbnailSizePx) { - const CGFloat widthFactor = kMaxThumbnailSizePx / attachmentImageSizePx.width; - const CGFloat heightFactor = kMaxThumbnailSizePx / attachmentImageSizePx.height; - const CGFloat scalingFactor = MIN(widthFactor, heightFactor); - const CGFloat scaledWidthPx = (CGFloat)round(attachmentImageSizePx.width * scalingFactor); - const CGFloat scaledHeightPx = (CGFloat)round(attachmentImageSizePx.height * scalingFactor); - - if (scaledWidthPx <= 0 || scaledHeightPx <= 0) { - DDLogError(@"%@ can't determined desired size for attachment thumbnail.", self.logTag); - return nil; - } - - if (scaledWidthPx > 0 && scaledHeightPx > 0) { - attachmentImage = [attachmentImage resizedImageToSize:CGSizeMake(scaledWidthPx, scaledHeightPx)]; - if (!attachmentImage) { - DDLogError(@"%@ attachment thumbnail could not be resized.", self.logTag); - return nil; - } - - attachmentImageSizePx.width = CGImageGetWidth(attachmentImage.CGImage); - attachmentImageSizePx.height = CGImageGetHeight(attachmentImage.CGImage); - } - } - - if (attachmentImageSizePx.width <= 0 || attachmentImageSizePx.height <= 0) { - DDLogError(@"%@ resized attachment thumbnail has invalid size.", self.logTag); - return nil; - } - - NSData *_Nullable attachmentImageData = UIImagePNGRepresentation(attachmentImage); - if (!attachmentImage) { - OWSFail(@"%@ attachment thumbnail could not be written to PNG.", self.logTag); - return nil; - } - return attachmentImageData; -} - @end NS_ASSUME_NONNULL_END