Files
tongxunruanjian/mobile-next/lib/pages/chat/chat_logic.dart
T
编码工程师andmultica-agent 8046ec7483 feat(mobile-next): 导入官方 openim-flutter-demo 固定基线 3.8.3-patch.3 并完成双构建
- 上游: github.com/OpenIMSDK/openim-flutter-demo tag 3.8.3-patch.3
  commit b3dfdb1e8aaeaaf6f0793e10cadd20d5c184a31f
- 并行目录 mobile-next/ 原样导入(含 LICENSE),不覆盖现网 mobile/
- 锁定 Flutter 3.24.5 / Dart 3.5.4 / JDK 17 / Gradle 7.6.3 / AGP 7.3.1
- 实测 Android debug 与 release 构建均成功(见 docs/mobile-next-baseline-import.md)
- 本提交可单独回退:git revert 1db1229(重写前)

Co-authored-by: multica-agent <github@multica.ai>
2026-08-19 15:24:33 +08:00

1834 lines
54 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 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:common_utils/common_utils.dart';
import 'package:file_picker/file_picker.dart';
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:just_audio/just_audio.dart';
import 'package:mime/mime.dart';
import 'package:openim_common/openim_common.dart';
import 'package:pull_to_refresh_new/pull_to_refresh.dart';
import 'package:rxdart/rxdart.dart';
import 'package:sprintf/sprintf.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:video_compress/video_compress.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
import 'package:wechat_camera_picker/wechat_camera_picker.dart';
import 'package:openim_live/openim_live.dart';
import '../../core/controller/app_controller.dart';
import '../../core/controller/im_controller.dart';
import '../../core/im_callback.dart';
import '../../routes/app_navigator.dart';
import '../contacts/select_contacts/select_contacts_logic.dart';
import '../conversation/conversation_logic.dart';
import 'group_setup/group_member_list/group_member_list_logic.dart';
class ChatLogic extends SuperController {
final imLogic = Get.find<IMController>();
final appLogic = Get.find<AppController>();
final conversationLogic = Get.find<ConversationLogic>();
final cacheLogic = Get.find<CacheController>();
final downloadLogic = Get.find<DownloadController>();
final inputCtrl = TextEditingController();
final focusNode = FocusNode();
final scrollController = ScrollController();
final refreshController = RefreshController();
bool playOnce = false; // 点击的当前视频只能播放一次
final forceCloseToolbox = PublishSubject<bool>();
final forceCloseMenuSub = PublishSubject<bool>();
final sendStatusSub = PublishSubject<MsgStreamEv<bool>>();
late ConversationInfo conversationInfo;
Message? searchMessage;
final nickname = ''.obs;
final faceUrl = ''.obs;
Timer? typingTimer;
final typing = false.obs;
Timer? _debounce;
Message? quoteMsg;
final messageList = <Message>[].obs;
final tempMessages = <Message>[]; // 临时存放消息体,例如图片消息
var _lastCursorIndex = -1;
final onlineStatus = false.obs;
final onlineStatusDesc = ''.obs;
Timer? onlineStatusTimer;
final favoriteList = <String>[].obs;
final scaleFactor = Config.textScaleFactor.obs;
final background = "".obs;
final memberUpdateInfoMap = <String, GroupMembersInfo>{};
final groupMessageReadMembers = <String, List<String>>{};
final groupMutedStatus = 0.obs;
final groupMemberRoleLevel = 1.obs;
final muteEndTime = 0.obs;
GroupInfo? groupInfo;
GroupMembersInfo? groupMembersInfo;
List<GroupMembersInfo> ownerAndAdmin = [];
final isInGroup = true.obs;
final memberCount = 0.obs;
final privateMessageList = <Message>[];
final isInBlacklist = false.obs;
final _audioPlayer = AudioPlayer();
final _currentPlayClientMsgID = "".obs;
final isShowPopMenu = false.obs;
final scrollingCacheMessageList = <Message>[];
final announcement = ''.obs;
late StreamSubscription conversationSub;
late StreamSubscription memberAddSub;
late StreamSubscription memberDelSub;
late StreamSubscription joinedGroupAddedSub;
late StreamSubscription joinedGroupDeletedSub;
late StreamSubscription memberInfoChangedSub;
late StreamSubscription groupInfoUpdatedSub;
late StreamSubscription friendInfoChangedSub;
StreamSubscription? userStatusChangedSub;
StreamSubscription? selfInfoUpdatedSub;
late StreamSubscription connectionSub;
final syncStatus = IMSdkStatus.syncEnded.obs;
int? lastMinSeq;
final showCallingMember = false.obs;
bool _isReceivedMessageWhenSyncing = false;
bool _isStartSyncing = false;
bool _isFirstLoad = true;
final copyTextMap = <String?, String?>{};
final revokedTextMessage = <String, String>{};
String? groupOwnerID;
final _pageSize = 40;
RTCBridge? get rtcBridge => PackageBridge.rtcBridge;
bool get rtcIsBusy => rtcBridge?.hasConnection == true;
String? get userID => conversationInfo.userID;
String? get groupID => conversationInfo.groupID;
bool get isSingleChat => null != userID && userID!.trim().isNotEmpty;
bool get isGroupChat => null != groupID && groupID!.trim().isNotEmpty;
String get memberStr => isSingleChat ? "" : "($memberCount)";
String? get senderName => isSingleChat ? OpenIM.iMManager.userInfo.nickname : groupMembersInfo?.nickname;
bool get isAdminOrOwner =>
groupMemberRoleLevel.value == GroupRoleLevel.admin || groupMemberRoleLevel.value == GroupRoleLevel.owner;
final directionalUsers = <GroupMembersInfo>[].obs;
bool isCurrentChat(Message message) {
var senderId = message.sendID;
var receiverId = message.recvID;
var groupId = message.groupID;
var isCurSingleChat = message.isSingleChat &&
isSingleChat &&
(senderId == userID || senderId == OpenIM.iMManager.userID && receiverId == userID);
var isCurGroupChat = message.isGroupChat && isGroupChat && groupID == groupId;
return isCurSingleChat || isCurGroupChat;
}
void scrollBottom() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
scrollController.jumpTo(0);
});
}
Future<List<Message>> searchMediaMessage() async {
final messageList = await OpenIM.iMManager.messageManager.searchLocalMessages(
conversationID: conversationInfo.conversationID,
messageTypeList: [MessageType.picture, MessageType.video],
count: 500);
return messageList.searchResultItems?.first.messageList?.reversed.toList() ?? [];
}
@override
void onReady() {
_readDraftText();
_queryUserOnlineStatus();
_resetGroupAtType();
_getInputState();
_clearUnreadCount();
scrollController.addListener(() {
focusNode.unfocus();
});
super.onReady();
}
@override
void onInit() {
var arguments = Get.arguments;
conversationInfo = arguments['conversationInfo'];
searchMessage = arguments['searchMessage'];
nickname.value = conversationInfo.showName ?? '';
faceUrl.value = conversationInfo.faceURL ?? '';
_initChatConfig();
_initPlayListener();
_setSdkSyncDataListener();
conversationSub = imLogic.conversationChangedSubject.listen((value) {
final obj = value.firstWhereOrNull((e) => e.conversationID == conversationInfo.conversationID);
if (obj != null) {
conversationInfo = obj;
}
});
imLogic.onRecvNewMessage = (Message message) async {
if (isCurrentChat(message)) {
if (message.contentType == MessageType.typing) {
} else {
if (!messageList.contains(message) && !scrollingCacheMessageList.contains(message)) {
_isReceivedMessageWhenSyncing = true;
if (isShowPopMenu.value || scrollController.offset != 0) {
scrollingCacheMessageList.add(message);
} else {
messageList.add(message);
scrollBottom();
}
}
}
}
};
imLogic.onRecvMessageRevoked = (RevokedInfo info) {
var message = messageList.firstWhereOrNull((e) => e.clientMsgID == info.clientMsgID);
message?.notificationElem = NotificationElem(detail: jsonEncode(info));
message?.contentType = MessageType.revokeMessageNotification;
if (null != message) {
messageList.refresh();
}
};
imLogic.onRecvC2CReadReceipt = (List<ReadReceiptInfo> list) {
try {
for (var readInfo in list) {
if (readInfo.userID == userID) {
for (var e in messageList) {
if (readInfo.msgIDList?.contains(e.clientMsgID) == true) {
e.isRead = true;
e.hasReadTime = _timestamp;
}
}
}
}
messageList.refresh();
} catch (e) {}
};
joinedGroupAddedSub = imLogic.joinedGroupAddedSubject.listen((event) {
if (event.groupID == groupID) {
isInGroup.value = true;
_queryGroupInfo();
}
});
joinedGroupDeletedSub = imLogic.joinedGroupDeletedSubject.listen((event) {
if (event.groupID == groupID) {
isInGroup.value = false;
inputCtrl.clear();
}
});
memberAddSub = imLogic.memberAddedSubject.listen((info) {
var groupId = info.groupID;
if (groupId == groupID) {
_putMemberInfo([info]);
}
});
memberDelSub = imLogic.memberDeletedSubject.listen((info) {
if (info.groupID == groupID && info.userID == OpenIM.iMManager.userID) {
isInGroup.value = false;
inputCtrl.clear();
}
});
memberInfoChangedSub = imLogic.memberInfoChangedSubject.listen((info) {
if (info.groupID == groupID) {
if (info.userID == OpenIM.iMManager.userID) {
muteEndTime.value = info.muteEndTime ?? 0;
groupMemberRoleLevel.value = info.roleLevel ?? GroupRoleLevel.member;
groupMembersInfo = info;
();
}
_putMemberInfo([info]);
final index = ownerAndAdmin.indexWhere((element) => element.userID == info.userID);
if (info.roleLevel == GroupRoleLevel.member) {
if (index > -1) {
ownerAndAdmin.removeAt(index);
}
} else if (info.roleLevel == GroupRoleLevel.admin || info.roleLevel == GroupRoleLevel.owner) {
if (index == -1) {
ownerAndAdmin.add(info);
} else {
ownerAndAdmin[index] = info;
}
}
for (var msg in messageList) {
if (msg.sendID == info.userID) {
if (msg.isNotificationType) {
final map = json.decode(msg.notificationElem!.detail!);
final ntf = GroupNotification.fromJson(map);
ntf.opUser?.nickname = info.nickname;
ntf.opUser?.faceURL = info.faceURL;
msg.notificationElem?.detail = jsonEncode(ntf);
} else {
msg.senderFaceUrl = info.faceURL;
msg.senderNickname = info.nickname;
}
}
}
messageList.refresh();
}
});
groupInfoUpdatedSub = imLogic.groupInfoUpdatedSubject.listen((value) {
if (groupID == value.groupID) {
groupInfo = value;
nickname.value = value.groupName ?? '';
faceUrl.value = value.faceURL ?? '';
groupMutedStatus.value = value.status ?? 0;
memberCount.value = value.memberCount ?? 0;
}
});
friendInfoChangedSub = imLogic.friendInfoChangedSubject.listen((value) {
if (userID == value.userID) {
nickname.value = value.getShowName();
faceUrl.value = value.faceURL ?? '';
for (var msg in messageList) {
if (msg.sendID == value.userID) {
msg.senderFaceUrl = value.faceURL;
msg.senderNickname = value.nickname;
}
}
messageList.refresh();
}
});
selfInfoUpdatedSub = imLogic.selfInfoUpdatedSubject.listen((value) {
for (var msg in messageList) {
if (msg.sendID == value.userID) {
msg.senderFaceUrl = value.faceURL;
msg.senderNickname = value.nickname;
}
}
messageList.refresh();
});
inputCtrl.addListener(() {
sendTypingMsg(focus: true);
if (_debounce?.isActive ?? false) _debounce?.cancel();
_debounce = Timer(1.seconds, () {
sendTypingMsg(focus: false);
});
});
focusNode.addListener(() {
_lastCursorIndex = inputCtrl.selection.start;
focusNodeChanged(focusNode.hasFocus);
});
imLogic.onSignalingMessage = (value) {
if (value.userID == userID) {
messageList.add(value.message);
scrollBottom();
}
};
imLogic.inputStateChangedSubject.listen((value) {
if (value.conversationID == conversationInfo.conversationID && value.userID == userID) {
typing.value = value.platformIDs?.isNotEmpty == true;
}
});
super.onInit();
}
Future chatSetup() => isSingleChat
? AppNavigator.startChatSetup(conversationInfo: conversationInfo)
: AppNavigator.startGroupChatSetup(conversationInfo: conversationInfo);
void _putMemberInfo(List<GroupMembersInfo>? list) {
list?.forEach((member) {
memberUpdateInfoMap[member.userID!] = member;
});
messageList.refresh();
}
void sendTextMsg() async {
var content = IMUtils.safeTrim(inputCtrl.text);
if (content.isEmpty) return;
Message message = await OpenIM.iMManager.messageManager.createTextMessage(
text: content,
);
_sendMessage(message);
}
Future sendPicture({required String path, bool sendNow = true}) async {
final file = await IMUtils.compressImageAndGetFile(File(path));
var message = await OpenIM.iMManager.messageManager.createImageMessageFromFullPath(
imagePath: file!.path,
);
if (sendNow) {
return _sendMessage(message);
} else {
messageList.add(message);
tempMessages.add(message);
}
}
void sendVoice(int duration, String path) async {
var message = await OpenIM.iMManager.messageManager.createSoundMessageFromFullPath(
soundPath: path,
duration: duration,
);
_sendMessage(message);
}
Future sendVideo(
{required String videoPath,
required String mimeType,
required int duration,
required String thumbnailPath,
bool sendNow = true}) async {
var d = duration > 1000.0 ? duration / 1000.0 : duration;
var message = await OpenIM.iMManager.messageManager.createVideoMessageFromFullPath(
videoPath: videoPath,
videoType: mimeType,
duration: d.toInt(),
snapshotPath: thumbnailPath,
);
if (sendNow) {
return _sendMessage(message);
} else {
messageList.add(message);
tempMessages.add(message);
}
}
void sendFile({required String filePath, required String fileName}) async {
var message = await OpenIM.iMManager.messageManager.createFileMessageFromFullPath(
filePath: filePath,
fileName: fileName,
);
_sendMessage(message);
}
void sendLocation({
required dynamic location,
}) async {
var message = await OpenIM.iMManager.messageManager.createLocationMessage(
latitude: location['latitude'],
longitude: location['longitude'],
description: location['description'],
);
_sendMessage(message);
}
sendForwardRemarkMsg(
String content, {
String? userId,
String? groupId,
}) async {
final message = await OpenIM.iMManager.messageManager.createTextMessage(
text: content,
);
_sendMessage(message, userId: userId, groupId: groupId);
}
sendForwardMsg(
Message originalMessage, {
String? userId,
String? groupId,
}) async {
var message = await OpenIM.iMManager.messageManager.createForwardMessage(
message: originalMessage,
);
_sendMessage(message, userId: userId, groupId: groupId);
}
void sendTypingMsg({bool focus = false}) async {
if (isSingleChat) {
OpenIM.iMManager.conversationManager
.changeInputStates(conversationID: conversationInfo.conversationID, focus: focus);
}
}
void sendCarte({
required String userID,
String? nickname,
String? faceURL,
}) async {
var message = await OpenIM.iMManager.messageManager.createCardMessage(
userID: userID,
nickname: nickname!,
faceURL: faceURL,
);
_sendMessage(message);
}
void sendCustomMsg({
required String data,
required String extension,
required String description,
}) async {
var message = await OpenIM.iMManager.messageManager.createCustomMessage(
data: data,
extension: extension,
description: description,
);
_sendMessage(message);
}
Future _sendMessage(
Message message, {
String? userId,
String? groupId,
bool addToUI = true,
}) {
log('send : ${json.encode(message)}');
userId = IMUtils.emptyStrToNull(userId);
groupId = IMUtils.emptyStrToNull(groupId);
if (null == userId && null == groupId ||
userId == userID && userId != null ||
groupId == groupID && groupId != null) {
if (addToUI) {
messageList.add(message);
scrollBottom();
}
}
Logger.print('uid:$userID userId:$userId gid:$groupID groupId:$groupId');
_reset(message);
bool useOuterValue = null != userId || null != groupId;
final recvUserID = useOuterValue ? userId : userID;
message.recvID = recvUserID;
return OpenIM.iMManager.messageManager
.sendMessage(
message: message,
userID: recvUserID,
groupID: useOuterValue ? groupId : groupID,
offlinePushInfo: Config.offlinePushInfo,
)
.then((value) => _sendSucceeded(message, value))
.catchError((error, _) => _senFailed(message, groupId, userId, error, _))
.whenComplete(() => _completed());
}
void _sendSucceeded(Message oldMsg, Message newMsg) {
Logger.print('message send success----');
oldMsg.update(newMsg);
sendStatusSub.addSafely(MsgStreamEv<bool>(
id: oldMsg.clientMsgID!,
value: true,
));
}
void _senFailed(Message message, String? groupId, String? userId, error, stack) async {
Logger.print('message send failed userID: $userId groupId:$groupId, catch error :$error $stack');
message.status = MessageStatus.failed;
sendStatusSub.addSafely(MsgStreamEv<bool>(
id: message.clientMsgID!,
value: false,
));
if (error is PlatformException) {
int code = int.tryParse(error.code) ?? 0;
if (isSingleChat) {
int? customType;
if (code == SDKErrorCode.hasBeenBlocked) {
customType = CustomMessageType.blockedByFriend;
} else if (code == SDKErrorCode.notFriend) {
customType = CustomMessageType.deletedByFriend;
}
if (null != customType) {
final hintMessage = (await OpenIM.iMManager.messageManager.createFailedHintMessage(type: customType))
..status = 2
..isRead = true;
if (userId != null) {
if (userId == userID) {
messageList.add(hintMessage);
}
} else {
messageList.add(hintMessage);
}
OpenIM.iMManager.messageManager.insertSingleMessageToLocalStorage(
message: hintMessage,
receiverID: userId ?? userID,
senderID: OpenIM.iMManager.userID,
);
}
} else {
if ((code == SDKErrorCode.userIsNotInGroup || code == SDKErrorCode.groupDisbanded) && null == groupId) {
final status = groupInfo?.status;
final hintMessage = (await OpenIM.iMManager.messageManager.createFailedHintMessage(
type: status == 2 ? CustomMessageType.groupDisbanded : CustomMessageType.removedFromGroup))
..status = 2
..isRead = true;
messageList.add(hintMessage);
OpenIM.iMManager.messageManager.insertGroupMessageToLocalStorage(
message: hintMessage,
groupID: groupID,
senderID: OpenIM.iMManager.userID,
);
}
}
}
}
void _reset(Message message) {
if (message.contentType == MessageType.text) {
inputCtrl.clear();
}
}
void _completed() {
messageList.refresh();
}
void deleteMsg(Message message) async {
LoadingView.singleton.wrap(asyncFunction: () => _deleteMessage(message));
}
_deleteMessage(Message message) async {
try {
await OpenIM.iMManager.messageManager
.deleteMessageFromLocalAndSvr(
conversationID: conversationInfo.conversationID,
clientMsgID: message.clientMsgID!,
)
.then((value) => privateMessageList.remove(message))
.then((value) => messageList.remove(message));
} catch (e) {
await OpenIM.iMManager.messageManager
.deleteMessageFromLocalStorage(
conversationID: conversationInfo.conversationID,
clientMsgID: message.clientMsgID!,
)
.then((value) => privateMessageList.remove(message))
.then((value) => messageList.remove(message));
}
}
void forward(Message? message) async {
final result = await AppNavigator.startSelectContacts(
action: SelAction.forward,
ex: null != message ? IMUtils.parseMsg(message) : null,
);
if (null != result) {
final checkedList = result['checkedList'];
for (var info in checkedList) {
final userID = IMUtils.convertCheckedToUserID(info);
final groupID = IMUtils.convertCheckedToGroupID(info);
if (null != message) {
sendForwardMsg(message, userId: userID, groupId: groupID);
}
}
}
}
void markMessageAsRead(Message message, bool visible) async {
Logger.print('markMessageAsRead: ${message.textElem?.content}, $visible');
if (visible && message.contentType! < 1000 && message.contentType! != MessageType.voice) {
var data = IMUtils.parseCustomMessage(message);
if (null != data && data['viewType'] == CustomMessageType.call) {
Logger.print('markMessageAsRead: call message $data');
return;
}
_markMessageAsRead(message);
}
}
_markMessageAsRead(Message message) async {
if (!message.isRead! && message.sendID != OpenIM.iMManager.userID) {
try {
Logger.print('mark conversation message as read${message.clientMsgID!} ${message.isRead}');
await OpenIM.iMManager.conversationManager
.markConversationMessageAsRead(conversationID: conversationInfo.conversationID);
} catch (e) {
Logger.print('failed to send group message read receipt ${message.clientMsgID} ${message.isRead}');
} finally {
message.isRead = true;
message.hasReadTime = _timestamp;
messageList.refresh();
}
}
}
_clearUnreadCount() {
if (conversationInfo.unreadCount > 0) {
OpenIM.iMManager.conversationManager
.markConversationMessageAsRead(conversationID: conversationInfo.conversationID);
}
}
void _getInputState() async {
if (conversationInfo.isSingleChat) {
final result =
await OpenIM.iMManager.conversationManager.getInputStates(conversationInfo.conversationID, userID!);
typing.value = result?.isNotEmpty == true;
}
}
void _changeInputStatus(bool focus) async {
if (conversationInfo.isSingleChat) {
await OpenIM.iMManager.conversationManager
.changeInputStates(conversationID: conversationInfo.conversationID, focus: focus);
}
}
void closeToolbox() {
forceCloseToolbox.addSafely(true);
}
void onTapLocation() async {
var location = await Get.to(
const ChatWebViewMap(host: Config.locationHost, webKey: Config.webKey, webServerKey: Config.webServerKey),
transition: Transition.cupertino,
popGesture: true,
);
if (null != location) {
Logger.print(location);
sendLocation(location: location);
}
}
void onTapAlbum() async {
final List<AssetEntity>? assets = await AssetPicker.pickAssets(Get.context!,
pickerConfig: AssetPickerConfig(
sortPathsByModifiedDate: true,
filterOptions: PMFilter.defaultValue(containsPathModified: true),
selectPredicate: (_, entity, isSelected) async {
if (entity.type == AssetType.image) {
if (await allowSendImageType(entity)) {
return true;
}
IMViews.showToast(StrRes.supportsTypeHint);
return false;
}
if (entity.videoDuration > const Duration(seconds: 5 * 60)) {
IMViews.showToast(sprintf(StrRes.selectVideoLimit, [5]) + StrRes.minute);
return false;
}
return true;
}));
if (null != assets) {
for (var asset in assets) {
await _handleAssets(asset, sendNow: false);
}
for (var msg in tempMessages) {
await _sendMessage(msg, addToUI: false);
}
tempMessages.clear();
}
}
void onTapCamera() async {
final AssetEntity? entity = await CameraPicker.pickFromCamera(
Get.context!,
locale: Get.locale,
pickerConfig: CameraPickerConfig(
enableAudio: true,
enableRecording: true,
enableScaledPreview: false,
maximumRecordingDuration: 60.seconds,
onMinimumRecordDurationNotMet: () {
IMViews.showToast(StrRes.tapTooShort);
},
),
);
_handleAssets(entity);
}
void onTapFile() async {
await FilePicker.platform.clearTemporaryFiles();
FilePickerResult? result = await FilePicker.platform.pickFiles(
allowMultiple: true,
);
if (result != null) {
for (var file in result.files) {
String? mimeType = lookupMimeType(file.name);
if (mimeType != null) {
if (IMUtils.allowImageType(mimeType)) {
sendPicture(path: file.path!);
continue;
} else if (mimeType.contains('video/')) {
try {
final videoPath = file.path!;
final mediaInfo = await VideoCompress.getMediaInfo(videoPath);
var thumbnailFile = await VideoCompress.getFileThumbnail(
videoPath,
quality: 60,
);
sendVideo(
videoPath: videoPath,
mimeType: mimeType,
duration: mediaInfo.duration!.toInt(),
thumbnailPath: thumbnailFile.path,
);
continue;
} catch (e, s) {
Logger.print('e :$e s:$s');
}
}
}
sendFile(filePath: file.path!, fileName: file.name);
}
} else {}
}
Future<bool> allowSendImageType(AssetEntity entity) async {
final mimeType = await entity.mimeTypeAsync;
return IMUtils.allowImageType(mimeType);
}
void onTapCarte() async {
var result = await AppNavigator.startSelectContacts(
action: SelAction.carte,
);
if (result is UserInfo || result is FriendInfo) {
sendCarte(
userID: result.userID!,
nickname: result.nickname,
faceURL: result.faceURL,
);
}
}
Future _handleAssets(AssetEntity? asset, {bool sendNow = true}) async {
if (null != asset) {
Logger.print('--------assets type-----${asset.type} create time: ${asset.createDateTime}');
final originalFile = await asset.file;
final originalPath = originalFile!.path;
var path = originalPath.toLowerCase().endsWith('.gif') ? originalPath : originalFile.path;
Logger.print('--------assets path-----$path');
switch (asset.type) {
case AssetType.image:
await sendPicture(path: path, sendNow: sendNow);
break;
case AssetType.video:
var thumbnailFile = await IMUtils.getVideoThumbnail(File(path));
LoadingView.singleton.show();
final file = await IMUtils.compressVideoAndGetFile(File(path));
LoadingView.singleton.dismiss();
await sendVideo(
videoPath: file!.path,
mimeType: asset.mimeType ?? IMUtils.getMediaType(path) ?? '',
duration: asset.duration,
thumbnailPath: thumbnailFile.path,
sendNow: sendNow,
);
break;
default:
break;
}
if (Platform.isIOS) {
originalFile.deleteSync();
}
}
}
void onTapDirectionalMessage() async {
if (null != groupInfo) {
final list = await AppNavigator.startGroupMemberList(
groupInfo: groupInfo!,
opType: GroupMemberOpType.call,
);
if (list is List<GroupMembersInfo>) {
directionalUsers.assignAll(list);
}
}
}
TextSpan? directionalText() {
if (directionalUsers.isNotEmpty) {
final temp = <TextSpan>[];
for (var e in directionalUsers) {
final r = TextSpan(
text: '${e.nickname ?? ''} ${directionalUsers.last == e ? '' : ','} ',
style: Styles.ts_0089FF_14sp,
);
temp.add(r);
}
return TextSpan(
text: '${StrRes.directedTo}:',
style: Styles.ts_8E9AB0_14sp,
children: temp,
);
}
return null;
}
void onClearDirectional() {
directionalUsers.clear();
}
void parseClickEvent(Message msg) async {
log('parseClickEvent:${jsonEncode(msg)}');
if (msg.contentType == MessageType.custom) {
var data = msg.customElem!.data;
var map = json.decode(data!);
var customType = map['customType'];
if (CustomMessageType.call == customType && !isInBlacklist.value) {
} else if (CustomMessageType.meeting == customType) {
joinMeeting(msg);
} else if (CustomMessageType.tag == customType) {
final data = map['data'];
if (null != data['soundElem']) {
final soundElem = SoundElem.fromJson(data['soundElem']);
msg.soundElem = soundElem;
_playVoiceMessage(msg);
}
}
return;
}
if (msg.contentType == MessageType.voice) {
_playVoiceMessage(msg);
_markMessageAsRead(msg);
return;
}
IMUtils.parseClickEvent(
msg,
onViewUserInfo: (userInfo) {
viewUserInfo(userInfo, isCard: msg.isCardType);
},
meetingItemClick: joinMeeting,
onForward: () => forward(msg),
);
}
void onLongPressLeftAvatar(Message message) {
if (isInvalidGroup) return;
if (isGroupChat) {
var uid = message.sendID!;
var cursor = inputCtrl.selection.base.offset;
if (!focusNode.hasFocus) {
focusNode.requestFocus();
cursor = _lastCursorIndex;
}
if (cursor < 0) cursor = 0;
var start = inputCtrl.text.substring(0, cursor);
var end = inputCtrl.text.substring(cursor);
var at = '@$uid ';
inputCtrl.text = '$start$at$end';
Logger.print('start:$start end:$end at:$at content:${inputCtrl.text}');
inputCtrl.selection = TextSelection.collapsed(offset: '$start$at'.length);
_lastCursorIndex = inputCtrl.selection.start;
}
}
void onTapLeftAvatar(Message message) {
viewUserInfo(UserInfo()
..userID = message.sendID
..nickname = message.senderNickname
..faceURL = message.senderFaceUrl);
}
void onTapRightAvatar() {
viewUserInfo(OpenIM.iMManager.userInfo);
}
void viewUserInfo(UserInfo userInfo, {bool isCard = false}) {
if (isGroupChat && !isAdminOrOwner && !isCard) {
if (groupInfo!.lookMemberInfo != 1) {
AppNavigator.startUserProfilePane(
userID: userInfo.userID!,
nickname: userInfo.nickname,
faceURL: userInfo.faceURL,
groupID: groupID,
offAllWhenDelFriend: isSingleChat,
);
}
} else {
AppNavigator.startUserProfilePane(
userID: userInfo.userID!,
nickname: userInfo.nickname,
faceURL: userInfo.faceURL,
groupID: groupID,
offAllWhenDelFriend: isSingleChat,
forceCanAdd: isCard,
);
}
}
void clickLinkText(url, type) async {
if (await canLaunch(url)) {
await launch(url);
}
}
void _readDraftText() {
var draftText = Get.arguments['draftText'];
Logger.print('readDraftText:$draftText');
if (null != draftText && "" != draftText) {
var map = json.decode(draftText!);
String text = map['text'];
Map<String, dynamic> atMap = map['at'];
Logger.print('text:$text atMap:$atMap');
inputCtrl.text = text;
inputCtrl.selection = TextSelection.fromPosition(TextPosition(
offset: text.length,
));
if (text.isNotEmpty) {
focusNode.requestFocus();
}
}
}
exit() async {
if (isShowPopMenu.value) {
forceCloseMenuSub.add(true);
return false;
}
Get.back();
return true;
}
void focusNodeChanged(bool hasFocus) {
_changeInputStatus(hasFocus);
if (hasFocus) {
Logger.print('focus:$hasFocus');
scrollBottom();
}
}
void copy(Message message) {
final content = copyTextMap[message.clientMsgID] ?? message.textElem?.content;
if (null != content) {
IMUtils.copy(text: content.replaceAll('\u200B', ''));
}
}
Message indexOfMessage(int index, {bool calculate = true}) => IMUtils.calChatTimeInterval(
messageList,
calculate: calculate,
).reversed.elementAt(index);
ValueKey itemKey(Message message) => ValueKey(message.clientMsgID!);
@override
void onClose() {
sendTypingMsg();
_clearUnreadCount();
_unSubscribeUserOnlineStatus();
inputCtrl.dispose();
focusNode.dispose();
_audioPlayer.dispose();
forceCloseToolbox.close();
conversationSub.cancel();
sendStatusSub.close();
memberAddSub.cancel();
memberDelSub.cancel();
memberInfoChangedSub.cancel();
groupInfoUpdatedSub.cancel();
friendInfoChangedSub.cancel();
userStatusChangedSub?.cancel();
selfInfoUpdatedSub?.cancel();
forceCloseMenuSub.close();
joinedGroupAddedSub.cancel();
joinedGroupDeletedSub.cancel();
connectionSub.cancel();
_debounce?.cancel();
super.onClose();
}
String? getShowTime(Message message) {
if (message.exMap['showTime'] == true) {
return IMUtils.getChatTimeline(message.sendTime!);
}
return null;
}
void clearAllMessage() {
messageList.clear();
}
void onAddEmoji(String emoji) {
var input = inputCtrl.text;
if (_lastCursorIndex != -1 && input.isNotEmpty) {
var part1 = input.substring(0, _lastCursorIndex);
var part2 = input.substring(_lastCursorIndex);
inputCtrl.text = '$part1$emoji$part2';
_lastCursorIndex = _lastCursorIndex + emoji.length;
} else {
inputCtrl.text = '$input$emoji';
_lastCursorIndex = emoji.length;
}
inputCtrl.selection = TextSelection.fromPosition(TextPosition(
offset: _lastCursorIndex,
));
}
void onDeleteEmoji() {
final input = inputCtrl.text;
final regexEmoji = emojiFaces.keys.toList().join('|').replaceAll('[', '\\[').replaceAll(']', '\\]');
final list = [regexEmoji];
final pattern = '(${list.toList().join('|')})';
final emojiReg = RegExp(regexEmoji);
var reg = RegExp(pattern);
var cursor = _lastCursorIndex;
if (cursor == 0) return;
Match? match;
if (reg.hasMatch(input)) {
for (var m in reg.allMatches(input)) {
var matchText = m.group(0)!;
var start = m.start;
var end = start + matchText.length;
if (end == cursor) {
match = m;
break;
}
}
}
var matchText = match?.group(0);
if (matchText != null) {
var start = match!.start;
var end = start + matchText.length;
if (emojiReg.hasMatch(matchText)) {
inputCtrl.text = input.replaceRange(start, end, "");
cursor = start;
} else {
inputCtrl.text = input.replaceRange(cursor - 1, cursor, '');
--cursor;
}
} else {
inputCtrl.text = input.replaceRange(cursor - 1, cursor, '');
--cursor;
}
_lastCursorIndex = cursor;
}
String? get subTile => typing.value ? StrRes.typing : onlineStatusDesc.value;
bool showOnlineStatus() => !typing.value && onlineStatusDesc.isNotEmpty;
bool enabledReadStatus(Message message) {
if (message.isNotificationType) {
return false;
}
return true;
}
void favoriteManage() => AppNavigator.startFavoriteMange();
void addEmoji(Message message) {
if (message.contentType == MessageType.picture) {
var url = message.pictureElem?.sourcePicture?.url;
var width = message.pictureElem?.sourcePicture?.width;
var height = message.pictureElem?.sourcePicture?.height;
cacheLogic.addFavoriteFromUrl(url, width, height);
IMViews.showToast(StrRes.addSuccessfully);
} else if (message.contentType == MessageType.customFace) {
var index = message.faceElem?.index;
var data = message.faceElem?.data;
if (-1 != index) {
} else if (null != data) {
var map = json.decode(data);
var url = map['url'];
var width = map['width'];
var height = map['height'];
cacheLogic.addFavoriteFromUrl(url, width, height);
IMViews.showToast(StrRes.addSuccessfully);
}
}
}
void sendFavoritePic(int index, String url) async {
var emoji = cacheLogic.favoriteList.elementAt(index);
var message = await OpenIM.iMManager.messageManager.createFaceMessage(
data: json.encode({'url': emoji.url, 'width': emoji.width, 'height': emoji.height}),
);
_sendMessage(message);
}
void _initChatConfig() async {
scaleFactor.value = DataSp.getChatFontSizeFactor();
var path = DataSp.getChatBackground(otherId) ?? '';
if (path.isNotEmpty && (await File(path).exists())) {
background.value = path;
}
}
String get otherId => isSingleChat ? userID! : groupID!;
void failedResend(Message message) {
Logger.print('failedResend: ${message.clientMsgID}');
if (message.status == MessageStatus.sending) {
return;
}
sendStatusSub.addSafely(MsgStreamEv<bool>(
id: message.clientMsgID!,
value: true,
));
Logger.print('failedResending: ${message.clientMsgID}');
_sendMessage(message..status = MessageStatus.sending, addToUI: false);
}
int readTime(Message message) {
var isPrivate = message.attachedInfoElem?.isPrivateChat ?? false;
var burnDuration = message.attachedInfoElem?.burnDuration ?? 30;
burnDuration = burnDuration > 0 ? burnDuration : 30;
if (isPrivate) {
var hasReadTime = message.hasReadTime ?? 0;
if (hasReadTime > 0) {
var end = hasReadTime + (burnDuration * 1000);
var diff = (end - _timestamp) ~/ 1000;
if (diff > 0) {
privateMessageList.addIf(() => !privateMessageList.contains(message), message);
}
return diff < 0 ? 0 : diff;
}
}
return 0;
}
static int get _timestamp => DateTime.now().millisecondsSinceEpoch;
void destroyMsg() {
for (var message in privateMessageList) {
OpenIM.iMManager.messageManager.deleteMessageFromLocalAndSvr(
conversationID: conversationInfo.conversationID,
clientMsgID: message.clientMsgID!,
);
}
}
Future _queryMyGroupMemberInfo() async {
if (!isGroupChat) {
return;
}
var list = await OpenIM.iMManager.groupManager.getGroupMembersInfo(
groupID: groupID!,
userIDList: [OpenIM.iMManager.userID],
);
groupMembersInfo = list.firstOrNull;
groupMemberRoleLevel.value = groupMembersInfo?.roleLevel ?? GroupRoleLevel.member;
muteEndTime.value = groupMembersInfo?.muteEndTime ?? 0;
if (null != groupMembersInfo) {
memberUpdateInfoMap[OpenIM.iMManager.userID] = groupMembersInfo!;
}
return;
}
Future _queryOwnerAndAdmin() async {
if (isGroupChat) {
ownerAndAdmin = await OpenIM.iMManager.groupManager.getGroupMemberList(groupID: groupID!, filter: 5, count: 20);
}
return;
}
void _isJoinedGroup() async {
if (!isGroupChat) {
return;
}
isInGroup.value = await OpenIM.iMManager.groupManager.isJoinedGroup(
groupID: groupID!,
);
if (!isInGroup.value) {
return;
}
_queryGroupInfo();
_queryOwnerAndAdmin();
}
void _queryGroupInfo() async {
if (!isGroupChat) {
return;
}
var list = await OpenIM.iMManager.groupManager.getGroupsInfo(
groupIDList: [groupID!],
);
groupInfo = list.firstOrNull;
groupOwnerID = groupInfo?.ownerUserID;
groupMutedStatus.value = groupInfo?.status ?? 0;
if (null != groupInfo?.memberCount) {
memberCount.value = groupInfo!.memberCount!;
}
_queryMyGroupMemberInfo();
}
bool get havePermissionMute =>
isGroupChat &&
(groupInfo?.ownerUserID == OpenIM.iMManager.userID /*||
groupMembersInfo?.roleLevel == 2*/
);
bool isNotificationType(Message message) => message.contentType! >= 1000;
Map<String, String> getAtMapping(Message message) {
return {};
}
void _queryUserOnlineStatus() {
if (isSingleChat) {
OpenIM.iMManager.userManager.subscribeUsersStatus([userID!]).then((value) {
final status = value.firstWhereOrNull((element) => element.userID == userID);
_configUserStatusChanged(status);
});
userStatusChangedSub = imLogic.userStatusChangedSubject.listen((value) {
if (value.userID == userID) {
_configUserStatusChanged(value);
}
});
}
}
void _unSubscribeUserOnlineStatus() {
if (isSingleChat) {
OpenIM.iMManager.userManager.unsubscribeUsersStatus([userID!]);
}
}
void _configUserStatusChanged(UserStatusInfo? status) {
if (status != null) {
onlineStatus.value = status.status == 1;
onlineStatusDesc.value =
status.status == 0 ? StrRes.offline : _onlineStatusDes(status.platformIDs!) + StrRes.online;
}
}
String _onlineStatusDes(List<int> plamtforms) {
var des = <String>[];
for (final platform in plamtforms) {
switch (platform) {
case 1:
des.add('iOS');
break;
case 2:
des.add('Android');
break;
case 3:
des.add('Windows');
break;
case 4:
des.add('Mac');
break;
case 5:
des.add('Web');
break;
case 6:
des.add('mini_web');
break;
case 7:
des.add('Linux');
break;
case 8:
des.add('Android_pad');
break;
case 9:
des.add('iPad');
break;
default:
}
}
return des.join('/');
}
void _checkInBlacklist() async {
if (userID != null) {
var list = await OpenIM.iMManager.friendshipManager.getBlacklist();
var user = list.firstWhereOrNull((e) => e.userID == userID);
isInBlacklist.value = user != null;
}
}
bool isExceed24H(Message message) {
int milliseconds = message.sendTime!;
return !DateUtil.isToday(milliseconds);
}
bool isPlaySound(Message message) {
return _currentPlayClientMsgID.value == message.clientMsgID!;
}
void _initPlayListener() {
_audioPlayer.playerStateStream.listen((state) {
switch (state.processingState) {
case ProcessingState.idle:
case ProcessingState.loading:
case ProcessingState.buffering:
case ProcessingState.ready:
break;
case ProcessingState.completed:
_currentPlayClientMsgID.value = "";
break;
}
});
}
void _playVoiceMessage(Message message) async {
var isClickSame = _currentPlayClientMsgID.value == message.clientMsgID;
if (_audioPlayer.playerState.playing) {
_currentPlayClientMsgID.value = "";
_audioPlayer.stop();
}
if (!isClickSame) {
bool isValid = await _initVoiceSource(message);
if (isValid) {
_audioPlayer.setVolume(rtcIsBusy ? 0 : 1.0);
_audioPlayer.seek(Duration.zero);
_audioPlayer.play();
_currentPlayClientMsgID.value = message.clientMsgID!;
}
}
}
void stopVoice() {
if (_audioPlayer.playerState.playing) {
_currentPlayClientMsgID.value = '';
_audioPlayer.stop();
}
}
Future<bool> _initVoiceSource(Message message) async {
bool isReceived = message.sendID != OpenIM.iMManager.userID;
String? path = message.soundElem?.soundPath;
String? url = message.soundElem?.sourceUrl;
bool isExistSource = false;
if (isReceived) {
if (null != url && url.trim().isNotEmpty) {
isExistSource = true;
_audioPlayer.setUrl(url);
}
} else {
bool existFile = false;
if (path != null && path.trim().isNotEmpty) {
var file = File(path);
existFile = await file.exists();
}
if (existFile) {
isExistSource = true;
_audioPlayer.setFilePath(path!);
} else if (null != url && url.trim().isNotEmpty) {
isExistSource = true;
_audioPlayer.setUrl(url);
}
}
return isExistSource;
}
void onPopMenuShowChanged(show) {
isShowPopMenu.value = show;
if (!show && scrollingCacheMessageList.isNotEmpty) {
messageList.addAll(scrollingCacheMessageList);
scrollingCacheMessageList.clear();
}
}
String? getNewestNickname(Message message) {
if (isSingleChat) null;
return message.senderNickname;
}
String? getNewestFaceURL(Message message) {
return message.senderFaceUrl;
}
bool get isInvalidGroup => !isInGroup.value && isGroupChat;
void _resetGroupAtType() {
if (conversationInfo.groupAtType != GroupAtType.atNormal) {
OpenIM.iMManager.conversationManager.resetConversationGroupAtType(
conversationID: conversationInfo.conversationID,
);
}
}
void revokeMsgV2(Message message) async {
late bool canRevoke;
if (isGroupChat) {
if (message.sendID == OpenIM.iMManager.userID) {
canRevoke = true;
} else {
var list = await LoadingView.singleton
.wrap(asyncFunction: () => OpenIM.iMManager.groupManager.getGroupOwnerAndAdmin(groupID: groupID!));
var sender = list.firstWhereOrNull((e) => e.userID == message.sendID);
var revoker = list.firstWhereOrNull((e) => e.userID == OpenIM.iMManager.userID);
if (revoker != null && sender == null) {
canRevoke = true;
} else if (revoker == null && sender != null) {
canRevoke = false;
} else if (revoker != null && sender != null) {
if (revoker.roleLevel == sender.roleLevel) {
canRevoke = false;
} else if (revoker.roleLevel == GroupRoleLevel.owner) {
canRevoke = true;
} else {
canRevoke = false;
}
} else {
canRevoke = false;
}
}
} else {
if (message.sendID == OpenIM.iMManager.userID) {
canRevoke = true;
}
}
if (canRevoke) {
try {
await LoadingView.singleton.wrap(
asyncFunction: () => OpenIM.iMManager.messageManager.revokeMessage(
conversationID: conversationInfo.conversationID,
clientMsgID: message.clientMsgID!,
),
);
message.contentType = MessageType.revokeMessageNotification;
message.notificationElem = NotificationElem(detail: jsonEncode(_buildRevokeInfo(message)));
messageList.refresh();
} catch (e) {
IMViews.showToast(e.toString());
}
} else {
IMViews.showToast('no permission');
}
}
RevokedInfo _buildRevokeInfo(Message message) {
return RevokedInfo.fromJson({
'revokerID': OpenIM.iMManager.userInfo.userID,
'revokerRole': 0,
'revokerNickname': OpenIM.iMManager.userInfo.nickname,
'clientMsgID': message.clientMsgID,
'revokeTime': 0,
'sourceMessageSendTime': 0,
'sourceMessageSendID': message.sendID,
'sourceMessageSenderNickname': message.senderNickname,
'sessionType': message.sessionType,
});
}
bool showCopyMenu(Message message) {
return message.isTextType;
}
bool showDelMenu(Message message) {
return true;
}
bool showForwardMenu(Message message) {
if (message.status != MessageStatus.succeeded) {
return false;
}
if (message.isNotificationType) {
return false;
}
return true;
}
bool showReplyMenu(Message message) {
if (message.status != MessageStatus.succeeded) {
return false;
}
return message.isTextType ||
message.isVideoType ||
message.isPictureType ||
message.isLocationType ||
message.isFileType ||
message.isCardType ||
message.isCustomFaceType;
}
bool showRevokeMenu(Message message) {
if (message.status != MessageStatus.succeeded ||
message.isNotificationType ||
isExceed24H(message) && isSingleChat) {
return false;
}
if (isGroupChat) {
if (groupMemberRoleLevel.value == GroupRoleLevel.owner ||
(groupMemberRoleLevel.value == GroupRoleLevel.admin &&
ownerAndAdmin.firstWhereOrNull((element) => element.userID == message.sendID) == null)) {
return true;
}
}
if (message.sendID == OpenIM.iMManager.userID) {
if (DateTime.now().millisecondsSinceEpoch - (message.sendTime ??= 0) < (1000 * 60 * 5)) {
return true;
}
}
return false;
}
bool showAddEmojiMenu(Message message) {
if (message.status != MessageStatus.succeeded) {
return false;
}
return message.contentType == MessageType.picture || message.contentType == MessageType.customFace;
}
WillPopCallback? willPop() {
return isShowPopMenu.value ? () async => exit() : null;
}
void expandCallingMemberPanel() {
showCallingMember.value = !showCallingMember.value;
}
void call() {
if (rtcIsBusy) {
IMViews.showToast(StrRes.callingBusy);
return;
}
IMViews.openIMCallSheet(nickname.value, (index) {
imLogic.call(
callObj: CallObj.single,
callType: index == 0 ? CallType.audio : CallType.video,
inviteeUserIDList: [if (isSingleChat) userID!],
);
});
}
void onScrollToTop() {
if (scrollingCacheMessageList.isNotEmpty) {
messageList.addAll(scrollingCacheMessageList);
scrollingCacheMessageList.clear();
}
}
String get markText {
String? phoneNumber = imLogic.userInfo.value.phoneNumber;
if (phoneNumber != null) {
int start = phoneNumber.length > 4 ? phoneNumber.length - 4 : 0;
final sub = phoneNumber.substring(start);
return "${OpenIM.iMManager.userInfo.nickname!}$sub";
}
return OpenIM.iMManager.userInfo.nickname ?? '';
}
bool isFailedHintMessage(Message message) {
if (message.contentType == MessageType.custom) {
var data = message.customElem!.data;
var map = json.decode(data!);
var customType = map['customType'];
return customType == CustomMessageType.deletedByFriend || customType == CustomMessageType.blockedByFriend;
}
return false;
}
void sendFriendVerification() => AppNavigator.startSendVerificationApplication(userID: userID);
void _setSdkSyncDataListener() {
connectionSub = imLogic.imSdkStatusPublishSubject.listen((value) {
syncStatus.value = value.status;
if (value.status == IMSdkStatus.syncStart) {
_isStartSyncing = true;
} else if (value.status == IMSdkStatus.syncEnded) {
if (/*_isReceivedMessageWhenSyncing &&*/ _isStartSyncing) {
_isReceivedMessageWhenSyncing = false;
_isStartSyncing = false;
_isFirstLoad = true;
_loadHistoryForSyncEnd();
}
} else if (value.status == IMSdkStatus.syncFailed) {
_isReceivedMessageWhenSyncing = false;
_isStartSyncing = false;
}
});
}
bool get isSyncFailed => syncStatus.value == IMSdkStatus.syncFailed;
String? get syncStatusStr {
switch (syncStatus.value) {
case IMSdkStatus.syncStart:
case IMSdkStatus.synchronizing:
return StrRes.synchronizing;
case IMSdkStatus.syncFailed:
return StrRes.syncFailed;
default:
return null;
}
}
bool showBubbleBg(Message message) {
return !isNotificationType(message) && !isFailedHintMessage(message) && !isRevokeMessage(message);
}
bool isRevokeMessage(Message message) {
return message.contentType == MessageType.revokeMessageNotification;
}
void markRevokedMessage(Message message) {
if (message.contentType == MessageType.text) {
revokedTextMessage[message.clientMsgID!] = jsonEncode(message);
}
}
Future<AdvancedMessage> _fetchHistoryMessages() {
Logger.print(
'_fetchHistoryMessages: is first load: $_isFirstLoad, last client id: ${_isFirstLoad ? null : messageList.firstOrNull?.clientMsgID}');
return OpenIM.iMManager.messageManager.getAdvancedHistoryMessageList(
conversationID: conversationInfo.conversationID,
count: _pageSize,
startMsg: _isFirstLoad ? null : messageList.firstOrNull,
);
}
Future<bool> onScrollToBottomLoad() async {
late List<Message> list;
final result = await _fetchHistoryMessages();
if (result.messageList == null || result.messageList!.isEmpty) {
_getGroupInfoAfterLoadMessage();
return false;
}
list = result.messageList!;
if (_isFirstLoad) {
_isFirstLoad = false;
// remove the message that has been timed down
list.removeWhere((msg) => _isBeDeleteMessage(msg));
messageList.assignAll(list);
scrollBottom();
_getGroupInfoAfterLoadMessage();
} else {
list.removeWhere((msg) => _isBeDeleteMessage(msg));
messageList.insertAll(0, list);
}
return result.isEnd != true;
}
Future<void> _loadHistoryForSyncEnd() async {
final result = await OpenIM.iMManager.messageManager.getAdvancedHistoryMessageList(
conversationID: conversationInfo.conversationID,
count: messageList.length < _pageSize ? _pageSize : messageList.length,
startMsg: null,
);
if (result.messageList == null || result.messageList!.isEmpty) return;
final list = result.messageList!;
list.removeWhere((msg) => _isBeDeleteMessage(msg));
final offset = scrollController.offset;
messageList.assignAll(list);
scrollController.jumpTo(offset);
}
bool _isBeDeleteMessage(Message message) {
final isPrivate = message.attachedInfoElem?.isPrivateChat ?? false;
final hasReadTime = message.hasReadTime ?? 0;
if (isPrivate && hasReadTime > 0) {
return readTime(message) <= 0;
}
return false;
}
void _getGroupInfoAfterLoadMessage() {
if (isGroupChat && ownerAndAdmin.isEmpty) {
_isJoinedGroup();
} else {
_checkInBlacklist();
}
}
recommendFriendCarte(UserInfo userInfo) async {
final result = await AppNavigator.startSelectContacts(
action: SelAction.recommend,
ex: '[${StrRes.carte}]${userInfo.nickname}',
);
if (null != result) {
final customEx = result['customEx'];
final checkedList = result['checkedList'];
for (var info in checkedList) {
final userID = IMUtils.convertCheckedToUserID(info);
final groupID = IMUtils.convertCheckedToGroupID(info);
if (customEx is String && customEx.isNotEmpty) {
_sendMessage(
await OpenIM.iMManager.messageManager.createTextMessage(
text: customEx,
),
userId: userID,
groupId: groupID,
);
}
_sendMessage(
await OpenIM.iMManager.messageManager.createCardMessage(
userID: userInfo.userID!,
nickname: userInfo.nickname!,
faceURL: userInfo.faceURL,
),
userId: userID,
groupId: groupID,
);
}
}
}
void joinMeeting(Message msg) {}
@override
void onDetached() {}
@override
void onHidden() {}
@override
void onInactive() {}
@override
void onPaused() {}
@override
void onResumed() {
_loadHistoryForSyncEnd();
}
}