| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278 |
- //
- // 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..<messages.count {
- let message = messages[index]
- let msg = _parseMessage(message)
- msgs.insert(msg, at: 0)
- if isNeedInsertTimeLine(message.timestamp.intValue) || index == messages.count - 1 {
- let timeContent = JCMessageTimeLineContent(date: Date(timeIntervalSince1970: TimeInterval(message.timestamp.intValue / 1000)))
- let m = JCMessage(content: timeContent)
- m.options.showsTips = false
- msgs.insert(m, at: 0)
- }
- }
- if page != 0 {
- minIndex = minIndex + msgs.count
- chatView.insert(contentsOf: msgs, at: 0)
- } else {
- minIndex = msgs.count - 1
- chatView.append(contentsOf: msgs)
- }
- self.messages.insert(contentsOf: msgs, at: 0)
- }
-
- private func isNeedInsertTimeLine(_ time: Int) -> 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..<index1))
- let index2 = text.index(text.startIndex, offsetBy: currentIndex + 1)
- let suffix = text.substring(with: (index2..<text.endIndex))
- inputBar.text = prefix + displayName + " " + suffix
- let _ = self.updateRemids(inputBar, "@" + displayName + " ", range, currentIndex)
- }
- self.reminds.append(remind)
- self.reminds.sort(by: { (r1, r2) -> Bool in
- return r1.startIndex < r2.startIndex
- })
- }
-
- func updateRemids(_ inputBar: SAIInputBar, _ string: String, _ range: NSRange, _ currentIndex: Int) -> Bool {
- for index in 0..<reminds.count {
- let remind = reminds[index]
- let length = remind.length
- let startIndex = remind.startIndex
- let endIndex = remind.endIndex
- // Delete
- if currentIndex == endIndex - 1 && string.length == 0 {
- for _ in 0..<length {
- inputBar.deleteBackward()
- }
- // Move Other Index
- for subIndex in (index + 1)..<reminds.count {
- let subTemp = reminds[subIndex]
- subTemp.startIndex -= length
- subTemp.endIndex -= length
- }
- reminds.remove(at: index)
- return false;
- } else if currentIndex > startIndex && currentIndex < endIndex {
- // Delete Content
- if string.length == 0 {
- for subIndex in (index + 1)..<reminds.count {
- let subTemp = reminds[subIndex]
- subTemp.startIndex -= 1
- subTemp.endIndex -= 1
- }
- reminds.remove(at: index)
- return true
- }
- // Add Content
- else {
- for subIndex in (index + 1)..<reminds.count {
- let subTemp = reminds[subIndex]
- subTemp.startIndex += string.length
- subTemp.endIndex += string.length
- }
- reminds.remove(at: index)
- return true
- }
- }
- }
- for index in 0..<reminds.count {
- let tempDic = reminds[index]
- let startIndex = tempDic.startIndex
- if currentIndex <= startIndex {
- if string.characters.count == 0 {
- for subIndex in index..<reminds.count {
- let subTemp = reminds[subIndex]
- subTemp.startIndex -= 1
- subTemp.endIndex -= 1
- }
- return true
- } else {
- for subIndex in index..<reminds.count {
- let subTemp = reminds[subIndex]
- subTemp.startIndex += string.length
- subTemp.endIndex += string.length
- }
- return true
- }
- }
- }
- return true
- }
-
- func inputBar(touchDown recordButton: UIButton, inputBar: SAIInputBar) {
- if recordingHub != nil {
- recordingHub.removeFromSuperview()
- }
- recordingHub = JCRecordingView(frame: CGRect.zero)
- recordHelper.updateMeterDelegate = recordingHub
- recordingHub.startRecordingHUDAtView(view)
- recordingHub.frame = CGRect(x: view.centerX - 70, y: view.centerY - 70, width: 136, height: 136)
- recordHelper.startRecordingWithPath(String.getRecorderPath()) {
- }
- }
-
- func inputBar(dragInside recordButton: UIButton, inputBar: SAIInputBar) {
- recordingHub.pauseRecord()
- }
-
- func inputBar(dragOutside recordButton: UIButton, inputBar: SAIInputBar) {
- recordingHub.resaueRecord()
- }
-
- func inputBar(touchUpInside recordButton: UIButton, inputBar: SAIInputBar) {
- if recordHelper.recorder == nil {
- return
- }
- recordHelper.finishRecordingCompletion()
- if (recordHelper.recordDuration! as NSString).floatValue < 1 {
- recordingHub.showErrorTips()
- let time: TimeInterval = 1.5
- let hub = recordingHub
- DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + time) {
- hub?.removeFromSuperview()
- }
- return
- } else {
- recordingHub.removeFromSuperview()
- }
- let data = try! Data(contentsOf: URL(fileURLWithPath: recordHelper.recordPath!))
- send(voiceData: data, duration: Double(recordHelper.recordDuration!)!)
- }
-
- func inputBar(touchUpOutside recordButton: UIButton, inputBar: SAIInputBar) {
- recordHelper.cancelledDeleteWithCompletion()
- recordingHub.removeFromSuperview()
- }
- }
- // MARK: - JCRecordVoiceHelperDelegate
- extension JCChatViewController: JCRecordVoiceHelperDelegate {
- public func beyondLimit(_ time: TimeInterval) {
- recordHelper.finishRecordingCompletion()
- recordingHub.removeFromSuperview()
- let data = try! Data(contentsOf: URL(fileURLWithPath: recordHelper.recordPath!))
- send(voiceData: data, duration: Double(recordHelper.recordDuration!)!)
- }
- }
- extension JCChatViewController: UIGestureRecognizerDelegate {
- public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> 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
- }
- }
|