chatapp3-flutter/lib/shared/tools/sc_pick_utils.dart
2026-04-09 21:32:23 +08:00

231 lines
6.6 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
},
),
),
);
}
}