ソースを参照

Merge branch 'feature/ios_ws_message' into 'develop'

ios消息展现和聊天消息功能

See merge request o2oa/o2oa!713
楼国栋 5 年 前
コミット
265e843b79
25 ファイル変更932 行追加159 行削除
  1. 24 0
      o2ios/O2Platform.xcodeproj/project.pbxproj
  2. 112 26
      o2ios/O2Platform/App/IM-聊天/IMChatViewController.swift
  3. 1 1
      o2ios/O2Platform/App/IM-聊天/IMChatViewController.xib
  4. 121 13
      o2ios/O2Platform/App/IM-聊天/IMConversationListViewController.swift
  5. 64 0
      o2ios/O2Platform/App/IM-聊天/IMInstantMessageViewController.swift
  6. 30 0
      o2ios/O2Platform/App/IM-聊天/IMInstantMessageViewController.xib
  7. 101 20
      o2ios/O2Platform/App/IM-聊天/IMViewModel.swift
  8. 20 0
      o2ios/O2Platform/App/IM-聊天/Model/IMConversationInfo.swift
  9. 1 0
      o2ios/O2Platform/App/IM-聊天/O2IM.swift
  10. 68 0
      o2ios/O2Platform/App/IM-聊天/View/IMChatEmojiBarView.swift
  11. 43 0
      o2ios/O2Platform/App/IM-聊天/View/IMChatEmojiBarView.xib
  12. 25 0
      o2ios/O2Platform/App/IM-聊天/View/IMChatEmojiItemCell.swift
  13. 41 0
      o2ios/O2Platform/App/IM-聊天/View/IMChatEmojiItemCell.xib
  14. 0 4
      o2ios/O2Platform/App/IM-聊天/View/IMChatMessageSendViewCell.swift
  15. 51 2
      o2ios/O2Platform/App/IM-聊天/View/IMChatMessageViewCell.swift
  16. 28 7
      o2ios/O2Platform/App/IM-聊天/View/IMConversationItemCell.swift
  17. 29 5
      o2ios/O2Platform/App/IM-聊天/View/IMConversationItemCell.xib
  18. 83 36
      o2ios/O2Platform/App/O2MainController.swift
  19. 18 16
      o2ios/O2Platform/App/contacts/c/ContactPersonInfoV2ViewController.swift
  20. 21 0
      o2ios/O2Platform/Assets.xcassets/首页/group_default.imageset/Contents.json
  21. BIN
      o2ios/O2Platform/Assets.xcassets/首页/group_default.imageset/group_default@2x.png
  22. 26 24
      o2ios/O2Platform/Extension/Date+Extension.swift
  23. 21 3
      o2ios/O2Platform/Framework/O2API/Communicate/CommunicateAPI.swift
  24. 2 2
      o2ios/O2Platform/Info.plist
  25. 2 0
      o2ios/O2Platform/config/config.swift

+ 24 - 0
o2ios/O2Platform.xcodeproj/project.pbxproj

@@ -126,6 +126,12 @@
 		B1489B52248E192D009EE9FD /* IMChatMessageViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1489B50248E192D009EE9FD /* IMChatMessageViewCell.xib */; };
 		B1489BFE2490BE51009EE9FD /* IMChatMessageSendViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1489BFC2490BE51009EE9FD /* IMChatMessageSendViewCell.swift */; };
 		B1489BFF2490BE51009EE9FD /* IMChatMessageSendViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1489BFD2490BE51009EE9FD /* IMChatMessageSendViewCell.xib */; };
+		B1489C7B2491FEFE009EE9FD /* IMChatEmojiBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1489C7A2491FEFE009EE9FD /* IMChatEmojiBarView.swift */; };
+		B1489CAE2491FF13009EE9FD /* IMChatEmojiBarView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1489CAD2491FF13009EE9FD /* IMChatEmojiBarView.xib */; };
+		B1489CB12492045D009EE9FD /* IMChatEmojiItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1489CAF2492045D009EE9FD /* IMChatEmojiItemCell.swift */; };
+		B1489CB22492045D009EE9FD /* IMChatEmojiItemCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1489CB02492045D009EE9FD /* IMChatEmojiItemCell.xib */; };
+		B1489CB624935104009EE9FD /* IMInstantMessageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1489CB424935104009EE9FD /* IMInstantMessageViewController.swift */; };
+		B1489CB724935104009EE9FD /* IMInstantMessageViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1489CB524935104009EE9FD /* IMInstantMessageViewController.xib */; };
 		B14B339F2356EB1500442968 /* CloudFileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14B339E2356EB1500442968 /* CloudFileViewModel.swift */; };
 		B14E07532301137F00AE85A0 /* ContactPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14E07522301137F00AE85A0 /* ContactPickerViewController.swift */; };
 		B14E07862301418400AE85A0 /* ContactUnitPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14E07852301418400AE85A0 /* ContactUnitPickerViewController.swift */; };
@@ -1447,6 +1453,12 @@
 		B1489B50248E192D009EE9FD /* IMChatMessageViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = IMChatMessageViewCell.xib; sourceTree = "<group>"; };
 		B1489BFC2490BE51009EE9FD /* IMChatMessageSendViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IMChatMessageSendViewCell.swift; sourceTree = "<group>"; };
 		B1489BFD2490BE51009EE9FD /* IMChatMessageSendViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = IMChatMessageSendViewCell.xib; sourceTree = "<group>"; };
+		B1489C7A2491FEFE009EE9FD /* IMChatEmojiBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IMChatEmojiBarView.swift; sourceTree = "<group>"; };
+		B1489CAD2491FF13009EE9FD /* IMChatEmojiBarView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = IMChatEmojiBarView.xib; sourceTree = "<group>"; };
+		B1489CAF2492045D009EE9FD /* IMChatEmojiItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IMChatEmojiItemCell.swift; sourceTree = "<group>"; };
+		B1489CB02492045D009EE9FD /* IMChatEmojiItemCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = IMChatEmojiItemCell.xib; sourceTree = "<group>"; };
+		B1489CB424935104009EE9FD /* IMInstantMessageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IMInstantMessageViewController.swift; sourceTree = "<group>"; };
+		B1489CB524935104009EE9FD /* IMInstantMessageViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = IMInstantMessageViewController.xib; sourceTree = "<group>"; };
 		B14B339E2356EB1500442968 /* CloudFileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudFileViewModel.swift; sourceTree = "<group>"; };
 		B14E07522301137F00AE85A0 /* ContactPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactPickerViewController.swift; sourceTree = "<group>"; };
 		B14E07852301418400AE85A0 /* ContactUnitPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactUnitPickerViewController.swift; sourceTree = "<group>"; };
@@ -2574,6 +2586,8 @@
 				B1173A692488D4AD005075F0 /* O2IM.swift */,
 				B1489B1A248E0F4D009EE9FD /* IMChatViewController.swift */,
 				B1489B1B248E0F4D009EE9FD /* IMChatViewController.xib */,
+				B1489CB424935104009EE9FD /* IMInstantMessageViewController.swift */,
+				B1489CB524935104009EE9FD /* IMInstantMessageViewController.xib */,
 			);
 			path = "IM-聊天";
 			sourceTree = "<group>";
@@ -2603,6 +2617,10 @@
 				B1489B50248E192D009EE9FD /* IMChatMessageViewCell.xib */,
 				B1489BFC2490BE51009EE9FD /* IMChatMessageSendViewCell.swift */,
 				B1489BFD2490BE51009EE9FD /* IMChatMessageSendViewCell.xib */,
+				B1489C7A2491FEFE009EE9FD /* IMChatEmojiBarView.swift */,
+				B1489CAD2491FF13009EE9FD /* IMChatEmojiBarView.xib */,
+				B1489CAF2492045D009EE9FD /* IMChatEmojiItemCell.swift */,
+				B1489CB02492045D009EE9FD /* IMChatEmojiItemCell.xib */,
 			);
 			path = View;
 			sourceTree = "<group>";
@@ -5574,6 +5592,7 @@
 				E46E6CB51DD41F5D00AB7561 /* jQuery.js in Resources */,
 				E46E6C801DD41F5D00AB7561 /* ZSSh6@2x.png in Resources */,
 				E46E6C971DD41F5D00AB7561 /* ZSSparagraph.png in Resources */,
+				B1489CAE2491FF13009EE9FD /* IMChatEmojiBarView.xib in Resources */,
 				E4B888E01D9D48F1002E1A46 /* apps.storyboard in Resources */,
 				E428AF6120AD4DCE00D964B9 /* OOAttanceCheckInController.xib in Resources */,
 				B165CD5D2242093500373B66 /* SourceSansPro-Regular.ttf in Resources */,
@@ -5595,6 +5614,7 @@
 				B165CD682242093500373B66 /* Montserrat-Regular.ttf in Resources */,
 				E41441DB1DCAC5D700E3DDA3 /* bbs.storyboard in Resources */,
 				E46E6C9B1DD41F5D00AB7561 /* ZSSquicklink.png in Resources */,
