Marking.Client.Moblie/marking_app/lib/components/PictureOverview.dart

843 lines
32 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.

/*
* @Author: wangyang 1147192855@qq.com
* @Date: 2022-07-23 11:55:16
* @LastEditors: wangyang 1147192855@qq.com
* @LastEditTime: 2022-09-28 17:01:15
* @FilePath: \marking_app\lib\components\PictureOverview.dart
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import 'dart:async';
import 'dart:io';
import 'package:crypto/crypto.dart' as crypto;
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:functional_widget_annotation/functional_widget_annotation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:marking_app/common/mixin/common.dart';
import 'package:marking_app/common/model/common/base_structure_result.dart';
import 'package:marking_app/common/model/common/upload_img_secret_key.dart';
import 'package:marking_app/common/model/enum/review_marks_bottom_btns_enum.dart';
import 'package:marking_app/common/model/event_bus/bottom_annotation_switch_cleanall.dart';
import 'package:marking_app/common/model/job/test_questions_image_info.dart';
import 'package:marking_app/common/model/job/upload_file_interface_config.dart';
import 'package:marking_app/common/model/job/upload_file_interface_config_params.dart';
import 'package:marking_app/common/model/marking/annotation_graffiti_switch.dart';
import 'package:marking_app/common/model/marking/do_marking_keyboard_model.dart';
import 'package:marking_app/common/model/marking/marking_history_zoom_info.dart';
import 'package:marking_app/common/model/marking/marking_text_question.dart';
import 'package:marking_app/common/model/marking/switch_keyboard_to_reload_images.dart';
import 'package:marking_app/pages/common/event_bus_mixin.dart';
import 'package:marking_app/pages/homework_correction/hooks/use_cached_img_refresh.dart';
import 'package:marking_app/pages/marking/hooks/use_zoom_image_history.dart';
import 'package:marking_app/pages/marking/provider/do_paper_bottom_review_marks_provider.dart';
import 'package:marking_app/pages/marking/provider/zoom_height_provider.dart';
import 'package:marking_app/pages/marking/provider/zoom_history_provider.dart';
import 'package:marking_app/provider/annotation_graffiti_switch_provider.dart';
import 'package:marking_app/provider/do_marking_provider.dart';
import 'package:marking_app/provider/upload_file_provider.dart';
import 'package:marking_app/utils/index.dart';
import 'package:marking_app/utils/my_text.dart';
import 'package:marking_app/utils/request/rest_client.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:ui' as ui;
import 'package:uuid/uuid.dart';
import 'package:zoom_widget/zoom_widget.dart';
import '../pages/marking/provider/draw_marking_provider.dart';
part 'PictureOverview.g.dart';
typedef PageChanged = void Function(int index);
//这里就是关键的代码定义一个key
final GlobalKey<PictureOverviewState> pictureOverviewKey = GlobalKey<PictureOverviewState>();
class PictureOverview extends StatefulHookConsumerWidget {
final double imageScale;
final Offset? imagePosition;
final String questionNum;
final int markingUserId;
final bool homework;
final List imageItems; //图片列表
final bool annotationsFlag;
final String testQuestionNumber;
final Map<String, String> commentImageMap;
final MarkingTextQuestion data;
const PictureOverview({
required this.imageItems,
required this.annotationsFlag,
required this.commentImageMap,
required this.testQuestionNumber,
required this.questionNum,
required this.markingUserId,
required this.data,
this.homework = false,
this.imageScale = 1,
this.imagePosition,
Key? key,
}) : super(key: key);
@override
PictureOverviewState createState() => PictureOverviewState();
}
class PictureOverviewState extends ConsumerState<PictureOverview> with CommonMixin, EventBusMixin {
final GlobalKey theglobalKey = GlobalKey();
final GlobalKey<_ExamPaperDrawingState> examPaperDrawingKey = GlobalKey<_ExamPaperDrawingState>();
final GlobalKey zoomGlobalKey = GlobalKey(); // zoom
double? initScale;
Offset? zoomOffset;
ImageStreamListener? _imageStreamListener;
TestQuestionsImageInfo? imagInfoModel; // 试题图片数据
ImageStream? _imageStream;
final int currentIndex = 0;
late AnnotationGraffitiSwitch graffitiSwitch;
late RemoveListener _annotationsListener; // 批注关闭监听
File? temFile; // 批注临时数据
// 用于记录绘图结果的变量
Offset? globalPosition; // 是否正在绘制
MarkingHistoryZoomInfo? zoomInfo;
bool illegalArea = false; // 非法区域(批阅笔迹不在试题图片中)
final GlobalKey _zoomKey = GlobalKey<State<Zoom>>();
@override
void initState() {
super.initState();
FastData.getInstance().getMarkingZoomInfo().then((value) {
if (value == null) return;
bool flag = value.questionNum == widget.questionNum && value.markingUserId == widget.markingUserId;
if (flag) {
if (value.positionX != 0 && value.positionY != 0) {
// zoomOffset = Offset(value.positionX, value.positionY);
}
if (value.scale < 1) {
initScale = value.scale;
// 5
Future.delayed(Duration(seconds: 1), () => ref.read(zoomHistoryProvider.notifier).setState(initScale!));
}
}
});
Future.delayed(Duration.zero, () {
ref.read(zoomHeightProvider.notifier).setState(0.0);
});
_annotationsListener = ref.read(annotationGraffitiSwitchProvider.notifier).addListener((state) {
graffitiSwitch = state;
toUpState(setState, () {}, mounted);
});
// 事件总线监听 清空数据
eventOn(callback: (BottomAnnotationSwitchCleanallOfMarking item) async {
if (ref.read(drawMarkingProvider).data.isEmpty) {
if (widget.data.commentImageUrl.isNotEmpty) {
bool? res = await showDialog<bool>(
// 表示点击灰色背景的时候是否消失弹出框
barrierDismissible: false,
context: context,
builder: (context1) {
return AlertDialog(content: quickText("是否撤销上次批注痕迹"), actions: <Widget>[
TextButton(child: quickText("取消"), onPressed: () => Navigator.pop(context1, false)),
TextButton(child: quickText("确定", color: Theme.of(context).primaryColor), onPressed: () => Navigator.pop(context1, true))
]);
},
);
if (res == true) {
widget.data.commentImageUrl.removeAt(currentIndex);
var theUrl = widget.imageItems[currentIndex];
widget.commentImageMap[theUrl] = theUrl;
toUpState(setState, () {}, mounted);
}
} else {
ToastUtils.showInfo('批注已清空');
}
}
});
}
@override
void dispose() {
_annotationsListener();
eventCancel();
try {
_imageStream?.removeListener(_imageStreamListener!);
if (temFile != null) {
bool flieExist = temFile!.existsSync();
if (flieExist) temFile!.delete();
}
// if (zoomOffset != null) saveZoomPosition();
} catch (e) {}
super.dispose();
}
Future<FileResult?> saveImage() async {
try {
ToastUtils.showLoading();
if (temFile == null) {
if (examPaperDrawingKey.currentState?.pointsPureData.isEmpty ?? true) return null;
// 没有图片就上传图片
RenderRepaintBoundary? boundary = theglobalKey.currentContext!.findRenderObject() as RenderRepaintBoundary?;
if (boundary == null) return null;
// double dpr = MediaQuery.of(context).devicePixelRatio;
double pixelRatio = MediaQuery.of(context).devicePixelRatio;
if (imagInfoModel?.pixelRatio != null) pixelRatio = imagInfoModel!.pixelRatio;
ui.Image image = await boundary.toImage(pixelRatio: pixelRatio);
ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
if (byteData == null) {
/// 等于null 已经是异常了
return null;
}
Directory appDocDir = await getApplicationDocumentsDirectory();
List<int> bytes = byteData.buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes);
String filePath = '${appDocDir.path}/${Uuid().v1()}.png'; // 文件路径及名称
File file = File(filePath); // 创建文件对象
await file.writeAsBytes(bytes); // 将ByteData写入文件
temFile = file;
}
if (imagInfoModel != null) {
// 剪切图片
}
crypto.Digest fileMd5 = crypto.md5.convert(await temFile!.readAsBytes());
RestClient _client = await getClient();
BaseStructureResult<UploadFileInterfaceConfig> resUploadConfig = await _client.getUploadFile(UploadFileInterfaceConfigParams(
fileName: '1.png',
fileMd5: fileMd5.toString(),
contentLength: temFile!.lengthSync(),
));
if (!resUploadConfig.success || resUploadConfig.data == null) {
ToastUtils.getFluttertoast(msg: '获取图片上传配置失败', context: context);
return null;
}
if (resUploadConfig.data!.uploadUri == null) {
return FileResult(myObject: '', success: true, url: resUploadConfig.data!.downloadUri);
}
FileResult? resFile = await ref.read(uploadFileProvider.notifier).getUploadFileConfig(resUploadConfig.data!, temFile!);
// FileResult? resFile = await ref
// .read(uploadFileProvider.notifier)
// .uploadFile(temFile!.path, widget.imageItems[currentIndex], ref.read(userProvider).id.toString());
if (resFile != null && resFile.success) {
resFile.otherParam = currentIndex;
return resFile;
}
} catch (e) {
toPrint(val: e.toString());
} finally {
// ToastUtils.dismiss();
}
return null;
}
// 缩放组件 ==> 位置更新
void onPositionUpdate(Offset position) async {
MarkingHistoryZoomInfo? historyZoomInfo = await FastData.getInstance().getMarkingZoomInfo();
double? scale = historyZoomInfo?.scale ?? 1;
MarkingHistoryZoomInfo info = MarkingHistoryZoomInfo(
scale: scale,
positionX: position.dx,
positionY: position.dy,
questionNum: widget.questionNum,
markingUserId: widget.markingUserId,
);
FastData.getInstance().setMarkingZoomInfo(info);
}
void saveZoomPosition() async {
MarkingHistoryZoomInfo? historyZoomInfo = await FastData.getInstance().getMarkingZoomInfo();
if (historyZoomInfo != null) {
FastData.getInstance().setMarkingZoomInfo(MarkingHistoryZoomInfo(
scale: historyZoomInfo.scale,
positionX: zoomOffset!.dx,
positionY: zoomOffset!.dy,
questionNum: historyZoomInfo.questionNum,
markingUserId: historyZoomInfo.markingUserId,
));
}
}
void onPanUpPosition(Offset val) async {
// 手指在移动 非物体移动的位置
// print('**************** 正在移动位置 YYY:${val.dy}');
// print('**************** 正在移动位置 XXX:${val.dx}');
zoomOffset = val;
}
// 缩放组件 ==> 缩放监听
void onScaleUpdate(double scale, double zoom) async {
// print('zoom$zoom');
// print('scale${scale}');
// MarkingHistoryZoomInfo? historyZoomInfo = await FastData.getInstance().getMarkingZoomInfo();
MarkingHistoryZoomInfo? historyZoomInfo = await FastData.getInstance().getMarkingZoomInfo();
double positionX = historyZoomInfo?.positionX ?? 0;
double positionY = historyZoomInfo?.positionY ?? 0;
MarkingHistoryZoomInfo info = MarkingHistoryZoomInfo(
scale: zoom,
positionX: positionX,
positionY: positionY,
questionNum: widget.questionNum,
markingUserId: widget.markingUserId,
);
zoomInfo = info;
if (double.parse(zoom.toStringAsFixed(2)) <= 1) zoom = 1;
if (imagInfoModel != null) {
// 根据缩放比例重置被放大的图片的尺寸
imagInfoModel = TestQuestionsImageInfo(
// 获取图片的宽高
boxHeight: imagInfoModel!.boxHeight,
boxWidth: imagInfoModel!.boxWidth,
url: imagInfoModel!.url,
height: imagInfoModel!.height,
width: imagInfoModel!.width,
zoom: zoom,
);
}
FastData.getInstance().setMarkingZoomInfo(info);
}
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
height: double.infinity,
alignment: Alignment.center,
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
double containerWidth = constraints.maxWidth;
double containerHeight = constraints.maxHeight;
return $LocalAndNetworkSwitch(
zoomGlobalKey: zoomGlobalKey,
containerWidth: containerWidth,
containerHeight: containerHeight,
imagePosition: widget.imagePosition ?? Offset(0, 0),
imageScale: widget.imageScale,
homework: widget.homework,
theglobalKey: theglobalKey,
graffitiSwitch: graffitiSwitch,
drawFlag: widget.annotationsFlag,
examGlobalKey: examPaperDrawingKey,
testQuestionNumber: widget.testQuestionNumber,
imageUrl: widget.commentImageMap[widget.imageItems[currentIndex]]!,
updateTempFileCall: (File? file) {
temFile = file;
print('更新需要上传的文件');
},
imageBuilder: (imageBuilderContext, imageProvider) {
Image imageWidget = Image(image: imageProvider, fit: BoxFit.fitWidth);
if (imagInfoModel == null || (imagInfoModel?.boxHeight != containerHeight || imagInfoModel?.boxWidth != containerWidth)) {
if (_imageStreamListener != null) _imageStream?.removeListener(_imageStreamListener!);
_imageStreamListener = ImageStreamListener((ImageInfo info, bool _) {
imagInfoModel = TestQuestionsImageInfo(
// 获取图片的宽高
boxHeight: containerHeight,
boxWidth: containerWidth,
url: widget.commentImageMap[widget.imageItems[currentIndex]]!,
height: info.image.height.toDouble(),
width: info.image.width.toDouble(),
);
Future.delayed(Duration.zero, () {
ref.read(zoomHeightProvider.notifier).setState(imagInfoModel?.scaleHeight ?? 0.0);
});
});
_imageStream = imageWidget.image.resolve(ImageConfiguration())..addListener(_imageStreamListener!);
}
var btnEnum = ref.watch(doPaperBottomReviewMarksProvider);
// return imageWidget;
return Listener(
behavior: HitTestBehavior.opaque,
onPointerMove: (PointerMoveEvent details) {
if (btnEnum != ReviewMarksBottomBtnsEnum.HANDWRITING) return;
if (globalPosition != null) {
// 预防双指同时作用于屏幕
double dx = globalPosition!.dx;
double dy = globalPosition!.dy;
double dxNew = details.localPosition.dx;
double dyNew = details.localPosition.dy;
if ((dxNew - dx).abs() > 22 || (dyNew - dy).abs() > 22) return;
}
globalPosition = details.localPosition;
Offset localPosition = globalPosition!;
if (imagInfoModel != null &&
(localPosition.dy < imagInfoModel!.imageHeightOffsetStart! || localPosition.dy > imagInfoModel!.imageHeightOffsetend!)) {
// 笔迹画出图片区域 直接断笔
var dataVal = ref.read(drawMarkingProvider).data;
if (dataVal.length - 1 > -1 && dataVal[dataVal.length - 1].data != null) {
var newVal = ref.read(drawMarkingProvider).data..add(GestureRecording(eraser: graffitiSwitch.openEraser));
var newVal1 = ref.read(drawMarkingProvider).offsets..add(null);
ref.read(drawMarkingProvider.notifier).setState(DrawMarkingVal(newVal, newVal1));
}
illegalArea = true;
return;
}
illegalArea = false;
double remainingHeight = imagInfoModel!.imageHeightOffsetStart!; // 剩余高度
if (remainingHeight > 1) {
localPosition = Offset(localPosition.dx, localPosition.dy - remainingHeight);
// if (zoomOffset != null) {
// // var density = (imagInfoModel!.boxWidth / imagInfoModel!.scaleWidth!);
// localPosition = Offset(localPosition.dx/, localPosition.dy);
// }
} else {
if (zoomOffset != null) {
localPosition = Offset(localPosition.dx, localPosition.dy + zoomOffset!.dy.abs());
}
}
if (imagInfoModel?.zoom != null && imagInfoModel?.zoom != 1) {
// 计算视图被放大比例 还原笔迹坐标
localPosition = Offset(localPosition.dx / imagInfoModel!.zoom, localPosition.dy / imagInfoModel!.zoom);
if (zoomOffset != null) {
// 如果滚动条有触动就加上滚动条滚动的位置
localPosition = Offset(localPosition.dx + zoomOffset!.dx.abs() / imagInfoModel!.zoom, localPosition.dy);
}
}
var newVal = ref.read(drawMarkingProvider).data..add(GestureRecording(eraser: graffitiSwitch.openEraser, data: localPosition));
var newVal1 = ref.read(drawMarkingProvider).offsets..add(localPosition);
ref.read(drawMarkingProvider.notifier).setState(DrawMarkingVal(newVal, newVal1));
},
// onPointerDown: (PointerDownEvent event) {
// },
onPointerUp: (PointerUpEvent details) {
if (btnEnum != ReviewMarksBottomBtnsEnum.HANDWRITING) return;
// 如果在空白区域 非试题图片区域就返回
if (illegalArea) return;
globalPosition = null;
var newVal = ref.read(drawMarkingProvider).data..add(GestureRecording(eraser: graffitiSwitch.openEraser));
var newVal1 = ref.read(drawMarkingProvider).offsets..add(null);
ref.read(drawMarkingProvider.notifier).setState(DrawMarkingVal(newVal, newVal1));
},
child: IgnorePointer(
ignoring: btnEnum != ReviewMarksBottomBtnsEnum.DRAG,
child: Zoom(
key: _zoomKey,
// initTotalZoomOut: true,
child: ExamPaperDrawing(
key: examPaperDrawingKey,
globalKey: theglobalKey,
child: imageWidget,
graffitiSwitch: graffitiSwitch,
decoration: const BoxDecoration(color: const Color.fromRGBO(249, 250, 254, 1)),
),
maxZoomWidth: containerWidth,
canvasColor: Colors.transparent,
backgroundColor: Colors.transparent,
maxZoomHeight: imagInfoModel?.scaleHeight != null ? (imagInfoModel!.scaleHeight! / imagInfoModel!.zoom) : null,
initScale: initScale ?? 1,
initPosition: zoomOffset,
onScaleUpdate: onScaleUpdate,
onPositionUpdate: onPanUpPosition,
),
),
);
},
);
},
));
}
}
// 试卷绘制
class ExamPaperDrawing extends StatefulHookConsumerWidget {
// String imgUrl;
Widget child;
BoxDecoration? decoration;
AnnotationGraffitiSwitch graffitiSwitch;
GlobalKey globalKey;
// Function(String) imageCall;
ExamPaperDrawing({
// required this.imgUrl,
required this.child,
required this.graffitiSwitch,
required this.globalKey,
this.decoration,
Key? key,
}) : super(key: key);
@override
_ExamPaperDrawingState createState() => _ExamPaperDrawingState();
}
class _ExamPaperDrawingState extends ConsumerState<ExamPaperDrawing> with EventBusMixin<BottomAnnotationSwitchCleanallOfMarking> {
// 用于记录手指位置的变量
late RemoveListener removeListener;
late ValueNotifier<List<GestureRecording>> _vnHandWritings;
late List<Offset?> pointsPureData = [];
@override
void initState() {
_vnHandWritings = ValueNotifier<List<GestureRecording>>([]);
removeListener = ref.read(drawMarkingProvider.notifier).addListener((state) {
pointsPureData = state.offsets;
_vnHandWritings.value = [...state.data];
}, fireImmediately: false);
// 事件总线监听
eventOn(callback: (BottomAnnotationSwitchCleanallOfMarking item) {
if (item.previousStep) {
if (ref.read(drawMarkingProvider).data.isEmpty) {
return;
}
var index = pointsPureData.toList().lastIndexOf(null);
if (index != -1) {
if (index + 1 == pointsPureData.length) {
pointsPureData = pointsPureData.sublist(0, index);
ref.read(drawMarkingProvider.notifier).setState(DrawMarkingVal(ref.read(drawMarkingProvider).data.sublist(0, index), pointsPureData));
index = pointsPureData.toList().lastIndexOf(null);
index == -1 ? -1 : index + 1;
}
if (index != -1) {
pointsPureData = pointsPureData.sublist(0, index);
ref.read(drawMarkingProvider.notifier).setState(DrawMarkingVal(ref.read(drawMarkingProvider).data.sublist(0, index), pointsPureData));
} else {
item.cleanAll = true;
}
} else {
item.cleanAll = true;
}
}
if (item.cleanAll) {
pointsPureData.clear();
ref.read(drawMarkingProvider.notifier).setState(DrawMarkingVal([], []));
}
});
super.initState();
}
@override
void dispose() {
eventCancel();
removeListener();
_vnHandWritings.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
print('_ExamPaperDrawingState的build....');
return RepaintBoundary(
key: widget.globalKey,
child: CustomPaint(
isComplex: true,
willChange: true,
foregroundPainter: DrawingPainter(ctrl: _vnHandWritings),
child: widget.child,
),
);
}
}
class DrawingPainter extends CustomPainter {
final ValueNotifier<List<GestureRecording>> ctrl;
final Paint paintBrush = Paint();
DrawingPainter({required this.ctrl}) : super(repaint: ctrl) {
paintBrush
..color = Colors.red
..strokeCap = StrokeCap.round
..strokeWidth = 1.5.r;
}
@override
void paint(Canvas canvas, Size size) {
var points = ctrl.value;
var pointsLength = points.length;
print('数据.....................[[[[[${points.length}]]]]]');
for (int i = 0; i < pointsLength; i++) {
GestureRecording item = points[i];
Offset? offsetData = item.data;
Offset? nextOffsetData = pointsLength - 1 == i ? null : points[i + 1].data;
if (offsetData != null && nextOffsetData != null) {
canvas.drawLine(offsetData, nextOffsetData, paintBrush);
}
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
print('FFFFFF55555555555555');
// if (oldDelegate is DrawingPainter) {
// var repaint = ctrl.value.length != oldDelegate.ctrl.value.length || oldDelegate.ctrl.value != ctrl.value;
// print('调用是否绘制:$repaint');
// return repaint;
// }
return false;
}
}
/**
* 手势记录
*/
class GestureRecording {
bool eraser; // 是否是橡皮擦
Offset? data; // 位置记录 可能为null
GestureRecording({required this.eraser, this.data});
}
@hwidget
Widget $myCachedNetworkImage({
required String imageUrl,
required File? tempFile,
required double width,
required double height,
required ImageWidgetBuilder? imageBuilder,
}) {
UseCachedImgRefresh _useImgRefsh = UseCachedImgRefresh.use();
if (tempFile != null) {
// 注释临时本地图片
return Image.file(tempFile, fit: BoxFit.contain, width: double.infinity, height: double.infinity);
}
useEffect(() {
_useImgRefsh.eventOnSub<SwitchKeyboardToReloadImages>(callback: (SwitchKeyboardToReloadImages event) {
if (event.reload) {
_useImgRefsh.imageKey.value = UniqueKey();
}
});
return () {
_useImgRefsh.eventCancelSub();
};
}, []);
return CacheNetImageView(
cacheNetImageKey: _useImgRefsh.imageKey.value,
imageUrl: imageUrl,
imageBuilder: imageBuilder,
errorWidget: (context, url, error) {
return GestureDetector(
onTap: () => (_useImgRefsh.imageKey.value = UniqueKey()),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset('assets/images/test_paper_loading_failed.png'),
quickText('加载失败,点击重试', color: Color.fromRGBO(148, 163, 182, 1), size: 12.sp),
],
),
);
},
);
}
class CacheNetImageView extends ConsumerWidget {
final Key cacheNetImageKey;
final String imageUrl;
final ImageWidgetBuilder? imageBuilder;
final LoadingErrorWidgetBuilder? errorWidget;
const CacheNetImageView({
required this.cacheNetImageKey,
required this.imageUrl,
required this.imageBuilder,
required this.errorWidget,
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
double zoomHeight = ref.watch(zoomHeightProvider);
double initScale = ref.watch(zoomHistoryProvider);
print('zoomView的视图最大高度:$zoomHeight 和 缩放比例$initScale');
return CachedNetworkImage(
key: cacheNetImageKey,
imageUrl: imageUrl,
fit: BoxFit.fitWidth,
width: double.infinity,
alignment: Alignment.center,
imageBuilder: imageBuilder,
placeholder: (context, url) => Center(child: SpinKitWave(color: Theme.of(context).primaryColor, size: 50.r)),
errorWidget: errorWidget,
);
}
}
// double zoomHeight = ref.watch(zoomHeightProvider);
/// 网络图和本地图切换
@hwidget
Widget $localAndNetworkSwitch(
BuildContext context, {
double imageScale = 1,
Offset imagePosition = const Offset(0, 0),
required ImageWidgetBuilder? imageBuilder,
required GlobalKey zoomGlobalKey,
required double containerWidth,
required double containerHeight,
required String testQuestionNumber, // 试题题号
required String imageUrl, // 图片
required bool drawFlag, // 是否打开绘画操作
required GlobalKey theglobalKey,
required bool homework,
required GlobalKey<_ExamPaperDrawingState> examGlobalKey,
required AnnotationGraffitiSwitch graffitiSwitch,
required Function(File?) updateTempFileCall,
}) {
// ImageStream? imageStream; // 图片监听数据
// TestQuestionsImageInfo? imagInfoModel; // 试题图片数据
// ImageStreamListener theImageStreamListener;
UseLocalAndNetworkSwitch _useSwitch = UseLocalAndNetworkSwitch.use(!homework);
UseZoomImageHistory _useZoomHistory = UseZoomImageHistory.use(testQuestionNumber);
// TransformationController _transContller = useTransformationController();
useValueChanged<File?, String>(
_useSwitch.temFile.value,
(oldValue, oldResult) {
updateTempFileCall(_useSwitch.temFile.value);
return null;
},
);
useValueChanged<String, String>(testQuestionNumber, (oldValue, oldResult) {
if (testQuestionNumber.length > 0) {
_useZoomHistory.initInfo(testQuestionNumber);
}
});
useValueChanged<bool, String>(drawFlag, (oldValue, oldResult) {
if (!drawFlag) {
// 关闭的时候创建临时图片文件在设备
_useSwitch.createTempFile(context, theglobalKey: theglobalKey, examGlobalKey: examGlobalKey).then((File? theFile) {
if (theFile == null) {
// TODO 代表保存失败的逻辑
// 当前情况_useSwich.showZoomImg.value 没有设置为true还是展示的原来的绘图组件ExamPaperDrawing
toPrint(val: '进入错误逻辑.........');
}
_useSwitch.showZoomImg.value = true;
});
return;
}
_useSwitch.showZoomImg.value = !drawFlag;
});
useEffect(() {
_useZoomHistory.initInfo(); // 初始化历史数据
// _useScrollController
// ..addListener(() {
// _scrollPosition.value = _useScrollController.position.pixels;
// });
return () {
// try {
// _useImageSize.imageStream.value?.removeListener(_useImageSize.imageListener.value!);
// } catch (e) {}
// ..removeListener(() {})
};
}, []);
print('是否更新视图.... ${_useZoomHistory.initPosition.value}');
return $MyCachedNetworkImage(
imageUrl: imageUrl,
tempFile: _useSwitch.temFile.value,
width: containerWidth,
height: containerHeight,
imageBuilder: imageBuilder,
);
}
class UseLocalAndNetworkSwitch {
ValueNotifier<bool> showZoomImg;
ValueNotifier<File?> temFile;
ValueNotifier<List<GestureRecording>?> points;
ValueNotifier<List<dynamic>?> pointsPureData;
ValueNotifier<Map<String, ui.Image>> imageLoaded;
UseLocalAndNetworkSwitch._({
required this.showZoomImg,
required this.temFile,
required this.points,
required this.pointsPureData,
required this.imageLoaded,
});
// 工厂构造函数
factory UseLocalAndNetworkSwitch.use(bool defaultVal) {
return UseLocalAndNetworkSwitch._(
points: useState(null),
pointsPureData: useState(null),
showZoomImg: useState(defaultVal),
temFile: useState(null),
imageLoaded: useState({}),
);
}
Future<File?> createTempFile(
BuildContext context, {
required GlobalKey theglobalKey,
required GlobalKey<_ExamPaperDrawingState> examGlobalKey,
}) async {
Timer? _timer;
try {
_timer = Timer(Duration(seconds: 1), () {
// 执行操作的代码
ToastUtils.showLoading();
});
if (examGlobalKey.currentState?.pointsPureData.isEmpty ?? true) {
try {
temFile.value?.delete();
} catch (e) {}
temFile.value = null;
return null;
}
RenderRepaintBoundary? boundary = theglobalKey.currentContext!.findRenderObject() as RenderRepaintBoundary?;
if (boundary == null) return null;
ui.Image image = await boundary.toImage(pixelRatio: 3.0);
ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
if (byteData != null) {
Directory appDocDir = await getApplicationDocumentsDirectory();
List<int> bytes = byteData.buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes);
String filePath = '${appDocDir.path}/${Uuid().v1()}.png'; // 文件路径及名称
File file = File(filePath); // 创建文件对象
await file.writeAsBytes(bytes); // 将ByteData写入文件
temFile.value?.delete();
temFile.value = file; // 保存临时文件
points.value = examGlobalKey.currentState?._vnHandWritings.value;
pointsPureData.value = examGlobalKey.currentState?.pointsPureData;
toPrint(val: '图片保存成功:');
return temFile.value;
}
} catch (e) {
toPrint(val: '图片生成错误:${e}');
toPrint(val: e.toString());
ToastUtils.getFluttertoast(context: context, msg: '保存图片报错,请稍后重试');
} finally {
_timer?.cancel();
ToastUtils.dismiss();
}
return null;
}
}