ShareAttachment.swift 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  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. var msg: DcMsg
  106. if result.sd_imageFormat == .webP {
  107. msg = self.dcContext.newMessage(viewType: DC_MSG_STICKER)
  108. } else {
  109. msg = self.dcContext.newMessage(viewType: DC_MSG_IMAGE)
  110. }
  111. msg.setFile(filepath: path)
  112. self.messages.append(msg)
  113. self.delegate?.onAttachmentChanged()
  114. if self.imageThumbnail == nil {
  115. self.imageThumbnail = result
  116. self.delegate?.onThumbnailChanged()
  117. }
  118. }
  119. if let error = error {
  120. self.dcContext.logger?.error("Could not load share item as image: \(error.localizedDescription)")
  121. }
  122. }
  123. }
  124. private func createMovieMsg(_ item: NSItemProvider) {
  125. item.loadItem(forTypeIdentifier: kUTTypeMovie as String, options: nil) { data, error in
  126. switch data {
  127. case let url as URL:
  128. self.addDcMsg(url: url, viewType: DC_MSG_VIDEO)
  129. self.delegate?.onAttachmentChanged()
  130. if self.imageThumbnail == nil {
  131. DispatchQueue.global(qos: .background).async {
  132. self.imageThumbnail = DcUtils.generateThumbnailFromVideo(url: url)
  133. DispatchQueue.main.async {
  134. self.delegate?.onThumbnailChanged()
  135. }
  136. }
  137. }
  138. default:
  139. self.dcContext.logger?.debug("Unexpected data: \(type(of: data))")
  140. }
  141. if let error = error {
  142. self.dcContext.logger?.error("Could not load share item as video: \(error.localizedDescription)")
  143. }
  144. }
  145. }
  146. private func createAudioMsg(_ item: NSItemProvider) {
  147. createMessageFromItemURL(item: item, typeIdentifier: kUTTypeAudio, viewType: DC_MSG_AUDIO)
  148. }
  149. private func createFileMsg(_ item: NSItemProvider) {
  150. createMessageFromItemURL(item: item, typeIdentifier: kUTTypeFileURL, viewType: DC_MSG_FILE)
  151. }
  152. private func createMessageFromItemURL(item: NSItemProvider, typeIdentifier: CFString, viewType: Int32) {
  153. item.loadItem(forTypeIdentifier: typeIdentifier as String, options: nil) { data, error in
  154. switch data {
  155. case let url as URL:
  156. self.addDcMsg(url: url, viewType: viewType)
  157. self.delegate?.onAttachmentChanged()
  158. if self.imageThumbnail == nil {
  159. self.generateThumbnailRepresentations(url: url)
  160. }
  161. default:
  162. self.dcContext.logger?.debug("Unexpected data: \(type(of: data))")
  163. }
  164. if let error = error {
  165. self.dcContext.logger?.error("Could not load share item: \(error.localizedDescription)")
  166. }
  167. }
  168. }
  169. private func addDcMsg(url: URL, viewType: Int32) {
  170. let msg = dcContext.newMessage(viewType: viewType)
  171. msg.setFile(filepath: url.relativePath)
  172. self.messages.append(msg)
  173. }
  174. private func generateThumbnailRepresentations(url: URL) {
  175. let size: CGSize = CGSize(width: self.thumbnailSize * 2 / 3, height: self.thumbnailSize)
  176. let scale = UIScreen.main.scale
  177. if #available(iOSApplicationExtension 13.0, *) {
  178. let request = QLThumbnailGenerator.Request(fileAt: url,
  179. size: size,
  180. scale: scale,
  181. representationTypes: .all)
  182. let generator = QLThumbnailGenerator.shared
  183. generator.generateRepresentations(for: request) { (thumbnail, _, error) in
  184. DispatchQueue.main.async {
  185. if thumbnail == nil || error != nil {
  186. self.dcContext.logger?.warning(error?.localizedDescription ?? "Could not create thumbnail.")
  187. } else {
  188. self.attachmentThumbnail = thumbnail?.uiImage
  189. self.delegate?.onThumbnailChanged()
  190. }
  191. }
  192. }
  193. } else {
  194. let controller = UIDocumentInteractionController(url: url)
  195. self.attachmentThumbnail = controller.icons.first
  196. self.delegate?.onThumbnailChanged()
  197. }
  198. }
  199. private func addSharedUrl(_ item: NSItemProvider) {
  200. if let delegate = self.delegate {
  201. item.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil) { data, error in
  202. switch data {
  203. case let url as URL:
  204. delegate.onUrlShared(url: url)
  205. default:
  206. self.dcContext.logger?.debug("Unexpected data: \(type(of: data))")
  207. }
  208. if let error = error {
  209. self.dcContext.logger?.error("Could not share URL: \(error.localizedDescription)")
  210. }
  211. }
  212. }
  213. }
  214. }