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> redPacketFutureCache = {}; static Future 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 _gf(File file) async { // 对于 GIF,我们可以考虑: // 1. 使用其他库处理(如 image 包) // 2. 调整尺寸但保持动图特性 // 3. 或者直接返回原文件(保持动图效果) // 这里简单返回原文件,避免破坏 GIF 动画 print('GIF 文件保持原样发送'); return file; } static Future 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 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> getMessageOnlineUrl({ required String msgID, }) async { var result = await TencentImSDKPlugin.v2TIMManager .getMessageManager() .getMessageOnlineUrl(msgID: msgID); return result; } /// 清空与该联系人的所有单聊消息记录 static Future clearAllMessages(String userId) async { V2TimCallback clearResult = await TencentImSDKPlugin.v2TIMManager .getMessageManager() .clearC2CHistoryMessage(userID: userId); // 指定要清空消息的联系人ID[citation:2] } }