mirror of https://github.com/oxen-io/session-ios
parent
1d4c0624be
commit
c56e8acc51
@ -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
|
@ -0,0 +1,197 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSQuotedReplyModel.h"
|
||||
#import <SignalServiceKit/MIMETypeUtil.h>
|
||||
#import <SignalServiceKit/OWSMessageSender.h>
|
||||
#import <SignalServiceKit/TSAccountManager.h>
|
||||
#import <SignalServiceKit/TSAttachmentStream.h>
|
||||
#import <SignalServiceKit/TSIncomingMessage.h>
|
||||
#import <SignalServiceKit/TSMessage.h>
|
||||
#import <SignalServiceKit/TSOutgoingMessage.h>
|
||||
#import <SignalServiceKit/TSQuotedMessage.h>
|
||||
#import <SignalServiceKit/TSThread.h>
|
||||
|
||||
// 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
|
Loading…
Reference in New Issue