import 'dart:async'; import 'package:flutter_mm_core/im_core.dart'; import 'package:get/get.dart'; import 'package:jydj/tools_base/debug_log.dart'; import 'package:jydj/tools_base/global_store/store.dart'; import 'package:jydj/tools_base/loading/loading_alert_widget.dart'; import 'package:jydj/tools_base/toast.dart'; import 'api/im_api_service.dart'; import 'chat_detail_page/chat_detail_page.dart'; import 'im_ad/im_ad_manager.dart'; import 'im_notification.dart'; import 'model/im_message_resp.dart'; import 'model/im_user_model.dart'; class ImManager { // 单例 static final ImManager _ins = ImManager._(); factory ImManager() => _ins; String socketURL = ''; String dynamicConfigDomain = ''; bool _isConnectSuccess = false; // true 连接成功 bool _isConnectIng = false; //true: 正在连接中 Future? _connectServerFuture; Future? _refreshTokenFuture; ImManager._(); String get _imToken => globalStore.meInfo?.imToken ?? ""; int? get imUserId => globalStore.meInfo?.imUserId; bool _initFinish = false; void initImSDK() { if (socketURL.isEmpty || dynamicConfigDomain.isEmpty) { debugLog("IM-initImSDK 参数配置有问题"); return; } if (_initFinish) return; _initFinish = true; ImCore.initSDK( appKey: '', appSecret: '', socketURL: socketURL, dynamicConfigDomain: dynamicConfigDomain, ); ImCore.configureLog( showGeneralLog: true, showHttpLog: true, showWebSocketLog: true, showHeartbeatLog: false, showDatabaseLog: true, showMediaLog: true, ); debugLog('IM-SDK 初始化完成'); _addChatListener(); } // token失效或者断开连接,需要重新连接 Future connectServer() { if (_isConnectSuccess) return Future.value(true); if (_refreshTokenFuture != null) return _refreshTokenFuture!; if (_connectServerFuture != null) return _connectServerFuture!; _connectServerFuture = _connectServer().whenComplete(() { _connectServerFuture = null; }); return _connectServerFuture!; } Future _connectServer() async { try { if (_refreshTokenFuture != null) return _refreshTokenFuture!; if (_isConnectSuccess) return true; if (_isConnectIng) return _isConnectSuccess; _isConnectIng = true; _isConnectSuccess = false; if (imUserId == null) { debugLog('imUserId 为空'); ImUserModel? model = await IMApiService.getImUserInfo(uid: globalStore.meInfo?.uid); globalStore.meInfo?.imUserId = int.tryParse(model?.imUserId ?? ""); } if (_imToken.isEmpty) { debugLog('IM- IM-token 获取失败'); } else { bool ok = await _loginWithToken(_imToken); if (ok) { await ImAdManager.pullImAdsOnce(); _onIMConversation(); } } } catch (e) { debugLog('IM-登录失败:$e'); } finally { _isConnectIng = false; } return _isConnectSuccess; } Future _loginWithToken(String token) async { final ok = await ImCore.linkImRequest(token: token).timeout(const Duration(seconds: 15)); if (!ok) { debugLog('IM-登录失败'); return false; } await ImCore.whenReady.timeout(const Duration(seconds: 15)); _isConnectSuccess = true; debugLog('IM-登录成功'); return true; } Future _refreshTokenAndReconnect() { if (_refreshTokenFuture != null) return _refreshTokenFuture!; _refreshTokenFuture = _refreshTokenAndReconnectInternal().whenComplete(() { _refreshTokenFuture = null; }); return _refreshTokenFuture!; } Future _refreshTokenAndReconnectInternal() async { try { _isConnectSuccess = false; debugLog('IM-token 已失效,开始重新获取'); ImTokenInfo? model = await IMApiService.getImToken(); final token = model?.imToken ?? ''; if (token.isEmpty) { debugLog('IM-token 重新获取失败'); return false; } globalStore.meInfo?.imToken = token; if (model?.imUserId != null) { globalStore.meInfo?.imUserId = model?.imUserId; } final ok = await _loginWithToken(token); debugLog(ok ? 'IM-token 刷新并重连成功' : 'IM-token 刷新后重连失败'); return ok; } catch (e) { debugLog('IM-token 刷新并重连失败:$e'); return false; } } void signOut() async { try { _isConnectSuccess = false; await ImCore.loginOutRequest(); debugLog('IM-退出成功'); } catch (e) { debugLog('IM-退出失败:$e'); } } /// 账号切换/重新登录:先登出旧账号 IM(重置当前用户运行态与本地会话), /// 再用新账号(globalStore.meInfo 已更新)重新连接,避免会话列表残留旧数据。 Future reloginOnAccountSwitch() async { try { _isConnectSuccess = false; _isConnectIng = false; _connectServerFuture = null; _refreshTokenFuture = null; await ImCore.loginOutRequest(); debugLog('IM-切换账号已登出旧会话'); } catch (e) { debugLog('IM-切换账号登出失败:$e'); } await connectServer(); } void _addChatListener() { removeAllListener(); ImCore.addRecvNewMessageListener(_onMessageReceived); ImCore.addSendFailedListener(_onSendFailed); ImCore.addMessageAckFailedListener(_onMessageAckFailed); ImCore.addConversationChangedListener(_onIMConversation); ImCore.addTokenExpiredListener(_onTokenExpired); ImCore.onTokenExpired = _onTokenExpired; } void _onIMConversation() async { ModelConversationListResult sessionRet = await ImCore.getConversations(includeArchivedCount: false); ImMsgProvider().setUnReadStatus(sessionRet); } void _onTokenExpired() { unawaited(_refreshTokenAndReconnect()); } void _onMessageReceived(ModelMessage message) { debugLog('IM-消息收到:${message.content},来自:${message.senderID}'); if (message.isPassthrough) { // 全局透传消息(chatMessageType=400)监听:用于接收后端 AD_NOTIFY 等业务信号。 // 透传不入库、不计未读、不进会话;只在线推。 // 解析 / 去重 / 拉广告逻辑统一在 ImAdManager 里。 ImAdManager.onRecv(message); } else { imEventBus.emit(IMMsgEvent(message, true)); } } void _onSendFailed(ImCoreException exception, ModelMessage? message) { imEventBus.emit(IMMsgEvent(message, false)); debugLog('IM-消息发送失败:${exception.message}'); if (exception.message.contains("当前 IM 未连接")) { _isConnectSuccess = false; connectServer(); } } void _onMessageAckFailed(ImCoreException exception, ModelMessage? message) { debugLog('IM-消息 ACK 失败:${exception.message}'); _isConnectSuccess = false; connectServer(); } Future sendMessage(ModelCustomMessage message) async { try { final ok = await ImCore.sendMessage(msg: message); if (ok) { debugLog('已请求发送消息'); return true; } else { debugLog('发送消息失败'); return false; } } catch (e) { debugLog('发送消息失败:$e'); return false; } } void removeAllListener() { ImCore.removeRecvNewMessageListener(_onMessageReceived); ImCore.removeSendFailedListener(_onSendFailed); ImCore.removeMessageAckFailedListener(_onMessageAckFailed); ImCore.removeConversationChangedListener(_onIMConversation); ImCore.removeTokenExpiredListener(_onTokenExpired); } } Future jumpToChatDetail(IMSessionModel model) async { // 优先用 Session.imUserId(SDK conversation.targetId,已经是 IM userId) // 没有再回退到 ensureFriend 转换 int peerImUserId = model.imUserId ?? 0; if (peerImUserId <= 0) { final peerId = model.peerId ?? 0; if (peerId <= 0) return; try { LoadingAlertWidget.show(); final result = await IMApiService.ensureFriend(peerId); peerImUserId = int.tryParse(result.peerImUserId ?? '') ?? 0; LoadingAlertWidget.cancel(); } catch (_) { LoadingAlertWidget.cancel(); showToast('无法打开聊天,请稍后再试'); return; } } if (peerImUserId <= 0) { showToast('无法打开聊天,请稍后再试'); return; } return Get.to( ChatDetailPage( imUserId: peerImUserId, userName: model.nickName, uid: model.peerId, peerAvatar: model.avatar, ), preventDuplicates: false, ); }