1441f0148f
CI / flutter-gates (push) Successful in 2m30s
- MediaUploader:选图→压缩(80→60 降质阶梯,10 MiB 超限拒绝)→createUpload →预签名 PUT 直传(进度回调)→confirm→ready assetId 交付;并发槽位 2、 顺序即 position、单图失败不拖垮整批;凭据过期预检与存储侧 403 各自动 换新凭据一次;失败重试复用压缩产物、从 createUpload 全新开始 - 孤儿防护:未 confirm 的 assetId 只以管线局部变量存在,快照 assetId 与 ready 态断言绑定,buildAttachRequests 非全员 ready 即抛 StateError - 线路修正:media 两步上传端点由 user 服务(:8082 MediaController)提供, T3-12 误挂 community 客户端(:8084 无 media 路由),补 mediaApi 分端口 直连,compose 真链路实测证实 - UploadProgressOverlay 四态进度层(05 号规范 §3.3);直传层本地 HttpServer 三线路测试(200/403/断连);compose 冒烟测试 (PATBOND_MEDIA_SMOKE=1 启用,默认跳过) - 新依赖:image_picker、flutter_image_compress(03 号评估选型) - flutter test 379 全绿(基线 347,+32)、analyze 0、format 无 diff Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
49 lines
1.8 KiB
Dart
49 lines
1.8 KiB
Dart
import 'dart:typed_data';
|
|
|
|
import 'package:image_picker/image_picker.dart';
|
|
|
|
/// 待上传的一张原始图(选择器产物,压缩前形态)。
|
|
///
|
|
/// 只持有内存字节:不落磁盘副本、不携带原始文件路径,配合压缩层剥离
|
|
/// EXIF(含 GPS)的隐私纪律(03 号评估 §4.1)。
|
|
class PickedMediaImage {
|
|
const PickedMediaImage({required this.bytes, this.name});
|
|
|
|
/// 原始字节(供压缩层消费与格内缩略预览)。
|
|
final Uint8List bytes;
|
|
|
|
/// 原始文件名(仅诊断用途,不参与上传——objectKey 由服务端生成)。
|
|
final String? name;
|
|
}
|
|
|
|
/// 图片选择抽象:MediaUploader 只依赖本接口,单测注入假实现,
|
|
/// widget 测试无需平台通道。
|
|
abstract class MediaImagePicker {
|
|
/// 拉起系统选择器,最多返回 [limit] 张;用户取消返回空列表。
|
|
Future<List<PickedMediaImage>> pickImages({required int limit});
|
|
}
|
|
|
|
/// 基于 image_picker 的系统选择器实现(03 号评估 §4.1 选型:官方维护、
|
|
/// pickMultiImage 多选,M3 不引入重型相册组件)。
|
|
class SystemMediaImagePicker implements MediaImagePicker {
|
|
SystemMediaImagePicker({ImagePicker? picker})
|
|
: _picker = picker ?? ImagePicker();
|
|
|
|
final ImagePicker _picker;
|
|
|
|
@override
|
|
Future<List<PickedMediaImage>> pickImages({required int limit}) async {
|
|
// 仅剩一个空位时用单选(pickMultiImage 的 limit 在部分平台要求 ≥2)。
|
|
final files = limit <= 1
|
|
? [?await _picker.pickImage(source: ImageSource.gallery)]
|
|
: await _picker.pickMultiImage(limit: limit);
|
|
final images = <PickedMediaImage>[];
|
|
for (final file in files.take(limit)) {
|
|
images.add(
|
|
PickedMediaImage(bytes: await file.readAsBytes(), name: file.name),
|
|
);
|
|
}
|
|
return images;
|
|
}
|
|
}
|