import 'dart:convert'; import 'dart:io'; import 'package:auto_route/annotations.dart'; import 'package:bot_toast/bot_toast.dart'; import 'package:eitc_erm_dental_flutter/db_util.dart'; import 'package:eitc_erm_dental_flutter/entity/db/local_patient_info.dart'; import 'package:eitc_erm_dental_flutter/exts.dart'; import 'package:eitc_erm_dental_flutter/global.dart'; import 'package:eitc_erm_dental_flutter/pages/view/widget/preview_operation_view.dart'; import 'package:eitc_erm_dental_flutter/pages/view/widget/preview_title.dart'; import 'package:eitc_erm_dental_flutter/pages/view/widget/video_patient_info_bar.dart'; import 'package:eitc_erm_dental_flutter/sp_util.dart'; import 'package:flutter/material.dart'; import 'package:image/image.dart' as img; import 'package:path_provider/path_provider.dart'; import 'package:photo_view/photo_view.dart'; import '../../funcs.dart'; ///照片预览页面 @RoutePage(name: "photoPreviewRoute") class PhotoPreviewPage extends StatefulWidget { final LocalPatientInfo info; final String path; final String area; final DateTime time; final String mobile; final String wifi; final String deviceModel; const PhotoPreviewPage( {super.key, required this.info, required this.path, required this.area, required this.time, required this.mobile, required this.deviceModel, required this.wifi}); @override State createState() => _PhotoPreviewPageState(); } class _PhotoPreviewPageState extends State { bool _isSaving = false; bool _isSaved = false; @override void initState() { super.initState(); } @override void dispose() { super.dispose(); _deleteCache(); } @override Widget build(BuildContext context) { Size screenSize = MediaQuery.of(context).size; return PopScope( canPop: !_isSaving, child: Scaffold( body: SafeArea( child: Container( color: Colors.black, child: Stack( children: [ PhotoView.customChild( childSize: MediaQuery.of(context).orientation == Orientation.landscape ? Size(screenSize.width, screenSize.height + screenSize.height / 2) : null, child: Container( alignment: Alignment.center, child: Column( mainAxisSize: MainAxisSize.min, children: [ Image.file(File(widget.path)), VideoPatientInfoBar( info: widget.info, orientation: MediaQuery.of(context).orientation, area: widget.area, deviceModel: widget.deviceModel, time: widget.time, ), ], ), )), Align( alignment: Alignment.topLeft, child: PreviewTitle( title: getS().previewPhoto, ), ), PreviewOperationView( onSave: _onSave, onCancel: _onCancel, ), ], ), ), ), )); } Future _editPhoto() async { int height = 55; int fontHeight = 48; //读取原图片 logd("读取原图片,path=${widget.path}"); img.Command sourceImgCmd = img.Command(); sourceImgCmd.decodeJpgFile(widget.path); await sourceImgCmd.executeThread(); img.Image? sourceImg = await sourceImgCmd.getImage(); if (sourceImg == null) { logd("读取原图片返回null"); return null; } logd("原图片读取成功,image=${sourceImg.toString()}"); logd("读取名字图片"); LocalPatientInfo? patientInfo = await DbUtil.instance.getLocalPatientById(selectedPatientId); //读取名字图片 img.Image? nameImg; img.Command? nameImgCmd; if (patientInfo != null && !patientInfo.namePic.isNullOrEmpty) { nameImgCmd = img.Command(); nameImgCmd.decodePng(base64Decode(patientInfo.namePic!)); await nameImgCmd.executeThread(); nameImg = await nameImgCmd.getImage(); logd("名字读片读取成功,image=${nameImg.toString()}"); } else { logd("名字图片不存在,咨询人信息为null或名字图片字段为空"); } //性别 int gender = getGenderFromIdCard(widget.info.idCardDecrypt); String sex = getS().unknown; if (gender == 0) { sex = getS().male; } else if (gender == 1) { sex = getS().female; } //年龄 int age = getAgeFromIdCard( widget.info.idCardDecrypt.isEmpty ? "--" : widget.info.idCardDecrypt); //区域 String toothArea = toothAreaTranslate(widget.area); //时间 String yyyy = widget.time.yyyyMMddHHmmss; logd("信息,性别=$sex,年龄=$age,area=$toothArea,时间=$yyyy"); logd("创建黑条并写入信息"); int gap = 30; //创建黑条 img.Command blackCmd = img.Command() ..createImage( width: sourceImg.width, height: height, ) ..fill(color: img.ColorRgb8(0, 0, 0)) ..drawString( "/$sex/${getS().xxAge(age < 0 ? 0 : age)} $toothArea $yyyy", font: photoFont ?? img.arial48, x: (nameImg?.width ?? 0) + gap, y: (height - fontHeight) ~/ 2, color: img.ColorRgb8(255, 255, 255)) ..drawString(widget.deviceModel.isEmpty ? "--" : widget.deviceModel, font: photoFont ?? img.arial48, rightJustify: true, //右对齐坐标从右往左算 x: sourceImg.width - gap, y: (height - fontHeight) ~/ 2, color: img.ColorRgb8(255, 255, 255)); if (nameImg != null && nameImgCmd != null) { logd("存在名字图片,合并到黑条中"); blackCmd.compositeImage(nameImgCmd, dstX: gap, dstY: (height - nameImg.height) ~/ 2); } logd("黑条创建成功"); logd("创建最终图片,合并原图片和黑条"); //创建一个更大的图片装下原图片和黑条 img.Command allCmd = img.Command(); allCmd.createImage( width: sourceImg.width, height: sourceImg.height + height); allCmd.compositeImage(sourceImgCmd); allCmd.compositeImage(blackCmd, dstY: sourceImg.height); logd("最终图片创建成功"); logd("开始保存"); //保存 Directory? dir; if (Platform.isIOS) { dir = await getApplicationDocumentsDirectory(); } else { dir = await getDownloadsDirectory(); } if (dir == null) { return null; } dir = Directory("${dir.path}/${Platform.isIOS ? "jpgimage" : "tempPhoto"}"); if (!await dir.exists()) { await dir.create(recursive: true); } String prefix = makeFilePrefix( name: widget.info.name ?? "", idCard: widget.info.idCardDecrypt, mobile: widget.mobile, area: widget.area, wifi: widget.wifi, time: widget.time.millisecondsSinceEpoch, userId: await SpUtil.getUserId()); String newPath = "${dir.path}/$prefix${widget.time.millisecondsSinceEpoch}.jpg"; logd("保存路径=$newPath"); allCmd.encodeJpgFile(newPath); await allCmd.executeThread(); logd("执行完毕"); return newPath; } void _onSave() async { logd("保存"); setState(() { _isSaving = true; }); var cancelFunc = BotToast.showLoading(clickClose: false, crossPage: false); try { //编辑并保存照片 String? path = await _editPhoto(); if (path == null) { setState(() { _isSaving = false; }); cancelFunc(); showToast(text: getS().saveFailed); return; } //通知原生保存图片 await videoChannel.invokeMethod("savePhoto", [widget.path, path]); _isSaved = true; cancelFunc(); setState(() { _isSaving = false; }); showToast(text: getS().saveSuccess); if (mounted) { Navigator.pop(context); } } catch (e) { setState(() { _isSaving = false; }); cancelFunc(); showToast(text: getS().saveFailed); loge("保存图片异常", error: e); } } void _onCancel() { logd("取消"); Navigator.pop(context); } void _deleteCache() { if (widget.path.isEmpty && _isSaved) { return; } try { fileChannel.invokeMethod("deleteFile", widget.path); } catch (e) { loge("照片预览删除文件异常", error: e); } } }