Files
hgmj_android/lib/im_manager/im_manager.dart
T
2026-10-08 11:37:25 +07:00

279 lines
8.4 KiB
Dart
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.
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<bool>? _connectServerFuture;
Future<bool>? _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<bool> 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<bool> _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<bool> _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<bool> _refreshTokenAndReconnect() {
if (_refreshTokenFuture != null) return _refreshTokenFuture!;
_refreshTokenFuture = _refreshTokenAndReconnectInternal().whenComplete(() {
_refreshTokenFuture = null;
});
return _refreshTokenFuture!;
}
Future<bool> _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<void> 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<bool> 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,
);
}