// // JCChatViewController.swift // JChat // // Created by deng on 2017/2/28. // Copyright © 2017年 HXHG. All rights reserved. // import UIKit import YHPhotoKit import MobileCoreServices class JCChatViewController: UIViewController { open var conversation: JMSGConversation fileprivate var isGroup = false //MARK - life cycle public required init(conversation: JMSGConversation) { self.conversation = conversation super.init(nibName: nil, bundle: nil) automaticallyAdjustsScrollViewInsets = false; if let draft = JCDraft.getDraft(conversation) { self.draft = draft } } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() _init() } override func loadView() { super.loadView() // y:64 - y:0 let frame = CGRect(x: 0, y: 0, width: self.view.width, height: self.view.height - 64) chatView = JCChatView(frame: frame, chatViewLayout: chatViewLayout) chatView.delegate = self chatView.messageDelegate = self toolbar.translatesAutoresizingMaskIntoConstraints = false toolbar.delegate = self toolbar.text = draft } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) toolbar.isHidden = false } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillChangeFrameNotification, object: nil) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) NotificationCenter.default.addObserver(self, selector: #selector(keyboardFrameChanged(_:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil) if let group = conversation.target as? JMSGGroup { self.title = group.displayName() } navigationController?.interactivePopGestureRecognizer?.delegate = self navigationController?.navigationBar.barTintColor = base_color navigationController?.navigationBar.tintColor = navbar_tint_color } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) // navigationController?.navigationBar.isTranslucent = true JCDraft.update(text: toolbar.text, conversation: conversation) } deinit { NotificationCenter.default.removeObserver(self) JMessage.remove(self, with: conversation) } private var draft: String? fileprivate lazy var toolbar: SAIInputBar = SAIInputBar(type: .default) fileprivate lazy var inputViews: [String: UIView] = [:] fileprivate weak var inputItem: SAIInputItem? var chatViewLayout: JCChatViewLayout = .init() var chatView: JCChatView! fileprivate lazy var reminds: [JCRemind] = [] fileprivate lazy var documentInteractionController = UIDocumentInteractionController() fileprivate lazy var imagePicker: UIImagePickerController = { var picker = UIImagePickerController() picker.sourceType = .camera picker.cameraCaptureMode = .photo picker.delegate = self return picker }() fileprivate lazy var videoPicker: UIImagePickerController = { var picker = UIImagePickerController() picker.mediaTypes = [kUTTypeMovie as String] picker.sourceType = .camera picker.cameraCaptureMode = .video picker.videoMaximumDuration = 10 picker.delegate = self return picker }() fileprivate lazy var _emoticonGroups: [JCCEmoticonGroup] = { var groups: [JCCEmoticonGroup] = [] if let group = JCCEmoticonGroup(identifier: "com.apple.emoji") { groups.append(group) } if let group = JCCEmoticonGroup(identifier: "cn.jchat.guangguang") { groups.append(group) } return groups }() fileprivate lazy var _emoticonSendBtn: UIButton = { var button = UIButton() button.titleLabel?.font = UIFont.systemFont(ofSize: 15) button.contentEdgeInsets = UIEdgeInsets(top: 0, left: 10 + 8, bottom: 0, right: 8) button.setTitle("发送", for: .normal) button.setTitleColor(.white, for: .normal) button.setBackgroundImage(UIImage.loadImage("chat_emoticon_btn_send_blue"), for: .normal) button.setBackgroundImage(UIImage.loadImage("chat_emoticon_btn_send_gray"), for: .disabled) button.addTarget(self, action: #selector(_sendHandler), for: .touchUpInside) return button }() fileprivate lazy var emoticonView: JCEmoticonInputView = { let emoticonView = JCEmoticonInputView(frame: CGRect(x: 0, y: 0, width: self.view.width, height: 275)) emoticonView.delegate = self emoticonView.dataSource = self return emoticonView }() fileprivate lazy var toolboxView: SAIToolboxInputView = { var toolboxView = SAIToolboxInputView(frame: CGRect(x: 0, y: 0, width: self.view.width, height: 197)) toolboxView.delegate = self toolboxView.dataSource = self return toolboxView }() fileprivate lazy var _toolboxItems: [SAIToolboxItem] = { return [ SAIToolboxItem("page:pic", "照片", UIImage.loadImage("chat_tool_pic")), SAIToolboxItem("page:camera", "拍照", UIImage.loadImage("chat_tool_camera")), SAIToolboxItem("page:video_s", "小视频", UIImage.loadImage("chat_tool_video_short")), SAIToolboxItem("page:location", "位置", UIImage.loadImage("chat_tool_location")), SAIToolboxItem("page:businessCard", "名片", UIImage.loadImage("chat_tool_businessCard")), ] }() fileprivate var myAvator: UIImage? lazy var messages: [JCMessage] = [] fileprivate let currentUser = JMSGUser.myInfo() fileprivate var messagePage = 0 fileprivate var currentMessage: JCMessageType! fileprivate var maxTime = 0 fileprivate var minTime = 0 fileprivate var minIndex = 0 fileprivate var jMessageCount = 0 fileprivate var isFristLaunch = true fileprivate var recordingHub: JCRecordingView! fileprivate lazy var recordHelper: JCRecordVoiceHelper = { let recordHelper = JCRecordVoiceHelper() recordHelper.delegate = self return recordHelper }() fileprivate lazy var leftButton: UIButton = { let leftButton = UIButton(frame: CGRect(x: 0, y: 0, width: 90, height: 65 / 3)) leftButton.setImage(UIImage.loadImage("com_icon_back"), for: .normal) leftButton.setImage(UIImage.loadImage("com_icon_back"), for: .highlighted) leftButton.addTarget(self, action: #selector(_back), for: .touchUpInside) leftButton.setTitle("会话", for: .normal) leftButton.titleLabel?.font = UIFont.systemFont(ofSize: 16) leftButton.contentHorizontalAlignment = .left return leftButton }() private func _init() { myAvator = UIImage.getMyAvator() isGroup = conversation.ex.isGroup _updateTitle() view.backgroundColor = .white JMessage.add(self, with: conversation) _setupNavigation() _loadMessage(messagePage) let tap = UITapGestureRecognizer(target: self, action: #selector(_tapView)) tap.delegate = self chatView.addGestureRecognizer(tap) view.addSubview(chatView) _updateBadge() NotificationCenter.default.addObserver(self, selector: #selector(keyboardFrameChanged(_:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(_removeAllMessage), name: NSNotification.Name(rawValue: kDeleteAllMessage), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(_reloadMessage), name: NSNotification.Name(rawValue: kReloadAllMessage), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(_updateFileMessage(_:)), name: NSNotification.Name(rawValue: kUpdateFileMessage), object: nil) } @objc func _updateFileMessage(_ notification: Notification) { let userInfo = notification.userInfo let msgId = userInfo?[kUpdateFileMessage] as! String let message = conversation.message(withMessageId: msgId)! let content = message.content as! JMSGFileContent let url = URL(fileURLWithPath: content.originMediaLocalPath ?? "") let data = try! Data(contentsOf: url) updateMediaMessage(message, data: data) } private func _updateTitle() { if let group = conversation.target as? JMSGGroup { title = group.displayName() } else { title = conversation.title } } @objc func _reloadMessage() { _removeAllMessage() messagePage = 0 _loadMessage(messagePage) DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { self.chatView.scrollToLast(animated: false) } } @objc func _removeAllMessage() { jMessageCount = 0 messages.removeAll() chatView.removeAll() } @objc func _tapView() { view.endEditing(true) toolbar.resignFirstResponder() } private func _setupNavigation() { let navButton = UIButton(frame: CGRect(x: 0, y: 0, width: 30, height: 30)) if isGroup { navButton.setImage(UIImage.loadImage("com_icon_group_w"), for: .normal) navButton.addTarget(self, action: #selector(_getGroupInfo), for: .touchUpInside) } else { navButton.setImage(UIImage.loadImage("com_icon_user_w"), for: .normal) navButton.addTarget(self, action: #selector(_getSingleInfo), for: .touchUpInside) } let item1 = UIBarButtonItem(customView: navButton) navigationItem.rightBarButtonItems = [item1] let item2 = UIBarButtonItem(customView: leftButton) navigationItem.leftBarButtonItems = [item2] navigationController?.interactivePopGestureRecognizer?.isEnabled = true navigationController?.interactivePopGestureRecognizer?.delegate = self } @objc func _back() { navigationController?.popViewController(animated: true) } fileprivate func _loadMessage(_ page: Int) { let messages = conversation.messageArrayFromNewest(withOffset: NSNumber(value: jMessageCount), limit: NSNumber(value: 17)) if messages.count == 0 { return } var msgs: [JCMessage] = [] for index in 0.. Bool { if maxTime == 0 || minTime == 0 { maxTime = time minTime = time return true } if (time - maxTime) >= 5 * 60000 { maxTime = time return true } if (minTime - time) >= 5 * 60000 { minTime = time return true } return false } // MARK: - parse message fileprivate func _parseMessage(_ message: JMSGMessage, _ isNewMessage: Bool = true) -> JCMessage { if isNewMessage { jMessageCount += 1 } return message.parseMessage(self, { [weak self] (message, data) in self?.updateMediaMessage(message, data: data) }) } // MARK: - send message func send(_ message: JCMessage, _ jmessage: JMSGMessage) { if isNeedInsertTimeLine(jmessage.timestamp.intValue) { let timeContent = JCMessageTimeLineContent(date: Date(timeIntervalSince1970: TimeInterval(jmessage.timestamp.intValue / 1000))) let m = JCMessage(content: timeContent) m.options.showsTips = false messages.append(m) chatView.append(m) } message.msgId = jmessage.msgId message.name = currentUser.displayName() message.senderAvator = myAvator message.sender = currentUser message.options.alignment = .right message.options.state = .sending if let group = conversation.target as? JMSGGroup { message.targetType = .group message.unreadCount = group.memberArray().count - 1 } else { message.targetType = .single message.unreadCount = 1 } chatView.append(message) messages.append(message) chatView.scrollToLast(animated: false) conversation.send(jmessage, optionalContent: JMSGOptionalContent.ex.default) } func send(forText text: NSAttributedString) { let message = JCMessage(content: JCMessageTextContent(attributedText: text)) let content = JMSGTextContent(text: text.string) let msg = JMSGMessage.ex.createMessage(conversation, content, reminds) reminds.removeAll() send(message, msg) } func send(forLargeEmoticon emoticon: JCCEmoticonLarge) { guard let image = emoticon.contents as? UIImage else { return } let messageContent = JCMessageImageContent() messageContent.image = image messageContent.delegate = self let message = JCMessage(content: messageContent) let content = JMSGImageContent(imageData: image.pngData()!) let msg = JMSGMessage.ex.createMessage(conversation, content!, nil) msg.ex.isLargeEmoticon = true message.options.showsTips = true send(message, msg) } func send(forImage image: UIImage) { let data = image.jpegData(compressionQuality: 1) let content = JMSGImageContent(imageData: data!) let message = JMSGMessage.ex.createMessage(conversation, content!, nil) let imageContent = JCMessageImageContent() imageContent.delegate = self imageContent.image = image content?.uploadHandler = { (percent:Float, msgId:(String?)) -> Void in imageContent.upload?(percent) } let msg = JCMessage(content: imageContent) send(msg, message) } func send(voiceData: Data, duration: Double) { let voiceContent = JCMessageVoiceContent() voiceContent.data = voiceData voiceContent.duration = duration voiceContent.delegate = self let content = JMSGVoiceContent(voiceData: voiceData, voiceDuration: NSNumber(value: duration)) let message = JMSGMessage.ex.createMessage(conversation, content, nil) let msg = JCMessage(content: voiceContent) send(msg, message) } func send(fileData: Data) { let videoContent = JCMessageVideoContent() videoContent.data = fileData videoContent.delegate = self let content = JMSGFileContent(fileData: fileData, fileName: "小视频") let message = JMSGMessage.ex.createMessage(conversation, content, nil) message.ex.isShortVideo = true let msg = JCMessage(content: videoContent) send(msg, message) } func send(address: String, lon: NSNumber, lat: NSNumber) { let locationContent = JCMessageLocationContent() locationContent.address = address locationContent.lat = lat.doubleValue locationContent.lon = lon.doubleValue locationContent.delegate = self let content = JMSGLocationContent(latitude: lat, longitude: lon, scale: NSNumber(value: 1), address: address) let message = JMSGMessage.ex.createMessage(conversation, content, nil) let msg = JCMessage(content: locationContent) send(msg, message) } @objc func keyboardFrameChanged(_ notification: Notification) { let dic = NSDictionary(dictionary: (notification as NSNotification).userInfo!) let keyboardValue = dic.object(forKey: UIResponder.keyboardFrameEndUserInfoKey) as! NSValue let bottomDistance = UIScreen.main.bounds.size.height - keyboardValue.cgRectValue.origin.y let duration = Double(dic.object(forKey: UIResponder.keyboardAnimationDurationUserInfoKey) as! NSNumber) UIView.animate(withDuration: duration, animations: { }) { (finish) in if (bottomDistance == 0 || bottomDistance == self.toolbar.height) && !self.isFristLaunch { return } DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { self.chatView.scrollToLast(animated: false) } self.isFristLaunch = false } } @objc func _sendHandler() { let text = toolbar.attributedText if text != nil && (text?.length)! > 0 { send(forText: text!) toolbar.attributedText = nil } } @objc func _getSingleInfo() { // let vc = JCSingleSettingViewController() // vc.user = conversation.target as? JMSGUser // navigationController?.pushViewController(vc, animated: true) guard let uid = (conversation.target as? JMSGUser)?.username else { return } goToPerson(uid: uid) } @objc func _getGroupInfo() { let vc = JCGroupSettingViewController() let group = conversation.target as? JMSGGroup vc.group = group navigationController?.pushViewController(vc, animated: true) } } //MARK: - JMSGMessage Delegate extension JCChatViewController: JMessageDelegate { fileprivate func updateMediaMessage(_ message: JMSGMessage, data: Data) { DispatchQueue.main.async { if let index = self.messages.index(message) { let msg = self.messages[index] switch(message.contentType) { case .file: if message.ex.isShortVideo { let videoContent = msg.content as! JCMessageVideoContent videoContent.data = data videoContent.delegate = self msg.content = videoContent } else { let fileContent = msg.content as! JCMessageFileContent fileContent.data = data fileContent.delegate = self msg.content = fileContent } case .image: let imageContent = msg.content as! JCMessageImageContent let image = UIImage(data: data) imageContent.image = image msg.content = imageContent default: break } msg.updateSizeIfNeeded = true self.chatView.update(msg, at: index) msg.updateSizeIfNeeded = false // self.chatView.update(msg, at: index) } } } func _updateBadge() { JMSGConversation.allConversations { (result, error) in guard let conversations = result as? [JMSGConversation] else { return } let count = conversations.unreadCount if count == 0 { self.leftButton.setTitle("会话", for: .normal) } else { self.leftButton.setTitle("会话(\(count))", for: .normal) } } } func onReceive(_ message: JMSGMessage!, error: Error!) { if error != nil { return } let message = _parseMessage(message) // TODO: 这个判断是sdk bug导致的,暂时只能这么改 if messages.contains(where: { (m) -> Bool in return m.msgId == message.msgId }) { let indexs = chatView.indexPathsForVisibleItems for index in indexs { var m = messages[index.row] if !m.msgId.isEmpty { m = _parseMessage(conversation.message(withMessageId: m.msgId)!, false) chatView.update(m, at: index.row) } } return } messages.append(message) chatView.append(message) updateUnread([message]) conversation.clearUnreadCount() if !chatView.isRoll { chatView.scrollToLast(animated: true) } _updateBadge() } func onSendMessageResponse(_ message: JMSGMessage!, error: Error!) { if let error = error as NSError? { if error.code == 803009 { MBProgressHUD_JChat.show(text: "发送失败,消息中包含敏感词", view: view, 2.0) } if error.code == 803005 { MBProgressHUD_JChat.show(text: "您已不是群成员", view: view, 2.0) } } if let index = messages.index(message) { let msg = messages[index] msg.options.state = message.ex.state chatView.update(msg, at: index) } } func onReceive(_ retractEvent: JMSGMessageRetractEvent!) { if let index = messages.index(retractEvent.retractMessage) { let msg = _parseMessage(retractEvent.retractMessage, false) messages[index] = msg chatView.update(msg, at: index) } } func onSyncOfflineMessageConversation(_ conversation: JMSGConversation!, offlineMessages: [JMSGMessage]!) { let msgs = offlineMessages.sorted(by: { (m1, m2) -> Bool in return m1.timestamp.intValue < m2.timestamp.intValue }) for item in msgs { let message = _parseMessage(item) messages.append(message) chatView.append(message) updateUnread([message]) conversation.clearUnreadCount() if !chatView.isRoll { chatView.scrollToLast(animated: true) } } _updateBadge() } func onReceive(_ receiptEvent: JMSGMessageReceiptStatusChangeEvent!) { for message in receiptEvent.messages! { if let index = messages.index(message) { let msg = messages[index] msg.unreadCount = message.getUnreadCount() chatView.update(msg, at: index) } } } } // MARK: - JCEmoticonInputViewDataSource & JCEmoticonInputViewDelegate extension JCChatViewController: JCEmoticonInputViewDataSource, JCEmoticonInputViewDelegate { open func numberOfEmotionGroups(in emoticon: JCEmoticonInputView) -> Int { return _emoticonGroups.count } open func emoticon(_ emoticon: JCEmoticonInputView, emotionGroupForItemAt index: Int) -> JCEmoticonGroup { return _emoticonGroups[index] } open func emoticon(_ emoticon: JCEmoticonInputView, numberOfRowsForGroupAt index: Int) -> Int { return _emoticonGroups[index].rows } open func emoticon(_ emoticon: JCEmoticonInputView, numberOfColumnsForGroupAt index: Int) -> Int { return _emoticonGroups[index].columns } open func emoticon(_ emoticon: JCEmoticonInputView, moreViewForGroupAt index: Int) -> UIView? { if _emoticonGroups[index].type.isSmall { return _emoticonSendBtn } else { return nil } } open func emoticon(_ emoticon: JCEmoticonInputView, insetForGroupAt index: Int) -> UIEdgeInsets { return UIEdgeInsets(top: 12, left: 10, bottom: 12 + 24, right: 10) } open func emoticon(_ emoticon: JCEmoticonInputView, didSelectFor item: JCEmoticon) { if item.isBackspace { toolbar.deleteBackward() return } if let emoticon = item as? JCCEmoticonLarge { send(forLargeEmoticon: emoticon) return } if let code = item.contents as? String { return toolbar.insertText(code) } if let image = item.contents as? UIImage { let d = toolbar.font?.descender ?? 0 let h = toolbar.font?.lineHeight ?? 0 let attachment = NSTextAttachment() attachment.image = image attachment.bounds = CGRect(x: 0, y: d, width: h, height: h) toolbar.insertAttributedText(NSAttributedString(attachment: attachment)) return } } } // MARK: - SAIToolboxInputViewDataSource & SAIToolboxInputViewDelegate extension JCChatViewController: SAIToolboxInputViewDataSource, SAIToolboxInputViewDelegate { open func numberOfToolboxItems(in toolbox: SAIToolboxInputView) -> Int { return _toolboxItems.count } open func toolbox(_ toolbox: SAIToolboxInputView, toolboxItemForItemAt index: Int) -> SAIToolboxItem { return _toolboxItems[index] } open func toolbox(_ toolbox: SAIToolboxInputView, numberOfRowsForSectionAt index: Int) -> Int { return 2 } open func toolbox(_ toolbox: SAIToolboxInputView, numberOfColumnsForSectionAt index: Int) -> Int { return 4 } open func toolbox(_ toolbox: SAIToolboxInputView, insetForSectionAt index: Int) -> UIEdgeInsets { return UIEdgeInsets(top: 12, left: 10, bottom: 12, right: 10) } open func toolbox(_ toolbox: SAIToolboxInputView, shouldSelectFor item: SAIToolboxItem) -> Bool { return true } private func _pushToSelectPhotos() { let vc = YHPhotoPickerViewController() vc.maxPhotosCount = 9; vc.pickerDelegate = self present(vc, animated: true) } open func toolbox(_ toolbox: SAIToolboxInputView, didSelectFor item: SAIToolboxItem) { toolbar.resignFirstResponder() switch item.identifier { case "page:pic": if PHPhotoLibrary.authorizationStatus() != .authorized { PHPhotoLibrary.requestAuthorization({ (status) in DispatchQueue.main.sync { if status != .authorized { JCAlertView.bulid().setTitle("无权限访问照片").setMessage("请在设备的设置-极光 IM中允许访问照片。").setDelegate(self).addCancelButton("好的").addButton("去设置").setTag(10001).show() } else { self._pushToSelectPhotos() } } }) } else { _pushToSelectPhotos() } case "page:camera": present(imagePicker, animated: true, completion: nil) case "page:video_s": present(videoPicker, animated: true, completion: nil) case "page:location": let vc = JCAddMapViewController() vc.addressBlock = { (dict: Dictionary?) in if dict != nil { let lon = Float(dict?["lon"] as! String) let lat = Float(dict?["lat"] as! String) let address = dict?["address"] as! String self.send(address: address, lon: NSNumber(value: lon!), lat: NSNumber(value: lat!)) } } navigationController?.pushViewController(vc, animated: true) case "page:businessCard": let vc = FriendsBusinessCardViewController() vc.conversation = conversation let nav = JCNavigationController(rootViewController: vc) present(nav, animated: true, completion: { self.toolbar.isHidden = true }) default: break } } open override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { super.present(viewControllerToPresent, animated: flag, completion: completion) } } // MARK: - UIImagePickerControllerDelegate & YHPhotoPickerViewControllerDelegate extension JCChatViewController: YHPhotoPickerViewControllerDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate { func selectedPhotoBeyondLimit(_ count: Int32, currentView view: UIView!) { MBProgressHUD_JChat.show(text: "最多选择\(count)张图片", view: nil) } func yhPhotoPickerViewController(_ PhotoPickerViewController: YHSelectPhotoViewController!, selectedPhotos photos: [Any]!) { for item in photos { guard let photo = item as? UIImage else { return } DispatchQueue.main.async { self.send(forImage: photo) } } } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { picker.dismiss(animated: true, completion: nil) } func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { picker.dismiss(animated: true, completion: nil) let image = info[UIImagePickerController.InfoKey.originalImage] as! UIImage? if let image = image?.fixOrientation() { send(forImage: image) } let videoUrl = info[UIImagePickerController.InfoKey.mediaURL] as! URL? if videoUrl != nil { let data = try! Data(contentsOf: videoUrl!) send(fileData: data) } } } // MARK: - JCMessageDelegate extension JCChatViewController: JCMessageDelegate { func message(message: JCMessageType, videoData data: Data?) { if let data = data { JCVideoManager.playVideo(data: data, currentViewController: self) } } func message(message: JCMessageType, location address: String?, lat: Double, lon: Double) { let vc = JCAddMapViewController() vc.isOnlyShowMap = true vc.lat = lat vc.lon = lon navigationController?.pushViewController(vc, animated: true) } func message(message: JCMessageType, image: UIImage?) { let browserImageVC = JCImageBrowserViewController() browserImageVC.messages = messages browserImageVC.conversation = conversation browserImageVC.currentMessage = message present(browserImageVC, animated: true) { self.toolbar.isHidden = true } } func message(message: JCMessageType, fileData data: Data?, fileName: String?, fileType: String?) { if data == nil { let vc = JCFileDownloadViewController() vc.title = fileName let msg = conversation.message(withMessageId: message.msgId) vc.fileSize = msg?.ex.fileSize vc.message = msg navigationController?.pushViewController(vc, animated: true) } else { guard let fileType = fileType else { return } let msg = conversation.message(withMessageId: message.msgId)! let content = msg.content as! JMSGFileContent switch fileType.fileFormat() { case .document: let vc = JCDocumentViewController() vc.title = fileName vc.fileData = data vc.filePath = content.originMediaLocalPath vc.fileType = fileType navigationController?.pushViewController(vc, animated: true) case .video, .voice: let url = URL(fileURLWithPath: content.originMediaLocalPath ?? "") try! JCVideoManager.playVideo(data: Data(contentsOf: url), fileType, currentViewController: self) case .photo: let browserImageVC = JCImageBrowserViewController() let image = UIImage(contentsOfFile: content.originMediaLocalPath ?? "") browserImageVC.imageArr = [image!] browserImageVC.imgCurrentIndex = 0 present(browserImageVC, animated: true) { self.toolbar.isHidden = true } default: let url = URL(fileURLWithPath: content.originMediaLocalPath ?? "") documentInteractionController.url = url documentInteractionController.presentOptionsMenu(from: .zero, in: self.view, animated: true) } } } func message(message: JCMessageType, user: JMSGUser?, businessCardName: String, businessCardAppKey: String) { if let user = user { let vc = JCUserInfoViewController() vc.user = user navigationController?.pushViewController(vc, animated: true) } } func clickTips(message: JCMessageType) { currentMessage = message let alertView = UIAlertView(title: "重新发送", message: "是否重新发送该消息?", delegate: self, cancelButtonTitle: "取消", otherButtonTitles: "发送") alertView.show() } func tapAvatarView(message: JCMessageType) { toolbar.resignFirstResponder() if message.options.alignment == .right { navigationController?.pushViewController(SPersonViewController(), animated: true) } else { guard let uid = message.sender?.username else { return } goToPerson(uid: uid) } } func longTapAvatarView(message: JCMessageType) { if !isGroup || message.options.alignment == .right { return } toolbar.becomeFirstResponder() if let user = message.sender { toolbar.text.append("@") handleAt(toolbar, NSMakeRange(toolbar.text.length - 1, 0), user, false, user.displayName().length) } } func tapUnreadTips(message: JCMessageType) { let vc = UnreadListViewController() let msg = conversation.message(withMessageId: message.msgId) vc.message = msg navigationController?.pushViewController(vc, animated: true) } private func goToPerson(uid: String) { let storyBoard = UIStoryboard(name: "contacts", bundle: nil) guard let personVC = storyBoard.instantiateViewController(withIdentifier: "ContactPersonInfoV2") as? ContactPersonInfoV2ViewController else { return } let person = PersonV2() person.id = uid personVC.person = person navigationController?.pushViewController(personVC, animated: true) } } extension JCChatViewController: JCChatViewDelegate { func refershChatView( chatView: JCChatView) { messagePage += 1 _loadMessage(messagePage) chatView.stopRefresh() } func deleteMessage(message: JCMessageType) { conversation.deleteMessage(withMessageId: message.msgId) if let index = messages.index(message) { jMessageCount -= 1 messages.remove(at: index) if let message = messages.last { if message.content is JCMessageTimeLineContent { messages.removeLast() chatView.remove(at: messages.count) } } } } func forwardMessage(message: JCMessageType) { if let message = conversation.message(withMessageId: message.msgId) { let vc = JCForwardViewController() vc.message = message let nav = JCNavigationController(rootViewController: vc) self.present(nav, animated: true, completion: { self.toolbar.isHidden = true }) } } func withdrawMessage(message: JCMessageType) { guard let message = conversation.message(withMessageId: message.msgId) else { return } JMSGMessage.retractMessage(message, completionHandler: { (result, error) in if error == nil { if let index = self.messages.index(message) { let msg = self._parseMessage(self.conversation.message(withMessageId: message.msgId)!, false) self.messages[index] = msg self.chatView.update(msg, at: index) } } else { MBProgressHUD_JChat.show(text: "发送时间过长,不能撤回", view: self.view) } }) } func indexPathsForVisibleItems(chatView: JCChatView, items: [IndexPath]) { for item in items { if item.row <= minIndex { var msgs: [JCMessage] = [] for index in item.row...minIndex { msgs.append(messages[index]) } updateUnread(msgs) minIndex = item.row } } } fileprivate func updateUnread(_ messages: [JCMessage]) { for message in messages { if message.options.alignment != .left { continue } if let msg = conversation.message(withMessageId: message.msgId) { if msg.isHaveRead { continue } msg.setMessageHaveRead({ _,_ in }) } } } } extension JCChatViewController: UIAlertViewDelegate { func alertView(_ alertView: UIAlertView, clickedButtonAt buttonIndex: Int) { if alertView.tag == 10001 { if buttonIndex == 1 { JCAppManager.openAppSetter() } return } switch buttonIndex { case 1: if let index = messages.index(currentMessage) { messages.remove(at: index) chatView.remove(at: index) let msg = conversation.message(withMessageId: currentMessage.msgId) currentMessage.options.state = .sending if let msg = msg { if let content = currentMessage.content as? JCMessageImageContent, let imageContent = msg.content as? JMSGImageContent { imageContent.uploadHandler = { (percent:Float, msgId:(String?)) -> Void in content.upload?(percent) } } } messages.append(currentMessage as! JCMessage) chatView.append(currentMessage) conversation.send(msg!, optionalContent: JMSGOptionalContent.ex.default) chatView.scrollToLast(animated: true) } default: break } } } // MARK: - SAIInputBarDelegate & SAIInputBarDisplayable extension JCChatViewController: SAIInputBarDelegate, SAIInputBarDisplayable { open override var inputAccessoryView: UIView? { return toolbar } open var scrollView: SAIInputBarScrollViewType { return chatView } open override var canBecomeFirstResponder: Bool { return true } open func inputView(with item: SAIInputItem) -> UIView? { if let view = inputViews[item.identifier] { return view } switch item.identifier { case "kb:emoticon": let view = JCEmoticonInputView() view.delegate = self view.dataSource = self inputViews[item.identifier] = view return view case "kb:toolbox": let view = SAIToolboxInputView() view.delegate = self view.dataSource = self inputViews[item.identifier] = view return view default: return nil } } open func inputViewContentSize(_ inputView: UIView) -> CGSize { return CGSize(width: view.frame.width, height: 216) } func inputBar(_ inputBar: SAIInputBar, shouldDeselectFor item: SAIInputItem) -> Bool { return true } open func inputBar(_ inputBar: SAIInputBar, shouldSelectFor item: SAIInputItem) -> Bool { if item.identifier == "kb:audio" { return true } guard let _ = inputView(with: item) else { return false } return true } open func inputBar(_ inputBar: SAIInputBar, didSelectFor item: SAIInputItem) { inputItem = item if item.identifier == "kb:audio" { inputBar.deselectBarAllItem() return } if let kb = inputView(with: item) { inputBar.setInputMode(.selecting(kb), animated: true) } } open func inputBar(didChangeMode inputBar: SAIInputBar) { if inputItem?.identifier == "kb:audio" { return } if let item = inputItem, !inputBar.inputMode.isSelecting { inputBar.deselectBarItem(item, animated: true) } } open func inputBar(didChangeText inputBar: SAIInputBar) { _emoticonSendBtn.isEnabled = inputBar.attributedText.length != 0 } public func inputBar(shouldReturn inputBar: SAIInputBar) -> Bool { if inputBar.attributedText.length == 0 { return false } send(forText: inputBar.attributedText) inputBar.attributedText = nil return false } func inputBar(_ inputBar: SAIInputBar, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { let currentIndex = range.location if !isGroup { return true } if string == "@" { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { let vc = JCRemindListViewController() vc.finish = { (user, isAtAll, length) in self.handleAt(inputBar, range, user, isAtAll, length) } vc.group = self.conversation.target as? JMSGGroup let nav = JCNavigationController(rootViewController: vc) self.present(nav, animated: true, completion: {}) } } else { return updateRemids(inputBar, string, range, currentIndex) } return true } func handleAt(_ inputBar: SAIInputBar, _ range: NSRange, _ user: JMSGUser?, _ isAtAll: Bool, _ length: Int) { let text = inputBar.text! let currentIndex = range.location var displayName = "所有成员" if let user = user { displayName = user.displayName() } let remind = JCRemind(user, currentIndex, currentIndex + 2 + displayName.length, displayName.length + 2, isAtAll) if text.length == currentIndex + 1 { inputBar.text = text + displayName + " " } else { let index1 = text.index(text.endIndex, offsetBy: currentIndex - text.length + 1) let prefix = text.substring(with: (text.startIndex.. Bool in return r1.startIndex < r2.startIndex }) } func updateRemids(_ inputBar: SAIInputBar, _ string: String, _ range: NSRange, _ currentIndex: Int) -> Bool { for index in 0.. startIndex && currentIndex < endIndex { // Delete Content if string.length == 0 { for subIndex in (index + 1).. Bool { guard let view = touch.view else { return true } if view.isKind(of: JCMessageTextContentView.self) { return false } return true } } extension JCChatViewController: UIDocumentInteractionControllerDelegate { func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController { return self } func documentInteractionControllerViewForPreview(_ controller: UIDocumentInteractionController) -> UIView? { return view } func documentInteractionControllerRectForPreview(_ controller: UIDocumentInteractionController) -> CGRect { return view.frame } }