ChatViewController.swift 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. //
  2. // ChatViewController.swift
  3. // deltachat-ios
  4. //
  5. // Created by Bastian van de Wetering on 08.11.17.
  6. // Copyright © 2017 Jonas Reinsch. All rights reserved.
  7. //
  8. import ALCameraViewController
  9. import MapKit
  10. import MessageInputBar
  11. import MessageKit
  12. import UIKit
  13. class ChatViewController: MessagesViewController {
  14. let outgoingAvatarOverlap: CGFloat = 17.5
  15. let loadCount = 30
  16. let chatId: Int
  17. let refreshControl = UIRefreshControl()
  18. var messageList: [MRMessage] = []
  19. var msgChangedObserver: Any?
  20. var incomingMsgObserver: Any?
  21. var disableWriting = false
  22. var previewView: UIView?
  23. init(chatId: Int, title: String? = nil) {
  24. self.chatId = chatId
  25. super.init(nibName: nil, bundle: nil)
  26. if let title = title {
  27. updateTitleView(title: title, subtitle: nil)
  28. }
  29. }
  30. @objc
  31. func loadMoreMessages() {
  32. DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 1) {
  33. DispatchQueue.main.async {
  34. self.messageList = self.getMessageIds(self.loadCount, from: self.messageList.count) + self.messageList
  35. self.messagesCollectionView.reloadDataAndKeepOffset()
  36. self.refreshControl.endRefreshing()
  37. }
  38. }
  39. }
  40. func loadFirstMessages() {
  41. DispatchQueue.global(qos: .userInitiated).async {
  42. DispatchQueue.main.async {
  43. self.messageList = self.getMessageIds(self.loadCount)
  44. self.messagesCollectionView.reloadData()
  45. self.refreshControl.endRefreshing()
  46. self.messagesCollectionView.scrollToBottom(animated: false)
  47. }
  48. }
  49. }
  50. var textDraft: String? {
  51. // FIXME: need to free pointer
  52. if let draft = dc_get_draft(mailboxPointer, UInt32(chatId)) {
  53. if let text = dc_msg_get_text(draft) {
  54. let s = String(validatingUTF8: text)!
  55. return s
  56. }
  57. return nil
  58. }
  59. return nil
  60. }
  61. func getMessageIds(_ count: Int, from: Int? = nil) -> [MRMessage] {
  62. let c_messageIds = dc_get_chat_msgs(mailboxPointer, UInt32(chatId), 0, 0)
  63. let ids: [Int]
  64. if let from = from {
  65. ids = Utils.copyAndFreeArrayWithOffset(inputArray: c_messageIds, len: count, skipEnd: from)
  66. } else {
  67. ids = Utils.copyAndFreeArrayWithLen(inputArray: c_messageIds, len: count)
  68. }
  69. let markIds: [UInt32] = ids.map { return UInt32($0) }
  70. dc_markseen_msgs(mailboxPointer, UnsafePointer(markIds), Int32(ids.count))
  71. return ids.map {
  72. MRMessage(id: $0)
  73. }
  74. }
  75. required init?(coder _: NSCoder) {
  76. fatalError("init(coder:) has not been implemented")
  77. }
  78. override func viewWillAppear(_ animated: Bool) {
  79. super.viewWillAppear(animated)
  80. let cnt = Int(dc_get_fresh_msg_cnt(mailboxPointer, UInt32(chatId)))
  81. logger.info("updating count for chat \(cnt)")
  82. UIApplication.shared.applicationIconBadgeNumber = cnt
  83. if #available(iOS 11.0, *) {
  84. if disableWriting {
  85. navigationController?.navigationBar.prefersLargeTitles = true
  86. }
  87. }
  88. let nc = NotificationCenter.default
  89. msgChangedObserver = nc.addObserver(forName: dc_notificationChanged,
  90. object: nil, queue: OperationQueue.main) {
  91. notification in
  92. if let ui = notification.userInfo {
  93. if self.chatId == ui["chat_id"] as! Int {
  94. self.updateMessage(ui["message_id"] as! Int)
  95. }
  96. }
  97. }
  98. incomingMsgObserver = nc.addObserver(forName: dc_notificationIncoming,
  99. object: nil, queue: OperationQueue.main) {
  100. notification in
  101. if let ui = notification.userInfo {
  102. if self.chatId == ui["chat_id"] as! Int {
  103. let id = ui["message_id"] as! Int
  104. self.insertMessage(MRMessage(id: id))
  105. }
  106. }
  107. }
  108. }
  109. func setTextDraft() {
  110. if let text = self.messageInputBar.inputTextView.text {
  111. let draft = dc_msg_new(mailboxPointer, DC_MSG_TEXT)
  112. dc_msg_set_text(draft, text.cString(using: .utf8))
  113. dc_set_draft(mailboxPointer, UInt32(chatId), draft)
  114. // cleanup
  115. dc_msg_unref(draft)
  116. }
  117. }
  118. override func viewWillDisappear(_ animated: Bool) {
  119. super.viewWillDisappear(animated)
  120. if #available(iOS 11.0, *) {
  121. if disableWriting {
  122. navigationController?.navigationBar.prefersLargeTitles = false
  123. }
  124. }
  125. }
  126. override func viewDidDisappear(_ animated: Bool) {
  127. super.viewDidDisappear(animated)
  128. setTextDraft()
  129. let nc = NotificationCenter.default
  130. if let msgChangedObserver = self.msgChangedObserver {
  131. nc.removeObserver(msgChangedObserver)
  132. }
  133. if let incomingMsgObserver = self.incomingMsgObserver {
  134. nc.removeObserver(incomingMsgObserver)
  135. }
  136. }
  137. override var inputAccessoryView: UIView? {
  138. if disableWriting {
  139. return nil
  140. }
  141. return messageInputBar
  142. }
  143. override func viewDidLoad() {
  144. super.viewDidLoad()
  145. if !MRConfig.configured {
  146. // TODO: display message about nothing being configured
  147. return
  148. }
  149. let chat = MRChat(id: chatId)
  150. updateTitleView(title: chat.name, subtitle: chat.subtitle)
  151. configureMessageCollectionView()
  152. if !disableWriting {
  153. configureMessageInputBar()
  154. messageInputBar.inputTextView.text = textDraft
  155. messageInputBar.inputTextView.becomeFirstResponder()
  156. }
  157. loadFirstMessages()
  158. }
  159. func configureMessageCollectionView() {
  160. messagesCollectionView.messagesDataSource = self
  161. messagesCollectionView.messageCellDelegate = self
  162. scrollsToBottomOnKeyboardBeginsEditing = true // default false
  163. maintainPositionOnKeyboardFrameChanged = true // default false
  164. messagesCollectionView.addSubview(refreshControl)
  165. refreshControl.addTarget(self, action: #selector(loadMoreMessages), for: .valueChanged)
  166. let layout = messagesCollectionView.collectionViewLayout as? MessagesCollectionViewFlowLayout
  167. layout?.sectionInset = UIEdgeInsets(top: 1, left: 8, bottom: 1, right: 8)
  168. // Hide the outgoing avatar and adjust the label alignment to line up with the messages
  169. layout?.setMessageOutgoingAvatarSize(.zero)
  170. layout?.setMessageOutgoingMessageTopLabelAlignment(LabelAlignment(textAlignment: .right, textInsets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 8)))
  171. layout?.setMessageOutgoingMessageBottomLabelAlignment(LabelAlignment(textAlignment: .right, textInsets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 8)))
  172. // Set outgoing avatar to overlap with the message bubble
  173. layout?.setMessageIncomingMessageTopLabelAlignment(LabelAlignment(textAlignment: .left, textInsets: UIEdgeInsets(top: 0, left: 18, bottom: outgoingAvatarOverlap, right: 0)))
  174. layout?.setMessageIncomingAvatarSize(CGSize(width: 30, height: 30))
  175. layout?.setMessageIncomingMessagePadding(UIEdgeInsets(top: -outgoingAvatarOverlap, left: -18, bottom: outgoingAvatarOverlap, right: 18))
  176. layout?.setMessageIncomingAccessoryViewSize(CGSize(width: 30, height: 30))
  177. layout?.setMessageIncomingAccessoryViewPadding(HorizontalEdgeInsets(left: 8, right: 0))
  178. layout?.setMessageOutgoingAccessoryViewSize(CGSize(width: 30, height: 30))
  179. layout?.setMessageOutgoingAccessoryViewPadding(HorizontalEdgeInsets(left: 0, right: 8))
  180. messagesCollectionView.messagesLayoutDelegate = self
  181. messagesCollectionView.messagesDisplayDelegate = self
  182. }
  183. func configureMessageInputBar() {
  184. messageInputBar.delegate = self
  185. messageInputBar.inputTextView.tintColor = Constants.primaryColor
  186. messageInputBar.sendButton.tintColor = Constants.primaryColor
  187. messageInputBar.isTranslucent = true
  188. messageInputBar.separatorLine.isHidden = true
  189. messageInputBar.inputTextView.tintColor = Constants.primaryColor
  190. messageInputBar.delegate = self
  191. scrollsToBottomOnKeyboardBeginsEditing = true
  192. messageInputBar.inputTextView.backgroundColor = UIColor(red: 245 / 255, green: 245 / 255, blue: 245 / 255, alpha: 1)
  193. messageInputBar.inputTextView.placeholderTextColor = UIColor(red: 0.6, green: 0.6, blue: 0.6, alpha: 1)
  194. messageInputBar.inputTextView.textContainerInset = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 38)
  195. messageInputBar.inputTextView.placeholderLabelInsets = UIEdgeInsets(top: 8, left: 20, bottom: 8, right: 38)
  196. messageInputBar.inputTextView.layer.borderColor = UIColor(red: 200 / 255, green: 200 / 255, blue: 200 / 255, alpha: 1).cgColor
  197. messageInputBar.inputTextView.layer.borderWidth = 1.0
  198. messageInputBar.inputTextView.layer.cornerRadius = 16.0
  199. messageInputBar.inputTextView.layer.masksToBounds = true
  200. messageInputBar.inputTextView.scrollIndicatorInsets = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
  201. configureInputBarItems()
  202. }
  203. private func configureInputBarItems() {
  204. messageInputBar.setLeftStackViewWidthConstant(to: 44, animated: false)
  205. messageInputBar.setRightStackViewWidthConstant(to: 36, animated: false)
  206. let sendButtonImage = UIImage(named: "paper_plane")?.withRenderingMode(.alwaysTemplate)
  207. messageInputBar.sendButton.image = sendButtonImage
  208. messageInputBar.sendButton.tintColor = UIColor(white: 1, alpha: 1)
  209. messageInputBar.sendButton.backgroundColor = UIColor(white: 0.9, alpha: 1)
  210. messageInputBar.sendButton.contentEdgeInsets = UIEdgeInsets(top: 6, left: 0, bottom: 6, right: 0)
  211. messageInputBar.sendButton.setSize(CGSize(width: 34, height: 34), animated: false)
  212. messageInputBar.sendButton.title = nil
  213. messageInputBar.sendButton.layer.cornerRadius = 18
  214. messageInputBar.textViewPadding.right = -40
  215. let leftItems = [
  216. InputBarButtonItem()
  217. .configure {
  218. $0.spacing = .fixed(0)
  219. $0.image = UIImage(named: "camera")?.withRenderingMode(.alwaysTemplate)
  220. $0.setSize(CGSize(width: 36, height: 36), animated: false)
  221. $0.tintColor = UIColor(white: 0.8, alpha: 1)
  222. }.onSelected {
  223. $0.tintColor = Constants.primaryColor
  224. }.onDeselected {
  225. $0.tintColor = UIColor(white: 0.8, alpha: 1)
  226. }.onTouchUpInside { _ in
  227. self.didPressPhotoButton()
  228. },
  229. ]
  230. messageInputBar.setStackViewItems(leftItems, forStack: .left, animated: false)
  231. // This just adds some more flare
  232. messageInputBar.sendButton
  233. .onEnabled { item in
  234. UIView.animate(withDuration: 0.3, animations: {
  235. item.backgroundColor = Constants.primaryColor
  236. })
  237. }.onDisabled { item in
  238. UIView.animate(withDuration: 0.3, animations: {
  239. item.backgroundColor = UIColor(white: 0.9, alpha: 1)
  240. })
  241. }
  242. }
  243. // MARK: - UICollectionViewDataSource
  244. public override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  245. guard let messagesDataSource = messagesCollectionView.messagesDataSource else {
  246. fatalError("Ouch. nil data source for messages")
  247. }
  248. // guard !isSectionReservedForTypingBubble(indexPath.section) else {
  249. // return super.collectionView(collectionView, cellForItemAt: indexPath)
  250. // }
  251. let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
  252. if case .custom = message.kind {
  253. let cell = messagesCollectionView.dequeueReusableCell(CustomCell.self, for: indexPath)
  254. cell.configure(with: message, at: indexPath, and: messagesCollectionView)
  255. return cell
  256. }
  257. return super.collectionView(collectionView, cellForItemAt: indexPath)
  258. }
  259. }
  260. // MARK: - MessagesDataSource
  261. extension ChatViewController: MessagesDataSource {
  262. func numberOfSections(in _: MessagesCollectionView) -> Int {
  263. return messageList.count
  264. }
  265. func currentSender() -> Sender {
  266. let currentSender = Sender(id: "1", displayName: "Alice")
  267. return currentSender
  268. }
  269. func messageForItem(at indexPath: IndexPath, in _: MessagesCollectionView) -> MessageType {
  270. return messageList[indexPath.section]
  271. }
  272. func avatar(for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> Avatar {
  273. let message = messageList[indexPath.section]
  274. let contact = message.fromContact
  275. return Avatar(image: contact.profileImage, initials: Utils.getInitials(inputName: contact.name))
  276. }
  277. func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
  278. if isTimeLabelVisible(at: indexPath) {
  279. return NSAttributedString(string: MessageKitDateFormatter.shared.string(from: message.sentDate), attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 10), NSAttributedString.Key.foregroundColor: UIColor.darkGray])
  280. }
  281. return nil
  282. }
  283. func messageTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
  284. if !isPreviousMessageSameSender(at: indexPath) {
  285. let name = message.sender.displayName
  286. return NSAttributedString(string: name, attributes: [NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .caption1)])
  287. }
  288. return nil
  289. }
  290. func isTimeLabelVisible(at indexPath: IndexPath) -> Bool {
  291. // TODO: better heuristic when to show the time label
  292. return indexPath.section % 3 == 0 && !isPreviousMessageSameSender(at: indexPath)
  293. }
  294. func isPreviousMessageSameSender(at indexPath: IndexPath) -> Bool {
  295. guard indexPath.section - 1 >= 0 else { return false }
  296. return messageList[indexPath.section].fromContactId == messageList[indexPath.section - 1].fromContactId
  297. }
  298. func isInfoMessage(at indexPath: IndexPath) -> Bool {
  299. return messageList[indexPath.section].isInfo
  300. }
  301. func isNextMessageSameSender(at indexPath: IndexPath) -> Bool {
  302. guard indexPath.section + 1 < messageList.count else { return false }
  303. return messageList[indexPath.section].fromContactId == messageList[indexPath.section + 1].fromContactId
  304. }
  305. func messageBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
  306. guard indexPath.section < messageList.count else { return nil }
  307. let m = messageList[indexPath.section]
  308. if !isNextMessageSameSender(at: indexPath), isFromCurrentSender(message: message) {
  309. return NSAttributedString(string: m.stateOutDescription(), attributes: [NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .caption1)])
  310. }
  311. return nil
  312. }
  313. func updateMessage(_ messageId: Int) {
  314. if let index = messageList.firstIndex(where: { $0.id == messageId }) {
  315. dc_markseen_msgs(mailboxPointer, UnsafePointer([UInt32(messageId)]), 1)
  316. messageList[index] = MRMessage(id: messageId)
  317. // Reload section to update header/footer labels
  318. messagesCollectionView.performBatchUpdates({
  319. messagesCollectionView.reloadSections([index])
  320. if index > 0 {
  321. messagesCollectionView.reloadSections([index - 1])
  322. }
  323. if index < messageList.count - 1 {
  324. messagesCollectionView.reloadSections([index + 1])
  325. }
  326. }, completion: { [weak self] _ in
  327. if self?.isLastSectionVisible() == true {
  328. self?.messagesCollectionView.scrollToBottom(animated: true)
  329. }
  330. })
  331. } else {
  332. insertMessage(MRMessage(id: messageId))
  333. }
  334. }
  335. func insertMessage(_ message: MRMessage) {
  336. dc_markseen_msgs(mailboxPointer, UnsafePointer([UInt32(message.id)]), 1)
  337. messageList.append(message)
  338. // Reload last section to update header/footer labels and insert a new one
  339. messagesCollectionView.performBatchUpdates({
  340. messagesCollectionView.insertSections([messageList.count - 1])
  341. if messageList.count >= 2 {
  342. messagesCollectionView.reloadSections([messageList.count - 2])
  343. }
  344. }, completion: { [weak self] _ in
  345. if self?.isLastSectionVisible() == true {
  346. self?.messagesCollectionView.scrollToBottom(animated: true)
  347. }
  348. })
  349. }
  350. func isLastSectionVisible() -> Bool {
  351. guard !messageList.isEmpty else { return false }
  352. let lastIndexPath = IndexPath(item: 0, section: messageList.count - 1)
  353. return messagesCollectionView.indexPathsForVisibleItems.contains(lastIndexPath)
  354. }
  355. }
  356. // MARK: - MessagesDisplayDelegate
  357. extension ChatViewController: MessagesDisplayDelegate {
  358. // MARK: - Text Messages
  359. func textColor(for _: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> UIColor {
  360. return .darkText
  361. }
  362. // MARK: - All Messages
  363. func backgroundColor(for message: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> UIColor {
  364. return isFromCurrentSender(message: message) ? Constants.messagePrimaryColor : Constants.messageSecondaryColor
  365. }
  366. func messageStyle(for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> MessageStyle {
  367. if isInfoMessage(at: indexPath) {
  368. return .custom { view in
  369. view.style = .none
  370. view.backgroundColor = UIColor(alpha: 0, red: 0, green: 0, blue: 0)
  371. view.center.x = self.view.center.x
  372. }
  373. }
  374. var corners: UIRectCorner = []
  375. if isFromCurrentSender(message: message) {
  376. corners.formUnion(.topLeft)
  377. corners.formUnion(.bottomLeft)
  378. if !isPreviousMessageSameSender(at: indexPath) {
  379. corners.formUnion(.topRight)
  380. }
  381. if !isNextMessageSameSender(at: indexPath) {
  382. corners.formUnion(.bottomRight)
  383. }
  384. } else {
  385. corners.formUnion(.topRight)
  386. corners.formUnion(.bottomRight)
  387. if !isPreviousMessageSameSender(at: indexPath) {
  388. corners.formUnion(.topLeft)
  389. }
  390. if !isNextMessageSameSender(at: indexPath) {
  391. corners.formUnion(.bottomLeft)
  392. }
  393. }
  394. return .custom { view in
  395. let radius: CGFloat = 16
  396. let path = UIBezierPath(roundedRect: view.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
  397. let mask = CAShapeLayer()
  398. mask.path = path.cgPath
  399. view.layer.mask = mask
  400. }
  401. }
  402. func configureAvatarView(_ avatarView: AvatarView, for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) {
  403. let message = messageList[indexPath.section]
  404. let contact = message.fromContact
  405. let avatar = Avatar(image: contact.profileImage, initials: Utils.getInitials(inputName: contact.name))
  406. avatarView.set(avatar: avatar)
  407. avatarView.isHidden = isNextMessageSameSender(at: indexPath) || message.isInfo
  408. }
  409. func enabledDetectors(for _: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> [DetectorType] {
  410. return [.url, .date, .phoneNumber, .address]
  411. }
  412. }
  413. // MARK: - MessagesLayoutDelegate
  414. extension ChatViewController: MessagesLayoutDelegate {
  415. func cellTopLabelHeight(for _: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> CGFloat {
  416. if isTimeLabelVisible(at: indexPath) {
  417. return 18
  418. }
  419. return 0
  420. }
  421. func messageTopLabelHeight(for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> CGFloat {
  422. if isFromCurrentSender(message: message) {
  423. return !isPreviousMessageSameSender(at: indexPath) ? 20 : 0
  424. } else {
  425. return !isPreviousMessageSameSender(at: indexPath) ? (20 + outgoingAvatarOverlap) : 0
  426. }
  427. }
  428. func messageBottomLabelHeight(for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> CGFloat {
  429. return (!isNextMessageSameSender(at: indexPath) && isFromCurrentSender(message: message)) && !isInfoMessage(at: indexPath) ? 16 : 0
  430. }
  431. func heightForLocation(message _: MessageType, at _: IndexPath, with _: CGFloat, in _: MessagesCollectionView) -> CGFloat {
  432. return 40
  433. }
  434. func footerViewSize(for _: MessageType, at _: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize {
  435. return CGSize(width: messagesCollectionView.bounds.width, height: 10)
  436. }
  437. @objc func didPressPhotoButton() {
  438. if UIImagePickerController.isSourceTypeAvailable(.camera) {
  439. let cameraViewController = CameraViewController { [weak self] image, _ in
  440. self?.dismiss(animated: true, completion: nil)
  441. DispatchQueue.global().async {
  442. if let pickedImage = image {
  443. let width = Int32(exactly: pickedImage.size.width)!
  444. let height = Int32(exactly: pickedImage.size.height)!
  445. let path = Utils.saveImage(image: pickedImage)
  446. let msg = dc_msg_new(mailboxPointer, DC_MSG_IMAGE)
  447. dc_msg_set_file(msg, path, "image/jpeg")
  448. dc_msg_set_dimension(msg, width, height)
  449. dc_send_msg(mailboxPointer, UInt32(self!.chatId), msg)
  450. // cleanup
  451. dc_msg_unref(msg)
  452. }
  453. }
  454. }
  455. present(cameraViewController, animated: true, completion: nil)
  456. } else {
  457. let alert = UIAlertController(title: "Camera is not available", message: nil, preferredStyle: .alert)
  458. alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
  459. self.dismiss(animated: true, completion: nil)
  460. }))
  461. present(alert, animated: true, completion: nil)
  462. }
  463. }
  464. }
  465. // MARK: - MessageCellDelegate
  466. extension ChatViewController: MessageCellDelegate {
  467. func didTapMessage(in _: MessageCollectionViewCell) {
  468. logger.info("Message tapped")
  469. }
  470. func didTapAvatar(in _: MessageCollectionViewCell) {
  471. logger.info("Avatar tapped")
  472. }
  473. @objc(didTapCellTopLabelIn:) func didTapCellTopLabel(in _: MessageCollectionViewCell) {
  474. logger.info("Top label tapped")
  475. }
  476. func didTapBottomLabel(in _: MessageCollectionViewCell) {
  477. print("Bottom label tapped")
  478. }
  479. }
  480. // MARK: - MessageLabelDelegate
  481. extension ChatViewController: MessageLabelDelegate {
  482. func didSelectAddress(_ addressComponents: [String: String]) {
  483. let mapAddress = Utils.formatAddressForQuery(address: addressComponents)
  484. if let escapedMapAddress = mapAddress.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
  485. // Use query, to handle malformed addresses
  486. if let url = URL(string: "http://maps.apple.com/?q=\(escapedMapAddress)") {
  487. UIApplication.shared.open(url as URL)
  488. }
  489. }
  490. }
  491. func didSelectDate(_ date: Date) {
  492. let interval = date.timeIntervalSinceReferenceDate
  493. if let url = NSURL(string: "calshow:\(interval)") {
  494. UIApplication.shared.open(url as URL)
  495. }
  496. }
  497. func didSelectPhoneNumber(_ phoneNumber: String) {
  498. logger.info("phone open", phoneNumber)
  499. if let escapedPhoneNumber = phoneNumber.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
  500. if let url = NSURL(string: "tel:\(escapedPhoneNumber)") {
  501. UIApplication.shared.open(url as URL)
  502. }
  503. }
  504. }
  505. func didSelectURL(_ url: URL) {
  506. UIApplication.shared.open(url)
  507. }
  508. }
  509. // MARK: - LocationMessageDisplayDelegate
  510. /*
  511. extension ChatViewController: LocationMessageDisplayDelegate {
  512. func annotationViewForLocation(message: MessageType, at indexPath: IndexPath, in messageCollectionView: MessagesCollectionView) -> MKAnnotationView? {
  513. let annotationView = MKAnnotationView(annotation: nil, reuseIdentifier: nil)
  514. let pinImage = #imageLiteral(resourceName: "ic_block_36pt").withRenderingMode(.alwaysTemplate)
  515. annotationView.image = pinImage
  516. annotationView.centerOffset = CGPoint(x: 0, y: -pinImage.size.height / 2)
  517. return annotationView
  518. }
  519. func animationBlockForLocation(message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> ((UIImageView) -> Void)? {
  520. return { view in
  521. view.layer.transform = CATransform3DMakeScale(0, 0, 0)
  522. view.alpha = 0.0
  523. UIView.animate(withDuration: 0.6, delay: 0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0, options: [], animations: {
  524. view.layer.transform = CATransform3DIdentity
  525. view.alpha = 1.0
  526. }, completion: nil)
  527. }
  528. }
  529. }
  530. */
  531. // MARK: - MessageInputBarDelegate
  532. extension ChatViewController: MessageInputBarDelegate {
  533. func messageInputBar(_ inputBar: MessageInputBar, didPressSendButtonWith text: String) {
  534. DispatchQueue.global().async {
  535. dc_send_text_msg(mailboxPointer, UInt32(self.chatId), text)
  536. }
  537. inputBar.inputTextView.text = String()
  538. }
  539. }