import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_openim_sdk/flutter_openim_sdk.dart'; import 'package:get/get.dart'; import 'package:openim_common/openim_common.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:openim_live/openim_live.dart'; import '../../company/feature_flags.dart'; import '../../company/rtc/call_permissions.dart'; import '../../company/rtc/call_session_guard.dart'; import '../../company/rtc/rtc_token_api.dart'; import '../im_callback.dart'; class IMController extends GetxController with IMCallback, OpenIMLive { late Rx userInfo; late String atAllTag; final _rtcTokenApi = RtcTokenApi(); final callGuard = CallSessionGuard(); @override void onClose() { callGuard.dispose(); super.close(); onCloseLive(); super.onClose(); } @override Future getRtcCertificate(String roomID, String userID) { final token = DataSp.imToken; if (token == null || token.isEmpty) { return Future.error(StateError('登录状态已失效,请重新登录')); } return _rtcTokenApi.getToken(room: roomID, identity: userID, authToken: token); } @override void onLiveSessionClosed() { callGuard.dispose(); } @override void onLiveConnected() { callGuard.markConnected(); } @override void receiveNewInvitation(SignalingInfo info) { if (!FeatureFlags.livekitCall) return; final invitation = info.invitation; final roomID = invitation?.roomID ?? ''; if (invitation?.mediaType != 'audio' || invitation?.sessionType != ConversationType.single) { onTapReject(info); return; } if (!callGuard.acceptSignaling(customType: CustomMessageType.callingInvite, roomID: roomID)) { return; } if (isBusy || callGuard.isBusy) { onTapReject(info); return; } if (callGuard.startIncoming(roomID) != null) { onTapReject(info); return; } super.receiveNewInvitation(info); } @override void call({ required CallObj callObj, required CallType callType, CallState callState = CallState.call, String? roomID, String? inviterUserID, required List inviteeUserIDList, String? groupID, SignalingCertificate? credentials, }) async { if (!FeatureFlags.livekitCall) return; if (callType != CallType.audio || callObj != CallObj.single) { IMViews.showToast('暂只支持一对一语音通话'); return; } if (isBusy || callGuard.isBusy) { IMViews.showToast(StrRes.callingBusy); return; } final granted = await CallPermissions.ensureMicrophone(); if (!granted) return; callGuard.startOutgoing(roomID ?? ''); super.call( callObj: callObj, callType: callType, callState: callState, roomID: roomID, inviterUserID: inviterUserID, inviteeUserIDList: inviteeUserIDList, groupID: groupID, credentials: credentials, ); } @override Future onTapPickup(SignalingInfo signaling) async { final granted = await CallPermissions.ensureMicrophone(); if (!granted) { await onTapReject(signaling); return Future.error(StateError('microphone denied')); } return super.onTapPickup(signaling); } @override void onInit() async { super.onInit(); onInitLive(); WidgetsBinding.instance.addPostFrameCallback((_) => initOpenIM()); } void initOpenIM() async { final initialized = await OpenIM.iMManager.initSDK( platformID: IMUtils.getPlatform(), apiAddr: Config.imApiUrl, wsAddr: Config.imWsUrl, dataDir: Config.cachePath, logLevel: Config.logLevel, logFilePath: Config.cachePath, listener: OnConnectListener( onConnecting: () { imSdkStatus(IMSdkStatus.connecting); }, onConnectFailed: (code, error) { imSdkStatus(IMSdkStatus.connectionFailed); }, onConnectSuccess: () { imSdkStatus(IMSdkStatus.connectionSucceeded); }, onKickedOffline: kickedOffline, onUserTokenExpired: kickedOffline, onUserTokenInvalid: userTokenInvalid, ), ); OpenIM.iMManager ..setUploadLogsListener(OnUploadLogsListener(onUploadProgress: uploadLogsProgress)) ..userManager.setUserListener(OnUserListener( onSelfInfoUpdated: (u) { selfInfoUpdated(u); userInfo.update((val) { val?.nickname = u.nickname; val?.faceURL = u.faceURL; val?.remark = u.remark; val?.ex = u.ex; val?.globalRecvMsgOpt = u.globalRecvMsgOpt; }); }, onUserStatusChanged: userStausChanged)) ..messageManager.setAdvancedMsgListener(OnAdvancedMsgListener( onRecvC2CReadReceipt: recvC2CMessageReadReceipt, onRecvNewMessage: recvNewMessage, onNewRecvMessageRevoked: recvMessageRevoked, onRecvOfflineNewMessage: recvOfflineMessage, onRecvOnlineOnlyMessage: (msg) { if (msg.isCustomType) { final data = msg.customElem!.data; final map = jsonDecode(data!); final customType = map['customType']; if (customType == CustomMessageType.callingInvite || customType == CustomMessageType.callingAccept || customType == CustomMessageType.callingReject || customType == CustomMessageType.callingCancel || customType == CustomMessageType.callingHungup) { if (!FeatureFlags.livekitCall) return; final signaling = SignalingInfo(invitation: InvitationInfo.fromJson(map['data'])); signaling.userID = signaling.invitation?.inviterUserID; final roomID = signaling.invitation?.roomID ?? ''; if (customType != CustomMessageType.callingInvite && !callGuard.acceptSignaling(customType: customType as int, roomID: roomID)) { return; } switch (customType) { case CustomMessageType.callingInvite: receiveNewInvitation(signaling); break; case CustomMessageType.callingAccept: inviteeAccepted(signaling); break; case CustomMessageType.callingReject: inviteeRejected(signaling); break; case CustomMessageType.callingCancel: invitationCancelled(signaling); break; case CustomMessageType.callingHungup: beHangup(signaling); break; } } } }, )) ..messageManager.setMsgSendProgressListener(OnMsgSendProgressListener( onProgress: progressCallback, )) ..messageManager.setCustomBusinessListener(OnCustomBusinessListener( onRecvCustomBusinessMessage: recvCustomBusinessMessage, )) ..friendshipManager.setFriendshipListener(OnFriendshipListener( onBlackAdded: blacklistAdded, onBlackDeleted: blacklistDeleted, onFriendApplicationAccepted: friendApplicationAccepted, onFriendApplicationAdded: friendApplicationAdded, onFriendApplicationDeleted: friendApplicationDeleted, onFriendApplicationRejected: friendApplicationRejected, onFriendInfoChanged: friendInfoChanged, onFriendAdded: friendAdded, onFriendDeleted: friendDeleted, )) ..conversationManager.setConversationListener(OnConversationListener( onConversationChanged: conversationChanged, onNewConversation: newConversation, onTotalUnreadMessageCountChanged: totalUnreadMsgCountChanged, onInputStatusChanged: inputStateChanged, onSyncServerFailed: (reInstall) { imSdkStatus(IMSdkStatus.syncFailed, reInstall: reInstall ?? false); }, onSyncServerFinish: (reInstall) { imSdkStatus(IMSdkStatus.syncEnded, reInstall: reInstall ?? false); if (Platform.isAndroid) { Permissions.request([Permission.systemAlertWindow]); } }, onSyncServerStart: (reInstall) { imSdkStatus(IMSdkStatus.syncStart, reInstall: reInstall ?? false); }, onSyncServerProgress: (progress) { imSdkStatus(IMSdkStatus.syncProgress, progress: progress); })) ..groupManager.setGroupListener(OnGroupListener( onGroupApplicationAccepted: groupApplicationAccepted, onGroupApplicationAdded: groupApplicationAdded, onGroupApplicationDeleted: groupApplicationDeleted, onGroupApplicationRejected: groupApplicationRejected, onGroupInfoChanged: groupInfoChanged, onGroupMemberAdded: groupMemberAdded, onGroupMemberDeleted: groupMemberDeleted, onGroupMemberInfoChanged: groupMemberInfoChanged, onJoinedGroupAdded: joinedGroupAdded, onJoinedGroupDeleted: joinedGroupDeleted, )); initializedSubject.sink.add(initialized); } Future login(String userID, String token) async { try { var user = await OpenIM.iMManager.login( userID: userID, token: token, defaultValue: () async => UserInfo(userID: userID), ); ApiService().setToken(token); userInfo = UserFullInfo.fromJson(user.toJson()).obs; _queryMyFullInfo(); _queryAtAllTag(); } catch (e, s) { Logger.print('e: $e s:$s'); await _handleLoginRepeatError(e); return Future.error(e, s); } } Future logout() { return OpenIM.iMManager.logout(); } void _queryAtAllTag() async { atAllTag = OpenIM.iMManager.conversationManager.atAllTag; } void _queryMyFullInfo() async { final data = await Apis.queryMyFullInfo(); if (data is UserFullInfo) { userInfo.update((val) { val?.allowAddFriend = data.allowAddFriend; val?.allowBeep = data.allowBeep; val?.allowVibration = data.allowVibration; val?.nickname = data.nickname; val?.faceURL = data.faceURL; val?.phoneNumber = data.phoneNumber; val?.email = data.email; val?.birth = data.birth; val?.gender = data.gender; }); } } _handleLoginRepeatError(e) async { if (e is PlatformException && (e.code == "13002" || e.code == '1507')) { await logout(); await DataSp.removeLoginCertificate(); } } }