import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:image_cropper/image_cropper.dart'; import 'package:yumi/app_localizations.dart'; import 'package:yumi/ui_kit/components/sc_tts.dart'; import 'package:yumi/shared/tools/sc_loading_manager.dart'; import 'package:yumi/app/constants/sc_global_config.dart'; import 'package:yumi/app/constants/sc_screen.dart'; import 'package:yumi/app/routes/sc_fluro_navigator.dart'; import 'package:yumi/shared/data_sources/sources/repositories/sc_general_repository_imp.dart'; typedef OnUpLoadCallBack = void Function(bool success, String url); class CropImagePage extends StatefulWidget { final double? aspectRatio; // 需要锁定的宽高比,例如 1 表示 1:1 final bool? backOriginalFile; final OnUpLoadCallBack? onUpLoadCallBack; final File image; // 原始图片文件 final bool needUpload; const CropImagePage( this.image, { Key? key, this.onUpLoadCallBack, this.aspectRatio, this.backOriginalFile = true, this.needUpload = true, }) : super(key: key); @override _CropImageRouteState createState() => _CropImageRouteState(); } class _CropImageRouteState extends State { late Image imageView; @override void initState() { super.initState(); imageView = Image.file(widget.image, fit: BoxFit.contain); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xff333333), body: SafeArea( top: false, child: Stack( alignment: Alignment.center, children: [ // 预览原图 Align( alignment: Alignment.center, child: Container( width: double.infinity, height: SCScreen.designHeight - (kToolbarHeight * 2) - ScreenUtil().bottomBarHeight - ScreenUtil().statusBarHeight, child: imageView, ), ), // 顶部栏 Align( alignment: Alignment.topCenter, child: Container( height: kToolbarHeight + ScreenUtil().statusBarHeight, width: double.infinity, padding: EdgeInsets.only( left: width(15), right: width(15), top: ScreenUtil().statusBarHeight, ), color: Colors.transparent, child: Stack( alignment: Alignment.centerLeft, children: [ GestureDetector( onTap: () => SCNavigatorUtils.goBackWithParams(context, ''), child: Padding( padding: const EdgeInsets.all(8.0), child: Icon( SCGlobalConfig.lang == "ar" ? Icons.keyboard_arrow_right : Icons.keyboard_arrow_left, color: Colors.white, ), ), ), Align( alignment: Alignment.center, child: Text( SCAppLocalizations.of(context)!.crop, style: TextStyle(fontSize: 18.sp, color: Colors.white), ), ), ], ), ), ), // 底部完成按钮 Align( alignment: Alignment.bottomCenter, child: Container( width: double.infinity, height: kToolbarHeight + ScreenUtil().bottomBarHeight, color: const Color(0xff666666), alignment: Alignment.centerRight, child: GestureDetector( onTap: () => _cropAndUpload( context, widget.image, needUpload: widget.needUpload, ), child: Padding( padding: const EdgeInsets.all(8.0).copyWith(right: 18), child: Text( SCAppLocalizations.of(context)!.finish, style: TextStyle(color: Colors.white, fontSize: 16.sp), ), ), ), ), ), ], ), ), ); } /// 使用 image_cropper 进行裁剪并上传 Future _cropAndUpload( BuildContext context, File originalFile, { bool needUpload = true, }) async { final originalExists = originalFile.existsSync(); final originalSize = originalExists ? originalFile.lengthSync() : -1; debugPrint( "[Room Cover Upload] crop start originalPath=${originalFile.path} originalExists=$originalExists originalSize=$originalSize needUpload=$needUpload aspectRatio=${widget.aspectRatio}", ); // 调用系统裁剪界面 CroppedFile? cropped = await ImageCropper().cropImage( sourcePath: originalFile.path, aspectRatio: widget.aspectRatio != null ? CropAspectRatio(ratioX: widget.aspectRatio!, ratioY: 1) : null, uiSettings: [ AndroidUiSettings( toolbarTitle: SCAppLocalizations.of(context)!.crop, toolbarColor: Colors.black, toolbarWidgetColor: Colors.white, hideBottomControls: true, lockAspectRatio: widget.aspectRatio != null, ), IOSUiSettings( title: SCAppLocalizations.of(context)!.crop, resetButtonHidden: true, rotateButtonsHidden: true, aspectRatioPickerButtonHidden: true, rotateClockwiseButtonHidden: true, aspectRatioLockEnabled: widget.aspectRatio != null, ), ], ); // 用户可能直接点“取消”,此时 cropped 为 null File fileToUpload; if (cropped == null) { if (widget.backOriginalFile ?? true) { fileToUpload = originalFile; debugPrint( "[Room Cover Upload] crop cancelled, fallback to original file path=${fileToUpload.path}", ); } else { debugPrint("[Room Cover Upload] crop cancelled, no fallback file"); widget.onUpLoadCallBack?.call(false, ""); return; } } else { fileToUpload = File(cropped.path); final croppedExists = fileToUpload.existsSync(); final croppedSize = croppedExists ? fileToUpload.lengthSync() : -1; debugPrint( "[Room Cover Upload] crop result path=${fileToUpload.path} exists=$croppedExists size=$croppedSize", ); } if (needUpload) { // 弹出上传中的 loading SCLoadingManager.show(context: context); await upload(fileToUpload); } else { widget.onUpLoadCallBack?.call(true, fileToUpload.path); SCNavigatorUtils.goBack(context); } } /// 上传文件至oss Future upload(File file) async { try { final fileExists = file.existsSync(); final fileSize = fileExists ? file.lengthSync() : -1; debugPrint( "[Room Cover Upload] local file before request path=${file.path} exists=$fileExists size=$fileSize", ); String fileUrl = await SCGeneralRepositoryImp().upload(file); debugPrint("[Room Cover Upload] upload success fileUrl=$fileUrl"); SCLoadingManager.hide(); SCNavigatorUtils.goBack(context); await Future.delayed(const Duration(milliseconds: 100)); widget.onUpLoadCallBack?.call(true, fileUrl); } catch (e, stackTrace) { debugPrint( "[Room Cover Upload] upload failed path=${file.path} error=$e", ); debugPrint("[Room Cover Upload] stackTrace=$stackTrace"); SCTts.show("upload fail $e"); SCLoadingManager.hide(); } } /* * 获取 OssToken(如果依然需要,可保留) */ Future _getOssToken() async {} }