diff --git a/marking_app/README.md b/marking_app/README.md index 0b70d96..7622a87 100644 --- a/marking_app/README.md +++ b/marking_app/README.md @@ -14,3 +14,7 @@ A few resources to get you started if this is your first Flutter project: For help getting started with Flutter development, view the [online documentation](https://docs.flutter.dev/), which offers tutorials, samples, guidance on mobile development, and a full API reference. + + + +fvm flutter build apk --release --no-tree-shake-icons \ No newline at end of file diff --git a/marking_app/android/app/src/main/AndroidManifest.xml b/marking_app/android/app/src/main/AndroidManifest.xml index 8348071..df14510 100644 --- a/marking_app/android/app/src/main/AndroidManifest.xml +++ b/marking_app/android/app/src/main/AndroidManifest.xml @@ -35,7 +35,14 @@ android:name="flutterEmbedding" android:value="2" /> - + + + + @@ -47,8 +54,10 @@ - - + + + + @@ -58,4 +67,6 @@ + + diff --git a/marking_app/android/app/src/main/res/xml/file_paths.xml b/marking_app/android/app/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000..c4e8da4 --- /dev/null +++ b/marking_app/android/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/marking_app/android/app/src/profile/AndroidManifest.xml b/marking_app/android/app/src/profile/AndroidManifest.xml index 6281e88..186b8b4 100644 --- a/marking_app/android/app/src/profile/AndroidManifest.xml +++ b/marking_app/android/app/src/profile/AndroidManifest.xml @@ -13,9 +13,13 @@ - + - + + + + + diff --git a/marking_app/lib/common/model/enum/review_marks_bottom_btns_enum.dart b/marking_app/lib/common/model/enum/review_marks_bottom_btns_enum.dart new file mode 100644 index 0000000..5698a07 --- /dev/null +++ b/marking_app/lib/common/model/enum/review_marks_bottom_btns_enum.dart @@ -0,0 +1,6 @@ +enum ReviewMarksBottomBtnsEnum { + DRAG, // 拖动 + HANDWRITING, // 笔迹 + RETURN_PREVIOUS_LEVEL, // 返回上一级 + CLEAR_ALL // 清空全部 +} diff --git a/marking_app/lib/common/model/event_bus/bottom_annotation_switch_cleanall.dart b/marking_app/lib/common/model/event_bus/bottom_annotation_switch_cleanall.dart index 89e53ca..fec4a61 100644 --- a/marking_app/lib/common/model/event_bus/bottom_annotation_switch_cleanall.dart +++ b/marking_app/lib/common/model/event_bus/bottom_annotation_switch_cleanall.dart @@ -15,8 +15,23 @@ class BottomAnnotationSwitchCleanall extends Object { BottomAnnotationSwitchCleanall({this.cleanAll = false, this.previousStep = false, this.uploadImage = false}); - factory BottomAnnotationSwitchCleanall.fromJson(Map srcJson) => - _$BottomAnnotationSwitchCleanallFromJson(srcJson); + factory BottomAnnotationSwitchCleanall.fromJson(Map srcJson) => _$BottomAnnotationSwitchCleanallFromJson(srcJson); Map toJson() => _$BottomAnnotationSwitchCleanallToJson(this); } + +@JsonSerializable() +class BottomAnnotationSwitchCleanallOfMarking extends Object { + @JsonKey(name: 'cleanAll') + bool cleanAll; + + @JsonKey(name: 'previousStep') + bool previousStep; + + BottomAnnotationSwitchCleanallOfMarking({this.cleanAll = false, this.previousStep = false}); + + factory BottomAnnotationSwitchCleanallOfMarking.fromJson(Map srcJson) => + _$BottomAnnotationSwitchCleanallOfMarkingFromJson(srcJson); + + Map toJson() => _$BottomAnnotationSwitchCleanallOfMarkingToJson(this); +} diff --git a/marking_app/lib/common/model/job/gesture_recording.dart b/marking_app/lib/common/model/job/gesture_recording.dart index 5e36be8..f021a6e 100644 --- a/marking_app/lib/common/model/job/gesture_recording.dart +++ b/marking_app/lib/common/model/job/gesture_recording.dart @@ -41,3 +41,11 @@ class GestureHandwritingRecording { required this.intervalTime, }); } + +/** + * 笔记还原页面 查看原稿 + */ +class ShowStudentMmanuscript { + bool showManuscript; // 查看原稿 + ShowStudentMmanuscript(this.showManuscript); +} diff --git a/marking_app/lib/common/model/job/test_questions_image_info.dart b/marking_app/lib/common/model/job/test_questions_image_info.dart index e9a1e7c..23539ea 100644 --- a/marking_app/lib/common/model/job/test_questions_image_info.dart +++ b/marking_app/lib/common/model/job/test_questions_image_info.dart @@ -35,8 +35,17 @@ class TestQuestionsImageInfo extends Object { double? imageHeightOffsetend; - TestQuestionsImageInfo( - {required this.width, required this.height, required this.url, required this.boxWidth, required this.boxHeight, this.pixelRatio = 1}) { + double zoom; // 图片放大比例 + + TestQuestionsImageInfo({ + required this.width, + required this.height, + required this.url, + required this.boxWidth, + required this.boxHeight, + this.pixelRatio = 1, + this.zoom = 1, + }) { // print('图片宽度:$width'); // print('图片高度:$height'); @@ -46,11 +55,11 @@ class TestQuestionsImageInfo extends Object { pixelRatio = width / boxWidth; scale = boxWidth / width; - scaleHeight = scale! * height; - scaleWidth = scale! * width; + scaleHeight = scale! * height * zoom; + scaleWidth = scale! * width * zoom; if (scaleHeight != null) { - imageHeightOffsetStart = (boxHeight - scaleHeight!) / 2; + imageHeightOffsetStart = boxHeight <= scaleHeight! ? 0 : (boxHeight - scaleHeight!) / 2; imageHeightOffsetend = imageHeightOffsetStart! + scaleHeight!; } } diff --git a/marking_app/lib/common/model/marking/marking_text_question.dart b/marking_app/lib/common/model/marking/marking_text_question.dart index f206ab9..d917720 100644 --- a/marking_app/lib/common/model/marking/marking_text_question.dart +++ b/marking_app/lib/common/model/marking/marking_text_question.dart @@ -96,9 +96,9 @@ class MarkingTextQuestion extends Object { @JsonKey(name: 'papersUrlStr') List? papersUrlStr; - // 批注图片 预提交集合 - @JsonKey(name: 'commentImageUrlTodo') - List? commentImageUrlTodo; + // // 批注图片 预提交集合 + // @JsonKey(name: 'commentImageUrlTodo') + // List? commentImageUrlTodo; // 批注图片Map @JsonKey(name: 'commentImageUrlMap') @@ -115,8 +115,8 @@ class MarkingTextQuestion extends Object { @JsonKey(name: 'historicalScorings') List? historicalScorings; - MarkingTextQuestion(this.id, this.totalScore, this.isFinish, this.subQuestionDetailList, this.isException, - this.questionNum, this.score, this.studentAnswerList, + MarkingTextQuestion( + this.id, this.totalScore, this.isFinish, this.subQuestionDetailList, this.isException, this.questionNum, this.score, this.studentAnswerList, // this.questionIndex, {required this.commentImageUrl, required this.lastOne, @@ -130,7 +130,7 @@ class MarkingTextQuestion extends Object { // this.totalCount = 0, // this.currentIndex = 0, this.papersUrl = const [], - this.commentImageUrlTodo, + // this.commentImageUrlTodo, this.commentImageUrlMap = const {}}) { score = isFinish ? score : null; completeRating = isFinish && score != null; @@ -140,14 +140,20 @@ class MarkingTextQuestion extends Object { commentImageUrlMap = Map(); if (commentImageUrl.isNotEmpty) { - for (var element in commentImageUrl) { - commentImageUrlMap[element] = '$element?${DateTime.now().millisecondsSinceEpoch}'; + // for (var element in commentImageUrl) { + // commentImageUrlMap[element] = '$element?${DateTime.now().millisecondsSinceEpoch}'; + // } + for (var i = 0; i < commentImageUrl.length; i++) { + var element = commentImageUrl[i]; + var originalImage = studentAnswerList[i]; + commentImageUrlMap[originalImage] = 'https:$element?${DateTime.now().millisecondsSinceEpoch}'; } } else { for (var element in studentAnswerList) { commentImageUrlMap[element] = element; } } + // if (historicalScore != null) { // this.historicalScorings = (jsonDecode(historicalScore!) as List).map((e) { // return HistoricalScoring.fromJson(e as Map); @@ -165,10 +171,6 @@ class MarkingTextQuestion extends Object { factory MarkingTextQuestion.fromJson(Map srcJson) => _$MarkingTextQuestionFromJson(srcJson); List getImageData() { - if (commentImageUrlTodo != null && commentImageUrlTodo!.isNotEmpty) { - return commentImageUrlTodo!; - } - if (commentImageUrl.isNotEmpty) { return commentImageUrl; } @@ -178,10 +180,8 @@ class MarkingTextQuestion extends Object { bool setImageList(String resImage, int imageIndex) { try { - commentImageUrlTodo ??= getImageData().map((e) => e).toList(); String oldImage = studentAnswerList[imageIndex]; commentImageUrlMap[oldImage] = '$resImage?${DateTime.now().millisecondsSinceEpoch}'; - commentImageUrlTodo![imageIndex] = resImage; return true; } catch (e) { return false; @@ -220,11 +220,7 @@ class SubQuestions extends Object { bool completeRating; SubQuestions( - {required this.subQuestionScore, - required this.subQuestionNum, - this.subQuestionGotScore, - required this.isFinish, - this.completeRating = false}) { + {required this.subQuestionScore, required this.subQuestionNum, this.subQuestionGotScore, required this.isFinish, this.completeRating = false}) { subQuestionGotScore = isFinish ? subQuestionGotScore : null; completeRating = isFinish && subQuestionGotScore != null; } diff --git a/marking_app/lib/components/PictureOverview.dart b/marking_app/lib/components/PictureOverview.dart index 545f4c5..d9e4fb4 100644 --- a/marking_app/lib/components/PictureOverview.dart +++ b/marking_app/lib/components/PictureOverview.dart @@ -22,6 +22,7 @@ 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'; @@ -29,10 +30,12 @@ import 'package:marking_app/common/model/job/upload_file_interface_config_params 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'; @@ -46,7 +49,8 @@ import 'dart:ui' as ui; import 'package:uuid/uuid.dart'; import 'package:zoom_widget/zoom_widget.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); @@ -66,6 +70,8 @@ class PictureOverview extends StatefulHookConsumerWidget { final bool annotationsFlag; final String testQuestionNumber; final Map commentImageMap; + final MarkingTextQuestion data; + final Function callAnnotationTips; const PictureOverview({ required this.imageItems, @@ -74,6 +80,8 @@ class PictureOverview extends StatefulHookConsumerWidget { required this.testQuestionNumber, required this.questionNum, required this.markingUserId, + required this.data, + required this.callAnnotationTips, this.homework = false, this.imageScale = 1, this.imagePosition, @@ -84,7 +92,7 @@ class PictureOverview extends StatefulHookConsumerWidget { PictureOverviewState createState() => PictureOverviewState(); } -class PictureOverviewState extends ConsumerState with CommonMixin { +class PictureOverviewState extends ConsumerState with CommonMixin, EventBusMixin { final GlobalKey theglobalKey = GlobalKey(); final GlobalKey<_ExamPaperDrawingState> examPaperDrawingKey = GlobalKey<_ExamPaperDrawingState>(); final GlobalKey zoomGlobalKey = GlobalKey(); // zoom @@ -100,6 +108,12 @@ class PictureOverviewState extends ConsumerState with CommonMix late RemoveListener _annotationsListener; // 批注关闭监听 File? temFile; // 批注临时数据 + // 用于记录绘图结果的变量 + Offset? globalPosition; // 是否正在绘制 + MarkingHistoryZoomInfo? zoomInfo; + bool illegalArea = false; // 非法区域(批阅笔迹不在试题图片中) + final GlobalKey _zoomKey = GlobalKey>(); + @override void initState() { super.initState(); @@ -108,11 +122,12 @@ class PictureOverviewState extends ConsumerState with CommonMix 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); + // zoomOffset = Offset(value.positionX, value.positionY); } if (value.scale < 1) { initScale = value.scale; - Future.delayed(Duration(seconds: 5), () => ref.read(zoomHistoryProvider.notifier).setState(initScale!)); + // 5 + Future.delayed(Duration(seconds: 1), () => ref.read(zoomHistoryProvider.notifier).setState(initScale!)); } } }); @@ -123,19 +138,49 @@ class PictureOverviewState extends ConsumerState with CommonMix graffitiSwitch = state; toUpState(setState, () {}, mounted); }); + + // 事件总线监听 清空数据 + eventOn(callback: (BottomAnnotationSwitchCleanallOfMarking item) async { + widget.callAnnotationTips(); // 调用回调 通知父级批注记录被更改 + if (ref.read(drawMarkingProvider).data.isEmpty) { + if (widget.data.commentImageUrl.isNotEmpty) { + bool? res = await showDialog( + // 表示点击灰色背景的时候是否消失弹出框 + barrierDismissible: false, + context: context, + builder: (context1) { + return AlertDialog(content: quickText("是否撤销上次批阅批注痕迹"), actions: [ + 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() { - super.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 saveImage() async { @@ -146,9 +191,12 @@ class PictureOverviewState extends ConsumerState with CommonMix // 没有图片就上传图片 RenderRepaintBoundary? boundary = theglobalKey.currentContext!.findRenderObject() as RenderRepaintBoundary?; if (boundary == null) return null; - double dpr = MediaQuery.of(context).devicePixelRatio; - // double dpr = ui.window.devicePixelRatio; - ui.Image image = await boundary.toImage(pixelRatio: dpr); + // 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 已经是异常了 @@ -162,11 +210,14 @@ class PictureOverviewState extends ConsumerState with CommonMix temFile = file; } - crypto.Digest fileMd5 = crypto.md5.convert(await temFile!.readAsBytes()); + if (imagInfoModel != null) { + // 剪切图片 + } + crypto.Digest fileMd5 = crypto.md5.convert(await temFile!.readAsBytes()); RestClient _client = await getClient(); - BaseStructureResult resUploadConfig = - await _client.getUploadFile(UploadFileInterfaceConfigParams( + + BaseStructureResult resUploadConfig = await _client.getMarkingUploadFile(UploadFileInterfaceConfigParams( fileName: '1.png', fileMd5: fileMd5.toString(), contentLength: temFile!.lengthSync(), @@ -179,8 +230,7 @@ class PictureOverviewState extends ConsumerState with CommonMix 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).getUploadFileConfig(resUploadConfig.data!, temFile!); // FileResult? resFile = await ref // .read(uploadFileProvider.notifier) // .uploadFile(temFile!.path, widget.imageItems[currentIndex], ref.read(userProvider).id.toString()); @@ -210,27 +260,60 @@ class PictureOverviewState extends ConsumerState with CommonMix 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 scale1) async { - print('这是第一个scale:$scale'); - print('这是第二个noScale:$scale1'); + 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: scale1, + 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) { - DoMarkingKeyboardModel _model = ref.watch(markingKeyboardProvider); return Container( width: double.infinity, height: double.infinity, @@ -257,10 +340,9 @@ class PictureOverviewState extends ConsumerState with CommonMix temFile = file; print('更新需要上传的文件'); }, - imageBuilder: (context, imageProvider) { + imageBuilder: (imageBuilderContext, imageProvider) { Image imageWidget = Image(image: imageProvider, fit: BoxFit.fitWidth); - if (imagInfoModel == null || - (imagInfoModel?.boxHeight != containerHeight || imagInfoModel?.boxWidth != containerWidth)) { + if (imagInfoModel == null || (imagInfoModel?.boxHeight != containerHeight || imagInfoModel?.boxWidth != containerWidth)) { if (_imageStreamListener != null) _imageStream?.removeListener(_imageStreamListener!); _imageStreamListener = ImageStreamListener((ImageInfo info, bool _) { imagInfoModel = TestQuestionsImageInfo( @@ -271,7 +353,6 @@ class PictureOverviewState extends ConsumerState with CommonMix height: info.image.height.toDouble(), width: info.image.width.toDouble(), ); - printJson(imagInfoModel!.toJson()); Future.delayed(Duration.zero, () { ref.read(zoomHeightProvider.notifier).setState(imagInfoModel?.scaleHeight ?? 0.0); }); @@ -279,20 +360,111 @@ class PictureOverviewState extends ConsumerState with CommonMix _imageStream = imageWidget.image.resolve(ImageConfiguration())..addListener(_imageStreamListener!); } + var btnEnum = ref.watch(doPaperBottomReviewMarksProvider); // return imageWidget; - return Zoom( - // initTotalZoomOut: true, - child: imageWidget, - maxZoomWidth: containerWidth, - canvasColor: Colors.transparent, - backgroundColor: Colors.transparent, - maxZoomHeight: imagInfoModel?.scaleHeight, - initScale: initScale ?? 1, - initPosition: zoomOffset, - // initPosition: , - // onPositionUpdate: onPositionUpdate, - onScaleUpdate: onScaleUpdate, - // zoomSensibility: 0.5, + 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; + + var _theKey = _zoomKey.currentState; + print(_theKey); + + double remainingHeight = imagInfoModel!.imageHeightOffsetStart!; // 剩余高度 + if (remainingHeight > 1) { + localPosition = Offset(localPosition.dx, localPosition.dy - remainingHeight); + } + + // print(localPosition.dy); + print(localPosition.dx); + + double _theZoomVal = imagInfoModel?.zoom ?? 1; + var _dx = zoomOffset?.dx ?? 0; + _dx = _dx > 0 ? 0 : _dx.abs() / _theZoomVal; + var _dy = zoomOffset?.dy ?? 0; + _dy = _dy > 0 ? 0 : _dy.abs() / _theZoomVal; + + if (_theZoomVal > 1) { + // 计算视图被放大比例 还原笔迹坐标 + localPosition = Offset(localPosition.dx / _theZoomVal, localPosition.dy / _theZoomVal); + if (zoomOffset != null) { + // 如果滚动条有触动就加上滚动条滚动的位置 + localPosition = Offset(localPosition.dx + _dx, localPosition.dy + _dy); + } + } else if (_theZoomVal < 1) { + // 试图被缩小 + double imgSpaceWidthOfSingle = (imagInfoModel!.boxWidth - imagInfoModel!.scaleWidth!) / 2; + localPosition = Offset((localPosition.dx - imgSpaceWidthOfSingle) / _theZoomVal, localPosition.dy / _theZoomVal + _dy); + // localPosition = Offset(localPosition.dx * _theZoomVal - imgSpaceWidthOfSingle, localPosition.dy / _theZoomVal); + } else { + localPosition = Offset(localPosition.dx, localPosition.dy + _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)); + widget.callAnnotationTips(); + }, + // 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, + ), + ), ); }, ); @@ -303,24 +475,17 @@ class PictureOverviewState extends ConsumerState with CommonMix // 试卷绘制 class ExamPaperDrawing extends StatefulHookConsumerWidget { - String imgUrl; - bool homework; + // String imgUrl; + Widget child; BoxDecoration? decoration; AnnotationGraffitiSwitch graffitiSwitch; - List? points; - List? pointsPureData; - ValueNotifier> imageLoaded; - GlobalKey globalKey; // Function(String) imageCall; ExamPaperDrawing({ - required this.imgUrl, - required this.homework, - required this.points, - required this.pointsPureData, + // required this.imgUrl, + required this.child, required this.graffitiSwitch, required this.globalKey, - required this.imageLoaded, this.decoration, Key? key, }) : super(key: key); @@ -329,73 +494,38 @@ class ExamPaperDrawing extends StatefulHookConsumerWidget { _ExamPaperDrawingState createState() => _ExamPaperDrawingState(); } -class _ExamPaperDrawingState extends ConsumerState - with EventBusMixin { - late Future _future; // 考试试卷 - +class _ExamPaperDrawingState extends ConsumerState with EventBusMixin { // 用于记录手指位置的变量 - late List points; - late List pointsPureData; - // 用于记录绘图结果的变量 - bool _isEraserPressed = false; // 橡皮擦按下 - Offset? _eraserPosition; // 按下位置 - Offset? globalPosition = null; // 是否正在绘制 - - Future loadImage(String url) async { - try { - Map map = widget.imageLoaded.value; - ui.Image? image = map[url]; - if (image != null) { - return image; - } - final httpClient = HttpClient(); - final request = await httpClient.getUrl(Uri.parse(url)); - final response = await request.close(); - final bytes = await consolidateHttpClientResponseBytes(response); - final codec = await ui.instantiateImageCodec(bytes); - final frame = await codec.getNextFrame(); - ui.Image theImage = frame.image; - map[url] = theImage; - return theImage; - } catch (e) { - print('请求图片报错:${e.toString()}'); - } - return null; - } - - // void _onPointerDown(DragDownDetails details) { - // if (widget.graffitiSwitch.openEraser) { - // _eraserPosition = (context.findRenderObject() as RenderBox).globalToLocal(details.globalPosition); - // _isEraserPressed = true; - // toUpState(setState, ()=>{}, mounted); - // } - // } + late RemoveListener removeListener; + late ValueNotifier> _vnHandWritings; + late List pointsPureData = []; @override void initState() { - points = widget.points ?? []; - pointsPureData = widget.pointsPureData ?? []; - print('图片地址:${widget.imgUrl}'); - _future = loadImage(widget.imgUrl); + _vnHandWritings = ValueNotifier>([]); + removeListener = ref.read(drawMarkingProvider.notifier).addListener((state) { + pointsPureData = state.offsets; + _vnHandWritings.value = [...state.data]; + }, fireImmediately: false); + // 事件总线监听 - eventOn(callback: (BottomAnnotationSwitchCleanall item) { + eventOn(callback: (BottomAnnotationSwitchCleanallOfMarking item) { if (item.previousStep) { - if (points.isEmpty) { - ToastUtils.showInfo('批注已清空'); + 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); - points.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); - points = points.sublist(0, index); - toUpState(setState, () {}, mounted); + ref.read(drawMarkingProvider.notifier).setState(DrawMarkingVal(ref.read(drawMarkingProvider).data.sublist(0, index), pointsPureData)); } else { item.cleanAll = true; } @@ -406,21 +536,7 @@ class _ExamPaperDrawingState extends ConsumerState if (item.cleanAll) { pointsPureData.clear(); - points.clear(); - toUpState(setState, () {}, mounted); - } - - if (item.uploadImage) { - // 图片确认按钮,生成 - if (pointsPureData.isEmpty) { - ToastUtils.showInfo('请先批注再提交'); - return; - } - // _saveImage().then((FileResult? res) { - // if (res != null) { - // widget.imageCall(res.url!); - // } - // }); + ref.read(drawMarkingProvider.notifier).setState(DrawMarkingVal([], [])); } }); super.initState(); @@ -428,196 +544,65 @@ class _ExamPaperDrawingState extends ConsumerState @override void dispose() { - super.dispose(); eventCancel(); + removeListener(); + _vnHandWritings.dispose(); + super.dispose(); } @override Widget build(BuildContext context) { - return MyFutureBuilder.buildFutureBuilderOfSingleInstance( - context, - _future, - (ui.Image? theImage) { - if (theImage == null) return const Center(child: Text('图片加载错误')); + print('_ExamPaperDrawingState的build....'); - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - final double containerWidth = constraints.maxWidth; // 展示区域总宽度 - final double containerHeight = constraints.maxHeight; // 展示区域总宽度 - - final double imageWidth = theImage.width.toDouble(); // 图片原始宽度 - final double imageHeight = theImage.height.toDouble(); // 图片原始高度 - final double widthRatio = containerWidth / imageWidth; // - final double heightRatio = containerHeight / imageHeight; - final double scale = widthRatio > heightRatio ? heightRatio : widthRatio; - final double destWidth = imageWidth * scale; - final double destHeight = imageHeight * scale; - - final bool homework = widget.homework; - - return GestureDetector( - behavior: HitTestBehavior.opaque, - onPanUpdate: (DragUpdateDetails details) { - if (globalPosition != null) { - // 预防双指同时作用于屏幕 - double dx = globalPosition!.dx; - double dy = globalPosition!.dy; - - double dxNew = details.globalPosition.dx; - double dyNew = details.globalPosition.dy; - if ((dxNew - dx).abs() > 22 || (dyNew - dy).abs() > 22) { - return; - } - } - globalPosition = details.globalPosition; - try { - if (widget.graffitiSwitch.openBrush || widget.graffitiSwitch.openEraser) { - RenderBox renderBox = context.findRenderObject() as RenderBox; - Offset localPosition = renderBox.globalToLocal(details.globalPosition); - pointsPureData = List.from(pointsPureData)..add(localPosition); - points = List.from(points) - ..add(GestureRecording(eraser: widget.graffitiSwitch.openEraser, data: localPosition)); - _eraserPosition = localPosition; - _isEraserPressed = true; - setState(() {}); - } - } catch (e) { - toPrint(val: '进入报错'); - } - }, - onPanEnd: (DragEndDetails details) { - print('离开.............'); - globalPosition = null; - if (widget.graffitiSwitch.openBrush || widget.graffitiSwitch.openEraser) { - pointsPureData.add(null); // 增加空点以分隔不同的线段 - points.add(GestureRecording(eraser: widget.graffitiSwitch.openEraser)); - _isEraserPressed = false; - _eraserPosition = null; - } - }, - child: RepaintBoundary( - key: widget.globalKey, - child: CustomPaint( - // isComplex: true, - // willChange: true, - painter: DrawingPainter( - image: theImage, - points: points, - isErasing: widget.graffitiSwitch.openEraser, - destWidth: destWidth, - destHeight: destHeight, - imageWidth: imageWidth, - imageHeight: imageHeight, - homework: homework, - containerWidth: containerWidth, - containerHeight: containerHeight, - ), - // size: Size(homework ? containerWidth : destWidth, homework ? containerHeight : destHeight), - size: Size(containerWidth, containerHeight), - ), - ), - ); - }, - ); - }, + return RepaintBoundary( + key: widget.globalKey, + child: CustomPaint( + isComplex: true, + willChange: true, + foregroundPainter: DrawingPainter(ctrl: _vnHandWritings), + child: widget.child, + ), ); } } class DrawingPainter extends CustomPainter { - final List points; - final bool isErasing; - final ui.Image image; - final bool homework; - final double containerWidth; - final double containerHeight; - double destWidth; - double destHeight; - final double imageWidth; - final double imageHeight; - - // final Rect destRect; - // final Rect srcRect; - final bool openErasing; - DrawingPainter({ - required this.homework, - required this.points, - required this.isErasing, - required this.image, - required this.destWidth, - required this.destHeight, - required this.containerWidth, - required this.containerHeight, - required this.imageWidth, - required this.imageHeight, - }) : - // destRect = Rect.fromLTWH(0, 0,destWidth,destHeight), - // srcRect = Rect.fromLTWH(0, 0, imageWidth, imageHeight), - openErasing = points.isNotEmpty && isErasing, - super(); - - Paint paintBrush = Paint() - ..color = Colors.red - ..strokeCap = StrokeCap.round - ..strokeWidth = 1.5; - - Paint eraser = Paint() - ..blendMode = BlendMode.clear - ..color = Colors.transparent - ..style = PaintingStyle.stroke - ..strokeCap = StrokeCap.round - ..strokeWidth = 100; - - final emptyPaint = Paint(); - final emptyPaintWithWidth = Paint()..strokeWidth = 60.0; + final ValueNotifier> 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) { - double offsetX = (size.width - destWidth) / 2; - double offsetY = (size.height - destHeight) / 2; - if (destWidth < (size.width / 2)) { - destWidth = size.width / 2; - offsetX = (size.width - destWidth) / 2; - } - - // final rect = Rect.fromCenter(center: center, width: destWidth, height: destHeight); - Rect srcRect = Rect.fromLTRB(0, 0, image.width.toDouble(), image.height.toDouble()); - // Rect destRect = Rect.fromLTRB(offsetX, offsetY, containerWidth, containerHeight); - Rect destRect = Offset(offsetX, offsetY) & Size(destWidth, destHeight); - - canvas.drawImageRect(image, srcRect, destRect, emptyPaint); - - // canvas.drawImage(image, Offset.zero, emptyPaint); - if (points.isNotEmpty) { - // canvas.saveLayer(destRect, emptyPaintWithWidth); // 只绘制图片大小区域 - canvas.saveLayer(Rect.largest, emptyPaintWithWidth); // 整个视图区域 - - canvas.drawColor(Colors.transparent, BlendMode.clear); - } - - for (int i = 0; i < points.length - 1; i++) { + var points = ctrl.value; + var pointsLength = points.length; + print('数据.....................[[[[[${points.length}]]]]]'); + for (int i = 0; i < pointsLength; i++) { GestureRecording item = points[i]; - GestureRecording nextItem = points[i + 1]; Offset? offsetData = item.data; - Offset? nextOffsetData = nextItem.data; + Offset? nextOffsetData = pointsLength - 1 == i ? null : points[i + 1].data; if (offsetData != null && nextOffsetData != null) { - canvas.drawLine(offsetData, nextOffsetData, !item.eraser ? paintBrush : eraser); + canvas.drawLine(offsetData, nextOffsetData, paintBrush); } } - - // 恢复画布状态. - if (points.isNotEmpty) canvas.restore(); } - // @override - // bool shouldRepaint(DrawingPainter oldDelegate) { - // List thePoints = oldDelegate.points; - // // var flag = oldDelegate.points != points || oldDelegate.isErasing != isErasing; - // return thePoints != points; - // } - @override - bool shouldRepaint(DrawingPainter oldDelegate) => true; + 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; + } } /** @@ -676,51 +661,6 @@ Widget $myCachedNetworkImage({ ); }, ); - - // return Container( - // width: width, - // height: height, - // color: Colors.red, - // alignment: Alignment.center, - // child: tempFile != null - // ? Image.file( - // tempFile, - // fit: BoxFit.contain, - // width: double.infinity, - // height: double.infinity, - // ) - // : CachedNetworkImage( - // key: _useImgRefsh.imageKey.value, - // fit: BoxFit.contain, - // width: double.infinity, - // // height: double.infinity, - // imageUrl: imageUrl, - // placeholder: (context, url) => - // Center(child: SpinKitWave(color: Theme.of(context).primaryColor, size: 50.r)), - // // imageBuilder: (context, imageProvider) => Container( - // // decoration: BoxDecoration( - // // image: DecorationImage( - // // image: imageProvider, - // // fit: BoxFit.fitWidth, - // // // colorFilter: ColorFilter.mode(Colors.red, BlendMode.colorBurn), - // // ), - // // ), - // // ), - // 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 { @@ -802,9 +742,7 @@ Widget $localAndNetworkSwitch( useValueChanged(drawFlag, (oldValue, oldResult) { if (!drawFlag) { // 关闭的时候创建临时图片文件在设备 - _useSwitch - .createTempFile(context, theglobalKey: theglobalKey, examGlobalKey: examGlobalKey) - .then((File? theFile) { + _useSwitch.createTempFile(context, theglobalKey: theglobalKey, examGlobalKey: examGlobalKey).then((File? theFile) { if (theFile == null) { // TODO 代表保存失败的逻辑 // 当前情况:_useSwich.showZoomImg.value 没有设置为true还是展示的原来的绘图组件ExamPaperDrawing @@ -832,50 +770,13 @@ Widget $localAndNetworkSwitch( }, []); print('是否更新视图.... ${_useZoomHistory.initPosition.value}'); - return _useSwitch.showZoomImg.value - ? - /** - Scrollbar( - // thumbVisibility: true, - thumbVisibility: true, - controller: _useScrollController, - child: SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), - padding: EdgeInsets.zero, - scrollDirection: Axis.vertical, // 设置垂直滚动 - child: Transform.scale( - scale: 0.4, - alignment: Alignment.topCenter, - child: $MyCachedNetworkImage( - imageUrl: imageUrl, - tempFile: _useSwitch.temFile.value, - width: containerWidth, - height: containerHeight, - ), - ), - ), - )*/ - - /** */ - $MyCachedNetworkImage( - imageUrl: imageUrl, - tempFile: _useSwitch.temFile.value, - width: containerWidth, - height: containerHeight, - imageBuilder: imageBuilder, - ) - : ExamPaperDrawing( - imgUrl: imageUrl, - graffitiSwitch: graffitiSwitch, - points: _useSwitch.points.value, - pointsPureData: _useSwitch.pointsPureData.value, - decoration: const BoxDecoration(color: const Color.fromRGBO(249, 250, 254, 1)), - globalKey: theglobalKey, - key: examGlobalKey, - imageLoaded: _useSwitch.imageLoaded, - homework: homework, - // imageCall: (String str) => widget.imageCall(str, currentIndex), - ); + return $MyCachedNetworkImage( + imageUrl: imageUrl, + tempFile: _useSwitch.temFile.value, + width: containerWidth, + height: containerHeight, + imageBuilder: imageBuilder, + ); } class UseLocalAndNetworkSwitch { @@ -938,7 +839,7 @@ class UseLocalAndNetworkSwitch { temFile.value?.delete(); temFile.value = file; // 保存临时文件 - points.value = examGlobalKey.currentState?.points; + points.value = examGlobalKey.currentState?._vnHandWritings.value; pointsPureData.value = examGlobalKey.currentState?.pointsPureData; toPrint(val: '图片保存成功:'); return temFile.value; diff --git a/marking_app/lib/components/TestPaperItem.dart b/marking_app/lib/components/TestPaperItem.dart index 813c8f1..4293660 100644 --- a/marking_app/lib/components/TestPaperItem.dart +++ b/marking_app/lib/components/TestPaperItem.dart @@ -29,9 +29,7 @@ class TestPaperItem extends ConsumerWidget with CommonMixin { final bool isHomeworkCorrection; final VoidCallback? call; final MarkingListType? markingtype; - const TestPaperItem( - {required this.markingItem, this.markingtype, this.isHomeworkCorrection = false, this.call, Key? key}) - : super(key: key); + const TestPaperItem({required this.markingItem, this.markingtype, this.isHomeworkCorrection = false, this.call, Key? key}) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { @@ -65,7 +63,7 @@ class TestPaperItem extends ConsumerWidget with CommonMixin { padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.h), constraints: BoxConstraints( minHeight: 120.h, - maxHeight: 130.h, + maxHeight: 144.h, ), decoration: BoxDecoration( color: Colors.white, @@ -117,8 +115,7 @@ class TestPaperItem extends ConsumerWidget with CommonMixin { margin: EdgeInsets.only(right: 8.w), alignment: Alignment.center, decoration: BoxDecoration( - borderRadius: - BorderRadius.only(topLeft: Radius.circular(6.r), bottomRight: Radius.circular(6.r)), + borderRadius: BorderRadius.only(topLeft: Radius.circular(6.r), bottomRight: Radius.circular(6.r)), color: const Color.fromRGBO(245, 108, 108, 0.236), ), child: Text( @@ -137,8 +134,7 @@ class TestPaperItem extends ConsumerWidget with CommonMixin { margin: EdgeInsets.only(right: 8.w), alignment: Alignment.center, decoration: BoxDecoration( - borderRadius: - BorderRadius.only(topLeft: Radius.circular(6.r), bottomRight: Radius.circular(6.r)), + borderRadius: BorderRadius.only(topLeft: Radius.circular(6.r), bottomRight: Radius.circular(6.r)), color: const Color.fromRGBO(4, 201, 208, 0.10), ), child: Text( @@ -157,8 +153,7 @@ class TestPaperItem extends ConsumerWidget with CommonMixin { margin: EdgeInsets.only(right: 8.w), alignment: Alignment.center, decoration: BoxDecoration( - borderRadius: - BorderRadius.only(topLeft: Radius.circular(6.r), bottomRight: Radius.circular(6.r)), + borderRadius: BorderRadius.only(topLeft: Radius.circular(6.r), bottomRight: Radius.circular(6.r)), color: const Color.fromRGBO(231, 236, 255, 1), ), child: Text( @@ -270,10 +265,7 @@ class TestPaperItem extends ConsumerWidget with CommonMixin { Text( '${markingItem.finishCount}', style: TextStyle( - color: markingItem.isFinish && isHomeworkCorrection - ? Colors.green - : Theme.of(context).primaryColor, - fontSize: 12.sp), + color: markingItem.isFinish && isHomeworkCorrection ? Colors.green : Theme.of(context).primaryColor, fontSize: 12.sp), ), Text( '/', @@ -299,8 +291,7 @@ class TestPaperItem extends ConsumerWidget with CommonMixin { style: TextStyle(color: Colors.white, fontSize: 8.sp), ), // linearStrokeCap: LinearStrokeCap.butt, - progressColor: - markingItem.isFinish && isHomeworkCorrection ? Colors.green : Theme.of(context).primaryColor, + progressColor: markingItem.isFinish && isHomeworkCorrection ? Colors.green : Theme.of(context).primaryColor, backgroundColor: const Color.fromRGBO(219, 224, 243, 1), barRadius: Radius.circular(10.r), ), @@ -503,9 +494,7 @@ class TestPaperItem extends ConsumerWidget with CommonMixin { RestClient client = await getClient(); BaseStructureResult result = await client.endMarkingTask(markingUserId); if (result.code == RequestConfig.successCode && result.data == null ? false : result.data!) { - ref - .read(currentTaskIdProvider.notifier) - .setDoTaskEntity(CurrentReviewTask(taskId: markingItem.markingUserId, refresh: true)); + ref.read(currentTaskIdProvider.notifier).setDoTaskEntity(CurrentReviewTask(taskId: markingItem.markingUserId, refresh: true)); } else { ToastUtils.getFluttertoast(context: context, msg: '提交失败'); } diff --git a/marking_app/lib/components/marking/bottom_annotation_switch.dart b/marking_app/lib/components/marking/bottom_annotation_switch.dart deleted file mode 100644 index 607722f..0000000 --- a/marking_app/lib/components/marking/bottom_annotation_switch.dart +++ /dev/null @@ -1,298 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:flutter_screenutil/flutter_screenutil.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:marking_app/common/model/enum/KeyboardType.dart'; -import 'package:marking_app/common/model/event_bus/bottom_annotation_switch_cleanall.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/pages/common/event_bus_mixin.dart'; -import 'package:marking_app/provider/annotation_graffiti_switch_provider.dart'; -import 'package:marking_app/provider/do_marking_provider.dart'; -import 'package:marking_app/utils/index.dart'; - -/** - * 底部批注开关 - */ -class BottomAnnotationSwitch extends StatefulHookConsumerWidget { - final double? maxWidth; - final bool homework; - const BottomAnnotationSwitch({this.maxWidth, this.homework = false, Key? key}) : super(key: key); - - @override - _BottomAnnotationSwitchState createState() => _BottomAnnotationSwitchState(); -} - -class _BottomAnnotationSwitchState extends ConsumerState - with SingleTickerProviderStateMixin, EventBusMixin { - late RemoveListener _annotationsListener; // 批注关闭监听 - late AnimationController _animationController; // 动画 - late AnnotationGraffitiSwitch graffitiSwitch; - late DoMarkingKeyboardModel _preferenceModel; - late double upperBound; - late double lowerBound; - Color? bgc; - bool isIos = false; - - @override - void initState() { - if (Platform.isIOS) { - toPrint(val: 'IOS'); - isIos = true; - } else if (Platform.isAndroid) { - toPrint(val: '安卓'); - } - var graffitiHander = ref.read(annotationGraffitiSwitchProvider.notifier); - if (widget.homework) { - setTimeOut(500, () { - graffitiHander.setSwitch(true); - if (!graffitiHander.state.openBrush) graffitiHander.setSwitchBrush(); - }); // 默认打开可以书写 - } - - _preferenceModel = ref.read(markingKeyboardProvider.notifier).state; // 偏好设置 - bool isVertical = _preferenceModel.screenDirection == ScreenDirection.VERTICAL_SCREEN; // 是否是垂直方向 - - switch (_preferenceModel.keyboard) { - case KeyboardType.RIGHT_SELECTION: - double isVerticalNumber = 58.w; - double noVerticalNumber = 28.w; - if (isIos) { - noVerticalNumber = 50.w; - } - upperBound = ScreenUtil().screenWidth - (isVertical ? isVerticalNumber : noVerticalNumber); - lowerBound = isVertical ? 34.w : 18.w; - break; - case KeyboardType.INPUT_TYPE: - // 输入型键盘不存在竖屏 - upperBound = ScreenUtil().screenWidth - (isIos ? 115.w : 95.w); - lowerBound = 18.w; - - break; - case KeyboardType.BOTTOM_SELECTION: - upperBound = ScreenUtil().screenWidth; - lowerBound = isVertical ? 34.w : 18.w; - - break; - } - - upperBound = widget.maxWidth ?? upperBound; - _animationController = AnimationController( - value: graffitiHander.state.annotationSwitch ? upperBound : lowerBound, // 设置默认值 - duration: const Duration(milliseconds: 300), - lowerBound: lowerBound, - upperBound: upperBound, - vsync: this, - )..addListener(toUp); - - _annotationsListener = graffitiHander.addListener((state) { - graffitiSwitch = state; - if (state.annotationSwitch) { - bgc = const Color.fromRGBO(51, 57, 62, 1); - // toUp(); - _animationController.forward(); - } else { - _animationController.reverse(); - setTimeOut(300, () => bgc = null); - } - }); - - super.initState(); - } - - @override - void dispose() { - _annotationsListener(); - _animationController - ..removeListener(toUp) - ..dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - ScreenDirection _screenDirection = _preferenceModel.screenDirection; - bool isVertical = _screenDirection == ScreenDirection.VERTICAL_SCREEN; // 是否是垂直 - - // print('当前屏幕情况:${graffitiSwitch.openBrush}'); - // print(isVertical); - - AnnotationGraffitiSwitch _graffitiSwitch = ref.watch(annotationGraffitiSwitchProvider); - - double barrierSize = ScreenUtil().screenWidth / (isVertical ? 20 : 6); - Color actionColor = Colors.white; - Color defaultColor = Color.fromRGBO(132, 146, 163, 1); - return OrientationBuilder( - builder: (BuildContext context, Orientation orientation) { - bool isVertical = orientation == Orientation.portrait; - - double iconSize = (isVertical ? 32 : 28).sp; - return Container( - height: 52.h, - // width: widget.homework ? double.infinity : _animationController.value, - width: _animationController.value, - color: widget.homework ? Color.fromRGBO(51, 57, 62, 0.2) : bgc, - padding: EdgeInsets.symmetric(vertical: 1.h), - child: widget.homework - ? Row( - children: [ - SizedBox(width: barrierSize + 20.w), - InkWell( - onTap: () { - easyThrottle('setSwitchBrush', - () => ref.read(annotationGraffitiSwitchProvider.notifier).setSwitchBrush()); - }, - child: Icon(const IconData(0xe623, fontFamily: "AlibabaIcon"), - size: iconSize, color: _graffitiSwitch.openBrush ? actionColor : defaultColor), - ), - SizedBox(width: 24.w), - InkWell( - onTap: () { - eventFire(model: BottomAnnotationSwitchCleanall(previousStep: true)); - }, - child: Icon(IconData(0xe61d, fontFamily: "AlibabaIcon"), size: iconSize, color: defaultColor), - ), - const Expanded(child: SizedBox()), - InkWell( - onTap: () { - easyThrottle('setSwitchMagnifier', - () => ref.read(annotationGraffitiSwitchProvider.notifier).setMagnifier()); - }, - // IconData(0xe62f, fontFamily: "AlibabaIcon") - child: Icon(IconData(0xe634, fontFamily: "AlibabaIcon"), - size: iconSize, color: _graffitiSwitch.magnifier ? actionColor : defaultColor), - ), - SizedBox(width: 24.w), - InkWell( - onTap: () { - eventFire(model: BottomAnnotationSwitchCleanall(cleanAll: true)); - }, - child: Icon(IconData(0xe61f, fontFamily: "AlibabaIcon"), size: iconSize, color: defaultColor), - ), - SizedBox(width: 24.w), - InkWell( - onTap: () { - easyThrottle( - 'setSwitchMagnifier', - () => ref.read(annotationGraffitiSwitchProvider.notifier).setTrajectory(), - ); - }, - child: Icon( - IconData(0xe629, fontFamily: "AlibabaIcon"), - size: iconSize, - color: _graffitiSwitch.trajectoryDisplay ? actionColor : defaultColor, - ), - ), - SizedBox(width: isIos ? 40.w : 50.w), - ], - ) - : Stack( - alignment: const FractionalOffset(0, 0.5), - children: [ - if (bgc != null) - Row( - children: [ - SizedBox(width: barrierSize + 20.w), - InkWell( - onTap: () { - easyThrottle('setSwitchBrush', - () => ref.read(annotationGraffitiSwitchProvider.notifier).setSwitchBrush()); - }, - child: Icon(const IconData(0xe623, fontFamily: "AlibabaIcon"), - size: iconSize, color: _graffitiSwitch.openBrush ? actionColor : defaultColor), - ), - SizedBox(width: 24.w), - InkWell( - onTap: () { - eventFire(model: BottomAnnotationSwitchCleanall(previousStep: true)); - }, - child: - Icon(IconData(0xe61d, fontFamily: "AlibabaIcon"), size: iconSize, color: defaultColor), - ), - - // 不需要橡皮擦 - // InkWell( - // onTap: () { - // easyThrottle( - // 'setSwitchEraser', () => ref.read(annotationGraffitiSwitchProvider.notifier).setSwitchEraser()); - // }, - // child: Icon(const IconData(0xe61c, fontFamily: "AlibabaIcon"), - // color: _graffitiSwitch.openEraser ? Theme.of(context).primaryColor : Colors.white), - // ), - const Expanded(child: SizedBox()), - InkWell( - onTap: () { - easyThrottle('setSwitchMagnifier', - () => ref.read(annotationGraffitiSwitchProvider.notifier).setMagnifier()); - }, - child: Icon(IconData(0xe62f, fontFamily: "AlibabaIcon"), - size: iconSize, color: _graffitiSwitch.magnifier ? actionColor : defaultColor), - ), - SizedBox(width: 24.w), - InkWell( - onTap: () { - eventFire(model: BottomAnnotationSwitchCleanall(cleanAll: true)); - }, - child: - Icon(IconData(0xe61f, fontFamily: "AlibabaIcon"), size: iconSize, color: defaultColor), - ), - // SizedBox(width: 24.w), - // InkWell( - // onTap: (){ - // eventFire(model:BottomAnnotationSwitchCleanall(uploadImage: true)); - // }, - // child: Column( - // mainAxisAlignment: MainAxisAlignment.center, - // children: [ - // Icon(const IconData(0xe614, fontFamily: "AlibabaIcon"), color: Colors.white,size: 22.sp,), - // quickText('提交批注',color: Colors.white,size: 9.sp), - // ], - // ) - // ), - - SizedBox(width: isIos ? 40.w : 26.w), - ], - ), - InkWell( - onTap: () { - easyThrottle('setSwitchMarkingGraffiti', () { - if ([upperBound, lowerBound].contains(_animationController.value)) { - ref - .read(annotationGraffitiSwitchProvider.notifier) - .setSwitch(!graffitiSwitch.annotationSwitch); - } - }); - }, - child: Container( - // width: isVertical ? 34.w : 16.w, - width: barrierSize, - padding: EdgeInsets.only(top: 4.h, bottom: 4.h, right: 3.w), - alignment: Alignment.centerRight, - decoration: BoxDecoration( - color: const Color.fromRGBO(253, 147, 21, 1), - borderRadius: BorderRadius.only( - topRight: Radius.circular(30.sp), - bottomRight: Radius.circular(30.sp), - ), - ), - child: Icon( - !graffitiSwitch.annotationSwitch - ? const IconData(0xe622, fontFamily: "AlibabaIcon") - : const IconData(0xe621, fontFamily: "AlibabaIcon"), - color: Colors.white, - size: isVertical ? 20.sp : 26.sp, - ), - ), - ) - ], - ), - ); - }, - ); - } - - void toUp() { - toUpState(setState, () {}, mounted); - } -} diff --git a/marking_app/lib/pages/homework_correction/components/trajectoryView.dart b/marking_app/lib/pages/homework_correction/components/trajectoryView.dart index c940af3..2ce7669 100644 --- a/marking_app/lib/pages/homework_correction/components/trajectoryView.dart +++ b/marking_app/lib/pages/homework_correction/components/trajectoryView.dart @@ -224,7 +224,7 @@ class TrajectoryViewState extends ConsumerState { var zhixinCall = () async { if (mounted) { - print('执行添加笔画${i},${j}'); + // print('执行添加笔画${i},${j}'); trajectorys = List.from(trajectorys)..add(theRecording); ref.read(jobDrawingTrajectoryProvider.notifier).setVal(trajectorys); } diff --git a/marking_app/lib/pages/homework_correction/job_home.dart b/marking_app/lib/pages/homework_correction/job_home.dart index 9e067a7..c144930 100644 --- a/marking_app/lib/pages/homework_correction/job_home.dart +++ b/marking_app/lib/pages/homework_correction/job_home.dart @@ -1,19 +1,568 @@ +// import 'package:flutter/material.dart'; +// import 'package:flutter/services.dart'; +// import 'package:flutter_hooks/flutter_hooks.dart'; +// import 'package:flutter_screenutil/flutter_screenutil.dart'; +// import 'package:functional_widget_annotation/functional_widget_annotation.dart'; +// import 'package:marking_app/common/mixin/common.dart'; +// import 'package:marking_app/common/model/event_bus/job_home_refresh_bus.dart'; +// import 'package:marking_app/common/model/marking/marking_list_params.dart'; +// import 'package:marking_app/pages/common/event_bus_mixin.dart'; +// import 'package:marking_app/routes/RouterManager.dart'; +// import 'package:marking_app/utils/index.dart'; +// import 'package:marking_app/utils/my_text.dart'; +// import 'package:flutter_easyrefresh/easy_refresh.dart'; + +// import 'package:badges/badges.dart' as badges; + +// part 'job_home.g.dart'; + +// class JobHome extends StatefulWidget { +// const JobHome({super.key}); + +// @override +// State createState() => _JobHomeState(); +// } + +// class _JobHomeState extends State with CommonMixin, EventBusMixin, AutomaticKeepAliveClientMixin { +// @override +// bool get wantKeepAlive => true; + +// late LinkHeaderNotifier _linkNotifier; +// late ValueNotifier _secondFloorOpen; + +// @override +// void initState() { +// getData(); +// eventOn(callback: (JobHomeRefreshBus item) => getData()); +// _linkNotifier = LinkHeaderNotifier(); +// _secondFloorOpen = ValueNotifier(false); +// super.initState(); +// } + +// @override +// void dispose() { +// eventCancel(); +// _linkNotifier.dispose(); +// _secondFloorOpen.dispose(); +// super.dispose(); +// } + +// Future getData() async { +// try { +// var _client = await getClient(); +// var _result = await _client.getJobsByPage(MarkingListParams( +// isFinish: false, +// page: 1, +// limit: 1, +// pageType: 0, +// )); +// var data = _result.data?.total ?? 0; +// eventFire(model: QuantityToBeReviewedData(data)); +// return data; +// } catch (e) { +// return 0; +// } +// } + +// @override +// Widget build(BuildContext context) { +// super.build(context); + +// return AnnotatedRegion( +// value: const SystemUiOverlayStyle( +// systemNavigationBarColor: Color(0xFF000000), +// systemNavigationBarDividerColor: null, +// statusBarColor: Colors.white, +// systemNavigationBarIconBrightness: Brightness.light, +// statusBarIconBrightness: Brightness.dark, +// statusBarBrightness: Brightness.light, +// ), +// child: SizedBox( +// height: ScreenUtil().screenHeight, +// width: ScreenUtil().screenWidth, +// child: Column( +// children: [ +// // 二楼 +// SecondFloorWidget(_linkNotifier, _secondFloorOpen, refreshCall: () => eventFire(model: JobHomeRefreshBus())), +// Expanded( +// child: EasyRefresh.custom( +// header: LinkHeader( +// _linkNotifier, +// extent: 70.0, +// triggerDistance: 70.0, +// completeDuration: Duration(milliseconds: 500), +// ), +// onRefresh: () async { +// if (_secondFloorOpen.value) return; +// // await Future.delayed(Duration(seconds: 2), () { +// // if (mounted) { +// // setState(() { +// // _count = 20; +// // }); +// // } +// // }); +// }, +// onLoad: () async { +// // await Future.delayed(Duration(seconds: 2), () { +// // if (mounted) { +// // setState(() { +// // _count += 20; +// // }); +// // } +// // }); +// }, +// slivers: [ +// SliverAppBar( +// expandedHeight: 300.h, +// pinned: true, +// floating: true, +// backgroundColor: Colors.red, +// flexibleSpace: FlexibleSpaceBar( +// centerTitle: false, +// title: Column( +// mainAxisSize: MainAxisSize.min, +// children: [ +// SlidingData([ +// EntranceModel( +// title: '作业批阅', image: 'assets/images/job_home_marking.png', navigationUrl: RouterManager.jobMainListPagePath), +// EntranceModel( +// title: '学生历史作业', +// image: 'assets/images/job_home_history.png', +// navigationUrl: '${RouterManager.jobStudentGroupPath}?page=history', +// ), +// EntranceModel( +// title: '知识点点掌握', +// image: 'assets/images/job_home_knowledge.png', +// navigationUrl: RouterManager.jobKnowledgePointsPath) +// ]), +// $TermRow([ +// EntranceModel( +// title: '答题轨迹', +// image: 'assets/images/job_home_answer_record.png', +// navigationUrl: RouterManager.answerTrajectoryPath), +// EntranceModel( +// title: '优先批阅设定', +// image: 'assets/images/job_home_youxian.png', +// navigationUrl: '${RouterManager.jobStudentGroupPath}?page=set', +// ) +// ], 0), +// ], +// )), +// ), +// SliverList( +// delegate: SliverChildBuilderDelegate( +// (context, index) { +// return Container( +// height: 40.h, +// color: Colors.amber, +// width: ScreenUtil().screenWidth, +// ); +// }, +// childCount: 10, +// ), +// ), +// ], +// ), +// ), +// ], +// ), +// )); +// } +// } + +// class EntranceModel extends Object { +// String title; +// String image; +// String navigationUrl; +// EntranceModel({required this.title, required this.image, required this.navigationUrl}); +// } + +// class QuantityToBeReviewedData extends Object { +// int num; +// QuantityToBeReviewedData(this.num); +// } + +// @swidget +// Widget $termRow(BuildContext context, List items, int data) { +// var leng = items.length; +// Widget childWidget; +// switch (leng) { +// case 1: +// childWidget = Row(children: [Expanded(child: $TermItem(items[0], data))]); +// break; +// case 2: +// childWidget = Row(children: [ +// Expanded(flex: 9, child: $TermItem(items[0], data)), +// Expanded(flex: 1, child: SizedBox()), +// Expanded(flex: 9, child: $TermItem(items[1], data)), +// ]); +// break; +// case 3: +// double _theHeight = ScreenUtil().screenWidth / 19 + 54.h * 2; +// childWidget = Row( +// children: [ +// Expanded(child: $TermItem(items[0], data, theHeight: _theHeight)), +// SizedBox(width: ScreenUtil().screenWidth / 19), +// Expanded( +// child: SizedBox( +// height: _theHeight, +// child: Column( +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// $TermItem(items[1], data), +// $TermItem(items[2], data), +// ], +// ), +// ), +// ), +// ], +// ); +// break; +// default: +// childWidget = Container(); +// } + +// return Container(padding: EdgeInsets.symmetric(horizontal: 14.w), child: childWidget); +// } + +// @swidget +// Widget $termItem(BuildContext context, EntranceModel e, int data, {double? theHeight}) { +// bool isJob = e.title == '作业批阅'; + +// return Material( +// color: Colors.white, +// elevation: 3.r, +// shadowColor: const Color.fromRGBO(231, 231, 231, 1), +// borderRadius: BorderRadius.all(Radius.circular(8.r)), +// child: InkWell( +// onTap: () => easyThrottle('GO_TO_JOB_HOME_NAVIGATION', () { +// RouterManager.router.navigateTo(context, e.navigationUrl, transition: getTransition()); +// }), + +// // splashColor: splashColor, +// borderRadius: BorderRadius.all(Radius.circular(8.r)), +// child: badges.Badge( +// showBadge: isJob && data > 0, +// ignorePointer: false, +// badgeContent: quickText(data, color: Colors.white, size: 10.sp), +// badgeAnimation: badges.BadgeAnimation.rotation( +// animationDuration: Duration(seconds: 1), +// colorChangeAnimationDuration: Duration(seconds: 1), +// loopAnimation: false, +// curve: Curves.fastOutSlowIn, +// colorChangeAnimationCurve: Curves.easeInCubic, +// ), +// badgeStyle: badges.BadgeStyle( +// badgeColor: Color.fromRGBO(255, 105, 105, 1), +// shape: badges.BadgeShape.square, +// borderRadius: BorderRadius.only(topLeft: Radius.circular(10.r), topRight: Radius.circular(8.5.r), bottomRight: Radius.circular(8.5.r)), +// // borderSide: BorderSide(color: Colors.white, width: 2), +// elevation: 1, +// padding: EdgeInsets.symmetric(horizontal: isPad() ? 11.w : 16.w, vertical: 2.h), +// ), +// position: badges.BadgePosition.topEnd(top: 10.r, end: 10.r), +// child: Container( +// height: theHeight, +// padding: EdgeInsets.symmetric(vertical: 12.h), +// decoration: BoxDecoration( +// borderRadius: BorderRadius.all(Radius.circular(8.r)), +// // boxShadow: [ +// // BoxShadow( +// // color: const Color.fromRGBO(231, 231, 231, 1), +// // offset: Offset(4.w, 6.h), //阴影y轴偏移量 +// // blurRadius: 8, //阴影模糊程度 +// // spreadRadius: 0.2, //阴影扩散程度 +// // ) +// // ], +// // border: Border.all(width: 0.5.w, color: Color.fromARGB(255, 219, 226, 250)), +// ), +// alignment: Alignment.center, +// child: isJob +// ? Column( +// mainAxisAlignment: MainAxisAlignment.center, +// children: [ +// Image.asset(e.image, height: 32.r, width: 32.r, fit: BoxFit.cover), +// SizedBox(height: 6.r), +// quickText(e.title, size: 12.sp, color: Color.fromRGBO(79, 79, 79, 1), fontWeight: FontWeight.w500), +// ], +// ) +// : Row( +// mainAxisAlignment: MainAxisAlignment.center, +// children: [ +// Image.asset(e.image, height: 32.r, width: 32.r, fit: BoxFit.cover), +// SizedBox(width: 6.r), +// quickText(e.title, size: 12.sp, color: Color.fromRGBO(79, 79, 79, 1), fontWeight: FontWeight.w500), +// ], +// ), +// ), +// )), +// ); +// } + +// class SlidingData extends HookWidget with EventBusMixin { +// final List items; +// SlidingData(this.items); + +// @override +// Widget build(BuildContext context) { +// var dataNumber = useState(null); + +// useEffect(() { +// eventOn(callback: (QuantityToBeReviewedData data) => (dataNumber.value = data)); +// return () { +// eventCancel(); +// }; +// }, []); + +// return $TermRow(items, dataNumber.value?.num ?? 0); +// } +// } + +// /// 二楼视图 +// class SecondFloorWidget extends StatefulWidget { +// // Header连接通知器 +// final LinkHeaderNotifier linkNotifier; +// // 二楼开启状态 +// final ValueNotifier secondFloorOpen; +// final Function refreshCall; + +// const SecondFloorWidget(this.linkNotifier, this.secondFloorOpen, {required this.refreshCall, Key? key}) : super(key: key); + +// @override +// State createState() => SecondFloorWidgetState(); +// } + +// class SecondFloorWidgetState extends State { +// // 触发二楼高度 +// final double _openSecondFloorExtent = 100.0; +// // 指示器值 +// double? _indicatorValue = 0.0; + +// // 二楼高度 +// double _secondFloor = 0.0; +// // 显示展开收起动画 +// bool _toggleAnimation = false; +// Duration _toggleAnimationDuration = Duration(milliseconds: 300); +// // 二楼是否打开 +// bool _isOpen = false; + +// RefreshMode get _refreshState => widget.linkNotifier.refreshState; +// double get _pulledExtent => widget.linkNotifier.pulledExtent; + +// @override +// void initState() { +// widget.linkNotifier.addListener(onLinkNotify); +// super.initState(); +// } + +// void onLinkNotify() { +// setState(() { +// if (_refreshState == RefreshMode.armed || _refreshState == RefreshMode.refresh) { +// _indicatorValue = null; +// // 判断是否到展开二楼 +// if (widget.secondFloorOpen.value && !_toggleAnimation) { +// _isOpen = true; +// _secondFloor = MediaQuery.of(context).size.height - 60.h; +// _toggleAnimation = true; +// Future.delayed(_toggleAnimationDuration, () { +// if (mounted) { +// setState(() { +// _toggleAnimation = false; +// }); +// } +// }); +// } +// } else if (_refreshState == RefreshMode.refreshed || _refreshState == RefreshMode.done) { +// _indicatorValue = 1.0; +// } else { +// if (_refreshState == RefreshMode.inactive) { +// _indicatorValue = 0.0; +// _toggleAnimation = true; +// Future.delayed(_toggleAnimationDuration, () { +// if (mounted) { +// setState(() { +// _toggleAnimation = false; +// }); +// } +// }); +// } else { +// double indicatorValue = _pulledExtent / 70.0 * 0.8; +// _indicatorValue = indicatorValue < 0.8 ? indicatorValue : 0.8; +// // 判断是否到达打开二楼高度 +// if (_refreshState == RefreshMode.drag) { +// if (_pulledExtent >= _openSecondFloorExtent) { +// widget.secondFloorOpen.value = true; +// } else { +// widget.secondFloorOpen.value = false; +// } +// } +// } +// } +// }); +// } + +// @override +// Widget build(BuildContext context) { +// // var spaceWidth = SizedBox(height: ScreenUtil().screenWidth / 19); +// // return RefreshIndicator( +// // onRefresh: () async => widget.refreshCall(), +// // child: ListView( +// // children: [ +// // Container( +// // constraints: BoxConstraints( +// // minHeight: 200.h, +// // maxWidth: double.infinity, +// // ), +// // child: Image.asset('assets/images/job_home_top_bgm.png', fit: BoxFit.fitWidth), +// // ), +// // SizedBox(height: 30.h), +// // SlidingData([ +// // EntranceModel(title: '作业批阅', image: 'assets/images/job_home_marking.png', navigationUrl: RouterManager.jobMainListPagePath), +// // EntranceModel( +// // title: '学生历史作业', +// // image: 'assets/images/job_home_history.png', +// // navigationUrl: '${RouterManager.jobStudentGroupPath}?page=history', +// // ), +// // EntranceModel(title: '知识点点掌握', image: 'assets/images/job_home_knowledge.png', navigationUrl: RouterManager.jobKnowledgePointsPath) +// // ]), +// // spaceWidth, +// // $TermRow([ +// // EntranceModel(title: '答题轨迹', image: 'assets/images/job_home_answer_record.png', navigationUrl: RouterManager.answerTrajectoryPath), +// // EntranceModel( +// // title: '优先批阅设定', +// // image: 'assets/images/job_home_youxian.png', +// // navigationUrl: '${RouterManager.jobStudentGroupPath}?page=set', +// // ) +// // ], 0), +// // // spaceWidth, +// // // $TermRow([EntranceModel(title: '批阅设置', image: 'assets/images/job_home_marking_set.png', navigationUrl: '')], 0), +// // ], +// // ), +// // ); +// var heightVal = _isOpen +// ? _secondFloor +// : _refreshState == RefreshMode.inactive +// ? 0.0 +// : _pulledExtent; +// return AnnotatedRegion( +// value: const SystemUiOverlayStyle( +// systemNavigationBarColor: Color(0xFF000000), +// systemNavigationBarDividerColor: null, +// statusBarColor: Colors.white, +// systemNavigationBarIconBrightness: Brightness.light, +// statusBarIconBrightness: Brightness.dark, +// statusBarBrightness: Brightness.light, +// ), +// child: InkWell( +// onTap: () { +// if (_isOpen) { +// setState(() { +// _isOpen = false; +// _toggleAnimation = true; +// Future.delayed(_toggleAnimationDuration, () { +// if (mounted) { +// setState(() { +// _toggleAnimation = false; +// }); +// } +// }); +// }); +// } +// }, +// child: AnimatedContainer( +// padding: EdgeInsets.zero, +// height: heightVal, +// color: Colors.white, +// duration: _toggleAnimation ? _toggleAnimationDuration : Duration(milliseconds: 1), +// child: Stack( +// children: [ +// Positioned( +// bottom: 0.0, +// left: 0.0, +// right: 0.0, +// child: Container( +// height: MediaQuery.of(context).size.height, +// width: double.infinity, +// child: Image.asset( +// 'assets/images/job_home_top_bgm.png', +// fit: BoxFit.fitHeight, +// ), +// ), +// ), +// Positioned( +// bottom: 0.0, +// left: 0.0, +// right: 0.0, +// child: AnimatedCrossFade( +// firstChild: Center( +// child: Container( +// alignment: Alignment.center, +// margin: EdgeInsets.only( +// bottom: 20.0, +// top: 10.0, +// ), +// width: 24.0, +// height: 24.0, +// child: Offstage( +// offstage: widget.secondFloorOpen.value, +// child: CircularProgressIndicator( +// value: _indicatorValue, +// valueColor: AlwaysStoppedAnimation(Colors.white), +// strokeWidth: 2.4, +// ), +// ), +// ), +// ), +// secondChild: Center( +// child: Container( +// alignment: Alignment.center, +// margin: EdgeInsets.only( +// bottom: 20.0, +// top: 10.0, +// ), +// child: Offstage( +// offstage: !widget.secondFloorOpen.value, +// child: Text( +// '欢迎来到二楼', +// style: TextStyle(fontSize: 18.0, color: Colors.white), +// ), +// ), +// ), +// ), +// crossFadeState: widget.secondFloorOpen.value ? CrossFadeState.showSecond : CrossFadeState.showFirst, +// duration: Duration(milliseconds: 300), +// ), +// ), +// ], +// ), +// ), +// ), +// ); +// } +// } + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_easyrefresh/easy_refresh.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:functional_widget_annotation/functional_widget_annotation.dart'; import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_page_data.dart'; import 'package:marking_app/common/model/event_bus/job_home_refresh_bus.dart'; +import 'package:marking_app/common/model/job/job_task_item.dart'; import 'package:marking_app/common/model/marking/marking_list_params.dart'; import 'package:marking_app/pages/common/event_bus_mixin.dart'; import 'package:marking_app/routes/RouterManager.dart'; +import 'package:marking_app/utils/easy_refresh/MyEmptyWidget.dart'; +import 'package:marking_app/utils/easy_refresh/mixin/refresh_data_handle.dart'; import 'package:marking_app/utils/index.dart'; import 'package:marking_app/utils/my_text.dart'; import 'package:badges/badges.dart' as badges; +import 'package:marking_app/utils/request/rest_client.dart'; -import '../../utils/my_future_builder.dart'; +import 'components/new_version_of_homework/homework_tasks_view_item.dart'; part 'job_home.g.dart'; @@ -24,44 +573,58 @@ class JobHome extends StatefulWidget { State createState() => _JobHomeState(); } -class _JobHomeState extends State with CommonMixin, EventBusMixin, AutomaticKeepAliveClientMixin { +class _JobHomeState extends State + with CommonMixin, EventBusMixin, RefreshDataHandle, AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; + var param = MarkingListParams(isFinish: false, page: 1, limit: 1, pageType: 0); + + int totalJobNumber = 0; + List jobDatas = []; + late final EasyRefreshController _refreshController; + @override void initState() { - getData(); - eventOn(callback: (JobHomeRefreshBus item) => getData()); + _refreshController = EasyRefreshController(); super.initState(); } @override void dispose() { + _refreshController.dispose(); eventCancel(); super.dispose(); } - Future getData() async { - try { - var _client = await getClient(); - var _result = await _client.getJobsByPage(MarkingListParams( - isFinish: false, - page: 1, - limit: 1, - pageType: 0, - )); - var data = _result.data?.total ?? 0; - eventFire(model: QuantityToBeReviewedData(data)); - return data; - } catch (e) { - return 0; + /* 发起请求 => 作业 */ + Future toGetPageData({bool isReFresh = false}) async { + if (!isReFresh) { + param.page++; + } + RestClient client = await getClient(); + BasePageData? results = await toRefreshData( + _refreshController, + api: client.getJobsByPage, + params: param, + isReFresh: isReFresh, + context: context, + ); + if (results != null) { + Future.delayed(Duration(seconds: 1), () => eventFire(model: QuantityToBeReviewedData(results.total))); + if (isReFresh) { + jobDatas.clear(); + jobDatas = results.items; + } else + jobDatas.addAll(results.items); + setState(() {}); } } @override Widget build(BuildContext context) { super.build(context); - var spaceWidth = SizedBox(height: ScreenUtil().screenWidth / 19); + return AnnotatedRegion( value: const SystemUiOverlayStyle( systemNavigationBarColor: Color(0xFF000000), @@ -71,46 +634,46 @@ class _JobHomeState extends State with CommonMixin, EventBusMixin, Auto statusBarIconBrightness: Brightness.dark, statusBarBrightness: Brightness.light, ), - child: RefreshIndicator( - onRefresh: () async => eventFire(model: JobHomeRefreshBus()), + child: EasyRefresh( + firstRefresh: true, + taskIndependence: true, + enableControlFinishLoad: true, + enableControlFinishRefresh: true, + emptyWidget: jobDatas.isEmpty ? $TheJobMainBox(emptyWidget: MyEmptyWidget()) : null, + controller: _refreshController, + header: MaterialHeader(), + footer: TaurusFooter(), child: ListView( children: [ - Container( - constraints: BoxConstraints( - minHeight: 200.h, - maxWidth: double.infinity, - ), - // decoration: BoxDecoration( - // image: DecorationImage( - // image: AssetImage('assets/images/job_home_top_bgm.png'), - // fit: BoxFit.fitWidth, // 完全填充 - // ), - // ), - child: Image.asset('assets/images/job_home_top_bgm.png', fit: BoxFit.fitWidth), - ), - SizedBox(height: 30.h), - SlidingData([ - EntranceModel(title: '作业批阅', image: 'assets/images/job_home_marking.png', navigationUrl: RouterManager.jobMainListPagePath), - EntranceModel( - title: '学生历史作业', - image: 'assets/images/job_home_history.png', - navigationUrl: '${RouterManager.jobStudentGroupPath}?page=history', - ), - EntranceModel(title: '知识点点掌握', image: 'assets/images/job_home_knowledge.png', navigationUrl: RouterManager.jobKnowledgePointsPath) - ]), - spaceWidth, - $TermRow([ - EntranceModel(title: '答题轨迹', image: 'assets/images/job_home_answer_record.png', navigationUrl: RouterManager.answerTrajectoryPath), - EntranceModel( - title: '优先批阅设定', - image: 'assets/images/job_home_youxian.png', - navigationUrl: '${RouterManager.jobStudentGroupPath}?page=set', - ) - ], 0), + // Container( + // constraints: BoxConstraints( + // minHeight: 200.h, + // maxWidth: double.infinity, + // ), + // // decoration: BoxDecoration( + // // image: DecorationImage( + // // image: AssetImage('assets/images/job_home_top_bgm.png'), + // // fit: BoxFit.fitWidth, // 完全填充 + // // ), + // // ), + // child: Image.asset('assets/images/job_home_top_bgm.png', fit: BoxFit.fitWidth), + // ), + $TheJobMainBox(), // spaceWidth, // $TermRow([EntranceModel(title: '批阅设置', image: 'assets/images/job_home_marking_set.png', navigationUrl: '')], 0), + + Container( + padding: EdgeInsets.symmetric(horizontal: 12.w), + child: Column( + children: jobDatas + .map((e) => HomeworkTasksViewItem(completed: false, jobTaskItem: e, call: () => _refreshController.callRefresh())) + .toList(), + ), + ) ], ), + onRefresh: () => toGetPageData(isReFresh: true), + onLoad: () => toGetPageData(isReFresh: false), ), ); } @@ -263,3 +826,36 @@ class SlidingData extends HookWidget with EventBusMixin { return $TermRow(items, dataNumber.value?.num ?? 0); } } + +// 作业主页 +@swidget +Widget $theJobMainBox(BuildContext context, {Widget? emptyWidget}) { + var spaceWidth = SizedBox(height: ScreenUtil().screenWidth / 19); + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: MediaQuery.of(context).padding.top + 20.h), + SlidingData([ + EntranceModel(title: '作业批阅', image: 'assets/images/job_home_marking.png', navigationUrl: RouterManager.jobMainListPagePath), + EntranceModel( + title: '学生历史作业', + image: 'assets/images/job_home_history.png', + navigationUrl: '${RouterManager.jobStudentGroupPath}?page=history', + ), + EntranceModel(title: '知识点点掌握', image: 'assets/images/job_home_knowledge.png', navigationUrl: RouterManager.jobKnowledgePointsPath) + ]), + spaceWidth, + $TermRow([ + EntranceModel(title: '答题轨迹', image: 'assets/images/job_home_answer_record.png', navigationUrl: RouterManager.answerTrajectoryPath), + EntranceModel( + title: '优先批阅设定', + image: 'assets/images/job_home_youxian.png', + navigationUrl: '${RouterManager.jobStudentGroupPath}?page=set', + ) + ], 0), + if (emptyWidget != null) emptyWidget, + spaceWidth, + ], + ); +} diff --git a/marking_app/lib/pages/homework_correction/providers/handwriting_drawing_trajectory_provider.dart b/marking_app/lib/pages/homework_correction/providers/handwriting_drawing_trajectory_provider.dart index 30f40bf..de6dfd6 100644 --- a/marking_app/lib/pages/homework_correction/providers/handwriting_drawing_trajectory_provider.dart +++ b/marking_app/lib/pages/homework_correction/providers/handwriting_drawing_trajectory_provider.dart @@ -16,3 +16,15 @@ class JobHandwritingDrawingTrajectoryProviderHandle extends StateNotifier( + (ref) => JobHandwritingStudentManuscriptHandle(ShowStudentMmanuscript(true))); + +class JobHandwritingStudentManuscriptHandle extends StateNotifier { + JobHandwritingStudentManuscriptHandle(ShowStudentMmanuscript progress) : super(progress); + + setVal(ShowStudentMmanuscript val) { + state = val; + } +} diff --git a/marking_app/lib/pages/homework_correction/widget/answer_handwriting.dart b/marking_app/lib/pages/homework_correction/widget/answer_handwriting.dart index e3d77e6..d0921a8 100644 --- a/marking_app/lib/pages/homework_correction/widget/answer_handwriting.dart +++ b/marking_app/lib/pages/homework_correction/widget/answer_handwriting.dart @@ -375,19 +375,34 @@ class _HandwritingDrawBoxState extends ConsumerState with Ev late ImageStreamListener theImageStreamListener; late ValueNotifier> _vnHandWritings; + late ValueNotifier> _vnPrimaryHandWritings; late RemoveListener _jobHandwritingDrawingTrajectoryListener; // 批注关闭监听 + late RemoveListener _jobHandwritingDrawingTrajectoryListener1; // 查看原稿 List> _packagedHandwritingDatas = []; - List _packagedHandwritingDataAll = []; + List _packagedHandwritingDataAll = []; // 总数据 List pendingData = []; // 待执行数据 List timers = []; int handwritingTime = 0; int handwritingDuration = 0; - double speed = 1; // 播放速度 + double speed = 2.0; // 播放速度 + @override void initState() { super.initState(); _vnHandWritings = ValueNotifier>([]); + _vnPrimaryHandWritings = ValueNotifier>(_packagedHandwritingDataAll); + _jobHandwritingDrawingTrajectoryListener1 = ref.read(jobHandwritingStudentManuscriptProvider.notifier).addListener((state) { + // 查看原稿控制 + if (state.showManuscript) { + // 查看原稿 + eventFire(model: JobHandwritingPlaybarBus(false)); // 暂停 + _vnPrimaryHandWritings.value = [..._packagedHandwritingDataAll]; + } else { + // 清空原稿数据 + _vnPrimaryHandWritings.value = []; + } + }, fireImmediately: false); _jobHandwritingDrawingTrajectoryListener = ref.read(jobHandwritingDrawingTrajectoryProvider.notifier).addListener((state) { _vnHandWritings.value = state; }, fireImmediately: false); @@ -447,7 +462,9 @@ class _HandwritingDrawBoxState extends ConsumerState with Ev if (e.isActive) e.cancel(); }); _jobHandwritingDrawingTrajectoryListener(); + _jobHandwritingDrawingTrajectoryListener1(); _vnHandWritings.dispose(); + _vnPrimaryHandWritings.dispose(); try { imageStream?.removeListener(theImageStreamListener); eventCancel(); @@ -537,7 +554,7 @@ class _HandwritingDrawBoxState extends ConsumerState with Ev pendingData.addAll(_packagedHandwritingDataAll); ref.read(jobHandwritingDrawingTrajectoryProvider.notifier).setVal([]); } - + ref.read(jobHandwritingStudentManuscriptProvider.notifier).setVal(ShowStudentMmanuscript(false)); executableData.forEach((e) { var ter = Timer(Duration(milliseconds: e.intervalTime ~/ speed), () => zhixinCall(e)); timers.add(ter); @@ -572,6 +589,9 @@ class _HandwritingDrawBoxState extends ConsumerState with Ev _packagedHandwritingDatas.add(newTrajectoryData); // 分组数据 _packagedHandwritingDataAll.addAll(newTrajectoryData); // 不分组数据 + try { + Future.delayed(Duration.zero, () => ref.read(jobHandwritingStudentManuscriptProvider.notifier).setVal(ShowStudentMmanuscript(true))); + } catch (e) {} } Future.delayed(Duration.zero, () => eventFire(model: JobHandwritingGetReadyBus())); // 通知外部可以播放笔迹 } @@ -579,14 +599,12 @@ class _HandwritingDrawBoxState extends ConsumerState with Ev @override Widget build(BuildContext context) { + var showManuscript = ref.watch(jobHandwritingStudentManuscriptProvider).showManuscript; return Container( alignment: Alignment.center, child: RepaintBoundary( child: CustomPaint( - willChange: true, - isComplex: true, - foregroundPainter: HandWritingDrawingPainter(ctrl: _vnHandWritings), - // size: Size(ScreenUtil().screenWidth - 60.r, widget.boxHeight), + foregroundPainter: HandWritingDrawingPainter(ctrl: showManuscript ? _vnPrimaryHandWritings : _vnHandWritings), child: RepaintBoundary( child: $TheCachedNetworkImage( imageUrl: widget.image, @@ -746,8 +764,8 @@ Widget $bottomPlaybar(BuildContext context, int timeConsuming, int pauseCount, L }, []); return Container( - height: 60.h, - padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.h), + height: 62.h, + padding: EdgeInsets.symmetric(horizontal: 10.w), alignment: Alignment.center, color: Color.fromRGBO(0, 0, 0, 0.4), child: Row( @@ -785,7 +803,7 @@ Widget $bottomPlaybar(BuildContext context, int timeConsuming, int pauseCount, L left: unitScale * item.startTime, child: Container( width: unitScale * (item.apart ?? 0), - height: 8.h, + height: 10.h, decoration: BoxDecoration( color: Color.fromRGBO(202, 201, 201, 1), borderRadius: isFirst @@ -802,7 +820,7 @@ Widget $bottomPlaybar(BuildContext context, int timeConsuming, int pauseCount, L Stack( children: [ Container( - height: 8.h, + height: 10.h, width: containerWidth, decoration: BoxDecoration( // color: Color.fromRGBO(146, 146, 146, 1), @@ -812,12 +830,12 @@ Widget $bottomPlaybar(BuildContext context, int timeConsuming, int pauseCount, L ), ...pauseTickMarks, Container( - height: 8.h, + height: 10.h, width: containerWidth, // color: Theme.of(context).primaryColor, child: SliderTheme( data: SliderTheme.of(context).copyWith( - trackHeight: 8.h, // 轨道高度 + trackHeight: 10.h, // 轨道高度 trackShape: RoundedRectSliderTrackShape(), // 轨道形状,可以自定义 activeTrackColor: Theme.of(context).primaryColor, // 激活的轨道颜色 inactiveTrackColor: Colors.transparent, // 未激活的轨道颜色 @@ -847,7 +865,7 @@ Widget $bottomPlaybar(BuildContext context, int timeConsuming, int pauseCount, L ), ], ), - SizedBox(height: 4.h), + SizedBox(height: 8.h), SizedBox( width: containerWidth, child: Row( @@ -863,31 +881,61 @@ Widget $bottomPlaybar(BuildContext context, int timeConsuming, int pauseCount, L }), ), SizedBox(width: 16.w), - InkWell( - onTap: () => easyThrottle('job_handwriting_speed', () { - var theIndex = PlaybackSpeed.values.indexOf(usePlaybar.constantFastSpeed.value); - if (theIndex == PlaybackSpeed.values.length - 1) { - theIndex = -1; - } - usePlaybar.constantFastSpeed.value = PlaybackSpeed.values[theIndex + 1]; - }, duration: Duration(milliseconds: 500)), - child: Container( - // alignment: Alignment., - padding: EdgeInsets.symmetric(horizontal: 3.w, vertical: 1.5.h), - decoration: BoxDecoration(color: Color.fromRGBO(182, 197, 250, 1), borderRadius: BorderRadius.circular(4.r)), - child: quickText( - '${usePlaybar.constantFastSpeed.value.name}', - color: Color.fromRGBO(79, 114, 244, 1), - size: 8.sp, - align: TextAlign.center, + Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + InkWell( + onTap: () => easyThrottle('job_handwriting_speed', () { + var theIndex = PlaybackSpeed.values.indexOf(usePlaybar.constantFastSpeed.value); + if (theIndex == PlaybackSpeed.values.length - 1) { + theIndex = -1; + } + usePlaybar.constantFastSpeed.value = PlaybackSpeed.values[theIndex + 1]; + }, duration: Duration(milliseconds: 500)), + child: Container( + // alignment: Alignment., + padding: EdgeInsets.symmetric(horizontal: 3.w, vertical: 1.5.h), + decoration: BoxDecoration(color: Color.fromRGBO(182, 197, 250, 1), borderRadius: BorderRadius.circular(4.r)), + child: quickText( + '${usePlaybar.constantFastSpeed.value.name}', + color: Color.fromRGBO(79, 114, 244, 1), + size: 8.sp, + align: TextAlign.center, + ), + ), ), - ), + SizedBox(height: 7.h), + StudentManuscriptBtn(), + ], ), ], ), ); } +// 学生原稿按钮视图 +class StudentManuscriptBtn extends ConsumerWidget { + const StudentManuscriptBtn({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + var _showVal = ref.watch(jobHandwritingStudentManuscriptProvider); + return InkWell( + onTap: () => easyThrottle('job_handwriting_udent_manuscript', () { + var showManuscript = ref.read(jobHandwritingStudentManuscriptProvider).showManuscript; + ref.read(jobHandwritingStudentManuscriptProvider.notifier).setVal(ShowStudentMmanuscript(!showManuscript)); + }), + child: Container( + padding: EdgeInsets.symmetric(horizontal: 3.w, vertical: 1.5.h), + decoration: + BoxDecoration(color: _showVal.showManuscript ? Theme.of(context).primaryColor : Colors.grey, borderRadius: BorderRadius.circular(4.r)), + child: quickText('学生原稿', color: Colors.white, size: 8.sp, align: TextAlign.center), + ), + ); + } +} + class SysjTime extends StatefulWidget { const SysjTime({super.key}); @@ -921,7 +969,7 @@ class _SysjTimeState extends State with EventBusMixin handwritingDuration; // 笔迹总时长 final ValueNotifier playPause; // 播放暂停 - final ValueNotifier constantFastSpeed; // 原速、快速 默认原速 + final ValueNotifier constantFastSpeed; // 播放速度 final ValueNotifier handWritingReady; final ValueNotifier useTime; // 耗时 单位:(秒) @@ -942,7 +990,7 @@ class UseBottomPlaybar with EventBusMixin { if ((milliseconds % 1000) > 500) handwritingDuration += 1; return UseBottomPlaybar._( playPause: useState(false), - constantFastSpeed: useState(PlaybackSpeed.ORIGINAL_SPEED), + constantFastSpeed: useState(PlaybackSpeed.DOUBLE_SPEED), // 默认两倍速 useTime: useState(handwritingDuration), timer: useState(null), handwritingDuration: useState(handwritingDuration), diff --git a/marking_app/lib/pages/login/index.dart b/marking_app/lib/pages/login/index.dart index 2317dec..8095459 100644 --- a/marking_app/lib/pages/login/index.dart +++ b/marking_app/lib/pages/login/index.dart @@ -12,6 +12,7 @@ import 'dart:io'; import 'package:dio/dio.dart'; import 'package:dio/adapter.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; @@ -44,10 +45,8 @@ class _TheLoginState extends ConsumerState with CommonMixin { //添加证书 setHttpsPEM() async { - (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = - (client) { - client.badCertificateCallback = - (X509Certificate cert, String host, int port) { + (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) { + client.badCertificateCallback = (X509Certificate cert, String host, int port) { return true; }; }; @@ -75,7 +74,7 @@ class _TheLoginState extends ConsumerState with CommonMixin { @override void initState() { - Future.delayed(Duration(seconds: 2), () => sysProtocol(context)); + Future.delayed(Duration(seconds: 1), () => sysProtocol(context)); Future(() { // 延迟更新 Provider ref.read(userTokenProvider.notifier).clean(); // 进入登录页先清空信息 @@ -89,14 +88,12 @@ class _TheLoginState extends ConsumerState with CommonMixin { receiveTimeout: 8000, ), ); - dio.interceptors - .add(LogInterceptor(responseBody: true, requestBody: true)); //添加日志 + dio.interceptors.add(LogInterceptor(responseBody: true, requestBody: true)); //添加日志 setHttpsPEM(); client = RestClient(dio, baseUrl: RequestConfig().loginBaseUrl); - _userNameController = TextEditingController() - ..addListener(userNameListener); + _userNameController = TextEditingController()..addListener(userNameListener); _passwordController = TextEditingController(); _pwdFocus = FocusNode(); _theFocus = FocusNode(); @@ -114,11 +111,10 @@ class _TheLoginState extends ConsumerState with CommonMixin { void getShowRegister() async { RestClient client = await getClientLogin(); BaseStructureResult resultData = await client.showRegister(); - if(resultData.success){ + if (resultData.success) { setState(() { showRegister = resultData.data; }); - } } @@ -126,8 +122,7 @@ class _TheLoginState extends ConsumerState with CommonMixin { String userName = _userNameController.text; int useNameLength = userName.length; bool hasNameValNew = useNameLength > 0; - if (hasNameValNew != hasNameVal) - toUpState(setState, () => hasNameVal = hasNameValNew, mounted); + if (hasNameValNew != hasNameVal) toUpState(setState, () => hasNameVal = hasNameValNew, mounted); const isProd = bool.fromEnvironment('dart.vm.product'); if (!isProd && useNameLength == 11) { _passwordController.text = userName.substring(useNameLength - 6); @@ -182,15 +177,12 @@ class _TheLoginState extends ConsumerState with CommonMixin { child: SizedBox( height: 86.w, width: 86.w, - child: Image.asset('assets/images/logo.png', - fit: BoxFit.cover), + child: Image.asset('assets/images/logo.png', fit: BoxFit.cover), ), ), Container( - margin: EdgeInsets.symmetric( - horizontal: 32.w, vertical: 24.h), - padding: EdgeInsets.only( - top: 34.h, bottom: 16.h, left: 22.w, right: 22.w), + margin: EdgeInsets.symmetric(horizontal: 32.w, vertical: 24.h), + padding: EdgeInsets.only(top: 34.h, bottom: 16.h, left: 22.w, right: 22.w), decoration: BoxDecoration( color: Colors.white, border: Border.all(width: 1.w, color: Colors.white), @@ -219,18 +211,13 @@ class _TheLoginState extends ConsumerState with CommonMixin { ), decoration: InputDecoration( hintText: "请输入账号", - hintStyle: TextStyle( - fontSize: 16.sp, - color: const Color.fromRGBO(153, 153, 153, 1)), + hintStyle: TextStyle(fontSize: 16.sp, color: const Color.fromRGBO(153, 153, 153, 1)), labelText: "账号", - labelStyle: TextStyle( - fontSize: 16.sp, - color: const Color.fromRGBO(148, 163, 182, 1)), + labelStyle: TextStyle(fontSize: 16.sp, color: const Color.fromRGBO(148, 163, 182, 1)), suffixIcon: !hasNameVal ? null : Transform.translate( - offset: - Offset(10, 10), // 根据原始组件的padding值来设置偏移量 + offset: Offset(10, 10), // 根据原始组件的padding值来设置偏移量 child: IconButton( alignment: Alignment.center, padding: EdgeInsets.zero, @@ -266,19 +253,13 @@ class _TheLoginState extends ConsumerState with CommonMixin { onTap: _showPassword, child: Icon( Icons.remove_red_eye, - color: !_isShowPwd - ? Theme.of(context).primaryColor - : Colors.grey, + color: !_isShowPwd ? Theme.of(context).primaryColor : Colors.grey, ), ), - hintStyle: TextStyle( - fontSize: 16.sp, - color: const Color.fromRGBO(153, 153, 153, 1)), + hintStyle: TextStyle(fontSize: 16.sp, color: const Color.fromRGBO(153, 153, 153, 1)), labelText: "密码", isDense: true, - labelStyle: TextStyle( - fontSize: 16.sp, - color: const Color.fromRGBO(148, 163, 182, 1)), + labelStyle: TextStyle(fontSize: 16.sp, color: const Color.fromRGBO(148, 163, 182, 1)), ), ), SizedBox( @@ -294,10 +275,8 @@ class _TheLoginState extends ConsumerState with CommonMixin { checkColor: Colors.white, value: keepPwd, onChanged: (value) { - FocusScope.of(context) - .requestFocus(_pwdFocus); - FocusScope.of(context) - .requestFocus(_theFocus); + FocusScope.of(context).requestFocus(_pwdFocus); + FocusScope.of(context).requestFocus(_theFocus); setState(() { keepPwd = value ?? false; }); @@ -322,26 +301,20 @@ class _TheLoginState extends ConsumerState with CommonMixin { if (showRegister == true) InkWell( onTap: () { - RouterManager.router.navigateTo( - context, RouterManager.registerPath); + RouterManager.router.navigateTo(context, RouterManager.registerPath); }, child: Text( '注册', - style: TextStyle( - fontSize: 14.sp, - color: const Color.fromRGBO( - 148, 163, 182, 1)), + style: TextStyle(fontSize: 14.sp, color: const Color.fromRGBO(148, 163, 182, 1)), )), ], ), InkWell( - onTap: toLogin, + onTap: () => easyThrottle('TO_GO_LOGOIN', toLogin), child: Container( margin: EdgeInsets.symmetric(vertical: 10.h), decoration: BoxDecoration( - color: canLogin - ? const Color.fromRGBO(9, 105, 246, 1) - : Colors.grey, + color: canLogin ? const Color.fromRGBO(9, 105, 246, 1) : Colors.grey, boxShadow: [ BoxShadow( color: const Color.fromRGBO(46, 91, 255, 0.5), @@ -359,8 +332,7 @@ class _TheLoginState extends ConsumerState with CommonMixin { height: 50.h, child: Text( '登 录', - style: TextStyle( - fontSize: 16.sp, color: Colors.white), + style: TextStyle(fontSize: 16.sp, color: Colors.white), ), ), ), @@ -374,10 +346,8 @@ class _TheLoginState extends ConsumerState with CommonMixin { checkColor: Colors.white, value: readAgreement, onChanged: (value) { - FocusScope.of(context) - .requestFocus(_pwdFocus); - FocusScope.of(context) - .requestFocus(_theFocus); + FocusScope.of(context).requestFocus(_pwdFocus); + FocusScope.of(context).requestFocus(_theFocus); setState(() { readAgreement = value ?? false; }); @@ -446,34 +416,64 @@ class _TheLoginState extends ConsumerState with CommonMixin { setState(() => canLogin = true); } - FocusScope.of(context).requestFocus(_theFocus); - - String userName = _userNameController.text.trim(); - String userPwd = _passwordController.text.trim(); - if (userName == '') return toMsg('请填写用户账号'); - if (userPwd == '') return toMsg('请填写密码再试'); - if (!readAgreement) return toMsg('请勾选我已阅读用户协议和隐私协议'); - - String userPwdMd5 = CommonUtils.generateMD5(userPwd); - print('userPwdMd5=$userPwdMd5'); - EasyLoading.show(status: 'loading...'); try { - BaseStructureResult resultData = - await client.toLogin(UserLoginParams(userName, userPwdMd5)); - UserLogin? userData = resultData.code == 200 && resultData.data != null - ? UserLogin.fromJson(resultData.data) - : null; - if (resultData.code != 200 || - userData?.accessToken == null || - userData?.accessToken == '') { + FocusScope.of(context).requestFocus(_theFocus); + if (!readAgreement) { + var resFlag = await showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return CupertinoAlertDialog( + title: quickText('用户协议及隐私协议', size: 14.sp, color: Color.fromARGB(255, 53, 52, 52)), + content: SingleChildScrollView( + padding: EdgeInsets.only(top: 4.h), + child: RichText( + text: TextSpan( + text: '为了更好地保障您的合法权益,请您阅读并同意以下协议', + style: TextStyle(color: Color.fromARGB(255, 137, 138, 139), fontSize: 11.sp), + children: [ + TextSpan(text: '《用户协议》《隐式协议》', style: TextStyle(color: Colors.deepOrangeAccent, fontSize: 13.sp)), + ], + ), + ), + ), + actions: [ + CupertinoDialogAction( + child: Text("取消", style: TextStyle(color: Color.fromARGB(255, 58, 58, 58))), + onPressed: () => Navigator.of(context).pop(false), + ), + CupertinoDialogAction( + child: Text("确定"), + onPressed: () => Navigator.of(context).pop(true), + ), + ], + ); + }, + ); + if (!resFlag!) return; + setState(() => readAgreement = true); + } + + String userName = _userNameController.text.trim(); + String userPwd = _passwordController.text.trim(); + if (userName == '') return toMsg('请填写用户账号'); + if (userPwd == '') return toMsg('请填写密码再试'); + if (!readAgreement) return toMsg('请勾选我已阅读用户协议和隐私协议'); + + String userPwdMd5 = CommonUtils.generateMD5(userPwd); + print('userPwdMd5=$userPwdMd5'); + EasyLoading.show(status: 'loading...'); + + BaseStructureResult resultData = await client.toLogin(UserLoginParams(userName, userPwdMd5)); + UserLogin? userData = resultData.code == 200 && resultData.data != null ? UserLogin.fromJson(resultData.data) : null; + if (resultData.code != 200 || userData?.accessToken == null || userData?.accessToken == '') { return toMsg(resultData.message ?? '登录失败,请重试'); } FastData fastData = FastData.getInstance(); fastData.setToken(userData!.accessToken); - BaseStructureResult userRes = - await client.getUserInfo('Bearer ${userData.accessToken}'); + BaseStructureResult userRes = await client.getUserInfo('Bearer ${userData.accessToken}'); if (userRes.code != 200 || userRes.data == null) { throw Exception('登录失败,请重试'); @@ -489,8 +489,7 @@ class _TheLoginState extends ConsumerState with CommonMixin { ref.read(userTokenProvider.notifier).initToken(); // 跳转登录页 - RouterManager.router.navigateTo(context, RouterManager.root, - clearStack: true, transition: getTransition()); + RouterManager.router.navigateTo(context, RouterManager.root, clearStack: true, transition: getTransition()); }); } catch (e) { toPrint(val: e.toString()); @@ -522,6 +521,7 @@ class _TheLoginState extends ConsumerState with CommonMixin { return toMsg(msg ?? '登录失败,请重试'); } finally { EasyLoading.dismiss(); + setState(() => canLogin = true); } } } diff --git a/marking_app/lib/pages/mainPage.dart b/marking_app/lib/pages/mainPage.dart index a073354..82c2aed 100644 --- a/marking_app/lib/pages/mainPage.dart +++ b/marking_app/lib/pages/mainPage.dart @@ -19,6 +19,7 @@ import 'package:marking_app/common/model/user/user_info.dart'; import 'package:marking_app/pages/reports/index.dart'; import 'package:marking_app/provider/do_marking_provider.dart'; import 'package:marking_app/provider/upload_file_provider.dart'; +import 'package:marking_app/routes/RouterManager.dart'; import 'package:marking_app/utils/app_upgrade/UpdateDialog.dart'; import 'package:marking_app/utils/app_upgrade/model/UpdateAppEvent.dart'; import 'package:marking_app/common/model/sys/system_version.dart'; @@ -28,6 +29,7 @@ import 'package:marking_app/provider/user_provider.dart'; import 'package:marking_app/utils/index.dart'; import 'package:marking_app/utils/request/rest_client.dart'; import 'package:package_info/package_info.dart'; +import 'package:url_launcher/url_launcher.dart'; import 'homework_correction/job_home.dart'; @@ -72,10 +74,11 @@ class TheMainPageState extends ConsumerState with CommonMixin { // 由于本项目必须登录才能浏览,所以APP升级校验在登录后 _userListener = ref.read(userProvider.notifier).addListener((state) { if (state.id != '0' && state.id != '') { - if (!showUpgrade) { - getAppUpgrade(state); - } + if (!showUpgrade) getAppUpgrade(state); + _timer?.cancel(); _timer = Timer.periodic(Duration(seconds: 40), (e) { + String? routeName = ModalRoute.of(context)?.settings.name; + if (routeName == RouterManager.loginPath) return; // 在登录页面不更新APP if (!showUpgrade) getAppUpgrade(state); }); } @@ -104,6 +107,7 @@ class TheMainPageState extends ConsumerState with CommonMixin { } void getAppUpgrade(UserInfo user) async { + if (!bool.fromEnvironment('dart.vm.product')) return; try { showUpgrade = true; if (['18888888888'].contains(user.loginName)) return; diff --git a/marking_app/lib/pages/marking/components/do_paper_bottom_review_marks.dart b/marking_app/lib/pages/marking/components/do_paper_bottom_review_marks.dart new file mode 100644 index 0000000..6e78354 --- /dev/null +++ b/marking_app/lib/pages/marking/components/do_paper_bottom_review_marks.dart @@ -0,0 +1,124 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:flutter_screenutil/flutter_screenutil.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/pages/common/event_bus_mixin.dart'; +import 'package:marking_app/utils/anti_shake_throttling.dart'; +import 'package:marking_app/utils/my_text.dart'; + +import '../provider/do_paper_bottom_review_marks_provider.dart'; + +// 底部批阅动作切换区域 +class DoPaperBottomReviewMarks extends StatefulHookConsumerWidget { + const DoPaperBottomReviewMarks({super.key}); + + @override + ConsumerState createState() => _DoPaperBottomReviewMarksState(); +} + +class _DoPaperBottomReviewMarksState extends ConsumerState with EventBusMixin { + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + // TODO: implement dispose + eventCancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + var btnEnum = ref.watch(doPaperBottomReviewMarksProvider); + Color actionColor = Theme.of(context).primaryColor.withOpacity(0.9); + return Positioned( + left: 3.w, + bottom: 6.h, + child: AbsorbPointer( + absorbing: false, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(50.r), + color: const Color.fromRGBO(24, 32, 32, 0.1), + ), + padding: EdgeInsets.symmetric(horizontal: 10.w), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + // 拖动 + IconButton( + onPressed: () => easyThrottle( + 'REVIEW_MARKS_BOTTOM_BTNS', + () => ref.read(doPaperBottomReviewMarksProvider.notifier).setState(ReviewMarksBottomBtnsEnum.DRAG), + ), + padding: EdgeInsets.zero, + icon: Icon(Icons.back_hand_outlined, color: btnEnum == ReviewMarksBottomBtnsEnum.DRAG ? actionColor : Colors.white, size: 34.r), + ), + SizedBox(width: 4.w), + // 批阅笔迹 + IconButton( + onPressed: () => easyThrottle( + 'REVIEW_MARKS_BOTTOM_BTNS', + () => ref.read(doPaperBottomReviewMarksProvider.notifier).setState(ReviewMarksBottomBtnsEnum.HANDWRITING), + ), + padding: EdgeInsets.zero, + icon: Icon(Icons.create_outlined, color: btnEnum == ReviewMarksBottomBtnsEnum.HANDWRITING ? actionColor : Colors.white, size: 34.r), + ), + SizedBox(width: 4.w), + // 批阅痕迹 ==> 返回上一级 + IconButton( + onPressed: () => easyThrottle( + 'REVIEW_MARKS_BOTTOM_BTNS', + duration: const Duration(milliseconds: 100), + () => eventFire(model: BottomAnnotationSwitchCleanallOfMarking(previousStep: true)), + ), + padding: EdgeInsets.zero, + icon: Icon(Icons.reply_outlined, + color: btnEnum == ReviewMarksBottomBtnsEnum.RETURN_PREVIOUS_LEVEL ? actionColor : Colors.white, size: 34.r), + ), + SizedBox(width: 4.w), + // 批阅痕迹 ==> 清空 + IconButton( + onPressed: () => easyThrottle( + 'REVIEW_MARKS_BOTTOM_BTNS', + duration: const Duration(milliseconds: 100), + () async { + var resFlag = await showDialog( + context: context, + builder: (BuildContext context) { + return CupertinoAlertDialog( + title: quickText('撤销批阅痕迹', size: 14.sp, color: Color.fromARGB(255, 53, 52, 52)), + content: SingleChildScrollView( + padding: EdgeInsets.only(top: 4.h), + child: RichText(text: TextSpan(text: '请确认需要撤销所有批阅痕迹?', style: TextStyle(color: Color.fromARGB(255, 58, 58, 58)))), + ), + actions: [ + CupertinoDialogAction( + child: Text("取消", style: TextStyle(color: Color.fromARGB(255, 58, 58, 58))), + onPressed: () => Navigator.of(context).pop(false), + ), + CupertinoDialogAction( + child: Text("确定"), + onPressed: () => Navigator.of(context).pop(true), + ), + ], + ); + }, + ); + if (resFlag == true) eventFire(model: BottomAnnotationSwitchCleanallOfMarking(cleanAll: true)); + }, + ), + padding: EdgeInsets.zero, + icon: Icon(Icons.reply_all_outlined, color: btnEnum == ReviewMarksBottomBtnsEnum.CLEAR_ALL ? actionColor : Colors.white, size: 34.r), + ), + ], + ), + ), + ), + ); + } +} diff --git a/marking_app/lib/pages/marking/do_papers.dart b/marking_app/lib/pages/marking/do_papers.dart index 366d583..fd45696 100644 --- a/marking_app/lib/pages/marking/do_papers.dart +++ b/marking_app/lib/pages/marking/do_papers.dart @@ -14,6 +14,7 @@ import 'package:achievement_view/achievement_view.dart'; import 'package:collection/collection.dart'; import 'package:dotted_border/dotted_border.dart'; import 'package:dropdown_search/dropdown_search.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; @@ -28,6 +29,8 @@ 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/KeyboardType.dart'; import 'package:marking_app/common/model/enum/marking_list_type.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/marking/current_review_task.dart'; import 'package:marking_app/common/model/marking/do_marking_keyboard_model.dart'; import 'package:marking_app/common/model/marking/keyboard_assist_event.dart'; @@ -52,6 +55,8 @@ import 'package:marking_app/components/marking/review_records_view.dart'; import 'package:marking_app/components/marking/selectable_keyboard_bottom.dart'; import 'package:marking_app/pages/common/event_bus_mixin.dart'; import 'package:marking_app/pages/marking/hooks/use_zoomImage_history_utils.dart'; +import 'package:marking_app/pages/marking/provider/do_paper_bottom_review_marks_provider.dart'; +import 'package:marking_app/pages/marking/provider/draw_marking_provider.dart'; import 'package:marking_app/pages/marking/provider/rating_progress_provider.dart'; import 'package:marking_app/provider/annotation_graffiti_switch_provider.dart'; import 'package:marking_app/provider/do_marking_provider.dart'; @@ -66,6 +71,7 @@ import 'package:marking_app/utils/request/rest_client.dart'; import 'package:percent_indicator/percent_indicator.dart'; import 'package:wakelock/wakelock.dart'; +import 'components/do_paper_bottom_review_marks.dart'; import 'hooks/use_abnormal.dart'; part 'do_papers.g.dart'; @@ -138,6 +144,7 @@ class _MarkingPapersState extends ConsumerState bool _completionPromptTab = false; // tab下试题完成并提示 bool _reviewCompletedPrompted = false; // 已经批阅完成提示 + bool annotationTips = false; // 批注提示(用于已批注但未提交) // bool _switchQueTypePrompt = false; // 切换题型 @@ -157,6 +164,10 @@ class _MarkingPapersState extends ConsumerState fToast.init(context); Future.delayed(const Duration(seconds: 0)).then((onValue) { ref.read(annotationGraffitiSwitchProvider.notifier).init(); + // 重置批阅痕迹 + ref.read(drawMarkingProvider.notifier).setState(DrawMarkingVal([], [])); + // 重置批阅动作按钮 + ref.read(doPaperBottomReviewMarksProvider.notifier).setState(ReviewMarksBottomBtnsEnum.DRAG); }); // 批注监听 @@ -174,9 +185,8 @@ class _MarkingPapersState extends ConsumerState screenDirectionSwitch(state.screenDirection); } else { ScreenDirection nowScreenDirection = state.screenDirection; // 当前屏幕方向 - ScreenDirection theOrientation = MediaQuery.of(context).orientation == Orientation.landscape - ? ScreenDirection.HORIZONTAL_SCREEN - : ScreenDirection.VERTICAL_SCREEN; + ScreenDirection theOrientation = + MediaQuery.of(context).orientation == Orientation.landscape ? ScreenDirection.HORIZONTAL_SCREEN : ScreenDirection.VERTICAL_SCREEN; if (theOrientation != nowScreenDirection) { screenDirectionSwitch(nowScreenDirection); } @@ -188,6 +198,7 @@ class _MarkingPapersState extends ConsumerState setTimeOut(300, () { eventFireSub(model: SwitchKeyboardToReloadImages(true)); }); + eventOn(callback: (BottomAnnotationSwitchCleanallOfMarking item) async {}); }); params = MarkingTextQuestionParams( @@ -243,8 +254,7 @@ class _MarkingPapersState extends ConsumerState bool isHorizontal = direction == ScreenDirection.HORIZONTAL_SCREEN; Future.delayed(Duration.zero, () { - SystemChrome.setPreferredOrientations( - [isHorizontal ? DeviceOrientation.landscapeLeft : DeviceOrientation.portraitUp]); + SystemChrome.setPreferredOrientations([isHorizontal ? DeviceOrientation.landscapeLeft : DeviceOrientation.portraitUp]); SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); setTimeOut(1000, () => toUpState(setState, () => annotationsFlag = true, mounted)); }); @@ -260,8 +270,7 @@ class _MarkingPapersState extends ConsumerState return null; } currentQuestion!.papersUrlStr = paperUrls; - currentQuestion!.papersUrl = - paperUrls.asMap().keys.map((e) => GalleryExampleItemModel(id: e.toString(), resource: paperUrls[e])).toList(); + currentQuestion!.papersUrl = paperUrls.asMap().keys.map((e) => GalleryExampleItemModel(id: e.toString(), resource: paperUrls[e])).toList(); return currentQuestion!.papersUrl; } @@ -426,8 +435,7 @@ class _MarkingPapersState extends ConsumerState Future submitTestQuestions(BuildContext theContext, MarkingTextQuestion data) async { Timer timer = Timer(const Duration(milliseconds: 300), () => ToastUtils.showLoading()); try { - if (widget.markingtype == MarkingListType.NORMAL && currentQuestion!.isException) - return ToastUtils.showError('异常题,不允许再评分'); + if (widget.markingtype == MarkingListType.NORMAL && currentQuestion!.isException) return ToastUtils.showError('异常题,不允许再评分'); if (currentQuestion == null) { return ToastUtils.showError('提交失败,请退出重试。'); @@ -463,15 +471,10 @@ class _MarkingPapersState extends ConsumerState RestClient client = await getClient(); String? commentImageUrlStr; - List? commentImageUrl = currentQuestion?.commentImageUrlTodo; - if (commentImageUrl?.isEmpty ?? true) commentImageUrl = currentQuestion?.commentImageUrl; + List? commentImageUrl = + (currentQuestion?.commentImageUrl.isNotEmpty ?? false) || res?.url != null ? currentQuestion?.commentImageUrlMap.values.toList() : null; if (commentImageUrl?.isNotEmpty ?? false) commentImageUrlStr = jsonEncode(commentImageUrl); - // bool excessContinue = - // _currentTab?.agreementExcess == null ? true : _currentTab!.agreementExcess!; // 如果没有询问默认为true 如果询问了 就已询问为主 - bool isExit = false; - MarkingTextQuestionTab? nextTag; - BaseStructureResult result = await client.submitTestQuestionsOfExam(SubmitExamParams( widget.markingUserId, detailId, @@ -507,6 +510,7 @@ class _MarkingPapersState extends ConsumerState e.isFinish = true; } } + annotationTips = false; // ScaffoldMessenger.of(context).showSnackBar( // SnackBar( @@ -758,12 +762,44 @@ class _MarkingPapersState extends ConsumerState return ToastUtils.getFluttertoast(context: context, msg: '请先评分后提交再进入下一题'); } + print(annotationTips); + if (annotationTips && currentQuestion != null) { + // 改变批注未提交 需要提示 + var resFlag = await showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return CupertinoAlertDialog( + title: quickText('未提交批注提示', size: 14.sp, color: Color.fromARGB(255, 53, 52, 52)), + content: SingleChildScrollView( + padding: EdgeInsets.only(top: 4.h), + child: RichText(text: TextSpan(text: '当前批注未提交切换试题将清空本次笔迹,是否继续?', style: TextStyle(color: Color.fromARGB(255, 58, 58, 58)))), + ), + actions: [ + CupertinoDialogAction( + child: Text("提交", style: TextStyle(color: Color.fromARGB(255, 58, 58, 58))), + onPressed: () => Navigator.of(context).pop(false), + ), + CupertinoDialogAction( + child: Text("继续"), + onPressed: () => Navigator.of(context).pop(true), + ), + ], + ); + }, + ); + + if (!resFlag!) { + // 提交批注 + return submitTestQuestions(context, currentQuestion!); + } + } + int detailId = theId ?? (isNext ? currentQuestion!.nextId : currentQuestion!.prevId); bool isReview = widget.isReview; if (theId != null && _currentTab != null && theQuestionNum != _currentTab!.questionNum) { - MarkingTextQuestionTab? _foundCrrentTab = - _currentTabs.firstWhereOrNull((element) => element.questionNum == theQuestionNum); + MarkingTextQuestionTab? _foundCrrentTab = _currentTabs.firstWhereOrNull((element) => element.questionNum == theQuestionNum); if (_foundCrrentTab != null) { _currentTab = _foundCrrentTab; } @@ -791,11 +827,7 @@ class _MarkingPapersState extends ConsumerState // 同步小题得分 score:分值 continueScoring:是否可以持续打分 hasSubtopic:是否有小题; cleanScore:是否是清空 Future synchroScore( - {required double score, - required bool continueScoring, - required bool hasSubtopic, - bool allWrong = false, - bool cleanScore = false}) async { + {required double score, required bool continueScoring, required bool hasSubtopic, bool allWrong = false, bool cleanScore = false}) async { if (currentQuestion == null) return; if (cleanScore) { @@ -879,8 +911,8 @@ class _MarkingPapersState extends ConsumerState res.data = res.data!.where((element) => !(element.isFinished && element.finishCount == 0)).toList(); // 方便更新tags key是题号 - Map _currentTabsMap = Map.fromIterable(_currentTabs, - key: (item) => item.questionNum, value: (item) => item); + Map _currentTabsMap = + Map.fromIterable(_currentTabs, key: (item) => item.questionNum, value: (item) => item); MarkingTextQuestionTab? theCurrentTab; String currentTagQueNum = _currentTab!.questionNum; res.data!.forEach((e) { @@ -918,31 +950,25 @@ class _MarkingPapersState extends ConsumerState if (!widget.exceptional && _currentTabs.length <= 0 && _currentTab == null) { // tag为空,请求tag数据并且为对应的tag赋值上分值步长 RestClient client = await getClient(); - List res = await Future.wait([ - client.getTestQuestionsOfTab(widget.markingUserId), - client.getTestQuestionsOfTabStepSize(widget.examSubjectId) - ]); - BaseStructureResult> resultTab = - res[0] as BaseStructureResult>; - BaseStructureResult> resultTabStep = - res[1] as BaseStructureResult>; + List res = + await Future.wait([client.getTestQuestionsOfTab(widget.markingUserId), client.getTestQuestionsOfTabStepSize(widget.examSubjectId)]); + BaseStructureResult> resultTab = res[0] as BaseStructureResult>; + BaseStructureResult> resultTabStep = res[1] as BaseStructureResult>; if ((!resultTab.success || (resultTab.data?.isEmpty ?? true)) || (!resultTabStep.success || (resultTabStep.data?.isEmpty ?? true)) || (resultTabStep.data!.length < resultTab.data!.length)) { throw Error(); } - Map tabStepMap = Map.fromIterable(resultTabStep.data!, - key: (item) => item.questionNum, value: (item) => item.scoreInterval); + Map tabStepMap = + Map.fromIterable(resultTabStep.data!, key: (item) => item.questionNum, value: (item) => item.scoreInterval); resultTab.data = resultTab.data!.where((element) => !(element.isFinished && element.finishCount == 0)).toList(); resultTab.data!.forEach((element) => element.setStepSize(tabStepMap[element.questionNum] ?? 1.0)); _currentTabs = resultTab.data!; // 获取当前tabs批次下第一个没有完成数据 - currentTab = resultTab.data!.firstWhere( - tabQuestionNum == null ? firstWhereCall : (e) => e.questionNum == tabQuestionNum, - orElse: () => - MarkingTextQuestionTab(isFinished: false, questionNum: '0.0', total: 0, finishCount: 0, isExcess: false)); + currentTab = resultTab.data!.firstWhere(tabQuestionNum == null ? firstWhereCall : (e) => e.questionNum == tabQuestionNum, + orElse: () => MarkingTextQuestionTab(isFinished: false, questionNum: '0.0', total: 0, finishCount: 0, isExcess: false)); if (currentTab.questionNum == '0.0') { // 全部都批改完成了,默认tab设置为第一个 @@ -1025,8 +1051,7 @@ class _MarkingPapersState extends ConsumerState if (hasNext) { // 查找下一个tab return _currentTabs.firstWhere(firstWhereCall, - orElse: () => MarkingTextQuestionTab( - questionNum: '0.0', total: 0, finishCount: 0, isExcess: false, isFinished: false)); + orElse: () => MarkingTextQuestionTab(questionNum: '0.0', total: 0, finishCount: 0, isExcess: false, isFinished: false)); } if (tabQuestionNum != null) { @@ -1036,8 +1061,7 @@ class _MarkingPapersState extends ConsumerState if (resetting) { // 是否进行重置,继续阅卷 MarkingTextQuestionTab theMarking = _currentTabs.firstWhere(firstWhereCall, - orElse: () => MarkingTextQuestionTab( - questionNum: '0.0', total: 0, finishCount: 0, isExcess: false, isFinished: false)); + orElse: () => MarkingTextQuestionTab(questionNum: '0.0', total: 0, finishCount: 0, isExcess: false, isFinished: false)); if (theMarking.questionNum != '0.0') { currentTab = theMarking; } @@ -1108,6 +1132,7 @@ class _MarkingPapersState extends ConsumerState // bool flagInputKeyboardGuidePage = await FastData.getInstance().getInputKeyboardGuidePage(); // 引导页标志 try { theRequesting = true; + RestClient client = await getClient(); MarkingTextQuestionTab? temTab; bool isNormal = !widget.exceptional; @@ -1116,8 +1141,6 @@ class _MarkingPapersState extends ConsumerState MarkingTextQuestionTab? oldCurrentTab = _currentTab; temTab = await getTabsData(tabQuestionNum: tabQuestionNum, resetting: resetting); // 切换tag 返回对应的tag - print('当前选中类型:${temTab.questionNum}'); - MarkingTextQuestionTabParams theParam = tabParams.setQuestionNum( tab: temTab, locaQuestionNum: oldCurrentTab?.questionNum, @@ -1140,11 +1163,16 @@ class _MarkingPapersState extends ConsumerState setTimeOut(300, () => ToastUtils.showError('请求错诶,请重试')); throw Error(); } + // 重置批阅动作 + ref.read(doPaperBottomReviewMarksProvider.notifier).setState(ReviewMarksBottomBtnsEnum.DRAG); + // 重置批阅痕迹 + ref.read(drawMarkingProvider.notifier).setState(DrawMarkingVal([], [])); + currentQuestion = result.data; if (temTab != null) { result.data!.scoreInterval = temTab.scoreInterval ?? 1; - MarkingZoom? markingZoom = await UseZoomImageHistoryUtils.getZoomImageInfo( - widget.markingUserId.toString() + '-' + (currentQuestion?.questionNum.toString() ?? '')); + MarkingZoom? markingZoom = + await UseZoomImageHistoryUtils.getZoomImageInfo(widget.markingUserId.toString() + '-' + (currentQuestion?.questionNum.toString() ?? '')); if (markingZoom != null) { imageScale = double.parse(markingZoom.scale.toStringAsFixed(2)); imagePosition = Offset( @@ -1156,60 +1184,8 @@ class _MarkingPapersState extends ConsumerState imageScale = 1; imagePosition = Offset(0, 0); } - - /** */ - // int? thetypeNum = tabParams.type; - - // // || _currentTab?.total == 0 是因为第一次进入页面 total没有更新造成了total是0 - // if (tabQuestionNum != null || _currentTab?.total == 0) - // await getTabsData(updateCurrentTag: true); // 切换tag更新当前Tag数据 - - // currentQuestion?.scoreInterval = temTab.scoreInterval!; - // if (oldCurrentQuestion == null || - // (oldCurrentQuestion.markingUserDetailId != currentQuestion!.markingUserDetailId)) { - // if (theExamIndex == 0 || tabQuestionNum != null) { - // // 当前试题位置 条件意思代表:当批阅的试题大于等于当前任务并且题池中没有试题 或者 获取当前试题是否继续取题池中的试题(!tabParams.excessContinue) - // theExamIndex = (temTab.finishCount >= temTab.total && temTab.excessCount <= 0) || - // (!tabParams.excessContinue && currentQuestion!.isFinish) - // ? temTab.finishCount - // : temTab.finishCount + 1; // 当前试题的位置 - // } else if (thetypeNum != null) { - // thetypeNum == 0 ? ++theExamIndex : --theExamIndex; - // } - // } - // theExamIndex = currentQuestion?.currentIndex ?? 0; - - /**后端返回当前试题位置不需要前端再去计算位置了 不需要判断超量题和平均量了 */ - // currentQuestion!.setTotalCountAndCurrentIndex( - // temTab.isExcess - // ? (temTab.total > temTab.excessAvgCount ? temTab.total : temTab.excessAvgCount) - // : temTab.total, - // temTab.finishCount); // 设置下标 - - // currentQuestion?.totalCount = temTab.total; } - /** - toUpState(setState, () { - bool hasSub = currentQuestion!.subQuestionDetailList.isNotEmpty; - activeQuestIndex = 0; // 选中题号 - double fullScore; - double getScore; - if (hasSub) { - SubQuestions questions = currentQuestion!.subQuestionDetailList[activeQuestIndex]; - bool isFinish = questions.isFinish; - fullScore = questions.subQuestionScore; - getScore = isFinish ? questions.subQuestionGotScore! : 0; - } else { - fullScore = currentQuestion!.totalScore; - bool isFinish = currentQuestion!.isFinish; // 是否提交 - getScore = isFinish ? currentQuestion!.score! : 0; - } - questScore = getScore == 0 ? '' : getScore.toString(); // 打分值 - hasZeroPointFive = getScore >= fullScore || questScore.length > 1; // 是否是满分/是否已经包含小数 - - // inputKeyboardGuidePage = flagInputKeyboardGuidePage; // 引导页标志配置 - }, mounted);*/ bool hasSub = currentQuestion!.subQuestionDetailList.isNotEmpty; activeQuestIndex = 0; // 选中题号 double fullScore; @@ -1232,8 +1208,7 @@ class _MarkingPapersState extends ConsumerState if (firstComeIn) setTimeOut(2000, () => firstComeIn = false); if (ref.read(markingSubtopicSwitchingProvider.notifier).state != activeQuestIndex) { // 重置小题题号位置下标 - Future.delayed( - Duration.zero, () => ref.read(markingSubtopicSwitchingProvider.notifier).setVal(activeQuestIndex)); + Future.delayed(Duration.zero, () => ref.read(markingSubtopicSwitchingProvider.notifier).setVal(activeQuestIndex)); } if (currentQuestion != null && widget.markingtype == MarkingListType.EXCEPTIONAL) { BaseStructureResult res = await client.getMarkingQuestionsErrorInfo(currentQuestion!.id); @@ -1250,6 +1225,7 @@ class _MarkingPapersState extends ConsumerState } } + Future.delayed(Duration.zero, () => annotationTips = false); // getMarkingQuestionsErrorInfo return currentQuestion; } catch (e) { @@ -1435,8 +1411,7 @@ class _MarkingPapersState extends ConsumerState bool isNormal = !widget.exceptional; // 正常批题 bool notNextTest = data.nextId == 0; // 没有下一个试题了 // 下一题点击触发的方法 - var pressedNextTest = - notNextTest ? null : () => easyThrottle('TestQuestionSwitch', () => refresh(isNext: true)); + var pressedNextTest = notNextTest ? null : () => easyThrottle('TestQuestionSwitch', () => refresh(isNext: true)); bool notHasPreviousTest = data.prevId == 0; /** 无需根据当前位置判断 @@ -1501,8 +1476,7 @@ class _MarkingPapersState extends ConsumerState color: const Color.fromRGBO(148, 163, 182, 1), ), ), - if (_currentTab!.isExcess && - _currentTab!.finishCount > _currentTab!.total) + if (_currentTab!.isExcess && _currentTab!.finishCount > _currentTab!.total) Container( margin: EdgeInsets.only(left: 4.w), child: Row( @@ -1513,8 +1487,7 @@ class _MarkingPapersState extends ConsumerState color: const Color.fromRGBO(148, 163, 182, 1), ), quickText( - (_currentTab!.finishCount - _currentTab!.total) - .toString(), + (_currentTab!.finishCount - _currentTab!.total).toString(), size: isBroadwise ? 18.sp : 14.sp, color: Color.fromRGBO(251, 144, 84, 1), ), @@ -1540,19 +1513,16 @@ class _MarkingPapersState extends ConsumerState ), ), SizedBox(width: 8.w), - if (model.hideQuestionId && - model.screenDirection == ScreenDirection.HORIZONTAL_SCREEN) + if (model.hideQuestionId && model.screenDirection == ScreenDirection.HORIZONTAL_SCREEN) InkWell( onTap: () { - Clipboard.setData(ClipboardData(text: data.paperNum ?? '')) - .then((value) { + Clipboard.setData(ClipboardData(text: data.paperNum ?? '')).then((value) { ToastUtils.showSuccess('试卷编号已复制'); }); }, child: Row( children: [ - quickText('试卷编号: ', - size: 16.sp, color: const Color.fromRGBO(148, 163, 182, 1)), + quickText('试卷编号: ', size: 16.sp, color: const Color.fromRGBO(148, 163, 182, 1)), quickText( data.paperNum ?? '', size: 16.sp, @@ -1585,8 +1555,7 @@ class _MarkingPapersState extends ConsumerState color: const Color.fromRGBO(4, 201, 208, 1), ), ), - if (isBroadwise && widget.markingtype == MarkingListType.EXCEPTIONAL || - data.isException) + if (isBroadwise && widget.markingtype == MarkingListType.EXCEPTIONAL || data.isException) Container( padding: EdgeInsets.symmetric(horizontal: 2.w, vertical: 2.h), color: const Color.fromRGBO(245, 108, 108, 0.236), @@ -1630,18 +1599,14 @@ class _MarkingPapersState extends ConsumerState size: isBroadwise ? 15.sp : 12.sp, ), SizedBox(width: 1.w), - quickText('继续阅卷', - color: const Color.fromRGBO(80, 87, 103, 1), - size: isBroadwise ? 15.sp : 12.sp), + quickText('继续阅卷', color: const Color.fromRGBO(80, 87, 103, 1), size: isBroadwise ? 15.sp : 12.sp), ], ), ), SizedBox(width: 10.w), InkWell( onTap: () { - ref - .read(ratingProgressProvider.notifier) - .setState(EndDrawerViewEnum.REVIEW_RECORD); + ref.read(ratingProgressProvider.notifier).setState(EndDrawerViewEnum.REVIEW_RECORD); scaffoldKey.currentState?.openEndDrawer(); }, child: Row( @@ -1652,9 +1617,7 @@ class _MarkingPapersState extends ConsumerState size: isBroadwise ? 15.sp : 12.sp, ), SizedBox(width: 1.w), - quickText('阅卷记录', - color: const Color.fromRGBO(80, 87, 103, 1), - size: isBroadwise ? 15.sp : 12.sp), + quickText('阅卷记录', color: const Color.fromRGBO(80, 87, 103, 1), size: isBroadwise ? 15.sp : 12.sp), ], ), ), @@ -1666,9 +1629,7 @@ class _MarkingPapersState extends ConsumerState ), InkWell( onTap: () { - ref - .read(ratingProgressProvider.notifier) - .setState(EndDrawerViewEnum.RATING_PROGRESS); + ref.read(ratingProgressProvider.notifier).setState(EndDrawerViewEnum.RATING_PROGRESS); scaffoldKey.currentState?.openEndDrawer(); }, child: Row( @@ -1679,9 +1640,7 @@ class _MarkingPapersState extends ConsumerState size: isBroadwise ? 15.sp : 12.sp, ), SizedBox(width: 1.w), - quickText('评分进度', - color: const Color.fromRGBO(80, 87, 103, 1), - size: isBroadwise ? 15.sp : 12.sp), + quickText('评分进度', color: const Color.fromRGBO(80, 87, 103, 1), size: isBroadwise ? 15.sp : 12.sp), ], ), ), @@ -1690,8 +1649,7 @@ class _MarkingPapersState extends ConsumerState InkWell( onTap: () { if (data.isException) { - return ToastUtils.getFluttertoast( - context: context, msg: '当前试题已为异常题,无需重复提交'); + return ToastUtils.getFluttertoast(context: context, msg: '当前试题已为异常题,无需重复提交'); } toUpState(setState, () => showAbnormal = true, mounted); }, @@ -1817,17 +1775,14 @@ class _MarkingPapersState extends ConsumerState return InkWell( onTap: () { double? getScore = quest.subQuestionGotScore; - ref - .read(markingSubtopicSwitchingProvider.notifier) - .setVal(index); + ref.read(markingSubtopicSwitchingProvider.notifier).setVal(index); toUpState(setState, () { activeQuestIndex = index; // TODO 这是什么 // pictureOverviewKey.currentState?.jumpToPage(activeQuestIndex); questScore = getScore?.toString() ?? ''; - hasZeroPointFive = - questScore != '' && getScore! >= quest.subQuestionScore; + hasZeroPointFive = questScore != '' && getScore! >= quest.subQuestionScore; }, mounted); }, child: Container( @@ -1848,8 +1803,7 @@ class _MarkingPapersState extends ConsumerState alignment: const FractionalOffset(0.5, 1.78), children: [ Container( - padding: EdgeInsets.symmetric( - horizontal: 4.w, vertical: 8.h), + padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 8.h), alignment: Alignment.center, decoration: BoxDecoration( color: !activeIndex @@ -1871,9 +1825,7 @@ class _MarkingPapersState extends ConsumerState maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( - color: activeIndex - ? Colors.white - : const Color.fromRGBO(80, 87, 103, 1), + color: activeIndex ? Colors.white : const Color.fromRGBO(80, 87, 103, 1), fontSize: 13.sp, fontWeight: FontWeight.w500, ), @@ -1884,22 +1836,18 @@ class _MarkingPapersState extends ConsumerState child: quickText( '(${getDoubleRemoveZero(data.subQuestionDetailList[index].subQuestionScore)})', size: 11.sp, - color: activeIndex - ? Colors.white - : const Color.fromRGBO( - 80, 87, 103, 1)), + color: + activeIndex ? Colors.white : const Color.fromRGBO(80, 87, 103, 1)), ) ], )), if (activeIndex) Icon(Icons.arrow_drop_down_outlined, - color: const Color.fromRGBO(46, 91, 255, 1), - size: 20.sp) + color: const Color.fromRGBO(46, 91, 255, 1), size: 20.sp) ], ), Container( - padding: - EdgeInsets.symmetric(horizontal: 4.w, vertical: 8.h), + padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 8.h), alignment: Alignment.center, child: Text( getDoubleRemoveZero(quest.subQuestionGotScore, '请评分'), @@ -1945,8 +1893,7 @@ class _MarkingPapersState extends ConsumerState ), child: Text( '>>', - style: - TextStyle(color: Theme.of(context).primaryColor, fontSize: 12.sp), + style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 12.sp), ), )), onHorizontalDragEnd: (detail) { @@ -1962,9 +1909,10 @@ class _MarkingPapersState extends ConsumerState children: [ // 试卷 Container( - margin: EdgeInsets.only(top: 6.h), + // margin: EdgeInsets.only(top: 6.h), padding: EdgeInsets.symmetric(horizontal: 1.w), child: PictureOverview( + data: data, questionNum: data.questionNum, markingUserId: widget.markingUserId, key: pictureOverviewKey, @@ -1973,21 +1921,14 @@ class _MarkingPapersState extends ConsumerState annotationsFlag: annotationSwitch, commentImageMap: data.commentImageUrlMap, testQuestionNumber: widget.markingUserId.toString() + '-' + data.questionNum, - imageItems: data.getImageData(), + imageItems: data.studentAnswerList, + callAnnotationTips: () { + // 批注记录被更改 + annotationTips = true; + }, ), ), - // 批注开关 暂时注释 - // if (!showSetingFlag && - // !widget.exceptional && - // currentQuestion != null && - // annotationsFlag && - // !showAbnormal && - // !showStandardAnswer) - // Positioned( - // left: 0, - // bottom: 0, - // child: BottomAnnotationSwitch(maxWidth: maxWidth - 1.5.w), - // ), + // 大题评分展示框 if (!hasSubtopic) Positioned( @@ -2000,10 +1941,7 @@ class _MarkingPapersState extends ConsumerState color: data.isException ? Colors.red : const Color.fromRGBO(46, 91, 255, 1), child: Container( padding: EdgeInsets.symmetric( - horizontal: (data.score == null || - (getDoubleRemoveZero(data.score ?? 0)).length >= 2) - ? 4.w - : 12.w, + horizontal: (data.score == null || (getDoubleRemoveZero(data.score ?? 0)).length >= 2) ? 4.w : 12.w, vertical: 6.h), alignment: Alignment.center, child: Text( @@ -2029,15 +1967,11 @@ class _MarkingPapersState extends ConsumerState color: data.isException ? Colors.red : const Color.fromRGBO(46, 91, 255, 1), child: Container( padding: EdgeInsets.symmetric( - horizontal: (data.score == null || - (getDoubleRemoveZero(data.score ?? 0)).length >= 2) - ? 4.w - : 12.w, + horizontal: (data.score == null || (getDoubleRemoveZero(data.score ?? 0)).length >= 2) ? 4.w : 12.w, vertical: 6.h), alignment: Alignment.center, child: Text( - getDoubleRemoveZero(data.score, - '请评分,满分${getDoubleRemoveZero(currentQuestion?.totalScore)}'), + getDoubleRemoveZero(data.score, '请评分,满分${getDoubleRemoveZero(currentQuestion?.totalScore)}'), style: TextStyle( fontSize: 18.sp, color: const Color.fromRGBO(46, 91, 255, 1), @@ -2047,26 +1981,23 @@ class _MarkingPapersState extends ConsumerState ), ), // 上一题 按钮 - if (widget.markingtype != MarkingListType.EXCEPTIONAL && - !_theOldAnnotationGraffiti && - !notHasPreviousTest) + if (widget.markingtype != MarkingListType.EXCEPTIONAL && !_theOldAnnotationGraffiti && !notHasPreviousTest) Positioned( left: 3.w, top: ScreenUtil().setHeight(MediaQuery.of(context).size.height) / 2 * 0.87, child: FloatingActionButton( tooltip: '前往上一题', - backgroundColor: notHasPreviousTest - ? const Color.fromRGBO(24, 32, 32, 0.04) - : const Color.fromRGBO(24, 32, 32, 0.1), + backgroundColor: + notHasPreviousTest ? const Color.fromRGBO(24, 32, 32, 0.04) : const Color.fromRGBO(24, 32, 32, 0.1), focusColor: Theme.of(context).primaryColor, elevation: 0, - onPressed: notHasPreviousTest - ? null - : () => easyThrottle('TestQuestionSwitch', () => refresh()), + onPressed: notHasPreviousTest ? null : () => easyThrottle('TestQuestionSwitch', () => refresh()), child: const Icon(Icons.arrow_back_ios, color: Colors.white), heroTag: 'other', ), ), + // 底部批阅痕迹按钮 + DoPaperBottomReviewMarks(), // 下一题 按钮 if (!_theOldAnnotationGraffiti && pressedNextTest != null && isNormal) Positioned( @@ -2075,9 +2006,8 @@ class _MarkingPapersState extends ConsumerState child: FloatingActionButton( elevation: 0, tooltip: '点击前往下一题', - backgroundColor: notNextTest - ? const Color.fromRGBO(24, 32, 32, 0.04) - : const Color.fromRGBO(24, 32, 32, 0.1), + backgroundColor: + notNextTest ? const Color.fromRGBO(24, 32, 32, 0.04) : const Color.fromRGBO(24, 32, 32, 0.1), foregroundColor: Colors.white, onPressed: pressedNextTest, child: const Icon(Icons.arrow_forward_ios, color: Colors.white), @@ -2169,8 +2099,7 @@ class _MarkingPapersState extends ConsumerState // 异常显示区域 $AbnormalBox( - businessHandle: (bool flag, String? reasonKey, String? otherReasons) => - initiateException(flag, reasonKey, otherReasons), + businessHandle: (bool flag, String? reasonKey, String? otherReasons) => initiateException(flag, reasonKey, otherReasons), isBroadwise: isBroadwise, showAbnormal: !showAbnormal, ), @@ -2202,8 +2131,7 @@ class _MarkingPapersState extends ConsumerState eventFireSub(model: SwitchKeyboardToReloadImages(true)); }); ref.read(annotationGraffitiSwitchProvider.notifier).setSwitch(false); - ToastUtils.showSuccess(newOpenAuxiliary ? '开启双栏打分' : '关闭双栏打分', - duration: const Duration(milliseconds: 300)); + ToastUtils.showSuccess(newOpenAuxiliary ? '开启双栏打分' : '关闭双栏打分', duration: const Duration(milliseconds: 300)); }); }, // 触发总线事件 @@ -2212,8 +2140,7 @@ class _MarkingPapersState extends ConsumerState width: isBroadwise ? 18.w : 42.w, alignment: Alignment.center, decoration: BoxDecoration( - borderRadius: - BorderRadius.only(topLeft: Radius.circular(8.w), bottomLeft: Radius.circular(8.w)), + borderRadius: BorderRadius.only(topLeft: Radius.circular(8.w), bottomLeft: Radius.circular(8.w)), color: Theme.of(context).primaryColor, ), child: Row( @@ -2247,8 +2174,7 @@ class _MarkingPapersState extends ConsumerState tabName: _currentTab?.questionNum ?? '', tabs: _currentTabs, direction: keyboardModel?.screenDirection ?? ScreenDirection.HORIZONTAL_SCREEN, - call: (int detailId, String selectedQuesiontNum) => - refresh(theId: detailId, theQuestionNum: selectedQuesiontNum), + call: (int detailId, String selectedQuesiontNum) => refresh(theId: detailId, theQuestionNum: selectedQuesiontNum), markingUserId: widget.markingUserId, questionNum: _currentTab?.questionNum, ), @@ -2429,10 +2355,8 @@ Widget $abnormalBox( child: TextField( controller: controller, textInputAction: TextInputAction.next, - onEditingComplete: () => easyThrottle( - 'Abnormal_submission_confirmation_button', - () => _useAbnormal.submit(context, controller, - (reasonType, reason) => businessHandle(true, reasonType, reason))), + onEditingComplete: () => easyThrottle('Abnormal_submission_confirmation_button', + () => _useAbnormal.submit(context, controller, (reasonType, reason) => businessHandle(true, reasonType, reason))), maxLines: 15, keyboardType: TextInputType.multiline, decoration: InputDecoration( @@ -2452,8 +2376,7 @@ Widget $abnormalBox( mainAxisAlignment: MainAxisAlignment.center, children: [ InkWell( - onTap: () => easyThrottle( - 'Abnormal_submission_confirmation_button', () => businessHandle(false, null, null)), + onTap: () => easyThrottle('Abnormal_submission_confirmation_button', () => businessHandle(false, null, null)), child: Container( padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), margin: EdgeInsets.only(right: 16.w), @@ -2483,8 +2406,7 @@ Widget $abnormalBox( InkWell( onTap: () => easyThrottle( 'Abnormal_submission_confirmation_button', - () => _useAbnormal.submit( - context, controller, (reasonType, reason) => businessHandle(true, reasonType, reason)), + () => _useAbnormal.submit(context, controller, (reasonType, reason) => businessHandle(true, reasonType, reason)), ), // () { @@ -2582,8 +2504,7 @@ class StandardAnswerRegion extends StatelessWidget { /// 异常题提交异常信息 @hwidget -Widget $abnormalQuestionInfo( - {required bool hasSubtopic, required bool isBroadwise, required bool show, double? score, ExceptionInfo? info}) { +Widget $abnormalQuestionInfo({required bool hasSubtopic, required bool isBroadwise, required bool show, double? score, ExceptionInfo? info}) { final _useFlagAnimation = useState(true); AnimationController _useAnimationHorizontal = useAnimationController( initialValue: 300, @@ -2675,14 +2596,12 @@ Widget $abnormalQuestionInfo( Row( children: [ if ((info?.studentName.length ?? 0) > 0) - quickText('考生:${info!.studentName}', - color: const Color.fromRGBO(104, 104, 113, 1), size: 14.sp + quickText('考生:${info!.studentName}', color: const Color.fromRGBO(104, 104, 113, 1), size: 14.sp // overflow: TextOverflow.clip, ), SizedBox(width: 3.w), if ((info?.studentExamNum.length ?? 0) > 0) - quickText('考号:${info!.studentExamNum}', - color: const Color.fromRGBO(104, 104, 113, 1), size: 14.sp + quickText('考号:${info!.studentExamNum}', color: const Color.fromRGBO(104, 104, 113, 1), size: 14.sp // overflow: TextOverflow.clip, ), ], @@ -2716,8 +2635,7 @@ Widget $abnormalQuestionInfo( /// 冲裁题仲裁历史得分信息 @hwidget -Widget $arbitrationQuestionInfo( - {required bool show, required bool isBroadwise, required bool hasSubtopic, required List data}) { +Widget $arbitrationQuestionInfo({required bool show, required bool isBroadwise, required bool hasSubtopic, required List data}) { final _useFlagAnimation = useState(true); AnimationController _useAnimationHorizontal = useAnimationController( initialValue: 300, diff --git a/marking_app/lib/pages/marking/provider/do_paper_bottom_review_marks_provider.dart b/marking_app/lib/pages/marking/provider/do_paper_bottom_review_marks_provider.dart new file mode 100644 index 0000000..60109f0 --- /dev/null +++ b/marking_app/lib/pages/marking/provider/do_paper_bottom_review_marks_provider.dart @@ -0,0 +1,13 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/model/enum/review_marks_bottom_btns_enum.dart'; + +// 底部按钮同步数据 +final doPaperBottomReviewMarksProvider = StateNotifierProvider( + (ref) => DoPaperBottomReviewMarksHandle(ReviewMarksBottomBtnsEnum.DRAG), +); + +class DoPaperBottomReviewMarksHandle extends StateNotifier { + DoPaperBottomReviewMarksHandle(ReviewMarksBottomBtnsEnum val) : super(val); + + void setState(ReviewMarksBottomBtnsEnum val) => state = val; +} diff --git a/marking_app/lib/pages/marking/provider/draw_marking_provider.dart b/marking_app/lib/pages/marking/provider/draw_marking_provider.dart new file mode 100644 index 0000000..fdf95be --- /dev/null +++ b/marking_app/lib/pages/marking/provider/draw_marking_provider.dart @@ -0,0 +1,33 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-14 18:16:06 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-08-01 16:17:33 + * @FilePath: \marking_app\lib\provider\user_provider.dart + * @Description: APP上传文件状态 + */ + +import 'dart:ui'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../../../components/PictureOverview.dart'; + +// 批阅痕迹同步 +final drawMarkingProvider = + StateNotifierProvider((ref) => DrawMarkingProviderHandle(DrawMarkingVal([], []))); + +class DrawMarkingProviderHandle extends StateNotifier { + DrawMarkingProviderHandle(DrawMarkingVal val) : super(val); + + void setState(DrawMarkingVal val) { + state = val; + } +} + +class DrawMarkingVal { + List data; + List offsets; + + DrawMarkingVal(this.data, this.offsets); +} diff --git a/marking_app/lib/provider/upload_file_provider.dart b/marking_app/lib/provider/upload_file_provider.dart index 6b85b02..044e6de 100644 --- a/marking_app/lib/provider/upload_file_provider.dart +++ b/marking_app/lib/provider/upload_file_provider.dart @@ -22,8 +22,8 @@ import 'package:marking_app/utils/request/rest_client.dart'; // API -final uploadFileProvider = StateNotifierProvider( - (ref) => UploadFileProviderHandle(UploadImgSecretKey())); +final uploadFileProvider = + StateNotifierProvider((ref) => UploadFileProviderHandle(UploadImgSecretKey())); class UploadFileProviderHandle extends StateNotifier with CommonMixin { // Minio? _minio; @@ -109,24 +109,35 @@ class UploadFileProviderHandle extends StateNotifier with Co Future getUploadFileConfig(UploadFileInterfaceConfig uploadConfig, File fileData) async { Dio dio = Dio(); + dio.options.contentType = null; try { late Response response; final bytes = await fileData.readAsBytes(); if (uploadConfig.uploadMethod == "PUT") { - response = await dio.put(uploadConfig.uploadUri!, - data: Stream.fromIterable(bytes.map((e) => [e])), - options: Options(headers: { + response = await dio.put( + uploadConfig.uploadUri!, + data: Stream.fromIterable(bytes.map((e) => [e])), + options: Options( + contentType: null, + headers: { Headers.contentLengthHeader: bytes.length, // Set the content-length. - HttpHeaders.contentTypeHeader: 'image/png', - })); + // HttpHeaders.contentTypeHeader: 'image/png', + }, + ), + ); } else if (uploadConfig.uploadMethod == "POST") { - response = await dio.post(uploadConfig.uploadUri!, - data: Stream.fromIterable(bytes.map((e) => [e])), - options: Options(headers: { + response = await dio.post( + uploadConfig.uploadUri!, + data: Stream.fromIterable(bytes.map((e) => [e])), + options: Options( + contentType: null, + headers: { Headers.contentLengthHeader: bytes.length, // Set the content-length. - HttpHeaders.contentTypeHeader: 'image/png', - })); + // HttpHeaders.contentTypeHeader: 'image/png', + }, + ), + ); } print('文件上传成功:${response.data}'); if ([200, 201, 204].contains(response.statusCode)) { diff --git a/marking_app/lib/utils/app_upgrade/DownloadApk.dart b/marking_app/lib/utils/app_upgrade/DownloadApk.dart index 2aeda91..1c09368 100644 --- a/marking_app/lib/utils/app_upgrade/DownloadApk.dart +++ b/marking_app/lib/utils/app_upgrade/DownloadApk.dart @@ -9,15 +9,16 @@ import 'dart:io'; import 'package:dio/dio.dart'; -import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:install_plugin/install_plugin.dart'; import 'package:marking_app/provider/upgrade_provider.dart'; -// import 'package:install_plugin_v2/install_plugin_v2.dart'; import 'package:marking_app/utils/app_upgrade/model/UpdateAppEvent.dart'; import 'package:marking_app/utils/index.dart'; -import 'package:permission_handler/permission_handler.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:app_installer/app_installer.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import 'UpgradePermission.dart'; class DownloadApk { /// 下载安卓更新包 @@ -58,26 +59,45 @@ class DownloadApk { debugPrint('make sure the apk file is set'); return false; } + try { + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text("未知应用安装权限提示", + style: TextStyle( + fontWeight: FontWeight.bold, + )), + content: const Text("请注意:更新时若出现需要同意“安装未知应用权限”,请同意!!!"), + actions: [ + MaterialButton( + color: Theme.of(context).primaryColor, + child: const Text("我已知晓", style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ); + }, + ); + await AppInstaller.installApk(_apkFilePath); // 安装APK + // 如果2秒还没有进入安装引导页面 一律视为更新失败,弹出其他方式更新 + await setTimeOut(5000, () async { + try { + // 其他方式下载 + // await SystemNavigator.pop(); // 退出APP + var options = ['应用市场更新APP', '浏览器下载并安装APP']; + var uri = Uri.parse('market://details?id=com.example.marking_app'); // 应用市场URI + if (!await canLaunchUrl(uri)) options.removeAt(0); // 如果不能打开应用市场 就屏蔽掉 这个安装方式 + String? option = await UpgradePermission.showCustomModalBottomSheet(context, options); + if (option == '应用市场更新APP') await launchUrl(uri); + if (option == '浏览器下载并安装APP') await launchUrl(Uri.parse(event.link)); + } catch (e) {} + }); - Map statuses = await [Permission.storage].request(); - - if (statuses[Permission.storage]!.isGranted) { - // String? result = await InstallPlugin.installApk(_apkFilePath, appId: event.packageName).catchError((error) { - // debugPrint('install apk error: $error'); - // }); - // debugPrint('install apk $result'); - // ToastUtils.getFluttertoast(context: context, msg: 'install apk $result'); - try { - final result = await InstallPlugin.installApk(_apkFilePath, appId: event.packageName); - print('这是是执行安装的程序:' + result.runtimeType.toString()); - if (result['isSuccess'] ?? false) { - ToastUtils.showSuccess('install apk $result'); - RestartWidget.restartApp(context); // 安装成功 重启APP - return true; - } - } catch (e) {} - } else { - debugPrint('Permission request fail!'); + print('安装执行完成了..............0.0'); + } catch (e) { + print('安装进入报错....'); + print(e); } return false; @@ -87,7 +107,7 @@ class DownloadApk { static void showDownloadProgress(context, num received, num total, WidgetRef ref) { if (total != -1) { double progress = double.parse((received / total).toStringAsFixed(2)); - debugPrint('下载进度$progress'); + // debugPrint('下载进度$progress'); ref.read(upgradeProvider.notifier).setVal(progress); } } diff --git a/marking_app/lib/utils/app_upgrade/UpdateDialog.dart b/marking_app/lib/utils/app_upgrade/UpdateDialog.dart index f16a0f9..6ba8e40 100644 --- a/marking_app/lib/utils/app_upgrade/UpdateDialog.dart +++ b/marking_app/lib/utils/app_upgrade/UpdateDialog.dart @@ -14,8 +14,6 @@ * @LastEditors: Please set LastEditors * @LastEditTime: 2021-01-12 15:08:43 */ -import 'dart:async'; -import 'package:clipboard/clipboard.dart'; import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:marking_app/provider/upgrade_provider.dart'; @@ -27,6 +25,7 @@ import 'package:marking_app/utils/my_text.dart'; import 'package:percent_indicator/linear_percent_indicator.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher_string.dart'; class UpdateDialog extends Dialog { @@ -145,6 +144,19 @@ class DownloadButton extends ConsumerWidget { final String deviceInfo; const DownloadButton(this.updateAppEvent, {required this.deviceInfo, Key? key}) : super(key: key); + // 打开浏览器或者对应的对应市场进行下载 + Future toLaunch(UpdateAppEvent data) async { + var uri = Uri.parse('market://details?id=com.example.marking_app'); + if (await canLaunchUrl(uri)) { + // 跳进对应的应用市场进行更新操作 + return await launchUrl(uri); + } + // 无法进入应用市场就打开浏览器进行下载 + uri = Uri.parse(data.link); + if (await canLaunchUrl(uri)) return await launchUrl(uri); + return false; + } + @override Widget build(BuildContext context, WidgetRef ref) { final count = ref.watch(upgradeProvider); @@ -161,12 +173,12 @@ class DownloadButton extends ConsumerWidget { ]), ), child: MaterialButton( - onPressed: () => easyThrottle('DownloadButton_App_Upgrade', () async { + onPressed: () => easyThrottle('DownloadButton_App_Upgrade', duration: const Duration(milliseconds: 1000), () async { if (deviceInfo == "android" && updateAppEvent.equipment == Equipment.android) { - bool flag = await UpgradePermission(updateAppEvent.deviceInfo).checkPermission(); + // 权限检查 判断是否有读写内存的权限 + bool flag = await UpgradePermission(updateAppEvent.deviceInfo).checkPermission(context, updateAppEvent); if (flag) { flag = await DownloadApk.installApk(context, updateAppEvent, ref); - if (!flag) { print('执行到了重置更新按钮的地方....'); ref.read(upgradeProvider.notifier).clean(); // 更新失败重置 更新按钮 @@ -176,8 +188,8 @@ class DownloadButton extends ConsumerWidget { } return; } - await FlutterClipboard.copy(updateAppEvent.link); - setTimeOut(1000, () => ToastUtils.showInfo('下载链接已经复制到设备,可前往浏览器下载安装')); + // await FlutterClipboard.copy(updateAppEvent.link); + // setTimeOut(1000, () => ToastUtils.showInfo('下载链接已经复制到设备,可前往浏览器下载安装')); } else if (deviceInfo == "ios" && updateAppEvent.equipment == Equipment.ios) { try { await launchUrlString(updateAppEvent.link); diff --git a/marking_app/lib/utils/app_upgrade/UpgradePermission.dart b/marking_app/lib/utils/app_upgrade/UpgradePermission.dart index 1337eca..eaa6dda 100644 --- a/marking_app/lib/utils/app_upgrade/UpgradePermission.dart +++ b/marking_app/lib/utils/app_upgrade/UpgradePermission.dart @@ -6,42 +6,137 @@ * @LastEditors: wangyang 1147192855@qq.com * @LastEditTime: 2022-08-01 14:08:57 */ -import 'package:marking_app/utils/index.dart'; +import 'package:app_installer/app_installer.dart'; +import 'package:flutter/material.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import 'model/UpdateAppEvent.dart'; class UpgradePermission { final String _flatform; const UpgradePermission(this._flatform); /// 检查是否有权限,用于安卓 - Future checkPermission() async { - // if (_flatform == 'android') { + /// noExecutions 执行次数 + Future checkPermission(BuildContext context, UpdateAppEvent updateAppEvent, [int? noExecutions]) async { + noExecutions ??= 1; + if (_flatform != 'android') return true; // 非安卓 + var status = await Permission.storage.request(); + if (status.isGranted) return true; - // return await Permission.storage.request().isGranted; - // } else { - // return true; - // } + if (status.isDenied) { + // 普通拒绝 可以再进行提示 - if (_flatform == 'android') { - final status = await Permission.storage.status; - if (status != PermissionStatus.granted) { - final result = await Permission.storage.request(); - if (result == PermissionStatus.granted) { - return true; - } - if (status == PermissionStatus.denied) { - ToastUtils.showError('拒绝了保存安装权限'); - } + await showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: const Text("权限提示"), + content: const Text("无法获取存储权限,请同意获取设备存储权限"), + actions: [ + MaterialButton( + color: Theme.of(context).primaryColor, + child: const Text("同意", style: TextStyle(color: Colors.white)), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ); + }, + ); - if (status == PermissionStatus.permanentlyDenied) { - ToastUtils.showError('用户永久拒绝保存安装权限,请前往设置权限'); - } - } else { - return true; - } - } else { - return true; + if (noExecutions < 2) return checkPermission(context, updateAppEvent, ++noExecutions); + // 执行次数大于2次,就不再询问直接打开设置权限页面(防止某些机型不会弹起权限询问交互弹框) } + + // 拒绝并不再提示 + bool? res = await showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: const Text("权限提示"), + content: const Text("储存权限被永久拒绝,并且不再提示。请前往设置页面同意储存权限"), + actions: [ + MaterialButton( + color: Colors.green.shade900, + child: const Text("其它方式更新", style: TextStyle(color: Colors.white)), + onPressed: () => Navigator.of(context).pop(false), + ), + MaterialButton( + color: Theme.of(context).primaryColor, + child: const Text("前往设置", style: TextStyle(color: Colors.white)), + onPressed: () => Navigator.of(context).pop(true), + ), + ], + ); + }, + ); + if (res == null || !res) { + // 其他方式下载 + // await SystemNavigator.pop(); // 退出APP + var options = ['应用市场更新APP', '浏览器下载并安装APP']; + var uri = Uri.parse('market://details?id=com.example.marking_app'); // 应用市场URI + // if (!await canLaunchUrl(uri)) options.removeAt(0); // 如果不能打开应用市场 就屏蔽掉 这个安装方式 + String? option = await showCustomModalBottomSheet(context, options); + if (option == '应用市场更新APP') { + if (await canLaunchUrl(uri)) + await launchUrl(uri); + else + await AppInstaller.goStore('com.example.marking_app', 'iOSAppId'); + } + if (option == '浏览器下载并安装APP') await launchUrl(Uri.parse(updateAppEvent.link)); + } else + await openAppSettings(); return false; } + + // 其他方式下载选择 + static Future showCustomModalBottomSheet(context, List options) async { + return showModalBottomSheet( + backgroundColor: Colors.transparent, + isScrollControlled: true, + context: context, + builder: (BuildContext context) { + return Container( + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: const Radius.circular(20.0), + topRight: const Radius.circular(20.0), + ), + ), + height: MediaQuery.of(context).size.height / 4.0, + child: Column(children: [ + SizedBox( + height: 50, + child: Stack( + textDirection: TextDirection.rtl, + children: [ + Center(child: Text('选择其它方式更新APP', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16.0))), + IconButton(icon: Icon(Icons.close), onPressed: () => Navigator.of(context).pop()), + ], + ), + ), + Divider(height: 1.0), + Expanded( + child: ListView.builder( + itemCount: options.length, + itemBuilder: (BuildContext context, int index) { + var name = options[index]; + return ListTile( + title: Text(name), + trailing: Icon(name.contains('浏览器') ? Icons.browser_updated_outlined : Icons.local_grocery_store_outlined), + onTap: () => Navigator.of(context).pop(name), + ); + }, + ), + ), + ]), + ); + }, + ); + } } diff --git a/marking_app/lib/utils/request/rest_client.dart b/marking_app/lib/utils/request/rest_client.dart index ac772d0..a6a81d8 100644 --- a/marking_app/lib/utils/request/rest_client.dart +++ b/marking_app/lib/utils/request/rest_client.dart @@ -190,6 +190,10 @@ abstract class RestClient { @the_retrofit.GET("/api/marking/rating-info") Future>> getMarkingRatingInfo(@the_retrofit.Query("markingUserId") int id); + // 阅卷 => 上传图片请求参数 + @the_retrofit.GET("/api/Upload") + Future> getMarkingUploadFile(@the_retrofit.Queries() UploadFileInterfaceConfigParams params); + // ------------------------------------------ 作业 ------------------------------------------ // 作业 => 作业列表 diff --git a/marking_app/pubspec.yaml b/marking_app/pubspec.yaml index 0c52cc6..f457dc2 100644 --- a/marking_app/pubspec.yaml +++ b/marking_app/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.0.106 +version: 1.0.107+2 environment: sdk: ">=2.17.1 <3.0.0" @@ -114,6 +114,8 @@ dependencies: flutter_staggered_grid_view: ^0.6.2 # 饼图 flutter_echart: ^2.0.0 + app_installer: ^1.1.0 + dev_dependencies: