ShareViewController.swift 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. import UIKit
  2. import Social
  3. import DcCore
  4. import MobileCoreServices
  5. import Intents
  6. import SDWebImageWebPCoder
  7. import SDWebImage
  8. class ShareViewController: SLComposeServiceViewController {
  9. class SimpleLogger: Logger {
  10. func verbose(_ message: String) {
  11. print("ShareViewController", "verbose", message)
  12. }
  13. func debug(_ message: String) {
  14. print("ShareViewController", "debug", message)
  15. }
  16. func info(_ message: String) {
  17. print("ShareViewController", "info", message)
  18. }
  19. func warning(_ message: String) {
  20. print("ShareViewController", "warning", message)
  21. }
  22. func error(_ message: String) {
  23. print("ShareViewController", "error", message)
  24. }
  25. }
  26. lazy var dbHelper: DatabaseHelper = {
  27. return DatabaseHelper(dcContext: dcContext)
  28. }()
  29. let logger = SimpleLogger()
  30. let dcContext: DcContext = DcContext()
  31. var selectedChatId: Int?
  32. var selectedChat: DcChat?
  33. var shareAttachment: ShareAttachment?
  34. var isAccountConfigured: Bool = true
  35. var isLoading: Bool = false
  36. var previewImageHeightConstraint: NSLayoutConstraint?
  37. var previewImageWidthConstraint: NSLayoutConstraint?
  38. lazy var preview: SDAnimatedImageView? = {
  39. let imageView = SDAnimatedImageView(frame: .zero)
  40. imageView.clipsToBounds = true
  41. imageView.shouldGroupAccessibilityChildren = true
  42. imageView.isAccessibilityElement = false
  43. imageView.contentMode = .scaleAspectFit
  44. previewImageHeightConstraint = imageView.constraintHeightTo(96)
  45. previewImageWidthConstraint = imageView.constraintWidthTo(96)
  46. previewImageHeightConstraint?.isActive = true
  47. previewImageWidthConstraint?.isActive = true
  48. return imageView
  49. }()
  50. override func viewDidLoad() {
  51. super.viewDidLoad()
  52. setupNavigationBar()
  53. // workaround for iOS13 bug
  54. if #available(iOS 13.0, *) {
  55. _ = NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidShowNotification, object: nil, queue: .main) { (_) in
  56. if let layoutContainerView = self.view.subviews.last {
  57. layoutContainerView.frame.size.height += 10
  58. }
  59. }
  60. }
  61. placeholder = String.localized("chat_input_placeholder")
  62. let webPCoder = SDImageWebPCoder.shared
  63. SDImageCodersManager.shared.addCoder(webPCoder)
  64. }
  65. override func presentationAnimationDidFinish() {
  66. if dbHelper.currentDatabaseLocation == dbHelper.sharedDbFile {
  67. dcContext.logger = self.logger
  68. dcContext.openDatabase(dbFile: dbHelper.sharedDbFile)
  69. isAccountConfigured = dcContext.isConfigured()
  70. if isAccountConfigured {
  71. if #available(iOSApplicationExtension 13.0, *) {
  72. if let intent = self.extensionContext?.intent as? INSendMessageIntent, let chatId = Int(intent.conversationIdentifier ?? "") {
  73. selectedChatId = chatId
  74. }
  75. }
  76. if selectedChatId == nil {
  77. selectedChatId = dcContext.getChatIdByContactId(contactId: Int(DC_CONTACT_ID_SELF))
  78. }
  79. if let chatId = selectedChatId {
  80. selectedChat = dcContext.getChat(chatId: chatId)
  81. }
  82. DispatchQueue.global(qos: .userInitiated).async { [weak self] in
  83. guard let self = self else { return }
  84. self.shareAttachment = ShareAttachment(dcContext: self.dcContext, inputItems: self.extensionContext?.inputItems, delegate: self)
  85. }
  86. }
  87. reloadConfigurationItems()
  88. validateContent()
  89. } else {
  90. cancel()
  91. }
  92. }
  93. override func loadPreviewView() -> UIView! {
  94. return preview
  95. }
  96. override func isContentValid() -> Bool {
  97. // Do validation of contentText and/or NSExtensionContext attachments here
  98. return isAccountConfigured && !isLoading && (!(contentText?.isEmpty ?? true) || !(self.shareAttachment?.isEmpty ?? true))
  99. }
  100. private func setupNavigationBar() {
  101. guard let item = navigationController?.navigationBar.items?.first else { return }
  102. let button = UIBarButtonItem(
  103. title: String.localized("menu_send"),
  104. style: .done,
  105. target: self,
  106. action: #selector(appendPostTapped))
  107. item.rightBarButtonItem? = button
  108. }
  109. /// Invoked when the user wants to post.
  110. @objc
  111. private func appendPostTapped() {
  112. if let chatId = self.selectedChatId {
  113. guard var messages = shareAttachment?.messages else { return }
  114. if !self.contentText.isEmpty {
  115. if messages.count == 1 {
  116. messages[0].text?.append(self.contentText)
  117. } else {
  118. let message = dcContext.newMessage(viewType: DC_MSG_TEXT)
  119. message.text = self.contentText
  120. messages.insert(message, at: 0)
  121. }
  122. }
  123. let chatListController = SendingController(chatId: chatId, dcMsgs: messages, dcContext: dcContext)
  124. chatListController.delegate = self
  125. self.pushConfigurationViewController(chatListController)
  126. }
  127. }
  128. func quit() {
  129. if dbHelper.currentDatabaseLocation == dbHelper.sharedDbFile {
  130. dcContext.closeDatabase()
  131. }
  132. // Inform the host that we're done, so it un-blocks its UI.
  133. self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
  134. }
  135. override func configurationItems() -> [Any]! {
  136. let item = SLComposeSheetConfigurationItem()
  137. if isAccountConfigured {
  138. logger.debug("configurationItems")
  139. // To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
  140. item?.title = String.localized("forward_to")
  141. item?.value = selectedChat?.name
  142. logger.debug("configurationItems chat name: \(String(describing: selectedChat?.name))")
  143. item?.tapHandler = {
  144. let chatListController = ChatListController(dcContext: self.dcContext, chatListDelegate: self)
  145. self.pushConfigurationViewController(chatListController)
  146. }
  147. } else {
  148. item?.title = String.localized("share_account_not_configured")
  149. }
  150. return [item as Any]
  151. }
  152. override func didSelectCancel() {
  153. quit()
  154. }
  155. }
  156. extension ShareViewController: ChatListDelegate {
  157. func onChatSelected(chatId: Int) {
  158. selectedChatId = chatId
  159. selectedChat = dcContext.getChat(chatId: chatId)
  160. reloadConfigurationItems()
  161. popConfigurationViewController()
  162. }
  163. }
  164. extension ShareViewController: SendingControllerDelegate {
  165. func onSendingAttemptFinished() {
  166. DispatchQueue.main.async {
  167. self.popConfigurationViewController()
  168. UserDefaults.shared?.set(true, forKey: UserDefaults.hasExtensionAttemptedToSend)
  169. self.quit()
  170. }
  171. }
  172. }
  173. extension ShareViewController: ShareAttachmentDelegate {
  174. func onUrlShared(url: URL) {
  175. DispatchQueue.main.async {
  176. if var contentText = self.contentText, !contentText.isEmpty {
  177. contentText.append("\n\(url.absoluteString)")
  178. self.textView.text = contentText
  179. } else {
  180. self.textView.text = "\(url.absoluteString)"
  181. }
  182. self.validateContent()
  183. }
  184. }
  185. func onAttachmentChanged() {
  186. DispatchQueue.main.async {
  187. self.validateContent()
  188. }
  189. }
  190. func onThumbnailChanged() {
  191. DispatchQueue.main.async { [weak self] in
  192. guard let self = self else { return }
  193. if let preview = self.preview {
  194. preview.image = self.shareAttachment?.thumbnail ?? nil
  195. if let image = preview.image, image.sd_imageFormat == .webP {
  196. self.previewImageWidthConstraint?.isActive = false
  197. self.previewImageHeightConstraint?.isActive = false
  198. preview.centerInSuperview()
  199. self.textView.text = nil
  200. self.textView.attributedText = nil
  201. self.placeholder = nil
  202. self.textView.isHidden = true
  203. }
  204. }
  205. }
  206. }
  207. func onLoadingStarted() {
  208. isLoading = true
  209. }
  210. func onLoadingFinished() {
  211. isLoading = false
  212. self.validateContent()
  213. }
  214. }