ShareAttachment.swift 9.0 KB

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