Initial commit: 91dj android client dev_1.0.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,278 @@
|
||||
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,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user