|  |  |  | @ -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) | 
		
	
	
		
			
				
					|  |  |  | 
 |