2025-08-21 10:50:38 +08:00
|
|
|
|
import 'dart:convert';
|
|
|
|
|
import 'dart:io';
|
|
|
|
|
|
|
|
|
|
import 'package:get/get.dart';
|
2025-07-21 15:46:30 +08:00
|
|
|
|
import 'package:logger/logger.dart';
|
2025-08-21 10:50:38 +08:00
|
|
|
|
import 'package:loopin/IM/controller/im_user_info_controller.dart';
|
2025-07-21 15:46:30 +08:00
|
|
|
|
import 'package:loopin/IM/im_result.dart';
|
2025-08-21 10:50:38 +08:00
|
|
|
|
import 'package:loopin/utils/parse_message_summary.dart';
|
2025-07-21 15:46:30 +08:00
|
|
|
|
import 'package:tencent_cloud_chat_sdk/enum/message_priority_enum.dart';
|
|
|
|
|
import 'package:tencent_cloud_chat_sdk/enum/offlinePushInfo.dart';
|
|
|
|
|
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
|
2025-08-21 10:50:38 +08:00
|
|
|
|
import 'package:tencent_cloud_chat_sdk/models/v2_tim_msg_create_info_result.dart';
|
2025-07-21 15:46:30 +08:00
|
|
|
|
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart';
|
|
|
|
|
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart';
|
|
|
|
|
|
|
|
|
|
class IMMessage {
|
2025-08-21 10:50:38 +08:00
|
|
|
|
final logger = Logger();
|
|
|
|
|
|
|
|
|
|
/// 1.发送消息
|
|
|
|
|
Future<ImResult> sendMessage({
|
|
|
|
|
required V2TimMessage msg,
|
2025-07-21 15:46:30 +08:00
|
|
|
|
String? toUserID,
|
|
|
|
|
String? groupID,
|
2025-08-21 10:50:38 +08:00
|
|
|
|
String? cloudCustomData,
|
2025-09-13 17:01:01 +08:00
|
|
|
|
String? groupName,
|
|
|
|
|
bool isPush = true,
|
|
|
|
|
bool isExcludedFromUnreadCount = false,
|
2025-07-21 15:46:30 +08:00
|
|
|
|
}) async {
|
|
|
|
|
// 必须且只能设置一个:toUserID(单聊)或 groupID(群聊)
|
|
|
|
|
if ((toUserID == null && groupID == null) || (toUserID != null && groupID != null)) {
|
|
|
|
|
return ImResult(
|
|
|
|
|
success: false,
|
|
|
|
|
code: -1,
|
2025-09-13 17:01:01 +08:00
|
|
|
|
desc: "只能指定一个receiver:toUserID或groupID",
|
2025-07-21 15:46:30 +08:00
|
|
|
|
);
|
|
|
|
|
}
|
2025-08-21 10:50:38 +08:00
|
|
|
|
if (cloudCustomData != null) {
|
|
|
|
|
msg.cloudCustomData = cloudCustomData;
|
2025-07-21 15:46:30 +08:00
|
|
|
|
}
|
2025-08-21 10:50:38 +08:00
|
|
|
|
// 解析消息类型
|
2025-07-21 15:46:30 +08:00
|
|
|
|
V2TimValueCallback<V2TimMessage> sendRes;
|
2025-08-21 10:50:38 +08:00
|
|
|
|
// final controller = Get.find<ChatDetailController>();
|
2025-07-21 15:46:30 +08:00
|
|
|
|
// 单聊
|
|
|
|
|
if (toUserID != null) {
|
2025-09-13 17:01:01 +08:00
|
|
|
|
final myInfo = Get.find<ImUserInfoController>();
|
|
|
|
|
logger.w('启用默认title:${myInfo.nickname.value}');
|
|
|
|
|
OfflinePushInfo offlinePushInfo = OfflinePushInfo(
|
|
|
|
|
title: myInfo.nickname.value,
|
|
|
|
|
desc: parseMessageSummary(msg),
|
|
|
|
|
ext: jsonEncode({"userID": myInfo.userID.value, "title": myInfo.nickname.value}),
|
|
|
|
|
);
|
2025-07-21 15:46:30 +08:00
|
|
|
|
sendRes = await TencentImSDKPlugin.v2TIMManager.getMessageManager().sendMessage(
|
2025-08-21 10:50:38 +08:00
|
|
|
|
message: msg,
|
2025-07-21 15:46:30 +08:00
|
|
|
|
receiver: toUserID,
|
2025-08-21 10:50:38 +08:00
|
|
|
|
// onSyncMsgID: (msgID) async {
|
|
|
|
|
// 这里立刻拿到消息ID,可以提前把这条消息展示到列表中(发送中状态)有时间再改吧
|
|
|
|
|
// 根据类型,创建对应的elem;
|
|
|
|
|
// logger.w(msg.imageElem!.toLogString());
|
|
|
|
|
// controller.chatList.add(msg.imageElem);
|
|
|
|
|
// controller.scrollToBottom();
|
|
|
|
|
// },
|
2025-07-21 15:46:30 +08:00
|
|
|
|
groupID: "",
|
|
|
|
|
priority: MessagePriorityEnum.V2TIM_PRIORITY_DEFAULT,
|
2025-09-13 17:01:01 +08:00
|
|
|
|
onlineUserOnly: false, // 是否只推送在线用户,默认全推
|
|
|
|
|
isExcludedFromUnreadCount: isExcludedFromUnreadCount, //默认记入 不计入未读,如果这里设置了,那么message中设置的将失效
|
|
|
|
|
isExcludedFromLastMessage: false, // 不作为会话的最新消息
|
|
|
|
|
isSupportMessageExtension: false, // 支持消息扩展
|
|
|
|
|
isExcludedFromContentModeration: false, // 绕过内容审核
|
|
|
|
|
needReadReceipt: false, // 已读回执
|
|
|
|
|
offlinePushInfo: isPush ? offlinePushInfo : null,
|
2025-08-21 10:50:38 +08:00
|
|
|
|
cloudCustomData: cloudCustomData,
|
2025-07-21 15:46:30 +08:00
|
|
|
|
localCustomData: "",
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
// 群聊
|
2025-09-13 17:01:01 +08:00
|
|
|
|
OfflinePushInfo offlinePushInfo = OfflinePushInfo(
|
|
|
|
|
title: groupName,
|
|
|
|
|
desc: parseMessageSummary(msg),
|
|
|
|
|
ext: jsonEncode({"groupID": groupID, "title": groupName ?? ''}),
|
|
|
|
|
);
|
2025-07-21 15:46:30 +08:00
|
|
|
|
sendRes = await TencentImSDKPlugin.v2TIMManager.getMessageManager().sendMessage(
|
2025-08-21 10:50:38 +08:00
|
|
|
|
message: msg,
|
2025-07-21 15:46:30 +08:00
|
|
|
|
receiver: "",
|
|
|
|
|
groupID: groupID!,
|
2025-09-13 17:01:01 +08:00
|
|
|
|
// onSyncMsgID: (msgID) async {
|
|
|
|
|
// 这里立刻拿到消息ID,可以提前把这条消息展示到列表中(发送中状态)有时间再改吧
|
|
|
|
|
// 根据类型,创建对应的elem;
|
|
|
|
|
// logger.w(msg.imageElem!.toLogString());
|
|
|
|
|
// controller.chatList.add(msg.imageElem);
|
|
|
|
|
// controller.scrollToBottom();
|
|
|
|
|
// },
|
2025-07-21 15:46:30 +08:00
|
|
|
|
priority: MessagePriorityEnum.V2TIM_PRIORITY_DEFAULT,
|
|
|
|
|
onlineUserOnly: false,
|
2025-09-13 17:01:01 +08:00
|
|
|
|
isExcludedFromUnreadCount: isExcludedFromUnreadCount,
|
2025-07-21 15:46:30 +08:00
|
|
|
|
isExcludedFromLastMessage: false,
|
2025-09-13 17:01:01 +08:00
|
|
|
|
isSupportMessageExtension: false,
|
|
|
|
|
isExcludedFromContentModeration: false,
|
2025-07-21 15:46:30 +08:00
|
|
|
|
needReadReceipt: false,
|
2025-09-13 17:01:01 +08:00
|
|
|
|
offlinePushInfo: isPush ? offlinePushInfo : null,
|
|
|
|
|
cloudCustomData: cloudCustomData,
|
2025-07-21 15:46:30 +08:00
|
|
|
|
localCustomData: "",
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ImResult(
|
|
|
|
|
success: sendRes.code == 0,
|
|
|
|
|
code: sendRes.code,
|
|
|
|
|
desc: sendRes.desc,
|
|
|
|
|
data: sendRes.data,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-21 10:50:38 +08:00
|
|
|
|
/// 2=创建自定义消息
|
|
|
|
|
Future<ImResult<V2TimMsgCreateInfoResult>> createCustomMessage({
|
2025-07-21 15:46:30 +08:00
|
|
|
|
required String data,
|
2025-08-21 10:50:38 +08:00
|
|
|
|
String desc = "",
|
|
|
|
|
String extension = "",
|
2025-07-21 15:46:30 +08:00
|
|
|
|
}) async {
|
2025-08-21 10:50:38 +08:00
|
|
|
|
final res = await TencentImSDKPlugin.v2TIMManager.getMessageManager().createCustomMessage(
|
2025-07-21 15:46:30 +08:00
|
|
|
|
data: data,
|
2025-08-21 10:50:38 +08:00
|
|
|
|
desc: desc,
|
|
|
|
|
extension: extension,
|
2025-07-21 15:46:30 +08:00
|
|
|
|
);
|
2025-08-21 10:50:38 +08:00
|
|
|
|
return ImResult.wrap(res);
|
2025-07-21 15:46:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 构造单聊伪消息
|
|
|
|
|
Future<ImResult> insertTimeLabel(String data, String userId) async {
|
|
|
|
|
final customMsgRes = await TencentImSDKPlugin.v2TIMManager.getMessageManager().createCustomMessage(
|
|
|
|
|
data: data,
|
|
|
|
|
desc: "时间标签",
|
|
|
|
|
extension: "",
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (customMsgRes.code != 0 || customMsgRes.data?.id == null) {
|
|
|
|
|
return ImResult(
|
|
|
|
|
success: false,
|
|
|
|
|
code: customMsgRes.code,
|
|
|
|
|
desc: "创建时间标签消息失败",
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final timeMsg = customMsgRes.data!.messageInfo;
|
|
|
|
|
timeMsg!.localCustomData = 'time_label';
|
|
|
|
|
|
|
|
|
|
return ImResult(
|
|
|
|
|
success: true,
|
|
|
|
|
code: customMsgRes.code,
|
|
|
|
|
desc: "success",
|
|
|
|
|
data: timeMsg,
|
|
|
|
|
);
|
2025-08-21 10:50:38 +08:00
|
|
|
|
}
|
2025-07-21 15:46:30 +08:00
|
|
|
|
|
2025-08-21 10:50:38 +08:00
|
|
|
|
/// 创建文本消息==1
|
|
|
|
|
Future<ImResult<V2TimMsgCreateInfoResult>> createTextMessage({
|
|
|
|
|
required String text,
|
|
|
|
|
}) async {
|
|
|
|
|
final res = await TencentImSDKPlugin.v2TIMManager.getMessageManager().createTextMessage(text: text);
|
|
|
|
|
return ImResult.wrap(res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 创建图片消息==3
|
|
|
|
|
Future<ImResult<V2TimMsgCreateInfoResult>> createImageMessage({
|
|
|
|
|
required String imagePath,
|
|
|
|
|
String? imageName,
|
|
|
|
|
}) async {
|
|
|
|
|
final fileExists = await File(imagePath).exists();
|
|
|
|
|
if (fileExists) {
|
|
|
|
|
final res = await TencentImSDKPlugin.v2TIMManager.getMessageManager().createImageMessage(
|
|
|
|
|
imagePath: imagePath,
|
|
|
|
|
imageName: imageName,
|
|
|
|
|
);
|
|
|
|
|
return ImResult.wrap(res);
|
|
|
|
|
} else {
|
|
|
|
|
// 构造失败的回调
|
|
|
|
|
final failed = V2TimValueCallback<V2TimMsgCreateInfoResult>.fromJson({
|
|
|
|
|
"code": -5,
|
|
|
|
|
"desc": "imagePath is not found",
|
|
|
|
|
"data": V2TimMsgCreateInfoResult.fromJson({}),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return ImResult.wrap(failed);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
///创建视频消息==5
|
|
|
|
|
Future<ImResult<V2TimMsgCreateInfoResult>> createVideoMessage({
|
|
|
|
|
//最大100MB
|
|
|
|
|
required String videoFilePath, //视频地址
|
|
|
|
|
required String type, // 类型mp4/avi==
|
|
|
|
|
required int duration, // 时长
|
|
|
|
|
required String snapshotPath, // 封面图
|
|
|
|
|
}) async {
|
|
|
|
|
final videoExists = await File(videoFilePath).exists();
|
|
|
|
|
final snapshotExists = await File(snapshotPath).exists();
|
|
|
|
|
|
|
|
|
|
if (videoExists && snapshotExists) {
|
|
|
|
|
final res = await TencentImSDKPlugin.v2TIMManager.getMessageManager().createVideoMessage(
|
|
|
|
|
videoFilePath: videoFilePath,
|
|
|
|
|
type: type,
|
|
|
|
|
duration: duration,
|
|
|
|
|
snapshotPath: snapshotPath,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return ImResult.wrap(res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 构造失败回调
|
|
|
|
|
final failed = V2TimValueCallback<V2TimMsgCreateInfoResult>.fromJson({
|
|
|
|
|
"code": -5,
|
|
|
|
|
"desc": "视频或首帧图缺失",
|
|
|
|
|
"data": V2TimMsgCreateInfoResult.fromJson({}),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return ImResult.wrap(failed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 语音消息==4
|
|
|
|
|
Future<ImResult<V2TimMsgCreateInfoResult>> createSoundMessage({
|
|
|
|
|
required String soundPath,
|
|
|
|
|
required int duration,
|
|
|
|
|
String? path,
|
|
|
|
|
}) async {
|
|
|
|
|
final soundExists = await File(soundPath).exists();
|
|
|
|
|
|
|
|
|
|
if (soundExists) {
|
|
|
|
|
final res = await TencentImSDKPlugin.v2TIMManager.getMessageManager().createSoundMessage(
|
|
|
|
|
soundPath: soundPath,
|
|
|
|
|
duration: duration,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return ImResult.wrap(res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final failed = V2TimValueCallback<V2TimMsgCreateInfoResult>.fromJson({
|
|
|
|
|
"code": -5,
|
|
|
|
|
"desc": "音频文件缺失",
|
|
|
|
|
"data": V2TimMsgCreateInfoResult.fromJson({}),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return ImResult.wrap(failed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 表情 == 8
|
|
|
|
|
Future<ImResult<V2TimMsgCreateInfoResult>> createFaceMessage({
|
|
|
|
|
required int index,
|
|
|
|
|
required String data,
|
|
|
|
|
}) async {
|
|
|
|
|
final res = await TencentImSDKPlugin.v2TIMManager.getMessageManager().createFaceMessage(
|
|
|
|
|
index: index,
|
|
|
|
|
data: data,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return ImResult.wrap(res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
///相当于一个被禁用了网络发送能力的 sendMessage() 接口
|
|
|
|
|
Future<ImResult<V2TimMessage>> insertC2CMessageToLocalStorageV2({
|
|
|
|
|
required String userID,
|
|
|
|
|
required String senderID,
|
|
|
|
|
V2TimMessage? message,
|
|
|
|
|
String? createdMsgID,
|
|
|
|
|
}) async {
|
|
|
|
|
final res = await TencentImSDKPlugin.v2TIMManager.getMessageManager().insertC2CMessageToLocalStorageV2(
|
|
|
|
|
userID: userID,
|
|
|
|
|
senderID: senderID,
|
|
|
|
|
message: message,
|
|
|
|
|
createdMsgID: createdMsgID,
|
|
|
|
|
);
|
|
|
|
|
return ImResult.wrap(res);
|
2025-07-21 15:46:30 +08:00
|
|
|
|
}
|
|
|
|
|
}
|