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: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 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, onCallBack: onCallBack, oSSAccessKeyId: oSSAccessKeyId, policy: policy, callback: callback, signature: signature, ossDirectory: ossDirectory, ossHost: ossHost); }); } } class LookImagesWidget extends StatefulWidget { final List 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 State createState() => _LookImagesWidgetState(); } class _LookImagesWidgetState extends State { List listData = []; late int currentPage; late int initialPage = 0; /// Dio 最简版:网络图片转 Uint8List Future networkImageToUint8ListWithDio(String imageUrl) async { final dio = Dio(); // 初始化 Dio 实例 try { // 发起 GET 请求,响应类型设为字节数组(关键) final response = await dio.get>( 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 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 uint8ListToTempFile(Uint8List uint8List, {String fileName = "temp_file"}) async { 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; if (widget.currentPage == null) { initialPage = 0; currentPage = 0; } else { currentPage = widget.currentPage ?? 0; } super.initState(); } @override Widget build(BuildContext context) { return Scaffold( body: Stack( children: [ PhotoViewGallery.builder( itemCount: listData.length, pageController: PageController(initialPage: currentPage), onPageChanged: (index) { setState(() { currentPage = index; }); }, builder: (_, index) { return PhotoViewGalleryPageOptions( imageProvider: NetworkImage( listData[index], ), ); }, ), Positioned( 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, right: 0, bottom: 20, child: Container( alignment: Alignment.center, child: Text( "${currentPage + 1}/${listData.length}", style: const TextStyle( color: Colors.white, fontSize: 16, decoration: TextDecoration.none, ), ), ), ) ], ), ); } }