Merge remote-tracking branch 'origin/main'

This commit is contained in:
2025-11-20 14:38:37 +08:00
12 changed files with 1083 additions and 51 deletions

View File

@@ -1,21 +1,43 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_common/upload_image/ossUtil.dart';
import 'package:get/get.dart';
import 'package:photo_view/photo_view.dart';
import 'package:image_editor_plus/image_editor_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'package:photo_view/photo_view_gallery.dart';
class LookImagesTool {
static lookImages({
required List<String> listData,
int? currentPage,
void Function(String)? onCallBack,
String? oSSAccessKeyId,
Function? callBack,
bool? isShowEdit,
String? policy,
String? callback,
String? signature,
String? ossDirectory,
String? ossHost,
}) async {
showDialog(
context: Get.context!,
useSafeArea: false,
builder: (_) {
return LookImagesWidget(
listData: listData,
currentPage: currentPage,
);
listData: listData,
currentPage: currentPage,
onCallBack: onCallBack,
oSSAccessKeyId: oSSAccessKeyId,
policy: policy,
callback: callback,
isShowEdit: isShowEdit,
signature: signature,
ossDirectory: ossDirectory,
ossHost: ossHost);
});
}
}
@@ -23,11 +45,27 @@ class LookImagesTool {
class LookImagesWidget extends StatefulWidget {
final List<String> listData;
final int? currentPage;
final String? oSSAccessKeyId;
final String? policy;
final String? callback;
final String? signature;
final String? ossDirectory;
final String? ossHost;
final void Function(String)? onCallBack;
final bool? isShowEdit;
const LookImagesWidget({
super.key,
required this.listData,
this.currentPage,
this.oSSAccessKeyId,
this.policy,
this.callback,
this.signature,
this.ossDirectory,
this.ossHost,
this.onCallBack,
this.isShowEdit,
});
@override
@@ -39,6 +77,132 @@ class _LookImagesWidgetState extends State<LookImagesWidget> {
late int currentPage;
late int initialPage = 0;
/// Dio 最简版:网络图片转 Uint8List
Future<Uint8List?> networkImageToUint8ListWithDio(String imageUrl) async {
final dio = Dio(); // 初始化 Dio 实例
try {
// 发起 GET 请求,响应类型设为字节数组(关键)
final response = await dio.get<List<int>>(
imageUrl,
options: Options(responseType: ResponseType.bytes),
);
// 响应成功且数据非空时,直接转为 Uint8List
return response.statusCode == 200 && response.data != null ? Uint8List.fromList(response.data!) : null;
} catch (e) {
print('图片转换失败:$e'); // 捕获网络错误、URL 非法等异常
return null;
}
}
Future<Uint8List?> editImage({required String url}) async {
Uint8List? imageBytes = await networkImageToUint8ListWithDio(url);
ImageEditor.setI18n({
'crop': '裁剪',
'rotate left': '左旋转',
'rotate right': '右旋转',
'flip': '水平翻转',
'brush': '涂抹',
'link': '链接',
'save': '保存',
'text': '文本',
'blur': '模糊',
'filter': '滤镜',
'size': '大小',
'color': '颜色',
'background color': '背景颜色',
'background opacity': '背景透明度',
'reset': '重置',
'freeform': '自由裁剪',
'remove': '移除',
'emoji': '表情',
'slider color': '滑块颜色',
'color opacity': '透明度',
'blur radius': '模糊半径',
});
if (mounted) {
Uint8List? editedImage = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ImageEditor(
image: imageBytes,
blurOption: null,
filtersOption: null,
brushOption: null,
textOption: null,
emojiOption: null,
),
),
);
return editedImage;
}
return null;
}
/// 可选:根据字节头自动识别文件后缀(如图片、视频)
String getExtension(Uint8List uint8List) {
if (uint8List.length < 4) return "bin"; // 无法识别时返回二进制后缀
// PNG 头89 50 4E 47
if (uint8List[0] == 0x89 && uint8List[1] == 0x50 && uint8List[2] == 0x4E && uint8List[3] == 0x47) {
return "png";
}
// JPG 头FF D8 FF
else if (uint8List[0] == 0xFF && uint8List[1] == 0xD8 && uint8List[2] == 0xFF) {
return "jpg";
}
// MP4 头00 00 00 18 66 74 79 70
else if (uint8List.length >= 8 &&
uint8List[0] == 0x00 &&
uint8List[1] == 0x00 &&
uint8List[2] == 0x00 &&
uint8List[3] == 0x18 &&
uint8List[4] == 0x66 &&
uint8List[5] == 0x74 &&
uint8List[6] == 0x79 &&
uint8List[7] == 0x70) {
return "mp4";
}
// 其他格式可自行扩展(如 GIF、PDF 等)
else {
return "bin";
}
}
/// Uint8List 转临时 File 并且上传到oss并返回访问路径
Future<String?> uint8ListToTempFile(Uint8List uint8List, {String fileName = "temp_file"}) async {
if (uint8List.isEmpty) return null;
try {
// 1. 获取临时存储目录(跨平台兼容)
Directory tempDir = await getTemporaryDirectory();
String tempPath = tempDir.path;
// 2. 拼接文件路径(可自定义后缀,如 .png、.mp4 等)
File tempFile = File("$tempPath/$fileName.${getExtension(uint8List)}"); // 自动识别后缀(可选)
// 3. 将 Uint8List 写入文件
await tempFile.writeAsBytes(uint8List);
// print("临时文件路径:${tempFile.path}");
String imageUrl = await UploadOss.upload(
tempFile.path,
fileType: getExtension(uint8List),
oSSAccessKeyId: widget.oSSAccessKeyId ?? '',
ossHost: widget.ossHost ?? '',
ossDirectory: widget.ossDirectory ?? '',
policy: widget.policy ?? '',
callback: widget.callback ?? '',
signature: widget.signature ?? '',
);
// print("上传后的访问路径:$imageUrl");
return imageUrl;
} catch (e) {
print("转换临时文件失败:$e");
return null;
}
}
@override
void initState() {
listData = widget.listData;
@@ -46,7 +210,6 @@ class _LookImagesWidgetState extends State<LookImagesWidget> {
initialPage = 0;
currentPage = 0;
} else {
// initialPage = 0;
currentPage = widget.currentPage ?? 0;
}
super.initState();
@@ -77,6 +240,20 @@ class _LookImagesWidgetState extends State<LookImagesWidget> {
left: 15,
top: 50,
child: GestureDetector(onTap: () => Get.back(), child: Icon(Icons.arrow_back_ios, color: Colors.white))),
Positioned(
right: 15,
top: 50,
child: GestureDetector(
onTap: () async {
Uint8List? imageFile = await editImage(url: listData[currentPage]);
String? url = await uint8ListToTempFile(imageFile ?? Uint8List(0));
if(widget.onCallBack != null){
widget.onCallBack!(url??'');
}
},
child: Visibility(
visible: widget.isShowEdit??false,
child: Icon(Icons.edit, color: Colors.white)))),
//图片张数指示器
Positioned(
left: 0,