566 lines
19 KiB
Swift
Executable File
566 lines
19 KiB
Swift
Executable File
//
|
||
// 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()
|
||
|
||
}
|
||
|
||
|
||
}
|