chatapp3-flutter/lib/modules/user/crop/crop_image_page.dart
2026-04-15 14:44:27 +08:00

238 lines
8.3 KiB
Dart
Raw 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: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<CropImagePage> {
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: <Widget>[
// 预览原图
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: <Widget>[
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<void> _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<void> 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<void> _getOssToken() async {}
}