ShareAttachment.swift 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. import Foundation
  2. import MobileCoreServices
  3. import DcCore
  4. import UIKit
  5. import QuickLookThumbnailing
  6. import SDWebImage
  7. protocol ShareAttachmentDelegate: class {
  8. func onAttachmentChanged()
  9. func onThumbnailChanged()
  10. func onUrlShared(url: URL)
  11. func onLoadingStarted()
  12. func onLoadingFinished()
  13. }
  14. class ShareAttachment {
  15. weak var delegate: ShareAttachmentDelegate?
  16. let dcContext: DcContext
  17. let thumbnailSize = CGFloat(96)
  18. var inputItems: [Any]?
  19. var messages: [DcMsg] = []
  20. private var imageThumbnail: UIImage?
  21. private var attachmentThumbnail: UIImage?
  22. var thumbnail: UIImage? {
  23. return self.imageThumbnail ?? self.attachmentThumbnail
  24. }
  25. var isEmpty: Bool {
  26. return messages.isEmpty
  27. }
  28. init(dcContext: DcContext, inputItems: [Any]?, delegate: ShareAttachmentDelegate) {
  29. self.dcContext = dcContext
  30. self.inputItems = inputItems
  31. self.delegate = delegate
  32. createMessages()
  33. }
  34. private func createMessages() {
  35. guard let items = inputItems as? [NSExtensionItem] else { return }
  36. delegate?.onLoadingStarted()
  37. for item in items {
  38. if let attachments = item.attachments {
  39. createMessageFromDataRepresentation(attachments)
  40. }
  41. }
  42. delegate?.onLoadingFinished()
  43. }
  44. private func createMessageFromDataRepresentation(_ attachments: [NSItemProvider]) {
  45. for attachment in attachments {
  46. if attachment.hasItemConformingToTypeIdentifier(kUTTypeGIF as String) {
  47. createAnimatedImageMsg(attachment)
  48. } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeImage as String) {
  49. createImageMsg(attachment)
  50. } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeMovie as String) {
  51. createMovieMsg(attachment)
  52. } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeAudio as String) {
  53. createAudioMsg(attachment)
  54. } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeFileURL as String) {
  55. createFileMsg(attachment)
  56. } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeURL as String) {
  57. addSharedUrl(attachment)
  58. }
  59. }
  60. }
  61. // for now we only support GIF
  62. private func createAnimatedImageMsg(_ item: NSItemProvider) {
  63. item.loadItem(forTypeIdentifier: kUTTypeGIF as String, options: nil) { data, error in
  64. var result: SDAnimatedImage?
  65. switch data {
  66. case let animatedImageData as Data:
  67. result = SDAnimatedImage(data: animatedImageData)
  68. case let url as URL:
  69. result = SDAnimatedImage(contentsOfFile: url.path)
  70. default:
  71. self.dcContext.logger?.debug("Unexpected data: \(type(of: data))")
  72. }
  73. if let result = result {
  74. let path = ImageFormat.saveImage(image: result)
  75. let msg = self.dcContext.newMessage(viewType: DC_MSG_GIF)
  76. msg.setFile(filepath: path)
  77. self.messages.append(msg)
  78. self.delegate?.onAttachmentChanged()
  79. if self.imageThumbnail == nil {
  80. self.imageThumbnail = result
  81. self.delegate?.onThumbnailChanged()
  82. }
  83. if let error = error {
  84. self.dcContext.logger?.error("Could not load share item as image: \(error.localizedDescription)")
  85. }
  86. }
  87. }
  88. }
  89. private func createImageMsg(_ item: NSItemProvider) {
  90. item.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil) { data, error in
  91. var result: UIImage?
  92. switch data {
  93. case let image as UIImage:
  94. result = image
  95. case let data as Data:
  96. result = ImageFormat.loadImageFrom(data: data)
  97. case let url as URL:
  98. result = ImageFormat.loadImageFrom(url: url)
  99. default:
  100. self.dcContext.logger?.debug("Unexpected data: \(type(of: data))")
  101. result = nil
  102. }
  103. if let result = result {
  104. let path: String? = ImageFormat.saveImage(image: result)
  105. let msg = self.dcContext.newMessage(viewType: DC_MSG_IMAGE)
  106. msg.setFile(filepath: path)
  107. self.messages.append(msg)
  108. self.delegate?.onAttachmentChanged()
  109. if self.imageThumbnail == nil {
  110. self.imageThumbnail = result.scaleDownImage(toMax: self.thumbnailSize)
  111. self.delegate?.onThumbnailChanged()
  112. }
  113. }
  114. if let error = error {
  115. self.dcContext.logger?.error("Could not load share item as image: \(error.localizedDescription)")
  116. }
  117. }
  118. }
  119. private func createMovieMsg(_ item: NSItemProvider) {
  120. item.loadItem(forTypeIdentifier: kUTTypeMovie as String, options: nil) { data, error in
  121. switch data {
  122. case let url as URL:
  123. self.addDcMsg(url: url, viewType: DC_MSG_VIDEO)
  124. self.delegate?.onAttachmentChanged()
  125. if self.imageThumbnail == nil {
  126. DispatchQueue.global(qos: .background).async {
  127. self.imageThumbnail = DcUtils.generateThumbnailFromVideo(url: url)
  128. DispatchQueue.main.async {
  129. self.delegate?.onThumbnailChanged()
  130. }
  131. }
  132. }
  133. default:
  134. self.dcContext.logger?.debug("Unexpected data: \(type(of: data))")
  135. }
  136. if let error = error {
  137. self.dcContext.logger?.error("Could not load share item as video: \(error.localizedDescription)")
  138. }
  139. }
  140. }
  141. private func createAudioMsg(_ item: NSItemProvider) {
  142. createMessageFromItemURL(item: item, typeIdentifier: kUTTypeAudio, viewType: DC_MSG_AUDIO)
  143. }
  144. private func createFileMsg(_ item: NSItemProvider) {
  145. createMessageFromItemURL(item: item, typeIdentifier: kUTTypeFileURL, viewType: DC_MSG_FILE)
  146. }
  147. private func createMessageFromItemURL(item: NSItemProvider, typeIdentifier: CFString, viewType: Int32) {
  148. item.loadItem(forTypeIdentifier: typeIdentifier as String, options: nil) { data, error in
  149. switch data {
  150. case let url as URL:
  151. self.addDcMsg(url: url, viewType: viewType)
  152. self.delegate?.onAttachmentChanged()
  153. if self.imageThumbnail == nil {
  154. self.generateThumbnailRepresentations(url: url)
  155. }
  156. default:
  157. self.dcContext.logger?.debug("Unexpected data: \(type(of: data))")
  158. }
  159. if let error = error {
  160. self.dcContext.logger?.error("Could not load share item: \(error.localizedDescription)")
  161. }
  162. }
  163. }
  164. private func addDcMsg(url: URL, viewType: Int32) {
  165. let msg = dcContext.newMessage(viewType: viewType)
  166. msg.setFile(filepath: url.relativePath)
  167. self.messages.append(msg)
  168. }
  169. private func generateThumbnailRepresentations(url: URL) {
  170. let size: CGSize = CGSize(width: self.thumbnailSize * 2 / 3, height: self.thumbnailSize)
  171. let scale = UIScreen.main.scale
  172. if #available(iOSApplicationExtension 13.0, *) {
  173. let request = QLThumbnailGenerator.Request(fileAt: url,
  174. size: size,
  175. scale: scale,
  176. representationTypes: .all)
  177. let generator = QLThumbnailGenerator.shared
  178. generator.generateRepresentations(for: request) { (thumbnail, _, error) in
  179. DispatchQueue.main.async {
  180. if thumbnail == nil || error != nil {
  181. self.dcContext.logger?.warning(error?.localizedDescription ?? "Could not create thumbnail.")
  182. } else {
  183. self.attachmentThumbnail = thumbnail?.uiImage
  184. self.delegate?.onThumbnailChanged()
  185. }
  186. }
  187. }
  188. } else {
  189. let controller = UIDocumentInteractionController(url: url)
  190. self.attachmentThumbnail = controller.icons.first
  191. self.delegate?.onThumbnailChanged()
  192. }
  193. }
  194. private func addSharedUrl(_ item: NSItemProvider) {
  195. if let delegate = self.delegate {
  196. item.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil) { data, error in
  197. switch data {
  198. case let url as URL:
  199. delegate.onUrlShared(url: url)
  200. default:
  201. self.dcContext.logger?.debug("Unexpected data: \(type(of: data))")
  202. }
  203. if let error = error {
  204. self.dcContext.logger?.error("Could not share URL: \(error.localizedDescription)")
  205. }
  206. }
  207. }
  208. }
  209. }