+				B1489CB22492045D009EE9FD /* IMChatEmojiItemCell.xib in Resources */,
 				E45755A91E0BA72E00EC44F4 /* qrcode_Scan_weixin_Line@2x.png in Resources */,
 				E4B697B920764A2D0062F6E8 /* buildlist.json in Resources */,
 				E4C24B4320844F3C00E426B0 /* defaultTheme.bundle in Resources */,
@@ -5629,6 +5649,7 @@
 				E46E6C6F1DD41F5D00AB7561 /* ZSSclearstyle.png in Resources */,
 				E46E6CB11DD41F5D00AB7561 /* ZSSunorderedlist.png in Resources */,
 				E40E246D20B68AF6009F8BE7 /* OOAttandanceTotalHeaderView.xib in Resources */,
+				B1489CB724935104009EE9FD /* IMInstantMessageViewController.xib in Resources */,
 				E4C24C1720844F5200E426B0 /* yh_image_no_picked@3x.png in Resources */,
 				E46E6C7E1DD41F5D00AB7561 /* ZSSh5@2x.png in Resources */,
 				E46E6C871DD41F5D00AB7561 /* ZSSindent.png in Resources */,
@@ -5974,6 +5995,7 @@
 				E4CB27721E78D5B1004A7ACB /* UINavigationBar+Flat.swift in Sources */,
 				E45DA90C1DAF76C800E0735D /* SCommonViewController.swift in Sources */,
 				B108F401229E34D400778050 /* LBXScanWrapper.swift in Sources */,
+				B1489CB624935104009EE9FD /* IMInstantMessageViewController.swift in Sources */,
 				E40C41F21E83715C00568805 /* ZLUISearchBar.swift in Sources */,
 				E4B888791D9D48F1002E1A46 /* Person.swift in Sources */,
 				E4C24BA820844F3C00E426B0 /* JCChatViewController.swift in Sources */,
@@ -6066,6 +6088,7 @@
 				B1FBA01A230533FA00A90722 /* PersonPickerTableViewCell.swift in Sources */,
 				E4B6981A2079A8BB0062F6E8 /* OOBindNodeViewController.swift in Sources */,
 				E4F4544F20902263002FBC32 /* OOConfigInfoModels.swift in Sources */,
+				B1489CB12492045D009EE9FD /* IMChatEmojiItemCell.swift in Sources */,
 				E4B2321A20B3E9440082F30A /* OOAttanceCheckinPromptView.swift in Sources */,
 				E4B69810207652960062F6E8 /* OOTaskModels.swift in Sources */,
 				E4B888761D9D48F1002E1A46 /* DepartmentDuty.swift in Sources */,
@@ -6080,6 +6103,7 @@
 				E4C24BD120844F3C00E426B0 /* JCNoteNameViewController.swift in Sources */,
 				B1B110E2223622C400775BEF /* O2BioLocalAuth.swift in Sources */,
 				E4184B231DB4B29B00FCC907 /* SPasswordChangeViewController.swift in Sources */,
+				B1489C7B2491FEFE009EE9FD /* IMChatEmojiBarView.swift in Sources */,
 				E4B7816C1DF8F2B2007B58A9 /* CMSData.swift in Sources */,
 				B165CD592242093500373B66 /* PresentrController.swift in Sources */,
 				E4B7816D1DF8F2B2007B58A9 /* CMSWrapOutCategoryList.swift in Sources */,

+ 112 - 26
o2ios/O2Platform/App/IM-聊天/IMChatViewController.swift

