mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Merge branch 'charlesmchen/multiSendSAE'
This commit is contained in:
commit
3ccf0a3c98
2
Pods
2
Pods
|
@ -1 +1 @@
|
|||
Subproject commit 7c5cd466ada8524ddea3b3d9b0fe2f734b882a04
|
||||
Subproject commit 24b0c88968ddd6f05dd12ced06e453fe4bfb206c
|
|
@ -20,6 +20,7 @@
|
|||
#import <SignalServiceKit/TSInvalidIdentityKeyReceivingErrorMessage.h>
|
||||
#import <SignalServiceKit/TSThread.h>
|
||||
#import <SignalServiceKit/UIImage+OWS.h>
|
||||
#import "DebugUIMessagesAssetLoader.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
@ -113,14 +114,25 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
[OWS2FAManager.sharedManager setDefaultRepetitionInterval];
|
||||
}]];
|
||||
|
||||
|
||||
#ifdef DEBUG
|
||||
[items addObject:[OWSTableItem subPageItemWithText:@"Share UIImage"
|
||||
actionBlock:^(UIViewController *viewController) {
|
||||
UIImage *image =
|
||||
[UIImage imageWithColor:UIColor.redColor size:CGSizeMake(1.f, 1.f)];
|
||||
[UIImage imageWithColor:UIColor.redColor size:CGSizeMake(1.f, 1.f)];
|
||||
[AttachmentSharing showShareUIForUIImage:image];
|
||||
}]];
|
||||
[items addObject:[OWSTableItem subPageItemWithText:@"Share 2 Images"
|
||||
actionBlock:^(UIViewController *viewController) {
|
||||
[DebugUIMisc shareImages:2];
|
||||
}]];
|
||||
[items addObject:[OWSTableItem subPageItemWithText:@"Share 2 Videos"
|
||||
actionBlock:^(UIViewController *viewController) {
|
||||
[DebugUIMisc shareVideos:2];
|
||||
}]];
|
||||
[items addObject:[OWSTableItem subPageItemWithText:@"Share 2 PDFs"
|
||||
actionBlock:^(UIViewController *viewController) {
|
||||
[DebugUIMisc sharePDFs:2];
|
||||
}]];
|
||||
#endif
|
||||
|
||||
[items
|
||||
|
@ -265,6 +277,66 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
[ThreadUtil enqueueMessageWithAttachment:attachment inThread:thread quotedReplyModel:nil];
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
+ (void)shareAssets:(NSUInteger)count
|
||||
fromAssetLoaders:(NSArray<DebugUIMessagesAssetLoader *> *)assetLoaders
|
||||
{
|
||||
[DebugUIMessagesAssetLoader prepareAssetLoaders:assetLoaders
|
||||
success:^{
|
||||
[self shareAssets:count
|
||||
fromPreparedAssetLoaders:assetLoaders];
|
||||
}
|
||||
failure:^{
|
||||
OWSLogError(@"Could not prepare asset loaders.");
|
||||
}];
|
||||
}
|
||||
|
||||
+ (void)shareAssets:(NSUInteger)count
|
||||
fromPreparedAssetLoaders:(NSArray<DebugUIMessagesAssetLoader *> *)assetLoaders
|
||||
{
|
||||
__block NSMutableArray<NSURL *> *urls = [NSMutableArray new];
|
||||
for (NSUInteger i = 0;i < count;i++) {
|
||||
DebugUIMessagesAssetLoader *assetLoader = assetLoaders[arc4random_uniform((uint32_t) assetLoaders.count)];
|
||||
NSString *filePath = [OWSFileSystem temporaryFilePathWithFileExtension:assetLoader.filePath.pathExtension];
|
||||
NSError *error;
|
||||
[[NSFileManager defaultManager] copyItemAtPath:assetLoader.filePath toPath:filePath error:&error];
|
||||
OWSAssertDebug(!error);
|
||||
[urls addObject:[NSURL fileURLWithPath:filePath]];
|
||||
}
|
||||
OWSLogVerbose(@"urls: %@", urls);
|
||||
[AttachmentSharing showShareUIForURLs:urls completion:^{
|
||||
urls = nil;
|
||||
}];
|
||||
}
|
||||
|
||||
+ (void)shareImages:(NSUInteger)count
|
||||
{
|
||||
[self shareAssets:count
|
||||
fromAssetLoaders:@[
|
||||
[DebugUIMessagesAssetLoader jpegInstance],
|
||||
[DebugUIMessagesAssetLoader tinyPngInstance],
|
||||
]];
|
||||
}
|
||||
|
||||
+ (void)shareVideos:(NSUInteger)count
|
||||
{
|
||||
[self shareAssets:count
|
||||
fromAssetLoaders:@[
|
||||
[DebugUIMessagesAssetLoader mp4Instance],
|
||||
]];
|
||||
}
|
||||
|
||||
+ (void)sharePDFs:(NSUInteger)count
|
||||
{
|
||||
[self shareAssets:count
|
||||
fromAssetLoaders:@[
|
||||
[DebugUIMessagesAssetLoader tinyPdfInstance],
|
||||
]];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -150,7 +150,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
- (UIStatusBarStyle)preferredStatusBarStyle
|
||||
{
|
||||
if (OWSWindowManager.sharedManager.hasCall) {
|
||||
if (!CurrentAppContext().isMainApp) {
|
||||
return super.preferredStatusBarStyle;
|
||||
} else if (OWSWindowManager.sharedManager.hasCall) {
|
||||
// Status bar is overlaying the green "call banner"
|
||||
return UIStatusBarStyleLightContent;
|
||||
} else {
|
||||
|
@ -165,7 +167,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
[UIView setAnimationsEnabled:NO];
|
||||
|
||||
if (@available(iOS 11.0, *)) {
|
||||
if (OWSWindowManager.sharedManager.hasCall) {
|
||||
if (!CurrentAppContext().isMainApp) {
|
||||
self.additionalSafeAreaInsets = UIEdgeInsetsZero;
|
||||
} else if (OWSWindowManager.sharedManager.hasCall) {
|
||||
self.additionalSafeAreaInsets = UIEdgeInsetsMake(20, 0, 0, 0);
|
||||
} else {
|
||||
self.additionalSafeAreaInsets = UIEdgeInsetsZero;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SelectThreadViewController.h"
|
||||
|
@ -7,11 +7,12 @@
|
|||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class SignalAttachment;
|
||||
|
||||
@protocol ShareViewDelegate;
|
||||
|
||||
@interface SharingThreadPickerViewController : SelectThreadViewController
|
||||
|
||||
@property (nonatomic) SignalAttachment *attachment;
|
||||
@property (nonatomic) NSArray<SignalAttachment *> *attachments;
|
||||
|
||||
- (instancetype)initWithShareViewDelegate:(id<ShareViewDelegate>)shareViewDelegate;
|
||||
|
||||
|
|
|
@ -127,13 +127,18 @@ typedef void (^SendMessageBlock)(SendCompletionBlock completion);
|
|||
|
||||
- (nullable NSString *)convertAttachmentToMessageTextIfPossible
|
||||
{
|
||||
if (!self.attachment.isConvertibleToTextMessage) {
|
||||
if (self.attachments.count > 1) {
|
||||
return nil;
|
||||
}
|
||||
if (self.attachment.dataLength >= kOversizeTextMessageSizeThreshold) {
|
||||
OWSAssertDebug(self.attachments.count == 1);
|
||||
SignalAttachment *attachment = self.attachments.firstObject;
|
||||
if (!attachment.isConvertibleToTextMessage) {
|
||||
return nil;
|
||||
}
|
||||
NSData *data = self.attachment.data;
|
||||
if (attachment.dataLength >= kOversizeTextMessageSizeThreshold) {
|
||||
return nil;
|
||||
}
|
||||
NSData *data = attachment.data;
|
||||
OWSAssertDebug(data.length < kOversizeTextMessageSizeThreshold);
|
||||
NSString *_Nullable messageText = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
OWSLogVerbose(@"messageTextForAttachment: %@", messageText);
|
||||
|
@ -142,42 +147,67 @@ typedef void (^SendMessageBlock)(SendCompletionBlock completion);
|
|||
|
||||
- (void)threadWasSelected:(TSThread *)thread
|
||||
{
|
||||
OWSAssertDebug(self.attachment);
|
||||
OWSAssertDebug(self.attachments.count > 0);
|
||||
OWSAssertDebug(thread);
|
||||
|
||||
self.thread = thread;
|
||||
|
||||
if (self.attachment.isConvertibleToContactShare) {
|
||||
[self showContactShareApproval];
|
||||
if ([self tryToShareAsMessageText]) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *_Nullable messageText = [self convertAttachmentToMessageTextIfPossible];
|
||||
|
||||
if (messageText) {
|
||||
MessageApprovalViewController *approvalVC =
|
||||
[[MessageApprovalViewController alloc] initWithMessageText:messageText
|
||||
thread:thread
|
||||
contactsManager:self.contactsManager
|
||||
delegate:self];
|
||||
|
||||
[self.navigationController pushViewController:approvalVC animated:YES];
|
||||
} else {
|
||||
// TODO ALBUMS - send album via SAE
|
||||
OWSNavigationController *approvalModal =
|
||||
[AttachmentApprovalViewController wrappedInNavControllerWithAttachments:@[ self.attachment ]
|
||||
approvalDelegate:self];
|
||||
[self presentViewController:approvalModal animated:YES completion:nil];
|
||||
if ([self tryToShareAsContactShare]) {
|
||||
return;
|
||||
}
|
||||
|
||||
OWSNavigationController *approvalModal =
|
||||
[AttachmentApprovalViewController wrappedInNavControllerWithAttachments:self.attachments approvalDelegate:self];
|
||||
[self presentViewController:approvalModal animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)showContactShareApproval
|
||||
- (BOOL)tryToShareAsMessageText
|
||||
{
|
||||
OWSAssertDebug(self.attachment);
|
||||
OWSAssertDebug(self.thread);
|
||||
OWSAssertDebug(self.attachment.isConvertibleToContactShare);
|
||||
OWSAssertDebug(self.attachments.count > 0);
|
||||
|
||||
NSData *data = self.attachment.data;
|
||||
NSString *_Nullable messageText = [self convertAttachmentToMessageTextIfPossible];
|
||||
if (!messageText) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
MessageApprovalViewController *approvalVC =
|
||||
[[MessageApprovalViewController alloc] initWithMessageText:messageText
|
||||
thread:self.thread
|
||||
contactsManager:self.contactsManager
|
||||
delegate:self];
|
||||
|
||||
[self.navigationController pushViewController:approvalVC animated:YES];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)tryToShareAsContactShare
|
||||
{
|
||||
OWSAssertDebug(self.attachments.count > 0);
|
||||
|
||||
if (self.attachments.count > 1) {
|
||||
return NO;
|
||||
}
|
||||
OWSAssertDebug(self.attachments.count == 1);
|
||||
SignalAttachment *attachment = self.attachments.firstObject;
|
||||
if (!attachment.isConvertibleToContactShare) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
[self showContactShareApproval:attachment];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)showContactShareApproval:(SignalAttachment *)attachment
|
||||
{
|
||||
OWSAssertDebug(attachment);
|
||||
OWSAssertDebug(self.thread);
|
||||
OWSAssertDebug(attachment.isConvertibleToContactShare);
|
||||
|
||||
NSData *data = attachment.data;
|
||||
|
||||
CNContact *_Nullable cnContact = [Contact cnContactWithVCardData:data];
|
||||
Contact *_Nullable contact = [[Contact alloc] initWithSystemContact:cnContact];
|
||||
|
@ -242,13 +272,13 @@ typedef void (^SendMessageBlock)(SendCompletionBlock completion);
|
|||
// the sending operation. Alternatively, we could use a durable send, but do more to make sure the
|
||||
// SAE runs as long as it needs.
|
||||
// TODO ALBUMS - send album via SAE
|
||||
outgoingMessage = [ThreadUtil sendMessageNonDurablyWithAttachment:attachments.firstObject
|
||||
inThread:self.thread
|
||||
quotedReplyModel:nil
|
||||
messageSender:self.messageSender
|
||||
completion:^(NSError *_Nullable error) {
|
||||
sendCompletion(error, outgoingMessage);
|
||||
}];
|
||||
outgoingMessage = [ThreadUtil sendMessageNonDurablyWithAttachments:attachments
|
||||
inThread:self.thread
|
||||
quotedReplyModel:nil
|
||||
messageSender:self.messageSender
|
||||
completion:^(NSError *_Nullable error) {
|
||||
sendCompletion(error, outgoingMessage);
|
||||
}];
|
||||
|
||||
// This is necessary to show progress.
|
||||
self.outgoingMessage = outgoingMessage;
|
||||
|
|
|
@ -144,9 +144,11 @@ public class OWSNavigationBar: UINavigationBar {
|
|||
}
|
||||
|
||||
public override func layoutSubviews() {
|
||||
guard OWSWindowManager.shared().hasCall() else {
|
||||
super.layoutSubviews()
|
||||
return
|
||||
if CurrentAppContext().isMainApp {
|
||||
guard OWSWindowManager.shared().hasCall() else {
|
||||
super.layoutSubviews()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
guard #available(iOS 11, *) else {
|
||||
|
|
|
@ -19,6 +19,8 @@ typedef void (^AttachmentSharingCompletion)(void);
|
|||
|
||||
+ (void)showShareUIForURL:(NSURL *)url completion:(nullable AttachmentSharingCompletion)completion;
|
||||
|
||||
+ (void)showShareUIForURLs:(NSArray<NSURL *> *)urls completion:(nullable AttachmentSharingCompletion)completion;
|
||||
|
||||
+ (void)showShareUIForText:(NSString *)text;
|
||||
|
||||
+ (void)showShareUIForText:(NSString *)text completion:(nullable AttachmentSharingCompletion)completion;
|
||||
|
|
|
@ -40,10 +40,18 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
+ (void)showShareUIForURL:(NSURL *)url completion:(nullable AttachmentSharingCompletion)completion
|
||||
{
|
||||
OWSAssertDebug(url);
|
||||
|
||||
|
||||
[AttachmentSharing showShareUIForActivityItems:@[
|
||||
url,
|
||||
]
|
||||
url,
|
||||
]
|
||||
completion:completion];
|
||||
}
|
||||
|
||||
+ (void)showShareUIForURLs:(NSArray<NSURL *> *)urls completion:(nullable AttachmentSharingCompletion)completion
|
||||
{
|
||||
OWSAssertDebug(urls.count > 0);
|
||||
|
||||
[AttachmentSharing showShareUIForActivityItems:urls
|
||||
completion:completion];
|
||||
}
|
||||
|
||||
|
|
|
@ -19,11 +19,19 @@ public class ProfileFetcherJob: NSObject {
|
|||
|
||||
@objc
|
||||
public class func run(thread: TSThread) {
|
||||
guard CurrentAppContext().isMainApp else {
|
||||
return
|
||||
}
|
||||
|
||||
ProfileFetcherJob().run(recipientIds: thread.recipientIdentifiers)
|
||||
}
|
||||
|
||||
@objc
|
||||
public class func run(recipientId: String, ignoreThrottling: Bool) {
|
||||
guard CurrentAppContext().isMainApp else {
|
||||
return
|
||||
}
|
||||
|
||||
ProfileFetcherJob(ignoreThrottling: ignoreThrottling).run(recipientIds: [recipientId])
|
||||
}
|
||||
|
||||
|
@ -67,6 +75,13 @@ public class ProfileFetcherJob: NSObject {
|
|||
public func run(recipientIds: [String]) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
guard CurrentAppContext().isMainApp else {
|
||||
// Only refresh profiles in the MainApp to decrease the chance of missed SN notifications
|
||||
// in the AppExtension for our users who choose not to verify contacts.
|
||||
owsFailDebug("Should only fetch profiles in the main app")
|
||||
return
|
||||
}
|
||||
|
||||
backgroundTask = OWSBackgroundTask(label: "\(#function)", completionBlock: { [weak self] status in
|
||||
AssertIsOnMainThread()
|
||||
|
||||
|
@ -79,13 +94,6 @@ public class ProfileFetcherJob: NSObject {
|
|||
Logger.error("background task time ran out before profile fetch completed.")
|
||||
})
|
||||
|
||||
if (!CurrentAppContext().isMainApp) {
|
||||
// Only refresh profiles in the MainApp to decrease the chance of missed SN notifications
|
||||
// in the AppExtension for our users who choose not to verify contacts.
|
||||
owsFailDebug("Should only fetch profiles in the main app")
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
for recipientId in recipientIds {
|
||||
self.updateProfile(recipientId: recipientId)
|
||||
|
|
|
@ -72,11 +72,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
failure:(void (^)(NSError *error))failureHandler;
|
||||
|
||||
// Used by SAE, otherwise we should use the durable `enqueue` counterpart
|
||||
+ (TSOutgoingMessage *)sendMessageNonDurablyWithAttachment:(SignalAttachment *)attachment
|
||||
inThread:(TSThread *)thread
|
||||
quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel
|
||||
messageSender:(OWSMessageSender *)messageSender
|
||||
completion:(void (^_Nullable)(NSError *_Nullable error))completion;
|
||||
+ (TSOutgoingMessage *)sendMessageNonDurablyWithAttachments:(NSArray<SignalAttachment *> *)attachments
|
||||
inThread:(TSThread *)thread
|
||||
quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel
|
||||
messageSender:(OWSMessageSender *)messageSender
|
||||
completion:(void (^_Nullable)(NSError *_Nullable error))completion;
|
||||
|
||||
// Used by SAE, otherwise we should use the durable `enqueue` counterpart
|
||||
+ (TSOutgoingMessage *)sendMessageNonDurablyWithContactShare:(OWSContact *)contactShare
|
||||
|
|
|
@ -213,15 +213,14 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
return message;
|
||||
}
|
||||
|
||||
+ (TSOutgoingMessage *)sendMessageNonDurablyWithAttachment:(SignalAttachment *)attachment
|
||||
inThread:(TSThread *)thread
|
||||
quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel
|
||||
messageSender:(OWSMessageSender *)messageSender
|
||||
completion:(void (^_Nullable)(NSError *_Nullable error))completion
|
||||
+ (TSOutgoingMessage *)sendMessageNonDurablyWithAttachments:(NSArray<SignalAttachment *> *)attachments
|
||||
inThread:(TSThread *)thread
|
||||
quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel
|
||||
messageSender:(OWSMessageSender *)messageSender
|
||||
completion:(void (^_Nullable)(NSError *_Nullable error))completion
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssertDebug(attachment);
|
||||
OWSAssertDebug([attachment mimeType].length > 0);
|
||||
OWSAssertDebug(attachments.count > 0);
|
||||
OWSAssertDebug(thread);
|
||||
OWSAssertDebug(messageSender);
|
||||
|
||||
|
@ -229,21 +228,28 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
[OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:thread.uniqueId];
|
||||
|
||||
uint32_t expiresInSeconds = (configuration.isEnabled ? configuration.durationSeconds : 0);
|
||||
BOOL isVoiceMessage = (attachments.count == 1 && attachments.firstObject.isVoiceMessage);
|
||||
NSString *_Nullable messageBody = (attachments.count == 1 ? attachments.firstObject.captionText : nil);
|
||||
TSOutgoingMessage *message =
|
||||
[[TSOutgoingMessage alloc] initOutgoingMessageWithTimestamp:[NSDate ows_millisecondTimeStamp]
|
||||
inThread:thread
|
||||
messageBody:attachment.captionText
|
||||
messageBody:messageBody
|
||||
attachmentIds:[NSMutableArray new]
|
||||
expiresInSeconds:expiresInSeconds
|
||||
expireStartedAt:0
|
||||
isVoiceMessage:[attachment isVoiceMessage]
|
||||
isVoiceMessage:isVoiceMessage
|
||||
groupMetaMessage:TSGroupMetaMessageUnspecified
|
||||
quotedMessage:[quotedReplyModel buildQuotedMessageForSending]
|
||||
contactShare:nil];
|
||||
|
||||
[messageSender sendAttachment:attachment.dataSource
|
||||
contentType:attachment.mimeType
|
||||
sourceFilename:attachment.filenameOrDefault
|
||||
NSMutableArray<OWSOutgoingAttachmentInfo *> *attachmentInfos = [NSMutableArray new];
|
||||
for (SignalAttachment *attachment in attachments) {
|
||||
OWSAssertDebug([attachment mimeType].length > 0);
|
||||
|
||||
[attachmentInfos addObject:[attachment buildOutgoingAttachmentInfoWithMessage:message]];
|
||||
}
|
||||
|
||||
[messageSender sendAttachments:attachmentInfos
|
||||
inMessage:message
|
||||
success:^{
|
||||
OWSLogDebug(@"Successfully sent message attachment.");
|
||||
|
|
|
@ -86,7 +86,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
_incomingMessageFinder = [[OWSIncomingMessageFinder alloc] initWithPrimaryStorage:primaryStorage];
|
||||
|
||||
OWSSingletonAssert();
|
||||
OWSAssertDebug(CurrentAppContext().isMainApp);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
|
|
@ -76,10 +76,16 @@ NS_SWIFT_NAME(MessageSender)
|
|||
- (void)sendAttachment:(DataSource *)dataSource
|
||||
contentType:(NSString *)contentType
|
||||
sourceFilename:(nullable NSString *)sourceFilename
|
||||
albumMessageId:(nullable NSString *)albumMessageId
|
||||
inMessage:(TSOutgoingMessage *)outgoingMessage
|
||||
success:(void (^)(void))successHandler
|
||||
failure:(void (^)(NSError *error))failureHandler;
|
||||
|
||||
- (void)sendAttachments:(NSArray<OWSOutgoingAttachmentInfo *> *)attachmentInfos
|
||||
inMessage:(TSOutgoingMessage *)message
|
||||
success:(void (^)(void))successHandler
|
||||
failure:(void (^)(NSError *error))failureHandler;
|
||||
|
||||
/**
|
||||
* Same as `sendAttachment:`, but deletes the local copy of the attachment after sending.
|
||||
* Used for sending sync request data, not for user visible attachments.
|
||||
|
|
|
@ -444,6 +444,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|||
[self sendAttachment:dataSource
|
||||
contentType:contentType
|
||||
sourceFilename:nil
|
||||
albumMessageId:nil
|
||||
inMessage:message
|
||||
success:successWithDeleteHandler
|
||||
failure:failureWithDeleteHandler];
|
||||
|
@ -452,26 +453,41 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|||
- (void)sendAttachment:(DataSource *)dataSource
|
||||
contentType:(NSString *)contentType
|
||||
sourceFilename:(nullable NSString *)sourceFilename
|
||||
albumMessageId:(nullable NSString *)albumMessageId
|
||||
inMessage:(TSOutgoingMessage *)message
|
||||
success:(void (^)(void))successHandler
|
||||
failure:(void (^)(NSError *error))failureHandler
|
||||
success:(void (^)(void))success
|
||||
failure:(void (^)(NSError *error))failure
|
||||
{
|
||||
OWSAssertDebug(dataSource);
|
||||
|
||||
NSString *albumMessageId = message.uniqueId;
|
||||
OWSOutgoingAttachmentInfo *attachmentInfo = [[OWSOutgoingAttachmentInfo alloc] initWithDataSource:dataSource
|
||||
contentType:contentType
|
||||
sourceFilename:sourceFilename
|
||||
caption:nil
|
||||
albumMessageId:albumMessageId];
|
||||
[OutgoingMessagePreparer prepareAttachments:@[ attachmentInfo ]
|
||||
[self sendAttachments:@[
|
||||
attachmentInfo,
|
||||
]
|
||||
inMessage:message
|
||||
success:success
|
||||
failure:failure];
|
||||
}
|
||||
|
||||
- (void)sendAttachments:(NSArray<OWSOutgoingAttachmentInfo *> *)attachmentInfos
|
||||
inMessage:(TSOutgoingMessage *)message
|
||||
success:(void (^)(void))success
|
||||
failure:(void (^)(NSError *error))failure
|
||||
{
|
||||
OWSAssertDebug(attachmentInfos.count > 0);
|
||||
|
||||
[OutgoingMessagePreparer prepareAttachments:attachmentInfos
|
||||
inMessage:message
|
||||
completionHandler:^(NSError *_Nullable error) {
|
||||
if (error) {
|
||||
failureHandler(error);
|
||||
failure(error);
|
||||
return;
|
||||
}
|
||||
[self sendMessage:message success:successHandler failure:failureHandler];
|
||||
[self sendMessage:message success:success failure:failure];
|
||||
}];
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
|
|||
private var progressPoller: ProgressPoller?
|
||||
var loadViewController: SAELoadViewController?
|
||||
|
||||
let shareViewNavigationController: OWSNavigationController = OWSNavigationController()
|
||||
private var shareViewNavigationController: OWSNavigationController?
|
||||
|
||||
override open func loadView() {
|
||||
super.loadView()
|
||||
|
@ -70,6 +70,9 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
|
|||
return
|
||||
}
|
||||
|
||||
let shareViewNavigationController = OWSNavigationController()
|
||||
self.shareViewNavigationController = shareViewNavigationController
|
||||
|
||||
let loadViewController = SAELoadViewController(delegate: self)
|
||||
self.loadViewController = loadViewController
|
||||
|
||||
|
@ -331,7 +334,7 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
|
|||
} else {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.buildAttachmentAndPresentConversationPicker()
|
||||
strongSelf.buildAttachmentsAndPresentConversationPicker()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -452,6 +455,10 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
|
|||
// If user is registered, do nothing.
|
||||
return
|
||||
}
|
||||
guard let shareViewNavigationController = shareViewNavigationController else {
|
||||
owsFailDebug("Missing shareViewNavigationController")
|
||||
return
|
||||
}
|
||||
guard let firstViewController = shareViewNavigationController.viewControllers.first else {
|
||||
// If no view has been presented yet, do nothing.
|
||||
return
|
||||
|
@ -513,6 +520,10 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
|
|||
private func showPrimaryViewController(_ viewController: UIViewController) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
guard let shareViewNavigationController = shareViewNavigationController else {
|
||||
owsFailDebug("Missing shareViewNavigationController")
|
||||
return
|
||||
}
|
||||
shareViewNavigationController.setViewControllers([viewController], animated: false)
|
||||
if self.presentedViewController == nil {
|
||||
Logger.debug("presenting modally: \(viewController)")
|
||||
|
@ -523,10 +534,10 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
|
|||
}
|
||||
}
|
||||
|
||||
private func buildAttachmentAndPresentConversationPicker() {
|
||||
private func buildAttachmentsAndPresentConversationPicker() {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
self.buildAttachment().map { [weak self] attachment in
|
||||
self.buildAttachments().map { [weak self] attachments in
|
||||
AssertIsOnMainThread()
|
||||
guard let strongSelf = self else { return }
|
||||
|
||||
|
@ -535,9 +546,9 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
|
|||
|
||||
let conversationPicker = SharingThreadPickerViewController(shareViewDelegate: strongSelf)
|
||||
Logger.debug("presentConversationPicker: \(conversationPicker)")
|
||||
conversationPicker.attachment = attachment
|
||||
conversationPicker.attachments = attachments
|
||||
strongSelf.showPrimaryViewController(conversationPicker)
|
||||
Logger.info("showing picker with attachment: \(attachment)")
|
||||
Logger.info("showing picker with attachments: \(attachments)")
|
||||
}.catch { [weak self] error in
|
||||
AssertIsOnMainThread()
|
||||
guard let strongSelf = self else { return }
|
||||
|
@ -574,6 +585,11 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
|
|||
return firstUtiType == utiType
|
||||
}
|
||||
|
||||
private class func isVisualMediaItem(itemProvider: NSItemProvider) -> Bool {
|
||||
return (itemProvider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) ||
|
||||
itemProvider.hasItemConformingToTypeIdentifier(kUTTypeMovie as String))
|
||||
}
|
||||
|
||||
private class func isUrlItem(itemProvider: NSItemProvider) -> Bool {
|
||||
return itemMatchesSpecificUtiType(itemProvider: itemProvider,
|
||||
utiType: kUTTypeURL as String)
|
||||
|
@ -600,26 +616,6 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
|
|||
return matchingUtiType
|
||||
}
|
||||
|
||||
private class func preferredItemProvider(inputItem: NSExtensionItem) -> NSItemProvider? {
|
||||
guard let attachments = inputItem.attachments else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Prefer a URL provider if available
|
||||
// TODO: Support multi-image messages.
|
||||
if let preferredAttachment = attachments.first(where: { (attachment: Any) -> Bool in
|
||||
guard let itemProvider = attachment as? NSItemProvider else {
|
||||
return false
|
||||
}
|
||||
return isUrlItem(itemProvider: itemProvider)
|
||||
}) {
|
||||
return preferredAttachment as? NSItemProvider
|
||||
}
|
||||
|
||||
// else return whatever is available
|
||||
return inputItem.attachments?.first as? NSItemProvider
|
||||
}
|
||||
|
||||
private class func createDataSource(utiType: String, url: URL, customFileName: String?) -> DataSource? {
|
||||
if utiType == (kUTTypeURL as String) {
|
||||
// Share URLs as oversize text messages whose text content is the URL.
|
||||
|
@ -651,10 +647,28 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
|
|||
}
|
||||
}
|
||||
|
||||
private func buildAttachment() -> Promise<SignalAttachment> {
|
||||
guard let inputItem: NSExtensionItem = self.extensionContext?.inputItems.first as? NSExtensionItem else {
|
||||
let error = ShareViewControllerError.assertionError(description: "no input item")
|
||||
return Promise(error: error)
|
||||
private class func preferredItemProviders(inputItem: NSExtensionItem) -> [NSItemProvider]? {
|
||||
guard let attachments = inputItem.attachments else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var visualMediaItemProviders = [NSItemProvider]()
|
||||
var hasNonVisualMedia = false
|
||||
for attachment in attachments {
|
||||
guard let itemProvider = attachment as? NSItemProvider else {
|
||||
owsFailDebug("Unexpected attachment type: \(String(describing: attachment))")
|
||||
continue
|
||||
}
|
||||
if isVisualMediaItem(itemProvider: itemProvider) {
|
||||
visualMediaItemProviders.append(itemProvider)
|
||||
} else {
|
||||
hasNonVisualMedia = true
|
||||
}
|
||||
}
|
||||
// Only allow multiple-attachment sends if all attachments
|
||||
// are visual media.
|
||||
if visualMediaItemProviders.count > 0 && !hasNonVisualMedia {
|
||||
return visualMediaItemProviders
|
||||
}
|
||||
|
||||
// A single inputItem can have multiple attachments, e.g. sharing from Firefox gives
|
||||
|
@ -664,10 +678,75 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
|
|||
// FIXME: For now, we prefer the URL provider and discard the text provider, since it's more useful to share the URL than the caption
|
||||
// but we *should* include both. This will be a bigger change though since our share extension is currently heavily predicated
|
||||
// on one itemProvider per share.
|
||||
guard let itemProvider: NSItemProvider = type(of: self).preferredItemProvider(inputItem: inputItem) else {
|
||||
let error = ShareViewControllerError.assertionError(description: "No item provider in input item attachments")
|
||||
|
||||
// Prefer a URL provider if available
|
||||
if let preferredAttachment = attachments.first(where: { (attachment: Any) -> Bool in
|
||||
guard let itemProvider = attachment as? NSItemProvider else {
|
||||
return false
|
||||
}
|
||||
return isUrlItem(itemProvider: itemProvider)
|
||||
}) {
|
||||
if let itemProvider = preferredAttachment as? NSItemProvider {
|
||||
return [itemProvider]
|
||||
} else {
|
||||
owsFailDebug("Unexpected attachment type: \(String(describing: preferredAttachment))")
|
||||
}
|
||||
}
|
||||
|
||||
// else return whatever is available
|
||||
if let itemProvider = inputItem.attachments?.first as? NSItemProvider {
|
||||
return [itemProvider]
|
||||
} else {
|
||||
owsFailDebug("Missing attachment.")
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
private func selectItemProviders() -> Promise<[NSItemProvider]> {
|
||||
guard let inputItems = self.extensionContext?.inputItems else {
|
||||
let error = ShareViewControllerError.assertionError(description: "no input item")
|
||||
return Promise(error: error)
|
||||
}
|
||||
|
||||
for inputItemRaw in inputItems {
|
||||
guard let inputItem = inputItemRaw as? NSExtensionItem else {
|
||||
Logger.error("invalid inputItem \(inputItemRaw)")
|
||||
continue
|
||||
}
|
||||
if let itemProviders = ShareViewController.preferredItemProviders(inputItem: inputItem) {
|
||||
return Promise.value(itemProviders)
|
||||
}
|
||||
}
|
||||
let error = ShareViewControllerError.assertionError(description: "no input item")
|
||||
return Promise(error: error)
|
||||
}
|
||||
|
||||
private
|
||||
struct LoadedItem {
|
||||
let itemProvider: NSItemProvider
|
||||
let itemUrl: URL
|
||||
let utiType: String
|
||||
|
||||
var customFileName: String?
|
||||
var isConvertibleToTextMessage = false
|
||||
var isConvertibleToContactShare = false
|
||||
|
||||
init(itemProvider: NSItemProvider,
|
||||
itemUrl: URL,
|
||||
utiType: String,
|
||||
customFileName: String? = nil,
|
||||
isConvertibleToTextMessage: Bool = false,
|
||||
isConvertibleToContactShare: Bool = false) {
|
||||
self.itemProvider = itemProvider
|
||||
self.itemUrl = itemUrl
|
||||
self.utiType = utiType
|
||||
self.customFileName = customFileName
|
||||
self.isConvertibleToTextMessage = isConvertibleToTextMessage
|
||||
self.isConvertibleToContactShare = isConvertibleToContactShare
|
||||
}
|
||||
}
|
||||
|
||||
private func loadItemProvider(itemProvider: NSItemProvider) -> Promise<LoadedItem> {
|
||||
Logger.info("attachment: \(itemProvider)")
|
||||
|
||||
// We need to be very careful about which UTI type we use.
|
||||
|
@ -685,17 +764,12 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
|
|||
}
|
||||
Logger.debug("matched utiType: \(srcUtiType)")
|
||||
|
||||
let (promise, resolver) = Promise<(itemUrl: URL, utiType: String)>.pending()
|
||||
|
||||
var customFileName: String?
|
||||
var isConvertibleToTextMessage = false
|
||||
var isConvertibleToContactShare = false
|
||||
let (promise, resolver) = Promise<LoadedItem>.pending()
|
||||
|
||||
let loadCompletion: NSItemProvider.CompletionHandler = { [weak self]
|
||||
(value, error) in
|
||||
|
||||
guard let _ = self else { return }
|
||||
|
||||
guard error == nil else {
|
||||
resolver.reject(error!)
|
||||
return
|
||||
|
@ -710,11 +784,13 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
|
|||
Logger.info("value type: \(type(of: value))")
|
||||
|
||||
if let data = value as? Data {
|
||||
let customFileName = "Contact.vcf"
|
||||
var isConvertibleToContactShare = false
|
||||
|
||||
// Although we don't support contacts _yet_, when we do we'll want to make
|
||||
// sure they are shared with a reasonable filename.
|
||||
if ShareViewController.itemMatchesSpecificUtiType(itemProvider: itemProvider,
|
||||
utiType: kUTTypeVCard as String) {
|
||||
customFileName = "Contact.vcf"
|
||||
|
||||
if Contact(vCardData: data) != nil {
|
||||
isConvertibleToContactShare = true
|
||||
|
@ -733,7 +809,11 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
|
|||
return
|
||||
}
|
||||
let fileUrl = URL(fileURLWithPath: tempFilePath)
|
||||
resolver.fulfill((itemUrl: fileUrl, utiType: srcUtiType))
|
||||
resolver.fulfill(LoadedItem(itemProvider: itemProvider,
|
||||
itemUrl: fileUrl,
|
||||
utiType: srcUtiType,
|
||||
customFileName: customFileName,
|
||||
isConvertibleToContactShare: isConvertibleToContactShare))
|
||||
} else if let string = value as? String {
|
||||
Logger.debug("string provider: \(string)")
|
||||
guard let data = string.filterStringForDisplay().data(using: String.Encoding.utf8) else {
|
||||
|
@ -749,21 +829,33 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
|
|||
|
||||
let fileUrl = URL(fileURLWithPath: tempFilePath)
|
||||
|
||||
isConvertibleToTextMessage = !itemProvider.registeredTypeIdentifiers.contains(kUTTypeFileURL as String)
|
||||
let isConvertibleToTextMessage = !itemProvider.registeredTypeIdentifiers.contains(kUTTypeFileURL as String)
|
||||
|
||||
if UTTypeConformsTo(srcUtiType as CFString, kUTTypeText) {
|
||||
resolver.fulfill((itemUrl: fileUrl, utiType: srcUtiType))
|
||||
resolver.fulfill(LoadedItem(itemProvider: itemProvider,
|
||||
itemUrl: fileUrl,
|
||||
utiType: srcUtiType,
|
||||
isConvertibleToTextMessage: isConvertibleToTextMessage))
|
||||
} else {
|
||||
resolver.fulfill((itemUrl: fileUrl, utiType: kUTTypeText as String))
|
||||
resolver.fulfill(LoadedItem(itemProvider: itemProvider,
|
||||
itemUrl: fileUrl,
|
||||
utiType: kUTTypeText as String,
|
||||
isConvertibleToTextMessage: isConvertibleToTextMessage))
|
||||
}
|
||||
} else if let url = value as? URL {
|
||||
// If the share itself is a URL (e.g. a link from Safari), try to send this as a text message.
|
||||
isConvertibleToTextMessage = (itemProvider.registeredTypeIdentifiers.contains(kUTTypeURL as String) &&
|
||||
let isConvertibleToTextMessage = (itemProvider.registeredTypeIdentifiers.contains(kUTTypeURL as String) &&
|
||||
!itemProvider.registeredTypeIdentifiers.contains(kUTTypeFileURL as String))
|
||||
if isConvertibleToTextMessage {
|
||||
resolver.fulfill((itemUrl: url, utiType: kUTTypeURL as String))
|
||||
resolver.fulfill(LoadedItem(itemProvider: itemProvider,
|
||||
itemUrl: url,
|
||||
utiType: kUTTypeURL as String,
|
||||
isConvertibleToTextMessage: isConvertibleToTextMessage))
|
||||
} else {
|
||||
resolver.fulfill((itemUrl: url, utiType: srcUtiType))
|
||||
resolver.fulfill(LoadedItem(itemProvider: itemProvider,
|
||||
itemUrl: url,
|
||||
utiType: srcUtiType,
|
||||
isConvertibleToTextMessage: isConvertibleToTextMessage))
|
||||
}
|
||||
} else if let image = value as? UIImage {
|
||||
if let data = UIImagePNGRepresentation(image) {
|
||||
|
@ -771,7 +863,8 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
|
|||
do {
|
||||
let url = NSURL.fileURL(withPath: tempFilePath)
|
||||
try data.write(to: url)
|
||||
resolver.fulfill((url, srcUtiType))
|
||||
resolver.fulfill(LoadedItem(itemProvider: itemProvider, itemUrl: url,
|
||||
utiType: srcUtiType))
|
||||
} catch {
|
||||
resolver.reject(ShareViewControllerError.assertionError(description: "couldn't write UIImage: \(String(describing: error))"))
|
||||
}
|
||||
|
@ -788,76 +881,108 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed
|
|||
|
||||
itemProvider.loadItem(forTypeIdentifier: srcUtiType, options: nil, completionHandler: loadCompletion)
|
||||
|
||||
return promise.then { [weak self] (itemUrl: URL, utiType: String) -> Promise<SignalAttachment> in
|
||||
return promise
|
||||
}
|
||||
|
||||
private func buildAttachment(forLoadedItem loadedItem: LoadedItem) -> Promise<SignalAttachment> {
|
||||
let itemProvider = loadedItem.itemProvider
|
||||
let itemUrl = loadedItem.itemUrl
|
||||
let utiType = loadedItem.utiType
|
||||
|
||||
var url = itemUrl
|
||||
do {
|
||||
if isVideoNeedingRelocation(itemProvider: itemProvider, itemUrl: itemUrl) {
|
||||
url = try SignalAttachment.copyToVideoTempDir(url: itemUrl)
|
||||
}
|
||||
} catch {
|
||||
let error = ShareViewControllerError.assertionError(description: "Could not copy video")
|
||||
return Promise(error: error)
|
||||
}
|
||||
|
||||
Logger.debug("building DataSource with url: \(url), utiType: \(utiType)")
|
||||
|
||||
guard let dataSource = ShareViewController.createDataSource(utiType: utiType, url: url, customFileName: loadedItem.customFileName) else {
|
||||
let error = ShareViewControllerError.assertionError(description: "Unable to read attachment data")
|
||||
return Promise(error: error)
|
||||
}
|
||||
|
||||
// start with base utiType, but it might be something generic like "image"
|
||||
var specificUTIType = utiType
|
||||
if utiType == (kUTTypeURL as String) {
|
||||
// Use kUTTypeURL for URLs.
|
||||
} else if UTTypeConformsTo(utiType as CFString, kUTTypeText) {
|
||||
// Use kUTTypeText for text.
|
||||
} else if url.pathExtension.count > 0 {
|
||||
// Determine a more specific utiType based on file extension
|
||||
if let typeExtension = MIMETypeUtil.utiType(forFileExtension: url.pathExtension) {
|
||||
Logger.debug("utiType based on extension: \(typeExtension)")
|
||||
specificUTIType = typeExtension
|
||||
}
|
||||
}
|
||||
|
||||
guard !SignalAttachment.isInvalidVideo(dataSource: dataSource, dataUTI: specificUTIType) else {
|
||||
// This can happen, e.g. when sharing a quicktime-video from iCloud drive.
|
||||
|
||||
let (promise, exportSession) = SignalAttachment.compressVideoAsMp4(dataSource: dataSource, dataUTI: specificUTIType)
|
||||
|
||||
// TODO: How can we move waiting for this export to the end of the share flow rather than having to do it up front?
|
||||
// Ideally we'd be able to start it here, and not block the UI on conversion unless there's still work to be done
|
||||
// when the user hits "send".
|
||||
if let exportSession = exportSession {
|
||||
let progressPoller = ProgressPoller(timeInterval: 0.1, ratioCompleteBlock: { return exportSession.progress })
|
||||
self.progressPoller = progressPoller
|
||||
progressPoller.startPolling()
|
||||
|
||||
guard let loadViewController = self.loadViewController else {
|
||||
owsFailDebug("load view controller was unexpectedly nil")
|
||||
return promise
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
loadViewController.progress = progressPoller.progress
|
||||
}
|
||||
}
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
let attachment = SignalAttachment.attachment(dataSource: dataSource, dataUTI: specificUTIType, imageQuality: .medium)
|
||||
if loadedItem.isConvertibleToContactShare {
|
||||
Logger.info("isConvertibleToContactShare")
|
||||
attachment.isConvertibleToContactShare = true
|
||||
} else if loadedItem.isConvertibleToTextMessage {
|
||||
Logger.info("isConvertibleToTextMessage")
|
||||
attachment.isConvertibleToTextMessage = true
|
||||
}
|
||||
return Promise.value(attachment)
|
||||
}
|
||||
|
||||
private func buildAttachments() -> Promise<[SignalAttachment]> {
|
||||
let promise = selectItemProviders().then { [weak self] (itemProviders) -> Promise<[SignalAttachment]> in
|
||||
guard let strongSelf = self else {
|
||||
let error = ShareViewControllerError.obsoleteShare
|
||||
let error = ShareViewControllerError.assertionError(description: "expired")
|
||||
return Promise(error: error)
|
||||
}
|
||||
|
||||
let url: URL = try {
|
||||
if strongSelf.isVideoNeedingRelocation(itemProvider: itemProvider, itemUrl: itemUrl) {
|
||||
return try SignalAttachment.copyToVideoTempDir(url: itemUrl)
|
||||
} else {
|
||||
return itemUrl
|
||||
var loadPromises = [Promise<SignalAttachment>]()
|
||||
for itemProvider in itemProviders {
|
||||
let loadPromise = strongSelf.loadItemProvider(itemProvider: itemProvider)
|
||||
.then({ (loadedItem) -> Promise<SignalAttachment> in
|
||||
return strongSelf.buildAttachment(forLoadedItem: loadedItem)
|
||||
})
|
||||
|
||||
loadPromises.append(loadPromise)
|
||||
}
|
||||
return when(fulfilled: loadPromises)
|
||||
}.map { (signalAttachments) -> [SignalAttachment] in
|
||||
guard signalAttachments.count > 0 else {
|
||||
let error = ShareViewControllerError.assertionError(description: "no valid attachments")
|
||||
throw error
|
||||
}
|
||||
}()
|
||||
|
||||
Logger.debug("building DataSource with url: \(url), utiType: \(utiType)")
|
||||
|
||||
guard let dataSource = ShareViewController.createDataSource(utiType: utiType, url: url, customFileName: customFileName) else {
|
||||
throw ShareViewControllerError.assertionError(description: "Unable to read attachment data")
|
||||
return signalAttachments
|
||||
}
|
||||
|
||||
// start with base utiType, but it might be something generic like "image"
|
||||
var specificUTIType = utiType
|
||||
if utiType == (kUTTypeURL as String) {
|
||||
// Use kUTTypeURL for URLs.
|
||||
} else if UTTypeConformsTo(utiType as CFString, kUTTypeText) {
|
||||
// Use kUTTypeText for text.
|
||||
} else if url.pathExtension.count > 0 {
|
||||
// Determine a more specific utiType based on file extension
|
||||
if let typeExtension = MIMETypeUtil.utiType(forFileExtension: url.pathExtension) {
|
||||
Logger.debug("utiType based on extension: \(typeExtension)")
|
||||
specificUTIType = typeExtension
|
||||
}
|
||||
}
|
||||
|
||||
guard !SignalAttachment.isInvalidVideo(dataSource: dataSource, dataUTI: specificUTIType) else {
|
||||
// This can happen, e.g. when sharing a quicktime-video from iCloud drive.
|
||||
|
||||
let (promise, exportSession) = SignalAttachment.compressVideoAsMp4(dataSource: dataSource, dataUTI: specificUTIType)
|
||||
|
||||
// TODO: How can we move waiting for this export to the end of the share flow rather than having to do it up front?
|
||||
// Ideally we'd be able to start it here, and not block the UI on conversion unless there's still work to be done
|
||||
// when the user hits "send".
|
||||
if let exportSession = exportSession {
|
||||
let progressPoller = ProgressPoller(timeInterval: 0.1, ratioCompleteBlock: { return exportSession.progress })
|
||||
strongSelf.progressPoller = progressPoller
|
||||
progressPoller.startPolling()
|
||||
|
||||
guard let loadViewController = strongSelf.loadViewController else {
|
||||
owsFailDebug("load view controller was unexpectedly nil")
|
||||
return promise
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
loadViewController.progress = progressPoller.progress
|
||||
}
|
||||
}
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
let attachment = SignalAttachment.attachment(dataSource: dataSource, dataUTI: specificUTIType, imageQuality: .medium)
|
||||
if isConvertibleToContactShare {
|
||||
Logger.info("isConvertibleToContactShare")
|
||||
attachment.isConvertibleToContactShare = isConvertibleToContactShare
|
||||
} else if isConvertibleToTextMessage {
|
||||
Logger.info("isConvertibleToTextMessage")
|
||||
attachment.isConvertibleToTextMessage = isConvertibleToTextMessage
|
||||
}
|
||||
return Promise.value(attachment)
|
||||
}
|
||||
promise.retainUntilComplete()
|
||||
return promise
|
||||
}
|
||||
|
||||
// Some host apps (e.g. iOS Photos.app) sometimes auto-converts some video formats (e.g. com.apple.quicktime-movie)
|
||||
|
|
Loading…
Reference in a new issue