214 lines
7.2 KiB
Dart
214 lines
7.2 KiB
Dart
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]
|
||
}
|
||
}
|