// // SessionUtil.swift // LegendTeam // // Created by 梁博 on 20/12/21. // import Foundation import NIMSDK class SessionUtil { var topViewHeight: CGFloat = 0 let mssagePageSize = 50 //var session: NIMSession! //var conversation: V2NIMConversation! var conversationId : String! let showTimeInterval: TimeInterval = 180 var cellModels: [SessionBaseModel]! = [SessionBaseModel]() /// Key: messageClientId。 ⚠️不能用messageServerId,因为消息可能是在发送中,导致没有messageServerId var msgIdDict: [String: SessionBaseModel]! = [String: SessionBaseModel]() func getLastMessageModel() -> SessionBaseModel? { var lastModel: SessionBaseModel? for model in cellModels { if let messageServerId = model.v2msg?.messageServerId, messageServerId.count > 0{ lastModel = model } } return lastModel } } // private extension SessionUtil { /// 最晚的时间, 也是排在最前面的时间 func firtTimeStamp() -> TimeInterval { guard let model = cellModels.first else { return 0 } return model.timeStamp } /// 最后一条消息的时候,也是离现在最近的时间 func lastTimeInterval() -> TimeInterval { return cellModels.last?.timeStamp ?? 0 } /// 是否能追加时间显示 func shouldAppendTimestamp(model: SessionBaseModel) -> Bool { if model.cellType == .aiLoading{ return false } return model.timeStamp - lastTimeInterval() > showTimeInterval } /// 是否能插入时间显示 func shouldInsertTimestamp(model: SessionBaseModel) -> Bool { return firtTimeStamp() - model.timeStamp > showTimeInterval } /// 是否需要忽略本条消息,不创建model,不显示 func ignore(info: IMBaseRemoteInfo) -> Bool{ var ignore = false if let customAttachment = info.customAttachment{ if let type = customAttachment.type{ if type == .HEARTBEAT_LEVEL_DOWN || type == .HEARTBEAT_LEVEL_UP || type == .INSUFFICIENT_BALANCE || type == .VOICE_CHAT_CONTENT { ignore = true } }else{ // 没有type的自定义消息 ignore = true } } return ignore } /// 是否需要追加一个model显示,判断一条消息显示两个model的情况 func needDeriveModel(message: V2NIMMessage) -> Bool { if message.messageType == .MESSAGE_TYPE_CUSTOM{ if let attachment = message.attachment?.raw, attachment.count > 0 { let model = CodableHelper.decode(IMCustomAttachment.self, from: attachment) if let type = model?.type, type.rawValue.uppercased() == CLCustomAttchType.IMAGE.rawValue{ return true } } } if message.serverExtension == nil { return false } let info = IMRemoteUtil.dealRemoteInfo(message: message) if info.cellType == .keyword || info.cellType == .gift { return true } return false } func needDecriveModel(with: IMBaseRemoteInfo) -> Bool{ if let customAttachment = with.customAttachment, let type = customAttachment.type{ if type == .IMAGE{ return true } } if with.cellType == .keyword || with.cellType == .gift{ return true } return false } /// 将model存在字典中 func setIdIdctObject(model: SessionBaseModel) { if model.isDeriveModel { return } guard let messageID = model.v2msg?.messageClientId else { return } msgIdDict[messageID] = model } /// message是否已经存在model func modelIsExist(model: SessionBaseModel) -> Bool { guard let messageID = model.v2msg?.messageClientId else { return false } return msgIdDict[messageID] != nil } func logAllCellModels(){ if APIConfig.environment == .test{ var string = "☁️cellModels(\(cellModels.count):\n" for per in cellModels { string += "\(per)\n" } dlog(string) } } } // MARK: - Deal Models /// Deal Models extension SessionUtil { /// 根据类型创建cellModel func createCellModelWith(type: SessionCellType) -> SessionBaseModel { let model = SessionBaseModel() let configType = SessionBaseModel.getContentConfigClass(type: type) // 创建配置类 model.config = configType.init() model.cellType = type switch type { case .image: model.shouldShowBubble = false case .gift: // 卡片 model.shouldShowBubble = false model.shouldShowCardView = true case .timeStamp,.tips, .create, .keyword, .phonecall: model.shouldShowBubble = false model.shouldShowTips = true default: // 默认bubble model.shouldShowBubble = true model.shouldShowTips = false break } return model } func createCellModelWithCustom(attachment:String?) -> SessionBaseModel{ let model = SessionBaseModel() guard let raw = attachment, let data = CodableHelper.decode(IMCustomAttachment.self, from: raw) else{ return model } // switch data.type{ // case .IMAGE: break // // } //let configType = SessionBaseModel.getContentConfigClass(type: type) return model } /// 处理查询回来的消息 func dealMessages(messages: [V2NIMMessage]?, completion: @escaping ((_ messageDatas: [V2NIMMessage]?, _ error: Error?) -> Void)) { var tempModels = modelWithV2(datas: messages!) var tempMessages = messages ?? [V2NIMMessage]() // while tempModels.count < mssagePageSize, messages!.count > 0 { // tempMessages = NIMSDK.shared().conversationManager.messages(in: session, message: tempMessages.first, limit: mssagePageSize)! // if Array.realEmpty(array: tempMessages) { // break // } // let theModels = modelWith(messsages: tempMessages) // tempModels.insert(contentsOf: theModels, at: 0) // } // let indexs = insertMessagesWith(models: tempModels.reversed()) DispatchQueue.main.async { completion( messages, nil) } } @discardableResult func inserMessageWith(message: V2NIMMessage, atIndex: Int) -> [Int]{ let models = modelWithV2(datas: [message]) guard let model = models.first else { return [] } self.cellModels.insert(model, at: atIndex) return [atIndex] } /// 追加model到数据源 func appendWith(models: [SessionBaseModel]) -> [Int] { if Array.realEmpty(array: models) { return [] } let count = cellModels.count for model in models { if modelIsExist(model: model), !model.isDeriveModel { continue } _ = appendWith(model: model) } var indexs = [Int]() for i in count ..< cellModels.count { indexs.append(i) } return indexs } /// 追加model到数据源 @discardableResult func appendWith(model: SessionBaseModel) -> [Int] { var indexs = [Int]() if shouldAppendTimestamp(model: model) { addTimestamp(model: model, index: cellModels.count) indexs.append(cellModels.count) } cellModels.insert(model, at: cellModels.count) indexs.append(cellModels.count) setIdIdctObject(model: model) return indexs } /// 追加model到数据源 func addTimestamp(model: SessionBaseModel, index: Int) { let timeModel = createCellModelWith(type: .timeStamp) timeModel.shouldShowBubble = false timeModel.shouldShowTips = true timeModel.timeStamp = model.v2msg?.createTime ?? model.timeStamp cellModels.insert(timeModel, at: index) } /// 通过消息查找当前model func findCellModel(message: V2NIMMessage?) -> SessionBaseModel? { guard let messageID = message?.messageClientId else { return nil } // 获取字典里的model数据 let model = msgIdDict[messageID] return model } /// 查询model在数据源中的位置index func modelIndexWith(model: SessionBaseModel) -> Int? { for i in 0 ..< cellModels.count { let obj = cellModels[i] //if obj.isEqual(model) { if obj === model{ return i } } return nil } /// 通过message 追加model 到数据源 @discardableResult func appendWith(messages: [V2NIMMessage]) -> [Int] { let models = modelWithV2(datas: messages) // modelWithV2 生成的是反序的 let indexs = appendWith(models: models.reversed()) return indexs } /// 通过message 更新数据源的 model func updateWith(message: V2NIMMessage) -> [Int]? { guard let model = findCellModel(message: message) else { return nil } guard let index = modelIndexWith(model: model) else { return nil } var indexs = [Int]() indexs.append(index) if needDeriveModel(message: message) { if index + 1 < cellModels.count { indexs.append(index + 1) } } return indexs } // MARK: V2 /// [V2NIMMessage] -> [SessionBaseModel] func modelWithV2(datas:[V2NIMMessage]) -> [SessionBaseModel]{ var array = [SessionBaseModel]() for i in 0 ..< datas.count { let message = datas[i] if let messageID = message.messageClientId, msgIdDict[messageID] != nil{ // 已经存在了 dlog("☁️⚠️⚠️\(message)已经存在了,应该更新,而不是新创建!⚠️⚠️") continue } if let serverExtension = message.serverExtension, serverExtension.count > 0{ dlog("☁️message___ext___\(serverExtension)") } // Step 通过消息获取初步的cellType let info = IMRemoteUtil.dealRemoteInfo(message: message) // 需要被忽略掉的消息 guard !ignore(info: info) else { continue } // Step 通过类型创建对应的cellModel if needDecriveModel(with: info) { // needDeriveModel(message: message) // 从下往上显示: // 消息主体(显示在上面👆cell的上方)// 消息主体 let cellModel = createCellModelWith(type: info.cellType) cellModel.baseRemoteInfo = info cellModel.isDeriveModel = true cellModel.shouldShowLeft = !message.isSelf cellModel.v2msg = message cellModel.timeStamp = message.createTime array.append(cellModel) if message.messageType == .MESSAGE_TYPE_TEXT { if message.isSelf{ let cellModel = createCellModelWith(type: .text) cellModel.baseRemoteInfo = info cellModel.shouldShowLeft = false cellModel.v2msg = message cellModel.timeStamp = message.createTime array.append(cellModel) }else{ let cellModel = createCellModelWith(type: .aimsg) cellModel.baseRemoteInfo = info cellModel.shouldShowLeft = true cellModel.v2msg = message cellModel.timeStamp = message.createTime array.append(cellModel) } } else if message.messageType == .MESSAGE_TYPE_TIP { let cellModel = createCellModelWith(type: .tips) cellModel.baseRemoteInfo = info cellModel.shouldShowLeft = !message.isSelf cellModel.v2msg = message cellModel.timeStamp = message.createTime array.append(cellModel) }else if message.messageType == .MESSAGE_TYPE_CUSTOM{ // 自定义消息,承载很多业务消息类型 if message.isSelf{ let cellModel = createCellModelWith(type: .text) cellModel.baseRemoteInfo = info cellModel.shouldShowLeft = !message.isSelf cellModel.v2msg = message cellModel.timeStamp = message.createTime array.append(cellModel) }else{ if info.cellType != .image{ let cellModel = createCellModelWith(type: .aimsg) cellModel.baseRemoteInfo = info cellModel.shouldShowLeft = !message.isSelf cellModel.v2msg = message cellModel.timeStamp = message.createTime array.append(cellModel) } } } } else { // 不需要附加一个cell,正常添加 let cellModel = createCellModelWith(type: info.cellType) cellModel.baseRemoteInfo = info cellModel.shouldShowLeft = !message.isSelf cellModel.v2msg = message cellModel.timeStamp = message.createTime array.append(cellModel) } } return array } func createAnAILoadingModel(){ let cellModel = createCellModelWith(type: .aiLoading) cellModel.isDeriveModel = true cellModel.shouldShowLeft = true cellModel.v2msg = nil cellModel.timeStamp = TimeInterval(Date().timeStampInSeconds) appendWith(model: cellModel) } func clearAILoadingModel(){ cellModels.removeAll(where: {$0.cellType == .aiLoading}) } @discardableResult func insertMessagesWith(models: [SessionBaseModel]) -> [Int] { if Array.realEmpty(array: models) { return [] } let count = cellModels.count for i in 0 ..< models.count { let model = models[i] if modelIsExist(model: model), !model.isDeriveModel, !model.shouldShowTips { continue } insertMessageWith(model: model, needTime: cellModels.count != count) } var indexs = [Int]() for i in 0 ..< cellModels.count - count { indexs.append(i) } return indexs } /// 插入model到数据源 func insertMessageWith(model: SessionBaseModel, needTime: Bool) { // 这条消息上面不能有时间戳,下面必须要有时间戳 let isCreateTimestampCell = (model.cellType == .create || model.cellType == .tips) if isCreateTimestampCell { if let theModel = cellModels.first, theModel.cellType != .timeStamp { addTimestamp(model: theModel, index: 0) } } else { if !shouldInsertTimestamp(model: model), needTime { let model = cellModels.first if model?.cellType == .timeStamp { cellModels.removeFirst() } } } cellModels.insert(model, at: 0) setIdIdctObject(model: model) if !isCreateTimestampCell { addTimestamp(model: model, index: 0) } } func loadMessages(completion: @escaping ((_ firstPage: Bool, _ messageDatas: [V2NIMMessage]?, _ error: V2NIMError?) -> Void)){ let option = V2NIMMessageListOption() var loadMoreMsgFlag = false if let v2Msg = getLastMessageModel()?.v2msg{ option.anchorMessage = v2Msg option.beginTime = v2Msg.createTime loadMoreMsgFlag = true } option.conversationId = conversationId NIMSDK.shared().v2MessageService.getMessageList(option) {[weak self] messages in if let models = self?.modelWithV2(datas: messages){ // self?.cellModels = models self?.insertMessagesWith(models: models) completion(!loadMoreMsgFlag, messages,nil) } } failure: { error in dlog("☁️Get messages error:\(error)") completion(!loadMoreMsgFlag, nil,error) } } } // MARK: - Messages extension SessionUtil { /// 发送消息 func sendMessage(message: V2NIMMessage) { sendMessage(message: message) { error in print(error ?? "") } } /// 发送消息 func sendMessage(message: V2NIMMessage, completion: @escaping ((_ error: Error?) -> Void)) { // See more params in https://doc.yunxin.163.com/messaging2/client-apis/DAxNjk0Mzc?platform=client#V2NIMSendMessageParams let param = V2NIMSendMessageParams() NIMSDK.shared().v2MessageService.send(message, conversationId: conversationId, params: param) { result in dlog("☁️✅send message ok \(message.text ?? "")") } failure: { error in dlog("☁️❌send message failed: \(error)") if error.code == 20000{ CLPurchase.shared.showChatModelsAndIAPEntrySheet() } } // NIMSDK.shared().chatManager.send(message, to: session) { error in // completion(error) // } } } // MARK: - Test extension SessionUtil{ func testModes(completion: @escaping (() -> Void)) { var array = [SessionBaseModel]() do{ let cellModel = createCellModelWith(type: .aimsg) cellModel.shouldShowLeft = true let msg = V2NIMMessage() msg.text = "(Watching her parents toast you respectfully, I feel very uncomfortable. After all, she has been standing on the top of the magic capital since she was a child. She has never seen her parents like this, but should I say that he is really handsome?) Are you?" cellModel.v2msg = msg cellModel.timeStamp = TimeInterval(Date().timeStamp - 1000) array.append(cellModel) } do{ let cellModel = createCellModelWith(type: .text) cellModel.shouldShowLeft = false let msg = V2NIMMessage() msg.text = "who are you" cellModel.v2msg = msg cellModel.timeStamp = TimeInterval(Date().timeStamp - 500) array.append(cellModel) } cellModels = array completion() } }