Files
flutter_common/lib/upload_image/look_images_widget.dart

263 lines
8.6 KiB
Dart
Raw Normal View History

2025-11-14 11:35:45 +08:00
import 'dart:io';
import 'package:dio/dio.dart';
2025-10-20 10:44:59 +08:00
import 'package:flutter/material.dart';
2025-11-14 11:35:45 +08:00
import 'package:flutter/services.dart';
import 'package:flutter_common/upload_image/ossUtil.dart';
2025-10-20 10:44:59 +08:00
import 'package:get/get.dart';
2025-11-14 11:35:45 +08:00
import 'package:image_editor_plus/image_editor_plus.dart';
import 'package:path_provider/path_provider.dart';
2025-10-20 10:44:59 +08:00
import 'package:photo_view/photo_view_gallery.dart';
class LookImagesTool {
static lookImages({
required List<String> listData,
int? currentPage,
2025-11-14 11:35:45 +08:00
String? oSSAccessKeyId,
String? policy,
String? callback,
String? signature,
String? ossDirectory,
String? ossHost,
2025-10-20 10:44:59 +08:00
}) async {
showDialog(
context: Get.context!,
useSafeArea: false,
2025-10-20 10:44:59 +08:00
builder: (_) {
return LookImagesWidget(
2025-11-14 11:35:45 +08:00
listData: listData,
currentPage: currentPage,
oSSAccessKeyId: oSSAccessKeyId,
policy: policy,
callback: callback,
signature: signature,
ossDirectory: ossDirectory,
ossHost: ossHost);
2025-10-20 10:44:59 +08:00
});
}
}
class LookImagesWidget extends StatefulWidget {
final List<String> listData;
final int? currentPage;
2025-11-14 11:35:45 +08:00
final String? oSSAccessKeyId;
final String? policy;
final String? callback;
final String? signature;
final String? ossDirectory;
final String? ossHost;
2025-10-20 10:44:59 +08:00
const LookImagesWidget({
super.key,
required this.listData,
this.currentPage,
2025-11-14 11:35:45 +08:00
this.oSSAccessKeyId,
this.policy,
this.callback,
this.signature,
this.ossDirectory,
this.ossHost,
2025-10-20 10:44:59 +08:00
});
@override
State<LookImagesWidget> createState() => _LookImagesWidgetState();
}
class _LookImagesWidgetState extends State<LookImagesWidget> {
List listData = [];
late int currentPage;
late int initialPage = 0;
2025-11-14 11:35:45 +08:00
/// 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;
}
2025-10-20 10:44:59 +08:00
@override
void initState() {
listData = widget.listData;
if (widget.currentPage == null) {
initialPage = 0;
currentPage = 0;
} else {
// initialPage = 0;
currentPage = widget.currentPage ?? 0;
}
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
2025-10-20 10:44:59 +08:00
children: [
PhotoViewGallery.builder(
itemCount: listData.length,
pageController: PageController(initialPage: currentPage),
onPageChanged: (index) {
setState(() {
currentPage = index;
});
},
builder: (_, index) {
return PhotoViewGalleryPageOptions(
imageProvider: NetworkImage(
listData[index],
),
);
},
2025-10-20 10:44:59 +08:00
),
Positioned(
left: 15,
top: 50,
child: GestureDetector(onTap: () => Get.back(), child: Icon(Icons.arrow_back_ios, color: Colors.white))),
Positioned(
right: 15,
top: 50,
2025-11-14 11:35:45 +08:00
child: GestureDetector(
onTap: () async {
Uint8List? imageFile = await editImage(url: listData[currentPage]);
/// 可选:根据字节头自动识别文件后缀(如图片、视频)
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
Future<File?> 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 string = 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 ?? '',
);
return tempFile;
} catch (e) {
print("转换临时文件失败:$e");
return null;
}
}
},
child: Icon(Icons.edit, color: Colors.white))),
2025-10-20 10:44:59 +08:00
//图片张数指示器
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,
),
),
),
)
],
),
);
}
}