diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m index 0779cdcfe..da1cfcb1b 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m @@ -252,9 +252,8 @@ NS_ASSUME_NONNULL_BEGIN if (self.isQuotedReply) { OWSAssert(!lastSubview); - TSMessage *message = (TSMessage *)self.viewItem.interaction; OWSQuotedMessageView *quotedMessageView = [OWSQuotedMessageView - quotedMessageViewForConversation:message.quotedMessage + quotedMessageViewForConversation:self.viewItem.quotedReply displayableQuotedText:(self.viewItem.hasQuotedText ? self.viewItem.displayableQuotedText : nil)]; self.quotedMessageView = quotedMessageView; [quotedMessageView createContents]; @@ -903,9 +902,8 @@ NS_ASSUME_NONNULL_BEGIN return CGSizeZero; } - TSMessage *message = (TSMessage *)self.viewItem.interaction; OWSQuotedMessageView *quotedMessageView = [OWSQuotedMessageView - quotedMessageViewForConversation:message.quotedMessage + quotedMessageViewForConversation:self.viewItem.quotedReply displayableQuotedText:(self.hasQuotedText ? self.viewItem.displayableQuotedText : nil)]; const int maxMessageWidth = [self maxMessageWidthForContentWidth:contentWidth]; CGSize result = [quotedMessageView sizeForMaxWidth:maxMessageWidth - kBubbleThornSideInset]; diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.h b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.h index 09cfcd274..272f811bf 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.h +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.h @@ -6,7 +6,7 @@ NS_ASSUME_NONNULL_BEGIN @class DisplayableText; @class OWSBubbleStrokeView; -@class TSQuotedMessage; +@class OWSQuotedReplyModel; @interface OWSQuotedMessageView : UIView @@ -21,11 +21,11 @@ NS_ASSUME_NONNULL_BEGIN - (CGSize)sizeForMaxWidth:(CGFloat)maxWidth; // Factory method for "message bubble" views. -+ (OWSQuotedMessageView *)quotedMessageViewForConversation:(TSQuotedMessage *)quotedMessage ++ (OWSQuotedMessageView *)quotedMessageViewForConversation:(OWSQuotedReplyModel *)quotedMessage displayableQuotedText:(nullable DisplayableText *)displayableQuotedText; // Factory method for "message compose" views. -+ (OWSQuotedMessageView *)quotedMessageViewForPreview:(TSQuotedMessage *)quotedMessage; ++ (OWSQuotedMessageView *)quotedMessageViewForPreview:(OWSQuotedReplyModel *)quotedMessage; @end diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m index 6c661f52e..2464dc963 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m @@ -19,7 +19,7 @@ NS_ASSUME_NONNULL_BEGIN @interface OWSQuotedMessageView () -@property (nonatomic, readonly) TSQuotedMessage *quotedMessage; +@property (nonatomic, readonly) OWSQuotedReplyModel *quotedMessage; @property (nonatomic, nullable, readonly) DisplayableText *displayableQuotedText; @property (nonatomic, nullable) OWSBubbleStrokeView *boundsStrokeView; @@ -30,7 +30,7 @@ NS_ASSUME_NONNULL_BEGIN @implementation OWSQuotedMessageView -+ (OWSQuotedMessageView *)quotedMessageViewForConversation:(TSQuotedMessage *)quotedMessage ++ (OWSQuotedMessageView *)quotedMessageViewForConversation:(OWSQuotedReplyModel *)quotedMessage displayableQuotedText:(nullable DisplayableText *)displayableQuotedText { OWSAssert(quotedMessage); @@ -39,7 +39,7 @@ NS_ASSUME_NONNULL_BEGIN [[OWSQuotedMessageView alloc] initWithQuotedMessage:quotedMessage displayableQuotedText:displayableQuotedText]; } -+ (OWSQuotedMessageView *)quotedMessageViewForPreview:(TSQuotedMessage *)quotedMessage ++ (OWSQuotedMessageView *)quotedMessageViewForPreview:(OWSQuotedReplyModel *)quotedMessage { OWSAssert(quotedMessage); @@ -52,7 +52,7 @@ NS_ASSUME_NONNULL_BEGIN [[OWSQuotedMessageView alloc] initWithQuotedMessage:quotedMessage displayableQuotedText:displayableQuotedText]; } -- (instancetype)initWithQuotedMessage:(TSQuotedMessage *)quotedMessage +- (instancetype)initWithQuotedMessage:(OWSQuotedReplyModel *)quotedMessage displayableQuotedText:(nullable DisplayableText *)displayableQuotedText { self = [super init]; @@ -140,9 +140,6 @@ NS_ASSUME_NONNULL_BEGIN [quotedAttachmentView autoSetDimension:ALDimensionHeight toSize:self.quotedAttachmentSize]; [quotedAttachmentView setContentHuggingHigh]; [quotedAttachmentView setCompressionResistanceHigh]; - - if (quotedAttachmentView) { - } } UILabel *quotedAuthorLabel = [self createQuotedAuthorLabel]; @@ -210,14 +207,10 @@ NS_ASSUME_NONNULL_BEGIN return nil; } - // FIXME - return nil; - - // - // // TODO: Possibly ignore data that is too large. - // UIImage *_Nullable image = self.quotedMessage.thumbnailImage; - // // TODO: Possibly ignore images that are too large. - // return image; + // TODO: Possibly ignore data that is too large. + UIImage *_Nullable image = self.quotedMessage.thumbnailImage; + // TODO: Possibly ignore images that are too large. + return image; } - (UIImageView *)imageViewForImage:(UIImage *)image diff --git a/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.h b/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.h index b1d62b65e..f95e5c315 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.h +++ b/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.h @@ -4,7 +4,7 @@ NS_ASSUME_NONNULL_BEGIN -@class OWSQuotedReplyDraft; +@class OWSQuotedReplyModel; @class SignalAttachment; @protocol ConversationInputToolbarDelegate @@ -58,7 +58,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)cancelVoiceMemoIfNecessary; -@property (nonatomic, nullable) OWSQuotedReplyDraft *quotedReplyDraft; +@property (nonatomic, nullable) OWSQuotedReplyModel *quotedReplyDraft; @end diff --git a/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m b/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m index d7325096f..04e7439db 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m @@ -250,7 +250,7 @@ static const CGFloat ConversationInputToolbarBorderViewHeight = 0.5; [self ensureContentConstraints]; } -- (void)setQuotedReplyDraft:(nullable OWSQuotedReplyDraft *)quotedReplyDraft +- (void)setQuotedReplyDraft:(nullable OWSQuotedReplyModel *)quotedReplyDraft { if (quotedReplyDraft == _quotedReplyDraft) { return; diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index deec382fd..9210f4367 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -2149,12 +2149,12 @@ typedef enum : NSUInteger { return; } - __block OWSQuotedReplyDraft *quotedReplyDraft; + __block OWSQuotedReplyModel *quotedReplyDraft; [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { quotedReplyDraft = [OWSMessageUtils quotedReplyDraftForMessage:message transaction:transaction]; }]; - if (![quotedReplyDraft isKindOfClass:[OWSQuotedReplyDraft class]]) { + if (![quotedReplyDraft isKindOfClass:[OWSQuotedReplyModel class]]) { OWSFail(@"%@ unexpected quotedMessage: %@", self.logTag, quotedReplyDraft.class); return; } diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h index 073249231..7defbbea1 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h @@ -26,6 +26,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType); @class ConversationViewCell; @class DisplayableText; @class OWSAudioMessageView; +@class OWSQuotedReplyModel; @class TSAttachmentPointer; @class TSAttachmentStream; @class TSInteraction; @@ -41,11 +42,13 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType); @interface ConversationViewItem : NSObject @property (nonatomic, readonly) TSInteraction *interaction; +@property (nonatomic, readonly, nullable) OWSQuotedReplyModel *quotedReply; @property (nonatomic, readonly) BOOL isGroupThread; @property (nonatomic, readonly) BOOL hasBodyText; +// TODO drive these off of the quotedReply? @property (nonatomic, readonly) BOOL isQuotedReply; @property (nonatomic, readonly) BOOL hasQuotedAttachment; @property (nonatomic, readonly) BOOL hasQuotedText; diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m index b64d95f7b..58fd731d7 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m @@ -57,8 +57,9 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) @property (nonatomic) OWSMessageCellType messageCellType; @property (nonatomic, nullable) DisplayableText *displayableBodyText; @property (nonatomic, nullable) DisplayableText *displayableQuotedText; -@property (nonatomic, nullable) NSString *quotedAttachmentMimetype; -@property (nonatomic, nullable) NSString *quotedRecipientId; +@property (nonatomic, nullable) OWSQuotedReplyModel *quotedReply; +@property (nonatomic, readonly, nullable) NSString *quotedAttachmentMimetype; +@property (nonatomic, readonly, nullable) NSString *quotedRecipientId; @property (nonatomic, nullable) TSAttachmentStream *attachmentStream; @property (nonatomic, nullable) TSAttachmentPointer *attachmentPointer; @property (nonatomic) CGSize mediaSize; @@ -101,10 +102,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) self.attachmentStream = nil; self.attachmentPointer = nil; self.mediaSize = CGSizeZero; - self.displayableQuotedText = nil; - self.quotedRecipientId = nil; - self.quotedAttachmentMimetype = nil; + self.quotedReply = nil; [self clearCachedLayoutState]; @@ -471,17 +470,29 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) self.displayableBodyText = [[DisplayableText alloc] initWithFullText:@"" displayText:@"" isTextTruncated:NO]; } - if (message.quotedMessage && message.quotedMessage.authorId.length > 0 - && (message.quotedMessage.body.length > 0 || message.quotedMessage.contentType.length > 0)) { - if (message.quotedMessage.body.length > 0) { + if (message.quotedMessage) { + self.quotedReply = + [[OWSQuotedReplyModel alloc] initWithQuotedMessage:message.quotedMessage transaction:transaction]; + + // TODO move this to OWSQuotedReplyModel? + if (self.quotedReply.body.length > 0) { self.displayableQuotedText = - [self displayableQuotedTextForText:message.quotedMessage.body interactionId:message.uniqueId]; + [self displayableQuotedTextForText:self.quotedReply.body interactionId:message.uniqueId]; } - self.quotedAttachmentMimetype = message.quotedMessage.contentType; - self.quotedRecipientId = message.quotedMessage.authorId; } } +// TODO converge naming +- (nullable NSString *)quotedAttachmentMimetype +{ + return self.quotedReply.contentType; +} + +- (nullable NSString *)quotedRecipientId +{ + return self.quotedReply.authorId; +} + - (OWSMessageCellType)messageCellType { OWSAssertIsOnMainThread(); diff --git a/Signal/src/views/QuotedReplyPreview.swift b/Signal/src/views/QuotedReplyPreview.swift index 893c2ce4e..399ec1e14 100644 --- a/Signal/src/views/QuotedReplyPreview.swift +++ b/Signal/src/views/QuotedReplyPreview.swift @@ -17,7 +17,7 @@ class QuotedReplyPreview: UIView { fatalError("init(coder:) has not been implemented") } - init(quotedReplyDraft: OWSQuotedReplyDraft) { + init(quotedReplyDraft: OWSQuotedReplyModel) { super.init(frame: .zero) let isQuotingSelf = quotedReplyDraft.authorId == TSAccountManager.localNumber() diff --git a/SignalMessaging/utils/ThreadUtil.h b/SignalMessaging/utils/ThreadUtil.h index 85ccf8406..72fe29f5a 100644 --- a/SignalMessaging/utils/ThreadUtil.h +++ b/SignalMessaging/utils/ThreadUtil.h @@ -48,6 +48,7 @@ NS_ASSUME_NONNULL_BEGIN @interface ThreadUtil : NSObject +// TODO should these all take the quotedMessageViewModel? + (TSOutgoingMessage *)sendMessageWithText:(NSString *)text inThread:(TSThread *)thread quotedMessage:(nullable TSQuotedMessage *)quotedMessage diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m index b28da12cb..6883fe452 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m @@ -480,12 +480,11 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec quotedAttachmentBuilder.contentType = attachment.contentType; quotedAttachmentBuilder.fileName = attachment.sourceFilename; + if (attachment.thumbnailAttachmentId) { + quotedAttachmentBuilder.thumbnail = + [self buildProtoForAttachmentId:attachment.thumbnailAttachmentId]; + } - // FIXME handle thumbnail uploading. The proto changes for this are up in the air. -// if (attachment.thumbnailAttachmentId) { -// quotedAttachmentBuilder.thumbnail = [self buildProtoForAttachmentId:attachment.attachmentThumbnailId]; -// } - [quoteBuilder addAttachments:[quotedAttachmentBuilder build]]; } } @@ -522,6 +521,19 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec return !self.hasSyncedTranscript; } +- (OWSSignalServiceProtosAttachmentPointer *)buildProtoForAttachmentId:(NSString *)attachmentId +{ + OWSAssert(attachmentId.length > 0); + + TSAttachment *attachment = [TSAttachmentStream fetchObjectWithUniqueID:attachmentId]; + if (![attachment isKindOfClass:[TSAttachmentStream class]]) { + DDLogError(@"Unexpected type for attachment builder: %@", attachment); + return nil; + } + TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment; + return [self buildProtoForAttachmentStream:attachmentStream filename:attachmentStream.sourceFilename]; +} + - (OWSSignalServiceProtosAttachmentPointer *)buildProtoForAttachmentId:(NSString *)attachmentId filename:(nullable NSString *)filename { diff --git a/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.h b/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.h index 0b3a83b01..caab5b77a 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.h +++ b/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.h @@ -11,7 +11,8 @@ NS_ASSUME_NONNULL_BEGIN @class TSAttachmentStream; @class TSQuotedMessage; -@interface OWSQuotedReplyDraft : NSObject +// View model which has already fetched any attachments. +@interface OWSQuotedReplyModel : NSObject @property (nonatomic, readonly) uint64_t timestamp; @property (nonatomic, readonly) NSString *authorId; @@ -35,6 +36,9 @@ NS_ASSUME_NONNULL_BEGIN body:(NSString *_Nullable)body attachmentStream:(nullable TSAttachmentStream *)attachment; +- (instancetype)initWithQuotedMessage:(TSQuotedMessage *)quotedMessage + transaction:(YapDatabaseReadTransaction *)transaction; + - (TSQuotedMessage *)buildQuotedMessage; @end @@ -43,8 +47,18 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly, nullable) NSString *contentType; @property (nonatomic, readonly, nullable) NSString *sourceFilename; + +// This is only set when sending a new attachment so we have a way +// 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; +// This is set once we've persisted a thumbnail +@property (atomic, nullable) NSString *thumbnailAttachmentId; + - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithAttachmentId:(nullable NSString *)attachmentId @@ -73,11 +87,13 @@ NS_ASSUME_NONNULL_BEGIN - (nullable NSString *)sourceFilename; @property (atomic, readonly) NSArray *quotedAttachments; -//- (void)addAttachment:(TSAttachmentStream *)attachment; -- (BOOL)hasAttachments; -- (nullable TSAttachment *)firstAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction; -- (nullable UIImage *)thumbnailImageWithTransaction:(YapDatabaseReadTransaction *)transaction; + +- (NSArray *)fetchAttachmentsWithTransaction:(YapDatabaseReadTransaction *)transaction; + +// Before sending, persist a thumbnail attachment derived from the quoted attachment +- (NSArray *)createThumbnailAttachmentsIfNecessaryWithTransaction: + (YapDatabaseReadWriteTransaction *)transaction; - (instancetype)init NS_UNAVAILABLE; diff --git a/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.m b/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.m index b0d1391fb..a91630ae9 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.m @@ -40,7 +40,7 @@ NS_ASSUME_NONNULL_BEGIN @end // View Model which has already fetched any thumbnail attachment. -@implementation OWSQuotedReplyDraft +@implementation OWSQuotedReplyModel // This is a MIME type. // @@ -78,6 +78,24 @@ NS_ASSUME_NONNULL_BEGIN return self; } +- (instancetype)initWithQuotedMessage:(TSQuotedMessage *)quotedMessage + transaction:(YapDatabaseReadTransaction *)transaction +{ + TSAttachment *attachment = + [TSAttachment fetchObjectWithUniqueID:quotedMessage.quotedAttachments.firstObject.attachmentId + transaction:transaction]; + + TSAttachmentStream *attachmentStream; + if ([attachment isKindOfClass:[TSAttachmentStream class]]) { + attachmentStream = (TSAttachmentStream *)attachment; + } + + return [self initWithTimestamp:quotedMessage.timestamp + authorId:quotedMessage.authorId + body:quotedMessage.body + attachmentStream:attachmentStream]; +} + - (TSQuotedMessage *)buildQuotedMessage { NSArray *attachments = self.attachmentStream ? @[ self.attachmentStream ] : @[]; @@ -167,6 +185,17 @@ NS_ASSUME_NONNULL_BEGIN return firstAttachment.sourceFilename; } +//- (NSArray *)fetchThumbnailAttachmentsWithTransaction:(YapDatabaseReadTransaction *)transaction +//{ +// NSMutableArray *attachments = [NSMutableArray new]; +// +// for (OWSAttachmentInfo *attachmentInfo in self.quotedAttachments) { +// TSAttachment *attachment = [TSAttachment fetchObjectWithUniqueID:attachmentInfo.attachmentId +// transaction:transaction]; +// +// } +//} + #pragma mark - Thumbnail //- (nullable TSAttachment *)firstAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction @@ -212,40 +241,49 @@ NS_ASSUME_NONNULL_BEGIN // //} // -- (void)createThumbnailAttachmentIfNecessaryWithTransaction:(YapDatabaseReadWriteTransaction *)transaction +- (NSArray *)createThumbnailAttachmentsIfNecessaryWithTransaction: + (YapDatabaseReadWriteTransaction *)transaction { -// for (OWSAttachmentInfo *info in self.quotedAttachments) { -// // TODO should we just cach an optional TSAttachment on the info? -// OWSAssert(info.attachmentId); -// TSAttachment *attachment = [TSAttachment fetchObjectWithUniqueID:info.attachmentId transaction:transaction]; -// if (![attachment isKindOfClass:[TSAttachmentStream class]]) { -// return; -// } -// -// TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachmentStream; -// NSData *thumbnailData = attachmentStream.thumbnailData; -// // Only some media types have thumbnails -// if (thumbnailData) { -// // Copy the thumbnail to a new attachment. -// NSString *thumbnailName = -// [NSString stringWithFormat:@"quoted-thumbnail-%@", attachmentStream.sourceFilename]; -// TSAttachmentStream *thumbnailAttachment = -// [[TSAttachmentStream alloc] initWithContentType:OWSMimeTypeJpeg -// byteCount:attachmentStream.byteCount -// sourceFilename:thumbnailName]; -// -// NSError *error; -// [thumbnailAttachment writeData:thumbnailData error:&error]; -// if (error) { -// DDLogError(@"%@ Couldn't copy attachment data for message sent to self: %@.", self.logTag, error); -// } else { -// [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { -// [thumbnailAttachment saveWithTransaction:transaction]; -// quotedMessage.attachments = [message saveWithTransaction:transaction]; -// }]; -// } -// } -// } + NSMutableArray *thumbnailAttachments = [NSMutableArray new]; + + for (OWSAttachmentInfo *info in self.quotedAttachments) { + + OWSAssert(info.attachmentId); + TSAttachment *attachment = [TSAttachment fetchObjectWithUniqueID:info.attachmentId transaction:transaction]; + if (![attachment isKindOfClass:[TSAttachmentStream class]]) { + continue; + } + + TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment; + NSData *thumbnailData = attachmentStream.thumbnailData; + // Only some media types have thumbnails + if (thumbnailData) { + // Copy the thumbnail to a new attachment. + NSString *thumbnailName = + [NSString stringWithFormat:@"quoted-thumbnail-%@", attachmentStream.sourceFilename]; + TSAttachmentStream *thumbnailAttachment = + [[TSAttachmentStream alloc] initWithContentType:@"image/jpeg" + byteCount:attachmentStream.byteCount + sourceFilename:thumbnailName]; + + NSError *error; + [thumbnailAttachment writeData:thumbnailData error:&error]; + if (error) { + DDLogError(@"%@ Couldn't copy attachment data for message sent to self: %@.", self.logTag, error); + } else { + [thumbnailAttachment saveWithTransaction:transaction]; + info.thumbnailAttachmentId = thumbnailAttachment.uniqueId; + [thumbnailAttachments addObject:thumbnailAttachment]; + } + } + } + + if (thumbnailAttachments.count > 0) { + // Save to record any self.quotedAttachments[].thumbnailAttachmentId + [self saveWithTransaction:transaction]; + } + + return [thumbnailAttachments copy]; } @end diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index 7cbda4151..d0fd4eadf 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -324,28 +324,32 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; [sendingQueue addOperation:uploadAttachmentOperation]; } - // - // if (message.quotedMessage) { - // - // // TODO do we want a different thumbnail size for quotes vs the gallery? This seems reasonable, - // // and has the advantage of already having been generated. - // __block TSAttachmentStream *attachment; - // [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - // [message.quotedMessage createThumbnailAttachmentIfNecessaryWithTransaction:transaction]; - // attachment = (TSAttachmentStream *)[message.quotedMessage - // thumbnailAttachmentWithTransaction:transaction]; - // }]; - // - // if (attachment) { - // OWSUploadOperation *uploadQuoteThumbnailOperation = - // [[OWSUploadOperation alloc] initWithAttachmentId:thumbnailAttachment.uniqueId - // dbConnection:self.dbConnection]; - // - // // TODO put attachment uploads on a (lowly) concurrent queue - // [sendMessageOperation addDependency:uploadQuoteThumbnailOperation]; - // [sendingQueue addOperation:uploadQuoteThumbnailOperation]; - // } - // } + + if (message.quotedMessage) { + + // TODO do we want a different thumbnail size for quotes vs the gallery? This seems reasonable, + // and has the advantage of already having been generated. + __block NSArray *thumbnailAttachments; + [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { + thumbnailAttachments = + [message.quotedMessage createThumbnailAttachmentsIfNecessaryWithTransaction:transaction]; + }]; + + // Though we currently only ever expect at most one thumbnail, the proto data model + // suggests this could change. The logic is intended to work with multiple, but + // if we ever actually want to send multiple, we should do more testing. + OWSAssert(thumbnailAttachments.count <= 1); + + for (TSAttachmentStream *thumbnailAttachment in thumbnailAttachments) { + OWSUploadOperation *uploadQuoteThumbnailOperation = + [[OWSUploadOperation alloc] initWithAttachmentId:thumbnailAttachment.uniqueId + dbConnection:self.dbConnection]; + + // TODO put attachment uploads on a (lowly) concurrent queue + [sendMessageOperation addDependency:uploadQuoteThumbnailOperation]; + [sendingQueue addOperation:uploadQuoteThumbnailOperation]; + } + } [sendingQueue addOperation:sendMessageOperation]; }); diff --git a/SignalServiceKit/src/Messages/OWSMessageUtils.h b/SignalServiceKit/src/Messages/OWSMessageUtils.h index 61a6987d2..5691dca35 100644 --- a/SignalServiceKit/src/Messages/OWSMessageUtils.h +++ b/SignalServiceKit/src/Messages/OWSMessageUtils.h @@ -4,7 +4,7 @@ NS_ASSUME_NONNULL_BEGIN -@class OWSQuotedReplyDraft; +@class OWSQuotedReplyModel; @class TSMessage; @class TSThread; @class YapDatabaseReadTransaction; @@ -20,7 +20,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)updateApplicationBadgeCount; -+ (nullable OWSQuotedReplyDraft *)quotedReplyDraftForMessage:(TSMessage *)message ++ (nullable OWSQuotedReplyModel *)quotedReplyDraftForMessage:(TSMessage *)message transaction:(YapDatabaseReadTransaction *)transaction; @end diff --git a/SignalServiceKit/src/Messages/OWSMessageUtils.m b/SignalServiceKit/src/Messages/OWSMessageUtils.m index 08a06ea2f..6c7fb49c0 100644 --- a/SignalServiceKit/src/Messages/OWSMessageUtils.m +++ b/SignalServiceKit/src/Messages/OWSMessageUtils.m @@ -104,7 +104,7 @@ NS_ASSUME_NONNULL_BEGIN return numberOfItems; } -+ (nullable OWSQuotedReplyDraft *)quotedReplyDraftForMessage:(TSMessage *)message ++ (nullable OWSQuotedReplyModel *)quotedReplyDraftForMessage:(TSMessage *)message transaction:(YapDatabaseReadTransaction *)transaction; { OWSAssert(message); @@ -127,7 +127,7 @@ NS_ASSUME_NONNULL_BEGIN return [self quotedReplyDraftForMessage:message authorId:authorId thread:thread transaction:transaction]; } -+ (nullable OWSQuotedReplyDraft *)quotedReplyDraftForMessage:(TSMessage *)message ++ (nullable OWSQuotedReplyModel *)quotedReplyDraftForMessage:(TSMessage *)message authorId:(NSString *)authorId thread:(TSThread *)thread transaction:(YapDatabaseReadTransaction *)transaction @@ -198,7 +198,7 @@ NS_ASSUME_NONNULL_BEGIN return nil; } - return [[OWSQuotedReplyDraft alloc] initWithTimestamp:timestamp + return [[OWSQuotedReplyModel alloc] initWithTimestamp:timestamp authorId:authorId body:quotedText attachmentStream:quotedAttachment];