278 lines
8.2 KiB
Dart
278 lines
8.2 KiB
Dart
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<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,
|
||
onCallBack: onCallBack,
|
||
oSSAccessKeyId: oSSAccessKeyId,
|
||
policy: policy,
|
||
callback: callback,
|
||
isShowEdit: isShowEdit,
|
||
signature: signature,
|
||
ossDirectory: ossDirectory,
|
||
ossHost: ossHost);
|
||
});
|
||
}
|
||
}
|
||
|
||
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
|
||
State<LookImagesWidget> createState() => _LookImagesWidgetState();
|
||
}
|
||
|
||
class _LookImagesWidgetState extends State<LookImagesWidget> {
|
||
List listData = [];
|
||
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 {
|
||
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,
|
||
),
|
||
),
|
||
),
|
||
)
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|