Files
yingmeng-ios-switf/yinmeng-ios/Modules/Chat/VM/ChatViewModel.swift
2024-03-01 14:22:06 +08:00

666 lines
19 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// ChatViewModel.swift
// yinmeng-ios
//
// Created by MaiMang on 2024/2/27.
//
import Foundation
import NIMSDK
public typealias ChatProviCompletion = ((Error?, [NIMMessage]?) -> ())
public enum LoadMessageDirection: Int {
case old = 1
case new
}
public protocol ChatViewModelDelegate: NSObjectProtocol {
func didAppend(_ message: NIMMessage)
func onDeleteMessage(_ message: NIMMessage, atIndexs: [IndexPath])
func send(_ message: NIMMessage, didCompleteWithError error: Error?)
func send(_ message: NIMMessage, progress: Float)
func onRecvMessages(_ messages: [NIMMessage])
func willSend(_ message: NIMMessage)
}
public class ChatViewModel: NSObject,
NIMConversationManagerDelegate, NIMSystemNotificationManagerDelegate {
private var userInfo = [String: NIMUser]()
public let messagPageNum: UInt = 100
public var session: NIMSession
///
public var messageObjects: [ChatSessionProtocol] = .init()
//
private var oldMsg: NIMMessage?
//
private var newMsg: NIMMessage?
//
public var credibleTimestamp: TimeInterval = 0
public var anchor: NIMMessage?
internal var isHistoryAnchorChat = false
public weak var delegate: ChatViewModelDelegate?
private func addMessageListener() {
NIMSDK.shared().chatManager.add(self)
NIMSDK.shared().conversationManager.add(self)
NIMSDK.shared().systemNotificationManager.add(self)
}
init(session: NIMSession) {
self.session = session
super.init()
addMessageListener()
}
///
/// - Parameters:
/// - message:
/// - completion:
public func sendMessage(message: NIMMessage, _ completion: @escaping (Error?) -> Void) {
NIMSDK.shared().chatManager.send(message, to: session, completion: completion)
}
////
public func sendAudioMessage(filePath: String, _ completion: @escaping (Error?) -> Void) {
let audioObject = NIMAudioObject(sourcePath: filePath)
let audioMessage = NIMMessage()
audioMessage.messageObject = audioObject
audioMessage.apnsContent = "发来了一段语音"
let setting = NIMMessageSetting()
setting.teamReceiptEnabled = false
audioMessage.setting = setting
sendMessage(message: audioMessage, completion)
}
///
public func sendImageMessage(image: UIImage, _ completion: @escaping (Error?) -> Void) {
let imageMessage = NIMMessage()
let imageOpt = NIMImageOption()
imageOpt.compressQuality = 0.8
let imageObject = NIMImageObject(image: image)
imageObject.option = imageOpt
imageMessage.messageObject = imageObject
imageMessage.apnsContent = "发送了一张图片"
sendMessage(message: imageMessage, completion)
}
public func sendTextMessage(text: String, _ completion: @escaping (Error?) -> Void) {
if text.count <= 0 {
return
}
let textMessage = NIMMessage()
textMessage.text = text
sendMessage(message: textMessage, completion)
}
//
public func reloadRemoteHistoryMessage(direction: LoadMessageDirection, updateCredible: Bool,
option: NIMHistoryMessageSearchOption,
_ completion: @escaping (Error?, NSInteger,
[ChatSessionProtocol]?) -> Void) {
weak var weakSelf = self
NIMSDK.shared().conversationManager.fetchMessageHistory(session, option: option) { error, messages in
if error == nil {
if let messageArray = messages, messageArray.count > 0 {
if direction == .old {
weakSelf?.oldMsg = messageArray.last
} else {
weakSelf?.newMsg = messageArray.first
}
for msg in messageArray {
if let model = weakSelf?.modelTransformMessage(message: msg) {
weakSelf?.addTimeMessage(msg)
weakSelf?.messageObjects.insert(model, at: 0)
}
}
if let updateMessage = messageArray.first, updateCredible {
//
weakSelf?.credibleTimestamp = updateMessage.timestamp
}
completion(error, messageArray.count, weakSelf?.messageObjects)
} else {
completion(error, 0, weakSelf?.messageObjects)
}
} else {
completion(error, 0, nil)
}
}
}
//
public func dropDownRemoteRefresh(_ completion: @escaping (Error?, NSInteger, [ChatSessionProtocol]?)
-> Void) {
let isCredible = isMessageCredible(message: oldMsg ?? NIMMessage())
if isCredible { //
getMoreMessageHistory(completion)
} else {
let option = NIMHistoryMessageSearchOption()
option.startTime = 0
option.endTime = oldMsg?.timestamp ?? 0
option.limit = messagPageNum
option.sync = true
//
reloadRemoteHistoryMessage(
direction: .old,
updateCredible: false,
option: option,
completion
)
}
}
public func queryRoamMsgHasMoreTime_v2(_ completion: @escaping (Error?, NSInteger, NSInteger,
[ChatSessionProtocol]?, Int) -> Void) {
weak var weakSelf = self
NIMSDK.shared().conversationManager.incompleteSessionInfo(by: session) { error, sessionInfos in
if error == nil {
let sessionInfo = sessionInfos?.first
//
weakSelf?.credibleTimestamp = sessionInfo?.timestamp ?? 0
weakSelf?.getMessageHistory(self.newMsg) { error, value, models in
completion(error, value, 0, models, 0)
}
}
}
}
//
public func getMessageHistory(_ message: NIMMessage?,
_ completion: @escaping (Error?, NSInteger, [ChatSessionProtocol]?)
-> Void) {
NIMSDK.shared().conversationManager.messages(in: session, message: message, limit: Int(messagPageNum), completion: result(completion))
func result(_ completion: @escaping (Error?, NSInteger, [ChatSessionProtocol]?) -> ()) -> ChatProviCompletion {
return { [weak self] error, messages in
if let messageArray = messages, messageArray.count > 0 {
self?.oldMsg = messageArray.first
for msg in messageArray {
if let model = self?.modelTransformMessage(message: msg) {
self?.addTimeMessage(msg)
self?.messageObjects.append(model)
}
}
completion(error, messageArray.count, self?.messageObjects)
self?.markRead(messages: messageArray) { error in
}
} else {
completion(error, 0, self?.messageObjects)
}
}
}
}
//
public func getMoreMessageHistory(_ completion: @escaping (Error?, NSInteger, [ChatSessionProtocol]?)
-> Void) {
let messageParam = oldMsg ?? newMsg
weak var weakSelf = self
NIMSDK.shared().conversationManager.messages(in: session, message: messageParam, limit: Int(messagPageNum)) { error, messages in
if let messageArray = messages, messageArray.count > 0 {
weakSelf?.oldMsg = messageArray.first
// 使
let isCredible = weakSelf?
.isMessageCredible(message: messageArray.first ?? NIMMessage())
if let isTrust = isCredible, isTrust {
for msg in messageArray.reversed() {
if let model = weakSelf?.modelTransformMessage(message: msg) {
weakSelf?.addTimeMessage(msg)
weakSelf?.messageObjects.insert(model, at: 0)
}
}
completion(error, messageArray.count, weakSelf?.messageObjects)
} else {
let option = NIMHistoryMessageSearchOption()
option.startTime = 0
option.endTime = weakSelf?.oldMsg?.timestamp ?? 0
option.limit = weakSelf?.messagPageNum ?? 100
option.sync = true
weakSelf?.reloadRemoteHistoryMessage(
direction: .old,
updateCredible: true,
option: option,
completion
)
}
weakSelf?.markRead(messages: messageArray) { error in
}
} else {
if let messageArray = messages, messageArray.isEmpty,
weakSelf?.credibleTimestamp ?? 0 > 0 {
//
let option = NIMHistoryMessageSearchOption()
option.startTime = 0
option.endTime = weakSelf?.oldMsg?.timestamp ?? 0
option.limit = weakSelf?.messagPageNum ?? 100
weakSelf?.reloadRemoteHistoryMessage(
direction: .old,
updateCredible: true,
option: option,
completion
)
} else {
completion(error, 0, weakSelf?.messageObjects)
}
}
}
}
//
public func searchMessageHistory(direction: LoadMessageDirection, startTime: TimeInterval,
endTime: TimeInterval,
_ completion: @escaping (Error?, NSInteger, [ChatSessionProtocol]?)
-> Void) {
let option = NIMMessageSearchOption()
option.startTime = startTime
option.endTime = endTime
option.order = .asc
option.limit = messagPageNum
NIMSDK.shared().conversationManager.searchMessages(session, option: option) { [weak self] error, messages in
if error == nil {
if let messageArray = messages, messageArray.count > 0 {
var newMessages = [NIMMessage]()
for msg in messageArray {
newMessages.append(msg)
if let model = self?.modelTransformMessage(message: msg) {
self?.addTimeMessage(msg)
self?.messageObjects.append(model)
}
}
if direction == .old {
self?.oldMsg = newMessages.first
} else {
self?.newMsg = newMessages.last
}
completion(error, newMessages.count, self?.messageObjects)
} else {
completion(error, 0, self?.messageObjects)
}
} else {
completion(error, 0, nil)
}
}
}
//
public func isMessageCredible(message: NIMMessage) -> Bool {
return credibleTimestamp <= 0 || message.timestamp >= credibleTimestamp
}
public func markRead(messages: [NIMMessage], _ completion: @escaping (Error?) -> Void) {
NIMSDK.shared().conversationManager.markAllMessagesRead(in: session) { error in
}
}
@discardableResult
public func resendMessage(message: NIMMessage) -> Error? {
var e: Error? = nil
do {
try NIMSDK.shared().chatManager.resend(message)
} catch {
e = error
}
return e
}
public func getUserInfo(userId: String) -> NIMUser? {
return NIMSDK.shared().userManager.userInfo(userId)
}
public func getTeamMember(userId: String, teamId: String) -> NIMTeamMember? {
// return repo.getTeamMemberList(userId: userId, teamId: teamId)
return nil
}
public func deleteMessage(message: NIMMessage) {
NIMSDK.shared().conversationManager.delete(message)
deleteMessageUpdateUI(message)
}
// MARK: collection
func addColletion(_ message: NIMMessage,
completion: @escaping (NSError?, NIMCollectInfo?) -> Void) {
let param = NIMAddCollectParams()
var string: String?
if message.messageType == .text {
string = message.text
param.type = 1024
} else {
switch message.messageType {
case .audio:
if let obj = message.messageObject as? NIMAudioObject {
string = obj.url
}
param.type = message.messageType.rawValue
case .image:
if let obj = message.messageObject as? NIMImageObject {
string = obj.url
}
param.type = message.messageType.rawValue
case .video:
if let obj = message.messageObject as? NIMVideoObject {
string = obj.url
}
param.type = message.messageType.rawValue
default:
param.type = 0
}
param.data = string ?? ""
}
param.uniqueId = message.serverID
// repo.collectMessage(param, completion)
}
// MARK: revoke
// history message insert message at first of messages, send message add last of messages
private func addTimeMessage(_ message: NIMMessage) {
let lastTs = messageObjects.last?.msg?.timestamp ?? 0.0
let curTs = message.timestamp
let dur = curTs - lastTs
print("curTs:\(curTs) lastTs:\(lastTs)")
if (dur / 60) > 5 {
messageObjects.append(timeModel(message))
}
}
private func ddTimeForHistoryMessage(_ message: NIMMessage) {
let firstTs = messageObjects.first?.msg?.timestamp ?? 0.0
let curTs = message.timestamp
let dur = firstTs - curTs
print("HistorycurTs:\(curTs) firstTs:\(firstTs)")
if (dur / 60) > 5 {
messageObjects.insert(timeModel(message), at: 0)
}
}
private func timeModel(_ message: NIMMessage) -> ChatSessionProtocol {
let timeMsg = NIMMessage()
timeMsg.timestamp = message.timestamp
let model = ChatTimeObject(msg: timeMsg)
return model
}
private func modelTransformMessage(message: NIMMessage) -> ChatSessionProtocol? {
var model: ChatSessionProtocol
switch message.messageType {
case .text:
model = ChatTextObject(msg: message)
case .audio:
model = ChatVoiceObject(msg: message)
case .image:
model = ChatImageObject(msg: message)
default:
return nil
}
if let uid = message.from {
model.userID = uid
let user = getUserInfo(userId: uid)
var fullName = uid
if let nickName = user?.userInfo?.nickName {
fullName = nickName
}
model.avatar = user?.userInfo?.avatarUrl
if session.sessionType == .team {
// team
let teamMember = getTeamMember(userId: uid, teamId: session.sessionId)
if let teamNickname = teamMember?.nickname {
fullName = teamNickname
}
}
if let alias = user?.alias {
fullName = alias
}
model.name = fullName
}
return model
}
func deleteMessageUpdateUI(_ message: NIMMessage) {
var index = -1
for (i, model) in messageObjects.enumerated() {
if model.msg?.serverID == message.serverID {
index = i
break
}
}
var indexs = [IndexPath]()
if index >= 0 {
// remove time tip
let last = index - 1
// if last >= 0, let timeModel = messages[last] as? MAIMessageTipsModel,
// timeModel.type == .time {
// messageObjects.removeSubrange(last ... index)
// indexs.append(IndexPath(row: last, section: 0))
// indexs.append(IndexPath(row: index, section: 0))
// } else {
messageObjects.remove(at: index)
indexs.append(IndexPath(row: index, section: 0))
// }
}
delegate?.onDeleteMessage(message, atIndexs: indexs)
}
private func getUserInfo(_ userId: String, _ completion: @escaping (NIMUser?, NSError?) -> Void) {
if let user = userInfo[userId] {
completion(user, nil)
}
if let user = getUserInfo(userId: userId) {
userInfo[userId] = user
completion(user, nil)
}
}
// p2p >ID team: ID
private func getShowName(userId: String, teamId: String?) -> String {
let user = getUserInfo(userId: userId)
var fullName = userId
if let nickName = user?.userInfo?.nickName {
fullName = nickName
}
// model.avatar = user?.userInfo?.thumbAvatarUrl
if let tID = teamId, session.sessionType == .team {
// team
let teamMember = getTeamMember(userId: userId, teamId: tID)
if let teamNickname = teamMember?.nickname {
fullName = teamNickname
}
}
if let alias = user?.alias {
fullName = alias
}
return fullName
}
public func fetchMessageAttachment(_ message: NIMMessage,
_ completion: @escaping (Error?) -> Void) {
do {
try NIMSDK.shared().chatManager.fetchMessageAttachment(message)
} catch let error {
completion(error)
}
}
public func downLoad(_ urlString: String, _ filePath: String, _ progress: NIMHttpProgressBlock?,
_ completion: NIMDownloadCompleteBlock?) {
NIMSDK.shared().resourceManager.download(urlString, filepath: filePath, progress: progress, completion: completion)
}
public func getUrls() -> [String] {
var urls = [String]()
messageObjects.forEach { model in
if model.type == .image, let message = model.msg?.messageObject as? NIMImageObject {
if let url = message.url {
urls.append(url)
} else {
if let path = message.path, FileManager.default.fileExists(atPath: path) {
urls.append(path)
}
}
}
}
return urls
}
public func sendInputTypingState() {
if session.sessionType == .P2P {
setTypingCustom(1)
}
}
public func sendInputTypingEndState() {
if session.sessionType == .P2P {
setTypingCustom(0)
}
}
func setTypingCustom(_ typing: Int) {
let message = NIMMessage()
if message.setting == nil {
message.setting = NIMMessageSetting()
}
message.setting?.apnsEnabled = false
message.setting?.shouldBeCounted = false
let noti =
NIMCustomSystemNotification(content: getJSONStringFromDictionary(["typing": typing]))
NIMSDK.shared().systemNotificationManager.sendCustomNotification(noti, to: session)
}
public func getHandSetEnable() -> Bool {
return false
// return repo.getHandsetMode()
}
public func getMessageRead() -> Bool {
return NIMSDK.shared().conversationManager.allUnreadCount() > 0
}
// MARK: NIMConversationManagerDelegate
private func getJSONStringFromDictionary(_ dictionary: [String: Any]) -> String {
if !JSONSerialization.isValidJSONObject(dictionary) {
print("not parse to json string")
return ""
}
if let data = try? JSONSerialization.data(withJSONObject: dictionary, options: []),
let JSONString = String(data: data, encoding: .utf8) {
return JSONString
}
return ""
}
private func getDictionaryFromJSONString(_ jsonString: String) -> NSDictionary? {
if let jsonData = jsonString.data(using: .utf8),
let dict = try? JSONSerialization.jsonObject(
with: jsonData,
options: .mutableContainers
) as? NSDictionary {
return dict
}
return nil
}
deinit {
print("deinit")
}
}
extension ChatViewModel: NIMChatManagerDelegate {
// MARK: NIMChatManagerDelegate
public func send(_ message: NIMMessage, didCompleteWithError error: Error?) {
for (i, msg) in messageObjects.enumerated() {
if message.messageId == msg.msg?.messageId {
messageObjects[i].msg = message
break
}
}
delegate?.send(message, didCompleteWithError: error)
}
//
public func onRecvMessages(_ messages: [NIMMessage]) {
for msg in messages {
if msg.session?.sessionId == session.sessionId {
if let model = modelTransformMessage(message: msg) {
newMsg = msg
addTimeMessage(msg)
self.messageObjects.append(model)
}
}
}
delegate?.onRecvMessages(messages)
}
public func willSend(_ message: NIMMessage) {
if message.session?.sessionId != session.sessionId {
return
}
guard let model = modelTransformMessage(message: message) else { return }
if newMsg == nil {
newMsg = message
}
var isResend = false
for (i, msg) in messageObjects.enumerated() {
if message.messageId == msg.msg?.messageId {
messageObjects[i].msg = message
isResend = true
break
}
}
if !isResend {
addTimeMessage(message)
messageObjects.append(model)
}
delegate?.didAppend(message)
}
public func onReceive(_ notification: NIMCustomSystemNotification) {
}
public func send(_ message: NIMMessage, progress: Float) {
delegate?.send(message, progress: progress)
}
}