JCChatViewController.swift 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350
  1. //
  2. // JCChatViewController.swift
  3. // JChat
  4. //
  5. // Created by deng on 2017/2/28.
  6. // Copyright © 2017年 HXHG. All rights reserved.
  7. //
  8. import UIKit
  9. import YHPhotoKit
  10. import MobileCoreServices
  11. class JCChatViewController: UIViewController {
  12. open var conversation: JMSGConversation
  13. fileprivate var isGroup = false
  14. //MARK - life cycle
  15. public required init(conversation: JMSGConversation) {
  16. self.conversation = conversation
  17. super.init(nibName: nil, bundle: nil)
  18. automaticallyAdjustsScrollViewInsets = false;
  19. if let draft = JCDraft.getDraft(conversation) {
  20. self.draft = draft
  21. }
  22. }
  23. required init?(coder aDecoder: NSCoder) {
  24. fatalError("init(coder:) has not been implemented")
  25. }
  26. override func viewDidLoad() {
  27. super.viewDidLoad()
  28. _init()
  29. }
  30. override func loadView() {
  31. super.loadView()
  32. // y:64 - y:0
  33. let frame = CGRect(x: 0, y: 0, width: self.view.width, height: self.view.height - 64)
  34. chatView = JCChatView(frame: frame, chatViewLayout: chatViewLayout)
  35. chatView.delegate = self
  36. chatView.messageDelegate = self
  37. toolbar.translatesAutoresizingMaskIntoConstraints = false
  38. toolbar.delegate = self
  39. toolbar.text = draft
  40. }
  41. override func viewWillAppear(_ animated: Bool) {
  42. super.viewWillAppear(animated)
  43. toolbar.isHidden = false
  44. }
  45. override func viewWillDisappear(_ animated: Bool) {
  46. super.viewWillDisappear(animated)
  47. NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
  48. }
  49. override func viewDidAppear(_ animated: Bool) {
  50. super.viewDidAppear(animated)
  51. NotificationCenter.default.addObserver(self, selector: #selector(keyboardFrameChanged(_:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
  52. if let group = conversation.target as? JMSGGroup {
  53. self.title = group.displayName()
  54. }
  55. navigationController?.interactivePopGestureRecognizer?.delegate = self
  56. navigationController?.navigationBar.barTintColor = base_color
  57. navigationController?.navigationBar.tintColor = navbar_tint_color
  58. }
  59. override func viewDidDisappear(_ animated: Bool) {
  60. super.viewDidDisappear(animated)
  61. // navigationController?.navigationBar.isTranslucent = true
  62. JCDraft.update(text: toolbar.text, conversation: conversation)
  63. }
  64. deinit {
  65. NotificationCenter.default.removeObserver(self)
  66. JMessage.remove(self, with: conversation)
  67. }
  68. private var draft: String?
  69. fileprivate lazy var toolbar: SAIInputBar = SAIInputBar(type: .default)
  70. fileprivate lazy var inputViews: [String: UIView] = [:]
  71. fileprivate weak var inputItem: SAIInputItem?
  72. var chatViewLayout: JCChatViewLayout = .init()
  73. var chatView: JCChatView!
  74. fileprivate lazy var reminds: [JCRemind] = []
  75. fileprivate lazy var documentInteractionController = UIDocumentInteractionController()
  76. fileprivate lazy var imagePicker: UIImagePickerController = {
  77. var picker = UIImagePickerController()
  78. picker.sourceType = .camera
  79. picker.cameraCaptureMode = .photo
  80. picker.delegate = self
  81. return picker
  82. }()
  83. fileprivate lazy var videoPicker: UIImagePickerController = {
  84. var picker = UIImagePickerController()
  85. picker.mediaTypes = [kUTTypeMovie as String]
  86. picker.sourceType = .camera
  87. picker.cameraCaptureMode = .video
  88. picker.videoMaximumDuration = 10
  89. picker.delegate = self
  90. return picker
  91. }()
  92. fileprivate lazy var _emoticonGroups: [JCCEmoticonGroup] = {
  93. var groups: [JCCEmoticonGroup] = []
  94. if let group = JCCEmoticonGroup(identifier: "com.apple.emoji") {
  95. groups.append(group)
  96. }
  97. if let group = JCCEmoticonGroup(identifier: "cn.jchat.guangguang") {
  98. groups.append(group)
  99. }
  100. return groups
  101. }()
  102. fileprivate lazy var _emoticonSendBtn: UIButton = {
  103. var button = UIButton()
  104. button.titleLabel?.font = UIFont.systemFont(ofSize: 15)
  105. button.contentEdgeInsets = UIEdgeInsets(top: 0, left: 10 + 8, bottom: 0, right: 8)
  106. button.setTitle("发送", for: .normal)
  107. button.setTitleColor(.white, for: .normal)
  108. button.setBackgroundImage(UIImage.loadImage("chat_emoticon_btn_send_blue"), for: .normal)
  109. button.setBackgroundImage(UIImage.loadImage("chat_emoticon_btn_send_gray"), for: .disabled)
  110. button.addTarget(self, action: #selector(_sendHandler), for: .touchUpInside)
  111. return button
  112. }()
  113. fileprivate lazy var emoticonView: JCEmoticonInputView = {
  114. let emoticonView = JCEmoticonInputView(frame: CGRect(x: 0, y: 0, width: self.view.width, height: 275))
  115. emoticonView.delegate = self
  116. emoticonView.dataSource = self
  117. return emoticonView
  118. }()
  119. fileprivate lazy var toolboxView: SAIToolboxInputView = {
  120. var toolboxView = SAIToolboxInputView(frame: CGRect(x: 0, y: 0, width: self.view.width, height: 197))
  121. toolboxView.delegate = self
  122. toolboxView.dataSource = self
  123. return toolboxView
  124. }()
  125. fileprivate lazy var _toolboxItems: [SAIToolboxItem] = {
  126. return [
  127. SAIToolboxItem("page:pic", "照片", UIImage.loadImage("chat_tool_pic")),
  128. SAIToolboxItem("page:camera", "拍照", UIImage.loadImage("chat_tool_camera")),
  129. SAIToolboxItem("page:video_s", "小视频", UIImage.loadImage("chat_tool_video_short")),
  130. SAIToolboxItem("page:location", "位置", UIImage.loadImage("chat_tool_location")),
  131. SAIToolboxItem("page:businessCard", "名片", UIImage.loadImage("chat_tool_businessCard")),
  132. ]
  133. }()
  134. fileprivate var myAvator: UIImage?
  135. lazy var messages: [JCMessage] = []
  136. fileprivate let currentUser = JMSGUser.myInfo()
  137. fileprivate var messagePage = 0
  138. fileprivate var currentMessage: JCMessageType!
  139. fileprivate var maxTime = 0
  140. fileprivate var minTime = 0
  141. fileprivate var minIndex = 0
  142. fileprivate var jMessageCount = 0
  143. fileprivate var isFristLaunch = true
  144. fileprivate var recordingHub: JCRecordingView!
  145. fileprivate lazy var recordHelper: JCRecordVoiceHelper = {
  146. let recordHelper = JCRecordVoiceHelper()
  147. recordHelper.delegate = self
  148. return recordHelper
  149. }()
  150. fileprivate lazy var leftButton: UIButton = {
  151. let leftButton = UIButton(frame: CGRect(x: 0, y: 0, width: 90, height: 65 / 3))
  152. leftButton.setImage(UIImage.loadImage("com_icon_back"), for: .normal)
  153. leftButton.setImage(UIImage.loadImage("com_icon_back"), for: .highlighted)
  154. leftButton.addTarget(self, action: #selector(_back), for: .touchUpInside)
  155. leftButton.setTitle("会话", for: .normal)
  156. leftButton.titleLabel?.font = UIFont.systemFont(ofSize: 16)
  157. leftButton.contentHorizontalAlignment = .left
  158. return leftButton
  159. }()
  160. private func _init() {
  161. myAvator = UIImage.getMyAvator()
  162. isGroup = conversation.ex.isGroup
  163. _updateTitle()
  164. view.backgroundColor = .white
  165. JMessage.add(self, with: conversation)
  166. _setupNavigation()
  167. _loadMessage(messagePage)
  168. let tap = UITapGestureRecognizer(target: self, action: #selector(_tapView))
  169. tap.delegate = self
  170. chatView.addGestureRecognizer(tap)
  171. view.addSubview(chatView)
  172. _updateBadge()
  173. NotificationCenter.default.addObserver(self, selector: #selector(keyboardFrameChanged(_:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
  174. NotificationCenter.default.addObserver(self, selector: #selector(_removeAllMessage), name: NSNotification.Name(rawValue: kDeleteAllMessage), object: nil)
  175. NotificationCenter.default.addObserver(self, selector: #selector(_reloadMessage), name: NSNotification.Name(rawValue: kReloadAllMessage), object: nil)
  176. NotificationCenter.default.addObserver(self, selector: #selector(_updateFileMessage(_:)), name: NSNotification.Name(rawValue: kUpdateFileMessage), object: nil)
  177. }
  178. @objc func _updateFileMessage(_ notification: Notification) {
  179. let userInfo = notification.userInfo
  180. let msgId = userInfo?[kUpdateFileMessage] as! String
  181. let message = conversation.message(withMessageId: msgId)!
  182. let content = message.content as! JMSGFileContent
  183. let url = URL(fileURLWithPath: content.originMediaLocalPath ?? "")
  184. let data = try! Data(contentsOf: url)
  185. updateMediaMessage(message, data: data)
  186. }
  187. private func _updateTitle() {
  188. if let group = conversation.target as? JMSGGroup {
  189. title = group.displayName()
  190. } else {
  191. title = conversation.title
  192. }
  193. }
  194. @objc func _reloadMessage() {
  195. _removeAllMessage()
  196. messagePage = 0
  197. _loadMessage(messagePage)
  198. DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) {
  199. self.chatView.scrollToLast(animated: false)
  200. }
  201. }
  202. @objc func _removeAllMessage() {
  203. jMessageCount = 0
  204. messages.removeAll()
  205. chatView.removeAll()
  206. }
  207. @objc func _tapView() {
  208. view.endEditing(true)
  209. toolbar.resignFirstResponder()
  210. }
  211. private func _setupNavigation() {
  212. let navButton = UIButton(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
  213. if isGroup {
  214. navButton.setImage(UIImage.loadImage("com_icon_group_w"), for: .normal)
  215. navButton.addTarget(self, action: #selector(_getGroupInfo), for: .touchUpInside)
  216. } else {
  217. navButton.setImage(UIImage.loadImage("com_icon_user_w"), for: .normal)
  218. navButton.addTarget(self, action: #selector(_getSingleInfo), for: .touchUpInside)
  219. }
  220. let item1 = UIBarButtonItem(customView: navButton)
  221. navigationItem.rightBarButtonItems = [item1]
  222. let item2 = UIBarButtonItem(customView: leftButton)
  223. navigationItem.leftBarButtonItems = [item2]
  224. navigationController?.interactivePopGestureRecognizer?.isEnabled = true
  225. navigationController?.interactivePopGestureRecognizer?.delegate = self
  226. }
  227. @objc func _back() {
  228. navigationController?.popViewController(animated: true)
  229. }
  230. fileprivate func _loadMessage(_ page: Int) {
  231. let messages = conversation.messageArrayFromNewest(withOffset: NSNumber(value: jMessageCount), limit: NSNumber(value: 17))
  232. if messages.count == 0 {
  233. return
  234. }
  235. var msgs: [JCMessage] = []
  236. for index in 0..<messages.count {
  237. let message = messages[index]
  238. let msg = _parseMessage(message)
  239. msgs.insert(msg, at: 0)
  240. if isNeedInsertTimeLine(message.timestamp.intValue) || index == messages.count - 1 {
  241. let timeContent = JCMessageTimeLineContent(date: Date(timeIntervalSince1970: TimeInterval(message.timestamp.intValue / 1000)))
  242. let m = JCMessage(content: timeContent)
  243. m.options.showsTips = false
  244. msgs.insert(m, at: 0)
  245. }
  246. }
  247. if page != 0 {
  248. minIndex = minIndex + msgs.count
  249. chatView.insert(contentsOf: msgs, at: 0)
  250. } else {
  251. minIndex = msgs.count - 1
  252. chatView.append(contentsOf: msgs)
  253. }
  254. self.messages.insert(contentsOf: msgs, at: 0)
  255. }
  256. private func isNeedInsertTimeLine(_ time: Int) -> Bool {
  257. if maxTime == 0 || minTime == 0 {
  258. maxTime = time
  259. minTime = time
  260. return true
  261. }
  262. if (time - maxTime) >= 5 * 60000 {
  263. maxTime = time
  264. return true
  265. }
  266. if (minTime - time) >= 5 * 60000 {
  267. minTime = time
  268. return true
  269. }
  270. return false
  271. }
  272. // MARK: - parse message
  273. fileprivate func _parseMessage(_ message: JMSGMessage, _ isNewMessage: Bool = true) -> JCMessage {
  274. if isNewMessage {
  275. jMessageCount += 1
  276. }
  277. return message.parseMessage(self, { [weak self] (message, data) in
  278. self?.updateMediaMessage(message, data: data)
  279. })
  280. }
  281. // MARK: - send message
  282. func send(_ message: JCMessage, _ jmessage: JMSGMessage) {
  283. if isNeedInsertTimeLine(jmessage.timestamp.intValue) {
  284. let timeContent = JCMessageTimeLineContent(date: Date(timeIntervalSince1970: TimeInterval(jmessage.timestamp.intValue / 1000)))
  285. let m = JCMessage(content: timeContent)
  286. m.options.showsTips = false
  287. messages.append(m)
  288. chatView.append(m)
  289. }
  290. message.msgId = jmessage.msgId
  291. message.name = currentUser.displayName()
  292. message.senderAvator = myAvator
  293. message.sender = currentUser
  294. message.options.alignment = .right
  295. message.options.state = .sending
  296. if let group = conversation.target as? JMSGGroup {
  297. message.targetType = .group
  298. message.unreadCount = group.memberArray().count - 1
  299. } else {
  300. message.targetType = .single
  301. message.unreadCount = 1
  302. }
  303. chatView.append(message)
  304. messages.append(message)
  305. chatView.scrollToLast(animated: false)
  306. conversation.send(jmessage, optionalContent: JMSGOptionalContent.ex.default)
  307. }
  308. func send(forText text: NSAttributedString) {
  309. let message = JCMessage(content: JCMessageTextContent(attributedText: text))
  310. let content = JMSGTextContent(text: text.string)
  311. let msg = JMSGMessage.ex.createMessage(conversation, content, reminds)
  312. reminds.removeAll()
  313. send(message, msg)
  314. }
  315. func send(forLargeEmoticon emoticon: JCCEmoticonLarge) {
  316. guard let image = emoticon.contents as? UIImage else {
  317. return
  318. }
  319. let messageContent = JCMessageImageContent()
  320. messageContent.image = image
  321. messageContent.delegate = self
  322. let message = JCMessage(content: messageContent)
  323. let content = JMSGImageContent(imageData: image.pngData()!)
  324. let msg = JMSGMessage.ex.createMessage(conversation, content!, nil)
  325. msg.ex.isLargeEmoticon = true
  326. message.options.showsTips = true
  327. send(message, msg)
  328. }
  329. func send(forImage image: UIImage) {
  330. let data = image.jpegData(compressionQuality: 1)
  331. let content = JMSGImageContent(imageData: data!)
  332. let message = JMSGMessage.ex.createMessage(conversation, content!, nil)
  333. let imageContent = JCMessageImageContent()
  334. imageContent.delegate = self
  335. imageContent.image = image
  336. content?.uploadHandler = { (percent:Float, msgId:(String?)) -> Void in
  337. imageContent.upload?(percent)
  338. }
  339. let msg = JCMessage(content: imageContent)
  340. send(msg, message)
  341. }
  342. func send(voiceData: Data, duration: Double) {
  343. let voiceContent = JCMessageVoiceContent()
  344. voiceContent.data = voiceData
  345. voiceContent.duration = duration
  346. voiceContent.delegate = self
  347. let content = JMSGVoiceContent(voiceData: voiceData, voiceDuration: NSNumber(value: duration))
  348. let message = JMSGMessage.ex.createMessage(conversation, content, nil)
  349. let msg = JCMessage(content: voiceContent)
  350. send(msg, message)
  351. }
  352. func send(fileData: Data) {
  353. let videoContent = JCMessageVideoContent()
  354. videoContent.data = fileData
  355. videoContent.delegate = self
  356. let content = JMSGFileContent(fileData: fileData, fileName: "小视频")
  357. let message = JMSGMessage.ex.createMessage(conversation, content, nil)
  358. message.ex.isShortVideo = true
  359. let msg = JCMessage(content: videoContent)
  360. send(msg, message)
  361. }
  362. func send(address: String, lon: NSNumber, lat: NSNumber) {
  363. let locationContent = JCMessageLocationContent()
  364. locationContent.address = address
  365. locationContent.lat = lat.doubleValue
  366. locationContent.lon = lon.doubleValue
  367. locationContent.delegate = self
  368. let content = JMSGLocationContent(latitude: lat, longitude: lon, scale: NSNumber(value: 1), address: address)
  369. let message = JMSGMessage.ex.createMessage(conversation, content, nil)
  370. let msg = JCMessage(content: locationContent)
  371. send(msg, message)
  372. }
  373. @objc func keyboardFrameChanged(_ notification: Notification) {
  374. let dic = NSDictionary(dictionary: (notification as NSNotification).userInfo!)
  375. let keyboardValue = dic.object(forKey: UIResponder.keyboardFrameEndUserInfoKey) as! NSValue
  376. let bottomDistance = UIScreen.main.bounds.size.height - keyboardValue.cgRectValue.origin.y
  377. let duration = Double(dic.object(forKey: UIResponder.keyboardAnimationDurationUserInfoKey) as! NSNumber)
  378. UIView.animate(withDuration: duration, animations: {
  379. }) { (finish) in
  380. if (bottomDistance == 0 || bottomDistance == self.toolbar.height) && !self.isFristLaunch {
  381. return
  382. }
  383. DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
  384. self.chatView.scrollToLast(animated: false)
  385. }
  386. self.isFristLaunch = false
  387. }
  388. }
  389. @objc func _sendHandler() {
  390. let text = toolbar.attributedText
  391. if text != nil && (text?.length)! > 0 {
  392. send(forText: text!)
  393. toolbar.attributedText = nil
  394. }
  395. }
  396. @objc func _getSingleInfo() {
  397. // let vc = JCSingleSettingViewController()
  398. // vc.user = conversation.target as? JMSGUser
  399. // navigationController?.pushViewController(vc, animated: true)
  400. guard let uid = (conversation.target as? JMSGUser)?.username else {
  401. return
  402. }
  403. goToPerson(uid: uid)
  404. }
  405. @objc func _getGroupInfo() {
  406. let vc = JCGroupSettingViewController()
  407. let group = conversation.target as? JMSGGroup
  408. vc.group = group
  409. navigationController?.pushViewController(vc, animated: true)
  410. }
  411. }
  412. //MARK: - JMSGMessage Delegate
  413. extension JCChatViewController: JMessageDelegate {
  414. fileprivate func updateMediaMessage(_ message: JMSGMessage, data: Data) {
  415. DispatchQueue.main.async {
  416. if let index = self.messages.index(message) {
  417. let msg = self.messages[index]
  418. switch(message.contentType) {
  419. case .file:
  420. if message.ex.isShortVideo {
  421. let videoContent = msg.content as! JCMessageVideoContent
  422. videoContent.data = data
  423. videoContent.delegate = self
  424. msg.content = videoContent
  425. } else {
  426. let fileContent = msg.content as! JCMessageFileContent
  427. fileContent.data = data
  428. fileContent.delegate = self
  429. msg.content = fileContent
  430. }
  431. case .image:
  432. let imageContent = msg.content as! JCMessageImageContent
  433. let image = UIImage(data: data)
  434. imageContent.image = image
  435. msg.content = imageContent
  436. default: break
  437. }
  438. msg.updateSizeIfNeeded = true
  439. self.chatView.update(msg, at: index)
  440. msg.updateSizeIfNeeded = false
  441. // self.chatView.update(msg, at: index)
  442. }
  443. }
  444. }
  445. func _updateBadge() {
  446. JMSGConversation.allConversations { (result, error) in
  447. guard let conversations = result as? [JMSGConversation] else {
  448. return
  449. }
  450. let count = conversations.unreadCount
  451. if count == 0 {
  452. self.leftButton.setTitle("会话", for: .normal)
  453. } else {
  454. self.leftButton.setTitle("会话(\(count))", for: .normal)
  455. }
  456. }
  457. }
  458. func onReceive(_ message: JMSGMessage!, error: Error!) {
  459. if error != nil {
  460. return
  461. }
  462. let message = _parseMessage(message)
  463. // TODO: 这个判断是sdk bug导致的,暂时只能这么改
  464. if messages.contains(where: { (m) -> Bool in
  465. return m.msgId == message.msgId
  466. }) {
  467. let indexs = chatView.indexPathsForVisibleItems
  468. for index in indexs {
  469. var m = messages[index.row]
  470. if !m.msgId.isEmpty {
  471. m = _parseMessage(conversation.message(withMessageId: m.msgId)!, false)
  472. chatView.update(m, at: index.row)
  473. }
  474. }
  475. return
  476. }
  477. messages.append(message)
  478. chatView.append(message)
  479. updateUnread([message])
  480. conversation.clearUnreadCount()
  481. if !chatView.isRoll {
  482. chatView.scrollToLast(animated: true)
  483. }
  484. _updateBadge()
  485. }
  486. func onSendMessageResponse(_ message: JMSGMessage!, error: Error!) {
  487. if let error = error as NSError? {
  488. if error.code == 803009 {
  489. MBProgressHUD_JChat.show(text: "发送失败,消息中包含敏感词", view: view, 2.0)
  490. }
  491. if error.code == 803005 {
  492. MBProgressHUD_JChat.show(text: "您已不是群成员", view: view, 2.0)
  493. }
  494. }
  495. if let index = messages.index(message) {
  496. let msg = messages[index]
  497. msg.options.state = message.ex.state
  498. chatView.update(msg, at: index)
  499. }
  500. }
  501. func onReceive(_ retractEvent: JMSGMessageRetractEvent!) {
  502. if let index = messages.index(retractEvent.retractMessage) {
  503. let msg = _parseMessage(retractEvent.retractMessage, false)
  504. messages[index] = msg
  505. chatView.update(msg, at: index)
  506. }
  507. }
  508. func onSyncOfflineMessageConversation(_ conversation: JMSGConversation!, offlineMessages: [JMSGMessage]!) {
  509. let msgs = offlineMessages.sorted(by: { (m1, m2) -> Bool in
  510. return m1.timestamp.intValue < m2.timestamp.intValue
  511. })
  512. for item in msgs {
  513. let message = _parseMessage(item)
  514. messages.append(message)
  515. chatView.append(message)
  516. updateUnread([message])
  517. conversation.clearUnreadCount()
  518. if !chatView.isRoll {
  519. chatView.scrollToLast(animated: true)
  520. }
  521. }
  522. _updateBadge()
  523. }
  524. func onReceive(_ receiptEvent: JMSGMessageReceiptStatusChangeEvent!) {
  525. for message in receiptEvent.messages! {
  526. if let index = messages.index(message) {
  527. let msg = messages[index]
  528. msg.unreadCount = message.getUnreadCount()
  529. chatView.update(msg, at: index)
  530. }
  531. }
  532. }
  533. }
  534. // MARK: - JCEmoticonInputViewDataSource & JCEmoticonInputViewDelegate
  535. extension JCChatViewController: JCEmoticonInputViewDataSource, JCEmoticonInputViewDelegate {
  536. open func numberOfEmotionGroups(in emoticon: JCEmoticonInputView) -> Int {
  537. return _emoticonGroups.count
  538. }
  539. open func emoticon(_ emoticon: JCEmoticonInputView, emotionGroupForItemAt index: Int) -> JCEmoticonGroup {
  540. return _emoticonGroups[index]
  541. }
  542. open func emoticon(_ emoticon: JCEmoticonInputView, numberOfRowsForGroupAt index: Int) -> Int {
  543. return _emoticonGroups[index].rows
  544. }
  545. open func emoticon(_ emoticon: JCEmoticonInputView, numberOfColumnsForGroupAt index: Int) -> Int {
  546. return _emoticonGroups[index].columns
  547. }
  548. open func emoticon(_ emoticon: JCEmoticonInputView, moreViewForGroupAt index: Int) -> UIView? {
  549. if _emoticonGroups[index].type.isSmall {
  550. return _emoticonSendBtn
  551. } else {
  552. return nil
  553. }
  554. }
  555. open func emoticon(_ emoticon: JCEmoticonInputView, insetForGroupAt index: Int) -> UIEdgeInsets {
  556. return UIEdgeInsets(top: 12, left: 10, bottom: 12 + 24, right: 10)
  557. }
  558. open func emoticon(_ emoticon: JCEmoticonInputView, didSelectFor item: JCEmoticon) {
  559. if item.isBackspace {
  560. toolbar.deleteBackward()
  561. return
  562. }
  563. if let emoticon = item as? JCCEmoticonLarge {
  564. send(forLargeEmoticon: emoticon)
  565. return
  566. }
  567. if let code = item.contents as? String {
  568. return toolbar.insertText(code)
  569. }
  570. if let image = item.contents as? UIImage {
  571. let d = toolbar.font?.descender ?? 0
  572. let h = toolbar.font?.lineHeight ?? 0
  573. let attachment = NSTextAttachment()
  574. attachment.image = image
  575. attachment.bounds = CGRect(x: 0, y: d, width: h, height: h)
  576. toolbar.insertAttributedText(NSAttributedString(attachment: attachment))
  577. return
  578. }
  579. }
  580. }
  581. // MARK: - SAIToolboxInputViewDataSource & SAIToolboxInputViewDelegate
  582. extension JCChatViewController: SAIToolboxInputViewDataSource, SAIToolboxInputViewDelegate {
  583. open func numberOfToolboxItems(in toolbox: SAIToolboxInputView) -> Int {
  584. return _toolboxItems.count
  585. }
  586. open func toolbox(_ toolbox: SAIToolboxInputView, toolboxItemForItemAt index: Int) -> SAIToolboxItem {
  587. return _toolboxItems[index]
  588. }
  589. open func toolbox(_ toolbox: SAIToolboxInputView, numberOfRowsForSectionAt index: Int) -> Int {
  590. return 2
  591. }
  592. open func toolbox(_ toolbox: SAIToolboxInputView, numberOfColumnsForSectionAt index: Int) -> Int {
  593. return 4
  594. }
  595. open func toolbox(_ toolbox: SAIToolboxInputView, insetForSectionAt index: Int) -> UIEdgeInsets {
  596. return UIEdgeInsets(top: 12, left: 10, bottom: 12, right: 10)
  597. }
  598. open func toolbox(_ toolbox: SAIToolboxInputView, shouldSelectFor item: SAIToolboxItem) -> Bool {
  599. return true
  600. }
  601. private func _pushToSelectPhotos() {
  602. // let vc = YHPhotoPickerViewController()
  603. // vc.maxPhotosCount = 9;
  604. // vc.pickerDelegate = self
  605. // present(vc, animated: true)
  606. let vc = FileBSImagePickerViewController()
  607. vc.defaultmaxNumberOfSelections = 9
  608. vc.defaultTakePhotos = false
  609. bs_presentImagePickerController(vc, animated: true,
  610. select: { (asset: PHAsset) -> Void in
  611. // User selected an asset.
  612. // Do something with it, start upload perhaps?
  613. }, deselect: { (asset: PHAsset) -> Void in
  614. // User deselected an assets.
  615. // Do something, cancel upload?
  616. }, cancel: { (assets: [PHAsset]) -> Void in
  617. // User cancelled. And this where the assets currently selected.
  618. }, finish: { (assets: [PHAsset]) -> Void in
  619. // for item in photos {
  620. // guard let photo = item as? UIImage else {
  621. // return
  622. // }
  623. // DispatchQueue.main.async {
  624. // self.send(forImage: photo)
  625. // }
  626. // }
  627. for asset in assets {
  628. if asset.mediaType == .image {
  629. let options = PHImageRequestOptions()
  630. options.isSynchronous = true
  631. options.deliveryMode = .fastFormat
  632. options.resizeMode = .none
  633. PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .default, options: options, resultHandler: { (image, array) in
  634. guard let uiimage = image else {
  635. return
  636. }
  637. DispatchQueue.main.async {
  638. self.send(forImage: uiimage)
  639. }
  640. })
  641. }
  642. }
  643. }, completion: nil)
  644. }
  645. open func toolbox(_ toolbox: SAIToolboxInputView, didSelectFor item: SAIToolboxItem) {
  646. toolbar.resignFirstResponder()
  647. switch item.identifier {
  648. case "page:pic":
  649. if PHPhotoLibrary.authorizationStatus() != .authorized {
  650. PHPhotoLibrary.requestAuthorization({ (status) in
  651. DispatchQueue.main.sync {
  652. if status != .authorized {
  653. self.gotoApplicationSettings(alertMessage: "无权限访问照片,请在设备的设置中允许访问照片?")
  654. } else {
  655. self._pushToSelectPhotos()
  656. }
  657. }
  658. })
  659. } else {
  660. self._pushToSelectPhotos()
  661. }
  662. case "page:camera":
  663. present(imagePicker, animated: true, completion: nil)
  664. case "page:video_s":
  665. present(videoPicker, animated: true, completion: nil)
  666. case "page:location":
  667. let vc = JCAddMapViewController()
  668. vc.addressBlock = { (dict: Dictionary?) in
  669. if dict != nil {
  670. let lon = Float(dict?["lon"] as! String)
  671. let lat = Float(dict?["lat"] as! String)
  672. let address = dict?["address"] as! String
  673. self.send(address: address, lon: NSNumber(value: lon!), lat: NSNumber(value: lat!))
  674. }
  675. }
  676. navigationController?.pushViewController(vc, animated: true)
  677. case "page:businessCard":
  678. let vc = FriendsBusinessCardViewController()
  679. vc.conversation = conversation
  680. let nav = JCNavigationController(rootViewController: vc)
  681. present(nav, animated: true, completion: {
  682. self.toolbar.isHidden = true
  683. })
  684. default:
  685. break
  686. }
  687. }
  688. open override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
  689. super.present(viewControllerToPresent, animated: flag, completion: completion)
  690. }
  691. }
  692. // MARK: - UIImagePickerControllerDelegate & YHPhotoPickerViewControllerDelegate
  693. extension JCChatViewController: YHPhotoPickerViewControllerDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
  694. func selectedPhotoBeyondLimit(_ count: Int32, currentView view: UIView!) {
  695. MBProgressHUD_JChat.show(text: "最多选择\(count)张图片", view: nil)
  696. }
  697. func yhPhotoPickerViewController(_ PhotoPickerViewController: YHSelectPhotoViewController!, selectedPhotos photos: [Any]!) {
  698. for item in photos {
  699. guard let photo = item as? UIImage else {
  700. return
  701. }
  702. DispatchQueue.main.async {
  703. self.send(forImage: photo)
  704. }
  705. }
  706. }
  707. func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
  708. picker.dismiss(animated: true, completion: nil)
  709. }
  710. func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
  711. picker.dismiss(animated: true, completion: nil)
  712. let image = info[UIImagePickerController.InfoKey.originalImage] as! UIImage?
  713. if let image = image?.fixOrientation() {
  714. send(forImage: image)
  715. }
  716. let videoUrl = info[UIImagePickerController.InfoKey.mediaURL] as! URL?
  717. if videoUrl != nil {
  718. let data = try! Data(contentsOf: videoUrl!)
  719. send(fileData: data)
  720. }
  721. }
  722. }
  723. // MARK: - JCMessageDelegate
  724. extension JCChatViewController: JCMessageDelegate {
  725. func message(message: JCMessageType, videoData data: Data?) {
  726. if let data = data {
  727. JCVideoManager.playVideo(data: data, currentViewController: self)
  728. }
  729. }
  730. func message(message: JCMessageType, location address: String?, lat: Double, lon: Double) {
  731. let vc = JCAddMapViewController()
  732. vc.isOnlyShowMap = true
  733. vc.lat = lat
  734. vc.lon = lon
  735. navigationController?.pushViewController(vc, animated: true)
  736. }
  737. func message(message: JCMessageType, image: UIImage?) {
  738. let browserImageVC = JCImageBrowserViewController()
  739. browserImageVC.messages = messages
  740. browserImageVC.conversation = conversation
  741. browserImageVC.currentMessage = message
  742. present(browserImageVC, animated: true) {
  743. self.toolbar.isHidden = true
  744. }
  745. }
  746. func message(message: JCMessageType, fileData data: Data?, fileName: String?, fileType: String?) {
  747. if data == nil {
  748. let vc = JCFileDownloadViewController()
  749. vc.title = fileName
  750. let msg = conversation.message(withMessageId: message.msgId)
  751. vc.fileSize = msg?.ex.fileSize
  752. vc.message = msg
  753. navigationController?.pushViewController(vc, animated: true)
  754. } else {
  755. guard let fileType = fileType else {
  756. return
  757. }
  758. let msg = conversation.message(withMessageId: message.msgId)!
  759. let content = msg.content as! JMSGFileContent
  760. switch fileType.fileFormat() {
  761. case .document:
  762. let vc = JCDocumentViewController()
  763. vc.title = fileName
  764. vc.fileData = data
  765. vc.filePath = content.originMediaLocalPath
  766. vc.fileType = fileType
  767. navigationController?.pushViewController(vc, animated: true)
  768. case .video, .voice:
  769. let url = URL(fileURLWithPath: content.originMediaLocalPath ?? "")
  770. try! JCVideoManager.playVideo(data: Data(contentsOf: url), fileType, currentViewController: self)
  771. case .photo:
  772. let browserImageVC = JCImageBrowserViewController()
  773. let image = UIImage(contentsOfFile: content.originMediaLocalPath ?? "")
  774. browserImageVC.imageArr = [image!]
  775. browserImageVC.imgCurrentIndex = 0
  776. present(browserImageVC, animated: true) {
  777. self.toolbar.isHidden = true
  778. }
  779. default:
  780. let url = URL(fileURLWithPath: content.originMediaLocalPath ?? "")
  781. documentInteractionController.url = url
  782. documentInteractionController.presentOptionsMenu(from: .zero, in: self.view, animated: true)
  783. }
  784. }
  785. }
  786. func message(message: JCMessageType, user: JMSGUser?, businessCardName: String, businessCardAppKey: String) {
  787. if let user = user {
  788. let vc = JCUserInfoViewController()
  789. vc.user = user
  790. navigationController?.pushViewController(vc, animated: true)
  791. }
  792. }
  793. func clickTips(message: JCMessageType) {
  794. currentMessage = message
  795. // let alertView = UIAlertView(title: "重新发送", message: "是否重新发送该消息?", delegate: self, cancelButtonTitle: "取消", otherButtonTitles: "发送")
  796. let okAction = UIAlertAction(title: "发送", style: .default) { (action) in
  797. if let index = self.messages.index(self.currentMessage) {
  798. self.messages.remove(at: index)
  799. self.chatView.remove(at: index)
  800. let msg = self.conversation.message(withMessageId: self.currentMessage.msgId)
  801. self.currentMessage.options.state = .sending
  802. if let msg = msg {
  803. if let content = self.currentMessage.content as? JCMessageImageContent,
  804. let imageContent = msg.content as? JMSGImageContent
  805. {
  806. imageContent.uploadHandler = { (percent:Float, msgId:(String?)) -> Void in
  807. content.upload?(percent)
  808. }
  809. }
  810. }
  811. self.messages.append(self.currentMessage as! JCMessage)
  812. self.chatView.append(self.currentMessage)
  813. self.conversation.send(msg!, optionalContent: JMSGOptionalContent.ex.default)
  814. self.chatView.scrollToLast(animated: true)
  815. }
  816. }
  817. let cancelAction = UIAlertAction(title: "取消", style: .cancel) { (action) in
  818. //
  819. }
  820. self.showDefaultConfirm(title: "重新发送", message: "是否重新发送该消息?", okAction: okAction, cancelAction: cancelAction)
  821. // alertView.show()
  822. }
  823. func tapAvatarView(message: JCMessageType) {
  824. toolbar.resignFirstResponder()
  825. if message.options.alignment == .right {
  826. navigationController?.pushViewController(SPersonViewController(), animated: true)
  827. } else {
  828. guard let uid = message.sender?.username else {
  829. return
  830. }
  831. goToPerson(uid: uid)
  832. }
  833. }
  834. func longTapAvatarView(message: JCMessageType) {
  835. if !isGroup || message.options.alignment == .right {
  836. return
  837. }
  838. toolbar.becomeFirstResponder()
  839. if let user = message.sender {
  840. toolbar.text.append("@")
  841. handleAt(toolbar, NSMakeRange(toolbar.text.length - 1, 0), user, false, user.displayName().length)
  842. }
  843. }
  844. func tapUnreadTips(message: JCMessageType) {
  845. let vc = UnreadListViewController()
  846. let msg = conversation.message(withMessageId: message.msgId)
  847. vc.message = msg
  848. navigationController?.pushViewController(vc, animated: true)
  849. }
  850. private func goToPerson(uid: String) {
  851. let storyBoard = UIStoryboard(name: "contacts", bundle: nil)
  852. guard let personVC = storyBoard.instantiateViewController(withIdentifier: "ContactPersonInfoV2") as? ContactPersonInfoV2ViewController else {
  853. return
  854. }
  855. let person = PersonV2()
  856. person.id = uid
  857. personVC.person = person
  858. navigationController?.pushViewController(personVC, animated: true)
  859. }
  860. }
  861. extension JCChatViewController: JCChatViewDelegate {
  862. func refershChatView( chatView: JCChatView) {
  863. messagePage += 1
  864. _loadMessage(messagePage)
  865. chatView.stopRefresh()
  866. }
  867. func deleteMessage(message: JCMessageType) {
  868. conversation.deleteMessage(withMessageId: message.msgId)
  869. if let index = messages.index(message) {
  870. jMessageCount -= 1
  871. messages.remove(at: index)
  872. if let message = messages.last {
  873. if message.content is JCMessageTimeLineContent {
  874. messages.removeLast()
  875. chatView.remove(at: messages.count)
  876. }
  877. }
  878. }
  879. }
  880. func forwardMessage(message: JCMessageType) {
  881. if let message = conversation.message(withMessageId: message.msgId) {
  882. let vc = JCForwardViewController()
  883. vc.message = message
  884. let nav = JCNavigationController(rootViewController: vc)
  885. self.present(nav, animated: true, completion: {
  886. self.toolbar.isHidden = true
  887. })
  888. }
  889. }
  890. func withdrawMessage(message: JCMessageType) {
  891. guard let message = conversation.message(withMessageId: message.msgId) else {
  892. return
  893. }
  894. JMSGMessage.retractMessage(message, completionHandler: { (result, error) in
  895. if error == nil {
  896. if let index = self.messages.index(message) {
  897. let msg = self._parseMessage(self.conversation.message(withMessageId: message.msgId)!, false)
  898. self.messages[index] = msg
  899. self.chatView.update(msg, at: index)
  900. }
  901. } else {
  902. MBProgressHUD_JChat.show(text: "发送时间过长,不能撤回", view: self.view)
  903. }
  904. })
  905. }
  906. func indexPathsForVisibleItems(chatView: JCChatView, items: [IndexPath]) {
  907. for item in items {
  908. if item.row <= minIndex {
  909. var msgs: [JCMessage] = []
  910. for index in item.row...minIndex {
  911. msgs.append(messages[index])
  912. }
  913. updateUnread(msgs)
  914. minIndex = item.row
  915. }
  916. }
  917. }
  918. fileprivate func updateUnread(_ messages: [JCMessage]) {
  919. for message in messages {
  920. if message.options.alignment != .left {
  921. continue
  922. }
  923. if let msg = conversation.message(withMessageId: message.msgId) {
  924. if msg.isHaveRead {
  925. continue
  926. }
  927. msg.setMessageHaveRead({ _,_ in
  928. })
  929. }
  930. }
  931. }
  932. }
  933. //extension JCChatViewController: UIAlertViewDelegate {
  934. //
  935. // func alertView(_ alertView: UIAlertView, clickedButtonAt buttonIndex: Int) {
  936. // if alertView.tag == 10001 {
  937. // if buttonIndex == 1 {
  938. // JCAppManager.openAppSetter()
  939. // }
  940. // return
  941. // }
  942. // switch buttonIndex {
  943. // case 1:
  944. // if let index = messages.index(currentMessage) {
  945. // messages.remove(at: index)
  946. // chatView.remove(at: index)
  947. // let msg = conversation.message(withMessageId: currentMessage.msgId)
  948. // currentMessage.options.state = .sending
  949. //
  950. // if let msg = msg {
  951. // if let content = currentMessage.content as? JCMessageImageContent,
  952. // let imageContent = msg.content as? JMSGImageContent
  953. // {
  954. // imageContent.uploadHandler = { (percent:Float, msgId:(String?)) -> Void in
  955. // content.upload?(percent)
  956. // }
  957. // }
  958. // }
  959. // messages.append(currentMessage as! JCMessage)
  960. // chatView.append(currentMessage)
  961. // conversation.send(msg!, optionalContent: JMSGOptionalContent.ex.default)
  962. // chatView.scrollToLast(animated: true)
  963. // }
  964. // default:
  965. // break
  966. // }
  967. // }
  968. //}
  969. // MARK: - SAIInputBarDelegate & SAIInputBarDisplayable
  970. extension JCChatViewController: SAIInputBarDelegate, SAIInputBarDisplayable {
  971. open override var inputAccessoryView: UIView? {
  972. return toolbar
  973. }
  974. open var scrollView: SAIInputBarScrollViewType {
  975. return chatView
  976. }
  977. open override var canBecomeFirstResponder: Bool {
  978. return true
  979. }
  980. open func inputView(with item: SAIInputItem) -> UIView? {
  981. if let view = inputViews[item.identifier] {
  982. return view
  983. }
  984. switch item.identifier {
  985. case "kb:emoticon":
  986. let view = JCEmoticonInputView()
  987. view.delegate = self
  988. view.dataSource = self
  989. inputViews[item.identifier] = view
  990. return view
  991. case "kb:toolbox":
  992. let view = SAIToolboxInputView()
  993. view.delegate = self
  994. view.dataSource = self
  995. inputViews[item.identifier] = view
  996. return view
  997. default:
  998. return nil
  999. }
  1000. }
  1001. open func inputViewContentSize(_ inputView: UIView) -> CGSize {
  1002. return CGSize(width: view.frame.width, height: 216)
  1003. }
  1004. func inputBar(_ inputBar: SAIInputBar, shouldDeselectFor item: SAIInputItem) -> Bool {
  1005. return true
  1006. }
  1007. open func inputBar(_ inputBar: SAIInputBar, shouldSelectFor item: SAIInputItem) -> Bool {
  1008. if item.identifier == "kb:audio" {
  1009. return true
  1010. }
  1011. guard let _ = inputView(with: item) else {
  1012. return false
  1013. }
  1014. return true
  1015. }
  1016. open func inputBar(_ inputBar: SAIInputBar, didSelectFor item: SAIInputItem) {
  1017. inputItem = item
  1018. if item.identifier == "kb:audio" {
  1019. inputBar.deselectBarAllItem()
  1020. return
  1021. }
  1022. if let kb = inputView(with: item) {
  1023. inputBar.setInputMode(.selecting(kb), animated: true)
  1024. }
  1025. }
  1026. open func inputBar(didChangeMode inputBar: SAIInputBar) {
  1027. if inputItem?.identifier == "kb:audio" {
  1028. return
  1029. }
  1030. if let item = inputItem, !inputBar.inputMode.isSelecting {
  1031. inputBar.deselectBarItem(item, animated: true)
  1032. }
  1033. }
  1034. open func inputBar(didChangeText inputBar: SAIInputBar) {
  1035. _emoticonSendBtn.isEnabled = inputBar.attributedText.length != 0
  1036. }
  1037. public func inputBar(shouldReturn inputBar: SAIInputBar) -> Bool {
  1038. if inputBar.attributedText.length == 0 {
  1039. return false
  1040. }
  1041. send(forText: inputBar.attributedText)
  1042. inputBar.attributedText = nil
  1043. return false
  1044. }
  1045. func inputBar(_ inputBar: SAIInputBar, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
  1046. let currentIndex = range.location
  1047. if !isGroup {
  1048. return true
  1049. }
  1050. if string == "@" {
  1051. DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
  1052. let vc = JCRemindListViewController()
  1053. vc.finish = { (user, isAtAll, length) in
  1054. self.handleAt(inputBar, range, user, isAtAll, length)
  1055. }
  1056. vc.group = self.conversation.target as? JMSGGroup
  1057. let nav = JCNavigationController(rootViewController: vc)
  1058. self.present(nav, animated: true, completion: {})
  1059. }
  1060. } else {
  1061. return updateRemids(inputBar, string, range, currentIndex)
  1062. }
  1063. return true
  1064. }
  1065. func handleAt(_ inputBar: SAIInputBar, _ range: NSRange, _ user: JMSGUser?, _ isAtAll: Bool, _ length: Int) {
  1066. let text = inputBar.text!
  1067. let currentIndex = range.location
  1068. var displayName = "所有成员"
  1069. if let user = user {
  1070. displayName = user.displayName()
  1071. }
  1072. let remind = JCRemind(user, currentIndex, currentIndex + 2 + displayName.length, displayName.length + 2, isAtAll)
  1073. if text.length == currentIndex + 1 {
  1074. inputBar.text = text + displayName + " "
  1075. } else {
  1076. let index1 = text.index(text.endIndex, offsetBy: currentIndex - text.length + 1)
  1077. let prefix = text.substring(with: (text.startIndex..<index1))
  1078. let index2 = text.index(text.startIndex, offsetBy: currentIndex + 1)
  1079. let suffix = text.substring(with: (index2..<text.endIndex))
  1080. inputBar.text = prefix + displayName + " " + suffix
  1081. let _ = self.updateRemids(inputBar, "@" + displayName + " ", range, currentIndex)
  1082. }
  1083. self.reminds.append(remind)
  1084. self.reminds.sort(by: { (r1, r2) -> Bool in
  1085. return r1.startIndex < r2.startIndex
  1086. })
  1087. }
  1088. func updateRemids(_ inputBar: SAIInputBar, _ string: String, _ range: NSRange, _ currentIndex: Int) -> Bool {
  1089. for index in 0..<reminds.count {
  1090. let remind = reminds[index]
  1091. let length = remind.length
  1092. let startIndex = remind.startIndex
  1093. let endIndex = remind.endIndex
  1094. // Delete
  1095. if currentIndex == endIndex - 1 && string.length == 0 {
  1096. for _ in 0..<length {
  1097. inputBar.deleteBackward()
  1098. }
  1099. // Move Other Index
  1100. for subIndex in (index + 1)..<reminds.count {
  1101. let subTemp = reminds[subIndex]
  1102. subTemp.startIndex -= length
  1103. subTemp.endIndex -= length
  1104. }
  1105. reminds.remove(at: index)
  1106. return false;
  1107. } else if currentIndex > startIndex && currentIndex < endIndex {
  1108. // Delete Content
  1109. if string.length == 0 {
  1110. for subIndex in (index + 1)..<reminds.count {
  1111. let subTemp = reminds[subIndex]
  1112. subTemp.startIndex -= 1
  1113. subTemp.endIndex -= 1
  1114. }
  1115. reminds.remove(at: index)
  1116. return true
  1117. }
  1118. // Add Content
  1119. else {
  1120. for subIndex in (index + 1)..<reminds.count {
  1121. let subTemp = reminds[subIndex]
  1122. subTemp.startIndex += string.length
  1123. subTemp.endIndex += string.length
  1124. }
  1125. reminds.remove(at: index)
  1126. return true
  1127. }
  1128. }
  1129. }
  1130. for index in 0..<reminds.count {
  1131. let tempDic = reminds[index]
  1132. let startIndex = tempDic.startIndex
  1133. if currentIndex <= startIndex {
  1134. if string.count == 0 {
  1135. for subIndex in index..<reminds.count {
  1136. let subTemp = reminds[subIndex]
  1137. subTemp.startIndex -= 1
  1138. subTemp.endIndex -= 1
  1139. }
  1140. return true
  1141. } else {
  1142. for subIndex in index..<reminds.count {
  1143. let subTemp = reminds[subIndex]
  1144. subTemp.startIndex += string.length
  1145. subTemp.endIndex += string.length
  1146. }
  1147. return true
  1148. }
  1149. }
  1150. }
  1151. return true
  1152. }
  1153. func inputBar(touchDown recordButton: UIButton, inputBar: SAIInputBar) {
  1154. if recordingHub != nil {
  1155. recordingHub.removeFromSuperview()
  1156. }
  1157. recordingHub = JCRecordingView(frame: CGRect.zero)
  1158. recordHelper.updateMeterDelegate = recordingHub
  1159. recordingHub.startRecordingHUDAtView(view)
  1160. recordingHub.frame = CGRect(x: view.centerX - 70, y: view.centerY - 70, width: 136, height: 136)
  1161. recordHelper.startRecordingWithPath(String.getRecorderPath()) {
  1162. }
  1163. }
  1164. func inputBar(dragInside recordButton: UIButton, inputBar: SAIInputBar) {
  1165. recordingHub.pauseRecord()
  1166. }
  1167. func inputBar(dragOutside recordButton: UIButton, inputBar: SAIInputBar) {
  1168. recordingHub.resaueRecord()
  1169. }
  1170. func inputBar(touchUpInside recordButton: UIButton, inputBar: SAIInputBar) {
  1171. if recordHelper.recorder == nil {
  1172. return
  1173. }
  1174. recordHelper.finishRecordingCompletion()
  1175. if (recordHelper.recordDuration! as NSString).floatValue < 1 {
  1176. recordingHub.showErrorTips()
  1177. let time: TimeInterval = 1.5
  1178. let hub = recordingHub
  1179. DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + time) {
  1180. hub?.removeFromSuperview()
  1181. }
  1182. return
  1183. } else {
  1184. recordingHub.removeFromSuperview()
  1185. }
  1186. let data = try! Data(contentsOf: URL(fileURLWithPath: recordHelper.recordPath!))
  1187. send(voiceData: data, duration: Double(recordHelper.recordDuration!)!)
  1188. }
  1189. func inputBar(touchUpOutside recordButton: UIButton, inputBar: SAIInputBar) {
  1190. recordHelper.cancelledDeleteWithCompletion()
  1191. recordingHub.removeFromSuperview()
  1192. }
  1193. }
  1194. // MARK: - JCRecordVoiceHelperDelegate
  1195. extension JCChatViewController: JCRecordVoiceHelperDelegate {
  1196. public func beyondLimit(_ time: TimeInterval) {
  1197. recordHelper.finishRecordingCompletion()
  1198. recordingHub.removeFromSuperview()
  1199. let data = try! Data(contentsOf: URL(fileURLWithPath: recordHelper.recordPath!))
  1200. send(voiceData: data, duration: Double(recordHelper.recordDuration!)!)
  1201. }
  1202. }
  1203. extension JCChatViewController: UIGestureRecognizerDelegate {
  1204. public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
  1205. guard let view = touch.view else {
  1206. return true
  1207. }
  1208. if view.isKind(of: JCMessageTextContentView.self) {
  1209. return false
  1210. }
  1211. return true
  1212. }
  1213. }
  1214. extension JCChatViewController: UIDocumentInteractionControllerDelegate {
  1215. func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
  1216. return self
  1217. }
  1218. func documentInteractionControllerViewForPreview(_ controller: UIDocumentInteractionController) -> UIView? {
  1219. return view
  1220. }
  1221. func documentInteractionControllerRectForPreview(_ controller: UIDocumentInteractionController) -> CGRect {
  1222. return view.frame
  1223. }
  1224. }