IMChatViewController.swift 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789
  1. //
  2. // IMChatViewController.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. import BSImagePicker
  11. import Photos
  12. import Alamofire
  13. import AlamofireImage
  14. import SwiftyJSON
  15. import QuickLook
  16. class IMChatViewController: UIViewController {
  17. // MARK: - IBOutlet
  18. //消息列表
  19. @IBOutlet weak var tableView: UITableView!
  20. //消息输入框
  21. @IBOutlet weak var messageInputView: UITextField!
  22. //底部工具栏的高度约束
  23. @IBOutlet weak var bottomBarHeightConstraint: NSLayoutConstraint!
  24. //底部工具栏
  25. @IBOutlet weak var bottomBar: UIView!
  26. private let emojiBarHeight = 196
  27. //表情窗口
  28. private lazy var emojiBar: IMChatEmojiBarView = {
  29. let view = Bundle.main.loadNibNamed("IMChatEmojiBarView", owner: self, options: nil)?.first as! IMChatEmojiBarView
  30. view.frame = CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: emojiBarHeight.toCGFloat)
  31. return view
  32. }()
  33. //语音录制按钮
  34. private lazy var audioBtnView: IMChatAudioView = {
  35. let view = Bundle.main.loadNibNamed("IMChatAudioView", owner: self, options: nil)?.first as! IMChatAudioView
  36. view.frame = CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: emojiBarHeight.toCGFloat)
  37. view.delegate = self
  38. return view
  39. }()
  40. //录音的时候显示的view
  41. private var voiceIconImage: UIImageView?
  42. private var voiceIocnTitleLable: UILabel?
  43. private var voiceImageSuperView: UIView?
  44. //预览文件
  45. private lazy var previewVC: CloudFilePreviewController = {
  46. return CloudFilePreviewController()
  47. }()
  48. private lazy var viewModel: IMViewModel = {
  49. return IMViewModel()
  50. }()
  51. // MARK: - properties
  52. var conversation: IMConversationInfo? = nil
  53. //private
  54. private var chatMessageList: [IMMessageInfo] = []
  55. private var page = 0
  56. private var isShowEmoji = false
  57. private var isShowAudioView = false
  58. private var bottomBarHeight = 64 //底部输入框 表情按钮 的高度
  59. private let bottomToolbarHeight = 46 //底部工具栏 麦克风 相册 相机等按钮的位置
  60. // MARK: - functions
  61. override func viewDidLoad() {
  62. super.viewDidLoad()
  63. self.tableView.delegate = self
  64. self.tableView.dataSource = self
  65. self.tableView.register(UINib(nibName: "IMChatMessageViewCell", bundle: nil), forCellReuseIdentifier: "IMChatMessageViewCell")
  66. self.tableView.register(UINib(nibName: "IMChatMessageSendViewCell", bundle: nil), forCellReuseIdentifier: "IMChatMessageSendViewCell")
  67. self.tableView.separatorStyle = .none
  68. // self.tableView.rowHeight = UITableView.automaticDimension
  69. // self.tableView.estimatedRowHeight = 144
  70. self.tableView.backgroundColor = UIColor(hex: "#f3f3f3")
  71. self.tableView.mj_header = MJRefreshNormalHeader(refreshingBlock: {
  72. self.loadMsgList()
  73. })
  74. self.messageInputView.delegate = self
  75. //底部安全距离 老机型没有
  76. self.bottomBarHeight = Int(iPhoneX ? 64 + IPHONEX_BOTTOM_SAFE_HEIGHT: 64) + self.bottomToolbarHeight
  77. self.bottomBarHeightConstraint.constant = self.bottomBarHeight.toCGFloat
  78. self.bottomBar.topBorder(width: 1, borderColor: base_gray_color.alpha(0.5))
  79. self.messageInputView.backgroundColor = base_gray_color
  80. //标题
  81. if self.conversation?.type == o2_im_conversation_type_single {
  82. if let c = self.conversation {
  83. var person = ""
  84. c.personList?.forEach({ (p) in
  85. if p != O2AuthSDK.shared.myInfo()?.distinguishedName {
  86. person = p
  87. }
  88. })
  89. if !person.isEmpty {
  90. self.title = person.split("@").first ?? ""
  91. }
  92. }
  93. } else {
  94. self.title = self.conversation?.title
  95. }
  96. //群会话 添加修改标题的按钮
  97. if self.conversation?.type == o2_im_conversation_type_group &&
  98. O2AuthSDK.shared.myInfo()?.distinguishedName == self.conversation?.adminPerson {
  99. navigationItem.rightBarButtonItem = UIBarButtonItem(title: "修改", style: .plain, target: self, action: #selector(clickUpdate))
  100. }
  101. //获取聊天数据
  102. self.loadMsgList()
  103. //阅读
  104. self.viewModel.readConversation(conversationId: self.conversation?.id)
  105. }
  106. override func viewWillAppear(_ animated: Bool) {
  107. NotificationCenter.default.addObserver(self, selector: #selector(receiveMessageFromWs(notice:)), name: OONotification.websocket.notificationName, object: nil)
  108. }
  109. override func viewWillDisappear(_ animated: Bool) {
  110. NotificationCenter.default.removeObserver(self)
  111. }
  112. @objc private func receiveMessageFromWs(notice: Notification) {
  113. DDLogDebug("接收到websocket im 消息")
  114. if let message = notice.object as? IMMessageInfo {
  115. if message.conversationId == self.conversation?.id {
  116. self.chatMessageList.append(message)
  117. self.scrollMessageToBottom()
  118. self.viewModel.readConversation(conversationId: self.conversation?.id)
  119. }
  120. }
  121. }
  122. @objc private func clickUpdate() {
  123. self.showSheetAction(title: "", message: "选择要修改的项", actions: [
  124. UIAlertAction(title: "修改群名", style: .default, handler: { (action) in
  125. self.updateTitle()
  126. }),
  127. UIAlertAction(title: "修改成员", style: .default, handler: { (action) in
  128. self.updatePeople()
  129. })
  130. ])
  131. }
  132. private func updateTitle() {
  133. self.showPromptAlert(title: "", message: "修改群名", inputText: "") { (action, result) in
  134. if result.isEmpty {
  135. self.showError(title: "请输入群名")
  136. }else {
  137. self.showLoading()
  138. self.viewModel.updateConversationTitle(id: (self.conversation?.id!)!, title: result)
  139. .then { (c) in
  140. self.title = result
  141. self.conversation?.title = result
  142. self.showSuccess(title: "修改成功")
  143. }.catch { (err) in
  144. DDLogError(err.localizedDescription)
  145. self.showError(title: "修改失败")
  146. }
  147. }
  148. }
  149. }
  150. private func updatePeople() {
  151. //选择人员 反选已经存在的成员
  152. if let users = self.conversation?.personList {
  153. self.showContactPicker(modes: [.person], callback: { (result) in
  154. if let people = result.users {
  155. if people.count >= 3 {
  156. var peopleDNs: [String] = []
  157. var containMe = false
  158. people.forEach { (item) in
  159. peopleDNs.append(item.distinguishedName!)
  160. if O2AuthSDK.shared.myInfo()?.distinguishedName == item.distinguishedName {
  161. containMe = true
  162. }
  163. }
  164. if !containMe {
  165. peopleDNs.append((O2AuthSDK.shared.myInfo()?.distinguishedName)!)
  166. }
  167. self.showLoading()
  168. self.viewModel.updateConversationPeople(id: (self.conversation?.id!)!, users: peopleDNs)
  169. .then { (c) in
  170. self.conversation?.personList = peopleDNs
  171. self.showSuccess(title: "修改成功")
  172. }.catch { (err) in
  173. DDLogError(err.localizedDescription)
  174. self.showError(title: "修改失败")
  175. }
  176. }else {
  177. self.showError(title: "选择人数不足3人")
  178. }
  179. }else {
  180. self.showError(title: "请选择人员")
  181. }
  182. }, initUserPickedArray: users)
  183. }else {
  184. self.showError(title: "成员列表数据错误!")
  185. }
  186. }
  187. //获取消息
  188. private func loadMsgList() {
  189. if let c = self.conversation, let id = c.id {
  190. self.viewModel.myMsgPageList(page: self.page + 1, conversationId: id).then { (list) in
  191. if !list.isEmpty {
  192. self.page += 1
  193. self.chatMessageList.insert(contentsOf: list, at: 0)
  194. if self.page == 1 {
  195. self.scrollMessageToBottom()
  196. }else {
  197. DispatchQueue.main.async {
  198. self.tableView.reloadData()
  199. }
  200. }
  201. }
  202. if self.tableView.mj_header.isRefreshing(){
  203. self.tableView.mj_header.endRefreshing()
  204. }
  205. }.catch { (error) in
  206. DDLogError(error.localizedDescription)
  207. if self.tableView.mj_header.isRefreshing(){
  208. self.tableView.mj_header.endRefreshing()
  209. }
  210. }
  211. } else {
  212. self.showError(title: "参数错误!!!")
  213. }
  214. }
  215. //刷新tableview 滚动到底部
  216. private func scrollMessageToBottom() {
  217. DispatchQueue.main.async {
  218. self.tableView.reloadData()
  219. if self.chatMessageList.count > 0 {
  220. self.tableView.scrollToRow(at: IndexPath(row: self.chatMessageList.count - 1, section: 0), at: .bottom, animated: false)
  221. }
  222. }
  223. }
  224. //发送文本消息
  225. private func sendTextMessage() {
  226. guard let msg = self.messageInputView.text else {
  227. return
  228. }
  229. self.messageInputView.text = ""
  230. let body = IMMessageBodyInfo()
  231. body.type = o2_im_msg_type_text
  232. body.body = msg
  233. sendMessage(body: body)
  234. }
  235. //发送表情消息
  236. private func sendEmojiMessage(emoji: String) {
  237. let body = IMMessageBodyInfo()
  238. body.type = o2_im_msg_type_emoji
  239. body.body = emoji
  240. sendMessage(body: body)
  241. }
  242. //发送地图消息消息
  243. private func sendLocationMessage(loc: O2LocationData) {
  244. let body = IMMessageBodyInfo()
  245. body.type = o2_im_msg_type_location
  246. body.body = o2_im_msg_body_location
  247. body.address = loc.address
  248. body.addressDetail = loc.addressDetail
  249. body.longitude = loc.longitude
  250. body.latitude = loc.latitude
  251. sendMessage(body: body)
  252. }
  253. //发送消息到服务器
  254. private func sendMessage(body: IMMessageBodyInfo) {
  255. let message = IMMessageInfo()
  256. message.body = body.toJSONString()
  257. message.id = UUID().uuidString
  258. message.conversationId = self.conversation?.id
  259. message.createPerson = O2AuthSDK.shared.myInfo()?.distinguishedName
  260. message.createTime = Date().formatterDate(formatter: "yyyy-MM-dd HH:mm:ss")
  261. //添加到界面
  262. self.chatMessageList.append(message)
  263. self.scrollMessageToBottom()
  264. //发送消息到服务器
  265. self.viewModel.sendMsg(msg: message)
  266. .then { (result) in
  267. DDLogDebug("发送消息成功 \(result)")
  268. self.viewModel.readConversation(conversationId: self.conversation?.id)
  269. }.catch { (error) in
  270. DDLogError(error.localizedDescription)
  271. self.showError(title: "发送消息失败!")
  272. }
  273. }
  274. //选择照片
  275. private func chooseImage() {
  276. let vc = FileBSImagePickerViewController().bsImagePicker()
  277. vc.settings.fetch.assets.supportedMediaTypes = [.image]
  278. presentImagePicker(vc, select: { (asset) in
  279. //选中一个
  280. }, deselect: { (asset) in
  281. //取消选中一个
  282. }, cancel: { (assets) in
  283. //取消
  284. }, finish: { (assets) in
  285. //结果
  286. if assets.count > 0 {
  287. switch assets[0].mediaType {
  288. case .image:
  289. let options = PHImageRequestOptions()
  290. options.isSynchronous = true
  291. options.deliveryMode = .fastFormat
  292. options.resizeMode = .none
  293. let fName = (assets[0].value(forKey: "filename") as? String) ?? "untitle.png"
  294. PHImageManager.default().requestImageData(for: assets[0], options: options) { (imageData, result, imageOrientation, dict) in
  295. guard let data = imageData else {
  296. return
  297. }
  298. var newData = data
  299. //处理图片旋转的问题
  300. if imageOrientation != UIImage.Orientation.up {
  301. let newImage = UIImage(data: data)?.fixOrientation()
  302. if newImage != nil {
  303. newData = newImage!.pngData()!
  304. }
  305. }
  306. // var fileName = ""
  307. // if dict?["PHImageFileURLKey"] != nil {
  308. // let fileURL = dict?["PHImageFileURLKey"] as! URL
  309. // fileName = fileURL.lastPathComponent
  310. // } else {
  311. // fileName = "\(UUID().uuidString).png"
  312. // }
  313. let localFilePath = self.storageLocalImage(imageData: newData, fileName: fName)
  314. let msgId = self.prepareForSendImageMsg(filePath: localFilePath)
  315. self.uploadFileAndSendMsg(messageId: msgId, data: newData, fileName: fName, type: o2_im_msg_type_image)
  316. }
  317. break
  318. default:
  319. //
  320. DDLogError("不支持的类型")
  321. self.showError(title: "不支持的类型!")
  322. break
  323. }
  324. }
  325. }, completion: nil)
  326. }
  327. //临时存储本地
  328. private func storageLocalImage(imageData: Data, fileName: String) -> String {
  329. let fileTempPath = FileUtil.share.cacheDir().appendingPathComponent(fileName)
  330. do {
  331. try imageData.write(to: fileTempPath)
  332. return fileTempPath.path
  333. } catch {
  334. print(error.localizedDescription)
  335. return fileTempPath.path
  336. }
  337. }
  338. //发送消息前 先载入界面
  339. private func prepareForSendImageMsg(filePath: String) -> String {
  340. let body = IMMessageBodyInfo()
  341. body.type = o2_im_msg_type_image
  342. body.body = o2_im_msg_body_image
  343. body.fileTempPath = filePath
  344. let message = IMMessageInfo()
  345. let msgId = UUID().uuidString
  346. message.body = body.toJSONString()
  347. message.id = msgId
  348. message.conversationId = self.conversation?.id
  349. message.createPerson = O2AuthSDK.shared.myInfo()?.distinguishedName
  350. message.createTime = Date().formatterDate(formatter: "yyyy-MM-dd HH:mm:ss")
  351. //添加到界面
  352. self.chatMessageList.append(message)
  353. self.scrollMessageToBottom()
  354. return msgId
  355. }
  356. //发送消息前 先载入界面
  357. private func prepareForSendFileMsg(tempMessage: IMMessageBodyInfo) -> String {
  358. let message = IMMessageInfo()
  359. let msgId = UUID().uuidString
  360. message.body = tempMessage.toJSONString()
  361. message.id = msgId
  362. message.conversationId = self.conversation?.id
  363. message.createPerson = O2AuthSDK.shared.myInfo()?.distinguishedName
  364. message.createTime = Date().formatterDate(formatter: "yyyy-MM-dd HH:mm:ss")
  365. //添加到界面
  366. self.chatMessageList.append(message)
  367. self.scrollMessageToBottom()
  368. return msgId
  369. }
  370. //上传图片 音频 等文件到服务器并发送消息
  371. private func uploadFileAndSendMsg(messageId: String, data: Data, fileName: String, type: String) {
  372. guard let cId = self.conversation?.id else {
  373. return
  374. }
  375. self.viewModel.uploadFile(conversationId: cId, type: type, fileName: fileName, file: data).then { back in
  376. DDLogDebug("上传文件成功")
  377. guard let message = self.chatMessageList.first (where: { (info) -> Bool in
  378. return info.id == messageId
  379. }) else {
  380. DDLogDebug("没有找到对应的消息")
  381. return
  382. }
  383. let body = IMMessageBodyInfo.deserialize(from: message.body)
  384. body?.fileId = back.id
  385. body?.fileExtension = back.fileExtension
  386. body?.fileTempPath = nil
  387. message.body = body?.toJSONString()
  388. //发送消息到服务器
  389. self.viewModel.sendMsg(msg: message)
  390. .then { (result) in
  391. DDLogDebug("消息 发送成功 \(result)")
  392. self.viewModel.readConversation(conversationId: self.conversation?.id)
  393. }.catch { (error) in
  394. DDLogError(error.localizedDescription)
  395. self.showError(title: "发送消息失败!")
  396. }
  397. }.catch { err in
  398. self.showError(title: "上传错误,\(err.localizedDescription)")
  399. }
  400. }
  401. // MARK: - IBAction
  402. //点击表情按钮
  403. @IBAction func clickEmojiBtn(_ sender: UIButton) {
  404. self.isShowEmoji.toggle()
  405. self.view.endEditing(true)
  406. if self.isShowEmoji {
  407. //audio view 先关闭
  408. self.isShowAudioView = false
  409. self.audioBtnView.removeFromSuperview()
  410. //开始添加emojiBar
  411. self.bottomBarHeightConstraint.constant = self.bottomBarHeight.toCGFloat + self.emojiBarHeight.toCGFloat
  412. self.emojiBar.delegate = self
  413. self.emojiBar.translatesAutoresizingMaskIntoConstraints = false
  414. self.bottomBar.addSubview(self.emojiBar)
  415. let top = NSLayoutConstraint(item: self.emojiBar, attribute: .top, relatedBy: .equal, toItem: self.emojiBar.superview!, attribute: .top, multiplier: 1, constant: CGFloat(self.bottomBarHeight))
  416. let width = NSLayoutConstraint(item: self.emojiBar, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: SCREEN_WIDTH)
  417. let height = NSLayoutConstraint(item: self.emojiBar, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: self.emojiBarHeight.toCGFloat)
  418. NSLayoutConstraint.activate([top, width, height])
  419. } else {
  420. self.bottomBarHeightConstraint.constant = self.bottomBarHeight.toCGFloat
  421. self.emojiBar.removeFromSuperview()
  422. }
  423. self.view.layoutIfNeeded()
  424. }
  425. @IBAction func micBtnClick(_ sender: UIButton) {
  426. DDLogDebug("点击了麦克风按钮")
  427. self.isShowAudioView.toggle()
  428. self.view.endEditing(true)
  429. if self.isShowAudioView {
  430. //emoji view 先关闭
  431. self.isShowEmoji = false
  432. self.emojiBar.removeFromSuperview()
  433. //开始添加emojiBar
  434. self.bottomBarHeightConstraint.constant = self.bottomBarHeight.toCGFloat + self.emojiBarHeight.toCGFloat
  435. self.audioBtnView.translatesAutoresizingMaskIntoConstraints = false
  436. self.bottomBar.addSubview(self.audioBtnView)
  437. let top = NSLayoutConstraint(item: self.audioBtnView, attribute: .top, relatedBy: .equal, toItem: self.audioBtnView.superview!, attribute: .top, multiplier: 1, constant: CGFloat(self.bottomBarHeight))
  438. let width = NSLayoutConstraint(item: self.audioBtnView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: SCREEN_WIDTH)
  439. let height = NSLayoutConstraint(item: self.audioBtnView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: self.emojiBarHeight.toCGFloat)
  440. NSLayoutConstraint.activate([top, width, height])
  441. } else {
  442. self.bottomBarHeightConstraint.constant = self.bottomBarHeight.toCGFloat
  443. self.audioBtnView.removeFromSuperview()
  444. }
  445. self.view.layoutIfNeeded()
  446. }
  447. @IBAction func imgBtnClick(_ sender: UIButton) {
  448. DDLogDebug("点击了图片按钮")
  449. self.chooseImage()
  450. }
  451. @IBAction func cameraBtnClick(_ sender: UIButton) {
  452. DDLogDebug("点击了相机按钮")
  453. self.takePhoto(delegate: self)
  454. }
  455. @IBAction func locationBtnClick(_ sender: UIButton) {
  456. DDLogDebug("点击了位置按钮")
  457. let vc = IMLocationChooseController.openChooseLocation { (data) in
  458. self.sendLocationMessage(loc: data)
  459. }
  460. self.navigationController?.pushViewController(vc, animated: false)
  461. }
  462. }
  463. // MARK: - 录音delegate
  464. extension IMChatViewController: IMChatAudioViewDelegate {
  465. func showAudioRecordingView() {
  466. if self.voiceIconImage == nil {
  467. self.voiceImageSuperView = UIView()
  468. self.view.addSubview(self.voiceImageSuperView!)
  469. self.voiceImageSuperView?.backgroundColor = UIColor(displayP3Red: 0, green: 0, blue: 0, alpha: 0.6)
  470. self.voiceImageSuperView?.snp_makeConstraints { (make) in
  471. make.center.equalTo(self.view)
  472. make.size.equalTo(CGSize(width:140, height:140))
  473. }
  474. self.voiceIconImage = UIImageView()
  475. self.voiceImageSuperView?.addSubview(self.voiceIconImage!)
  476. self.voiceIconImage?.snp_makeConstraints { (make) in
  477. make.top.left.equalTo(self.voiceImageSuperView!).inset(UIEdgeInsets(top: 20, left: 35, bottom: 0, right: 0))
  478. make.size.equalTo(CGSize(width: 70, height: 70))
  479. }
  480. let voiceIconTitleLabel = UILabel()
  481. self.voiceIocnTitleLable = voiceIconTitleLabel
  482. self.voiceIconImage?.addSubview(voiceIconTitleLabel)
  483. voiceIconTitleLabel.textColor = UIColor.white
  484. voiceIconTitleLabel.font = .systemFont(ofSize: 12)
  485. voiceIconTitleLabel.text = "松开发送,上滑取消"
  486. voiceIconTitleLabel.snp_makeConstraints { (make) in
  487. make.bottom.equalTo(self.voiceImageSuperView!).offset(-15)
  488. make.centerX.equalTo(self.voiceImageSuperView!)
  489. }
  490. }
  491. self.voiceImageSuperView?.isHidden = false
  492. self.voiceIconImage?.image = UIImage(named: "chat_audio_voice")
  493. self.voiceIocnTitleLable?.text = "松开发送,上滑取消";
  494. }
  495. func hideAudioRecordingView() {
  496. self.voiceImageSuperView?.isHidden = true
  497. }
  498. func changeRecordingView2uplide() {
  499. self.voiceIocnTitleLable?.text = "松开手指,取消发送";
  500. self.voiceIconImage?.image = UIImage(named: "chat_audio_cancel")
  501. }
  502. func changeRecordingView2down() {
  503. self.voiceIconImage?.image = UIImage(named: "chat_audio_voice")
  504. self.voiceIocnTitleLable?.text = "松开发送,上滑取消";
  505. }
  506. func sendVoice(path: String, voice: Data, duration: String) {
  507. let msg = IMMessageBodyInfo()
  508. msg.fileTempPath = path
  509. msg.body = o2_im_msg_body_audio
  510. msg.type = o2_im_msg_type_audio
  511. msg.audioDuration = duration
  512. let msgId = self.prepareForSendFileMsg(tempMessage: msg)
  513. let fileName = path.split("/").last ?? "MySound.ilbc"
  514. DDLogDebug("音频文件:\(fileName)")
  515. self.uploadFileAndSendMsg(messageId: msgId, data: voice, fileName: fileName, type: o2_im_msg_type_audio)
  516. }
  517. }
  518. // MARK: - 拍照delegate
  519. extension IMChatViewController: UIImagePickerControllerDelegate & UINavigationControllerDelegate {
  520. func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
  521. if let image = info[.editedImage] as? UIImage {
  522. let fileName = "\(UUID().uuidString).png"
  523. let newData = image.pngData()!
  524. let localFilePath = self.storageLocalImage(imageData: newData, fileName: fileName)
  525. let msgId = self.prepareForSendImageMsg(filePath: localFilePath)
  526. self.uploadFileAndSendMsg(messageId: msgId, data: newData, fileName: fileName, type: o2_im_msg_type_image)
  527. } else {
  528. DDLogError("没有选择到图片!")
  529. }
  530. picker.dismiss(animated: true, completion: nil)
  531. // var newData = data
  532. // //处理图片旋转的问题
  533. // if imageOrientation != UIImage.Orientation.up {
  534. // let newImage = UIImage(data: data)?.fixOrientation()
  535. // if newImage != nil {
  536. // newData = newImage!.pngData()!
  537. // }
  538. // }
  539. // var fileName = ""
  540. // if dict?["PHImageFileURLKey"] != nil {
  541. // let fileURL = dict?["PHImageFileURLKey"] as! URL
  542. // fileName = fileURL.lastPathComponent
  543. // } else {
  544. // fileName = "\(UUID().uuidString).png"
  545. // }
  546. }
  547. }
  548. // MARK: - 图片消息点击 delegate
  549. extension IMChatViewController: IMChatMessageDelegate {
  550. func openApplication(storyboard: String) {
  551. if storyboard == "mind" {
  552. let flutterViewController = O2FlutterViewController()
  553. flutterViewController.setInitialRoute("mindMap")
  554. self.present(flutterViewController, animated: false, completion: nil)
  555. }else {
  556. let storyBoard = UIStoryboard(name: storyboard, bundle: nil)
  557. guard let destVC = storyBoard.instantiateInitialViewController() else {
  558. return
  559. }
  560. destVC.modalPresentationStyle = .fullScreen
  561. if destVC.isKind(of: ZLNavigationController.self) {
  562. self.show(destVC, sender: nil)
  563. }else{
  564. self.navigationController?.pushViewController(destVC, animated: true)
  565. }
  566. }
  567. }
  568. func openWork(workId: String) {
  569. self.showLoading()
  570. self.viewModel.isWorkCompleted(work: workId).always {
  571. self.hideLoading()
  572. }.then{ result in
  573. if result {
  574. self.showMessage(msg: "工作已经完成了!")
  575. }else {
  576. self.openWorkPage(work: workId)
  577. }
  578. }.catch {_ in
  579. self.showMessage(msg: "工作已经完成了!")
  580. }
  581. }
  582. private func openWorkPage(work: String) {
  583. let storyBoard = UIStoryboard(name: "task", bundle: nil)
  584. let destVC = storyBoard.instantiateViewController(withIdentifier: "todoTaskDetailVC") as! TodoTaskDetailViewController
  585. let json = """
  586. {"work":"\(work)", "workCompleted":"", "title":""}
  587. """
  588. let todo = TodoTask(JSONString: json)
  589. destVC.todoTask = todo
  590. destVC.backFlag = 3 //隐藏就行
  591. self.show(destVC, sender: nil)
  592. }
  593. func openLocatinMap(info: IMMessageBodyInfo) {
  594. IMShowLocationViewController.pushShowLocation(vc: self, latitude: info.latitude, longitude: info.longitude,
  595. address: info.address, addressDetail: info.addressDetail)
  596. }
  597. func clickImageMessage(info: IMMessageBodyInfo) {
  598. if let id = info.fileId {
  599. self.showLoading()
  600. var ext = info.fileExtension ?? "png"
  601. if ext.isEmpty {
  602. ext = "png"
  603. }
  604. O2IMFileManager.shared
  605. .getFileLocalUrl(fileId: id, fileExtension: ext)
  606. .always {
  607. self.hideLoading()
  608. }.then { (path) in
  609. let currentURL = NSURL(fileURLWithPath: path.path)
  610. DDLogDebug(currentURL.description)
  611. DDLogDebug(path.path)
  612. if QLPreviewController.canPreview(currentURL) {
  613. self.previewVC.currentFileURLS.removeAll()
  614. self.previewVC.currentFileURLS.append(currentURL)
  615. self.previewVC.reloadData()
  616. self.pushVC(self.previewVC)
  617. } else {
  618. self.showError(title: "当前文件类型不支持预览!")
  619. }
  620. }
  621. .catch { (error) in
  622. DDLogError(error.localizedDescription)
  623. self.showError(title: "获取文件异常!")
  624. }
  625. } else if let temp = info.fileTempPath {
  626. let currentURL = NSURL(fileURLWithPath: temp)
  627. DDLogDebug(currentURL.description)
  628. DDLogDebug(temp)
  629. if QLPreviewController.canPreview(currentURL) {
  630. self.previewVC.currentFileURLS.removeAll()
  631. self.previewVC.currentFileURLS.append(currentURL)
  632. self.previewVC.reloadData()
  633. self.pushVC(self.previewVC)
  634. } else {
  635. self.showError(title: "当前文件类型不支持预览!")
  636. }
  637. }
  638. }
  639. }
  640. // MARK: - 表情点击 delegate
  641. extension IMChatViewController: IMChatEmojiBarClickDelegate {
  642. func clickEmoji(emoji: String) {
  643. DDLogDebug("发送表情消息 \(emoji)")
  644. self.sendEmojiMessage(emoji: emoji)
  645. }
  646. }
  647. // MARK: - tableview delegate
  648. extension IMChatViewController: UITableViewDelegate, UITableViewDataSource {
  649. func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  650. return self.chatMessageList.count
  651. }
  652. func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  653. let msg = self.chatMessageList[indexPath.row]
  654. if msg.createPerson == O2AuthSDK.shared.myInfo()?.distinguishedName { //发送者
  655. if let cell = tableView.dequeueReusableCell(withIdentifier: "IMChatMessageSendViewCell", for: indexPath) as? IMChatMessageSendViewCell {
  656. cell.setContent(item: self.chatMessageList[indexPath.row])
  657. cell.delegate = self
  658. return cell
  659. }
  660. } else {
  661. if let cell = tableView.dequeueReusableCell(withIdentifier: "IMChatMessageViewCell", for: indexPath) as? IMChatMessageViewCell {
  662. cell.setContent(item: self.chatMessageList[indexPath.row])
  663. cell.delegate = self
  664. return cell
  665. }
  666. }
  667. return UITableViewCell()
  668. }
  669. func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
  670. let msg = self.chatMessageList[indexPath.row]
  671. return cellHeight(item: msg)
  672. }
  673. func cellHeight(item: IMMessageInfo) -> CGFloat {
  674. if let jsonBody = item.body, let body = IMMessageBodyInfo.deserialize(from: jsonBody){
  675. if body.type == o2_im_msg_type_emoji {
  676. // 上边距 69 + emoji高度 + 内边距 + 底部空白高度
  677. return 69 + 36 + 20 + 10
  678. } else if body.type == o2_im_msg_type_image {
  679. // 上边距 69 + 图片高度 + 内边距 + 底部空白高度
  680. return 69 + 192 + 20 + 10
  681. } else if o2_im_msg_type_audio == body.type {
  682. // 上边距 69 + audio高度 + 内边距 + 底部空白高度
  683. return 69 + IMAudioView.IMAudioView_height + 20 + 10
  684. } else if o2_im_msg_type_location == body.type {
  685. // 上边距 69 + 位置图高度 + 内边距 + 底部空白高度
  686. return 69 + IMLocationView.IMLocationViewHeight + 20 + 10
  687. } else {
  688. let size = body.body!.getSizeWithMaxWidth(fontSize: 16, maxWidth: messageWidth)
  689. // 上边距 69 + 文字高度 + 内边距 + 底部空白高度
  690. return 69 + size.height + 28 + 10
  691. }
  692. }
  693. return 132
  694. }
  695. func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  696. tableView.deselectRow(at: indexPath, animated: false)
  697. }
  698. }
  699. // MARK: - textField delegate
  700. extension IMChatViewController: UITextFieldDelegate {
  701. func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
  702. DDLogDebug("准备开始输入......")
  703. closeOtherView()
  704. return true
  705. }
  706. private func closeOtherView() {
  707. self.isShowEmoji = false
  708. self.isShowAudioView = false
  709. self.bottomBarHeightConstraint.constant = self.bottomBarHeight.toCGFloat
  710. self.view.layoutIfNeeded()
  711. }
  712. func textFieldShouldReturn(_ textField: UITextField) -> Bool {
  713. DDLogDebug("回车。。。。")
  714. self.sendTextMessage()
  715. return true
  716. }
  717. }