IMChatViewController.swift 39 KB

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