@@ -21,6 +21,14 @@ class IMChatViewController: UIViewController {
     @IBOutlet weak var bottomBarHeightConstraint: NSLayoutConstraint!
     //底部工具栏
     @IBOutlet weak var bottomBar: UIView!
+    
+    private let emojiBarHeight = 256
+    //表情窗口
+    private lazy var emojiBar: IMChatEmojiBarView = {
+       let view = Bundle.main.loadNibNamed("IMChatEmojiBarView", owner: self, options: nil)?.first as! IMChatEmojiBarView
+        view.frame = CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: emojiBarHeight.toCGFloat)
+       return view
+    }()
 
     private lazy var viewModel: IMViewModel = {
         return IMViewModel()
@@ -53,19 +61,44 @@ class IMChatViewController: UIViewController {
         self.messageInputView.backgroundColor = base_gray_color
 
         //标题
-        if let c = self.conversation {
-            var person = ""
-            c.personList?.forEach({ (p) in
-                if  p != O2AuthSDK.shared.myInfo()?.distinguishedName {
-                    person = p
+        if self.conversation?.type == o2_im_conversation_type_single {
+            if let c = self.conversation {
+                var person = ""
+                c.personList?.forEach({ (p) in
+                    if  p != O2AuthSDK.shared.myInfo()?.distinguishedName {
+                        person = p
+                    }
+                })
+                if !person.isEmpty {
+                    self.title = person.split("@").first ?? ""
                 }
-            })
-            if !person.isEmpty {
-                self.title = person.split("@").first ?? ""
             }
+        }else {
+            self.title = self.conversation?.title
         }
         //获取聊天数据
         self.loadMsgList(page: page)
+        //阅读
+        self.viewModel.readConversation(conversationId: self.conversation?.id)
+    }
+    
+    override func viewWillAppear(_ animated: Bool) {
+         NotificationCenter.default.addObserver(self, selector: #selector(receiveMessageFromWs(notice:)), name: OONotification.websocket.notificationName, object: nil)
+    }
+    override func viewWillDisappear(_ animated: Bool) {
+        NotificationCenter.default.removeObserver(self)
+    }
+    
+    
+    @objc private func receiveMessageFromWs(notice: Notification) {
+        DDLogDebug("接收到websocket im 消息")
+        if let message = notice.object as? IMMessageInfo {
+            if message.conversationId == self.conversation?.id {
+                self.chatMessageList.append(message)
+                self.scrollMessageToBottom()
+                self.viewModel.readConversation(conversationId: self.conversation?.id)
+            }
+        }
     }
 
     //获取消息
@@ -73,17 +106,62 @@ class IMChatViewController: UIViewController {
         if let c = self.conversation, let id = c.id {
             self.viewModel.myMsgPageList(page: page, conversationId: id).then { (list) in
                 self.chatMessageList = list
-                DispatchQueue.main.async {
-                    self.tableView.reloadData()
-                    if self.chatMessageList.count > 0 {
-                        self.tableView.scrollToRow(at: IndexPath(row: self.chatMessageList.count-1, section: 0), at: .bottom, animated: true)
-                    }
-                }
+                self.scrollMessageToBottom()
             }
         } else {
             self.showError(title: "参数错误!!!")
         }
     }
+    //刷新tableview 滚动到底部
+    private func scrollMessageToBottom() {
+        DispatchQueue.main.async {
+            self.tableView.reloadData()
+            if self.chatMessageList.count > 0 {
+                self.tableView.scrollToRow(at: IndexPath(row: self.chatMessageList.count-1, section: 0), at: .bottom, animated: true)
+            }
+        }
+    }
+    
+    //发送文本消息
+    private func sendTextMessage() {
+        guard let msg = self.messageInputView.text else {
+            return
+        }
+        self.messageInputView.text = ""
+        let body = IMMessageBodyInfo()
+        body.type = o2_im_msg_type_text
+        body.body = msg
+        sendMessage(body: body)
+    }
+    //发送表情消息
+    private func sendEmojiMessage(emoji: String) {
+        let body = IMMessageBodyInfo()
+        body.type = o2_im_msg_type_emoji
+        body.body = emoji
+        sendMessage(body: body)
+    }
+    
+    //发送消息到服务器
+    private func sendMessage(body: IMMessageBodyInfo) {
+        let message = IMMessageInfo()
+        message.body = body.toJSONString()
+        message.id = UUID().uuidString
+        message.conversationId = self.conversation?.id
+        message.createPerson = O2AuthSDK.shared.myInfo()?.distinguishedName
+        message.createTime = Date().formatterDate(formatter: "yyyy-MM-dd HH:mm:ss")
+        //添加到界面
+        self.chatMessageList.append(message)
+        self.scrollMessageToBottom()
+        //发送消息到服务器
+        self.viewModel.sendMsg(msg: message)
+            .then { (result)  in
+                DDLogDebug("发送消息成功 \(result)")
+                self.viewModel.readConversation(conversationId: self.conversation?.id)
+        }.catch { (error) in
+            DDLogError(error.localizedDescription)
+            self.showError(title: "发送消息失败!")
+        }
+    }
 
 
     // MARK: - IBAction
@@ -92,10 +170,17 @@ class IMChatViewController: UIViewController {
         self.isShowEmoji.toggle()
         self.view.endEditing(true)
         if self.isShowEmoji {
-            self.bottomBarHeightConstraint.constant = self.bottomBarHeight.toCGFloat + 128
+            self.bottomBarHeightConstraint.constant = self.bottomBarHeight.toCGFloat + self.emojiBarHeight.toCGFloat
+            self.emojiBar.delegate = self
+            self.emojiBar.translatesAutoresizingMaskIntoConstraints = false
+            self.bottomBar.addSubview(self.emojiBar)
+            let top = NSLayoutConstraint(item: self.emojiBar, attribute: .top, relatedBy: .equal, toItem: self.emojiBar.superview!, attribute: .top, multiplier: 1, constant: CGFloat(self.bottomBarHeight))
+            let width = NSLayoutConstraint(item: self.emojiBar, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: SCREEN_WIDTH)
+            let height = NSLayoutConstraint(item: self.emojiBar, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: self.emojiBarHeight.toCGFloat)
+            NSLayoutConstraint.activate([top, width, height])
         } else {
             self.bottomBarHeightConstraint.constant = self.bottomBarHeight.toCGFloat
-
+            self.emojiBar.removeFromSuperview()
         }
         self.view.layoutIfNeeded()
     }
@@ -103,6 +188,15 @@ class IMChatViewController: UIViewController {
 
 
 }
+
+// MARK: - 表情点击 delegate
+extension IMChatViewController: IMChatEmojiBarClickDelegate {
+    func clickEmoji(emoji: String) {
+        DDLogDebug("发送表情消息 \(emoji)")
+        self.sendEmojiMessage(emoji: emoji)
+    }
+}
+
 // MARK: - tableview delegate
 extension IMChatViewController: UITableViewDelegate, UITableViewDataSource {
 
@@ -127,17 +221,8 @@ extension IMChatViewController: UITableViewDelegate, UITableViewDataSource {
     }
     
     func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-        
         tableView.deselectRow(at: indexPath, animated: false)
     }
-//    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
-//        guard let c = cell as? IMChatMessageViewCell else {
-//            return
-//        }
-//        //todo
-//        c.setContent(item: self.chatMessageList[indexPath.row])
-//    }
-
 
 }
 
@@ -151,12 +236,13 @@ extension IMChatViewController: UITextFieldDelegate {
 
     private func closeEmoji() {
         self.isShowEmoji = false
-        self.bottomBarHeightConstraint.constant = 64
+        self.bottomBarHeightConstraint.constant = self.bottomBarHeight.toCGFloat
         self.view.layoutIfNeeded()
     }
 
     func textFieldShouldReturn(_ textField: UITextField) -> Bool {
         DDLogDebug("回车。。。。")
+        self.sendTextMessage()
         return true
     }
 }

+ 1 - 1
o2ios/O2Platform/App/IM-聊天/IMChatViewController.xib

@@ -24,7 +24,7 @@
             <subviews>
                 <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="ZIb-3p-hnq">
                     <rect key="frame" x="0.0" y="44" width="414" height="754"/>
-                    <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                    <color key="backgroundColor" red="0.95294117649999999" green="0.95294117649999999" blue="0.95294117649999999" alpha="1" colorSpace="calibratedRGB"/>
                     <color key="sectionIndexBackgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                 </tableView>
                 <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="sDg-us-Ed9">

+ 121 - 13
o2ios/O2Platform/App/IM-聊天/IMConversationListViewController.swift

@@ -12,7 +12,7 @@ import CocoaLumberjack
 class IMConversationListViewController: UIViewController {
 
     fileprivate lazy var tableview: UITableView = {
-        var tableview = UITableView(frame: CGRect(x: 0, y: 0, width: self.view.width, height: self.view.height))
+        var tableview = UITableView(frame: CGRect(x: 0, y: 0, width: self.view.width, height: self.view.height - TAB_BAR_HEIGHT))
         tableview.delegate = self
         tableview.dataSource = self
         tableview.backgroundColor = UIColor(netHex: 0xe8edf3)
@@ -39,24 +39,43 @@ class IMConversationListViewController: UIViewController {
     }()
 
     private var conversationList: [IMConversationInfo] = []
+    private var instantMsgList: [InstantMessage] = []
 
     override func viewDidLoad() {
         super.viewDidLoad()
+        
+        self.navigationItem.rightBarButtonItems = [UIBarButtonItem(image: UIImage(named: "add"), style: .plain, target: self, action: #selector(addConversation))]
+        
         view.addSubview(tableview)
         view.addSubview(emptyView)
-        
+
         NotificationCenter.default.addObserver(self, selector: #selector(receiveMessageFromWs(notice:)), name: OONotification.websocket.notificationName, object: nil)
-        
-         
+
+
     }
-    
+
     override func viewWillAppear(_ animated: Bool) {
-        getConversationList()
+        getInstantMsgList()
+    }
+    
+    func getInstantMsgList() {
+        viewModel.getInstantMsgList().then { (list) in
+            self.instantMsgList = list
+            self.getConversationList()
+        }
     }
 
     func getConversationList() {
         viewModel.myConversationList().then { (list) in
             self.conversationList = list
+            var n = 0
+            if !self.conversationList.isEmpty {
+                for item in self.conversationList {
+                    if let number = item.unreadNumber {
+                        n += number
+                    }
+                }
+            }
             DispatchQueue.main.async {
                 if self.conversationList.count > 0 {
                     self.emptyView.isHidden = true
@@ -64,6 +83,7 @@ class IMConversationListViewController: UIViewController {
                     self.emptyView.isHidden = false
                 }
                 self.tableview.reloadData()
+                self.refreshRedPoint(number: n)
             }
 
         }.catch { (err) in
@@ -79,7 +99,7 @@ class IMConversationListViewController: UIViewController {
                 return info.id == message.conversationId
             }) {
                 DDLogDebug("有对应的会话 刷新列表")
-                var newList: [IMConversationInfo]  = []
+                var newList: [IMConversationInfo] = []
                 self.conversationList.forEach { (info) in
                     if message.conversationId != nil && info.id == message.conversationId {
                         info.lastMessage = message
@@ -91,22 +111,89 @@ class IMConversationListViewController: UIViewController {
                 DispatchQueue.main.async {
                     self.tableview.reloadData()
                 }
-            }else {
+            } else {
                 DDLogDebug("没有对应的会话 重新获取会话列表")
-                self.getConversationList()
+                self.getInstantMsgList()
             }
-        }else {
+        } else {
             DDLogError("不正确的消息类型。。。")
         }
     }
+
+
+    private func refreshRedPoint(number: Int) {
+        if number > 0 && number < 100 {
+            self.navigationController?.tabBarItem.badgeValue = "\(number)"
+        } else if number >= 100 {
+            self.navigationController?.tabBarItem.badgeValue = "99.."
+        }else {
+            self.navigationController?.tabBarItem.badgeValue = nil
+        }
+    }
     
     
+    @objc private func addConversation() {
+        self.showSheetAction(title: nil, message: nil, actions: [
+            UIAlertAction(title: "创建单聊", style: .default, handler: { (action) in
+                self.createSingleConversation()
+            }),
+            UIAlertAction(title: "创建群聊", style: .default, handler: { (action) in
+                self.createGroupConversation()
+            })
+        ])
+    }
+
+    private func createSingleConversation() {
+        self.showContactPicker(modes: [.person], callback: { (result) in
+            if let users = result.users, users.count > 0 {
+                self.viewModel.createConversation(type: o2_im_conversation_type_single, users: [users[0].distinguishedName!]).then { (con) in
+                    self.createConversationSuccess(conv: con)
+                }.catch { (err) in
+                    self.showError(title: "创建单聊失败, \(err.localizedDescription)")
+                }
+                
+            }
+        }, multiple: false)
+    }
+    
+    private func createGroupConversation() {
+        self.showContactPicker(modes: [.person], callback: { (result) in
+            if let users = result.users, users.count > 0 {
+                let array = users.map { (item) -> String in
+                    item.distinguishedName!
+                }
+                self.viewModel.createConversation(type: o2_im_conversation_type_group, users: array).then { (conv) in
+                    self.createConversationSuccess(conv: conv)
+                }.catch { (err) in
+                    self.showError(title: "创建群聊失败, \(err.localizedDescription)")
+                }
+            }
+        })
+    }
+    
+    //创建会话成功 打开聊天界面
+    private func createConversationSuccess(conv: IMConversationInfo) {
+        if !self.conversationList.contains(where: { (info) -> Bool in
+            return info.id == conv.id
+        }) {
+            self.conversationList.append(conv)
+            DispatchQueue.main.async {
+                self.tableview.reloadData()
+            }
+        }
+        let chatView = IMChatViewController()
+        chatView.conversation = conv
+        self.navigationController?.pushViewController(chatView, animated: true)
+    }
 
 }
 
 // MARK: - tableview delegate
 extension IMConversationListViewController: UITableViewDelegate, UITableViewDataSource {
     func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        if self.instantMsgList.count > 0 {
+            return self.conversationList.count + 1
+        }
         return self.conversationList.count
     }
 
@@ -117,17 +204,38 @@ extension IMConversationListViewController: UITableViewDelegate, UITableViewData
         guard let c = cell as? IMConversationItemCell else {
             return
         }
-        c.bindConversation(conversation: self.conversationList[indexPath.row])
+        if self.instantMsgList.count > 0 {
+            if indexPath.row == 0 {
+                c.setInstantContent(item: self.instantMsgList.last!)
+            }else {
+                c.bindConversation(conversation: self.conversationList[indexPath.row - 1])
+            }
+        }else {
+            c.bindConversation(conversation: self.conversationList[indexPath.row])
+        }
     }
     func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
         return 64
     }
     func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
         DDLogDebug("点击了 row \(indexPath.row)")
+        if self.instantMsgList.count > 0 {
+            if indexPath.row == 0 {
+                let instantView = IMInstantMessageViewController()
+                instantView.instantMsgList = self.instantMsgList
+                self.navigationController?.pushViewController(instantView, animated: true)
+            }else {
+                gotoChatView(row: indexPath.row-1)
+            }
+        }else {
+            gotoChatView(row: indexPath.row)
+        }
+    }
+    
+    private func gotoChatView(row: Int) {
         let chatView = IMChatViewController()
-        chatView.conversation = self.conversationList[indexPath.row]
+        chatView.conversation = self.conversationList[row]
         self.navigationController?.pushViewController(chatView, animated: true)
     }
-    //todo can edit
 
 }

+ 64 - 0
o2ios/O2Platform/App/IM-聊天/IMInstantMessageViewController.swift

@@ -0,0 +1,64 @@
+//
+//  IMInstantMessageViewController.swift
+//  O2Platform
+//
+//  Created by FancyLou on 2020/6/12.
+//  Copyright © 2020 zoneland. All rights reserved.
+//
+
+import UIKit
+
+class IMInstantMessageViewController: UITableViewController {
+        
+    private lazy var viewModel: IMViewModel = {
+           return IMViewModel()
+       }()
+    
+    var instantMsgList: [InstantMessage] = []
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        self.title = "通知消息"
+        self.tableView.register(UINib(nibName: "IMChatMessageViewCell", bundle: nil), forCellReuseIdentifier: "IMChatMessageViewCell")
+        self.tableView.separatorStyle = .none
+        self.tableView.rowHeight = UITableView.automaticDimension
+        self.tableView.estimatedRowHeight = 144
+        self.tableView.backgroundColor = UIColor(hex: "#f3f3f3")
+       
+    }
+    
+    override func viewDidAppear(_ animated: Bool) {
+        self.scrollMessageToBottom()
+    }
+    
+    //刷新tableview 滚动到底部
+    private func scrollMessageToBottom() {
+        DispatchQueue.main.async {
+            if self.instantMsgList.count > 0 {
+                self.tableView.scrollToRow(at: IndexPath(row: self.instantMsgList.count-1, section: 0), at: .bottom, animated: true)
+            }
+        }
+    }
+
+    // MARK: - Table view data source
+
+    override func numberOfSections(in tableView: UITableView) -> Int {
+        return 1
+    }
+
+    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return self.instantMsgList.count
+    }
+
+    
+    
+    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        if let cell = tableView.dequeueReusableCell(withIdentifier: "IMChatMessageViewCell", for: indexPath) as? IMChatMessageViewCell {
+            cell.setInstantContent(item: self.instantMsgList[indexPath.row])
+            return cell
+        }
+
+        return UITableViewCell()
+    }
+    
+}

+ 30 - 0
o2ios/O2Platform/App/IM-聊天/IMInstantMessageViewController.xib

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+    <device id="retina6_1" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="IMInstantMessageViewController" customModule="O2Platform" customModuleProvider="target">
+            <connections>
+                <outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
+            </connections>
+        </placeholder>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <tableView opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" bouncesZoom="NO" style="plain" separatorStyle="none" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="i5M-Pr-FkT">
+            <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
+            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+            <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+            <viewLayoutGuide key="safeArea" id="vLr-E1-eTs"/>
+            <color key="sectionIndexBackgroundColor" red="0.95294117649999999" green="0.95294117649999999" blue="0.95294117649999999" alpha="1" colorSpace="calibratedRGB"/>
+            <connections>
+                <outlet property="dataSource" destination="-1" id="Tng-2m-Rnh"/>
+                <outlet property="delegate" destination="-1" id="9aC-8N-iBw"/>
+            </connections>
+            <point key="canvasLocation" x="132" y="123"/>
+        </tableView>
+    </objects>
+</document>

+ 101 - 20
o2ios/O2Platform/App/IM-聊天/IMViewModel.swift

@@ -7,18 +7,75 @@
 //
 
 import Promises
-
+import CocoaLumberjack
 
 class IMViewModel: NSObject {
     override init() {
         super.init()
     }
-    
-    
+
+
     let communicateAPI = OOMoyaProvider<CommunicateAPI>()
 }
 
 extension IMViewModel {
+    
+    //创建会话 @param type: single group
+    func createConversation(type: String, users: [String]) -> Promise<IMConversationInfo> {
+        let conversation = IMConversationInfo()
+        conversation.type = type
+        conversation.personList = users
+        return Promise  { fulfill, reject in
+            self.communicateAPI.request(.createConversation(conversation), completion: { result in
+                let response = OOResult<BaseModelClass<IMConversationInfo>>(result)
+                if response.isResultSuccess() {
+                    if let info = response.model?.data {
+                        fulfill(info)
+                    } else {
+                        reject(OOAppError.apiEmptyResultError)
+                    }
+                } else {
+                    reject(response.error!)
+                }
+            })
+        }
+        
+    }
+    
+    //阅读会话
+    func readConversation(conversationId: String?) {
+        guard let id = conversationId else {
+            DDLogError("阅读会话失败, 传入id为空")
+            return
+        }
+        self.communicateAPI.request(.readConversation(id), completion: {result in
+            let response = OOResult<BaseModelClass<OOCommonIdModel>>(result)
+            if response.isResultSuccess() {
+                DDLogDebug("阅读当前会话成功!")
+            }else {
+                DDLogError("阅读会话失败!")
+            }
+        })
+    }
+
+    //发送消息
+    func sendMsg(msg: IMMessageInfo) -> Promise<Bool> {
+        return Promise { fulfill, reject in
+            self.communicateAPI.request(.sendMsg(msg), completion: { result in
+                    let response = OOResult<BaseModelClass<OOCommonIdModel>>(result)
+                    if response.isResultSuccess() {
+                        if let _ = response.model?.data {
+                            fulfill(true)
+                        } else {
+                            reject(OOAppError.apiEmptyResultError)
+                        }
+                    } else {
+                        reject(response.error!)
+                    }
+                })
+        }
+    }
+
     //查询会话列表
     func myConversationList() -> Promise<[IMConversationInfo]> {
         return Promise { fulfill, reject in
@@ -27,10 +84,10 @@ extension IMViewModel {
                 if response.isResultSuccess() {
                     if let list = response.model?.data {
                         fulfill(list)
-                    }else {
+                    } else {
                         reject(OOAppError.apiEmptyResultError)
                     }
-                }else {
+                } else {
                     reject(response.error!)
                 }
             })
@@ -40,24 +97,48 @@ extension IMViewModel {
     func myMsgPageList(page: Int, conversationId: String) -> Promise<[IMMessageInfo]> {
         return Promise { fulfill, reject in
             self.communicateAPI.request(.msgListByPaging(page, 40, conversationId), completion: { result in
-                let response = OOResult<BaseModelClass<[IMMessageInfo]>>(result)
-                if response.isResultSuccess() {
-                    if let list = response.model?.data {
-                        //列表翻转
-                        let rList = list.sorted { (f, s) -> Bool in
-                            if let ft = f.createTime, let st = s.createTime {
-                                return ft.toDate(formatter: "yyyy-MM-dd HH:mm:ss") < st.toDate(formatter: "yyyy-MM-dd HH:mm:ss")
+                    let response = OOResult<BaseModelClass<[IMMessageInfo]>>(result)
+                    if response.isResultSuccess() {
+                        if let list = response.model?.data {
+                            //列表翻转
+                            let rList = list.sorted { (f, s) -> Bool in
+                                if let ft = f.createTime, let st = s.createTime {
+                                    return ft.toDate(formatter: "yyyy-MM-dd HH:mm:ss") < st.toDate(formatter: "yyyy-MM-dd HH:mm:ss")
+                                }
+                                return true
                             }
-                            return true
+                            fulfill(rList)
+                        } else {
+                            reject(OOAppError.apiEmptyResultError)
                         }
-                        fulfill(rList)
-                    }else {
-                        reject(OOAppError.apiEmptyResultError)
+                    } else {
+                        reject(response.error!)
                     }
-                }else {
-                    reject(response.error!)
-                }
-            })
+                })
+        }
+    }
+    
+    func getInstantMsgList() -> Promise<[InstantMessage]> {
+        return Promise { fulfill, reject in
+            self.communicateAPI.request(.instantMessageList(100), completion: { result in
+                    let response = OOResult<BaseModelClass<[InstantMessage]>>(result)
+                    if response.isResultSuccess() {
+                        if let list = response.model?.data {
+                            //列表翻转
+                            let rList = list.sorted { (f, s) -> Bool in
+                                if let ft = f.createTime, let st = s.createTime {
+                                    return ft.toDate(formatter: "yyyy-MM-dd HH:mm:ss") < st.toDate(formatter: "yyyy-MM-dd HH:mm:ss")
+                                }
+                                return true
+                            }
+                            fulfill(rList)
+                        } else {
+                            reject(OOAppError.apiEmptyResultError)
+                        }
+                    } else {
+                        reject(response.error!)
+                    }
+                })
         }
     }
 }

+ 20 - 0
o2ios/O2Platform/App/IM-聊天/Model/IMConversationInfo.swift

@@ -81,3 +81,23 @@ class WsMessage: NSObject, DataModel {
 
     }
 }
+
+//其他消息
+class InstantMessage: NSObject, DataModel {
+    @objc var id: String?
+    @objc var title: String?
+    @objc var type: String?
+    @objc var body: String?
+    @objc var consumerList: [String]?
+    @objc var person: String?
+    var consumed: Bool?
+    @objc var createTime: String?
+    @objc var updateTime: String?
+    
+    required override init() { }
+
+    func mapping(mapper: HelpingMapper) {
+
+    }
+
+}

+ 1 - 0
o2ios/O2Platform/App/IM-聊天/O2IM.swift

@@ -13,6 +13,7 @@ let o2_im_ws_heartbeat = "heartbeat"
 
 
 let o2_im_conversation_type_single = "single"
+let o2_im_conversation_type_group = "group"
 
 let o2_im_msg_type_text = "text"
 let o2_im_msg_type_emoji = "emoji"

+ 68 - 0
o2ios/O2Platform/App/IM-聊天/View/IMChatEmojiBarView.swift

@@ -0,0 +1,68 @@
+//
+//  IMChatEmojiBarView.swift
+//  O2Platform
+//
+//  Created by FancyLou on 2020/6/11.
+//  Copyright © 2020 zoneland. All rights reserved.
+//
+
+import UIKit
+import CocoaLumberjack
+
+protocol IMChatEmojiBarClickDelegate {
+    func clickEmoji(emoji: String)
+}
+
+class IMChatEmojiBarView: UIView {
+    
+    @IBOutlet weak var collectionView: UICollectionView!
+    
+    private let emojiList: [String] = {
+        var list: [String] = []
+        for i in 1...87 {
+            if i < 10 {
+                list.append("[0\(i)]")
+            }else {
+                list.append("[\(i)]")
+            }
+        }
+        return list
+    }()
+    
+    var delegate: IMChatEmojiBarClickDelegate? = nil
+    
+    override func awakeFromNib() {
+        collectionView.register(UINib(nibName: "IMChatEmojiItemCell", bundle: nil), forCellWithReuseIdentifier: "IMChatEmojiItemCell")
+        collectionView.delegate = self
+        collectionView.dataSource = self
+        DDLogDebug("list size \(emojiList.count)")
+        
+    }
+}
+
+extension IMChatEmojiBarView: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
+    
+    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+        return emojiList.count
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
+        return CGSize(width:SCREEN_WIDTH / 10, height: 42)
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+        if let cell =  collectionView.dequeueReusableCell(withReuseIdentifier: "IMChatEmojiItemCell", for: indexPath) as? IMChatEmojiItemCell {
+            cell.setEmoji(emoji: self.emojiList[indexPath.row])
+            return cell
+        }
+        return UICollectionViewCell()
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+        if delegate != nil {
+            delegate?.clickEmoji(emoji: self.emojiList[indexPath.row])
+        }
+        collectionView.deselectItem(at: indexPath, animated: false)
+    }
+    
+}

+ 43 - 0
o2ios/O2Platform/App/IM-聊天/View/IMChatEmojiBarView.xib

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+    <device id="retina6_1" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="IMChatEmojiBarView" customModule="O2Platform" customModuleProvider="target">
+            <rect key="frame" x="0.0" y="0.0" width="414" height="256"/>
+            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+            <subviews>
+                <collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="none" translatesAutoresizingMaskIntoConstraints="NO" id="Ard-Nz-Tlw">
+                    <rect key="frame" x="0.0" y="0.0" width="414" height="256"/>
+                    <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                    <collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="9JR-h1-hg9">
+                        <size key="itemSize" width="128" height="128"/>
+                        <size key="headerReferenceSize" width="0.0" height="0.0"/>
+                        <size key="footerReferenceSize" width="0.0" height="0.0"/>
+                        <inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
+                    </collectionViewFlowLayout>
+                </collectionView>
+            </subviews>
+            <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+            <constraints>
+                <constraint firstItem="Ard-Nz-Tlw" firstAttribute="trailing" secondItem="vUN-kp-3ea" secondAttribute="trailing" id="CAd-rq-tVD"/>
+                <constraint firstItem="Ard-Nz-Tlw" firstAttribute="bottom" secondItem="vUN-kp-3ea" secondAttribute="bottom" id="CaO-m5-kmB"/>
+                <constraint firstItem="Ard-Nz-Tlw" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="sfG-sX-ZzB"/>
+                <constraint firstItem="Ard-Nz-Tlw" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="wmb-YF-ekq"/>
+            </constraints>
+            <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
+            <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
+            <connections>
+                <outlet property="collectionView" destination="Ard-Nz-Tlw" id="8ST-jl-atl"/>
+            </connections>
+            <point key="canvasLocation" x="100.00000000000001" y="-9.375"/>
+        </view>
+    </objects>
+</document>

+ 25 - 0
o2ios/O2Platform/App/IM-聊天/View/IMChatEmojiItemCell.swift

@@ -0,0 +1,25 @@
+//
+//  IMChatEmojiItemCell.swift
+//  O2Platform
+//
+//  Created by FancyLou on 2020/6/11.
+//  Copyright © 2020 zoneland. All rights reserved.
+//
+
+import UIKit
+
+class IMChatEmojiItemCell: UICollectionViewCell {
+    @IBOutlet weak var emojiImage: UIImageView!
+    
+    override func awakeFromNib() {
+        super.awakeFromNib()
+        // Initialization code
+    }
+    
+    func setEmoji(emoji: String) {
+        let bundle = Bundle().o2EmojiBundle(anyClass: IMChatEmojiItemCell.self)
+        let path = o2ImEmojiPath(emojiBody: emoji)
+        self.emojiImage.image = UIImage(named: path, in: bundle, compatibleWith: nil)
+    }
+
+}

+ 41 - 0
o2ios/O2Platform/App/IM-聊天/View/IMChatEmojiItemCell.xib

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+    <device id="retina6_1" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="gTV-IL-0wX" customClass="IMChatEmojiItemCell" customModule="O2Platform" customModuleProvider="target">
+            <rect key="frame" x="0.0" y="0.0" width="52" height="52"/>
+            <autoresizingMask key="autoresizingMask"/>
+            <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
+                <rect key="frame" x="0.0" y="0.0" width="52" height="52"/>
+                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                <subviews>
+                    <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="JXV-Hl-6Xb">
+                        <rect key="frame" x="10" y="10" width="32" height="32"/>
+                        <constraints>
+                            <constraint firstAttribute="height" constant="32" id="i41-s0-3eR"/>
+                            <constraint firstAttribute="width" constant="32" id="x6M-1W-tyM"/>
+                        </constraints>
+                    </imageView>
+                </subviews>
+            </view>
+            <constraints>
+                <constraint firstItem="JXV-Hl-6Xb" firstAttribute="centerX" secondItem="gTV-IL-0wX" secondAttribute="centerX" id="i60-Uy-DQL"/>
+                <constraint firstItem="JXV-Hl-6Xb" firstAttribute="centerY" secondItem="gTV-IL-0wX" secondAttribute="centerY" id="ssV-XP-TUi"/>
+            </constraints>
+            <viewLayoutGuide key="safeArea" id="ZTg-uK-7eu"/>
+            <size key="customSize" width="52" height="62"/>
+            <connections>
+                <outlet property="emojiImage" destination="JXV-Hl-6Xb" id="b2g-I1-yjf"/>
+            </connections>
+            <point key="canvasLocation" x="146.37681159420291" y="156.02678571428569"/>
+        </collectionViewCell>
+    </objects>
+</document>

+ 0 - 4
o2ios/O2Platform/App/IM-聊天/View/IMChatMessageSendViewCell.swift

@@ -12,9 +12,7 @@ class IMChatMessageSendViewCell: UITableViewCell {
     @IBOutlet weak var timeLabel: UILabel!
     @IBOutlet weak var avatarImageView: UIImageView!
     @IBOutlet weak var nameLabel: UILabel!
-     
     @IBOutlet weak var messageBackgroundView: UIView!
-    
     @IBOutlet weak var messageBgWidth: NSLayoutConstraint!
     @IBOutlet weak var messageBgHeight: NSLayoutConstraint!
     
@@ -102,7 +100,6 @@ class IMChatMessageSendViewCell: UITableViewCell {
         label.translatesAutoresizingMaskIntoConstraints = false
         self.messageBackgroundView.addSubview(label)
         let top = NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: label.superview!, attribute: .top, multiplier: 1, constant: 10)
-//        let bottom = NSLayoutConstraint(item: label.superview! , attribute: .bottom, relatedBy: .equal, toItem: label, attribute: .bottom, multiplier: 1, constant: 10)
         let left = NSLayoutConstraint(item: label, attribute: .leading, relatedBy: .equal, toItem: label.superview!, attribute: .leading, multiplier: 1, constant: 10)
         let right = NSLayoutConstraint(item: label.superview!, attribute: .trailing, relatedBy: .equal, toItem: label, attribute: .trailing, multiplier: 1, constant: 10)
         NSLayoutConstraint.activate([top, left, right])
@@ -115,7 +112,6 @@ class IMChatMessageSendViewCell: UITableViewCell {
         label.numberOfLines = 0
         label.lineBreakMode = .byCharWrapping
         label.preferredMaxLayoutWidth = size.width
-        
         return label
     }
     

+ 51 - 2
o2ios/O2Platform/App/IM-聊天/View/IMChatMessageViewCell.swift

@@ -26,9 +26,59 @@ class IMChatMessageViewCell: UITableViewCell {
 
     override func setSelected(_ selected: Bool, animated: Bool) {
         super.setSelected(selected, animated: animated)
-        // Configure the view for the selected state
+    }
+   
+    //普通通知消息
+    func setInstantContent(item: InstantMessage) {
+        if let time = item.createTime {
+            let date = time.toDate(formatter: "yyyy-MM-dd HH:mm:ss")
+            self.timeLabel.text = date.friendlyTime()
+        }
+        if let msg = item.title {
+            textMsgRender(msg: msg)
+        }
+        if let type = item.type {
+            if type.starts(with: "task_") {
+                self.avatarImage.image = UIImage(named: "icon_daiban")
+                self.titleLabel.text = "待办消息"
+            }else if type.starts(with: "taskCompleted_") {
+                self.avatarImage.image = UIImage(named: "icon_taskcompleted")
+                self.titleLabel.text = "已办消息"
+            }else if type.starts(with: "read_") {
+                self.avatarImage.image = UIImage(named: "icon_read")
+                self.titleLabel.text = "待阅消息"
+            }else if type.starts(with: "readCompleted_") {
+                self.avatarImage.image = UIImage(named: "icon_readcompleted")
+                self.titleLabel.text = "已阅消息"
+            }else if type.starts(with: "review_") || type.starts(with: "work_") || type.starts(with: "process_") {
+                self.avatarImage.image = UIImage(named: "icon_daiban")
+                self.titleLabel.text = "工作消息"
+            }else if type.starts(with: "meeting_") {
+                self.avatarImage.image = UIImage(named: "icon_meeting")
+                self.titleLabel.text = "会议消息"
+            }else if type.starts(with: "attachment_") {
+                self.avatarImage.image = UIImage(named: "icon_yunpan")
+                self.titleLabel.text = "云盘消息"
+            }else if type.starts(with: "calendar_") {
+                self.avatarImage.image = UIImage(named: "icon_calendar")
+                self.titleLabel.text = "日历消息"
+            }else if type.starts(with: "cms_") {
+                self.avatarImage.image = UIImage(named: "icon_cms")
+                self.titleLabel.text = "信息中心消息"
+            }else if type.starts(with: "bbs_") {
+                self.avatarImage.image = UIImage(named: "icon_bbs")
+                self.titleLabel.text = "论坛消息"
+            }else if type.starts(with: "mind_") {
+                self.avatarImage.image = UIImage(named: "icon_mindMap")
+                self.titleLabel.text = "脑图消息"
+            }else {
+                self.avatarImage.image = UIImage(named: "icon_email")
+                self.titleLabel.text = "其他消息"
+            }
+        }
     }
     
+    //聊天消息
     func setContent(item: IMMessageInfo) {
         //time
         if let time = item.createTime {
@@ -102,7 +152,6 @@ class IMChatMessageViewCell: UITableViewCell {
         label.translatesAutoresizingMaskIntoConstraints = false
         self.messageBackgroundView.addSubview(label)
         let top = NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: label.superview!, attribute: .top, multiplier: 1, constant: 10)
-//        let bottom = NSLayoutConstraint(item: label.superview! , attribute: .bottom, relatedBy: .equal, toItem: label, attribute: .bottom, multiplier: 1, constant: 10)
         let left = NSLayoutConstraint(item: label, attribute: .leading, relatedBy: .equal, toItem: label.superview!, attribute: .leading, multiplier: 1, constant: 10)
         let right = NSLayoutConstraint(item: label.superview!, attribute: .trailing, relatedBy: .equal, toItem: label, attribute: .trailing, multiplier: 1, constant: 10)
         NSLayoutConstraint.activate([top, left, right])

+ 28 - 7
o2ios/O2Platform/App/IM-聊天/View/IMConversationItemCell.swift

@@ -16,21 +16,33 @@ class IMConversationItemCell: UITableViewCell {
     @IBOutlet weak var nameLabel: UILabel!
     @IBOutlet weak var timeLabel: UILabel!
     @IBOutlet weak var messageLabel: UILabel!
-    
+    @IBOutlet weak var unreadNumberLabel: UILabel!
     @IBOutlet weak var emojiImg: UIImageView!
     
     
     override func awakeFromNib() {
         super.awakeFromNib()
-        // Initialization code
     }
 
     override func setSelected(_ selected: Bool, animated: Bool) {
         super.setSelected(selected, animated: animated)
-
-        // Configure the view for the selected state
     }
     
+    
+    func setInstantContent(item: InstantMessage) {
+        self.avatarImg.image = UIImage(named: "icon_email")
+        self.nameLabel.text = "通知消息"
+        self.messageLabel.isHidden = false
+        self.messageLabel.text = item.title
+        if let time = item.createTime {
+            let date = time.toDate(formatter: "yyyy-MM-dd HH:mm:ss")
+            self.timeLabel.text = date.friendlyTime()
+        }
+        self.emojiImg.isHidden = true
+        self.unreadNumberLabel.isHidden = true
+    }
+
+    
     func bindConversation(conversation: IMConversationInfo) {
         //avatar name
         if conversation.type == o2_im_conversation_type_single {
@@ -58,6 +70,7 @@ class IMConversationItemCell: UITableViewCell {
             }
         }else {//todo 群组头像 ?
             self.nameLabel.text = conversation.title
+            self.avatarImg.image = UIImage(named: "group_default")
         }
         //time
         if let time = conversation.lastMessage?.createTime {
@@ -75,17 +88,25 @@ class IMConversationItemCell: UITableViewCell {
             }else if body.type == o2_im_msg_type_emoji {
                 self.messageLabel.isHidden = true
                 self.emojiImg.isHidden = false
-//                self.emojiImg.image = UIImage(named: "setting_myCRM")
-                //todo emoji表情导入
                 let bundle = Bundle().o2EmojiBundle(anyClass: IMConversationItemCell.self)
                 let path = o2ImEmojiPath(emojiBody: body.body!)
-                DDLogDebug("path: \(path)")
                 self.emojiImg.image = UIImage(named: path, in: bundle, compatibleWith: nil)
             }else {
                 self.messageLabel.isHidden = true
                 self.emojiImg.isHidden = true
             }
         }
+        //unread number
+        let number = conversation.unreadNumber ?? 0
+        if number > 0 && number < 100 {
+            self.unreadNumberLabel.text = "\(number)"
+            self.unreadNumberLabel.isHidden = false
+        }else if number >= 100 {
+            self.unreadNumberLabel.text = "99.."
+            self.unreadNumberLabel.isHidden = false
+        }else {
+            self.unreadNumberLabel.isHidden = true
+        }
         
     }
     

+ 29 - 5
o2ios/O2Platform/App/IM-聊天/View/IMConversationItemCell.xib

@@ -29,20 +29,23 @@
                             </userDefinedRuntimeAttribute>
                         </userDefinedRuntimeAttributes>
                     </imageView>
-                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="姓名" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bOp-mm-qCN">
-                        <rect key="frame" x="66" y="10" width="154" height="21"/>
+                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="姓名" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bOp-mm-qCN">
+                        <rect key="frame" x="66" y="10" width="153" height="21"/>
                         <fontDescription key="fontDescription" type="system" pointSize="17"/>
                         <nil key="textColor"/>
                         <nil key="highlightedColor"/>
                     </label>
-                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="几分钟前" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="URQ-HD-MDc">
-                        <rect key="frame" x="257" y="10" width="58" height="17"/>
+                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2020-10-23" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="URQ-HD-MDc">
+                        <rect key="frame" x="231" y="10" width="84" height="17"/>
+                        <constraints>
+                            <constraint firstAttribute="width" constant="84" id="jM2-ye-nzP"/>
+                        </constraints>
                         <fontDescription key="fontDescription" type="system" pointSize="14"/>
                         <color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                         <nil key="highlightedColor"/>
                     </label>
                     <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="这里是消息" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="I3j-lM-VtV" customClass="IMCon">
-                        <rect key="frame" x="70" y="37" width="71.5" height="17"/>
+                        <rect key="frame" x="70" y="37" width="245" height="17"/>
                         <fontDescription key="fontDescription" type="system" pointSize="14"/>
                         <color key="textColor" systemColor="systemGrayColor" red="0.5568627451" green="0.5568627451" blue="0.57647058819999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                         <nil key="highlightedColor"/>
@@ -61,19 +64,39 @@
                             <constraint firstAttribute="height" constant="1" id="szG-5G-RRm"/>
                         </constraints>
                     </view>
+                    <label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="99.." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9Ff-Wo-dWM">
+                        <rect key="frame" x="34" y="10" width="20" height="20"/>
+                        <color key="backgroundColor" systemColor="systemRedColor" red="1" green="0.23137254900000001" blue="0.18823529410000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                        <constraints>
+                            <constraint firstAttribute="width" constant="20" id="XU3-CN-tXR"/>
+                            <constraint firstAttribute="height" constant="20" id="Yfh-b5-N7U"/>
+                        </constraints>
+                        <fontDescription key="fontDescription" type="system" weight="thin" pointSize="11"/>
+                        <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                        <nil key="highlightedColor"/>
+                        <userDefinedRuntimeAttributes>
+                            <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                                <real key="value" value="10"/>
+                            </userDefinedRuntimeAttribute>
+                        </userDefinedRuntimeAttributes>
+                    </label>
                 </subviews>
                 <constraints>
                     <constraint firstAttribute="bottom" secondItem="I3j-lM-VtV" secondAttribute="bottom" constant="10" id="0mD-L5-TRo"/>
                     <constraint firstAttribute="trailing" secondItem="ckc-YE-j5q" secondAttribute="trailing" id="2LG-5s-Yui"/>
                     <constraint firstItem="pgV-nP-wwV" firstAttribute="leading" secondItem="i51-Sc-dds" secondAttribute="trailing" constant="16" id="4Ao-s1-kAM"/>
                     <constraint firstItem="I3j-lM-VtV" firstAttribute="leading" secondItem="i51-Sc-dds" secondAttribute="trailing" constant="16" id="BFD-a2-hUs"/>
+                    <constraint firstItem="9Ff-Wo-dWM" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="10" id="Frj-Mc-oGy"/>
                     <constraint firstItem="URQ-HD-MDc" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="10" id="IES-nF-jpE"/>
                     <constraint firstAttribute="trailing" secondItem="URQ-HD-MDc" secondAttribute="trailing" constant="5" id="Jeh-u4-Tnd"/>
+                    <constraint firstAttribute="trailing" secondItem="I3j-lM-VtV" secondAttribute="trailing" constant="5" id="PJD-Jd-y3N"/>
                     <constraint firstItem="bOp-mm-qCN" firstAttribute="leading" secondItem="i51-Sc-dds" secondAttribute="trailing" constant="12" id="RCn-dh-ggD"/>
                     <constraint firstItem="i51-Sc-dds" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="10" id="Z9Q-tH-mWi"/>
                     <constraint firstAttribute="bottom" secondItem="i51-Sc-dds" secondAttribute="bottom" constant="10" id="Zew-6c-hWd"/>
+                    <constraint firstItem="9Ff-Wo-dWM" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="34" id="cM0-LZ-1iA"/>
                     <constraint firstItem="bOp-mm-qCN" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="10" id="iUq-ik-5sX"/>
                     <constraint firstAttribute="bottom" secondItem="ckc-YE-j5q" secondAttribute="bottom" id="nX2-Nw-uiy"/>
+                    <constraint firstItem="URQ-HD-MDc" firstAttribute="leading" secondItem="bOp-mm-qCN" secondAttribute="trailing" constant="12" id="obm-on-urU"/>
                     <constraint firstItem="ckc-YE-j5q" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="66" id="t0M-PG-f03"/>
                     <constraint firstAttribute="bottom" secondItem="pgV-nP-wwV" secondAttribute="bottom" constant="8" id="tXP-Lu-4aH"/>
                     <constraint firstItem="i51-Sc-dds" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="10" id="xep-37-rIl"/>
@@ -86,6 +109,7 @@
                 <outlet property="messageLabel" destination="I3j-lM-VtV" id="6z4-xX-xTv"/>
                 <outlet property="nameLabel" destination="bOp-mm-qCN" id="iqM-q2-qcC"/>
                 <outlet property="timeLabel" destination="URQ-HD-MDc" id="NbV-D2-ATA"/>
+                <outlet property="unreadNumberLabel" destination="9Ff-Wo-dWM" id="z3x-wN-Bfv"/>
             </connections>
             <point key="canvasLocation" x="-65.217391304347828" y="-31.473214285714285"/>
         </tableViewCell>

+ 83 - 36
o2ios/O2Platform/App/O2MainController.swift

@@ -30,6 +30,10 @@ class O2MainController: UITabBarController, UITabBarControllerDelegate {
     private let viewModel: OOLoginViewModel = {
         return OOLoginViewModel()
     }()
+    //获取消息数量
+    private lazy var imViewModel: IMViewModel = {
+        return IMViewModel()
+    }()
 
 
     override func viewDidLoad() {
@@ -42,13 +46,15 @@ class O2MainController: UITabBarController, UITabBarControllerDelegate {
         _initControllers()
         selectedIndex = 2
         currentIndex = 2
-        _loginIM()
+//        _loginIM()
         if O2IsConnect2Collect == false {
             //处理内部直连的时候推送的设备绑定
             O2JPushManager.shared.o2JPushBind()
         }
         //连接websocket
         self._startWebsocket()
+        //读取消息
+        self.getConversationList()
     }
 
     deinit {
@@ -71,29 +77,15 @@ class O2MainController: UITabBarController, UITabBarControllerDelegate {
 
     //MARK: -- delegate
     func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
-//        if currentIndex == 2 && tabBarController.selectedIndex == 2 {
-//            if tabBarController.selectedViewController is ZLNavigationController {
-//                (tabBarController.selectedViewController as! ZLNavigationController).viewControllers.forEach { (vc) in
-//                    if vc is MailViewController {
-//                        DDLogDebug("点击了首页 portal")
-//                        (vc as! MailViewController).loadDetailSubject()
-//                    }
-//                    if vc is MainTaskSecondViewController {
-//                        DDLogDebug("点击了首页index")
-//                    }
-//                }
-//            }
-//        }
         self.currentIndex = tabBarController.selectedIndex
     }
 
     private func _initControllers() {
         //消息
-        let conversationVC = JCConversationListViewController()
-//        let conversationVC = IMConversationListViewController()
+//        let conversationVC = JCConversationListViewController()
+        let conversationVC = IMConversationListViewController()
         conversationVC.title = "消息"
         let messages = ZLNavigationController(rootViewController: conversationVC)
-
         messages.tabBarItem = UITabBarItem(title: "消息", image: UIImage(named: "icon_news_nor"), selectedImage: O2ThemeManager.image(for: "Icon.icon_news_pre"))
 
         //通讯录
@@ -148,28 +140,82 @@ class O2MainController: UITabBarController, UITabBarControllerDelegate {
         }
     }
 
-    private func _loginIM() {
-        viewModel.registerIM().then { (result) in
-            self.viewModel.loginIM().then({ (result) in
-                Log.debug(message: "IM登陆完成")
-            })
-        }.catch { (imError) in
-            let error = imError as! OOLoginError
-            switch error {
-            case .imRegisterFail(let myErr):
-                Log.debug(message: myErr.errorDescription!)
-                self.viewModel.loginIM().then({ (result) in
-                    Log.debug(message: "IM登陆完成")
-                }).catch({ (loginError) in
-                    Log.error(message: "im Login Error \(loginError)")
-                })
-                break
-            default:
-                break
+    
+    // MARK: - IM message
+    func getConversationList() {
+        imViewModel.myConversationList().then { (list) in
+            var n = 0
+            if !list.isEmpty {
+                for item in list {
+                    if let number = item.unreadNumber {
+                        n += number
+                    }
+                }
+            }
+            DispatchQueue.main.async {
+                DDLogDebug("消息数量: \(n)")
+                if n > 0 && n < 100 {
+                    self.showRedPoint(number: "\(n)")
+                } else if n >= 100 {
+                    self.showRedPoint(number: "99..")
+                }
             }
         }
     }
 
+    private func showRedPoint(number: String) {
+        self.viewControllers?.forEach({ (vc) in
+            if let zl = vc as? ZLNavigationController, zl.tabBarItem?.title == "消息" {
+                zl.tabBarItem.badgeValue = number
+            }
+        })
+    }
+    //消息模块未读消息数量加1
+    private func addUnreadNumber() {
+        self.viewControllers?.forEach({ (vc) in
+            if let zl = vc as? ZLNavigationController, zl.tabBarItem?.title == "消息" {
+                if let badge = zl.tabBarItem.badgeValue {
+                    if badge != "99.." {
+                        if let n = Int(string: badge) {
+                            let number = n + 1
+                            if number > 0 && number < 100 {
+                                self.showRedPoint(number: "\(number)")
+                            } else if number >= 100 {
+                                self.showRedPoint(number: "99..")
+                            }
+                        }
+                    }
+                }else {
+                    self.showRedPoint(number: "1")
+                }
+            }
+        })
+    }
+
+//    private func _loginIM() {
+//        viewModel.registerIM().then { (result) in
+//            self.viewModel.loginIM().then({ (result) in
+//                Log.debug(message: "IM登陆完成")
+//            })
+//        }.catch { (imError) in
+//            let error = imError as! OOLoginError
+//            switch error {
+//            case .imRegisterFail(let myErr):
+//                Log.debug(message: myErr.errorDescription!)
+//                self.viewModel.loginIM().then({ (result) in
+//                    Log.debug(message: "IM登陆完成")
+//                }).catch({ (loginError) in
+//                    Log.error(message: "im Login Error \(loginError)")
+//                })
+//                break
+//            default:
+//                break
+//            }
+//        }
+//    }
+
+    
+    // MARK: - app update 
     private func checkAppVersion() {
         O2VersionManager.shared.checkAppUpdate { (info, error) in
             if let iosInfo = info {
@@ -252,9 +298,10 @@ extension O2MainController: WebSocketDelegate {
                                 DDLogDebug("接收到im消息 发送通知。。")
                                 NotificationCenter.post(customeNotification: OONotification.websocket, object: messageInfo.body)
                             }
+                            self.addUnreadNumber()
                         }
                     }
-                } catch {  }
+                } catch { }
             }
             break
         case .connected(let headers):

+ 18 - 16
o2ios/O2Platform/App/contacts/c/ContactPersonInfoV2ViewController.swift

@@ -67,6 +67,10 @@ class ContactPersonInfoV2ViewController: UITableViewController {
             isLoadPerson = false
         }
     }
+    //im 聊天
+    private lazy var viewModel: IMViewModel = {
+        return IMViewModel()
+    }()
     
     override func viewDidLoad() {
         super.viewDidLoad()
@@ -94,28 +98,26 @@ class ContactPersonInfoV2ViewController: UITableViewController {
     }
     
     @objc private func _startChat() {
-        MBProgressHUD_JChat.showMessage(message: "创建中...", toView: view)
         var username = ""
-        if self.person != nil {
-            username = self.person?.id ?? ""
-        }else if self.identity != nil {
-            username = self.identity?.person ?? ""
+        if self.contact != nil {
+            username = self.contact?.distinguishedName ?? ""
+        }else if self.person != nil {
+            username = self.person?.distinguishedName ?? ""
         }
         if username == "" {
+            self.showError(title: "无法创建聊天!")
             return
         }
-        JMSGConversation.createSingleConversation(withUsername: username) { (result, error) in
-            MBProgressHUD_JChat.hide(forView: self.view, animated: true)
-            if error == nil {
-                let conv = result as! JMSGConversation
-                let vc = JCChatViewController(conversation: conv)
-                NotificationCenter.default.post(name: NSNotification.Name(rawValue: kUpdateConversation), object: nil, userInfo: nil)
-                self.navigationController?.pushViewController(vc, animated: true)
-            }else{
-                DDLogError(error.debugDescription)
-                MBProgressHUD_JChat.show(text: "创建会话失败,请重试", view: self.view)
-            }
+        
+        self.viewModel.createConversation(type: o2_im_conversation_type_single, users: [username]).then { (conv) in
+            let chatView = IMChatViewController()
+            chatView.conversation = conv
+            self.navigationController?.pushViewController(chatView, animated: true)
+        }.catch { (err) in
+            self.showError(title: "创建单聊失败, \(err.localizedDescription)")
         }
+        
+        
     }
 
     override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

+ 21 - 0
o2ios/O2Platform/Assets.xcassets/首页/group_default.imageset/Contents.json

@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "group_default@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
o2ios/O2Platform/Assets.xcassets/首页/group_default.imageset/group_default@2x.png


+ 26 - 24
o2ios/O2Platform/Extension/Date+Extension.swift

@@ -276,34 +276,36 @@ extension Date {
     func friendlyTime() -> String {
         var returnTimeString = ""
         let now = Date()
-        if now.haveSameYearMonthDayAndHour(self) {
-            let gap = now.minute - self.minute
-            returnTimeString = "\(gap)分钟前"
-        }else if now.haveSameYearMonthAndDay(self) {
-            let gap = now.hour - self.hour
-            returnTimeString = "\(gap)小时前"
-        }else if now.haveSameYearAndMonth(self) {
-            let gap = now.day - self.day
-            if gap == 1 {
-                returnTimeString = "昨天"
-            }else if gap == 2 {
-                returnTimeString = "前天"
-            }else {
-                returnTimeString = "\(gap)天前"
-            }
-        }else if now.haveSameYear(self) {
-            let days = now.betweenDays(self)
-            if days > 30 {
-                let gap = now.month - self.month
-                if gap < 4 {
-                    returnTimeString = "\(gap)个月前"
+        let millisecond = CLongLong(round(self.timeIntervalSince1970*1000))
+        let nowMillisecond = CLongLong(round(now.timeIntervalSince1970*1000))
+        let lt = millisecond / 86400000
+        let ct = nowMillisecond / 86400000
+        let days = Int(ct - lt);
+        if (days == 0) {
+            let hour = Int((nowMillisecond - millisecond) / 3600000)
+            if (hour == 0) {
+                let minuts = Int((nowMillisecond - millisecond) / 60000)
+                if minuts > 1 {
+                    returnTimeString = "\(minuts)分钟前"
                 }else {
-                    returnTimeString = self.formatterDate(formatter: "yyyy-MM-dd")
+                    returnTimeString = "刚刚"
                 }
             }else {
-               returnTimeString = "\(days)天前"
+                returnTimeString = "\(hour)小时前"
             }
-        }else {
+        }else if (days == 1) {
+            returnTimeString = "昨天";
+        } else if (days == 2) {
+            returnTimeString = "前天 ";
+        } else if (days > 2 && days < 31) {
+            returnTimeString = "\(days)天前";
+        } else if (days >= 31 && days <= 2 * 31) {
+            returnTimeString = "一个月前";
+        } else if (days > 2 * 31 && days <= 3 * 31) {
+            returnTimeString = "2个月前";
+        } else if (days > 3 * 31 && days <= 4 * 31) {
+            returnTimeString = "3个月前";
+        } else {
             returnTimeString = self.formatterDate(formatter: "yyyy-MM-dd")
         }
         return returnTimeString

+ 21 - 3
o2ios/O2Platform/Framework/O2API/Communicate/CommunicateAPI.swift

@@ -14,6 +14,10 @@ import O2OA_Auth_SDK
 enum CommunicateAPI {
     case myConversationList
     case msgListByPaging(Int, Int, String)
+    case sendMsg(IMMessageInfo)
+    case readConversation(String)
+    case instantMessageList(Int)
+    case createConversation(IMConversationInfo)
     
     
 }
@@ -45,15 +49,25 @@ extension CommunicateAPI: TargetType {
             return "/jaxrs/im/conversation/list/my"
         case .msgListByPaging(let page, let size, _):
             return "/jaxrs/im/msg/list/\(page)/size/\(size)"
+        case .sendMsg(_):
+            return "/jaxrs/im/msg"
+        case .readConversation(let conversationId):
+            return "/jaxrs/im/conversation/\(conversationId)/read"
+        case .instantMessageList(let count):
+            return "/jaxrs/instant/list/currentperson/noim/count/\(count)/desc"
+        case .createConversation(_):
+            return "/jaxrs/im/conversation"
         }
     }
     
     var method: Moya.Method {
         switch self {
-        case .myConversationList:
+        case .myConversationList, .instantMessageList(_):
             return .get
-        case .msgListByPaging(_, _, _):
+        case .msgListByPaging(_, _, _), .sendMsg(_), .createConversation(_):
             return .post
+        case .readConversation(_):
+            return .put
         }
     }
     
@@ -63,12 +77,16 @@ extension CommunicateAPI: TargetType {
     
     var task: Task {
         switch self {
-        case .myConversationList:
+        case .myConversationList, .instantMessageList(_), .readConversation(_):
             return .requestPlain
         case .msgListByPaging(_, _, let conversationId):
             let form = IMMessageRequestForm()
             form.conversationId = conversationId
             return .requestParameters(parameters: form.toJSON()!, encoding: JSONEncoding.default)
+        case .sendMsg(let msg):
+            return .requestParameters(parameters: msg.toJSON()!, encoding: JSONEncoding.default)
+        case .createConversation(let conv):
+            return .requestParameters(parameters: conv.toJSON()!, encoding: JSONEncoding.default)
         }
     }
     

+ 2 - 2
o2ios/O2Platform/Info.plist

@@ -163,7 +163,7 @@
 		<key>centerContext</key>
 		<string>/x_program_center</string>
 		<key>centerHost</key>
-		<string>qywx.o2oa.net</string>
+		<string>dd.o2oa.net</string>
 		<key>centerPort</key>
 		<integer>20030</integer>
 		<key>httpProtocol</key>
@@ -171,7 +171,7 @@
 		<key>id</key>
 		<string>o2CenterServer</string>
 		<key>name</key>
-		<string>dev</string>
+		<string>dd</string>
 	</dict>
 </dict>
 </plist>

+ 2 - 0
o2ios/O2Platform/config/config.swift

@@ -36,6 +36,8 @@ let safeAreaTopHeight:CGFloat = (iPhoneX ? 88 : 64)
 let IOS11_TOP_STATUSBAR_HEIGHT = iPhoneX ? 44 : 20
 //底部安全高度
 let IPHONEX_BOTTOM_SAFE_HEIGHT: CGFloat = 34.0
+//uiTabBar 高度
+let TAB_BAR_HEIGHT: CGFloat  = iPhoneX ? (49 + 34) : 49