tap-to-retry failed thumbnail downloads

// FREEBIE
This commit is contained in:
Michael Kirk 2018-04-16 13:50:54 -04:00
parent 8716d6b7a6
commit 64ff4cd660
8 changed files with 171 additions and 42 deletions

View File

@ -5,10 +5,10 @@
NS_ASSUME_NONNULL_BEGIN
@class ConversationViewItem;
@class OWSQuotedReplyModel;
@class TSAttachmentPointer;
@class TSAttachmentStream;
@class TSOutgoingMessage;
@class TSQuotedMessage;
typedef NS_ENUM(NSUInteger, OWSMessageGestureLocation) {
// Message text, etc.
@ -37,7 +37,10 @@ typedef NS_ENUM(NSUInteger, OWSMessageGestureLocation) {
- (void)didTapFailedOutgoingMessage:(TSOutgoingMessage *)message;
- (void)didTapQuotedMessage:(ConversationViewItem *)viewItem quotedMessage:(TSQuotedMessage *)quotedMessage;
- (void)didTapConversationItem:(ConversationViewItem *)viewItem quotedReply:(OWSQuotedReplyModel *)quotedReply;
- (void)didTapConversationItem:(ConversationViewItem *)viewItem
quotedReply:(OWSQuotedReplyModel *)quotedReply
failedThumbnailDownloadAttachmentPointer:(TSAttachmentPointer *)attachmentPointer;
@end

View File

@ -17,7 +17,7 @@
NS_ASSUME_NONNULL_BEGIN
@interface OWSMessageBubbleView ()
@interface OWSMessageBubbleView () <OWSQuotedMessageViewDelegate>
@property (nonatomic) OWSBubbleView *bubbleView;
@ -272,6 +272,8 @@ NS_ASSUME_NONNULL_BEGIN
[OWSQuotedMessageView quotedMessageViewForConversation:self.viewItem.quotedReply
displayableQuotedText:displayableQuotedText
isOutgoing:isOutgoing];
quotedMessageView.delegate = self;
self.quotedMessageView = quotedMessageView;
[quotedMessageView createContents];
[self.bubbleView addSubview:quotedMessageView];
@ -1101,8 +1103,8 @@ NS_ASSUME_NONNULL_BEGIN
[self handleMediaTapGesture];
break;
case OWSMessageGestureLocation_QuotedReply:
if (self.message.quotedMessage) {
[self.delegate didTapQuotedMessage:self.viewItem quotedMessage:self.message.quotedMessage];
if (self.viewItem.quotedReply) {
[self.delegate didTapConversationItem:self.viewItem quotedReply:self.viewItem.quotedReply];
} else {
OWSFail(@"%@ Missing quoted message.", self.logTag);
}
@ -1198,6 +1200,14 @@ NS_ASSUME_NONNULL_BEGIN
return OWSMessageGestureLocation_Default;
}
- (void)didTapQuotedReply:(OWSQuotedReplyModel *)quotedReply
failedThumbnailDownloadAttachmentPointer:(TSAttachmentPointer *)attachmentPointer
{
[self.delegate didTapConversationItem:self.viewItem
quotedReply:quotedReply
failedThumbnailDownloadAttachmentPointer:attachmentPointer];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -7,10 +7,20 @@ NS_ASSUME_NONNULL_BEGIN
@class DisplayableText;
@class OWSBubbleStrokeView;
@class OWSQuotedReplyModel;
@class TSAttachmentPointer;
@class TSQuotedMessage;
@protocol OWSQuotedMessageViewDelegate
- (void)didTapQuotedReply:(OWSQuotedReplyModel *)quotedReply
failedThumbnailDownloadAttachmentPointer:(TSAttachmentPointer *)attachmentPointer;
@end
@interface OWSQuotedMessageView : UIView
@property (nonatomic, nullable, readonly) OWSBubbleStrokeView *boundsStrokeView;
@property (nonatomic, nullable, weak) id<OWSQuotedMessageViewDelegate> delegate;
- (instancetype)init NS_UNAVAILABLE;

View File

@ -116,7 +116,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssert(!self.boundsStrokeView);
self.backgroundColor = [UIColor whiteColor];
self.userInteractionEnabled = NO;
self.userInteractionEnabled = YES;
self.layoutMargins = UIEdgeInsetsZero;
self.clipsToBounds = YES;
@ -140,6 +140,27 @@ NS_ASSUME_NONNULL_BEGIN
quotedAttachmentView.layer.cornerRadius = 2.f;
quotedAttachmentView.clipsToBounds = YES;
quotedAttachmentView.backgroundColor = [UIColor whiteColor];
} else if (self.quotedMessage.thumbnailDownloadFailed) {
// TODO design review icon and color
UIImage *contentIcon =
[[UIImage imageNamed:@"btnRefresh--white"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
UIImageView *contentImageView = [self imageViewForImage:contentIcon];
contentImageView.contentMode = UIViewContentModeScaleAspectFit;
contentImageView.userInteractionEnabled = YES;
contentImageView.tintColor = UIColor.whiteColor;
quotedAttachmentView = [UIView containerView];
[quotedAttachmentView addSubview:contentImageView];
quotedAttachmentView.backgroundColor = self.highlightColor;
quotedAttachmentView.layer.cornerRadius = self.quotedAttachmentSize * 0.5f;
[contentImageView autoCenterInSuperview];
[contentImageView
autoSetDimensionsToSize:CGSizeMake(self.quotedAttachmentSize * 0.5f, self.quotedAttachmentSize * 0.5f)];
contentImageView.layer.minificationFilter = kCAFilterTrilinear;
contentImageView.layer.magnificationFilter = kCAFilterTrilinear;
UITapGestureRecognizer *tapGesture =
[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTapFailedThumbnailDownload:)];
[quotedAttachmentView addGestureRecognizer:tapGesture];
} else {
// TODO: This asset is wrong.
// TODO: There's a special asset for audio files.
@ -154,7 +175,7 @@ NS_ASSUME_NONNULL_BEGIN
autoSetDimensionsToSize:CGSizeMake(self.quotedAttachmentSize * 0.5f, self.quotedAttachmentSize * 0.5f)];
}
quotedAttachmentView.userInteractionEnabled = NO;
quotedAttachmentView.userInteractionEnabled = YES;
[self addSubview:quotedAttachmentView];
[quotedAttachmentView autoPinTrailingToSuperviewMarginWithInset:self.quotedContentHInset];
[quotedAttachmentView autoVCenterInSuperview];
@ -224,6 +245,24 @@ NS_ASSUME_NONNULL_BEGIN
}
}
- (void)didTapFailedThumbnailDownload:(UITapGestureRecognizer *)gestureRecognizer
{
DDLogDebug(@"%@ in didTapFailedThumbnailDownload", self.logTag);
if (!self.quotedMessage.thumbnailDownloadFailed) {
OWSFail(@"%@ in %s thumbnailDownloadFailed was unexpectedly false", self.logTag, __PRETTY_FUNCTION__);
return;
}
if (!self.quotedMessage.thumbnailAttachmentPointer) {
OWSFail(@"%@ in %s thumbnailAttachmentPointer was unexpectedly nil", self.logTag, __PRETTY_FUNCTION__);
return;
}
[self.delegate didTapQuotedReply:self.quotedMessage
failedThumbnailDownloadAttachmentPointer:self.quotedMessage.thumbnailAttachmentPointer];
}
- (nullable UIImage *)tryToLoadThumbnailImage
{
if (!self.hasQuotedAttachmentThumbnailImage) {

View File

@ -2125,13 +2125,51 @@ typedef enum : NSUInteger {
[self handleUnsentMessageTap:message];
}
- (void)didTapQuotedMessage:(ConversationViewItem *)viewItem quotedMessage:(TSQuotedMessage *)quotedMessage
- (void)didTapConversationItem:(ConversationViewItem *)viewItem
quotedReply:(OWSQuotedReplyModel *)quotedReply
failedThumbnailDownloadAttachmentPointer:(TSAttachmentPointer *)attachmentPointer
{
OWSAssertIsOnMainThread();
OWSAssert(viewItem);
OWSAssert(quotedMessage);
OWSAssert(quotedMessage.timestamp > 0);
OWSAssert(quotedMessage.authorId.length > 0);
OWSAssert(attachmentPointer);
TSMessage *message = (TSMessage *)viewItem.interaction;
if (![message isKindOfClass:[TSMessage class]]) {
OWSFail(@"%@ in %s message had unexpected class: %@", self.logTag, __PRETTY_FUNCTION__, message.class);
return;
}
OWSAttachmentsProcessor *processor =
[[OWSAttachmentsProcessor alloc] initWithAttachmentPointer:attachmentPointer
networkManager:self.networkManager];
[self.editingDatabaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[processor fetchAttachmentsForMessage:nil
transaction:transaction
success:^(TSAttachmentStream *_Nonnull attachmentStream) {
[self.editingDatabaseConnection
asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull postSuccessTransaction) {
[message setQuotedMessageThumbnailAttachmentStream:attachmentStream];
[message saveWithTransaction:postSuccessTransaction];
}];
}
failure:^(NSError *_Nonnull error) {
DDLogWarn(@"%@ Failed to redownload thumbnail with error: %@", self.logTag, error);
[self.editingDatabaseConnection
asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull postSuccessTransaction) {
[message touchWithTransaction:transaction];
}];
}];
}];
}
- (void)didTapConversationItem:(ConversationViewItem *)viewItem quotedMessage:(OWSQuotedReplyModel *)quotedReply
{
OWSAssertIsOnMainThread();
OWSAssert(viewItem);
OWSAssert(quotedReply);
OWSAssert(quotedReply.timestamp > 0);
OWSAssert(quotedReply.authorId.length > 0);
// We try to find the index of the item within the current thread's
// interactions that includes the "quoted interaction".
@ -2154,8 +2192,8 @@ typedef enum : NSUInteger {
__block NSNumber *_Nullable groupIndex = nil;
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
quotedInteraction = [ThreadUtil findInteractionInThreadByTimestamp:quotedMessage.timestamp
authorId:quotedMessage.authorId
quotedInteraction = [ThreadUtil findInteractionInThreadByTimestamp:quotedReply.timestamp
authorId:quotedReply.authorId
threadUniqueId:self.thread.uniqueId
transaction:transaction];
if (!quotedInteraction) {

View File

@ -655,7 +655,11 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
// no - op
}
func didTapQuotedMessage(_ viewItem: ConversationViewItem, quotedMessage: TSQuotedMessage) {
func didTapConversationItem(_ viewItem: ConversationViewItem, quotedReply: OWSQuotedReplyModel) {
// no - op
}
func didTapConversationItem(_ viewItem: ConversationViewItem, quotedReply: OWSQuotedReplyModel, failedThumbnailDownloadAttachmentPointer attachmentPointer: TSAttachmentPointer) {
// no - op
}

View File

@ -2,6 +2,7 @@
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
@class TSAttachmentPointer;
@class TSAttachmentStream;
@class TSMessage;
@class TSQuotedMessage;
@ -15,6 +16,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly) uint64_t timestamp;
@property (nonatomic, readonly) NSString *authorId;
@property (nonatomic, readonly, nullable) TSAttachmentStream *attachmentStream;
@property (nonatomic, readonly, nullable) TSAttachmentPointer *thumbnailAttachmentPointer;
@property (nonatomic, readonly) BOOL thumbnailDownloadFailed;
// This property should be set IFF we are quoting a text message
// or attachment with caption.
@ -29,11 +32,13 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly, nullable) NSString *sourceFilename;
@property (nonatomic, readonly, nullable) UIImage *thumbnailImage;
// Used for building an outgoing quoted reply preview, before it's sent
- (instancetype)initWithTimestamp:(uint64_t)timestamp
authorId:(NSString *)authorId
body:(NSString *_Nullable)body
attachmentStream:(nullable TSAttachmentStream *)attachment;
// Used for persisted quoted replies, both incoming and outgoing.
- (instancetype)initWithQuotedMessage:(TSQuotedMessage *)quotedMessage
transaction:(YapDatabaseReadTransaction *)transaction;

View File

@ -6,6 +6,7 @@
#import <SignalServiceKit/MIMETypeUtil.h>
#import <SignalServiceKit/OWSMessageSender.h>
#import <SignalServiceKit/TSAccountManager.h>
#import <SignalServiceKit/TSAttachmentPointer.h>
#import <SignalServiceKit/TSAttachmentStream.h>
#import <SignalServiceKit/TSIncomingMessage.h>
#import <SignalServiceKit/TSMessage.h>
@ -27,9 +28,51 @@
thumbnailImage:attachmentStream.thumbnailImage
contentType:attachmentStream.contentType
sourceFilename:attachmentStream.sourceFilename
attachmentStream:attachmentStream];
attachmentStream:attachmentStream
thumbnailAttachmentPointer:nil
thumbnailDownloadFailed:NO];
}
- (instancetype)initWithQuotedMessage:(TSQuotedMessage *)quotedMessage
transaction:(YapDatabaseReadTransaction *)transaction
{
OWSAssert(quotedMessage.quotedAttachments.count <= 1);
OWSAttachmentInfo *attachmentInfo = quotedMessage.quotedAttachments.firstObject;
BOOL thumbnailDownloadFailed = NO;
UIImage *_Nullable thumbnailImage;
TSAttachmentPointer *attachmentPointer;
if (attachmentInfo.thumbnailAttachmentStreamId) {
TSAttachment *attachment =
[TSAttachment fetchObjectWithUniqueID:attachmentInfo.thumbnailAttachmentStreamId transaction:transaction];
TSAttachmentStream *attachmentStream;
if ([attachment isKindOfClass:[TSAttachmentStream class]]) {
attachmentStream = (TSAttachmentStream *)attachment;
thumbnailImage = attachmentStream.image;
}
} else if (attachmentInfo.thumbnailAttachmentPointerId) {
// download failed, or hasn't completed yet.
TSAttachment *attachment = [TSAttachment fetchObjectWithUniqueID:attachmentInfo.thumbnailAttachmentPointerId transaction:transaction];
if ([attachment isKindOfClass:[TSAttachmentPointer class]]) {
attachmentPointer = (TSAttachmentPointer *)attachment;
if (attachmentPointer.state == TSAttachmentPointerStateFailed) {
thumbnailDownloadFailed = YES;
}
}
}
return [self initWithTimestamp:quotedMessage.timestamp
authorId:quotedMessage.authorId
body:quotedMessage.body
thumbnailImage:thumbnailImage
contentType:attachmentInfo.contentType
sourceFilename:attachmentInfo.sourceFilename
attachmentStream:nil
thumbnailAttachmentPointer:attachmentPointer
thumbnailDownloadFailed:thumbnailDownloadFailed];
}
- (instancetype)initWithTimestamp:(uint64_t)timestamp
authorId:(NSString *)authorId
@ -38,6 +81,8 @@
contentType:(nullable NSString *)contentType
sourceFilename:(nullable NSString *)sourceFilename
attachmentStream:(nullable TSAttachmentStream *)attachmentStream
thumbnailAttachmentPointer:(nullable TSAttachmentPointer *)thumbnailAttachmentPointer
thumbnailDownloadFailed:(BOOL)thumbnailDownloadFailed
{
self = [super init];
if (!self) {
@ -51,37 +96,12 @@
_contentType = contentType;
_sourceFilename = sourceFilename;
_attachmentStream = attachmentStream;
_thumbnailAttachmentPointer = thumbnailAttachmentPointer;
_thumbnailDownloadFailed = thumbnailDownloadFailed;
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 ] : @[];