ShareViewController.swift 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. //
  2. // ShareViewController.swift
  3. // ShareViewController
  4. //
  5. // Created by leizhe on 2022/3/17.
  6. //
  7. import MobileCoreServices
  8. import Social
  9. import UIKit
  10. import UniformTypeIdentifiers
  11. class ShareViewController: UIViewController {
  12. private var sharedData: SharedData = SharedData.init(resources: [])
  13. var groupContainerUrl: URL? {
  14. return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.logseq.logseq")
  15. }
  16. override public func viewDidAppear(_ animated: Bool) {
  17. super.viewDidAppear(animated)
  18. DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
  19. self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
  20. }
  21. }
  22. private func sendData() {
  23. let encoder: JSONEncoder = JSONEncoder()
  24. let data = try? encoder.encode(self.sharedData)
  25. let queryPayload = String(decoding: data!, as: UTF8.self)
  26. let queryItems =
  27. [
  28. URLQueryItem(
  29. name: "payload",
  30. value: queryPayload.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? ""),
  31. ]
  32. var urlComps = URLComponents(string: "logseq://shared?")!
  33. urlComps.queryItems = queryItems
  34. openURL(urlComps.url!)
  35. }
  36. fileprivate func createSharedFileUrl(_ url: URL?) -> URL? {
  37. let tempFilename = url!
  38. .lastPathComponent.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
  39. let copyFileUrl = groupContainerUrl!.appendingPathComponent(tempFilename)
  40. try? Data(contentsOf: url!).write(to: copyFileUrl)
  41. return copyFileUrl
  42. }
  43. // Screenshots, shared images from some system App are passed as UIImage
  44. func saveUIImage(_ image: UIImage) -> URL? {
  45. let dateFormatter = DateFormatter()
  46. dateFormatter.dateFormat = "yyyy-MM-dd-HH-mm-ss"
  47. let filename = dateFormatter.string(from: Date()) + ".png"
  48. let copyFileUrl = groupContainerUrl!.appendingPathComponent(filename)
  49. do {
  50. try image.pngData()?.write(to: copyFileUrl)
  51. return copyFileUrl
  52. } catch {
  53. print(error.localizedDescription)
  54. return nil
  55. }
  56. }
  57. // Can be a path or a web URL
  58. fileprivate func handleTypeUrl(_ attachment: NSItemProvider)
  59. async throws -> SharedResource
  60. {
  61. let results = try await attachment.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil)
  62. let url = results as! URL?
  63. var res = SharedResource()
  64. if url!.isFileURL {
  65. res.name = url!.lastPathComponent
  66. res.ext = url!.pathExtension
  67. res.type = url!.pathExtensionAsMimeType()
  68. res.url = createSharedFileUrl(url)
  69. } else {
  70. res.name = url!.absoluteString
  71. res.type = "text/plain"
  72. }
  73. return res
  74. }
  75. fileprivate func handleTypeText(_ attachment: NSItemProvider)
  76. async throws -> SharedResource?
  77. {
  78. let item = try await attachment.loadItem(forTypeIdentifier: kUTTypeText as String, options: nil)
  79. self.sharedData.text = item as? String
  80. return nil
  81. }
  82. fileprivate func handleTypeMovie(_ attachment: NSItemProvider)
  83. async throws -> SharedResource
  84. {
  85. let results = try await attachment.loadItem(forTypeIdentifier: kUTTypeMovie as String, options: nil)
  86. let url = results as! URL?
  87. let name = url!.lastPathComponent
  88. let ext = url!.pathExtension.lowercased()
  89. let type = url!.pathExtensionAsMimeType()
  90. let sharedUrl = createSharedFileUrl(url)
  91. let res = SharedResource(name: name, ext: ext, type: type, url: sharedUrl)
  92. return res
  93. }
  94. fileprivate func handleTypeImage(_ attachment: NSItemProvider)
  95. async throws -> SharedResource
  96. {
  97. let data = try await attachment.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil)
  98. var res = SharedResource()
  99. switch data {
  100. case let image as UIImage:
  101. res.url = self.saveUIImage(image)
  102. res.ext = "png"
  103. res.name = res.url?.lastPathComponent
  104. res.type = res.url?.pathExtensionAsMimeType()
  105. case let url as URL:
  106. res.name = url.lastPathComponent
  107. res.ext = url.pathExtension.lowercased()
  108. res.type = url.pathExtensionAsMimeType()
  109. res.url = self.createSharedFileUrl(url)
  110. default:
  111. print("Unexpected image data:", type(of: data))
  112. }
  113. return res
  114. }
  115. override public func viewDidLoad() {
  116. super.viewDidLoad()
  117. sharedData.empty()
  118. let inputItems = extensionContext?.inputItems as! [NSExtensionItem]
  119. Task {
  120. try await withThrowingTaskGroup(
  121. of: SharedResource?.self,
  122. body: { taskGroup in
  123. for extensionItem in inputItems {
  124. for attachment in extensionItem.attachments! {
  125. if attachment.hasItemConformingToTypeIdentifier(kUTTypeURL as String) {
  126. taskGroup.addTask {
  127. return try await self.handleTypeUrl(attachment)
  128. }
  129. } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeText as String) {
  130. taskGroup.addTask {
  131. return try await self.handleTypeText(attachment)
  132. }
  133. } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeMovie as String) {
  134. taskGroup.addTask {
  135. return try await self.handleTypeMovie(attachment)
  136. }
  137. } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeImage as String) {
  138. taskGroup.addTask {
  139. return try await self.handleTypeImage(attachment)
  140. }
  141. }
  142. }
  143. }
  144. for try await item in taskGroup {
  145. if let item = item {
  146. self.sharedData.resources.append(item)
  147. }
  148. }
  149. })
  150. self.sendData()
  151. }
  152. }
  153. @discardableResult
  154. @objc func openURL(_ url: URL) -> Bool {
  155. var responder: UIResponder? = self
  156. while responder != nil {
  157. if let application = responder as? UIApplication {
  158. return application.perform(#selector(openURL(_:)), with: url) != nil
  159. }
  160. responder = responder?.next
  161. }
  162. return false
  163. }
  164. }
  165. extension URL {
  166. func pathExtensionAsMimeType() -> String? {
  167. let type = UTType(filenameExtension: self.pathExtension)
  168. return type?.preferredMIMEType
  169. }
  170. }