IMChatMessageViewCell.swift 19 KB

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