231 lines
6.6 KiB
Dart
231 lines
6.6 KiB
Dart
import 'dart:io';
|
||
import 'package:flutter/cupertino.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:image_picker/image_picker.dart';
|
||
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||
import 'package:yumi/shared/tools/sc_path_utils.dart';
|
||
import 'package:yumi/app_localizations.dart';
|
||
import 'package:yumi/shared/data_sources/sources/repositories/sc_general_repository_imp.dart';
|
||
import 'package:yumi/modules/user/crop/crop_image_page.dart';
|
||
import 'package:yumi/shared/tools/sc_loading_manager.dart';
|
||
|
||
class SCPickUtils {
|
||
static final ImagePicker _pkr = ImagePicker();
|
||
|
||
static void pickImage(BuildContext context,
|
||
OnUpLoadCallBack onUpLoadCallBack, {
|
||
double? aspectRatio,
|
||
bool? backOriginalFile = true,
|
||
bool? neeCrop = true,
|
||
}) async {
|
||
try {
|
||
final XFile? pickedFile = await _pkr.pickImage(
|
||
source: ImageSource.gallery,
|
||
imageQuality: 90,
|
||
maxWidth: 1920,
|
||
maxHeight: 1080,
|
||
);
|
||
|
||
if (pickedFile == null) return;
|
||
if (!SCPathUtils.fileTypeIsPic(pickedFile.path)) {
|
||
SCTts.show("Please select sc_images in .jpg, .jpeg, .png format.");
|
||
return;
|
||
}
|
||
// 检查是否为 GIF 图像
|
||
if (await _igif(pickedFile)) {
|
||
SCTts.show("GIF sc_images are not supported");
|
||
return;
|
||
}
|
||
|
||
final File imageFile = File(pickedFile.path);
|
||
if (neeCrop ?? false) {
|
||
cropImage(
|
||
imageFile,
|
||
context,
|
||
aspectRatio,
|
||
backOriginalFile,
|
||
onUpLoadCallBack,
|
||
);
|
||
} else {
|
||
if (imageFile.lengthSync() > 4000000) {
|
||
SCTts.show(SCAppLocalizations.of(context)!.theImageSizeCannotExceed);
|
||
onUpLoadCallBack?.call(false, "");
|
||
return;
|
||
}
|
||
SCLoadingManager.show(context: context);
|
||
try {
|
||
String fileUrl = await SCGeneralRepositoryImp().upload(imageFile);
|
||
SCLoadingManager.hide();
|
||
onUpLoadCallBack?.call(true, fileUrl);
|
||
} catch (e) {
|
||
SCTts.show("upload fail $e");
|
||
onUpLoadCallBack?.call(false, "");
|
||
SCLoadingManager.hide();
|
||
}
|
||
}
|
||
} catch (e) {
|
||
onUpLoadCallBack?.call(false, "");
|
||
print("Image selection error: $e");
|
||
SCTts.show("Failed to select image");
|
||
}
|
||
}
|
||
|
||
|
||
/// 检查图像是否为 GIF 格式
|
||
static Future<bool> _igif(XFile file) async {
|
||
try {
|
||
// 方法1: 通过文件扩展名检查
|
||
if (file.path.toLowerCase().endsWith('.gif')) {
|
||
return true;
|
||
}
|
||
|
||
// 方法2: 通过读取文件头检查 (更可靠)
|
||
final bytes = await file.readAsBytes();
|
||
if (bytes.length >= 6) {
|
||
// GIF 文件头特征: "GIF87a" 或 "GIF89a"
|
||
final header = String.fromCharCodes(bytes.sublist(0, 6));
|
||
return header == 'GIF87a' || header == 'GIF89a';
|
||
}
|
||
|
||
return false;
|
||
} catch (e) {
|
||
print("Error checking GIF: $e");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
static Future<bool> _iwp(XFile file) async {
|
||
try {
|
||
// 方法1: 通过文件扩展名检查
|
||
if (file.path.toLowerCase().endsWith('.webp')) {
|
||
return true;
|
||
}
|
||
|
||
// 方法2: 通过读取文件头检查
|
||
final bytes = await file.readAsBytes();
|
||
|
||
if (bytes.length < 12) {
|
||
return false;
|
||
}
|
||
|
||
// 检查基本的 WebP 文件头
|
||
final riffHeader = String.fromCharCodes(bytes.sublist(0, 4));
|
||
final webpHeader = String.fromCharCodes(bytes.sublist(8, 12));
|
||
|
||
if (riffHeader == 'RIFF' && webpHeader == 'WEBP') {
|
||
return true;
|
||
}
|
||
|
||
// 检查扩展的 WebP 格式(如果有更多字节可用)
|
||
if (bytes.length >= 16) {
|
||
// 检查 VP8 (有损) WebP
|
||
if (bytes[12] == 0x56 && bytes[13] == 0x50 &&
|
||
bytes[14] == 0x38 && bytes[15] == 0x20) {
|
||
return true;
|
||
}
|
||
|
||
// 检查 VP8L (无损) WebP
|
||
if (bytes[12] == 0x56 && bytes[13] == 0x50 &&
|
||
bytes[14] == 0x38 && bytes[15] == 0x4C) {
|
||
return true;
|
||
}
|
||
|
||
// 检查 VP8X (扩展) WebP
|
||
if (bytes[12] == 0x56 && bytes[13] == 0x50 &&
|
||
bytes[14] == 0x38 && bytes[15] == 0x58) {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
} catch (e) {
|
||
print("Error checking WebP: $e");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// 拍照并过滤 GIF
|
||
static void takePhoto(BuildContext context,
|
||
OnUpLoadCallBack onUpLoadCallBack, {
|
||
double? aspectRatio,
|
||
bool? backOriginalFile = true,
|
||
}) async {
|
||
try {
|
||
final XFile? pickedFile = await _pkr.pickImage(
|
||
source: ImageSource.camera,
|
||
imageQuality: 90,
|
||
maxWidth: 1920,
|
||
maxHeight: 1080,
|
||
);
|
||
|
||
if (pickedFile == null) return;
|
||
|
||
// 相机拍摄的照片不会是 GIF,但为了安全也可以检查
|
||
if (await _igif(pickedFile)) {
|
||
SCTts.show("Unsupported image format");
|
||
return;
|
||
}
|
||
|
||
final File imageFile = File(pickedFile.path);
|
||
cropImage(
|
||
imageFile,
|
||
context,
|
||
aspectRatio,
|
||
backOriginalFile,
|
||
onUpLoadCallBack,
|
||
);
|
||
} catch (e) {
|
||
print("Camera error: $e");
|
||
SCTts.show("Camera failed");
|
||
}
|
||
}
|
||
|
||
/// 选择视频 (不涉及 GIF 过滤)
|
||
static Future<File?> pickVideo(BuildContext context) async {
|
||
try {
|
||
final XFile? videoFile = await _pkr.pickVideo(
|
||
source: ImageSource.gallery,
|
||
);
|
||
|
||
if (videoFile != null) {
|
||
return File(videoFile.path);
|
||
}
|
||
return null;
|
||
} catch (e) {
|
||
print("Video selection error: $e");
|
||
SCTts.show("Failed to select video_pic");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// 原有的 cropImage 方法保持不变
|
||
static void cropImage(File originalImage,
|
||
BuildContext context,
|
||
double? aspectRatio,
|
||
bool? backOriginalFile,
|
||
OnUpLoadCallBack onUpLoadCallBack,
|
||
{
|
||
bool needUpload = true,
|
||
}) async {
|
||
if (originalImage == null) {
|
||
return;
|
||
}
|
||
await Navigator.push(
|
||
context,
|
||
MaterialPageRoute(
|
||
builder: (context) =>
|
||
CropImagePage(
|
||
originalImage,
|
||
aspectRatio: aspectRatio,
|
||
backOriginalFile: backOriginalFile,
|
||
needUpload:needUpload,
|
||
onUpLoadCallBack: (success, url) {
|
||
if (url.isNotEmpty) {
|
||
onUpLoadCallBack(success, url);
|
||
}
|
||
},
|
||
),
|
||
),
|
||
);
|
||
}
|
||
} |