|
|
|
@ -334,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()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -534,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 }
|
|
|
|
|
|
|
|
|
@ -546,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 }
|
|
|
|
@ -585,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)
|
|
|
|
@ -611,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.
|
|
|
|
@ -662,10 +647,23 @@ 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]()
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if visualMediaItemProviders.count > 0 {
|
|
|
|
|
return visualMediaItemProviders
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A single inputItem can have multiple attachments, e.g. sharing from Firefox gives
|
|
|
|
@ -675,10 +673,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.
|
|
|
|
@ -696,17 +759,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
|
|
|
|
@ -721,11 +779,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
|
|
|
|
@ -744,7 +804,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 {
|
|
|
|
@ -760,21 +824,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) {
|
|
|
|
@ -782,7 +858,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))"))
|
|
|
|
|
}
|
|
|
|
@ -799,76 +876,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
|
|
|
|
|
guard let strongSelf = self else {
|
|
|
|
|
let error = ShareViewControllerError.obsoleteShare
|
|
|
|
|
return Promise(error: error)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let url: URL = try {
|
|
|
|
|
if strongSelf.isVideoNeedingRelocation(itemProvider: itemProvider, itemUrl: itemUrl) {
|
|
|
|
|
return try SignalAttachment.copyToVideoTempDir(url: itemUrl)
|
|
|
|
|
} else {
|
|
|
|
|
return itemUrl
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
return promise
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Logger.debug("building DataSource with url: \(url), utiType: \(utiType)")
|
|
|
|
|
private func buildAttachment(forLoadedItem loadedItem: LoadedItem) -> Promise<SignalAttachment> {
|
|
|
|
|
let itemProvider = loadedItem.itemProvider
|
|
|
|
|
let itemUrl = loadedItem.itemUrl
|
|
|
|
|
let utiType = loadedItem.utiType
|
|
|
|
|
|
|
|
|
|
guard let dataSource = ShareViewController.createDataSource(utiType: utiType, url: url, customFileName: customFileName) else {
|
|
|
|
|
throw ShareViewControllerError.assertionError(description: "Unable to read attachment data")
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
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.
|
|
|
|
|
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)
|
|
|
|
|
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()
|
|
|
|
|
// 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 = strongSelf.loadViewController else {
|
|
|
|
|
owsFailDebug("load view controller was unexpectedly nil")
|
|
|
|
|
return promise
|
|
|
|
|
}
|
|
|
|
|
guard let loadViewController = self.loadViewController else {
|
|
|
|
|
owsFailDebug("load view controller was unexpectedly nil")
|
|
|
|
|
return promise
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
loadViewController.progress = progressPoller.progress
|
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return promise
|
|
|
|
|
private func buildAttachments() -> Promise<[SignalAttachment]> {
|
|
|
|
|
let promise = selectItemProviders().then { [weak self] (itemProviders) -> Promise<[SignalAttachment]> in
|
|
|
|
|
guard let strongSelf = self else {
|
|
|
|
|
let error = ShareViewControllerError.assertionError(description: "expired")
|
|
|
|
|
return Promise(error: error)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
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 Promise.value(attachment)
|
|
|
|
|
}
|
|
|
|
|
return when(fulfilled: loadPromises)
|
|
|
|
|
}.then { (signalAttachments) -> Promise<[SignalAttachment]> in
|
|
|
|
|
guard signalAttachments.count > 0 else {
|
|
|
|
|
let error = ShareViewControllerError.assertionError(description: "no valid attachments")
|
|
|
|
|
return Promise(error: error)
|
|
|
|
|
}
|
|
|
|
|
return Promise.value(signalAttachments)
|
|
|
|
|
}
|
|
|
|
|
promise.retainUntilComplete()
|
|
|
|
|
return promise
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Some host apps (e.g. iOS Photos.app) sometimes auto-converts some video formats (e.g. com.apple.quicktime-movie)
|
|
|
|
|