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