JCChatViewController.swift 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347
  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().bsImagePicker()
  607. presentImagePicker(vc, select: { (asset: PHAsset) -> Void in
  608. // User selected an asset.
  609. // Do something with it, start upload perhaps?
  610. }, deselect: { (asset: PHAsset) -> Void in
  611. // User deselected an assets.
  612. // Do something, cancel upload?
  613. }, cancel: { (assets: [PHAsset]) -> Void in
  614. // User cancelled. And this where the assets currently selected.
  615. }, finish: { (assets: [PHAsset]) -> Void in
  616. // for item in photos {
  617. // guard let photo = item as? UIImage else {
  618. // return
  619. // }
  620. // DispatchQueue.main.async {
  621. // self.send(forImage: photo)
  622. // }
  623. // }
  624. for asset in assets {
  625. if asset.mediaType == .image {
  626. let options = PHImageRequestOptions()
  627. options.isSynchronous = true
  628. options.deliveryMode = .fastFormat
  629. options.resizeMode = .none
  630. PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .default, options: options, resultHandler: { (image, array) in
  631. guard let uiimage = image else {
  632. return
  633. }
  634. DispatchQueue.main.async {
  635. self.send(forImage: uiimage)
  636. }
  637. })
  638. }
  639. }
  640. }, completion: nil)
  641. }
  642. open func toolbox(_ toolbox: SAIToolboxInputView, didSelectFor item: SAIToolboxItem) {
  643. toolbar.resignFirstResponder()
  644. switch item.identifier {
  645. case "page:pic":
  646. if PHPhotoLibrary.authorizationStatus() != .authorized {
  647. PHPhotoLibrary.requestAuthorization({ (status) in
  648. DispatchQueue.main.sync {
  649. if status != .authorized {
  650. self.gotoApplicationSettings(alertMessage: "无权限访问照片,请在设备的设置中允许访问照片?")
  651. } else {
  652. self._pushToSelectPhotos()
  653. }
  654. }
  655. })
  656. } else {
  657. self._pushToSelectPhotos()
  658. }
  659. case "page:camera":
  660. present(imagePicker, animated: true, completion: nil)
  661. case "page:video_s":
  662. present(videoPicker, animated: true, completion: nil)
  663. case "page:location":
  664. let vc = JCAddMapViewController()
  665. vc.addressBlock = { (dict: Dictionary?) in
  666. if dict != nil {
  667. let lon = Float(dict?["lon"] as! String)
  668. let lat = Float(dict?["lat"] as! String)
  669. let address = dict?["address"] as! String
  670. self.send(address: address, lon: NSNumber(value: lon!), lat: NSNumber(value: lat!))
  671. }
  672. }
  673. navigationController?.pushViewController(vc, animated: true)
  674. case "page:businessCard":
  675. let vc = FriendsBusinessCardViewController()
  676. vc.conversation = conversation
  677. let nav = JCNavigationController(rootViewController: vc)
  678. present(nav, animated: true, completion: {
  679. self.toolbar.isHidden = true
  680. })
  681. default:
  682. break
  683. }
  684. }
  685. open override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
  686. super.present(viewControllerToPresent, animated: flag, completion: completion)
  687. }
  688. }
  689. // MARK: - UIImagePickerControllerDelegate & YHPhotoPickerViewControllerDelegate
  690. extension JCChatViewController: YHPhotoPickerViewControllerDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
  691. func selectedPhotoBeyondLimit(_ count: Int32, currentView view: UIView!) {
  692. MBProgressHUD_JChat.show(text: "最多选择\(count)张图片", view: nil)
  693. }
  694. func yhPhotoPickerViewController(_ PhotoPickerViewController: YHSelectPhotoViewController!, selectedPhotos photos: [Any]!) {
  695. for item in photos {
  696. guard let photo = item as? UIImage else {
  697. return
  698. }
  699. DispatchQueue.main.async {
  700. self.send(forImage: photo)
  701. }
  702. }
  703. }
  704. func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
  705. picker.dismiss(animated: true, completion: nil)
  706. }
  707. func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
  708. picker.dismiss(animated: true, completion: nil)
  709. let image = info[UIImagePickerController.InfoKey.originalImage] as! UIImage?
  710. if let image = image?.fixOrientation() {
  711. send(forImage: image)
  712. }
  713. let videoUrl = info[UIImagePickerController.InfoKey.mediaURL] as! URL?
  714. if videoUrl != nil {
  715. let data = try! Data(contentsOf: videoUrl!)
  716. send(fileData: data)
  717. }
  718. }
  719. }
  720. // MARK: - JCMessageDelegate
  721. extension JCChatViewController: JCMessageDelegate {
  722. func message(message: JCMessageType, videoData data: Data?) {
  723. if let data = data {
  724. JCVideoManager.playVideo(data: data, currentViewController: self)
  725. }
  726. }
  727. func message(message: JCMessageType, location address: String?, lat: Double, lon: Double) {
  728. let vc = JCAddMapViewController()
  729. vc.isOnlyShowMap = true
  730. vc.lat = lat
  731. vc.lon = lon
  732. navigationController?.pushViewController(vc, animated: true)
  733. }
  734. func message(message: JCMessageType, image: UIImage?) {
  735. let browserImageVC = JCImageBrowserViewController()
  736. browserImageVC.messages = messages
  737. browserImageVC.conversation = conversation
  738. browserImageVC.currentMessage = message
  739. present(browserImageVC, animated: true) {
  740. self.toolbar.isHidden = true
  741. }
  742. }
  743. func message(message: JCMessageType, fileData data: Data?, fileName: String?, fileType: String?) {
  744. if data == nil {
  745. let vc = JCFileDownloadViewController()
  746. vc.title = fileName
  747. let msg = conversation.message(withMessageId: message.msgId)
  748. vc.fileSize = msg?.ex.fileSize
  749. vc.message = msg
  750. navigationController?.pushViewController(vc, animated: true)
  751. } else {
  752. guard let fileType = fileType else {
  753. return
  754. }
  755. let msg = conversation.message(withMessageId: message.msgId)!
  756. let content = msg.content as! JMSGFileContent
  757. switch fileType.fileFormat() {
  758. case .document:
  759. let vc = JCDocumentViewController()
  760. vc.title = fileName
  761. vc.fileData = data
  762. vc.filePath = content.originMediaLocalPath
  763. vc.fileType = fileType
  764. navigationController?.pushViewController(vc, animated: true)
  765. case .video, .voice:
  766. let url = URL(fileURLWithPath: content.originMediaLocalPath ?? "")
  767. try! JCVideoManager.playVideo(data: Data(contentsOf: url), fileType, currentViewController: self)
  768. case .photo:
  769. let browserImageVC = JCImageBrowserViewController()
  770. let image = UIImage(contentsOfFile: content.originMediaLocalPath ?? "")
  771. browserImageVC.imageArr = [image!]
  772. browserImageVC.imgCurrentIndex = 0
  773. present(browserImageVC, animated: true) {
  774. self.toolbar.isHidden = true
  775. }
  776. default:
  777. let url = URL(fileURLWithPath: content.originMediaLocalPath ?? "")
  778. documentInteractionController.url = url
  779. documentInteractionController.presentOptionsMenu(from: .zero, in: self.view, animated: true)
  780. }
  781. }
  782. }
  783. func message(message: JCMessageType, user: JMSGUser?, businessCardName: String, businessCardAppKey: String) {
  784. if let user = user {
  785. let vc = JCUserInfoViewController()
  786. vc.user = user
  787. navigationController?.pushViewController(vc, animated: true)
  788. }
  789. }
  790. func clickTips(message: JCMessageType) {
  791. currentMessage = message
  792. // let alertView = UIAlertView(title: "重新发送", message: "是否重新发送该消息?", delegate: self, cancelButtonTitle: "取消", otherButtonTitles: "发送")
  793. let okAction = UIAlertAction(title: "发送", style: .default) { (action) in
  794. if let index = self.messages.index(self.currentMessage) {
  795. self.messages.remove(at: index)
  796. self.chatView.remove(at: index)
  797. let msg = self.conversation.message(withMessageId: self.currentMessage.msgId)
  798. self.currentMessage.options.state = .sending
  799. if let msg = msg {
  800. if let content = self.currentMessage.content as? JCMessageImageContent,
  801. let imageContent = msg.content as? JMSGImageContent
  802. {
  803. imageContent.uploadHandler = { (percent:Float, msgId:(String?)) -> Void in
  804. content.upload?(percent)
  805. }
  806. }
  807. }
  808. self.messages.append(self.currentMessage as! JCMessage)
  809. self.chatView.append(self.currentMessage)
  810. self.conversation.send(msg!, optionalContent: JMSGOptionalContent.ex.default)
  811. self.chatView.scrollToLast(animated: true)
  812. }
  813. }
  814. let cancelAction = UIAlertAction(title: "取消", style: .cancel) { (action) in
  815. //
  816. }
  817. self.showDefaultConfirm(title: "重新发送", message: "是否重新发送该消息?", okAction: okAction, cancelAction: cancelAction)
  818. // alertView.show()
  819. }
  820. func tapAvatarView(message: JCMessageType) {
  821. toolbar.resignFirstResponder()
  822. if message.options.alignment == .right {
  823. navigationController?.pushViewController(SPersonViewController(), animated: true)
  824. } else {
  825. guard let uid = message.sender?.username else {
  826. return
  827. }
  828. goToPerson(uid: uid)
  829. }
  830. }
  831. func longTapAvatarView(message: JCMessageType) {
  832. if !isGroup || message.options.alignment == .right {
  833. return
  834. }
  835. toolbar.becomeFirstResponder()
  836. if let user = message.sender {
  837. toolbar.text.append("@")
  838. handleAt(toolbar, NSMakeRange(toolbar.text.length - 1, 0), user, false, user.displayName().length)
  839. }
  840. }
  841. func tapUnreadTips(message: JCMessageType) {
  842. let vc = UnreadListViewController()
  843. let msg = conversation.message(withMessageId: message.msgId)
  844. vc.message = msg
  845. navigationController?.pushViewController(vc, animated: true)
  846. }
  847. private func goToPerson(uid: String) {
  848. let storyBoard = UIStoryboard(name: "contacts", bundle: nil)
  849. guard let personVC = storyBoard.instantiateViewController(withIdentifier: "ContactPersonInfoV2") as? ContactPersonInfoV2ViewController else {
  850. return
  851. }
  852. let person = PersonV2()
  853. person.id = uid
  854. personVC.person = person
  855. navigationController?.pushViewController(personVC, animated: true)
  856. }
  857. }
  858. extension JCChatViewController: JCChatViewDelegate {
  859. func refershChatView( chatView: JCChatView) {
  860. messagePage += 1
  861. _loadMessage(messagePage)
  862. chatView.stopRefresh()
  863. }
  864. func deleteMessage(message: JCMessageType) {
  865. conversation.deleteMessage(withMessageId: message.msgId)
  866. if let index = messages.index(message) {
  867. jMessageCount -= 1
  868. messages.remove(at: index)
  869. if let message = messages.last {
  870. if message.content is JCMessageTimeLineContent {
  871. messages.removeLast()
  872. chatView.remove(at: messages.count)
  873. }
  874. }
  875. }
  876. }
  877. func forwardMessage(message: JCMessageType) {
  878. if let message = conversation.message(withMessageId: message.msgId) {
  879. let vc = JCForwardViewController()
  880. vc.message = message
  881. let nav = JCNavigationController(rootViewController: vc)
  882. self.present(nav, animated: true, completion: {
  883. self.toolbar.isHidden = true
  884. })
  885. }
  886. }
  887. func withdrawMessage(message: JCMessageType) {
  888. guard let message = conversation.message(withMessageId: message.msgId) else {
  889. return
  890. }
  891. JMSGMessage.retractMessage(message, completionHandler: { (result, error) in
  892. if error == nil {
  893. if let index = self.messages.index(message) {
  894. let msg = self._parseMessage(self.conversation.message(withMessageId: message.msgId)!, false)
  895. self.messages[index] = msg
  896. self.chatView.update(msg, at: index)
  897. }
  898. } else {
  899. MBProgressHUD_JChat.show(text: "发送时间过长,不能撤回", view: self.view)
  900. }
  901. })
  902. }
  903. func indexPathsForVisibleItems(chatView: JCChatView, items: [IndexPath]) {
  904. for item in items {
  905. if item.row <= minIndex {
  906. var msgs: [JCMessage] = []
  907. for index in item.row...minIndex {
  908. msgs.append(messages[index])
  909. }
  910. updateUnread(msgs)
  911. minIndex = item.row
  912. }
  913. }
  914. }
  915. fileprivate func updateUnread(_ messages: [JCMessage]) {
  916. for message in messages {
  917. if message.options.alignment != .left {
  918. continue
  919. }
  920. if let msg = conversation.message(withMessageId: message.msgId) {
  921. if msg.isHaveRead {
  922. continue
  923. }
  924. msg.setMessageHaveRead({ _,_ in
  925. })
  926. }
  927. }
  928. }
  929. }
  930. //extension JCChatViewController: UIAlertViewDelegate {
  931. //
  932. // func alertView(_ alertView: UIAlertView, clickedButtonAt buttonIndex: Int) {
  933. // if alertView.tag == 10001 {
  934. // if buttonIndex == 1 {
  935. // JCAppManager.openAppSetter()
  936. // }
  937. // return
  938. // }
  939. // switch buttonIndex {
  940. // case 1:
  941. // if let index = messages.index(currentMessage) {
  942. // messages.remove(at: index)
  943. // chatView.remove(at: index)
  944. // let msg = conversation.message(withMessageId: currentMessage.msgId)
  945. // currentMessage.options.state = .sending
  946. //
  947. // if let msg = msg {
  948. // if let content = currentMessage.content as? JCMessageImageContent,
  949. // let imageContent = msg.content as? JMSGImageContent
  950. // {
  951. // imageContent.uploadHandler = { (percent:Float, msgId:(String?)) -> Void in
  952. // content.upload?(percent)
  953. // }
  954. // }
  955. // }
  956. // messages.append(currentMessage as! JCMessage)
  957. // chatView.append(currentMessage)
  958. // conversation.send(msg!, optionalContent: JMSGOptionalContent.ex.default)
  959. // chatView.scrollToLast(animated: true)
  960. // }
  961. // default:
  962. // break
  963. // }
  964. // }
  965. //}
  966. // MARK: - SAIInputBarDelegate & SAIInputBarDisplayable
  967. extension JCChatViewController: SAIInputBarDelegate, SAIInputBarDisplayable {
  968. open override var inputAccessoryView: UIView? {
  969. return toolbar
  970. }
  971. open var scrollView: SAIInputBarScrollViewType {
  972. return chatView
  973. }
  974. open override var canBecomeFirstResponder: Bool {
  975. return true
  976. }
  977. open func inputView(with item: SAIInputItem) -> UIView? {
  978. if let view = inputViews[item.identifier] {
  979. return view
  980. }
  981. switch item.identifier {
  982. case "kb:emoticon":
  983. let view = JCEmoticonInputView()
  984. view.delegate = self
  985. view.dataSource = self
  986. inputViews[item.identifier] = view
  987. return view
  988. case "kb:toolbox":
  989. let view = SAIToolboxInputView()
  990. view.delegate = self
  991. view.dataSource = self
  992. inputViews[item.identifier] = view
  993. return view
  994. default:
  995. return nil
  996. }
  997. }
  998. open func inputViewContentSize(_ inputView: UIView) -> CGSize {
  999. return CGSize(width: view.frame.width, height: 216)
  1000. }
  1001. func inputBar(_ inputBar: SAIInputBar, shouldDeselectFor item: SAIInputItem) -> Bool {
  1002. return true
  1003. }
  1004. open func inputBar(_ inputBar: SAIInputBar, shouldSelectFor item: SAIInputItem) -> Bool {
  1005. if item.identifier == "kb:audio" {
  1006. return true
  1007. }
  1008. guard let _ = inputView(with: item) else {
  1009. return false
  1010. }
  1011. return true
  1012. }
  1013. open func inputBar(_ inputBar: SAIInputBar, didSelectFor item: SAIInputItem) {
  1014. inputItem = item
  1015. if item.identifier == "kb:audio" {
  1016. inputBar.deselectBarAllItem()
  1017. return
  1018. }
  1019. if let kb = inputView(with: item) {
  1020. inputBar.setInputMode(.selecting(kb), animated: true)
  1021. }
  1022. }
  1023. open func inputBar(didChangeMode inputBar: SAIInputBar) {
  1024. if inputItem?.identifier == "kb:audio" {
  1025. return
  1026. }
  1027. if let item = inputItem, !inputBar.inputMode.isSelecting {
  1028. inputBar.deselectBarItem(item, animated: true)
  1029. }
  1030. }
  1031. open func inputBar(didChangeText inputBar: SAIInputBar) {
  1032. _emoticonSendBtn.isEnabled = inputBar.attributedText.length != 0
  1033. }
  1034. public func inputBar(shouldReturn inputBar: SAIInputBar) -> Bool {
  1035. if inputBar.attributedText.length == 0 {
  1036. return false
  1037. }
  1038. send(forText: inputBar.attributedText)
  1039. inputBar.attributedText = nil
  1040. return false
  1041. }
  1042. func inputBar(_ inputBar: SAIInputBar, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
  1043. let currentIndex = range.location
  1044. if !isGroup {
  1045. return true
  1046. }
  1047. if string == "@" {
  1048. DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
  1049. let vc = JCRemindListViewController()
  1050. vc.finish = { (user, isAtAll, length) in
  1051. self.handleAt(inputBar, range, user, isAtAll, length)
  1052. }
  1053. vc.group = self.conversation.target as? JMSGGroup
  1054. let nav = JCNavigationController(rootViewController: vc)
  1055. self.present(nav, animated: true, completion: {})
  1056. }
  1057. } else {
  1058. return updateRemids(inputBar, string, range, currentIndex)
  1059. }
  1060. return true
  1061. }
  1062. func handleAt(_ inputBar: SAIInputBar, _ range: NSRange, _ user: JMSGUser?, _ isAtAll: Bool, _ length: Int) {
  1063. let text = inputBar.text!
  1064. let currentIndex = range.location
  1065. var displayName = "所有成员"
  1066. if let user = user {
  1067. displayName = user.displayName()
  1068. }
  1069. let remind = JCRemind(user, currentIndex, currentIndex + 2 + displayName.length, displayName.length + 2, isAtAll)
  1070. if text.length == currentIndex + 1 {
  1071. inputBar.text = text + displayName + " "
  1072. } else {
  1073. let index1 = text.index(text.endIndex, offsetBy: currentIndex - text.length + 1)
  1074. let prefix = text.substring(with: (text.startIndex..<index1))
  1075. let index2 = text.index(text.startIndex, offsetBy: currentIndex + 1)
  1076. let suffix = text.substring(with: (index2..<text.endIndex))
  1077. inputBar.text = prefix + displayName + " " + suffix
  1078. let _ = self.updateRemids(inputBar, "@" + displayName + " ", range, currentIndex)
  1079. }
  1080. self.reminds.append(remind)
  1081. self.reminds.sort(by: { (r1, r2) -> Bool in
  1082. return r1.startIndex < r2.startIndex
  1083. })
  1084. }
  1085. func updateRemids(_ inputBar: SAIInputBar, _ string: String, _ range: NSRange, _ currentIndex: Int) -> Bool {
  1086. for index in 0..<reminds.count {
  1087. let remind = reminds[index]
  1088. let length = remind.length
  1089. let startIndex = remind.startIndex
  1090. let endIndex = remind.endIndex
  1091. // Delete
  1092. if currentIndex == endIndex - 1 && string.length == 0 {
  1093. for _ in 0..<length {
  1094. inputBar.deleteBackward()
  1095. }
  1096. // Move Other Index
  1097. for subIndex in (index + 1)..<reminds.count {
  1098. let subTemp = reminds[subIndex]
  1099. subTemp.startIndex -= length
  1100. subTemp.endIndex -= length
  1101. }
  1102. reminds.remove(at: index)
  1103. return false;
  1104. } else if currentIndex > startIndex && currentIndex < endIndex {
  1105. // Delete Content
  1106. if string.length == 0 {
  1107. for subIndex in (index + 1)..<reminds.count {
  1108. let subTemp = reminds[subIndex]
  1109. subTemp.startIndex -= 1
  1110. subTemp.endIndex -= 1
  1111. }
  1112. reminds.remove(at: index)
  1113. return true
  1114. }
  1115. // Add Content
  1116. else {
  1117. for subIndex in (index + 1)..<reminds.count {
  1118. let subTemp = reminds[subIndex]
  1119. subTemp.startIndex += string.length
  1120. subTemp.endIndex += string.length
  1121. }
  1122. reminds.remove(at: index)
  1123. return true
  1124. }
  1125. }
  1126. }
  1127. for index in 0..<reminds.count {
  1128. let tempDic = reminds[index]
  1129. let startIndex = tempDic.startIndex
  1130. if currentIndex <= startIndex {
  1131. if string.count == 0 {
  1132. for subIndex in index..<reminds.count {
  1133. let subTemp = reminds[subIndex]
  1134. subTemp.startIndex -= 1
  1135. subTemp.endIndex -= 1
  1136. }
  1137. return true
  1138. } else {
  1139. for subIndex in index..<reminds.count {
  1140. let subTemp = reminds[subIndex]
  1141. subTemp.startIndex += string.length
  1142. subTemp.endIndex += string.length
  1143. }
  1144. return true
  1145. }
  1146. }
  1147. }
  1148. return true
  1149. }
  1150. func inputBar(touchDown recordButton: UIButton, inputBar: SAIInputBar) {
  1151. if recordingHub != nil {
  1152. recordingHub.removeFromSuperview()
  1153. }
  1154. recordingHub = JCRecordingView(frame: CGRect.zero)
  1155. recordHelper.updateMeterDelegate = recordingHub
  1156. recordingHub.startRecordingHUDAtView(view)
  1157. recordingHub.frame = CGRect(x: view.centerX - 70, y: view.centerY - 70, width: 136, height: 136)
  1158. recordHelper.startRecordingWithPath(String.getRecorderPath()) {
  1159. }
  1160. }
  1161. func inputBar(dragInside recordButton: UIButton, inputBar: SAIInputBar) {
  1162. recordingHub.pauseRecord()
  1163. }
  1164. func inputBar(dragOutside recordButton: UIButton, inputBar: SAIInputBar) {
  1165. recordingHub.resaueRecord()
  1166. }
  1167. func inputBar(touchUpInside recordButton: UIButton, inputBar: SAIInputBar) {
  1168. if recordHelper.recorder == nil {
  1169. return
  1170. }
  1171. recordHelper.finishRecordingCompletion()
  1172. if (recordHelper.recordDuration! as NSString).floatValue < 1 {
  1173. recordingHub.showErrorTips()
  1174. let time: TimeInterval = 1.5
  1175. let hub = recordingHub
  1176. DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + time) {
  1177. hub?.removeFromSuperview()
  1178. }
  1179. return
  1180. } else {
  1181. recordingHub.removeFromSuperview()
  1182. }
  1183. let data = try! Data(contentsOf: URL(fileURLWithPath: recordHelper.recordPath!))
  1184. send(voiceData: data, duration: Double(recordHelper.recordDuration!)!)
  1185. }
  1186. func inputBar(touchUpOutside recordButton: UIButton, inputBar: SAIInputBar) {
  1187. recordHelper.cancelledDeleteWithCompletion()
  1188. recordingHub.removeFromSuperview()
  1189. }
  1190. }
  1191. // MARK: - JCRecordVoiceHelperDelegate
  1192. extension JCChatViewController: JCRecordVoiceHelperDelegate {
  1193. public func beyondLimit(_ time: TimeInterval) {
  1194. recordHelper.finishRecordingCompletion()
  1195. recordingHub.removeFromSuperview()
  1196. let data = try! Data(contentsOf: URL(fileURLWithPath: recordHelper.recordPath!))
  1197. send(voiceData: data, duration: Double(recordHelper.recordDuration!)!)
  1198. }
  1199. }
  1200. extension JCChatViewController: UIGestureRecognizerDelegate {
  1201. public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
  1202. guard let view = touch.view else {
  1203. return true
  1204. }
  1205. if view.isKind(of: JCMessageTextContentView.self) {
  1206. return false
  1207. }
  1208. return true
  1209. }
  1210. }
  1211. extension JCChatViewController: UIDocumentInteractionControllerDelegate {
  1212. func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
  1213. return self
  1214. }
  1215. func documentInteractionControllerViewForPreview(_ controller: UIDocumentInteractionController) -> UIView? {
  1216. return view
  1217. }
  1218. func documentInteractionControllerRectForPreview(_ controller: UIDocumentInteractionController) -> CGRect {
  1219. return view.frame
  1220. }
  1221. }