chatapp3-flutter/lib/shared/tools/sc_message_utils.dart
2026-04-09 21:32:23 +08:00

214 lines
7.2 KiB
Dart
Raw Permalink 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:io';
import 'dart:math';
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:path_provider/path_provider.dart';
import 'package:tencent_cloud_chat_sdk/enum/friend_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_online_url.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart';
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart';
import 'package:video_thumbnail/video_thumbnail.dart';
import 'package:yumi/shared/data_sources/sources/local/file_cache_manager.dart';
import 'package:yumi/shared/business_logic/models/res/sc_user_red_packet_send_res.dart';
class SCMessageUtils {
//私聊红包Future缓存
static Map<String, Future<SCUserRedPacketSendRes>> redPacketFutureCache = {};
static Future<File> createImageElem(File file) async {
try {
// 2. 获取文件信息
final originalSize = file.lengthSync();
final fileExtension = _ge(file.path).toLowerCase();
print('压缩前文件: ${file.path}');
print('文件类型: $fileExtension');
print('压缩前大小: ${_fs(originalSize)}');
// 3. 小文件不压缩(小于 100KB
if (originalSize < 100 * 1024) {
print('文件较小,跳过压缩');
return file;
}
// 4. GIF 文件特殊处理
if (fileExtension == 'gif') {
print('GIF 文件,使用特殊处理');
return await _gf(file);
}
// 5. 创建临时目录和文件
final directory = await getTemporaryDirectory();
final timestamp = DateTime.now().millisecondsSinceEpoch;
final String fileName =
'compressed_${timestamp}_${_fh(file)}.jpg';
final File newFile = File("${directory.path}/$fileName");
// 6. 动态调整压缩质量(根据原文件大小)
final int quality = _cq(originalSize);
print('使用压缩质量: $quality%');
// 7. 执行压缩
final XFile? compressedFile =
await FlutterImageCompress.compressAndGetFile(
file.absolute.path,
newFile.path,
quality: quality,
minWidth: 480,
// 设置最小宽度,保持图片可用性
minHeight: 480,
// 设置最小高度
rotate: 0,
format: CompressFormat.jpeg,
inSampleSize: _ss(originalSize), // 采样率,减少内存使用
);
if (compressedFile == null) {
print('压缩失败,返回原文件');
return file;
}
// 8. 检查压缩效果
final compressedSize = await compressedFile.length();
final compressionRatio =
(originalSize - compressedSize) / originalSize * 100;
print('压缩后大小: ${_fs(compressedSize)}');
print('压缩率: ${compressionRatio.toStringAsFixed(1)}%');
// 9. 如果压缩后文件反而更大,返回原文件
if (compressedSize >= originalSize) {
print('压缩后文件更大,返回原文件');
await newFile.delete(); // 删除无用的压缩文件
return file;
}
// 10. 缓存管理
File localFile = File(compressedFile.path);
final String cacheKey = _fh(localFile);
await FileCacheManager.getInstance().putFileFromFile(cacheKey, localFile);
print('压缩成功,发送路径: ${compressedFile.path}');
return localFile;
} catch (e) {
print('图片压缩异常: $e');
return file; // 异常时返回原文件,保证功能可用
}
}
// 获取文件扩展名
static String _ge(String path) {
return path.split('.').last;
}
// 格式化文件大小显示
static String _fs(int bytes) {
if (bytes <= 0) return "0 B";
const suffixes = ["B", "KB", "MB", "GB"];
final i = (log(bytes) / log(1024)).floor();
return '${(bytes / pow(1024, i)).toStringAsFixed(1)} ${suffixes[i]}';
}
// 计算压缩质量(根据文件大小动态调整)
static int _cq(int fileSize) {
if (fileSize > 5 * 1024 * 1024) {
// > 5MB
return 50;
} else if (fileSize > 2 * 1024 * 1024) {
// 2-5MB
return 60;
} else if (fileSize > 1 * 1024 * 1024) {
// 1-2MB
return 70;
} else if (fileSize > 500 * 1024) {
// 500KB-1MB
return 80;
} else {
return 85; // < 500KB 使用较高质量
}
}
// 计算采样率
static int _ss(int fileSize) {
if (fileSize > 5 * 1024 * 1024) return 4;
if (fileSize > 2 * 1024 * 1024) return 3;
if (fileSize > 1 * 1024 * 1024) return 2;
return 1;
}
// 生成文件哈希(用于缓存键)
static String _fh(File file) {
final stats = file.statSync();
return '${file.path}_${stats.modified.millisecondsSinceEpoch}'.hashCode
.toString();
}
// GIF 文件压缩处理
static Future<File> _gf(File file) async {
// 对于 GIF我们可以考虑
// 1. 使用其他库处理(如 image 包)
// 2. 调整尺寸但保持动图特性
// 3. 或者直接返回原文件(保持动图效果)
// 这里简单返回原文件,避免破坏 GIF 动画
print('GIF 文件保持原样发送');
return file;
}
static Future<String?> generateFileThumbnail(
String videoPath,
int maxHeight,
) async {
try {
final thumbnailPath = await VideoThumbnail.thumbnailFile(
video: videoPath,
thumbnailPath: (await getTemporaryDirectory()).path,
// 保存到临时目录
maxHeight: maxHeight,
// 指定高度,宽度按比例缩放
imageFormat: ImageFormat.PNG,
quality: 75,
);
return thumbnailPath; // 返回文件路径,可用于 Image.file(File(thumbnailPath!))
} catch (e) {
print("生成缩略图文件时出错: $e");
return null;
}
}
///下载多媒体消息
static Future<V2TimCallback> downloadMessage({
required String msgID,
required int messageType,
required int imageType, // 图片类型仅messageType为图片消息是有效
required bool isSnapshot, // 是否是视频封面仅messageType为视频消息是有效
}) async {
var result = await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.downloadMessage(
msgID: msgID,
messageType: messageType,
imageType: imageType,
isSnapshot: isSnapshot,
);
return result;
}
Future<V2TimValueCallback<V2TimMessageOnlineUrl>> getMessageOnlineUrl({
required String msgID,
}) async {
var result = await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.getMessageOnlineUrl(msgID: msgID);
return result;
}
/// 清空与该联系人的所有单聊消息记录
static Future<void> clearAllMessages(String userId) async {
V2TimCallback clearResult = await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.clearC2CHistoryMessage(userID: userId); // 指定要清空消息的联系人ID[citation:2]
}
}