New downloading progress view (#2006)
Replace previous "scary" warning-style attachment notifications with something less alarming. Includes file name and file type emoji when discernable. // FREEBIE
This commit is contained in:
parent
67e94e2b55
commit
d9e3e87735
|
@ -138,7 +138,7 @@ CHECKOUT OPTIONS:
|
|||
:commit: 7054e4b13ee5bcd6d524adb6dc9a726e8c466308
|
||||
:git: https://github.com/WhisperSystems/JSQMessagesViewController.git
|
||||
SignalServiceKit:
|
||||
:commit: 1032588da1b3c8a006110b1cbd024bda5ff79f2c
|
||||
:commit: 1fe093074077a686931acf527e0d8255ea73a22d
|
||||
:git: https://github.com/WhisperSystems/SignalServiceKit.git
|
||||
SocketRocket:
|
||||
:commit: 877ac7438be3ad0b45ef5ca3969574e4b97112bf
|
||||
|
|
|
@ -86,6 +86,8 @@
|
|||
452C468F1E427E200087B011 /* OutboundCallInitiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452C468E1E427E200087B011 /* OutboundCallInitiator.swift */; };
|
||||
452C46901E427E200087B011 /* OutboundCallInitiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452C468E1E427E200087B011 /* OutboundCallInitiator.swift */; };
|
||||
452D1EE81DCA90D100A57EC4 /* MesssagesBubblesSizeCalculatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452D1EE71DCA90D100A57EC4 /* MesssagesBubblesSizeCalculatorTest.swift */; };
|
||||
452EA0971EA662330078744B /* AttachmentPointerAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452EA0961EA662330078744B /* AttachmentPointerAdapter.swift */; };
|
||||
452EA09E1EA7ABE00078744B /* AttachmentPointerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452EA09D1EA7ABE00078744B /* AttachmentPointerView.swift */; };
|
||||
452ECA4D1E087E7200E2F016 /* MessageFetcherJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452ECA4C1E087E7200E2F016 /* MessageFetcherJob.swift */; };
|
||||
452ECA4E1E087E7200E2F016 /* MessageFetcherJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452ECA4C1E087E7200E2F016 /* MessageFetcherJob.swift */; };
|
||||
4531C9C41DD8E6D800F08304 /* JSQMessagesCollectionViewCell+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = 4531C9C31DD8E6D800F08304 /* JSQMessagesCollectionViewCell+OWS.m */; };
|
||||
|
@ -460,6 +462,8 @@
|
|||
4526BD481CA61C8D00166BC8 /* OWSMessageEditing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageEditing.h; sourceTree = "<group>"; };
|
||||
452C468E1E427E200087B011 /* OutboundCallInitiator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutboundCallInitiator.swift; sourceTree = "<group>"; };
|
||||
452D1EE71DCA90D100A57EC4 /* MesssagesBubblesSizeCalculatorTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MesssagesBubblesSizeCalculatorTest.swift; path = Models/MesssagesBubblesSizeCalculatorTest.swift; sourceTree = "<group>"; };
|
||||
452EA0961EA662330078744B /* AttachmentPointerAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentPointerAdapter.swift; sourceTree = "<group>"; };
|
||||
452EA09D1EA7ABE00078744B /* AttachmentPointerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentPointerView.swift; sourceTree = "<group>"; };
|
||||
452ECA4C1E087E7200E2F016 /* MessageFetcherJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MessageFetcherJob.swift; path = Jobs/MessageFetcherJob.swift; sourceTree = "<group>"; };
|
||||
4531C9C21DD8E6D800F08304 /* JSQMessagesCollectionViewCell+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JSQMessagesCollectionViewCell+OWS.h"; sourceTree = "<group>"; };
|
||||
4531C9C31DD8E6D800F08304 /* JSQMessagesCollectionViewCell+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "JSQMessagesCollectionViewCell+OWS.m"; sourceTree = "<group>"; };
|
||||
|
@ -1265,6 +1269,7 @@
|
|||
45F2B1961D9CA207000D2C69 /* OWSOutgoingMessageCollectionViewCell.xib */,
|
||||
34330AA11E79686200DF2FB9 /* OWSProgressView.h */,
|
||||
34330AA21E79686200DF2FB9 /* OWSProgressView.m */,
|
||||
452EA09D1EA7ABE00078744B /* AttachmentPointerView.swift */,
|
||||
);
|
||||
name = Views;
|
||||
path = views;
|
||||
|
@ -1311,6 +1316,7 @@
|
|||
B6A3EB4A1A423B3800B2236B /* TSPhotoAdapter.m */,
|
||||
A5E9D4BA1A65FAD800E4481C /* TSVideoAttachmentAdapter.h */,
|
||||
A5E9D4B91A65FAD800E4481C /* TSVideoAttachmentAdapter.m */,
|
||||
452EA0961EA662330078744B /* AttachmentPointerAdapter.swift */,
|
||||
);
|
||||
name = TSMessageAdapters;
|
||||
path = TSMessageAdapaters;
|
||||
|
@ -1989,6 +1995,7 @@
|
|||
450873C31D9D5149006B54F2 /* OWSExpirationTimerView.m in Sources */,
|
||||
B6258B331C29E2E60014138E /* NotificationsManager.m in Sources */,
|
||||
34B3F87B1E8DF1700035BE1A /* ExperienceUpgradesPageViewController.swift in Sources */,
|
||||
452EA09E1EA7ABE00078744B /* AttachmentPointerView.swift in Sources */,
|
||||
45666EC91D994C0D008FE134 /* OWSGroupAvatarBuilder.m in Sources */,
|
||||
34B3F87A1E8DF1700035BE1A /* DebugUITableViewController.m in Sources */,
|
||||
34B3F87C1E8DF1700035BE1A /* FingerprintViewController.m in Sources */,
|
||||
|
@ -2001,6 +2008,7 @@
|
|||
45C681C61D305C9E0050903A /* OWSDisplayedMessageCollectionViewCell.m in Sources */,
|
||||
34B3F8861E8DF1700035BE1A /* NotificationSettingsOptionsViewController.m in Sources */,
|
||||
452ECA4D1E087E7200E2F016 /* MessageFetcherJob.swift in Sources */,
|
||||
452EA0971EA662330078744B /* AttachmentPointerAdapter.swift in Sources */,
|
||||
34DFCB851E8E04B500053165 /* AddToBlockListViewController.m in Sources */,
|
||||
34B3F8321E8DF11D0035BE1A /* GroupContactsResult.m in Sources */,
|
||||
45855F371D9498A40084F340 /* OWSContactAvatarBuilder.m in Sources */,
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
/**
|
||||
* Represents a download-in-progress
|
||||
*/
|
||||
class AttachmentPointerAdapter: JSQMediaItem, OWSMessageEditing {
|
||||
|
||||
let TAG = "[AttachmentPointerAdapter]"
|
||||
let isIncoming: Bool
|
||||
let attachmentPointer: TSAttachmentPointer
|
||||
var cachedView: UIView?
|
||||
var attachmentPointerView: AttachmentPointerView?
|
||||
|
||||
required init(attachmentPointer: TSAttachmentPointer, isIncoming: Bool) {
|
||||
self.isIncoming = isIncoming
|
||||
self.attachmentPointer = attachmentPointer
|
||||
super.init(maskAsOutgoing: !isIncoming)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
assertionFailure("init(coder:) has not been implemented")
|
||||
self.isIncoming = true
|
||||
self.attachmentPointer = TSAttachmentPointer()
|
||||
super.init(coder: aDecoder)
|
||||
}
|
||||
|
||||
func canPerformAction(_ action: Selector) -> Bool {
|
||||
// No actions can be performed on a downloading attachment.
|
||||
return false
|
||||
}
|
||||
|
||||
func performAction(_ action: Selector) {
|
||||
// Should not get here, as you can't perform any actions on a downloading attachment.
|
||||
Logger.error("\(TAG) unexpectedly trying to perform action: \(action) on downloading attachment.")
|
||||
assertionFailure()
|
||||
}
|
||||
|
||||
override func mediaViewDisplaySize() -> CGSize {
|
||||
return CGSize(width: 200, height: 90)
|
||||
}
|
||||
|
||||
override func mediaView() -> UIView? {
|
||||
guard self.cachedView == nil else {
|
||||
return self.cachedView
|
||||
}
|
||||
|
||||
let frame = CGRect(origin: CGPoint.zero, size: self.mediaViewDisplaySize())
|
||||
let view = UIView(frame: frame)
|
||||
self.cachedView = view
|
||||
|
||||
JSQMessagesMediaViewBubbleImageMasker.applyBubbleImageMask(toMediaView: view, isOutgoing:!isIncoming)
|
||||
|
||||
view.isUserInteractionEnabled = false
|
||||
view.clipsToBounds = true
|
||||
|
||||
let attachmentPointerView = AttachmentPointerView(attachmentPointer: attachmentPointer, isIncoming: self.isIncoming)
|
||||
self.attachmentPointerView = attachmentPointerView
|
||||
view.addSubview(attachmentPointerView)
|
||||
|
||||
attachmentPointerView.autoPinWidthToSuperview(withMargin: 20)
|
||||
attachmentPointerView.autoVCenterInSuperview()
|
||||
|
||||
switch attachmentPointer.state {
|
||||
case .downloading:
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(attachmentDownloadProgress), name: NSNotification.Name.attachmentDownloadProgress, object: nil)
|
||||
view.backgroundColor = isIncoming ? UIColor.jsq_messageBubbleLightGray() : UIColor.ows_fadedBlue()
|
||||
case .enqueued:
|
||||
view.backgroundColor = isIncoming ? UIColor.jsq_messageBubbleLightGray() : UIColor.ows_fadedBlue()
|
||||
case .failed:
|
||||
view.backgroundColor = UIColor.gray
|
||||
}
|
||||
|
||||
return cachedView
|
||||
}
|
||||
|
||||
func attachmentDownloadProgress(_ notification: NSNotification) {
|
||||
guard let attachmentPointerView = self.attachmentPointerView else {
|
||||
Logger.error("\(TAG) downloading view was unexpectedly nil for notification: \(notification)")
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
guard let userInfo = notification.userInfo else {
|
||||
Logger.error("\(TAG) user info was unexpectedly nil for notification: \(notification)")
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
guard let progress = userInfo[kAttachmentDownloadProgressKey] as? CGFloat else {
|
||||
Logger.error("\(TAG) missing progress measure for notification user info: \(userInfo)")
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
guard let attachmentId = userInfo[kAttachmentDownloadAttachmentIDKey] as? String else {
|
||||
Logger.error("\(TAG) missing attachmentId for notification user info: \(userInfo)")
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
if (self.attachmentPointer.uniqueId == attachmentId) {
|
||||
attachmentPointerView.progress = progress
|
||||
}
|
||||
}
|
||||
}
|
|
@ -177,19 +177,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
}
|
||||
} else if ([attachment isKindOfClass:[TSAttachmentPointer class]]) {
|
||||
TSAttachmentPointer *pointer = (TSAttachmentPointer *)attachment;
|
||||
adapter.messageType = TSInfoMessageAdapter;
|
||||
|
||||
switch (pointer.state) {
|
||||
case TSAttachmentPointerStateEnqueued:
|
||||
adapter.messageBody = NSLocalizedString(@"ATTACHMENT_QUEUED", nil);
|
||||
break;
|
||||
case TSAttachmentPointerStateDownloading:
|
||||
adapter.messageBody = NSLocalizedString(@"ATTACHMENT_DOWNLOADING", nil);
|
||||
break;
|
||||
case TSAttachmentPointerStateFailed:
|
||||
adapter.messageBody = NSLocalizedString(@"ATTACHMENT_DOWNLOAD_FAILED", nil);
|
||||
break;
|
||||
}
|
||||
adapter.mediaItem =
|
||||
[[AttachmentPointerAdapter alloc] initWithAttachmentPointer:pointer
|
||||
isIncoming:isIncomingAttachment];
|
||||
} else {
|
||||
DDLogError(@"We retrieved an attachment that doesn't have a known type : %@",
|
||||
NSStringFromClass([attachment class]));
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
#import "OWSDispatch.h"
|
||||
#import "OWSError.h"
|
||||
#import "OWSLogger.h"
|
||||
#import "OWSMessageEditing.h"
|
||||
#import "OWSProgressView.h"
|
||||
#import "OWSWebRTCDataProtos.pb.h"
|
||||
#import "PrivacySettingsTableViewController.h"
|
||||
#import "PropertyListPreferences.h"
|
||||
|
@ -27,6 +29,9 @@
|
|||
#import "UIUtil.h"
|
||||
#import "UIView+OWS.h"
|
||||
#import "ViewControllerUtils.h"
|
||||
#import <JSQMessagesViewController/JSQMediaItem.h>
|
||||
#import <JSQMessagesViewController/JSQMessagesMediaViewBubbleImageMasker.h>
|
||||
#import <JSQMessagesViewController/UIColor+JSQMessages.h>
|
||||
#import <JSQSystemSoundPlayer.h>
|
||||
#import <PureLayout/PureLayout.h>
|
||||
#import <SignalServiceKit/Contact.h>
|
||||
|
@ -36,6 +41,7 @@
|
|||
#import <SignalServiceKit/NSData+Base64.h>
|
||||
#import <SignalServiceKit/NSDate+millisecondTimeStamp.h>
|
||||
#import <SignalServiceKit/OWSAcknowledgeMessageDeliveryRequest.h>
|
||||
#import <SignalServiceKit/OWSAttachmentsProcessor.h>
|
||||
#import <SignalServiceKit/OWSCallAnswerMessage.h>
|
||||
#import <SignalServiceKit/OWSCallBusyMessage.h>
|
||||
#import <SignalServiceKit/OWSCallHangupMessage.h>
|
||||
|
@ -53,6 +59,7 @@
|
|||
#import <SignalServiceKit/SignalRecipient.h>
|
||||
#import <SignalServiceKit/TSAccountManager.h>
|
||||
#import <SignalServiceKit/TSAttachment.h>
|
||||
#import <SignalServiceKit/TSAttachmentPointer.h>
|
||||
#import <SignalServiceKit/TSAttachmentStream.h>
|
||||
#import <SignalServiceKit/TSCall.h>
|
||||
#import <SignalServiceKit/TSContactThread.h>
|
||||
|
|
|
@ -1201,6 +1201,15 @@ typedef enum : NSUInteger {
|
|||
// JSQM does some setup in super method
|
||||
[super collectionView:collectionView shouldShowMenuForItemAtIndexPath:indexPath];
|
||||
|
||||
|
||||
// Don't show menu for in-progress downloads.
|
||||
// We don't want to give the user the wrong idea that deleting would "cancel" the download.
|
||||
id<OWSMessageData> message = [self messageAtIndexPath:indexPath];
|
||||
if ([message.media isKindOfClass:[AttachmentPointerAdapter class]]) {
|
||||
AttachmentPointerAdapter *attachmentPointerAdapter = (AttachmentPointerAdapter *)message.media;
|
||||
return attachmentPointerAdapter.attachmentPointer.state == TSAttachmentPointerStateFailed;
|
||||
}
|
||||
|
||||
// Super method returns false for media methods. We want menu for *all* items
|
||||
return YES;
|
||||
}
|
||||
|
@ -1856,15 +1865,32 @@ typedef enum : NSUInteger {
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if ([messageItem.media isKindOfClass:[AttachmentPointerAdapter class]]) {
|
||||
AttachmentPointerAdapter *attachmentPointerAdadpter = (AttachmentPointerAdapter *)messageItem.media;
|
||||
TSAttachmentPointer *attachmentPointer = attachmentPointerAdadpter.attachmentPointer;
|
||||
// Restart failed downloads
|
||||
if (attachmentPointer.state == TSAttachmentPointerStateFailed) {
|
||||
if (![interaction isKindOfClass:[TSMessage class]]) {
|
||||
DDLogError(@"%@ Expected attachment downloads from an instance of message, but found: %@", self.tag, interaction);
|
||||
OWSAssert(NO);
|
||||
return;
|
||||
}
|
||||
TSMessage *message = (TSMessage *)interaction;
|
||||
[self handleFailedDownloadTapForMessage:message attachmentPointer:attachmentPointer];
|
||||
} else {
|
||||
DDLogVerbose(@"%@ Ignoring tap for attachment pointer %@ with state %lu",
|
||||
self.tag,
|
||||
attachmentPointer,
|
||||
(unsigned long)attachmentPointer.state);
|
||||
}
|
||||
} else {
|
||||
DDLogDebug(@"%@ Unhandled tap on 'media item' with media: %@", self.tag, messageItem.media);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case TSErrorMessageAdapter:
|
||||
[self handleErrorMessageTap:(TSErrorMessage *)interaction];
|
||||
break;
|
||||
case TSInfoMessageAdapter:
|
||||
[self handleWarningTap:interaction];
|
||||
break;
|
||||
case TSCallAdapter:
|
||||
break;
|
||||
default:
|
||||
|
@ -1896,41 +1922,6 @@ typedef enum : NSUInteger {
|
|||
}
|
||||
}
|
||||
|
||||
- (void)handleWarningTap:(TSInteraction *)interaction
|
||||
{
|
||||
if ([interaction isKindOfClass:[TSIncomingMessage class]]) {
|
||||
TSIncomingMessage *message = (TSIncomingMessage *)interaction;
|
||||
|
||||
for (NSString *attachmentId in message.attachmentIds) {
|
||||
__block TSAttachment *attachment;
|
||||
|
||||
[self.editingDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
attachment = [TSAttachment fetchObjectWithUniqueID:attachmentId transaction:transaction];
|
||||
}];
|
||||
|
||||
if ([attachment isKindOfClass:[TSAttachmentPointer class]]) {
|
||||
TSAttachmentPointer *pointer = (TSAttachmentPointer *)attachment;
|
||||
|
||||
// FIXME possible for pointer to get stuck in isDownloading state if app is closed while downloading.
|
||||
// see: https://github.com/WhisperSystems/Signal-iOS/issues/1254
|
||||
if (pointer.state != TSAttachmentPointerStateDownloading) {
|
||||
OWSAttachmentsProcessor *processor =
|
||||
[[OWSAttachmentsProcessor alloc] initWithAttachmentPointer:pointer
|
||||
networkManager:self.networkManager];
|
||||
[processor fetchAttachmentsForMessage:message
|
||||
success:^(TSAttachmentStream *_Nonnull attachmentStream) {
|
||||
DDLogInfo(
|
||||
@"%@ Successfully redownloaded attachment in thread: %@", self.tag, message.thread);
|
||||
}
|
||||
failure:^(NSError *_Nonnull error) {
|
||||
DDLogWarn(@"%@ Failed to redownload message with error: %@", self.tag, error);
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// There's more than one way to exit the fullscreen video playback.
|
||||
// There's a done button, a "toggle fullscreen" button and I think
|
||||
// there's some gestures too. These fire slightly different notifications.
|
||||
|
@ -2042,6 +2033,50 @@ typedef enum : NSUInteger {
|
|||
|
||||
#pragma mark Bubble User Actions
|
||||
|
||||
- (void)handleFailedDownloadTapForMessage:(TSMessage *)message
|
||||
attachmentPointer:(TSAttachmentPointer *)attachmentPointer
|
||||
{
|
||||
UIAlertController *actionSheetController = [UIAlertController
|
||||
alertControllerWithTitle:NSLocalizedString(@"MESSAGES_VIEW_FAILED_DOWNLOAD_ACTIONSHEET_TITLE", comment
|
||||
: "Action sheet title after tapping on failed download.")
|
||||
message:nil
|
||||
preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
|
||||
UIAlertAction *dismissAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"TXT_CANCEL_TITLE", @"")
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:nil];
|
||||
[actionSheetController addAction:dismissAction];
|
||||
|
||||
UIAlertAction *deleteMessageAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"TXT_DELETE_TITLE", @"")
|
||||
style:UIAlertActionStyleDestructive
|
||||
handler:^(UIAlertAction *_Nonnull action) {
|
||||
[message remove];
|
||||
}];
|
||||
[actionSheetController addAction:deleteMessageAction];
|
||||
|
||||
UIAlertAction *resendMessageAction = [UIAlertAction
|
||||
actionWithTitle:NSLocalizedString(@"MESSAGES_VIEW_FAILED_DOWNLOAD_RETRY_ACTION", @"Action sheet button text")
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction *_Nonnull action) {
|
||||
OWSAttachmentsProcessor *processor =
|
||||
[[OWSAttachmentsProcessor alloc] initWithAttachmentPointer:attachmentPointer
|
||||
networkManager:self.networkManager];
|
||||
[processor fetchAttachmentsForMessage:message
|
||||
success:^(TSAttachmentStream *_Nonnull attachmentStream) {
|
||||
DDLogInfo(
|
||||
@"%@ Successfully redownloaded attachment in thread: %@", self.tag, message.thread);
|
||||
}
|
||||
failure:^(NSError *_Nonnull error) {
|
||||
DDLogWarn(@"%@ Failed to redownload message with error: %@", self.tag, error);
|
||||
}];
|
||||
}];
|
||||
|
||||
[actionSheetController addAction:resendMessageAction];
|
||||
|
||||
[self presentViewController:actionSheetController animated:YES completion:nil];
|
||||
}
|
||||
|
||||
|
||||
- (void)handleUnsentMessageTap:(TSOutgoingMessage *)message {
|
||||
UIAlertController *actionSheetController = [UIAlertController alertControllerWithTitle:message.mostRecentFailureText
|
||||
message:nil
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class AttachmentPointerView: UIView {
|
||||
|
||||
let TAG = "[AttachmentPointerView]"
|
||||
|
||||
let progressView = OWSProgressView()
|
||||
let nameLabel = UILabel()
|
||||
let statusLabel = UILabel()
|
||||
let isIncoming: Bool
|
||||
let filename: String
|
||||
let attachmentPointer: TSAttachmentPointer
|
||||
let genericFilename = NSLocalizedString("ATTACHMENT_DOWNLOADING_DEFAULT_ATTACHMENT_LABEL", comment: "Generic name label when downloading an attachment with no known name.")
|
||||
|
||||
var progress: CGFloat = 0 {
|
||||
didSet {
|
||||
self.progressView.progress = progress
|
||||
}
|
||||
}
|
||||
|
||||
required init(attachmentPointer: TSAttachmentPointer, isIncoming: Bool) {
|
||||
self.isIncoming = isIncoming
|
||||
self.attachmentPointer = attachmentPointer
|
||||
|
||||
let attachmentPointerFilename = attachmentPointer.filename
|
||||
if let filename = attachmentPointerFilename, !filename.isEmpty {
|
||||
self.filename = filename
|
||||
} else {
|
||||
self.filename = genericFilename
|
||||
}
|
||||
|
||||
super.init(frame: CGRect.zero)
|
||||
|
||||
createSubviews()
|
||||
updateViews()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
override init(frame: CGRect) {
|
||||
assertionFailure()
|
||||
// This initializer should never be called, but we assign some bogus values to keep the compiler happy.
|
||||
self.filename = genericFilename
|
||||
self.isIncoming = false
|
||||
self.attachmentPointer = TSAttachmentPointer()
|
||||
super.init(frame: frame)
|
||||
self.createSubviews()
|
||||
self.updateViews()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
assertionFailure()
|
||||
// This initializer should never be called, but we assign some bogus values to keep the compiler happy.
|
||||
self.filename = genericFilename
|
||||
self.isIncoming = false
|
||||
self.attachmentPointer = TSAttachmentPointer()
|
||||
super.init(coder: aDecoder)
|
||||
self.createSubviews()
|
||||
self.updateViews()
|
||||
}
|
||||
|
||||
func createSubviews() {
|
||||
self.addSubview(nameLabel)
|
||||
// truncate middle to be sure we include file extension
|
||||
nameLabel.lineBreakMode = .byTruncatingMiddle
|
||||
nameLabel.textAlignment = .center
|
||||
|
||||
nameLabel.textColor = self.textColor
|
||||
nameLabel.font = UIFont.ows_dynamicTypeBody()
|
||||
|
||||
nameLabel.autoPinWidthToSuperview()
|
||||
nameLabel.autoPinEdge(toSuperviewEdge: .top)
|
||||
|
||||
self.addSubview(progressView)
|
||||
progressView.autoPinWidthToSuperview()
|
||||
progressView.autoPinEdge(.top, to: .bottom, of: nameLabel, withOffset: 6)
|
||||
progressView.autoSetDimension(.height, toSize: 8)
|
||||
|
||||
self.addSubview(statusLabel)
|
||||
statusLabel.textAlignment = .center
|
||||
statusLabel.adjustsFontSizeToFitWidth = true
|
||||
|
||||
statusLabel.textColor = self.textColor
|
||||
statusLabel.font = UIFont.ows_footnote()
|
||||
|
||||
statusLabel.autoPinWidthToSuperview()
|
||||
statusLabel.autoPinEdge(.top, to: .bottom, of: progressView, withOffset: 4)
|
||||
statusLabel.autoPinEdge(toSuperviewEdge: .bottom)
|
||||
}
|
||||
|
||||
func emojiForContentType(_ contentType: String) -> String {
|
||||
if MIMETypeUtil.isImage(contentType) {
|
||||
return "📷"
|
||||
} else if MIMETypeUtil.isVideo(contentType) {
|
||||
return "📽"
|
||||
} else if MIMETypeUtil.isAudio(contentType) {
|
||||
return "📻"
|
||||
} else if MIMETypeUtil.isAnimated(contentType) {
|
||||
return "🎡"
|
||||
} else {
|
||||
// generic file
|
||||
return "📁"
|
||||
}
|
||||
}
|
||||
|
||||
func updateViews() {
|
||||
let emoji = self.emojiForContentType(self.attachmentPointer.contentType)
|
||||
nameLabel.text = "\(emoji) \(self.filename)"
|
||||
|
||||
statusLabel.text = {
|
||||
switch self.attachmentPointer.state {
|
||||
case .enqueued:
|
||||
return NSLocalizedString("ATTACHMENT_DOWNLOADING_STATUS_QUEUED", comment: "Status label when an attachment is enqueued, but hasn't yet started downloading")
|
||||
case .downloading:
|
||||
return NSLocalizedString("ATTACHMENT_DOWNLOADING_STATUS_IN_PROGRESS", comment: "Status label when an attachment is currently downloading")
|
||||
case .failed:
|
||||
return NSLocalizedString("ATTACHMENT_DOWNLOADING_STATUS_FAILED", comment: "Status label when an attachment download has failed.")
|
||||
}
|
||||
}()
|
||||
|
||||
if attachmentPointer.state == .downloading {
|
||||
progressView.isHidden = false
|
||||
} else {
|
||||
progressView.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
var textColor: UIColor {
|
||||
return self.isIncoming ? UIColor.darkText : UIColor.white
|
||||
}
|
||||
}
|
|
@ -61,11 +61,17 @@
|
|||
/* Label for 'send' button in the 'attachment approval' dialog. */
|
||||
"ATTACHMENT_APPROVAL_SEND_BUTTON" = "Send";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"ATTACHMENT_DOWNLOAD_FAILED" = "Attachment download failed, tap to retry.";
|
||||
/* Generic name label when downloading an attachment with no known name. */
|
||||
"ATTACHMENT_DOWNLOADING_DEFAULT_ATTACHMENT_LABEL" = "Attachment";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"ATTACHMENT_DOWNLOADING" = "Attachment is downloading";
|
||||
/* Status label when an attachment download has failed. */
|
||||
"ATTACHMENT_DOWNLOADING_STATUS_FAILED" = "Failed. Tap to retry.";
|
||||
|
||||
/* Status label when an attachment is currently downloading */
|
||||
"ATTACHMENT_DOWNLOADING_STATUS_IN_PROGRESS" = "Downloading...";
|
||||
|
||||
/* Status label when an attachment is enqueued, but hasn't yet started downloading */
|
||||
"ATTACHMENT_DOWNLOADING_STATUS_QUEUED" = "Queued";
|
||||
|
||||
/* The title of the 'attachment error' alert. */
|
||||
"ATTACHMENT_ERROR_ALERT_TITLE" = "Error Sending Attachment";
|
||||
|
@ -94,9 +100,6 @@
|
|||
/* Accessibility label for attaching photos */
|
||||
"ATTACHMENT_LABEL" = "Attachment";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"ATTACHMENT_QUEUED" = "New attachment queued for retrieval.";
|
||||
|
||||
/* An explanation of the consequences of blocking another user. */
|
||||
"BLOCK_BEHAVIOR_EXPLANATION" = "Blocked users will not be able to call you or send you messages.";
|
||||
|
||||
|
@ -607,6 +610,12 @@
|
|||
/* Indicates that this 1:1 conversation has been blocked. */
|
||||
"MESSAGES_VIEW_CONTACT_BLOCKED" = "You Blocked this User";
|
||||
|
||||
/* Action sheet title after tapping on failed download. */
|
||||
"MESSAGES_VIEW_FAILED_DOWNLOAD_ACTIONSHEET_TITLE" = "Download Failed.";
|
||||
|
||||
/* Action sheet button text */
|
||||
"MESSAGES_VIEW_FAILED_DOWNLOAD_RETRY_ACTION" = "Download Again";
|
||||
|
||||
/* Indicates that a single member of this group has been blocked. */
|
||||
"MESSAGES_VIEW_GROUP_1_MEMBER_BLOCKED" = "You Blocked 1 Member of this Group";
|
||||
|
||||
|
@ -818,7 +827,7 @@
|
|||
"REGISTER_CONTACTS_WELCOME" = "Welcome!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"REGISTER_FAILED_TRY_AGAIN" = "Try again";
|
||||
"REGISTER_FAILED_TRY_AGAIN" = "Try Again";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"REGISTER_RATE_LIMITING_BODY" = "You have tried too often. Please wait a minute before trying again.";
|
||||
|
@ -896,7 +905,7 @@
|
|||
"SECURE_SESSION_RESET" = "Secure session was reset.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"SEND_AGAIN_BUTTON" = "Send again";
|
||||
"SEND_AGAIN_BUTTON" = "Send Again";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"SEND_BUTTON_TITLE" = "Send";
|
||||
|
|
Loading…
Reference in New Issue