import 'dart:typed_data'; import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:patbond_flutter/features/community/media_picking.dart'; /// 压缩产物(直传本体)。mimeType 与直传 PUT 的 Content-Type、 /// createUpload 登记值三处必须一致(Content-Type 已签进预签名签名)。 class CompressedMediaImage { const CompressedMediaImage({required this.bytes, required this.mimeType}); final Uint8List bytes; final String mimeType; int get byteSize => bytes.length; } /// 压缩抽象:quality 由 MediaUploader 的降质阶梯驱动(80 → 60), /// 单测注入假实现控制产物大小。 abstract class MediaImageCompressor { Future compress( PickedMediaImage source, { required int quality, }); } /// 基于 flutter_image_compress 的原生压缩实现(03 号评估 §4.1 选型)。 /// /// 策略(03 号 §4.1 + 13 号报告偏差 #6):长边 ≤2048 重采样、统一转码 /// JPEG(服务端 mime 白名单不收 HEIC,客户端统一出 jpeg); /// autoCorrectionAngle 矫正方向后不保留 EXIF——顺带剥离 GPS 定位隐私, /// **不要开 keepExif**。 class NativeMediaImageCompressor implements MediaImageCompressor { const NativeMediaImageCompressor({this.maxLongEdge = 2048}); final int maxLongEdge; @override Future compress( PickedMediaImage source, { required int quality, }) async { final bytes = await FlutterImageCompress.compressWithList( source.bytes, minWidth: maxLongEdge, minHeight: maxLongEdge, quality: quality, format: CompressFormat.jpeg, autoCorrectionAngle: true, keepExif: false, ); return CompressedMediaImage(bytes: bytes, mimeType: 'image/jpeg'); } }