IMChatMessageViewCell.swift 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. //
  2. // IMChatMessageViewCell.swift
  3. // O2Platform
  4. //
  5. // Created by FancyLou on 2020/6/8.
  6. // Copyright © 2020 zoneland. All rights reserved.
  7. //
  8. import UIKit
  9. import CocoaLumberjack
  10. protocol IMChatMessageDelegate {
  11. func playAudio(info: IMMessageBodyInfo, id: String?)
  12. func openImageOrFileMessage(info: IMMessageBodyInfo)
  13. func openLocatinMap(info: IMMessageBodyInfo)
  14. func openApplication(storyboard: String)
  15. func openWork(workId: String)
  16. }
  17. class IMChatMessageViewCell: UITableViewCell {
  18. @IBOutlet weak var avatarImage: UIImageView!
  19. @IBOutlet weak var titleLabel: UILabel!
  20. @IBOutlet weak var timeLabel: UILabel!
  21. @IBOutlet weak var messageBackgroundView: UIView!
  22. @IBOutlet weak var messageBackgroundWidth: NSLayoutConstraint!
  23. @IBOutlet weak var messageBackgroundHeight: NSLayoutConstraint!
  24. private lazy var audioView: IMAudioView = {
  25. let view = Bundle.main.loadNibNamed("IMAudioView", owner: self, options: nil)?.first as! IMAudioView
  26. view.frame = CGRect(x: 0, y: 0, width: IMAudioView.IMAudioView_width, height: IMAudioView.IMAudioView_height)
  27. return view
  28. }()
  29. //位置消息 主体view
  30. private lazy var locationView: IMLocationView = {
  31. let view = Bundle.main.loadNibNamed("IMLocationView", owner: self, options: nil)?.first as! IMLocationView
  32. view.frame = CGRect(x: 0, y: 0, width: IMLocationView.IMLocationViewWidth, height: IMLocationView.IMLocationViewHeight)
  33. return view
  34. }()
  35. //文件消息
  36. private lazy var fileView: IMFileView = {
  37. let view = Bundle.main.loadNibNamed("IMFileView", owner: self, options: nil)?.first as! IMFileView
  38. view.frame = CGRect(x: 0, y: 0, width: IMFileView.IMFileView_width, height: IMFileView.IMFileView_height)
  39. return view
  40. }()
  41. // 流程消息卡片
  42. private lazy var processView: IMProcessCardView = {
  43. let view = Bundle.main.loadNibNamed("IMProcessCardView", owner: self, options: nil)?.first as! IMProcessCardView
  44. view.frame = CGRect(x: 0, y: 0, width: IMProcessCardView.IMProcessCardView_width, height: IMProcessCardView.IMProcessCardView_height)
  45. return view
  46. }()
  47. var delegate: IMChatMessageDelegate?
  48. //是否正在播放音频 音频消息使用
  49. private var isPlayingAudio = false
  50. var msgInfo: IMMessageInfo? = nil
  51. override func awakeFromNib() {
  52. super.awakeFromNib()
  53. }
  54. override func setSelected(_ selected: Bool, animated: Bool) {
  55. super.setSelected(selected, animated: animated)
  56. }
  57. // UIMenuController菜单弹出 这个参数很重要
  58. override var canBecomeFirstResponder: Bool {
  59. return true
  60. }
  61. //普通通知消息
  62. func setInstantContent(item: InstantMessage) {
  63. if let time = item.createTime {
  64. let date = time.toDate(formatter: "yyyy-MM-dd HH:mm:ss")
  65. self.timeLabel.text = date.friendlyTime()
  66. }
  67. self.messageBackgroundView.removeSubviews()
  68. if let msg = item.title {
  69. let msgLabel = textMsgRender(msg: msg)
  70. setColorAndClickEvent(item: item, label: msgLabel)
  71. }
  72. if let type = item.type {
  73. if type.starts(with: "task_") {
  74. self.avatarImage.image = UIImage(named: "icon_daiban")
  75. self.titleLabel.text = "待办消息"
  76. } else if type.starts(with: "taskCompleted_") {
  77. self.avatarImage.image = UIImage(named: "icon_taskcompleted")
  78. self.titleLabel.text = "已办消息"
  79. } else if type.starts(with: "read_") {
  80. self.avatarImage.image = UIImage(named: "icon_read")
  81. self.titleLabel.text = "待阅消息"
  82. } else if type.starts(with: "readCompleted_") {
  83. self.avatarImage.image = UIImage(named: "icon_readcompleted")
  84. self.titleLabel.text = "已阅消息"
  85. } else if type.starts(with: "review_") || type.starts(with: "work_") || type.starts(with: "process_") {
  86. self.avatarImage.image = UIImage(named: "icon_daiban")
  87. self.titleLabel.text = "工作消息"
  88. } else if type.starts(with: "meeting_") {
  89. self.avatarImage.image = UIImage(named: "icon_meeting")
  90. self.titleLabel.text = "会议消息"
  91. } else if type.starts(with: "attachment_") {
  92. self.avatarImage.image = UIImage(named: "icon_yunpan")
  93. self.titleLabel.text = "云盘消息"
  94. } else if type.starts(with: "calendar_") {
  95. self.avatarImage.image = UIImage(named: "icon_calendar")
  96. self.titleLabel.text = "日历消息"
  97. } else if type.starts(with: "cms_") {
  98. self.avatarImage.image = UIImage(named: "icon_cms")
  99. self.titleLabel.text = "信息中心消息"
  100. } else if type.starts(with: "bbs_") {
  101. self.avatarImage.image = UIImage(named: "icon_bbs")
  102. self.titleLabel.text = "论坛消息"
  103. } else if type.starts(with: "mind_") {
  104. self.avatarImage.image = UIImage(named: "icon_mindMap")
  105. self.titleLabel.text = "脑图消息"
  106. } else {
  107. self.avatarImage.image = UIImage(named: "icon_email")
  108. self.titleLabel.text = "其他消息"
  109. }
  110. }
  111. }
  112. private func setcc(label:UILabel, clickEvent: ((UITapGestureRecognizer)->Void)?) {
  113. if let textString = label.text {
  114. let attributedString = NSMutableAttributedString(string: textString)
  115. attributedString.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: NSRange(location: 0, length: attributedString.length))
  116. attributedString.addAttribute(.foregroundColor, value: base_blue_color, range: NSRange(location: 0, length: attributedString.length))
  117. label.attributedText = attributedString
  118. label.addTapGesture(action: clickEvent)
  119. }
  120. }
  121. private func setColorAndClickEvent(item: InstantMessage, label:UILabel) {
  122. func parseWorkId(body: String) -> String? {
  123. if let jsonData = String(body).data(using: .utf8) {
  124. let dicArr = try! JSONSerialization.jsonObject(with: jsonData, options: .allowFragments) as! [String:AnyObject]
  125. if let work = dicArr["work"] as? String {
  126. return work
  127. }
  128. if let workCompleted = dicArr["workCompleted"] as? String {
  129. return workCompleted
  130. }
  131. }
  132. return nil
  133. }
  134. if let type = item.type {
  135. if type.starts(with: "task_") {
  136. if !type.contains("_delete") {
  137. guard let body = item.body else {
  138. return
  139. }
  140. guard let workId = parseWorkId(body: body) else {
  141. return
  142. }
  143. setcc(label: label) { tap in
  144. //打开工作 ?
  145. self.delegate?.openWork(workId: workId)
  146. }
  147. }
  148. } else if type.starts(with: "taskCompleted_") {
  149. if !type.contains("_delete") {
  150. guard let body = item.body else {
  151. return
  152. }
  153. guard let workId = parseWorkId(body: body) else {
  154. return
  155. }
  156. setcc(label: label) { tap in
  157. //打开已办
  158. self.delegate?.openWork(workId: workId)
  159. }
  160. }
  161. } else if type.starts(with: "read_") {
  162. if !type.contains("_delete") {
  163. guard let body = item.body else {
  164. return
  165. }
  166. guard let workId = parseWorkId(body: body) else {
  167. return
  168. }
  169. setcc(label: label) { tap in
  170. //打开待阅
  171. self.delegate?.openWork(workId: workId)
  172. }
  173. }
  174. } else if type.starts(with: "readCompleted_") {
  175. if !type.contains("_delete") {
  176. guard let body = item.body else {
  177. return
  178. }
  179. guard let workId = parseWorkId(body: body) else {
  180. return
  181. }
  182. setcc(label: label) { tap in
  183. //打开已阅
  184. self.delegate?.openWork(workId: workId)
  185. }
  186. }
  187. } else if type.starts(with: "review_") || type.starts(with: "work_") || type.starts(with: "process_") {
  188. if !type.contains("_delete") {
  189. guard let body = item.body else {
  190. return
  191. }
  192. guard let workId = parseWorkId(body: body) else {
  193. return
  194. }
  195. setcc(label: label) { tap in
  196. //打开 其他工作
  197. self.delegate?.openWork(workId: workId)
  198. }
  199. }
  200. } else if type.starts(with: "meeting_") {
  201. setcc(label: label) { tap in
  202. //打开会议模块
  203. self.delegate?.openApplication(storyboard: "meeting")
  204. }
  205. } else if type.starts(with: "attachment_") {
  206. setcc(label: label) { tap in
  207. //打开云盘
  208. self.delegate?.openApplication(storyboard: "CloudFile")
  209. }
  210. } else if type.starts(with: "calendar_") {
  211. setcc(label: label) { tap in
  212. //打开日历
  213. self.delegate?.openApplication(storyboard: "calendar")
  214. }
  215. } else if type.starts(with: "cms_") {
  216. setcc(label: label) { tap in
  217. //打开cms
  218. self.delegate?.openApplication(storyboard: "information")
  219. }
  220. } else if type.starts(with: "bbs_") {
  221. setcc(label: label) { tap in
  222. //打开论坛
  223. self.delegate?.openApplication(storyboard: "bbs")
  224. }
  225. } else if type.starts(with: "mind_") {
  226. setcc(label: label) { tap in
  227. //打开脑图
  228. self.delegate?.openApplication(storyboard: "mind")
  229. }
  230. } else {
  231. }
  232. }
  233. }
  234. //聊天消息
  235. func setContent(item: IMMessageInfo, isPlayingAudio: Bool) {
  236. self.msgInfo = item
  237. self.isPlayingAudio = isPlayingAudio
  238. //time
  239. if let time = item.createTime {
  240. let date = time.toDate(formatter: "yyyy-MM-dd HH:mm:ss")
  241. self.timeLabel.text = date.friendlyTime()
  242. }
  243. //name avatart
  244. if let person = item.createPerson {
  245. let urlstr = AppDelegate.o2Collect.generateURLWithAppContextKey(ContactContext.contactsContextKeyV2, query: ContactContext.personIconByNameQueryV2, parameter: ["##name##": person as AnyObject], generateTime: false)
  246. if let u = URL(string: urlstr!) {
  247. self.avatarImage.hnk_setImageFromURL(u)
  248. } else {
  249. self.avatarImage.image = UIImage(named: "icon_men")
  250. }
  251. //姓名
  252. self.titleLabel.text = person.split("@").first ?? ""
  253. } else {
  254. self.avatarImage.image = UIImage(named: "icon_men")
  255. self.titleLabel.text = ""
  256. }
  257. self.messageBackgroundView.removeSubviews()
  258. if let jsonBody = item.body, let body = parseJson(msg: jsonBody) {
  259. if body.type == o2_im_msg_type_emoji {
  260. emojiMsgRender(emoji: body.body!)
  261. } else if body.type == o2_im_msg_type_image {
  262. imageMsgRender(info: body)
  263. } else if o2_im_msg_type_audio == body.type {
  264. audioMsgRender(info: body, id: item.id)
  265. } else if o2_im_msg_type_location == body.type {
  266. locationMsgRender(info: body)
  267. } else if o2_im_msg_type_file == body.type {
  268. fileMsgRender(info: body)
  269. } else if o2_im_msg_type_process == body.type {
  270. processMsgRender(info: body)
  271. } else {
  272. _ = textMsgRender(msg: body.body ?? "")
  273. }
  274. }
  275. }
  276. // 流程工作卡片消息
  277. private func processMsgRender(info: IMMessageBodyInfo) {
  278. self.messageBackgroundWidth.constant = IMProcessCardView.IMProcessCardView_width + 20
  279. self.messageBackgroundHeight.constant = IMProcessCardView.IMProcessCardView_height + 20
  280. self.processView.translatesAutoresizingMaskIntoConstraints = false
  281. self.messageBackgroundView.addSubview(self.processView)
  282. self.processView.setCardInfo(info: info)
  283. self.constraintWithContent(contentView: self.processView)
  284. self.processView.addTapGesture { tap in
  285. self.delegate?.openWork(workId: info.work ?? "")
  286. }
  287. }
  288. //文件消息
  289. private func fileMsgRender(info: IMMessageBodyInfo) {
  290. self.messageBackgroundWidth.constant = IMFileView.IMFileView_width + 20
  291. self.messageBackgroundHeight.constant = IMFileView.IMFileView_height + 20
  292. self.fileView.translatesAutoresizingMaskIntoConstraints = false
  293. self.messageBackgroundView.addSubview(self.fileView)
  294. if let fileId = info.fileId {
  295. self.fileView.setFile(name: info.fileName ?? fileId, fileExt: info.fileExtension)
  296. }else if let filePath = info.fileTempPath {
  297. let ext = filePath.pathExtension
  298. let fileName = filePath.pathFileName
  299. self.fileView.setFile(name: fileName, fileExt: ext)
  300. }
  301. self.fileView.addTapGesture { (tap) in
  302. self.delegate?.openImageOrFileMessage(info: info)
  303. }
  304. //点击事件
  305. self.constraintWithContent(contentView: self.fileView)
  306. }
  307. //位置消息
  308. private func locationMsgRender(info: IMMessageBodyInfo) {
  309. self.messageBackgroundWidth.constant = IMLocationView.IMLocationViewWidth + 20
  310. self.messageBackgroundHeight.constant = IMLocationView.IMLocationViewHeight + 20
  311. self.locationView.translatesAutoresizingMaskIntoConstraints = false
  312. self.messageBackgroundView.addSubview(self.locationView)
  313. self.locationView.setLocationAddress(address: info.address ?? "")
  314. //点击打开地址
  315. self.locationView.addTapGesture { (tap) in
  316. //open map view
  317. self.delegate?.openLocatinMap(info: info)
  318. }
  319. self.constraintWithContent(contentView: self.locationView)
  320. }
  321. //音频消息
  322. private func audioMsgRender(info: IMMessageBodyInfo, id: String?) {
  323. let width = IMAudioView.IMAudioView_width + 20
  324. let height = IMAudioView.IMAudioView_height + 20
  325. self.messageBackgroundWidth.constant = width
  326. self.messageBackgroundHeight.constant = height
  327. //背景图片
  328. let bgImg = UIImageView(frame: CGRect(x: 0, y: 0, width: width, height: height))
  329. let insets = UIEdgeInsets(top: 28, left: 10, bottom: 5, right: 5); // 上、左、下、右
  330. var bubble = UIImage(named: "chat_bubble_incomming")
  331. bubble = bubble?.resizableImage(withCapInsets: insets, resizingMode: .stretch)
  332. bgImg.image = bubble
  333. self.messageBackgroundView.addSubview(bgImg)
  334. self.audioView.translatesAutoresizingMaskIntoConstraints = false
  335. self.messageBackgroundView.addSubview(self.audioView)
  336. self.audioView.setDuration(duration: info.audioDuration ?? "0")
  337. if self.isPlayingAudio {
  338. self.audioView.playAudioGif()
  339. }else {
  340. self.audioView.stopPlayAudioGif()
  341. }
  342. self.audioView.addTapGesture { (tap) in
  343. self.delegate?.playAudio(info: info, id: id)
  344. }
  345. self.constraintWithContent(contentView: self.audioView)
  346. }
  347. private func constraintWithContent(contentView: UIView) {
  348. let top = NSLayoutConstraint(item: contentView, attribute: .top, relatedBy: .equal, toItem: contentView.superview!, attribute: .top, multiplier: 1, constant: 10)
  349. let bottom = NSLayoutConstraint(item: contentView.superview!, attribute: .bottom, relatedBy: .equal, toItem: contentView, attribute: .bottom, multiplier: 1, constant: 10)
  350. let left = NSLayoutConstraint(item: contentView, attribute: .leading, relatedBy: .equal, toItem: contentView.superview!, attribute: .leading, multiplier: 1, constant: 10)
  351. let right = NSLayoutConstraint(item: contentView.superview!, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1, constant: 10)
  352. NSLayoutConstraint.activate([top, bottom, left, right])
  353. }
  354. //图片消息
  355. private func imageMsgRender(info: IMMessageBodyInfo) {
  356. let width: CGFloat = 144
  357. let height: CGFloat = 192
  358. self.messageBackgroundWidth.constant = width + 20
  359. self.messageBackgroundHeight.constant = height + 20
  360. //图片
  361. let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: width, height: height))
  362. if let fileId = info.fileId {
  363. DDLogDebug("file id :\(fileId)")
  364. let urlStr = AppDelegate.o2Collect.generateURLWithAppContextKey(
  365. CommunicateContext.communicateContextKey,
  366. query: CommunicateContext.imDownloadImageWithSizeQuery,
  367. parameter: ["##id##": fileId as AnyObject,
  368. "##width##": "144" as AnyObject,
  369. "##height##": "192" as AnyObject], generateTime: false)
  370. if let url = URL(string: urlStr!) {
  371. imageView.hnk_setImageFromURL(url)
  372. } else {
  373. imageView.image = UIImage(named: "chat_image")
  374. }
  375. } else if let filePath = info.fileTempPath {
  376. DDLogDebug("filePath :\(filePath)")
  377. imageView.hnk_setImageFromFile(filePath)
  378. } else {
  379. imageView.image = UIImage(named: "chat_image")
  380. }
  381. imageView.translatesAutoresizingMaskIntoConstraints = false
  382. self.messageBackgroundView.addSubview(imageView)
  383. imageView.addTapGesture { (tap) in
  384. self.delegate?.openImageOrFileMessage(info: info)
  385. }
  386. self.constraintWithContent(contentView: imageView)
  387. }
  388. private func emojiMsgRender(emoji: String) {
  389. let emojiSize = 36
  390. let width = CGFloat(emojiSize + 20)
  391. let height = CGFloat(emojiSize + 20)
  392. self.messageBackgroundWidth.constant = width
  393. self.messageBackgroundHeight.constant = height
  394. //背景图片
  395. let bgImg = UIImageView(frame: CGRect(x: 0, y: 0, width: width, height: height))
  396. let insets = UIEdgeInsets(top: 28, left: 10, bottom: 5, right: 5); // 上、左、下、右
  397. var bubble = UIImage(named: "chat_bubble_incomming")
  398. bubble = bubble?.resizableImage(withCapInsets: insets, resizingMode: .stretch)
  399. bgImg.image = bubble
  400. self.messageBackgroundView.addSubview(bgImg)
  401. //表情图
  402. let emojiImage = UIImageView(frame: CGRect(x: 0, y: 0, width: emojiSize, height: emojiSize))
  403. let bundle = Bundle().o2EmojiBundle(anyClass: IMChatMessageViewCell.self)
  404. let path = o2ImEmojiPath(emojiBody: emoji)
  405. emojiImage.image = UIImage(named: path, in: bundle, compatibleWith: nil)
  406. emojiImage.translatesAutoresizingMaskIntoConstraints = false
  407. self.messageBackgroundView.addSubview(emojiImage)
  408. self.constraintWithContent(contentView: emojiImage)
  409. }
  410. private func textMsgRender(msg: String) -> UILabel {
  411. let size = msg.getSizeWithMaxWidth(fontSize: 16, maxWidth: messageWidth)
  412. self.messageBackgroundWidth.constant = size.width + 28
  413. self.messageBackgroundHeight.constant = size.height + 28
  414. //背景图片
  415. let bgImg = UIImageView(frame: CGRect(x: 0, y: 0, width: size.width + 28, height: size.height + 28))
  416. let insets = UIEdgeInsets(top: 28, left: 10, bottom: 5, right: 5); // 上、左、下、右
  417. var bubble = UIImage(named: "chat_bubble_incomming")
  418. bubble = bubble?.resizableImage(withCapInsets: insets, resizingMode: .stretch)
  419. bgImg.image = bubble
  420. self.messageBackgroundView.addSubview(bgImg)
  421. //文字
  422. let label = generateMessagelabel(str: msg, size: size)
  423. label.translatesAutoresizingMaskIntoConstraints = false
  424. self.messageBackgroundView.addSubview(label)
  425. self.constraintWithContent(contentView: label)
  426. return label
  427. // let top = NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: label.superview!, attribute: .top, multiplier: 1, constant: 10)
  428. // let left = NSLayoutConstraint(item: label, attribute: .leading, relatedBy: .equal, toItem: label.superview!, attribute: .leading, multiplier: 1, constant: 10)
  429. // let right = NSLayoutConstraint(item: label.superview!, attribute: .trailing, relatedBy: .equal, toItem: label, attribute: .trailing, multiplier: 1, constant: 10)
  430. // NSLayoutConstraint.activate([top, left, right])
  431. }
  432. private func generateMessagelabel(str: String, size: CGSize) -> UILabel {
  433. let label = UILabel(frame: CGRect(x: 0, y: 0, width: size.width + 8, height: size.height + 8))
  434. label.text = str
  435. label.font = UIFont.systemFont(ofSize: 16)
  436. label.numberOfLines = 0
  437. label.lineBreakMode = .byCharWrapping
  438. label.preferredMaxLayoutWidth = size.width
  439. return label
  440. }
  441. // private func calTextSize(str: String) -> CGSize {
  442. // let size = CGSize(width: messageWidth.toCGFloat, height: CGFloat(MAXFLOAT))
  443. // return str.boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16)], context: nil).size
  444. // }
  445. //解析json为消息对象
  446. private func parseJson(msg: String) -> IMMessageBodyInfo? {
  447. return IMMessageBodyInfo.deserialize(from: msg)
  448. }
  449. }