Visual_Novel_iOS/crush/Crush/Src/Modules/Chat/Util/IMManager.swift

525 lines
16 KiB
Swift
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

//
// IMManager.swift
// Crush
//
// Created by Leon on 2025/8/18.
//
import NIMSDK
import Combine
import UserNotifications
class IMManager: NSObject {
public static let shared = IMManager()
var retryCount = 0
// appkey
var imInfo: IMConfigInfo?
// oc
var accountId: String {
imInfo?.accountId ?? ""
}
// session Cache
var cacheSessions = [String]()
// Config
var notNeedSendNotification = false
// UnreadCount
var totalUnreadCount: Int {
return chatTabAllUnreadCount
}
/// 🔥 chat tab
@Published var chatTabAllUnreadCount: Int = 0
/// 🔥notice center
@Published var noticeUnreadCount: Int = 0
@Published var noticeStat: MessageStat?
/// 🔥IM
@Published var sessionUnreadCount: Int = 0
private var cancellables = Set<AnyCancellable>()
override init() {
super.init()
setupDefaultConfig()
setupEvent()
}
func setupEvent() {
NotificationCenter.default.addObserver(self, selector: #selector(notiLoginSuccess), name: AppNotificationName.userLoginSuccess.notificationName, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(notiLogout), name: AppNotificationName.userLogout.notificationName, object: nil)
//
// UserCore.shared.isLogin()
Publishers.CombineLatest($noticeUnreadCount, $sessionUnreadCount)
.map { $0 + $1 }
.assign(to: \.chatTabAllUnreadCount, on: self)
.store(in: &cancellables)
$chatTabAllUnreadCount.sink { count in
UIApplication.shared.applicationIconBadgeNumber = count
}.store(in: &cancellables)
}
// sdk
func setupDefaultConfig() {
let config = NIMSDKConfig.shared()
// config.delegate = self
//
config.shouldSyncUnreadCount = true
//
config.maxAutoLoginRetryTimes = 10
// log
config.maximumLogDays = 30
//
config.shouldCountTeamNotification = true
//
config.animatedImageThumbnailEnabled = false
//
config.fetchAttachmentAutomaticallyAfterReceiving = false
//
config.fetchAttachmentAutomaticallyAfterReceivingInChatroom = true
//
config.asyncLoadRecentSessionEnabled = true
// 退
config.reconnectInBackgroundStateDisabled = true
// 使
config.shouldSyncStickTopSessionInfos = false
}
/// 🔥SDK
public func setupNIM() {
NIMSDK.shared().v2LoginService.add(self)
NIMSDK.shared().v2ConversationService.add(self)
NIMSDK.shared().v2SubscriptionService.add(self)
NIMSDK.shared().v2MessageService.add(self)
// SDK
v2InitailSDK()
refreshIMServerInfoConfig()
//setupNIMAppKey()
}
func v2InitailSDK(){
// guard UserCore.shared.isLogin() else {
// return
// }
let appkey = "2d6abc076f434fc52320c7118de5fead"
var option = NIMSDKOption.init(appKey: appkey)
#if DEBUG
option.apnsCername = "developerpush"
#else
option.apnsCername = "distributionpush"
#endif
let v2Option = V2NIMSDKOption()
v2Option.enableV2CloudConversation = true
NIMSDK.shared().register(withOptionV2: option, v2Option: v2Option)
if NIMSDK.shared().loginManager.isLogined(){
dlog("💬❌ NIMSDK 已登录")
}
// v2AutoLogin()
}
func v2AutoLogin(){
if imInfo == nil {
//
if let info = AppCache.fetchCache(key: CacheKey.ImAppkeyInfo.rawValue, type: IMConfigInfo.self) {
imInfo = info
} else {
return
}
}
guard UserCore.shared.isLogin() else { return }
guard let info = imInfo else { return }
guard let account = info.accountId else { return }
guard let imToken = info.token else { return }
let option = V2NIMLoginOption()
option.retryCount = 3
NIMSDK.shared().v2LoginService.login(account, token: imToken, option: option) {[weak self] in
// dlog("Log Success")
self?.regetConversationAllUnreadCount()
}
}
func logoutIM(){
NIMSDK.shared().v2LoginService.logout {
dlog("Log out")
} failure: { error in
dlog("Log error: \(error)")
}
}
/// IM
private func clearLogData() {
resetCount()
UserCore.shared.logout()
NotificationCenter.post(name: .presentSignInVc, object: nil, userInfo: nil)
}
func handleReceiveCreate(recentSession: V2NIMConversation) {
guard let message = recentSession.lastMessage else { return }
dealNotice(message: message)
}
///
func handleReveiceUpdate(recentSession: V2NIMConversation) {
guard let message = recentSession.lastMessage else { return }
if let remoteExt = message.serverExtension{
// if let type = remoteExt["type"] as? String{
// if type == MessageTypeStringCustom || type == MessageTypeStringNormal, let session = recentSession.session{
// //
// dealPhoneCallMessage(message: message, session: session)
// }
// }
}else if message.messageType == .MESSAGE_TYPE_CUSTOM {
//
}
if message.messageType == .MESSAGE_TYPE_CUSTOM{
}else{
dealNotice(message: message)
}
}
func dealNotice(message: V2NIMLastMessage) {
// NIMMessage
guard UIApplication.shared.applicationState == .active else {
return
}
let generator = UIImpactFeedbackGenerator(style: .medium)
generator.impactOccurred()
}
}
// loadData
extension IMManager {
/// app
func refreshIMServerInfoConfig() {
guard UserCore.shared.isLogin() else {
return
}
guard retryCount <= 5 else {
//
return
}
IMProvider.request(.getIMAccount, modelType: IMConfigInfo.self) {[weak self] result in
switch result {
case let .success(model):
if model != nil {
self?.imInfo = model
AppCache.cache(key: CacheKey.ImAppkeyInfo.rawValue, value: model)
//self?.setupNIMAppKey()
self?.v2AutoLogin() //
self?.retryCount = 0
}
if String.realEmpty(str: model?.accountId) {
//
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self?.refreshIMServerInfoConfig()
self?.retryCount += 1
}
}
case let .failure(error):
dlog(error)
//
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self?.refreshIMServerInfoConfig()
self?.retryCount += 1
}
}
}
}
public func retryRefreshConfig() {
if NIMSDK.shared().loginManager.isLogined() {
return
}
//
retryCount = 0
refreshIMServerInfoConfig()
}
}
// MARK: 🔴Unread Count
extension IMManager {
public func regetAllUnreadCount(){
regetNoticeUnread()
regetConversationAllUnreadCount()
}
///
func regetNoticeUnread(completion:((Bool, MessageStat?)-> Void)? = nil){
guard UserCore.shared.isLogin() else{
completion?(false, nil)
return
}
IMProvider.request(.messageStat, modelType: MessageStat.self) {[weak self] result in
switch result {
case .success(let model):
self?.noticeStat = model
self?.noticeUnreadCount = model?.unRead ?? 0
completion?(true, model)
case .failure:
break
}
}
}
public func regetConversationAllUnreadCount(){
//
sessionUnreadCount = NIMSDK.shared().v2ConversationService.getTotalUnreadCount()
dlog("🚩IMManager reget unread count:\(sessionUnreadCount)")
}
func registerNIMCount(){
let filter = V2NIMConversationFilter()
filter.conversationTypes = [1]//V2NIMConversationType.CONVERSATION_TYPE_P2P
filter.ignoreMuted = true
NIMSDK.shared().v2ConversationService.subscribeUnreadCount(by: filter)
// Result can see in onUnreadCountChangedByFilter
}
/// 🔥conversationIds
func clearUnreadCountBy(ids:[String]){
guard ids.count > 0 else{ return }
NIMSDK.shared().v2ConversationService.clearUnreadCount(byIds: ids) {[weak self] resuls in
let getUnreadcount = NIMSDK.shared().v2ConversationService.getTotalUnreadCount()
// dlog("💬getTotalUnreadCount: \(getUnreadcount)")
self?.sessionUnreadCount = getUnreadcount
}
}
func clearAllUnread(){
NIMSDK.shared().v2ConversationService.clearTotalUnreadCount {
dlog("Clear all unread message ok✅")
}
}
//
func resetCount() {
notNeedSendNotification = true
sessionUnreadCount = 0
noticeUnreadCount = 0
notNeedSendNotification = false
}
}
extension IMManager: V2NIMLoginListener{
func onLoginStatus(_ status: V2NIMLoginStatus) {
dlog("login status:\(status)")
if status == .LOGIN_STATUS_LOGINED{
//
dlog("login success")
//
registerNIMCount()
regetConversationAllUnreadCount()
regetNoticeUnread()
}
}
func onKickedOffline(_ detail: V2NIMKickedOfflineDetail) {
//
guard UserCore.shared.isLogin() else {
return
}
let reason = "Your account has signed in through another device"
let alert = Alert(title: "Session Interrupted", text: reason)
let action1 = AlertAction(title: "Got it", actionStyle: .confirm) {[weak self] in
self?.clearLogData()
}
alert.addAction(action1)
alert.show()
}
func onLoginFailed(_ error: V2NIMError) {
dlog("onAutoLoginFailed__ \(error.description)")
if error.code == 417 {
guard let _ = imInfo?.accountId, let _ = imInfo?.token else { return }
v2AutoLogin()
}
}
}
// MARK: NIMConversationManagerDelegate
extension IMManager: V2NIMConversationListener{
func onSyncStarted() {
dlog("onSyncStarted")
}
func onSyncFinished() {
dlog("onSyncFinished")
}
func onSyncFailed(_ error: V2NIMError) {
dlog("onSyncFailed:\(error)")
}
func onConversationCreated(_ conversation: V2NIMConversation) {
handleReveiceUpdate(recentSession: conversation)
}
func onConversationChanged(_ conversations: [V2NIMConversation]) {
for per in conversations{
handleReveiceUpdate(recentSession: per)
}
}
func onConversationDeleted(_ conversationIds: [String]) {
dlog("💬🗑onConversationDeleted: \(conversationIds)")
}
func onTotalUnreadCountChanged(_ unreadCount: Int) {
dlog("💬onTotalUnreadCountChanged : \(unreadCount)")
sessionUnreadCount = unreadCount
}
//
func onConversationReadTimeUpdated(_ conversationId: String, readTime: TimeInterval) {
}
func onUnreadCountChanged(by filter: V2NIMConversationFilter, unreadCount: Int) {
// dlog("unreadCount:\(unreadCount)")
}
}
extension IMManager : V2NIMSubscribeListener{
///
func onUserStatusChanged(_ data: [V2NIMUserStatus]) {
// none
dlog("onUserStatusChanged\(data)")
}
}
extension IMManager : V2NIMMessageListener{
func onReceive(_ messages: [V2NIMMessage]) {
// ...
regetConversationAllUnreadCount()
}
}
// MARK: Notification
extension IMManager {
@objc func notiLoginSuccess() {
retryCount = 0
refreshIMServerInfoConfig()
UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .sound, .alert]) { granted, error in
print("Notification granted: \(granted), error: \(String(describing: error))")
}
}
@objc func notiLogout() {
imInfo = nil
resetCount()
removeAllCache()
NIMSDK.shared().v2LoginService.logout { [weak self] in
self?.resetCount()
}
}
}
// public cache
extension IMManager {
public func addCache(sessionID: String) {
if !cacheSessions.contains(sessionID) {
cacheSessions.append(sessionID)
}
}
public func deleteCache(sessionID: String) {
if cacheSessions.contains(sessionID) {
cacheSessions.removeObj(sessionID)
}
}
func findCache(sessionID: String) -> Bool{
return cacheSessions.contains(sessionID)
}
func removeAllCache() {
cacheSessions.removeAll()
}
}
//
extension IMManager {
/// , account IDuserID
static func sendMessage(msgText: String, accID:String) {
if String.realEmpty(str: msgText.trimmed) {
//
return
}
//
let message = IMMessageMaker.msgWithText(msgText)
//
let canSend = IMManager.dealWillSendMessage(message: message)
if canSend {
//
// let session = NIMSession(accID, type: .P2P)
// NIMSDK.shared().chatManager.send(message, to: session) { error in
// //
// #warning("test action")
// }
} else {
//
}
}
//
static public func dealWillSendMessage(message: V2NIMMessage) -> Bool {
// var dict = [String:String]()
// if message.messageType == .text {
// //
// let word = GameDataManager.shared.matchKeywordWith(sourceStr: message.text?.lowercased() ?? "")
// if word.count > 0 {
// dict["keyword"] = word
// dict["type"] = "NOTICE_KEYWORD"
// message.remoteExt = dict
// return false
// }
// }
return true
}
}